From e1226e6ae5a75388fe5d1a0e1ed008f80fee25ed Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 17 May 2026 14:21:25 +0800
Subject: [PATCH] Release Apptag 6.0.5 build 610
---
Apptag/ApptagApp.swift | 15
Apptag/Localization/cs.json | 22
Apptag/Localization/no.json | 22
Apptag/AppGridItem.swift | 103 +++
Apptag/Localization/zh-Hant.json | 22
Apptag/Localization/ja.json | 22
Apptag/Info.plist | 4
Apptag/Localization/th.json | 22
Apptag/Localization/nl.json | 22
Apptag/Localization/sr-Cyrl.json | 22
Apptag/Localization/zh-Hans.json | 22
Apptag/Localization/de.json | 22
Apptag/Localization/ms.json | 22
Apptag/Localization/nn.json | 22
Apptag/Localization/pt-BR.json | 22
Apptag/Localization/es.json | 22
Apptag/Localization/tr.json | 22
AppStore_Submission.md | 51 -
Apptag/Localization/en.json | 22
Apptag/Localization/nb.json | 22
Apptag/Localization/ar.json | 22
Apptag/Localization/vi.json | 22
Apptag/Localization/fr.json | 22
Apptag/Localization/id.json | 22
Apptag/Localization/da.json | 22
CHANGELOG.md | 31 +
Apptag/Localization/uk.json | 22
Apptag/Localization/ar-Najdi.json | 22
Apptag/TagGroupView.swift | 4
Apptag/Localization/ko.json | 22
Apptag/ContentView.swift | 593 ++++++++++++++++++-
Apptag/DataLayer.swift | 273 ++++++++
Apptag/Localization/it.json | 22
Apptag/Localization/sv.json | 22
Apptag/Localization/ro.json | 22
Apptag/Localization/pl.json | 22
Apptag/Localization/ru.json | 22
37 files changed, 1,578 insertions(+), 134 deletions(-)
diff --git a/AppStore_Submission.md b/AppStore_Submission.md
index 8681c7b..5d9a815 100644
--- a/AppStore_Submission.md
+++ b/AppStore_Submission.md
@@ -1,6 +1,6 @@
# Apptag — Mac App Store 提审资料
-> 版本: 5.7.8 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-15
+> 版本: 6.0.5 | Bundle ID: com.apptag.launcher | 更新日期: 2026-05-17
---
@@ -11,8 +11,8 @@
| **App 名称** | Apptag |
| **副标题** | Tag-Based App Launcher |
| **Bundle ID** | com.apptag.launcher |
-| **版本号** | 5.7.8 |
-| **Build 号** | 583 |
+| **版本号** | 6.0.5 |
+| **Build 号** | 610 |
| **SKU** | apptag-mac-001 |
| **主要类别** | Utilities (工具) |
| **次要类别** | Productivity (效率) |
@@ -187,36 +187,23 @@
## 8. What's New (本次版本更新说明)
```
-Apptag 5.7.8
+Apptag 6.0.5
-- Fixed duplicate overlay keyboard event monitors that could accumulate after repeatedly opening TagLauncher
-- Removed obsolete tag reorder fallback handlers that could interfere with click handling after drag sorting
-- Reduced unnecessary Dock refreshes when unrelated preferences change
-- Made the menu bar icon mandatory and removed the Show Menu Bar Icon preference across all localizations
-- Removed unused macOS app-menu items and added a localized Show App List command with symbol-only shortcut glyphs
-- Replaced the default macOS Services/Hide menu groups and made the menu bar status item use a fixed visible icon slot
-- Replaced the menu bar SF Symbol with a code-drawn template icon so the status item remains visible even if symbol rendering fails
-- Improved the General preferences layout for long localized text by using two-column style choices and aligned setting rows
-- Refined the app-list style chooser so Flat spans the first row and the remaining four styles form a balanced 2x2 grid
-- Reduced the app-list style option height and added compact visual glyphs for flat, masonry, and grid layouts
-- Added a symbol-only shortcut hint on the Language, Data, and About preference tabs for Option + Shift + Space
-- Fixed the menu bar icon visibility issue
-- Fixed Preferences placement when opened above TagLauncher from a fullscreen app Space
-- Fixed tag navigation reorder mode so clicking elsewhere exits the shaking state
-- Expanded localization support to 29 language variants
-- Updated the Language preferences tab to a two-column layout for better readability
-- Added long-press drag sorting for tag navigation in normal app-list views
-- Tag sorting now works from top, left, or right tag navigation positions across all 5 app-list styles
-- Tag reorder animations now update both the tag navigation and matching app containers
-- Added automatic migration for legacy non-sandbox tag databases into the App Store sandbox container
-- Added a dedicated Language tab in Preferences with live language switching
-- Preferences now opens centered above the active TagLauncher overlay, including multi-display setups
-- Improved tag reordering in edit mode with live visual feedback and synchronized group layout updates
-- Renamed backup actions to Export/Import Categories & Layout across all supported languages
-- Added Mac App Store sandbox entitlements and App Store build validation
-- Hid Launch at Login controls in sandboxed App Store builds
-- Removed deprecated app launch API usage
-- Version kept at 5.7.8, Build updated to 583
+- Added an Uncommon app marker for apps you are still learning, with optional hover notes
+- New apps can be marked as Uncommon automatically and are cleared after enough launches from TagLauncher
+- Added local note editing for Uncommon apps; notes stay in the exported JSON even if the marker is removed later
+- Added an Uncommon bubble toggle in the Data preferences tab, enabled by default
+- Reworked batch category editing into clear Add Tags and Remove Tags modes
+- Fixed batch editing so selected apps no longer inherit unrelated tags from each other
+- Added clear confirmation feedback showing the real number of tag links added or removed
+- Improved Remove Tags mode with red delete indicators and safer label truncation
+- Fixed edit-sidebar layout so long localized hints no longer stretch the page
+- Removed the edit-page center title and gave more room to the action hint and confirmation button
+- Improved shortcut hints in Language, Data, and About settings pages using symbol-only key graphics
+- Fixed a crash caused by toolbar hint layout constraints in the edit screen
+- Fixed Safari and other modern macOS system apps being missed when they live behind Cryptex system paths
+- Kept duplicate app icons out by resolving symlinks for scanning deduplication
+- Version updated to 6.0.5, Build updated to 610
```
---
diff --git a/Apptag/AppGridItem.swift b/Apptag/AppGridItem.swift
index 3b4cc5a..9fa97e2 100644
--- a/Apptag/AppGridItem.swift
+++ b/Apptag/AppGridItem.swift
@@ -10,10 +10,14 @@
var sourceTag: String? = nil
var dragModeActive: Bool = false
var onDragModeChange: ((Bool) -> Void)? = nil
+ var onBubbleHover: ((AppInfo, CGRect, Bool) -> Void)? = nil
+ var onEditNote: ((AppInfo, CGRect) -> Void)? = nil
let onSelect: () -> Void
@State private var isHovered = false
@State private var wiggle = false
+ @State private var globalFrame: CGRect = .zero
+ @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = true
static let hoverScale: CGFloat = 1.22
static let labelHeight: CGFloat = 14
@@ -62,9 +66,28 @@
.padding(.vertical, 8)
.padding(.horizontal, 4)
.frame(width: Self.stableWidth(iconSize: iconSize), height: Self.stableHeight(iconSize: iconSize))
+ .background(
+ GeometryReader { proxy in
+ Color.clear
+ .onAppear { globalFrame = proxy.frame(in: .global) }
+ .onChange(of: proxy.frame(in: .global)) { _, newFrame in
+ globalFrame = newFrame
+ }
+ }
+ )
.contentShape(Rectangle())
.onHover { hovering in
isHovered = hovering
+ if showUncommonAppBubbles && app.isUncommon {
+ onBubbleHover?(app, globalFrame, hovering)
+ }
+ }
+ .contextMenu {
+ if app.isUncommon {
+ Button(tr("appNote.edit")) {
+ onEditNote?(app, globalFrame)
+ }
+ }
}
.rotationEffect(.degrees(dragModeActive ? (wiggle ? 2.0 : -2.0) : 0))
.animation(
@@ -79,6 +102,86 @@
}
}
+enum BubblePlacement {
+ case above
+ case below
+}
+
+struct AppNameBubble: View {
+ let appName: String
+ let note: String?
+ let isEditing: Bool
+ let placement: BubblePlacement
+ @Binding var draftNote: String
+ var noteFocused: FocusState<Bool>.Binding
+ let onCommit: () -> Void
+ let onCancel: () -> Void
+
+ private var displayNote: String {
+ note?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
+ }
+
+ var body: some View {
+ content
+ .onExitCommand(perform: onCancel)
+ }
+
+ private var content: some View {
+ VStack(spacing: isEditing || !displayNote.isEmpty ? 8 : 0) {
+ Text(appName)
+ .font(.system(size: isEditing ? 22 : 24, weight: .bold))
+ .foregroundStyle(.white)
+ .lineLimit(1)
+ .truncationMode(.middle)
+ .frame(maxWidth: .infinity)
+
+ if isEditing {
+ VStack(alignment: .trailing, spacing: 4) {
+ TextField(tr("appNote.placeholder"), text: limitedDraft)
+ .textFieldStyle(.plain)
+ .font(.system(size: 15, weight: .medium))
+ .foregroundStyle(.white)
+ .padding(.horizontal, 12)
+ .frame(height: 36)
+ .background(
+ RoundedRectangle(cornerRadius: 9)
+ .fill(Color.white.opacity(0.12))
+ )
+ .focused(noteFocused)
+ .onSubmit(onCommit)
+ Text("\(draftNote.count) / \(TagDatabase.maxAppNoteLength)")
+ .font(.system(size: 10, weight: .medium))
+ .foregroundStyle(.white.opacity(0.35))
+ }
+ } else if !displayNote.isEmpty {
+ Text(displayNote)
+ .font(.system(size: 14, weight: .medium))
+ .lineSpacing(2)
+ .foregroundStyle(.white.opacity(0.78))
+ .lineLimit(4)
+ .multilineTextAlignment(.center)
+ .fixedSize(horizontal: false, vertical: true)
+ .frame(maxWidth: .infinity)
+ }
+ }
+ .padding(.horizontal, isEditing ? 22 : 24)
+ .padding(.vertical, isEditing ? 18 : 16)
+ .background(
+ RoundedRectangle(cornerRadius: 18, style: .continuous)
+ .fill(Color.black.opacity(0.92))
+ .shadow(color: .black.opacity(0.34), radius: 24, y: 16)
+ .shadow(color: .black.opacity(0.18), radius: 8, y: 3)
+ )
+ }
+
+ private var limitedDraft: Binding<String> {
+ Binding(
+ get: { draftNote },
+ set: { draftNote = String($0.prefix(TagDatabase.maxAppNoteLength)) }
+ )
+ }
+}
+
private struct DraggableAppIconView: NSViewRepresentable {
let icon: NSImage
let iconSize: CGFloat
diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index a04f8b8..e40b773 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -796,6 +796,7 @@
@AppStorage("hideAppNames") private var hideAppNames = false
@AppStorage("showDockIcon") private var showDockIcon = false
@AppStorage("launchAtLogin") private var launchAtLogin = true
+ @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = true
@State private var selectedLanguage = L10n.currentCode
@State private var isRefreshingLanguage = false
@State private var allApps: [AppInfo] = []
@@ -1064,7 +1065,19 @@
// Tab 4: Data
VStack(spacing: 0) {
- Spacer(minLength: 96)
+ Spacer(minLength: 54)
+
+ VStack(alignment: .leading, spacing: 5) {
+ Toggle(tr("settings.uncommonBubble"), isOn: $showUncommonAppBubbles)
+ .font(.system(size: 13, weight: .medium))
+ Text(tr("settings.uncommonBubbleDesc"))
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
+ }
+ .frame(width: 420, alignment: .leading)
+
+ Spacer(minLength: 28)
VStack(alignment: .leading, spacing: 8) {
Text(tr("settings.backup"))
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index cc4bdea..7b6037e 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -16,6 +16,11 @@
case editingApps
}
+enum EditTagOperation {
+ case add
+ case remove
+}
+
// MARK: - Native NSTextField (avoids SwiftUI TextField event issues)
/// Custom container that wraps NSTextField so that hitTest returns the container
@@ -220,6 +225,64 @@
// MARK: - Full-Screen Overlay
+private struct AppBubbleContext {
+ let app: AppInfo
+ let frame: CGRect
+}
+
+private struct EditActionFeedback: Identifiable {
+ let id = UUID()
+ let title: String
+ let message: String
+}
+
+private struct EditActionFeedbackBubble: View {
+ let title: String
+ let message: String
+ let onClose: () -> Void
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 14) {
+ HStack(alignment: .top, spacing: 12) {
+ Text(title)
+ .font(.system(size: 24, weight: .bold))
+ .foregroundStyle(.white)
+ .frame(maxWidth: .infinity, alignment: .leading)
+
+ Button(action: onClose) {
+ Image(systemName: "xmark")
+ .font(.system(size: 12, weight: .bold))
+ .foregroundStyle(.white.opacity(0.82))
+ .frame(width: 26, height: 26)
+ .background(
+ Circle()
+ .fill(Color.white.opacity(0.12))
+ )
+ }
+ .buttonStyle(.plain)
+ }
+
+ ScrollView(.vertical, showsIndicators: true) {
+ Text(message)
+ .font(.system(size: 15, weight: .medium))
+ .lineSpacing(3)
+ .foregroundStyle(.white.opacity(0.84))
+ .multilineTextAlignment(.leading)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .frame(maxHeight: 220)
+ }
+ .padding(.horizontal, 24)
+ .padding(.vertical, 20)
+ .background(
+ RoundedRectangle(cornerRadius: 18, style: .continuous)
+ .fill(Color.black.opacity(0.92))
+ .shadow(color: .black.opacity(0.34), radius: 24, y: 16)
+ .shadow(color: .black.opacity(0.18), radius: 8, y: 3)
+ )
+ }
+}
+
struct ContentView: View {
let hideOverlay: () -> Void
@@ -231,7 +294,9 @@
@State private var editPhase: EditPhase = .none
@State private var selectedAppPaths: Set<URL> = []
@State private var selectedTagNames: Set<String> = []
- @State private var successToast: String? = nil
+ @State private var removableTagNames: Set<String> = []
+ @State private var editTagOperation: EditTagOperation = .add
+ @State private var editActionFeedback: EditActionFeedback? = nil
@State private var draggedTagNames: [String] = [] // live drag order
@State private var dragItem: String? = nil // currently dragged tag
@State private var tagReorderFrames: [String: CGRect] = [:]
@@ -246,6 +311,10 @@
@State private var dropRefreshVisible = false
@State private var dropRefreshStartedAt: Date? = nil
@State private var layoutRefreshID = UUID()
+ @State private var hoveredBubble: AppBubbleContext? = nil
+ @State private var editingBubble: AppBubbleContext? = nil
+ @State private var bubbleDraftNote = ""
+ @FocusState private var bubbleNoteFocused: Bool
// Configurable defaults
@AppStorage("defaultGroupName") private var defaultGroupName = "Other"
@@ -255,6 +324,9 @@
@State private var notchHeight: CGFloat = 0
@AppStorage("displayMode") private var displayMode = "flat"
@AppStorage("hideAppNames") private var hideAppNames = false
+
+ private let editSidebarWidth: CGFloat = 188
+ private let editSidebarHorizontalInset: CGFloat = 12
private var isSideLayout: Bool {
tagPosition == "left" || tagPosition == "right"
@@ -292,6 +364,9 @@
case .editingApps:
editAppsView
}
+
+ uncommonAppBubbleOverlay
+ editActionFeedbackOverlay
if let message = dropWarningToast {
Text(message)
@@ -341,11 +416,17 @@
}
.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 editingBubble != nil && !focused {
+ commitBubbleNote()
+ }
}
}
@@ -502,6 +583,43 @@
.id(layoutRefreshID)
}
+ private var uncommonAppBubbleOverlay: some View {
+ GeometryReader { proxy in
+ if let context = editingBubble ?? hoveredBubble {
+ let rootFrame = proxy.frame(in: .global)
+ let editing = editingBubble != nil
+ let width = min(editing ? 440 : 520, max(260, proxy.size.width - 48))
+ let placement = bubblePlacement(for: context.frame, rootFrame: rootFrame)
+ let metrics = bubbleMetrics(
+ for: context,
+ rootFrame: rootFrame,
+ rootSize: proxy.size,
+ width: width,
+ placement: placement,
+ isEditing: editing
+ )
+
+ AppNameBubble(
+ appName: context.app.name,
+ note: currentNote(for: context.app),
+ isEditing: editing,
+ placement: placement,
+ draftNote: $bubbleDraftNote,
+ noteFocused: $bubbleNoteFocused,
+ onCommit: commitBubbleNote,
+ onCancel: dismissAppBubble
+ )
+ .frame(width: width)
+ .fixedSize(horizontal: false, vertical: true)
+ .position(x: metrics.centerX, y: metrics.centerY)
+ .transition(.scale(scale: 0.94).combined(with: .opacity))
+ .zIndex(500)
+ }
+ }
+ .ignoresSafeArea()
+ .allowsHitTesting(editingBubble != nil)
+ }
+
private var flatGrid: some View {
ScrollViewReader { proxy in
ScrollView {
@@ -515,6 +633,8 @@
showNames: !hideAppNames,
dragModeActive: appDragModeActive,
onDragModeChange: { setAppDragMode($0) },
+ onBubbleHover: handleBubbleHover,
+ onEditNote: beginEditingBubbleNote,
onDropApp: { path, source, copy in
dropApp(path: path, sourceTag: source, targetTag: group.name, copy: copy)
}
@@ -615,6 +735,8 @@
sourceTag: group.name,
dragModeActive: appDragModeActive,
onDragModeChange: { setAppDragMode($0) },
+ onBubbleHover: handleBubbleHover,
+ onEditNote: beginEditingBubbleNote,
onSelect: { openApp(app) }
)
}
@@ -643,6 +765,7 @@
.shadow(color: .black.opacity((isColored && isHovered) || isColorlessActive ? 0.22 : 0),
radius: (isColored && isHovered) || isColorlessActive ? 18 : 0,
y: (isColored && isHovered) || isColorlessActive ? 10 : 0)
+ .zIndex(isHovered ? 50 : 0)
.scaleEffect(isColored && isHovered ? 1.015 : 1.0)
.animation(.spring(response: 0.25, dampingFraction: 0.82), value: isHovered)
.animation(.spring(response: 0.25, dampingFraction: 0.82), value: isColorlessFilled)
@@ -849,6 +972,8 @@
sourceTag: group.name,
dragModeActive: appDragModeActive,
onDragModeChange: { setAppDragMode($0) },
+ onBubbleHover: handleBubbleHover,
+ onEditNote: beginEditingBubbleNote,
onSelect: { openApp(app) }
)
.frame(width: cellWidth)
@@ -889,6 +1014,7 @@
.shadow(color: .black.opacity((isColored && isHovered) || isColorlessGridActive ? 0.22 : 0),
radius: (isColored && isHovered) || isColorlessGridActive ? 18 : 0,
y: (isColored && isHovered) || isColorlessGridActive ? 10 : 0)
+ .zIndex(isHovered ? 50 : 0)
.scaleEffect(isColored && isHovered ? 1.015 : 1.0)
.animation(.spring(response: 0.25, dampingFraction: 0.82), value: isHovered)
.animation(.spring(response: 0.25, dampingFraction: 0.82), value: isColorlessFilled)
@@ -959,18 +1085,26 @@
private var editAppsView: some View {
VStack(spacing: 0) {
- HStack {
- Button { cancelEditApps(); setEditPhase(.none) } label: {
- Label(tr("edit.exit"), systemImage: "rectangle.portrait.and.arrow.right")
- .font(.system(size: 12))
+ HStack(alignment: .center, spacing: 14) {
+ HStack(alignment: .center, spacing: 12) {
+ Button { cancelEditApps(); setEditPhase(.none) } label: {
+ Label(tr("edit.exit"), systemImage: "rectangle.portrait.and.arrow.right")
+ .font(.system(size: 12))
+ }
+ .buttonStyle(.bordered)
+
+ operationHeaderPicker
}
- .buttonStyle(.bordered)
- Spacer()
- Text(tr("edit.title")).font(.headline)
- Spacer()
- Button(tr("edit.confirm")) { confirmAssign() }
+
+ Spacer(minLength: 16)
+
+ headerModeHintView
+
+ Button(editConfirmTitle) { confirmAssign() }
.buttonStyle(.borderedProminent)
- .disabled(selectedAppPaths.isEmpty)
+ .disabled(selectedAppPaths.isEmpty || pendingOperationTagNames.isEmpty)
+ .fixedSize(horizontal: true, vertical: false)
+ .layoutPriority(2)
}
.padding(.horizontal, 24)
.padding(.top, notchHeight > 0 ? notchHeight + 10 : 20)
@@ -980,8 +1114,28 @@
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: 4) {
- Text(tr("edit.selectTags")).font(.caption).foregroundStyle(.secondary).padding(.bottom, 4)
- Text(tr("edit.dragHint")).font(.caption2).foregroundStyle(.tertiary).padding(.bottom, 2)
+ VStack(alignment: .leading, spacing: 6) {
+ Text(tr("edit.selectTags"))
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ .lineLimit(2)
+ .fixedSize(horizontal: false, vertical: true)
+ .multilineTextAlignment(.leading)
+ .frame(maxWidth: .infinity, alignment: .leading)
+
+ Text(tr("edit.dragHint"))
+ .font(.caption2)
+ .foregroundStyle(.tertiary)
+ .lineLimit(nil)
+ .fixedSize(horizontal: false, vertical: true)
+ .multilineTextAlignment(.leading)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .padding(.horizontal, editSidebarHorizontalInset)
+ .padding(.top, 4)
+ .padding(.bottom, 4)
+ .frame(width: editSidebarWidth, alignment: .leading)
+
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: 4) {
ForEach(sortedTagNames, id: \.self) { tagName in
@@ -1006,8 +1160,9 @@
tagReorderFrames = frames
}
}
- .frame(width: 155)
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
+ .frame(width: editSidebarWidth, alignment: .topLeading)
Rectangle().fill(.secondary.opacity(0.12)).frame(width: 1)
if allApps.isEmpty {
@@ -1017,29 +1172,19 @@
}
}
- if let msg = successToast {
- Text(msg).font(.headline)
- .padding(.horizontal, 20).padding(.vertical, 10)
- .background(RoundedRectangle(cornerRadius: 10).fill(.ultraThickMaterial))
- .transition(.move(edge: .bottom).combined(with: .opacity))
- .onAppear {
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
- withAnimation { successToast = nil }
- }
- }
- }
}
}
private var editAppsGrid: some View {
ScrollView {
LazyVStack(alignment: .leading, spacing: 24) {
- ForEach(groups) { group in
+ ForEach(editGroups) { group in
editFlatGroup(group)
}
}
.padding(20)
}
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
private func editFlatGroup(_ group: TagGroup) -> some View {
@@ -1068,31 +1213,71 @@
) {
ForEach(group.apps) { app in editableAppItem(app) }
}
+ .frame(minHeight: group.apps.isEmpty ? max(52, iconSize + 12) : 0)
}
}
private func selectableTagItem(_ tagName: String) -> some View {
let isSelected = selectedTagNames.contains(tagName)
+ let isRemovableCandidate = removableTagNames.contains(tagName)
+ let isEnabled = editTagOperation == .add || isRemovableCandidate
+ let showsPendingRemoval = editTagOperation == .remove && isRemovableCandidate && !isSelected
+ let isUncommon = tagName == TagDatabase.uncommonTagKey
+ let colorIndex = isUncommon ? 0 : (tagColors[tagName] ?? 0)
return HStack(spacing: 6) {
Circle()
- .fill(isSelected ? Color.accentColor : Color.secondary.opacity(0.3))
+ .fill(isSelected ? Color.accentColor : Color.secondary.opacity(isEnabled ? 0.3 : 0.18))
.frame(width: 16, height: 16)
.overlay(isSelected ? Image(systemName: "checkmark").font(.system(size: 8, weight: .bold)).foregroundStyle(.white) : nil)
- Text(tagName).font(.system(size: 13, weight: .medium)).foregroundStyle(.primary)
+ Text(displayTagName(tagName))
+ .font(.system(size: 13, weight: isUncommon ? .semibold : .medium))
+ .foregroundStyle(isEnabled ? Color.primary : Color.secondary.opacity(0.66))
+ .lineLimit(1)
+ .truncationMode(.tail)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ HStack(spacing: 4) {
+ if isUncommon {
+ Image(systemName: "questionmark.bubble.fill")
+ .font(.system(size: 11, weight: .semibold))
+ .foregroundStyle(isEnabled ? Color.secondary : Color.secondary.opacity(0.55))
+ .frame(width: 16, height: 16)
+ }
+ if showsPendingRemoval {
+ Image(systemName: "trash.fill")
+ .font(.system(size: 11, weight: .bold))
+ .foregroundStyle(.red)
+ .frame(width: 16, height: 16)
+ .transition(.scale(scale: 0.85).combined(with: .opacity))
+ } else if editTagOperation == .remove {
+ Color.clear.frame(width: 16, height: 16)
+ }
+ }
+ .frame(
+ minWidth: editTagOperation == .remove
+ ? (isUncommon ? 36 : 16)
+ : (isUncommon ? 16 : 0),
+ alignment: .trailing
+ )
+ .layoutPriority(1)
}
- .padding(.horizontal, 10).padding(.vertical, 5)
+ .padding(.horizontal, 10)
+ .frame(height: 27)
.frame(maxWidth: .infinity, alignment: .leading)
.background(RoundedRectangle(cornerRadius: 6)
- .fill(Color(nsColor: TagColor.nsColor(for: tagColors[tagName] ?? 0).withAlphaComponent(0.3))))
+ .fill(Color(nsColor: TagColor.nsColor(for: colorIndex).withAlphaComponent(isEnabled ? (isUncommon ? 0.22 : 0.3) : 0.12))))
.contentShape(RoundedRectangle(cornerRadius: 6))
.onTapGesture {
+ guard isEnabled else { return }
if isSelected { selectedTagNames.remove(tagName) } else { selectedTagNames.insert(tagName) }
}
+ .opacity(isEnabled ? 1.0 : 0.58)
+ .animation(.easeInOut(duration: 0.15), value: showsPendingRemoval)
}
private func tagReorderGesture(for tagName: String) -> some Gesture {
DragGesture(minimumDistance: 4, coordinateSpace: .named("tagReorderList"))
.onChanged { value in
+ guard canReorderTag(tagName) else { return }
if dragItem == nil {
dragItem = tagName
}
@@ -1100,6 +1285,7 @@
reorderDraggedTag(at: value.location)
}
.onEnded { _ in
+ guard canReorderTag(tagName) else { return }
dragItem = nil
TagEditor.reorderTags(draggedTagNames)
}
@@ -1121,17 +1307,31 @@
}
private func confirmAssign() {
- guard !selectedAppPaths.isEmpty else { return }
- let paths = selectedAppPaths.map { $0.path }
- let tags = sortedTagNames.filter { selectedTagNames.contains($0) }
- TagEditor.setTags(tags, to: paths)
- selectedAppPaths = []; selectedTagNames = []
+ let selectedApps = allApps.filter { selectedAppPaths.contains($0.path) }
+ let tags = pendingOperationTagNames
+ guard !selectedApps.isEmpty, !tags.isEmpty else { return }
+
+ let paths = selectedApps.map { $0.path.path }
+ let feedback = buildEditActionFeedback(for: selectedApps, tags: tags)
+
+ switch editTagOperation {
+ case .add:
+ TagEditor.appendTags(tags, to: paths)
+ case .remove:
+ TagEditor.removeTags(tags, from: paths)
+ }
+ selectedAppPaths = []
+ selectedTagNames = []
+ removableTagNames = []
refreshApps()
- withAnimation { successToast = tr("edit.success") }
+ showEditActionFeedback(feedback)
}
private func cancelEditApps() {
- selectedAppPaths = []; selectedTagNames = []
+ selectedAppPaths = []
+ selectedTagNames = []
+ removableTagNames = []
+ editTagOperation = .add
}
private func editableAppItem(_ app: AppInfo) -> some View {
@@ -1169,20 +1369,219 @@
}
private func syncSelectedTagsFromSelectedApps() {
- let selectedApps = allApps.filter { selectedAppPaths.contains($0.path) }
- guard !selectedApps.isEmpty else {
+ guard !selectedAppPaths.isEmpty else {
selectedTagNames = []
+ removableTagNames = []
return
}
- let editableTags = Set(sortedTagNames)
- if selectedApps.count == 1 {
- selectedTagNames = Set(selectedApps[0].tags.filter { editableTags.contains($0) })
+ guard editTagOperation == .remove else {
+ removableTagNames = []
return
}
- selectedTagNames = selectedApps.reduce(into: Set<String>()) { result, app in
- result.formUnion(app.tags.filter { editableTags.contains($0) })
+ let selectedApps = allApps.filter { selectedAppPaths.contains($0.path) }
+ let autoSelectedTags = selectedApps.reduce(into: Set<String>()) { result, app in
+ result.formUnion(app.tags)
+ if app.isUncommon {
+ result.insert(TagDatabase.uncommonTagKey)
+ }
+ }
+ removableTagNames = autoSelectedTags
+ selectedTagNames = autoSelectedTags
+ }
+
+ private var operationHeaderPicker: some View {
+ HStack(spacing: 8) {
+ Text(tr("edit.operationLabel"))
+ .font(.system(size: 12, weight: .medium))
+ .foregroundStyle(.secondary)
+
+ HStack(spacing: 6) {
+ operationModeButton(.add, titleKey: "edit.modeAdd", systemImage: "plus.circle.fill")
+ operationModeButton(.remove, titleKey: "edit.modeRemove", systemImage: "minus.circle.fill")
+ }
+ }
+ }
+
+ private func operationModeButton(_ mode: EditTagOperation, titleKey: String, systemImage: String) -> some View {
+ let isActive = editTagOperation == mode
+ return Button {
+ setEditTagOperation(mode)
+ } label: {
+ Label(tr(titleKey), systemImage: systemImage)
+ .font(.system(size: 12, weight: .semibold))
+ .padding(.horizontal, 12)
+ .padding(.vertical, 6)
+ .foregroundStyle(isActive ? Color.white : Color.primary)
+ .background(
+ Capsule(style: .continuous)
+ .fill(isActive ? Color.accentColor : Color.secondary.opacity(0.12))
+ )
+ }
+ .buttonStyle(.plain)
+ }
+
+ private var editConfirmTitle: String {
+ switch editTagOperation {
+ case .add:
+ return tr("edit.confirmAdd")
+ case .remove:
+ return tr("edit.confirmRemove")
+ }
+ }
+
+ private func setEditTagOperation(_ mode: EditTagOperation) {
+ guard editTagOperation != mode else { return }
+ editTagOperation = mode
+ if mode == .add {
+ selectedTagNames = []
+ removableTagNames = []
+ } else {
+ syncSelectedTagsFromSelectedApps()
+ }
+ }
+
+ private var pendingOperationTagNames: [String] {
+ switch editTagOperation {
+ case .add:
+ return sortedTagNames.filter { selectedTagNames.contains($0) }
+ case .remove:
+ return sortedTagNames.filter { removableTagNames.contains($0) && !selectedTagNames.contains($0) }
+ }
+ }
+
+ private var editModeHintText: String {
+ editTagOperation == .remove ? tr("edit.removeHint") : tr("edit.modeHint")
+ }
+
+ private var headerModeHintView: some View {
+ Text(editModeHintText)
+ .font(.system(size: 12, weight: .medium))
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .truncationMode(.middle)
+ .frame(minWidth: 0, maxWidth: 760, alignment: .trailing)
+ .layoutPriority(1)
+ .help(editModeHintText)
+ }
+
+ private var editActionFeedbackOverlay: some View {
+ GeometryReader { proxy in
+ if let feedback = editActionFeedback {
+ ZStack {
+ Color.black.opacity(0.08)
+ .ignoresSafeArea()
+
+ EditActionFeedbackBubble(
+ title: feedback.title,
+ message: feedback.message,
+ onClose: dismissEditActionFeedback
+ )
+ .frame(width: min(760, max(360, proxy.size.width - 120)))
+ .position(x: proxy.size.width / 2, y: proxy.size.height / 2)
+ .transition(.scale(scale: 0.94).combined(with: .opacity))
+ }
+ .zIndex(700)
+ }
+ }
+ .ignoresSafeArea()
+ .allowsHitTesting(editActionFeedback != nil)
+ }
+
+ private func buildEditActionFeedback(for selectedApps: [AppInfo], tags: [String]) -> EditActionFeedback {
+ let appNames = selectedApps.map(\.name)
+ .joined(separator: localizedListSeparator)
+
+ switch editTagOperation {
+ case .add:
+ let actualAddCount = selectedApps.reduce(into: 0) { count, app in
+ count += tags.reduce(into: 0) { subtotal, tagName in
+ if !appHasTag(app, tagName: tagName) {
+ subtotal += 1
+ }
+ }
+ }
+
+ return EditActionFeedback(
+ title: tr("edit.confirmAdd"),
+ message: formattedFeedbackMessage(
+ forKey: "edit.feedbackAddFormat",
+ replacements: [
+ "%appCount%": "\(selectedApps.count)",
+ "%appNames%": appNames,
+ "%tagCount%": "\(actualAddCount)"
+ ]
+ )
+ )
+
+ case .remove:
+ let actualRemovedTagNames = tags.filter { tagName in
+ selectedApps.contains(where: { appHasTag($0, tagName: tagName) })
+ }
+ let actualRemoveCount = selectedApps.reduce(into: 0) { count, app in
+ count += tags.reduce(into: 0) { subtotal, tagName in
+ if appHasTag(app, tagName: tagName) {
+ subtotal += 1
+ }
+ }
+ }
+
+ return EditActionFeedback(
+ title: tr("edit.confirmRemove"),
+ message: formattedFeedbackMessage(
+ forKey: "edit.feedbackRemoveFormat",
+ replacements: [
+ "%appCount%": "\(selectedApps.count)",
+ "%appNames%": appNames,
+ "%tagCount%": "\(actualRemoveCount)",
+ "%tagNames%": actualRemovedTagNames
+ .map(displayTagName)
+ .joined(separator: localizedListSeparator)
+ ]
+ )
+ )
+ }
+ }
+
+ private func appHasTag(_ app: AppInfo, tagName: String) -> Bool {
+ if tagName == TagDatabase.uncommonTagKey {
+ return app.isUncommon
+ }
+ return app.tags.contains(tagName)
+ }
+
+ private func formattedFeedbackMessage(forKey key: String, replacements: [String: String]) -> String {
+ replacements.reduce(tr(key)) { partial, replacement in
+ partial.replacingOccurrences(of: replacement.key, with: replacement.value)
+ }
+ }
+
+ private func showEditActionFeedback(_ feedback: EditActionFeedback) {
+ let feedbackID = feedback.id
+ withAnimation(.spring(response: 0.24, dampingFraction: 0.82)) {
+ editActionFeedback = feedback
+ }
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2.6) {
+ guard editActionFeedback?.id == feedbackID else { return }
+ dismissEditActionFeedback()
+ }
+ }
+
+ private func dismissEditActionFeedback() {
+ withAnimation(.easeOut(duration: 0.18)) {
+ editActionFeedback = nil
+ }
+ }
+
+ private var localizedListSeparator: String {
+ switch L10n.currentCode {
+ case "zh-Hans", "zh-Hant", "ja":
+ return "、"
+ case "ar", "ar-Najdi":
+ return "، "
+ default:
+ return ", "
}
}
@@ -1193,7 +1592,11 @@
// Use user-defined order, fall back to alpha
let ordered = draggedTagNames.filter { filtered.contains($0) }
let remaining = filtered.filter { !ordered.contains($0) }.sorted()
- return ordered + remaining
+ return ordered + remaining + [TagDatabase.uncommonTagKey]
+ }
+
+ private func displayTagName(_ tagName: String) -> String {
+ tagName == TagDatabase.uncommonTagKey ? tr("group.uncommon") : tagName
}
private var tagLabels: [TagLabel] {
@@ -1217,7 +1620,98 @@
}
}
+ private var editGroups: [TagGroup] {
+ groups + [
+ TagGroup(
+ name: tr("group.uncommon"),
+ apps: allApps.filter(\.isUncommon).sorted {
+ $0.name.localizedStandardCompare($1.name) == .orderedAscending
+ }
+ )
+ ]
+ }
+
// MARK: - Actions
+
+ private func handleBubbleHover(app: AppInfo, frame: CGRect, hovering: Bool) {
+ guard editingBubble == nil else { return }
+ if hovering {
+ hoveredBubble = AppBubbleContext(app: app, frame: frame)
+ } else if hoveredBubble?.app.path == app.path {
+ hoveredBubble = nil
+ }
+ }
+
+ private func beginEditingBubbleNote(app: AppInfo, frame: CGRect) {
+ bubbleDraftNote = currentNote(for: app)
+ hoveredBubble = nil
+ editingBubble = AppBubbleContext(app: app, frame: frame)
+ DispatchQueue.main.async {
+ bubbleNoteFocused = true
+ }
+ }
+
+ private func commitBubbleNote() {
+ guard let context = editingBubble else { return }
+ let limited = String(bubbleDraftNote.prefix(TagDatabase.maxAppNoteLength))
+ .trimmingCharacters(in: .whitespacesAndNewlines)
+ TagEditor.setAppNote(limited, for: context.app.path.path)
+ bubbleDraftNote = limited
+ editingBubble = nil
+ bubbleNoteFocused = false
+ refreshApps()
+ }
+
+ private func dismissAppBubble() {
+ hoveredBubble = nil
+ editingBubble = nil
+ bubbleNoteFocused = false
+ }
+
+ private func currentNote(for app: AppInfo) -> String {
+ if let latest = allApps.first(where: { $0.path == app.path })?.note {
+ return latest
+ }
+ return app.note ?? ""
+ }
+
+ private func bubblePlacement(for frame: CGRect, rootFrame: CGRect) -> BubblePlacement {
+ frame.minY - rootFrame.minY < 170 ? .below : .above
+ }
+
+ private func bubbleMetrics(
+ for context: AppBubbleContext,
+ rootFrame: CGRect,
+ rootSize: CGSize,
+ width: CGFloat,
+ placement: BubblePlacement,
+ isEditing: Bool
+ ) -> (centerX: CGFloat, centerY: CGFloat) {
+ let appCenterX = context.frame.midX - rootFrame.minX
+ let halfWidth = width / 2
+ let centerX = min(max(appCenterX, halfWidth + 24), max(halfWidth + 24, rootSize.width - halfWidth - 24))
+ let estimatedHeight = estimatedBubbleHeight(for: context.app, width: width, isEditing: isEditing)
+ let topY: CGFloat
+ if placement == .below {
+ topY = context.frame.maxY - rootFrame.minY + 10
+ } else {
+ topY = context.frame.minY - rootFrame.minY - estimatedHeight - 10
+ }
+ let clampedTopY = min(max(topY, 14), max(14, rootSize.height - estimatedHeight - 14))
+ return (centerX, clampedTopY + estimatedHeight / 2)
+ }
+
+ private func estimatedBubbleHeight(for app: AppInfo, width: CGFloat, isEditing: Bool) -> CGFloat {
+ if isEditing {
+ return 120
+ }
+ let note = currentNote(for: app).trimmingCharacters(in: .whitespacesAndNewlines)
+ guard !note.isEmpty else { return 68 }
+ let contentWidth = max(160, width - 48)
+ let approxCharsPerLine = max(16, Int(contentWidth / 14))
+ let lines = min(4, max(1, Int(ceil(Double(note.count) / Double(approxCharsPerLine)))))
+ return 70 + CGFloat(lines) * 20
+ }
func scrollTo(_ id: String) {
withAnimation(.easeInOut(duration: 0.25)) { scrollProxy?.scrollTo(id, anchor: .top) }
@@ -1405,9 +1899,9 @@
func refreshApps(forceLayoutRefresh: Bool = false) {
DispatchQueue.global(qos: .userInitiated).async {
- var apps = AppIndexer.scan()
- let store = TagDatabase.load()
- apps = TagEditor.annotate(apps: apps)
+ let scannedApps = AppIndexer.scan()
+ let store = TagEditor.reconcileScannedApps(scannedApps)
+ let apps = TagEditor.annotate(apps: scannedApps, store: store)
let colors = store.tags.mapValues { $0.color }
let order = TagEditor.orderedTagNames()
DispatchQueue.main.async {
@@ -1428,6 +1922,9 @@
appDragModeActive = false
endTagNavReorder()
hideOverlay()
+ DispatchQueue.global(qos: .utility).async {
+ TagEditor.recordLauncherOpen(for: app.path.path)
+ }
let configuration = NSWorkspace.OpenConfiguration()
NSWorkspace.shared.openApplication(at: app.path, configuration: configuration)
}
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 2eb3d99..eacfa1d 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -10,11 +10,13 @@
let tags: [String]
let bundleIdentifier: String?
let icon: NSImage // Pre-loaded during background scan
+ var isUncommon: Bool = false
+ var note: String? = nil
/// True if this is an Apple pre-installed app.
var isAppleApp: Bool {
if let bid = bundleIdentifier, bid.hasPrefix("com.apple.") { return true }
- return path.path.hasPrefix("/System/Applications/")
+ return AppIndexer.isSystemAppPath(path.path)
}
func hash(into hasher: inout Hasher) { hasher.combine(path) }
@@ -53,45 +55,95 @@
static let searchPaths: [URL] = [
URL(fileURLWithPath: "/Applications"),
URL(fileURLWithPath: "/System/Applications"),
+ URL(fileURLWithPath: "/System/Cryptexes/App/System/Applications"),
+ URL(fileURLWithPath: "/System/Volumes/Preboot/Cryptexes/App/System/Applications"),
FileManager.default.homeDirectoryForCurrentUser
.appendingPathComponent("Applications")
]
+ private static let systemAppPathPrefixes = [
+ "/System/Applications/",
+ "/System/Cryptexes/App/System/Applications/",
+ "/System/Volumes/Preboot/Cryptexes/App/System/Applications/"
+ ]
+
+ static func isSystemAppPath(_ path: String) -> Bool {
+ systemAppPathPrefixes.contains { path.hasPrefix($0) }
+ }
+
/// Scan all standard locations. Tags are annotated from TagDatabase by the caller.
static func scan() -> [AppInfo] {
- var seen = Set<URL>()
+ var seenResolvedPaths = Set<String>()
var apps: [AppInfo] = []
for baseURL in searchPaths {
+ scanDirectChildren(
+ in: baseURL,
+ apps: &apps,
+ seenResolvedPaths: &seenResolvedPaths
+ )
+
guard let enumerator = FileManager.default.enumerator(
at: baseURL,
- includingPropertiesForKeys: [.isDirectoryKey],
+ includingPropertiesForKeys: [.isDirectoryKey, .isSymbolicLinkKey],
options: [.skipsHiddenFiles, .skipsPackageDescendants]
) else { continue }
for case let url as URL in enumerator {
- guard url.pathExtension == "app" else { continue }
- guard !seen.contains(url) else { continue }
- seen.insert(url)
-
- let name = url.deletingPathExtension().lastPathComponent
- let icon = NSWorkspace.shared.icon(forFile: url.path)
- icon.size = NSSize(width: 96, height: 96)
- var bundleId: String? = nil
- if let bundle = Bundle(url: url) {
- bundleId = bundle.bundleIdentifier
- }
-
- apps.append(AppInfo(
- name: name, path: url, tags: [],
- bundleIdentifier: bundleId, icon: icon
- ))
+ appendAppIfNeeded(
+ at: url,
+ apps: &apps,
+ seenResolvedPaths: &seenResolvedPaths
+ )
}
}
return apps.sorted {
$0.name.localizedStandardCompare($1.name) == .orderedAscending
}
+ }
+
+ private static func scanDirectChildren(
+ in baseURL: URL,
+ apps: inout [AppInfo],
+ seenResolvedPaths: inout Set<String>
+ ) {
+ guard let names = try? FileManager.default.contentsOfDirectory(atPath: baseURL.path) else { return }
+
+ for name in names where !name.hasPrefix(".") {
+ appendAppIfNeeded(
+ at: baseURL.appendingPathComponent(name),
+ apps: &apps,
+ seenResolvedPaths: &seenResolvedPaths
+ )
+ }
+ }
+
+ private static func appendAppIfNeeded(
+ at url: URL,
+ apps: inout [AppInfo],
+ seenResolvedPaths: inout Set<String>
+ ) {
+ guard url.pathExtension.lowercased() == "app" else { return }
+
+ let displayURL = url.standardizedFileURL
+ let resolvedURL = displayURL.resolvingSymlinksInPath().standardizedFileURL
+ guard seenResolvedPaths.insert(resolvedURL.path).inserted else { return }
+
+ let name = displayURL.deletingPathExtension().lastPathComponent
+ let icon = NSWorkspace.shared.icon(forFile: displayURL.path)
+ icon.size = NSSize(width: 96, height: 96)
+
+ let bundleId = Bundle(url: displayURL)?.bundleIdentifier
+ ?? Bundle(url: resolvedURL)?.bundleIdentifier
+
+ apps.append(AppInfo(
+ name: name,
+ path: displayURL,
+ tags: [],
+ bundleIdentifier: bundleId,
+ icon: icon
+ ))
}
/// Group apps by their tags (from TagDatabase).
@@ -140,6 +192,14 @@
}
enum TagDatabase {
+ static let uncommonTagKey = "__system.uncommon"
+ static let maxAppNoteLength = 80
+ static let autoUncommonOpenThreshold = 100
+
+ enum UncommonSource: String, Codable {
+ case auto
+ case manual
+ }
// MARK: Storage types
@@ -152,6 +212,42 @@
var tags: [String: TagDef] = [:]
var appTags: [String: [String]] = [:] // path → tag names
var tagOrder: [String] = [] // display order; empty → alpha sort
+ var uncommonAppPaths: [String] = [] // special marker; does not affect normal groups
+ var uncommonSources: [String: UncommonSource] = [:] // current uncommon source: auto/manual
+ var appOpenCounts: [String: Int] = [:] // launches opened from TagLauncher
+ var knownAppPaths: [String] = [] // baseline set to detect newly installed apps
+ var appNotes: [String: String] = [:] // path → user note; retained even if marker is removed
+
+ enum CodingKeys: String, CodingKey {
+ case version
+ case tags
+ case appTags
+ case tagOrder
+ case uncommonAppPaths
+ case uncommonSources
+ case appOpenCounts
+ case knownAppPaths
+ case appNotes
+ }
+
+ init() {}
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ version = try container.decodeIfPresent(Int.self, forKey: .version) ?? 1
+ tags = try container.decodeIfPresent([String: TagDef].self, forKey: .tags) ?? [:]
+ appTags = try container.decodeIfPresent([String: [String]].self, forKey: .appTags) ?? [:]
+ tagOrder = try container.decodeIfPresent([String].self, forKey: .tagOrder) ?? []
+ uncommonAppPaths = try container.decodeIfPresent([String].self, forKey: .uncommonAppPaths) ?? []
+ uncommonSources = try container.decodeIfPresent([String: UncommonSource].self, forKey: .uncommonSources) ?? [:]
+ appOpenCounts = try container.decodeIfPresent([String: Int].self, forKey: .appOpenCounts) ?? [:]
+ knownAppPaths = try container.decodeIfPresent([String].self, forKey: .knownAppPaths) ?? []
+ appNotes = try container.decodeIfPresent([String: String].self, forKey: .appNotes) ?? [:]
+
+ for path in uncommonAppPaths where uncommonSources[path] == nil {
+ uncommonSources[path] = .manual
+ }
+ }
}
// MARK: Paths
@@ -191,7 +287,9 @@
}
static func save(_ store: Store) {
- guard let data = try? JSONEncoder().encode(store) else { return }
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
+ guard let data = try? encoder.encode(store) else { return }
try? data.write(to: storeURL, options: .atomic)
}
@@ -299,12 +397,18 @@
/// Annotate scanned apps with tags from the database.
static func annotate(apps: [AppInfo]) -> [AppInfo] {
- let store = TagDatabase.load()
+ annotate(apps: apps, store: TagDatabase.load())
+ }
+
+ static func annotate(apps: [AppInfo], store: TagDatabase.Store) -> [AppInfo] {
+ let uncommonPaths = Set(store.uncommonAppPaths)
return apps.map { app in
let appTags = store.appTags[app.path.path] ?? []
return AppInfo(
name: app.name, path: app.path, tags: appTags,
- bundleIdentifier: app.bundleIdentifier, icon: app.icon
+ bundleIdentifier: app.bundleIdentifier, icon: app.icon,
+ isUncommon: uncommonPaths.contains(app.path.path),
+ note: store.appNotes[app.path.path]
)
}
}
@@ -333,13 +437,138 @@
/// Replace the full editable tag set for multiple apps.
static func setTags(_ tags: [String], to paths: [String]) {
var store = TagDatabase.load()
+ let selectedUncommon = tags.contains(TagDatabase.uncommonTagKey)
let validTags = tags.filter { store.tags[$0] != nil }
+ var uncommonPaths = Set(store.uncommonAppPaths)
for path in paths {
if validTags.isEmpty {
store.appTags.removeValue(forKey: path)
} else {
store.appTags[path] = validTags
}
+ if selectedUncommon {
+ uncommonPaths.insert(path)
+ store.uncommonSources[path] = .manual
+ } else {
+ uncommonPaths.remove(path)
+ store.uncommonSources.removeValue(forKey: path)
+ }
+ }
+ store.uncommonAppPaths = uncommonPaths.sorted()
+ TagDatabase.save(store)
+ }
+
+ /// Append tags to multiple apps without disturbing their existing tag sets.
+ static func appendTags(_ tags: [String], to paths: [String]) {
+ guard !tags.isEmpty, !paths.isEmpty else { return }
+
+ var store = TagDatabase.load()
+ let selectedUncommon = tags.contains(TagDatabase.uncommonTagKey)
+ let validTags = tags.filter { store.tags[$0] != nil }
+ var uncommonPaths = Set(store.uncommonAppPaths)
+
+ for path in paths {
+ var current = store.appTags[path] ?? []
+ for tag in validTags where !current.contains(tag) {
+ current.append(tag)
+ }
+ if !current.isEmpty {
+ store.appTags[path] = current
+ }
+
+ if selectedUncommon {
+ uncommonPaths.insert(path)
+ store.uncommonSources[path] = .manual
+ }
+ }
+
+ store.uncommonAppPaths = uncommonPaths.sorted()
+ TagDatabase.save(store)
+ }
+
+ /// Remove tags from multiple apps while preserving every unrelated tag.
+ static func removeTags(_ tags: [String], from paths: [String]) {
+ guard !tags.isEmpty, !paths.isEmpty else { return }
+
+ var store = TagDatabase.load()
+ let selectedUncommon = tags.contains(TagDatabase.uncommonTagKey)
+ let validTags = Set(tags.filter { store.tags[$0] != nil })
+ var uncommonPaths = Set(store.uncommonAppPaths)
+
+ for path in paths {
+ var current = store.appTags[path] ?? []
+ current.removeAll { validTags.contains($0) }
+
+ if current.isEmpty {
+ store.appTags.removeValue(forKey: path)
+ } else {
+ store.appTags[path] = current
+ }
+
+ if selectedUncommon {
+ uncommonPaths.remove(path)
+ store.uncommonSources.removeValue(forKey: path)
+ }
+ }
+
+ store.uncommonAppPaths = uncommonPaths.sorted()
+ TagDatabase.save(store)
+ }
+
+ static func reconcileScannedApps(_ apps: [AppInfo]) -> TagDatabase.Store {
+ var store = TagDatabase.load()
+ let scannedPaths = Set(apps.map { $0.path.path })
+ let knownPaths = Set(store.knownAppPaths)
+
+ if store.knownAppPaths.isEmpty {
+ store.knownAppPaths = scannedPaths.sorted()
+ if apps.isEmpty == false {
+ TagDatabase.save(store)
+ }
+ return store
+ }
+
+ let newPaths = scannedPaths.subtracting(knownPaths)
+ guard !newPaths.isEmpty else { return store }
+
+ var uncommonPaths = Set(store.uncommonAppPaths)
+ for path in newPaths {
+ uncommonPaths.insert(path)
+ store.uncommonSources[path] = .auto
+ if store.appOpenCounts[path] == nil {
+ store.appOpenCounts[path] = 0
+ }
+ }
+
+ store.uncommonAppPaths = uncommonPaths.sorted()
+ store.knownAppPaths = knownPaths.union(newPaths).sorted()
+ TagDatabase.save(store)
+ return store
+ }
+
+ static func recordLauncherOpen(for path: String) {
+ var store = TagDatabase.load()
+ store.appOpenCounts[path] = (store.appOpenCounts[path] ?? 0) + 1
+
+ if store.uncommonSources[path] == .auto,
+ store.appOpenCounts[path, default: 0] >= TagDatabase.autoUncommonOpenThreshold {
+ var uncommonPaths = Set(store.uncommonAppPaths)
+ uncommonPaths.remove(path)
+ store.uncommonAppPaths = uncommonPaths.sorted()
+ store.uncommonSources.removeValue(forKey: path)
+ }
+
+ TagDatabase.save(store)
+ }
+
+ static func setAppNote(_ note: String, for path: String) {
+ var store = TagDatabase.load()
+ let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines)
+ let limited = String(trimmed.prefix(TagDatabase.maxAppNoteLength))
+ if limited.isEmpty {
+ store.appNotes.removeValue(forKey: path)
+ } else {
+ store.appNotes[path] = limited
}
TagDatabase.save(store)
}
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 82ab965..fe978d6 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
- <string>5.7.8</string>
+ <string>6.0.5</string>
<key>CFBundleVersion</key>
- <string>583</string>
+ <string>610</string>
<key>LSMinimumSystemVersion</key>
<string>15.0</string>
<key>NSHighResolutionCapable</key>
diff --git a/Apptag/Localization/ar-Najdi.json b/Apptag/Localization/ar-Najdi.json
index 3c16b47..263bb36 100644
--- a/Apptag/Localization/ar-Najdi.json
+++ b/Apptag/Localization/ar-Najdi.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "فشل التصدير",
"settings.import": "استيراد التصنيفات والتخطيط…",
"settings.backupDesc": "التصدير يحفظ التصنيفات والتخطيط بملف JSON. الاستيراد يستبدل الإعداد الحالي بملف.",
+ "settings.uncommonBubble": "فقاعات للتطبيقات القليلة الاستخدام",
+ "settings.uncommonBubbleDesc": "ورّ فقاعة كبيرة للاسم والملاحظة بس للتطبيقات المحددة كقليلة الاستخدام.",
"settings.importFailed": "فشل الاستيراد",
"edit.exit": "إنهاء التحرير",
"edit.tags": "تحرير الوسوم",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "غير مصنف",
"group.appleBuiltIn": "مدمج",
+ "group.uncommon": "ما ينستخدم واجد",
"drop.systemDefaultWarning": "لا تسحب التطبيقات لتصنيفات النظام الافتراضية",
"tag.design": "تصميم",
"tag.development": "تطوير",
@@ -64,5 +67,22 @@
"tag.productivity": "إنتاجية",
"settings.coloredContainer": "حاوية ملونة",
"settings.gridContainer": "شبكة بدون لون",
- "settings.coloredGridContainer": "شبكة ملونة"
+ "settings.coloredGridContainer": "شبكة ملونة",
+ "appNote.edit": "عدّل ملاحظة الغرض…",
+ "appNote.placeholder": "ملاحظة غرض التطبيق",
+ "edit.modeAdd": "إضافة وسوم",
+ "edit.modeRemove": "إزالة وسوم",
+ "edit.modeHint": "علامات التحديد في اليسار تنطبق على هالعملية فقط.",
+ "edit.currentTags": "الوسوم الحالية",
+ "edit.commonTags": "الوسوم المشتركة",
+ "edit.selectionHint": "اختر التطبيقات من اليمين، ثم اختر من اليسار الوسوم اللي تبي تضيفها أو تزيلها.",
+ "edit.noAssignedTags": "التطبيق المحدد ما عليه وسوم للحين.",
+ "edit.noCommonTags": "ما فيه وسوم مشتركة بين التطبيقات المحددة.",
+ "edit.mixedTagsHint": "باقي الوسوم تختلف بين التطبيقات المحددة.",
+ "edit.operationLabel": "هالاجراء:",
+ "edit.confirmAdd": "تأكيد الإضافة",
+ "edit.confirmRemove": "تأكيد الإزالة",
+ "edit.feedbackAddFormat": "هالعملية اضافت %tagCount% من الوسوم على %appCount% تطبيقات (%appNames%).",
+ "edit.feedbackRemoveFormat": "هالعملية شالت %tagCount% من الوسوم من %appCount% تطبيقات (%appNames%). الوسوم اللي انشالت: %tagNames%.",
+ "edit.removeHint": "هنا ما تقدر تشيل الصح الا من الوسوم الموجودة من قبل. اي وسم يطلع معه رمز حذف احمر بينشال بعد التاكيد."
}
diff --git a/Apptag/Localization/ar.json b/Apptag/Localization/ar.json
index f4f3770..1989e89 100644
--- a/Apptag/Localization/ar.json
+++ b/Apptag/Localization/ar.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "فشل التصدير",
"settings.import": "استيراد التصنيفات والتخطيط…",
"settings.backupDesc": "يحفظ التصدير التصنيفات والتخطيط في ملف JSON. يستبدل الاستيراد الإعداد الحالي بملف.",
+ "settings.uncommonBubble": "فقاعات التطبيقات قليلة الاستخدام",
+ "settings.uncommonBubbleDesc": "اعرض فقاعة كبيرة للاسم والملاحظة فقط للتطبيقات المحددة كقليلة الاستخدام.",
"settings.importFailed": "فشل الاستيراد",
"edit.exit": "إنهاء التحرير",
"edit.tags": "تحرير الوسوم",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "غير مصنف",
"group.appleBuiltIn": "مدمج",
+ "group.uncommon": "غير مستخدم غالبًا",
"drop.systemDefaultWarning": "لا تسحب التطبيقات إلى التصنيفات الافتراضية للنظام",
"tag.design": "تصميم",
"tag.development": "تطوير",
@@ -64,5 +67,22 @@
"tag.productivity": "إنتاجية",
"settings.coloredContainer": "حاوية ملونة",
"settings.gridContainer": "شبكة بلا لون",
- "settings.coloredGridContainer": "شبكة ملونة"
+ "settings.coloredGridContainer": "شبكة ملونة",
+ "appNote.edit": "تحرير ملاحظة الغرض…",
+ "appNote.placeholder": "ملاحظة غرض التطبيق",
+ "edit.modeAdd": "إضافة وسوم",
+ "edit.modeRemove": "إزالة وسوم",
+ "edit.modeHint": "علامات التحديد على اليسار تنطبق فقط على هذه العملية.",
+ "edit.currentTags": "الوسوم الحالية",
+ "edit.commonTags": "الوسوم المشتركة",
+ "edit.selectionHint": "اختر التطبيقات من اليمين، ثم اختر من اليسار الوسوم التي تريد إضافتها أو إزالتها.",
+ "edit.noAssignedTags": "التطبيق المحدد لا يملك وسوما بعد.",
+ "edit.noCommonTags": "لا توجد وسوم مشتركة بين التطبيقات المحددة.",
+ "edit.mixedTagsHint": "الوسوم الأخرى تختلف بين التطبيقات المحددة.",
+ "edit.operationLabel": "هذا الاجراء:",
+ "edit.confirmAdd": "تأكيد الإضافة",
+ "edit.confirmRemove": "تأكيد الإزالة",
+ "edit.feedbackAddFormat": "اضافت هذه العملية %tagCount% من الوسوم عبر %appCount% تطبيقات (%appNames%).",
+ "edit.feedbackRemoveFormat": "ازالت هذه العملية %tagCount% من الوسوم من %appCount% تطبيقات (%appNames%). الوسوم التي تمت ازالتها: %tagNames%.",
+ "edit.removeHint": "هنا يمكنك الغاء تحديد الوسوم الموجودة بالفعل فقط. الوسوم التي يظهر بجانبها رمز حذف احمر ستزال بعد التاكيد."
}
diff --git a/Apptag/Localization/cs.json b/Apptag/Localization/cs.json
index 3ed3e51..71a0a80 100644
--- a/Apptag/Localization/cs.json
+++ b/Apptag/Localization/cs.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Export se nezdařil",
"settings.import": "Importovat kategorie a rozvržení…",
"settings.backupDesc": "Export uloží kategorie a rozvržení do souboru JSON. Import nahradí aktuální nastavení souborem.",
+ "settings.uncommonBubble": "Bubliny pro málo používané aplikace",
+ "settings.uncommonBubbleDesc": "Zobrazí velké jméno a poznámku jen u aplikací označených jako málo používané.",
"settings.importFailed": "Import se nezdařil",
"edit.exit": "Ukončit úpravy",
"edit.tags": "Upravit štítky",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Nezařazeno",
"group.appleBuiltIn": "Vestavěné",
+ "group.uncommon": "Málo používané",
"drop.systemDefaultWarning": "Nepřetahujte aplikace do výchozích systémových kategorií",
"tag.design": "Design",
"tag.development": "Vývoj",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivita",
"settings.coloredContainer": "Barevný kontejner",
"settings.gridContainer": "Bezbarvá mřížka",
- "settings.coloredGridContainer": "Barevná mřížka"
+ "settings.coloredGridContainer": "Barevná mřížka",
+ "appNote.edit": "Upravit poznámku k účelu…",
+ "appNote.placeholder": "Poznámka k účelu aplikace",
+ "edit.modeAdd": "Pridat stitky",
+ "edit.modeRemove": "Odebrat stitky",
+ "edit.modeHint": "Zatrzeni vlevo plati jen pro tuto operaci.",
+ "edit.currentTags": "Aktualni stitky",
+ "edit.commonTags": "Spolecne stitky",
+ "edit.selectionHint": "Vyberte aplikace vpravo a pak vlevo zvolte stitky, ktere chcete pridat nebo odebrat.",
+ "edit.noAssignedTags": "Vybrana aplikace zatim nema zadne stitky.",
+ "edit.noCommonTags": "Vybrane aplikace nemaji zadne spolecne stitky.",
+ "edit.mixedTagsHint": "Ostatni stitky se mezi vybranymi aplikacemi lisi.",
+ "edit.operationLabel": "Akce:",
+ "edit.confirmAdd": "Potvrdit pridani",
+ "edit.confirmRemove": "Potvrdit odebrani",
+ "edit.feedbackAddFormat": "Tato akce pridala %tagCount% stitku do %appCount% aplikaci (%appNames%).",
+ "edit.feedbackRemoveFormat": "Tato akce odebrala %tagCount% stitku z %appCount% aplikaci (%appNames%). Odebrane stitky: %tagNames%.",
+ "edit.removeHint": "Zde lze odskrtnout jen jiz existujici stitky. Stitky s cervenou ikonou odstraneni budou po potvrzeni odebrany."
}
diff --git a/Apptag/Localization/da.json b/Apptag/Localization/da.json
index 85105b8..223c133 100644
--- a/Apptag/Localization/da.json
+++ b/Apptag/Localization/da.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Eksport mislykkedes",
"settings.import": "Importer kategorier og layout…",
"settings.backupDesc": "Eksport gemmer kategorier og layout i en JSON-fil. Import erstatter den aktuelle opsætning med en fil.",
+ "settings.uncommonBubble": "Bobler for sjældent brugte apps",
+ "settings.uncommonBubbleDesc": "Viser stor navne- og noteboble kun for apps markeret som sjældent brugt.",
"settings.importFailed": "Import mislykkedes",
"edit.exit": "Afslut redigering",
"edit.tags": "Rediger tags",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Ukategoriseret",
"group.appleBuiltIn": "Indbygget",
+ "group.uncommon": "Sjældent brugt",
"drop.systemDefaultWarning": "Træk ikke apps til systemets standardkategorier",
"tag.design": "Design",
"tag.development": "Udvikling",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitet",
"settings.coloredContainer": "Farvet beholder",
"settings.gridContainer": "Farveløst gitter",
- "settings.coloredGridContainer": "Farvet gitter"
+ "settings.coloredGridContainer": "Farvet gitter",
+ "appNote.edit": "Rediger formålsnote…",
+ "appNote.placeholder": "Formålsnote for app",
+ "edit.modeAdd": "Tilfoj tags",
+ "edit.modeRemove": "Fjern tags",
+ "edit.modeHint": "Afkrydsningerne til venstre gaelder kun for denne handling.",
+ "edit.currentTags": "Nuvaerende tags",
+ "edit.commonTags": "Faelles tags",
+ "edit.selectionHint": "Vaelg apps til hojre, og vaelg derefter til venstre de tags, du vil tilfoje eller fjerne.",
+ "edit.noAssignedTags": "Den valgte app har ingen tags endnu.",
+ "edit.noCommonTags": "De valgte apps har ingen faelles tags.",
+ "edit.mixedTagsHint": "Andre tags varierer mellem de valgte apps.",
+ "edit.operationLabel": "Handling:",
+ "edit.confirmAdd": "Bekraeft tilfojelse",
+ "edit.confirmRemove": "Bekraeft fjernelse",
+ "edit.feedbackAddFormat": "Denne handling tilfojede %tagCount% tags til %appCount% apps (%appNames%).",
+ "edit.feedbackRemoveFormat": "Denne handling fjernede %tagCount% tags fra %appCount% apps (%appNames%). Fjernede tags: %tagNames%.",
+ "edit.removeHint": "Her kan kun eksisterende tags fjernes fra markeringen. Tags med et rodt sletteikon fjernes efter bekraeftelse."
}
diff --git a/Apptag/Localization/de.json b/Apptag/Localization/de.json
index c873c77..b603830 100644
--- a/Apptag/Localization/de.json
+++ b/Apptag/Localization/de.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Export fehlgeschlagen",
"settings.import": "Kategorien & Layout importieren…",
"settings.backupDesc": "Der Export speichert Kategorien und Layout als JSON-Datei. Der Import ersetzt die aktuelle Konfiguration durch eine Datei.",
+ "settings.uncommonBubble": "Hinweisblasen fuer seltene Apps",
+ "settings.uncommonBubbleDesc": "Zeigt Name und Notiz nur fuer Apps, die als selten genutzt markiert sind.",
"settings.importFailed": "Import fehlgeschlagen",
"edit.exit": "Bearbeitung beenden",
"edit.tags": "Tags bearbeiten",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Nicht kategorisiert",
"group.appleBuiltIn": "Integriert",
+ "group.uncommon": "Selten genutzt",
"drop.systemDefaultWarning": "Ziehe keine Apps in die Standard-Systemkategorien",
"tag.design": "Design",
"tag.development": "Entwicklung",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivität",
"settings.coloredContainer": "Farbiger Container",
"settings.gridContainer": "Farbloses Raster",
- "settings.coloredGridContainer": "Farbiges Raster"
+ "settings.coloredGridContainer": "Farbiges Raster",
+ "appNote.edit": "Verwendungsnotiz bearbeiten…",
+ "appNote.placeholder": "Verwendungsnotiz zur App",
+ "edit.modeAdd": "Tags hinzufuegen",
+ "edit.modeRemove": "Tags entfernen",
+ "edit.modeHint": "Die Haken links gelten nur fuer diesen Vorgang.",
+ "edit.currentTags": "Aktuelle Tags",
+ "edit.commonTags": "Gemeinsame Tags",
+ "edit.selectionHint": "Waehlen Sie rechts Apps aus und links die Tags, die in diesem Schritt hinzugefuegt oder entfernt werden sollen.",
+ "edit.noAssignedTags": "Die ausgewaehlte App hat noch keine Tags.",
+ "edit.noCommonTags": "Die ausgewaehlten Apps haben keine gemeinsamen Tags.",
+ "edit.mixedTagsHint": "Weitere Tags unterscheiden sich zwischen den ausgewaehlten Apps.",
+ "edit.operationLabel": "Aktion:",
+ "edit.confirmAdd": "Hinzufuegen bestaetigen",
+ "edit.confirmRemove": "Entfernen bestaetigen",
+ "edit.feedbackAddFormat": "Dieser Vorgang hat %tagCount% Tags zu %appCount% Apps hinzugefugt (%appNames%).",
+ "edit.feedbackRemoveFormat": "Dieser Vorgang hat %tagCount% Tags von %appCount% Apps entfernt (%appNames%). Entfernte Tags: %tagNames%.",
+ "edit.removeHint": "Hier konnen nur bereits vorhandene Tags abgewahlt werden. Tags mit rotem Loschsymbol werden nach der Bestatigung entfernt."
}
diff --git a/Apptag/Localization/en.json b/Apptag/Localization/en.json
index cf572bf..61f8c53 100644
--- a/Apptag/Localization/en.json
+++ b/Apptag/Localization/en.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Export Failed",
"settings.import": "Import Categories & Layout…",
"settings.backupDesc": "Export saves your categories and layout to a JSON file. Import replaces the current setup with one from a file.",
+ "settings.uncommonBubble": "Uncommon app bubble tips",
+ "settings.uncommonBubbleDesc": "Show a large name and note bubble only for apps marked as Uncommon.",
"settings.importFailed": "Import Failed",
"edit.exit": "Exit editing",
"edit.tags": "Edit Tags",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Uncategorized",
"group.appleBuiltIn": "Built-in",
+ "group.uncommon": "Uncommon",
"drop.systemDefaultWarning": "Do not drag apps into system default categories",
"tag.design": "Design",
"tag.development": "Development",
@@ -64,5 +67,22 @@
"tag.productivity": "Productivity",
"settings.coloredContainer": "Colored Container",
"settings.gridContainer": "Colorless Grid",
- "settings.coloredGridContainer": "Colored Grid"
+ "settings.coloredGridContainer": "Colored Grid",
+ "appNote.edit": "Edit purpose note…",
+ "appNote.placeholder": "App purpose note",
+ "edit.modeAdd": "Add tags",
+ "edit.modeRemove": "Remove tags",
+ "edit.modeHint": "Left-side checkmarks apply only to this operation.",
+ "edit.currentTags": "Current tags",
+ "edit.commonTags": "Shared tags",
+ "edit.selectionHint": "Select apps on the right, then choose the tags to add or remove on the left.",
+ "edit.noAssignedTags": "The selected app has no tags yet.",
+ "edit.noCommonTags": "These selected apps have no shared tags.",
+ "edit.mixedTagsHint": "Other tags differ between the selected apps.",
+ "edit.operationLabel": "Action:",
+ "edit.confirmAdd": "Confirm Add",
+ "edit.confirmRemove": "Confirm Remove",
+ "edit.feedbackAddFormat": "This operation added %tagCount% tags across %appCount% apps (%appNames%).",
+ "edit.feedbackRemoveFormat": "This operation removed %tagCount% tags across %appCount% apps (%appNames%). Removed tags: %tagNames%.",
+ "edit.removeHint": "Only existing tags can be unchecked here. Tags showing a red delete icon will be removed after confirmation."
}
diff --git a/Apptag/Localization/es.json b/Apptag/Localization/es.json
index 385f2d0..3a96fbf 100644
--- a/Apptag/Localization/es.json
+++ b/Apptag/Localization/es.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Error de exportación",
"settings.import": "Importar categorías y diseño…",
"settings.backupDesc": "La exportación guarda las categorías y el diseño en un archivo JSON. La importación reemplaza la configuración actual.",
+ "settings.uncommonBubble": "Burbujas para apps poco usadas",
+ "settings.uncommonBubbleDesc": "Muestra una burbuja grande con nombre y nota solo para apps marcadas como poco usadas.",
"settings.importFailed": "Error de importación",
"edit.exit": "Salir de edición",
"edit.tags": "Editar etiquetas",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Sin categorizar",
"group.appleBuiltIn": "Integradas",
+ "group.uncommon": "Poco usadas",
"drop.systemDefaultWarning": "No arrastres a las categorías predeterminadas del sistema",
"tag.design": "Diseño",
"tag.development": "Desarrollo",
@@ -64,5 +67,22 @@
"tag.productivity": "Oficina",
"settings.coloredContainer": "Contenedor coloreado",
"settings.gridContainer": "Cuadrícula sin color",
- "settings.coloredGridContainer": "Cuadrícula coloreada"
+ "settings.coloredGridContainer": "Cuadrícula coloreada",
+ "appNote.edit": "Editar nota de uso…",
+ "appNote.placeholder": "Nota de uso de la app",
+ "edit.modeAdd": "Anadir etiquetas",
+ "edit.modeRemove": "Quitar etiquetas",
+ "edit.modeHint": "Las marcas de la izquierda solo se aplican a esta operacion.",
+ "edit.currentTags": "Etiquetas actuales",
+ "edit.commonTags": "Etiquetas compartidas",
+ "edit.selectionHint": "Seleccione aplicaciones a la derecha y luego elija a la izquierda las etiquetas que desea anadir o quitar.",
+ "edit.noAssignedTags": "La aplicacion seleccionada aun no tiene etiquetas.",
+ "edit.noCommonTags": "Las aplicaciones seleccionadas no tienen etiquetas en comun.",
+ "edit.mixedTagsHint": "Las demas etiquetas difieren entre las aplicaciones seleccionadas.",
+ "edit.operationLabel": "Accion:",
+ "edit.confirmAdd": "Confirmar agregado",
+ "edit.confirmRemove": "Confirmar eliminacion",
+ "edit.feedbackAddFormat": "Esta operacion agrego %tagCount% etiquetas a %appCount% apps (%appNames%).",
+ "edit.feedbackRemoveFormat": "Esta operacion elimino %tagCount% etiquetas de %appCount% apps (%appNames%). Etiquetas eliminadas: %tagNames%.",
+ "edit.removeHint": "Aqui solo se pueden desmarcar etiquetas ya existentes. Las etiquetas con icono rojo de eliminacion se quitaran tras confirmar."
}
diff --git a/Apptag/Localization/fr.json b/Apptag/Localization/fr.json
index cbe2e65..0afa8b0 100644
--- a/Apptag/Localization/fr.json
+++ b/Apptag/Localization/fr.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Échec de l'export",
"settings.import": "Importer catégories et disposition…",
"settings.backupDesc": "L'export sauvegarde les catégories et la disposition dans un fichier JSON. L'import remplace la configuration actuelle.",
+ "settings.uncommonBubble": "Bulles pour apps peu utilisées",
+ "settings.uncommonBubbleDesc": "Affiche une grande bulle avec le nom et une note uniquement pour les apps marquées comme peu utilisées.",
"settings.importFailed": "Échec de l'import",
"edit.exit": "Quitter l'édition",
"edit.tags": "Modifier les étiquettes",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Non classé",
"group.appleBuiltIn": "Intégrées",
+ "group.uncommon": "Peu utilisé",
"drop.systemDefaultWarning": "Ne déposez pas dans les catégories système par défaut",
"tag.design": "Design",
"tag.development": "Développement",
@@ -64,5 +67,22 @@
"tag.productivity": "Bureau",
"settings.coloredContainer": "Conteneur coloré",
"settings.gridContainer": "Grille sans couleur",
- "settings.coloredGridContainer": "Grille colorée"
+ "settings.coloredGridContainer": "Grille colorée",
+ "appNote.edit": "Modifier la note d’usage…",
+ "appNote.placeholder": "Note d’usage de l’app",
+ "edit.modeAdd": "Ajouter des etiquettes",
+ "edit.modeRemove": "Retirer des etiquettes",
+ "edit.modeHint": "Les coches a gauche ne s'appliquent qu'a cette operation.",
+ "edit.currentTags": "Etiquettes actuelles",
+ "edit.commonTags": "Etiquettes communes",
+ "edit.selectionHint": "Selectionnez des apps a droite, puis choisissez a gauche les etiquettes a ajouter ou a retirer.",
+ "edit.noAssignedTags": "L'app selectionnee n'a pas encore d'etiquette.",
+ "edit.noCommonTags": "Les apps selectionnees n'ont aucune etiquette commune.",
+ "edit.mixedTagsHint": "Les autres etiquettes different selon les apps selectionnees.",
+ "edit.operationLabel": "Action :",
+ "edit.confirmAdd": "Confirmer ajout",
+ "edit.confirmRemove": "Confirmer retrait",
+ "edit.feedbackAddFormat": "Cette operation a ajoute %tagCount% etiquettes sur %appCount% apps (%appNames%).",
+ "edit.feedbackRemoveFormat": "Cette operation a supprime %tagCount% etiquettes sur %appCount% apps (%appNames%). Etiquettes retirees : %tagNames%.",
+ "edit.removeHint": "Ici, seuls les tags existants peuvent etre decoches. Les tags affichant une icone rouge de suppression seront retires apres confirmation."
}
diff --git a/Apptag/Localization/id.json b/Apptag/Localization/id.json
index c5f5048..e3a018b 100644
--- a/Apptag/Localization/id.json
+++ b/Apptag/Localization/id.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Ekspor gagal",
"settings.import": "Impor kategori & tata letak…",
"settings.backupDesc": "Ekspor menyimpan kategori dan tata letak ke file JSON. Impor mengganti pengaturan saat ini dengan file.",
+ "settings.uncommonBubble": "Gelembung app jarang dipakai",
+ "settings.uncommonBubbleDesc": "Tampilkan gelembung nama besar dan catatan hanya untuk app yang ditandai jarang dipakai.",
"settings.importFailed": "Impor gagal",
"edit.exit": "Keluar dari pengeditan",
"edit.tags": "Edit tag",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Tanpa kategori",
"group.appleBuiltIn": "Bawaan",
+ "group.uncommon": "Jarang dipakai",
"drop.systemDefaultWarning": "Jangan seret app ke kategori bawaan sistem",
"tag.design": "Desain",
"tag.development": "Pengembangan",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitas",
"settings.coloredContainer": "Kontainer berwarna",
"settings.gridContainer": "Grid tanpa warna",
- "settings.coloredGridContainer": "Grid berwarna"
+ "settings.coloredGridContainer": "Grid berwarna",
+ "appNote.edit": "Edit catatan kegunaan…",
+ "appNote.placeholder": "Catatan kegunaan app",
+ "edit.modeAdd": "Tambahkan label",
+ "edit.modeRemove": "Hapus label",
+ "edit.modeHint": "Centang di kiri hanya berlaku untuk operasi ini.",
+ "edit.currentTags": "Label saat ini",
+ "edit.commonTags": "Label bersama",
+ "edit.selectionHint": "Pilih app di kanan, lalu pilih di kiri label yang ingin ditambahkan atau dihapus.",
+ "edit.noAssignedTags": "App yang dipilih belum memiliki label.",
+ "edit.noCommonTags": "App yang dipilih tidak memiliki label bersama.",
+ "edit.mixedTagsHint": "Label lainnya berbeda di antara app yang dipilih.",
+ "edit.operationLabel": "Aksi:",
+ "edit.confirmAdd": "Konfirmasi tambah",
+ "edit.confirmRemove": "Konfirmasi hapus",
+ "edit.feedbackAddFormat": "Operasi ini menambahkan %tagCount% tag ke %appCount% app (%appNames%).",
+ "edit.feedbackRemoveFormat": "Operasi ini menghapus %tagCount% tag dari %appCount% app (%appNames%). Tag yang dihapus: %tagNames%.",
+ "edit.removeHint": "Di sini hanya tag yang sudah ada yang bisa dihapus centangnya. Tag dengan ikon hapus merah akan dihapus setelah konfirmasi."
}
diff --git a/Apptag/Localization/it.json b/Apptag/Localization/it.json
index 0f20df1..c736f78 100644
--- a/Apptag/Localization/it.json
+++ b/Apptag/Localization/it.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Esportazione fallita",
"settings.import": "Importa categorie e layout…",
"settings.backupDesc": "L'esportazione salva categorie e layout in un file JSON. L'importazione sostituisce la configurazione attuale.",
+ "settings.uncommonBubble": "Bolle per app poco usate",
+ "settings.uncommonBubbleDesc": "Mostra una grande bolla con nome e nota solo per le app segnate come poco usate.",
"settings.importFailed": "Importazione fallita",
"edit.exit": "Esci dalla modifica",
"edit.tags": "Modifica tag",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Non categorizzato",
"group.appleBuiltIn": "Integrate",
+ "group.uncommon": "Poco usate",
"drop.systemDefaultWarning": "Non trascinare nelle categorie di sistema predefinite",
"tag.design": "Design",
"tag.development": "Sviluppo",
@@ -64,5 +67,22 @@
"tag.productivity": "Ufficio",
"settings.coloredContainer": "Contenitore colorato",
"settings.gridContainer": "Griglia senza colore",
- "settings.coloredGridContainer": "Griglia colorata"
+ "settings.coloredGridContainer": "Griglia colorata",
+ "appNote.edit": "Modifica nota d’uso…",
+ "appNote.placeholder": "Nota d’uso dell’app",
+ "edit.modeAdd": "Aggiungi tag",
+ "edit.modeRemove": "Rimuovi tag",
+ "edit.modeHint": "I segni di spunta a sinistra valgono solo per questa operazione.",
+ "edit.currentTags": "Tag attuali",
+ "edit.commonTags": "Tag condivisi",
+ "edit.selectionHint": "Seleziona le app a destra, poi scegli a sinistra i tag da aggiungere o rimuovere.",
+ "edit.noAssignedTags": "L'app selezionata non ha ancora tag.",
+ "edit.noCommonTags": "Le app selezionate non hanno tag in comune.",
+ "edit.mixedTagsHint": "Gli altri tag variano tra le app selezionate.",
+ "edit.operationLabel": "Azione:",
+ "edit.confirmAdd": "Conferma aggiunta",
+ "edit.confirmRemove": "Conferma rimozione",
+ "edit.feedbackAddFormat": "Questa operazione ha aggiunto %tagCount% tag a %appCount% app (%appNames%).",
+ "edit.feedbackRemoveFormat": "Questa operazione ha rimosso %tagCount% tag da %appCount% app (%appNames%). Tag rimossi: %tagNames%.",
+ "edit.removeHint": "Qui puoi deselezionare solo i tag gia esistenti. I tag che mostrano un'icona rossa di eliminazione verranno rimossi dopo la conferma."
}
diff --git a/Apptag/Localization/ja.json b/Apptag/Localization/ja.json
index 146eb1d..0ab1749 100644
--- a/Apptag/Localization/ja.json
+++ b/Apptag/Localization/ja.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "エクスポート失敗",
"settings.import": "カテゴリとレイアウトをインポート…",
"settings.backupDesc": "エクスポートはカテゴリとレイアウトをJSONファイルに保存します。インポートは現在の設定をファイルの内容で置き換えます。",
+ "settings.uncommonBubble": "あまり使わないアプリの吹き出し",
+ "settings.uncommonBubbleDesc": "あまり使わないとマークしたアプリだけに、大きな名前と用途メモを表示します。",
"settings.importFailed": "インポート失敗",
"edit.exit": "編集を終了",
"edit.tags": "タグを編集",
@@ -54,6 +56,7 @@
"app.build": "ビルド",
"group.uncategorized": "未分類",
"group.appleBuiltIn": "Mac内蔵",
+ "group.uncommon": "あまり使わない",
"drop.systemDefaultWarning": "システム既定カテゴリにはドラッグできません",
"tag.design": "デザイン",
"tag.development": "プログラミング",
@@ -64,5 +67,22 @@
"tag.productivity": "オフィス",
"settings.coloredContainer": "カラーコンテナ",
"settings.gridContainer": "無色グリッド",
- "settings.coloredGridContainer": "カラーグリッド"
+ "settings.coloredGridContainer": "カラーグリッド",
+ "appNote.edit": "用途メモを編集…",
+ "appNote.placeholder": "アプリの用途メモ",
+ "edit.modeAdd": "タグを追加",
+ "edit.modeRemove": "タグを削除",
+ "edit.modeHint": "左側のチェックは今回の操作にのみ適用されます。",
+ "edit.currentTags": "現在のタグ",
+ "edit.commonTags": "共通タグ",
+ "edit.selectionHint": "右側でアプリを選択し、左側で今回追加または削除するタグを選んでください。",
+ "edit.noAssignedTags": "選択したアプリにはまだタグがありません。",
+ "edit.noCommonTags": "選択したアプリに共通するタグはありません。",
+ "edit.mixedTagsHint": "そのほかのタグは選択したアプリごとに異なります。",
+ "edit.operationLabel": "今回の操作:",
+ "edit.confirmAdd": "追加を確認",
+ "edit.confirmRemove": "削除を確認",
+ "edit.feedbackAddFormat": "今回の操作で、%appCount%個のアプリ(%appNames%)に%tagCount%個のタグを追加しました。",
+ "edit.feedbackRemoveFormat": "今回の操作で、%appCount%個のアプリ(%appNames%)から%tagCount%個のタグを削除しました。削除したタグ: %tagNames%。",
+ "edit.removeHint": "ここでは既存のタグだけチェックを外せます。赤い削除アイコンが表示されたタグは、確認後に削除されます。"
}
diff --git a/Apptag/Localization/ko.json b/Apptag/Localization/ko.json
index 0159799..a6696a1 100644
--- a/Apptag/Localization/ko.json
+++ b/Apptag/Localization/ko.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "내보내기 실패",
"settings.import": "분류와 레이아웃 가져오기…",
"settings.backupDesc": "내보내기는 분류와 레이아웃을 JSON 파일로 저장합니다. 가져오기는 파일의 내용으로 현재 설정을 대체합니다.",
+ "settings.uncommonBubble": "자주 쓰지 않는 앱 말풍선",
+ "settings.uncommonBubbleDesc": "자주 쓰지 않음으로 표시한 앱에만 큰 이름과 메모 말풍선을 표시합니다.",
"settings.importFailed": "가져오기 실패",
"edit.exit": "편집 종료",
"edit.tags": "태그 편집",
@@ -54,6 +56,7 @@
"app.build": "빌드",
"group.uncategorized": "미분류",
"group.appleBuiltIn": "Mac 내장",
+ "group.uncommon": "자주 쓰지 않음",
"drop.systemDefaultWarning": "시스템 기본 분류로 드래그하지 마세요",
"tag.design": "디자인",
"tag.development": "프로그래밍",
@@ -64,5 +67,22 @@
"tag.productivity": "오피스",
"settings.coloredContainer": "색상 컨테이너",
"settings.gridContainer": "무색 그리드",
- "settings.coloredGridContainer": "색상 그리드"
+ "settings.coloredGridContainer": "색상 그리드",
+ "appNote.edit": "용도 메모 편집…",
+ "appNote.placeholder": "앱 용도 메모",
+ "edit.modeAdd": "태그 추가",
+ "edit.modeRemove": "태그 제거",
+ "edit.modeHint": "왼쪽 체크는 이번 작업에만 적용됩니다.",
+ "edit.currentTags": "현재 태그",
+ "edit.commonTags": "공통 태그",
+ "edit.selectionHint": "오른쪽에서 앱을 선택한 뒤, 왼쪽에서 이번에 추가하거나 제거할 태그를 고르세요.",
+ "edit.noAssignedTags": "선택한 앱에는 아직 태그가 없습니다.",
+ "edit.noCommonTags": "선택한 앱들 사이에 공통 태그가 없습니다.",
+ "edit.mixedTagsHint": "다른 태그는 선택한 앱마다 서로 다릅니다.",
+ "edit.operationLabel": "이번 작업:",
+ "edit.confirmAdd": "추가 확인",
+ "edit.confirmRemove": "제거 확인",
+ "edit.feedbackAddFormat": "이번 작업으로 %appCount%개 앱(%appNames%)에 태그 %tagCount%개가 새로 추가되었습니다.",
+ "edit.feedbackRemoveFormat": "이번 작업으로 %appCount%개 앱(%appNames%)에서 태그 %tagCount%개가 제거되었습니다. 제거된 태그: %tagNames%.",
+ "edit.removeHint": "여기서는 이미 있는 태그만 체크 해제할 수 있습니다. 빨간 삭제 아이콘이 보이는 태그는 확인 후 제거됩니다."
}
diff --git a/Apptag/Localization/ms.json b/Apptag/Localization/ms.json
index d547d15..1a94997 100644
--- a/Apptag/Localization/ms.json
+++ b/Apptag/Localization/ms.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Ekspor gagal",
"settings.import": "Import kategori & susun atur…",
"settings.backupDesc": "Eksport menyimpan kategori dan susun atur ke fail JSON. Import menggantikan tetapan semasa dengan fail.",
+ "settings.uncommonBubble": "Gelembung app jarang digunakan",
+ "settings.uncommonBubbleDesc": "Tunjukkan gelembung nama besar dan nota hanya untuk app yang ditanda jarang digunakan.",
"settings.importFailed": "Impor gagal",
"edit.exit": "Keluar dari pengeditan",
"edit.tags": "Edit tag",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Tidak berkategori",
"group.appleBuiltIn": "Terbina dalam",
+ "group.uncommon": "Jarang digunakan",
"drop.systemDefaultWarning": "Jangan seret app ke kategori bawaan sistem",
"tag.design": "Desain",
"tag.development": "Pengembangan",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitas",
"settings.coloredContainer": "Kontainer berwarna",
"settings.gridContainer": "Grid tanpa warna",
- "settings.coloredGridContainer": "Grid berwarna"
+ "settings.coloredGridContainer": "Grid berwarna",
+ "appNote.edit": "Edit nota kegunaan…",
+ "appNote.placeholder": "Nota kegunaan app",
+ "edit.modeAdd": "Tambah tag",
+ "edit.modeRemove": "Buang tag",
+ "edit.modeHint": "Tanda di sebelah kiri hanya digunakan untuk operasi ini.",
+ "edit.currentTags": "Tag semasa",
+ "edit.commonTags": "Tag bersama",
+ "edit.selectionHint": "Pilih app di sebelah kanan, kemudian pilih di sebelah kiri tag yang mahu ditambah atau dibuang.",
+ "edit.noAssignedTags": "App yang dipilih belum mempunyai tag.",
+ "edit.noCommonTags": "App yang dipilih tidak mempunyai tag bersama.",
+ "edit.mixedTagsHint": "Tag lain berbeza antara app yang dipilih.",
+ "edit.operationLabel": "Tindakan:",
+ "edit.confirmAdd": "Sahkan tambah",
+ "edit.confirmRemove": "Sahkan buang",
+ "edit.feedbackAddFormat": "Operasi ini menambah %tagCount% tag pada %appCount% app (%appNames%).",
+ "edit.feedbackRemoveFormat": "Operasi ini membuang %tagCount% tag daripada %appCount% app (%appNames%). Tag yang dibuang: %tagNames%.",
+ "edit.removeHint": "Di sini hanya tag yang sedia ada boleh dinyah tanda. Tag yang memaparkan ikon padam merah akan dibuang selepas pengesahan."
}
diff --git a/Apptag/Localization/nb.json b/Apptag/Localization/nb.json
index 02c1e47..149c38b 100644
--- a/Apptag/Localization/nb.json
+++ b/Apptag/Localization/nb.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Eksport mislyktes",
"settings.import": "Importer kategorier og layout…",
"settings.backupDesc": "Eksport lagrer kategorier og layout i en JSON-fil. Import erstatter gjeldende oppsett med en fil.",
+ "settings.uncommonBubble": "Bobler for lite brukte apper",
+ "settings.uncommonBubbleDesc": "Viser stor navn- og notatboble bare for apper merket som lite brukt.",
"settings.importFailed": "Import mislyktes",
"edit.exit": "Avslutt redigering",
"edit.tags": "Rediger etiketter",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Ukategorisert",
"group.appleBuiltIn": "Innebygd",
+ "group.uncommon": "Lite brukt",
"drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
"tag.design": "Design",
"tag.development": "Utvikling",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitet",
"settings.coloredContainer": "Farget beholder",
"settings.gridContainer": "Fargeløst rutenett",
- "settings.coloredGridContainer": "Farget rutenett"
+ "settings.coloredGridContainer": "Farget rutenett",
+ "appNote.edit": "Rediger bruksnotat…",
+ "appNote.placeholder": "Bruksnotat for app",
+ "edit.modeAdd": "Legg til tagger",
+ "edit.modeRemove": "Fjern tagger",
+ "edit.modeHint": "Avkrysningene til venstre gjelder bare for denne handlingen.",
+ "edit.currentTags": "Gjeldende tagger",
+ "edit.commonTags": "Felles tagger",
+ "edit.selectionHint": "Velg apper til hoyre, og velg deretter til venstre hvilke tagger som skal legges til eller fjernes.",
+ "edit.noAssignedTags": "Den valgte appen har ingen tagger ennå.",
+ "edit.noCommonTags": "De valgte appene har ingen felles tagger.",
+ "edit.mixedTagsHint": "Andre tagger varierer mellom de valgte appene.",
+ "edit.operationLabel": "Handling:",
+ "edit.confirmAdd": "Bekreft legg til",
+ "edit.confirmRemove": "Bekreft fjern",
+ "edit.feedbackAddFormat": "Denne handlingen la til %tagCount% tagger i %appCount% apper (%appNames%).",
+ "edit.feedbackRemoveFormat": "Denne handlingen fjernet %tagCount% tagger fra %appCount% apper (%appNames%). Fjernede tagger: %tagNames%.",
+ "edit.removeHint": "Her kan bare eksisterende tagger fjernes fra avkrysningen. Tagger med rodt sletteikon blir fjernet etter bekreftelse."
}
diff --git a/Apptag/Localization/nl.json b/Apptag/Localization/nl.json
index 2c1fcf8..8fec88c 100644
--- a/Apptag/Localization/nl.json
+++ b/Apptag/Localization/nl.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Export mislukt",
"settings.import": "Categorieën en lay-out importeren…",
"settings.backupDesc": "Export slaat categorieën en lay-out op in een JSON-bestand. Import vervangt de huidige instelling door een bestand.",
+ "settings.uncommonBubble": "Bubbels voor weinig gebruikte apps",
+ "settings.uncommonBubbleDesc": "Toont een grote naam- en notitiebubbel alleen voor apps die als weinig gebruikt zijn gemarkeerd.",
"settings.importFailed": "Import mislukt",
"edit.exit": "Bewerken afsluiten",
"edit.tags": "Tags bewerken",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Niet gecategoriseerd",
"group.appleBuiltIn": "Ingebouwd",
+ "group.uncommon": "Weinig gebruikt",
"drop.systemDefaultWarning": "Sleep apps niet naar standaard systeemcategorieën",
"tag.design": "Ontwerp",
"tag.development": "Ontwikkeling",
@@ -64,5 +67,22 @@
"tag.productivity": "Productiviteit",
"settings.coloredContainer": "Gekleurde container",
"settings.gridContainer": "Kleurloos raster",
- "settings.coloredGridContainer": "Gekleurd raster"
+ "settings.coloredGridContainer": "Gekleurd raster",
+ "appNote.edit": "Gebruiksnotitie bewerken…",
+ "appNote.placeholder": "Gebruiksnotitie voor app",
+ "edit.modeAdd": "Tags toevoegen",
+ "edit.modeRemove": "Tags verwijderen",
+ "edit.modeHint": "De vinkjes links gelden alleen voor deze bewerking.",
+ "edit.currentTags": "Huidige tags",
+ "edit.commonTags": "Gedeelde tags",
+ "edit.selectionHint": "Selecteer rechts apps en kies links de tags die je wilt toevoegen of verwijderen.",
+ "edit.noAssignedTags": "De geselecteerde app heeft nog geen tags.",
+ "edit.noCommonTags": "De geselecteerde apps hebben geen gedeelde tags.",
+ "edit.mixedTagsHint": "Andere tags verschillen per geselecteerde app.",
+ "edit.operationLabel": "Actie:",
+ "edit.confirmAdd": "Toevoegen bevestigen",
+ "edit.confirmRemove": "Verwijderen bevestigen",
+ "edit.feedbackAddFormat": "Deze actie heeft %tagCount% tags toegevoegd aan %appCount% apps (%appNames%).",
+ "edit.feedbackRemoveFormat": "Deze actie heeft %tagCount% tags verwijderd van %appCount% apps (%appNames%). Verwijderde tags: %tagNames%.",
+ "edit.removeHint": "Hier kun je alleen bestaande tags uitvinken. Tags met een rood verwijderpictogram worden na bevestiging verwijderd."
}
diff --git a/Apptag/Localization/nn.json b/Apptag/Localization/nn.json
index 155bf42..da9202c 100644
--- a/Apptag/Localization/nn.json
+++ b/Apptag/Localization/nn.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Eksport mislyktes",
"settings.import": "Importer kategorier og layout…",
"settings.backupDesc": "Eksport lagrer kategorier og layout i en JSON-fil. Import erstatter gjeldende oppsett med en fil.",
+ "settings.uncommonBubble": "Bobler for lite brukte appar",
+ "settings.uncommonBubbleDesc": "Viser stor namn- og notatboble berre for appar merkte som lite brukt.",
"settings.importFailed": "Import mislyktes",
"edit.exit": "Avslutt redigering",
"edit.tags": "Rediger etiketter",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Ukategorisert",
"group.appleBuiltIn": "Innebygd",
+ "group.uncommon": "Lite brukt",
"drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
"tag.design": "Design",
"tag.development": "Utvikling",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitet",
"settings.coloredContainer": "Farget beholder",
"settings.gridContainer": "Fargeløst rutenett",
- "settings.coloredGridContainer": "Farget rutenett"
+ "settings.coloredGridContainer": "Farget rutenett",
+ "appNote.edit": "Rediger bruksnotat…",
+ "appNote.placeholder": "Bruksnotat for app",
+ "edit.modeAdd": "Legg til taggar",
+ "edit.modeRemove": "Fjern taggar",
+ "edit.modeHint": "Avkryssingane til venstre gjeld berre for denne handlinga.",
+ "edit.currentTags": "Noverande taggar",
+ "edit.commonTags": "Felles taggar",
+ "edit.selectionHint": "Vel appar til hogre, og vel deretter til venstre kva taggar som skal leggjast til eller fjernast.",
+ "edit.noAssignedTags": "Den valde appen har ingen taggar enno.",
+ "edit.noCommonTags": "Dei valde appane har ingen felles taggar.",
+ "edit.mixedTagsHint": "Andre taggar varierer mellom dei valde appane.",
+ "edit.operationLabel": "Handling:",
+ "edit.confirmAdd": "Stadfest legg til",
+ "edit.confirmRemove": "Stadfest fjern",
+ "edit.feedbackAddFormat": "Denne handlinga la til %tagCount% taggar i %appCount% appar (%appNames%).",
+ "edit.feedbackRemoveFormat": "Denne handlinga fjerna %tagCount% taggar fra %appCount% appar (%appNames%). Fjerna taggar: %tagNames%.",
+ "edit.removeHint": "Her kan berre eksisterande taggar fjernast frå avkryssinga. Taggar med raudt sletteikon blir fjerna etter stadfesting."
}
diff --git a/Apptag/Localization/no.json b/Apptag/Localization/no.json
index 02c1e47..149c38b 100644
--- a/Apptag/Localization/no.json
+++ b/Apptag/Localization/no.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Eksport mislyktes",
"settings.import": "Importer kategorier og layout…",
"settings.backupDesc": "Eksport lagrer kategorier og layout i en JSON-fil. Import erstatter gjeldende oppsett med en fil.",
+ "settings.uncommonBubble": "Bobler for lite brukte apper",
+ "settings.uncommonBubbleDesc": "Viser stor navn- og notatboble bare for apper merket som lite brukt.",
"settings.importFailed": "Import mislyktes",
"edit.exit": "Avslutt redigering",
"edit.tags": "Rediger etiketter",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Ukategorisert",
"group.appleBuiltIn": "Innebygd",
+ "group.uncommon": "Lite brukt",
"drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
"tag.design": "Design",
"tag.development": "Utvikling",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitet",
"settings.coloredContainer": "Farget beholder",
"settings.gridContainer": "Fargeløst rutenett",
- "settings.coloredGridContainer": "Farget rutenett"
+ "settings.coloredGridContainer": "Farget rutenett",
+ "appNote.edit": "Rediger bruksnotat…",
+ "appNote.placeholder": "Bruksnotat for app",
+ "edit.modeAdd": "Legg til tagger",
+ "edit.modeRemove": "Fjern tagger",
+ "edit.modeHint": "Avkrysningene til venstre gjelder bare for denne handlingen.",
+ "edit.currentTags": "Gjeldende tagger",
+ "edit.commonTags": "Felles tagger",
+ "edit.selectionHint": "Velg apper til hoyre, og velg deretter til venstre hvilke tagger som skal legges til eller fjernes.",
+ "edit.noAssignedTags": "Den valgte appen har ingen tagger ennå.",
+ "edit.noCommonTags": "De valgte appene har ingen felles tagger.",
+ "edit.mixedTagsHint": "Andre tagger varierer mellom de valgte appene.",
+ "edit.operationLabel": "Handling:",
+ "edit.confirmAdd": "Bekreft legg til",
+ "edit.confirmRemove": "Bekreft fjern",
+ "edit.feedbackAddFormat": "Denne handlingen la til %tagCount% tagger i %appCount% apper (%appNames%).",
+ "edit.feedbackRemoveFormat": "Denne handlingen fjernet %tagCount% tagger fra %appCount% apper (%appNames%). Fjernede tagger: %tagNames%.",
+ "edit.removeHint": "Her kan bare eksisterende tagger fjernes fra avkrysningen. Tagger med rodt sletteikon blir fjernet etter bekreftelse."
}
diff --git a/Apptag/Localization/pl.json b/Apptag/Localization/pl.json
index de7c8f6..f293449 100644
--- a/Apptag/Localization/pl.json
+++ b/Apptag/Localization/pl.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Eksport nieudany",
"settings.import": "Importuj kategorie i układ…",
"settings.backupDesc": "Eksport zapisuje kategorie i układ do pliku JSON. Import zastępuje bieżącą konfigurację plikiem.",
+ "settings.uncommonBubble": "Dymki dla rzadko używanych aplikacji",
+ "settings.uncommonBubbleDesc": "Pokazuje dużą nazwę i notatkę tylko dla aplikacji oznaczonych jako rzadko używane.",
"settings.importFailed": "Import nieudany",
"edit.exit": "Zakończ edycję",
"edit.tags": "Edytuj tagi",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Bez kategorii",
"group.appleBuiltIn": "Wbudowane",
+ "group.uncommon": "Rzadko używane",
"drop.systemDefaultWarning": "Nie przeciągaj aplikacji do domyślnych kategorii systemu",
"tag.design": "Projektowanie",
"tag.development": "Programowanie",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktywność",
"settings.coloredContainer": "Kolorowy kontener",
"settings.gridContainer": "Bezbarwna siatka",
- "settings.coloredGridContainer": "Kolorowa siatka"
+ "settings.coloredGridContainer": "Kolorowa siatka",
+ "appNote.edit": "Edytuj notatkę o przeznaczeniu…",
+ "appNote.placeholder": "Notatka o przeznaczeniu aplikacji",
+ "edit.modeAdd": "Dodaj tagi",
+ "edit.modeRemove": "Usun tagi",
+ "edit.modeHint": "Zaznaczenia po lewej dotycza tylko tej operacji.",
+ "edit.currentTags": "Biezace tagi",
+ "edit.commonTags": "Wspolne tagi",
+ "edit.selectionHint": "Wybierz aplikacje po prawej, a nastepnie po lewej wybierz tagi do dodania lub usuniecia.",
+ "edit.noAssignedTags": "Wybrana aplikacja nie ma jeszcze tagow.",
+ "edit.noCommonTags": "Wybrane aplikacje nie maja wspolnych tagow.",
+ "edit.mixedTagsHint": "Pozostale tagi roznia sie miedzy wybranymi aplikacjami.",
+ "edit.operationLabel": "Dzialanie:",
+ "edit.confirmAdd": "Potwierdz dodanie",
+ "edit.confirmRemove": "Potwierdz usuniecie",
+ "edit.feedbackAddFormat": "Ta operacja dodala %tagCount% tagow do %appCount% aplikacji (%appNames%).",
+ "edit.feedbackRemoveFormat": "Ta operacja usunela %tagCount% tagow z %appCount% aplikacji (%appNames%). Usuniete tagi: %tagNames%.",
+ "edit.removeHint": "Tutaj mozna odznaczyc tylko istniejace tagi. Tagi z czerwona ikona usuniecia zostana usuniete po potwierdzeniu."
}
diff --git a/Apptag/Localization/pt-BR.json b/Apptag/Localization/pt-BR.json
index 35f43e9..24501ef 100644
--- a/Apptag/Localization/pt-BR.json
+++ b/Apptag/Localization/pt-BR.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Falha ao exportar",
"settings.import": "Importar categorias e layout…",
"settings.backupDesc": "A exportação salva categorias e layout em um arquivo JSON. A importação substitui a configuração atual por um arquivo.",
+ "settings.uncommonBubble": "Balões para apps pouco usados",
+ "settings.uncommonBubbleDesc": "Mostra um balão grande com nome e nota apenas para apps marcados como pouco usados.",
"settings.importFailed": "Falha ao importar",
"edit.exit": "Sair da edição",
"edit.tags": "Editar etiquetas",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Sem categoria",
"group.appleBuiltIn": "Integrados",
+ "group.uncommon": "Pouco usados",
"drop.systemDefaultWarning": "Não arraste apps para categorias padrão do sistema",
"tag.design": "Design",
"tag.development": "Desenvolvimento",
@@ -64,5 +67,22 @@
"tag.productivity": "Produtividade",
"settings.coloredContainer": "Contêiner colorido",
"settings.gridContainer": "Grade sem cor",
- "settings.coloredGridContainer": "Grade colorida"
+ "settings.coloredGridContainer": "Grade colorida",
+ "appNote.edit": "Editar nota de uso…",
+ "appNote.placeholder": "Nota de uso do app",
+ "edit.modeAdd": "Adicionar tags",
+ "edit.modeRemove": "Remover tags",
+ "edit.modeHint": "As marcacoes a esquerda valem apenas para esta operacao.",
+ "edit.currentTags": "Tags atuais",
+ "edit.commonTags": "Tags em comum",
+ "edit.selectionHint": "Selecione apps a direita e depois escolha a esquerda as tags para adicionar ou remover.",
+ "edit.noAssignedTags": "O app selecionado ainda nao tem tags.",
+ "edit.noCommonTags": "Os apps selecionados nao tem tags em comum.",
+ "edit.mixedTagsHint": "As outras tags diferem entre os apps selecionados.",
+ "edit.operationLabel": "Acao:",
+ "edit.confirmAdd": "Confirmar adicao",
+ "edit.confirmRemove": "Confirmar remocao",
+ "edit.feedbackAddFormat": "Esta operacao adicionou %tagCount% tags em %appCount% apps (%appNames%).",
+ "edit.feedbackRemoveFormat": "Esta operacao removeu %tagCount% tags de %appCount% apps (%appNames%). Tags removidas: %tagNames%.",
+ "edit.removeHint": "Aqui so e possivel desmarcar tags ja existentes. As tags com icone vermelha de remocao serao removidas apos a confirmacao."
}
diff --git a/Apptag/Localization/ro.json b/Apptag/Localization/ro.json
index 6f2fa9f..4b099ff 100644
--- a/Apptag/Localization/ro.json
+++ b/Apptag/Localization/ro.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Export eșuat",
"settings.import": "Importă categorii și aspect…",
"settings.backupDesc": "Exportul salvează categoriile și aspectul într-un fișier JSON. Importul înlocuiește configurația curentă cu un fișier.",
+ "settings.uncommonBubble": "Bule pentru aplicații folosite rar",
+ "settings.uncommonBubbleDesc": "Afișează o bulă mare cu nume și notă doar pentru aplicațiile marcate ca folosite rar.",
"settings.importFailed": "Import eșuat",
"edit.exit": "Ieși din editare",
"edit.tags": "Editează etichete",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Necategorizat",
"group.appleBuiltIn": "Integrat",
+ "group.uncommon": "Folosit rar",
"drop.systemDefaultWarning": "Nu trage aplicații în categoriile implicite ale sistemului",
"tag.design": "Design",
"tag.development": "Dezvoltare",
@@ -64,5 +67,22 @@
"tag.productivity": "Productivitate",
"settings.coloredContainer": "Container colorat",
"settings.gridContainer": "Grilă fără culoare",
- "settings.coloredGridContainer": "Grilă colorată"
+ "settings.coloredGridContainer": "Grilă colorată",
+ "appNote.edit": "Editează nota de utilizare…",
+ "appNote.placeholder": "Notă de utilizare a aplicației",
+ "edit.modeAdd": "Adauga etichete",
+ "edit.modeRemove": "Elimina etichete",
+ "edit.modeHint": "Bifele din stanga se aplica doar acestei operatii.",
+ "edit.currentTags": "Etichete curente",
+ "edit.commonTags": "Etichete comune",
+ "edit.selectionHint": "Selecteaza aplicatii in dreapta, apoi alege in stanga etichetele pe care vrei sa le adaugi sau sa le elimini.",
+ "edit.noAssignedTags": "Aplicatia selectata nu are inca etichete.",
+ "edit.noCommonTags": "Aplicatiile selectate nu au etichete comune.",
+ "edit.mixedTagsHint": "Celelalte etichete difera intre aplicatiile selectate.",
+ "edit.operationLabel": "Actiune:",
+ "edit.confirmAdd": "Confirma adaugarea",
+ "edit.confirmRemove": "Confirma eliminarea",
+ "edit.feedbackAddFormat": "Aceasta actiune a adaugat %tagCount% etichete in %appCount% aplicatii (%appNames%).",
+ "edit.feedbackRemoveFormat": "Aceasta actiune a eliminat %tagCount% etichete din %appCount% aplicatii (%appNames%). Etichete eliminate: %tagNames%.",
+ "edit.removeHint": "Aici pot fi debifate doar etichetele deja existente. Etichetele care afiseaza o pictograma rosie de stergere vor fi eliminate dupa confirmare."
}
diff --git a/Apptag/Localization/ru.json b/Apptag/Localization/ru.json
index 71801d2..ac7856a 100644
--- a/Apptag/Localization/ru.json
+++ b/Apptag/Localization/ru.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Ошибка экспорта",
"settings.import": "Импорт категорий и макета…",
"settings.backupDesc": "Экспорт сохраняет категории и макет в JSON-файл. Импорт заменяет текущие настройки данными из файла.",
+ "settings.uncommonBubble": "Подсказки для редких приложений",
+ "settings.uncommonBubbleDesc": "Показывает большое имя и заметку только для приложений, отмеченных как редко используемые.",
"settings.importFailed": "Ошибка импорта",
"edit.exit": "Выйти из редактирования",
"edit.tags": "Редактировать теги",
@@ -54,6 +56,7 @@
"app.build": "Сборка",
"group.uncategorized": "Без категории",
"group.appleBuiltIn": "Встроенные",
+ "group.uncommon": "Редко используется",
"drop.systemDefaultWarning": "Не перетаскивайте в системные категории по умолчанию",
"tag.design": "Дизайн",
"tag.development": "Разработка",
@@ -64,5 +67,22 @@
"tag.productivity": "Офис",
"settings.coloredContainer": "Цветной контейнер",
"settings.gridContainer": "Бесцветная сетка",
- "settings.coloredGridContainer": "Цветная сетка"
+ "settings.coloredGridContainer": "Цветная сетка",
+ "appNote.edit": "Изменить заметку о назначении…",
+ "appNote.placeholder": "Заметка о назначении приложения",
+ "edit.modeAdd": "Добавить теги",
+ "edit.modeRemove": "Удалить теги",
+ "edit.modeHint": "Отметки слева применяются только к этой операции.",
+ "edit.currentTags": "Текущие теги",
+ "edit.commonTags": "Общие теги",
+ "edit.selectionHint": "Выберите приложения справа, затем слева отметьте теги, которые нужно добавить или удалить.",
+ "edit.noAssignedTags": "У выбранного приложения пока нет тегов.",
+ "edit.noCommonTags": "У выбранных приложений нет общих тегов.",
+ "edit.mixedTagsHint": "Остальные теги различаются у выбранных приложений.",
+ "edit.operationLabel": "Действие:",
+ "edit.confirmAdd": "Подтвердить добавление",
+ "edit.confirmRemove": "Подтвердить удаление",
+ "edit.feedbackAddFormat": "Эта операция добавила %tagCount% тегов для %appCount% приложений (%appNames%).",
+ "edit.feedbackRemoveFormat": "Эта операция удалила %tagCount% тегов у %appCount% приложений (%appNames%). Удаленные теги: %tagNames%.",
+ "edit.removeHint": "Здесь можно снять отметку только с уже существующих тегов. Теги с красным значком удаления будут удалены после подтверждения."
}
diff --git a/Apptag/Localization/sr-Cyrl.json b/Apptag/Localization/sr-Cyrl.json
index b5a1238..2016fec 100644
--- a/Apptag/Localization/sr-Cyrl.json
+++ b/Apptag/Localization/sr-Cyrl.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Извоз није успео",
"settings.import": "Увези категорије и распоред…",
"settings.backupDesc": "Извоз чува категорије и распоред у JSON датотеку. Увоз замењује тренутна подешавања датотеком.",
+ "settings.uncommonBubble": "Облачићи за ретке апликације",
+ "settings.uncommonBubbleDesc": "Приказује велико име и белешку само за апликације означене као ретко коришћене.",
"settings.importFailed": "Увоз није успео",
"edit.exit": "Изађи из уређивања",
"edit.tags": "Уреди ознаке",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Некатегорисано",
"group.appleBuiltIn": "Уграђено",
+ "group.uncommon": "Ретко коришћено",
"drop.systemDefaultWarning": "Не превлачите апликације у подразумеване системске категорије",
"tag.design": "Дизајн",
"tag.development": "Развој",
@@ -64,5 +67,22 @@
"tag.productivity": "Продуктивност",
"settings.coloredContainer": "Обојени контејнер",
"settings.gridContainer": "Безбојна мрежа",
- "settings.coloredGridContainer": "Обојена мрежа"
+ "settings.coloredGridContainer": "Обојена мрежа",
+ "appNote.edit": "Уреди белешку о намени…",
+ "appNote.placeholder": "Белешка о намени апликације",
+ "edit.modeAdd": "Додај ознаке",
+ "edit.modeRemove": "Уклони ознаке",
+ "edit.modeHint": "Означавање лево важи само за ову радњу.",
+ "edit.currentTags": "Тренутне ознаке",
+ "edit.commonTags": "Заједничке ознаке",
+ "edit.selectionHint": "Изаберите апликације десно, па лево изаберите ознаке које желите да додате или уклоните.",
+ "edit.noAssignedTags": "Изабрана апликација још нема ознаке.",
+ "edit.noCommonTags": "Изабране апликације немају заједничке ознаке.",
+ "edit.mixedTagsHint": "Остале ознаке се разликују између изабраних апликација.",
+ "edit.operationLabel": "Радња:",
+ "edit.confirmAdd": "Потврди додавање",
+ "edit.confirmRemove": "Потврди уклањање",
+ "edit.feedbackAddFormat": "Ова радња је додала %tagCount% ознака за %appCount% апликација (%appNames%).",
+ "edit.feedbackRemoveFormat": "Ова радња је уклонила %tagCount% ознака са %appCount% апликација (%appNames%). Уклоњене ознаке: %tagNames%.",
+ "edit.removeHint": "Овде можете одчекирати само постојеће ознаке. Ознаке са црвеном иконицом брисања биће уклоњене након потврде."
}
diff --git a/Apptag/Localization/sv.json b/Apptag/Localization/sv.json
index 13ab7f0..044d869 100644
--- a/Apptag/Localization/sv.json
+++ b/Apptag/Localization/sv.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Export misslyckades",
"settings.import": "Importera kategorier och layout…",
"settings.backupDesc": "Export sparar kategorier och layout till en JSON-fil. Import ersätter den aktuella konfigurationen med en fil.",
+ "settings.uncommonBubble": "Bubblor för sällan använda appar",
+ "settings.uncommonBubbleDesc": "Visar en stor namn- och anteckningsbubbla endast för appar markerade som sällan använda.",
"settings.importFailed": "Import misslyckades",
"edit.exit": "Avsluta redigering",
"edit.tags": "Redigera taggar",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Okategoriserat",
"group.appleBuiltIn": "Inbyggt",
+ "group.uncommon": "Sällan använd",
"drop.systemDefaultWarning": "Dra inte appar till systemets standardkategorier",
"tag.design": "Design",
"tag.development": "Utveckling",
@@ -64,5 +67,22 @@
"tag.productivity": "Produktivitet",
"settings.coloredContainer": "Färgad behållare",
"settings.gridContainer": "Färglöst rutnät",
- "settings.coloredGridContainer": "Färgat rutnät"
+ "settings.coloredGridContainer": "Färgat rutnät",
+ "appNote.edit": "Redigera användningsanteckning…",
+ "appNote.placeholder": "Användningsanteckning för app",
+ "edit.modeAdd": "Lagg till taggar",
+ "edit.modeRemove": "Ta bort taggar",
+ "edit.modeHint": "Markeringarna till vanster galler bara for den har atgarden.",
+ "edit.currentTags": "Nuvarande taggar",
+ "edit.commonTags": "Gemensamma taggar",
+ "edit.selectionHint": "Valj appar till hoger och valj sedan till vanster vilka taggar som ska laggas till eller tas bort.",
+ "edit.noAssignedTags": "Den valda appen har inga taggar an.",
+ "edit.noCommonTags": "De valda apparna har inga gemensamma taggar.",
+ "edit.mixedTagsHint": "Ovriga taggar skiljer sig mellan de valda apparna.",
+ "edit.operationLabel": "Atgard:",
+ "edit.confirmAdd": "Bekrafta lagg till",
+ "edit.confirmRemove": "Bekrafta ta bort",
+ "edit.feedbackAddFormat": "Den har atgarden lade till %tagCount% taggar i %appCount% appar (%appNames%).",
+ "edit.feedbackRemoveFormat": "Den har atgarden tog bort %tagCount% taggar fran %appCount% appar (%appNames%). Borttagna taggar: %tagNames%.",
+ "edit.removeHint": "Har kan bara befintliga taggar avmarkeras. Taggar med en rod raderingsikon tas bort efter bekraftelse."
}
diff --git a/Apptag/Localization/th.json b/Apptag/Localization/th.json
index 05274ad..63b72c8 100644
--- a/Apptag/Localization/th.json
+++ b/Apptag/Localization/th.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "ส่งออกไม่สำเร็จ",
"settings.import": "นำเข้าหมวดหมู่และเลย์เอาต์…",
"settings.backupDesc": "การส่งออกจะบันทึกหมวดหมู่และเลย์เอาต์เป็นไฟล์ JSON การนำเข้าจะแทนที่การตั้งค่าปัจจุบันด้วยไฟล์",
+ "settings.uncommonBubble": "บับเบิลสำหรับแอปที่ไม่ค่อยใช้",
+ "settings.uncommonBubbleDesc": "แสดงชื่อขนาดใหญ่และบันทึกเฉพาะแอปที่ทำเครื่องหมายว่าไม่ค่อยใช้",
"settings.importFailed": "นำเข้าไม่สำเร็จ",
"edit.exit": "ออกจากการแก้ไข",
"edit.tags": "แก้ไขแท็ก",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "ไม่จัดหมวดหมู่",
"group.appleBuiltIn": "ในตัว",
+ "group.uncommon": "ไม่ค่อยใช้",
"drop.systemDefaultWarning": "อย่าลากแอปไปยังหมวดหมู่เริ่มต้นของระบบ",
"tag.design": "ออกแบบ",
"tag.development": "พัฒนา",
@@ -64,5 +67,22 @@
"tag.productivity": "งาน",
"settings.coloredContainer": "คอนเทนเนอร์มีสี",
"settings.gridContainer": "กริดไม่มีสี",
- "settings.coloredGridContainer": "กริดมีสี"
+ "settings.coloredGridContainer": "กริดมีสี",
+ "appNote.edit": "แก้ไขบันทึกการใช้งาน…",
+ "appNote.placeholder": "บันทึกการใช้งานแอป",
+ "edit.modeAdd": "เพิ่มแท็ก",
+ "edit.modeRemove": "ลบแท็ก",
+ "edit.modeHint": "เครื่องหมายทางซ้ายมีผลกับการทำงานครั้งนี้เท่านั้น",
+ "edit.currentTags": "แท็กปัจจุบัน",
+ "edit.commonTags": "แท็กร่วม",
+ "edit.selectionHint": "เลือกแอปทางขวา แล้วเลือกทางซ้ายว่าแท็กใดจะเพิ่มหรือลบในการทำครั้งนี้",
+ "edit.noAssignedTags": "แอปที่เลือกยังไม่มีแท็ก",
+ "edit.noCommonTags": "แอปที่เลือกไม่มีแท็กร่วมกัน",
+ "edit.mixedTagsHint": "แท็กอื่น ๆ ของแอปที่เลือกยังแตกต่างกัน",
+ "edit.operationLabel": "การทำงานนี้:",
+ "edit.confirmAdd": "ยืนยันการเพิ่ม",
+ "edit.confirmRemove": "ยืนยันการลบ",
+ "edit.feedbackAddFormat": "การทำงานครั้งนี้ได้เพิ่มแท็ก %tagCount% รายการให้กับแอป %appCount% แอป (%appNames%).",
+ "edit.feedbackRemoveFormat": "การทำงานครั้งนี้ได้ลบแท็ก %tagCount% รายการออกจากแอป %appCount% แอป (%appNames%). แท็กที่ลบ: %tagNames%.",
+ "edit.removeHint": "ที่นี่สามารถยกเลิกเครื่องหมายได้เฉพาะแท็กที่มีอยู่แล้วเท่านั้น แท็กที่แสดงไอคอนลบสีแดงจะถูกลบหลังจากยืนยัน"
}
diff --git a/Apptag/Localization/tr.json b/Apptag/Localization/tr.json
index 0226153..c483666 100644
--- a/Apptag/Localization/tr.json
+++ b/Apptag/Localization/tr.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Dışa aktarma başarısız",
"settings.import": "Kategorileri ve düzeni içe aktar…",
"settings.backupDesc": "Dışa aktarma kategorileri ve düzeni JSON dosyasına kaydeder. İçe aktarma mevcut kurulumu dosyadakiyle değiştirir.",
+ "settings.uncommonBubble": "Az kullanılan app balonları",
+ "settings.uncommonBubbleDesc": "Yalnızca az kullanılan olarak işaretlenen app’lerde büyük ad ve not balonu gösterir.",
"settings.importFailed": "İçe aktarma başarısız",
"edit.exit": "Düzenlemeden çık",
"edit.tags": "Etiketleri düzenle",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Kategorisiz",
"group.appleBuiltIn": "Yerleşik",
+ "group.uncommon": "Az kullanılan",
"drop.systemDefaultWarning": "Uygulamaları sistem varsayılan kategorilerine sürüklemeyin",
"tag.design": "Tasarım",
"tag.development": "Geliştirme",
@@ -64,5 +67,22 @@
"tag.productivity": "Verimlilik",
"settings.coloredContainer": "Renkli Kapsayıcı",
"settings.gridContainer": "Renksiz Izgara",
- "settings.coloredGridContainer": "Renkli Izgara"
+ "settings.coloredGridContainer": "Renkli Izgara",
+ "appNote.edit": "Kullanım notunu düzenle…",
+ "appNote.placeholder": "App kullanım notu",
+ "edit.modeAdd": "Etiket ekle",
+ "edit.modeRemove": "Etiket kaldir",
+ "edit.modeHint": "Soldaki isaretler yalnizca bu islem icin gecerlidir.",
+ "edit.currentTags": "Guncel etiketler",
+ "edit.commonTags": "Ortak etiketler",
+ "edit.selectionHint": "Saga taraftan uygulamalari secin, sonra soldan eklemek veya kaldirmak istediginiz etiketleri secin.",
+ "edit.noAssignedTags": "Secilen uygulamanin henuz etiketi yok.",
+ "edit.noCommonTags": "Secilen uygulamalarin ortak etiketi yok.",
+ "edit.mixedTagsHint": "Diger etiketler secilen uygulamalar arasinda farklilik gosterir.",
+ "edit.operationLabel": "Islem:",
+ "edit.confirmAdd": "Eklemeyi onayla",
+ "edit.confirmRemove": "Kaldirmayi onayla",
+ "edit.feedbackAddFormat": "Bu islem, %appCount% uygulamada (%appNames%) %tagCount% etiket ekledi.",
+ "edit.feedbackRemoveFormat": "Bu islem, %appCount% uygulamadan (%appNames%) %tagCount% etiketi kaldirdi. Kaldirilan etiketler: %tagNames%.",
+ "edit.removeHint": "Burada sadece mevcut etiketlerin isareti kaldirilabilir. Kirmizi silme simgesi gorunen etiketler onaydan sonra kaldirilir."
}
diff --git a/Apptag/Localization/uk.json b/Apptag/Localization/uk.json
index d11f823..d59916c 100644
--- a/Apptag/Localization/uk.json
+++ b/Apptag/Localization/uk.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Помилка експорту",
"settings.import": "Імпорт категорій і макета…",
"settings.backupDesc": "Експорт зберігає категорії та макет у JSON-файл. Імпорт замінює поточні налаштування даними з файлу.",
+ "settings.uncommonBubble": "Підказки для рідкісних програм",
+ "settings.uncommonBubbleDesc": "Показує велику назву й нотатку лише для програм, позначених як рідко використовувані.",
"settings.importFailed": "Помилка імпорту",
"edit.exit": "Вийти з редагування",
"edit.tags": "Редагувати теги",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Без категорії",
"group.appleBuiltIn": "Вбудовані",
+ "group.uncommon": "Рідко використовується",
"drop.systemDefaultWarning": "Не перетягуйте застосунки в системні категорії за замовчуванням",
"tag.design": "Дизайн",
"tag.development": "Розробка",
@@ -64,5 +67,22 @@
"tag.productivity": "Продуктивність",
"settings.coloredContainer": "Кольоровий контейнер",
"settings.gridContainer": "Безбарвна сітка",
- "settings.coloredGridContainer": "Кольорова сітка"
+ "settings.coloredGridContainer": "Кольорова сітка",
+ "appNote.edit": "Редагувати нотатку про призначення…",
+ "appNote.placeholder": "Нотатка про призначення програми",
+ "edit.modeAdd": "Додати теги",
+ "edit.modeRemove": "Вилучити теги",
+ "edit.modeHint": "Позначки ліворуч застосовуються лише до цієї дії.",
+ "edit.currentTags": "Поточні теги",
+ "edit.commonTags": "Спільні теги",
+ "edit.selectionHint": "Виберіть застосунки праворуч, а потім ліворуч позначте теги, які треба додати або вилучити.",
+ "edit.noAssignedTags": "У вибраного застосунку ще немає тегів.",
+ "edit.noCommonTags": "У вибраних застосунків немає спільних тегів.",
+ "edit.mixedTagsHint": "Інші теги відрізняються між вибраними застосунками.",
+ "edit.operationLabel": "Дія:",
+ "edit.confirmAdd": "Підтвердити додавання",
+ "edit.confirmRemove": "Підтвердити вилучення",
+ "edit.feedbackAddFormat": "Ця дія додала %tagCount% тегів для %appCount% застосунків (%appNames%).",
+ "edit.feedbackRemoveFormat": "Ця дія вилучила %tagCount% тегів у %appCount% застосунків (%appNames%). Вилучені теги: %tagNames%.",
+ "edit.removeHint": "Тут можна зняти позначку лише з наявних тегів. Теги з червоною іконкою видалення буде вилучено після підтвердження."
}
diff --git a/Apptag/Localization/vi.json b/Apptag/Localization/vi.json
index f4add4a..77caf61 100644
--- a/Apptag/Localization/vi.json
+++ b/Apptag/Localization/vi.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "Xuất thất bại",
"settings.import": "Nhập danh mục & bố cục…",
"settings.backupDesc": "Xuất sẽ lưu danh mục và bố cục thành tệp JSON. Nhập sẽ thay thế thiết lập hiện tại bằng một tệp.",
+ "settings.uncommonBubble": "Bong bóng cho app ít dùng",
+ "settings.uncommonBubbleDesc": "Chỉ hiển thị bong bóng tên lớn và ghi chú cho app được đánh dấu là ít dùng.",
"settings.importFailed": "Nhập thất bại",
"edit.exit": "Thoát chỉnh sửa",
"edit.tags": "Sửa thẻ",
@@ -54,6 +56,7 @@
"app.build": "Build",
"group.uncategorized": "Chưa phân loại",
"group.appleBuiltIn": "Tích hợp",
+ "group.uncommon": "Ít dùng",
"drop.systemDefaultWarning": "Không kéo ứng dụng vào danh mục mặc định của hệ thống",
"tag.design": "Thiết kế",
"tag.development": "Phát triển",
@@ -64,5 +67,22 @@
"tag.productivity": "Năng suất",
"settings.coloredContainer": "Vùng chứa có màu",
"settings.gridContainer": "Lưới không màu",
- "settings.coloredGridContainer": "Lưới có màu"
+ "settings.coloredGridContainer": "Lưới có màu",
+ "appNote.edit": "Sửa ghi chú mục đích…",
+ "appNote.placeholder": "Ghi chú mục đích của app",
+ "edit.modeAdd": "Them nhan",
+ "edit.modeRemove": "Xoa nhan",
+ "edit.modeHint": "Cac dau tich ben trai chi ap dung cho thao tac nay.",
+ "edit.currentTags": "Nhan hien tai",
+ "edit.commonTags": "Nhan chung",
+ "edit.selectionHint": "Chon app o ben phai, sau do chon o ben trai cac nhan can them hoac xoa.",
+ "edit.noAssignedTags": "App da chon chua co nhan nao.",
+ "edit.noCommonTags": "Cac app da chon khong co nhan chung.",
+ "edit.mixedTagsHint": "Cac nhan khac khac nhau giua cac app da chon.",
+ "edit.operationLabel": "Tac vu nay:",
+ "edit.confirmAdd": "Xac nhan them",
+ "edit.confirmRemove": "Xac nhan xoa",
+ "edit.feedbackAddFormat": "Lan nay da them %tagCount% the cho %appCount% ung dung (%appNames%).",
+ "edit.feedbackRemoveFormat": "Lan nay da go %tagCount% the khoi %appCount% ung dung (%appNames%). The da go: %tagNames%.",
+ "edit.removeHint": "Tai day chi co the bo chon cac the da ton tai. Cac the hien bieu tuong xoa mau do se bi go sau khi xac nhan."
}
diff --git a/Apptag/Localization/zh-Hans.json b/Apptag/Localization/zh-Hans.json
index 9134421..fe7722a 100644
--- a/Apptag/Localization/zh-Hans.json
+++ b/Apptag/Localization/zh-Hans.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "导出失败",
"settings.import": "导入分类与布局…",
"settings.backupDesc": "导出将分类与布局保存为 JSON 文件。导入用一个文件替换当前设置。",
+ "settings.uncommonBubble": "不常用 App 气泡提示",
+ "settings.uncommonBubbleDesc": "仅对标记为“不常用”的 App 显示大号名称与用途备忘。",
"settings.importFailed": "导入失败",
"edit.exit": "退出编辑",
"edit.tags": "编辑标签",
@@ -54,6 +56,7 @@
"app.build": "构建",
"group.uncategorized": "未分类",
"group.appleBuiltIn": "Mac自带",
+ "group.uncommon": "不常用",
"drop.systemDefaultWarning": "请勿拖动至系统默认分类",
"tag.design": "设计",
"tag.development": "编程",
@@ -64,5 +67,22 @@
"tag.productivity": "办公",
"settings.coloredContainer": "彩色容器",
"settings.gridContainer": "无色网格",
- "settings.coloredGridContainer": "彩色网格"
+ "settings.coloredGridContainer": "彩色网格",
+ "appNote.edit": "编辑用途备忘…",
+ "appNote.placeholder": "APP 用途备忘",
+ "edit.modeAdd": "添加标签",
+ "edit.modeRemove": "移除标签",
+ "edit.modeHint": "左侧勾选只作用于本次操作。",
+ "edit.currentTags": "当前标签",
+ "edit.commonTags": "共同标签",
+ "edit.selectionHint": "先在右侧选择应用,再在左侧勾选本次要添加或移除的标签。",
+ "edit.noAssignedTags": "当前选中的应用还没有标签。",
+ "edit.noCommonTags": "当前选中的这些应用没有共同标签。",
+ "edit.mixedTagsHint": "这些应用的其他标签彼此不同。",
+ "edit.operationLabel": "本轮操作:",
+ "edit.confirmAdd": "确认添加",
+ "edit.confirmRemove": "确认移除",
+ "edit.feedbackAddFormat": "本次针对%appCount%个应用(%appNames%)新增了%tagCount%个标签。",
+ "edit.feedbackRemoveFormat": "本次针对%appCount%个应用(%appNames%)移除了%tagCount%个标签(%tagNames%)。",
+ "edit.removeHint": "仅可取消已有标签的勾选;出现红色删除图标的标签会在确认后被移除。"
}
diff --git a/Apptag/Localization/zh-Hant.json b/Apptag/Localization/zh-Hant.json
index 3258d52..48d347f 100644
--- a/Apptag/Localization/zh-Hant.json
+++ b/Apptag/Localization/zh-Hant.json
@@ -33,6 +33,8 @@
"settings.exportFailed": "匯出失敗",
"settings.import": "匯入分類與版面配置…",
"settings.backupDesc": "匯出將分類與版面配置儲存為 JSON 檔案。匯入用一個檔案取代目前設定。",
+ "settings.uncommonBubble": "不常用 App 氣泡提示",
+ "settings.uncommonBubbleDesc": "僅對標記為「不常用」的 App 顯示大號名稱與用途備忘。",
"settings.importFailed": "匯入失敗",
"edit.exit": "結束編輯",
"edit.tags": "編輯標籤",
@@ -54,6 +56,7 @@
"app.build": "建置",
"group.uncategorized": "未分類",
"group.appleBuiltIn": "Mac內建",
+ "group.uncommon": "不常用",
"drop.systemDefaultWarning": "請勿拖動至系統預設分類",
"tag.design": "設計",
"tag.development": "程式設計",
@@ -64,5 +67,22 @@
"tag.productivity": "辦公",
"settings.coloredContainer": "彩色容器",
"settings.gridContainer": "無色網格",
- "settings.coloredGridContainer": "彩色網格"
+ "settings.coloredGridContainer": "彩色網格",
+ "appNote.edit": "編輯用途備忘…",
+ "appNote.placeholder": "APP 用途備忘",
+ "edit.modeAdd": "新增標籤",
+ "edit.modeRemove": "移除標籤",
+ "edit.modeHint": "左側勾選只作用於本次操作。",
+ "edit.currentTags": "目前標籤",
+ "edit.commonTags": "共同標籤",
+ "edit.selectionHint": "先在右側選取應用,再在左側勾選本次要新增或移除的標籤。",
+ "edit.noAssignedTags": "目前選取的應用尚未有標籤。",
+ "edit.noCommonTags": "目前選取的這些應用沒有共同標籤。",
+ "edit.mixedTagsHint": "這些應用的其他標籤彼此不同。",
+ "edit.operationLabel": "本輪操作:",
+ "edit.confirmAdd": "確認新增",
+ "edit.confirmRemove": "確認移除",
+ "edit.feedbackAddFormat": "本次針對%appCount%個應用(%appNames%)新增了%tagCount%個標籤。",
+ "edit.feedbackRemoveFormat": "本次針對%appCount%個應用(%appNames%)移除了%tagCount%個標籤(%tagNames%)。",
+ "edit.removeHint": "僅可取消既有標籤的勾選;出現紅色刪除圖示的標籤會在確認後被移除。"
}
diff --git a/Apptag/TagGroupView.swift b/Apptag/TagGroupView.swift
index 249db2d..de24653 100644
--- a/Apptag/TagGroupView.swift
+++ b/Apptag/TagGroupView.swift
@@ -11,6 +11,8 @@
var showNames: Bool = true
var dragModeActive: Bool = false
var onDragModeChange: ((Bool) -> Void)? = nil
+ var onBubbleHover: ((AppInfo, CGRect, Bool) -> Void)? = nil
+ var onEditNote: ((AppInfo, CGRect) -> Void)? = nil
var onDropApp: ((String, String, Bool) -> Void)? = nil
/// Adaptive columns — auto-fit based on icon size and available width.
@@ -53,6 +55,8 @@
sourceTag: group.name,
dragModeActive: dragModeActive,
onDragModeChange: onDragModeChange,
+ onBubbleHover: onBubbleHover,
+ onEditNote: onEditNote,
onSelect: { onSelectApp(app) }
)
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 194c64b..873b152 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Apptag Changelog
+## [6.0.5] — 2026-05-16
+
+- 修正应用分类编辑页的批量操作逻辑:添加模式只追加手动勾选的标签,移除模式会先自动勾选右侧已选 App 现有的标签合集,未命中的标签置灰不可点
+- 移除模式下,取消某个已有标签的勾选后,会在标签右侧显示鲜红删除图标;重新勾回后,删除图标立即消失
+- 优化移除模式的左侧提示文案,明确说明只有已有标签可取消勾选,出现红色删除图标的标签会在确认后被移除
+- 新增居中全屏反馈提示:点击「确认添加 / 确认移除」后,会复用“不常用 App”气泡矩形样式显示本次操作结果,并支持自动消失和右上角关闭
+- 反馈统计改为真实增减计数:逐个 App 计算实际新增/移除的标签关系数,避免把原本已存在或不存在的标签重复计入
+- 调整编辑页顶部排版:将本轮操作说明移到确认按钮同一行,并为长文案提供省略与悬浮完整提示;左侧“选择标签 / 拖动排序”提示改为与标签列表左边缘对齐
+- 将英文标题文案由 `This edit:` 收敛为更自然的 `Action:`,并同步检查、收紧其他语言的对应表述
+- 修复编辑分类页左侧 sidebar 被说明文字横向撑开的布局问题,固定左栏宽度并恢复右侧应用区紧贴分隔线展开
+- 左侧 `Select tags / Drag to reorder` 提示现在严格约束在标签列表宽度内,超长时换行,不再越出左侧标签列表边界
+- 去掉编辑分类页顶部居中的标题,把空间让给右侧模式说明;右侧说明现在会优先尽量完整显示,只有左右按钮区确实排不下时才从中间省略
+- 修复顶部模式说明使用无限宽度参与窗口约束计算时可能触发 AppKit 约束更新异常并闪退的问题
+- 修复新版 macOS 上 Safari 等系统 App 可能被扫描遗漏的问题:扫描器现在会识别搜索目录的直接 `.app` 子级、补充 Cryptex 系统应用目录,并用真实路径去重但保留稳定入口路径
+- 补齐编辑模式相关新增文案到全部语言文件,避免切换语言后出现缺失键
+- 版本号更新为 `6.0.5`,Build 更新为 `610`
+
+## [6.0.0] — 2026-05-16
+
+- 新增“不常用”特殊系统标记:只在 App 分类编辑模式中显示,不作为普通分类出现在日常应用列表中
+- “不常用”不改变 App 原有分类归属;App 仍留在原分类中,只额外获得低频标记
+- 新增“不常用 App 气泡提示”开关,默认开启,放在设置页“数据”页签最上方
+- 对已标记为“不常用”的 App,在开关开启后 hover 显示大号 App 名称 Bubble;有用途备注时同步显示备注
+- 支持右键“不常用”App 编辑用途备忘,备注最多 80 个 Unicode 字符
+- 备注数据进入分类与布局 JSON,并与“不常用”标记状态解耦:移除“不常用”标记后备注仍保留
+- 编辑模式中的批量标签操作改为“添加标签 / 移除标签”双模式;右侧选中 App 不再自动反向勾选左侧标签,避免批量污染原有分类
+- 为编辑模式新增只读标签状态区:单选显示当前标签,多选显示共同标签,并提示其他标签可能不同
+- 新扫描到的 App 会自动标记为“不常用”,并开始统计从 TagLauncher 打开的次数;达到 100 次后,只有系统自动加上的“不常用”才会被自动移除
+- 为新增功能补齐全部 29 个语言版本文案
+- 版本号更新为 `6.0.0`,Build 更新为 `600`
+
## [5.7.8] — 2026-05-15
- 修复 TagLauncher 浮层每次打开都会重复添加 Escape 键本地监听的问题,关闭浮层时会同步释放监听器,避免事件监听累积
--
Gitblit v1.9.3