From 71bfdd4087d0b4d94bd5345cb45bb335617d737c Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 28 May 2026 15:27:05 +0800
Subject: [PATCH] Confirm single-tag removal on empty grid drop

---
 Apptag/Localization/cs.json        |    5 
 Apptag/Localization/no.json        |    5 
 Apptag/Localization/zh-Hant.json   |    5 
 Apptag/EditModeViews.swift         |   84 ++++++++++++
 Apptag/Localization/ja.json        |    5 
 Apptag/Localization/th.json        |    5 
 Apptag/AppDragCoordinator.swift    |   48 ++++++
 Apptag/Localization/nl.json        |    5 
 Apptag/Localization/sr-Cyrl.json   |    5 
 Apptag/Localization/zh-Hans.json   |    5 
 Apptag/Localization/de.json        |    5 
 Apptag/AppGridCollectionView.swift |   25 +++
 Apptag/Localization/ms.json        |    5 
 Apptag/Localization/nn.json        |    5 
 Apptag/Localization/pt-BR.json     |    5 
 Apptag/Localization/es.json        |    5 
 Apptag/Localization/tr.json        |    5 
 Apptag/Localization/en.json        |    5 
 Apptag/Localization/nb.json        |    5 
 Apptag/Localization/ar.json        |    5 
 Apptag/Localization/vi.json        |    5 
 Apptag/Localization/fr.json        |    5 
 Apptag/Localization/id.json        |    5 
 Apptag/Localization/da.json        |    5 
 Apptag/Localization/uk.json        |    5 
 Apptag/Localization/ar-Najdi.json  |    5 
 Apptag/Localization/ko.json        |    5 
 Apptag/ContentView.swift           |  112 +++++++++++++++
 Apptag/Localization/it.json        |    5 
 Apptag/Localization/sv.json        |    5 
 Apptag/Localization/ro.json        |    5 
 Apptag/AppDefaults.swift           |    1 
 Apptag/Localization/pl.json        |    5 
 Apptag/Localization/ru.json        |    5 
 34 files changed, 411 insertions(+), 4 deletions(-)

diff --git a/Apptag/AppDefaults.swift b/Apptag/AppDefaults.swift
index bfeecc8..71e08d9 100644
--- a/Apptag/AppDefaults.swift
+++ b/Apptag/AppDefaults.swift
@@ -23,6 +23,7 @@
             "showDockIcon": showDockIcon,
             "launchAtLogin": launchAtLogin,
             "showUncommonAppBubbles": showUncommonAppBubbles,
+            "skipTagRemovalDropConfirm": false,
             LauncherHotkeyRegistrationStore.mainStateKey: LauncherHotkeyRegistrationState.active.rawValue,
             LauncherHotkeyRegistrationStore.quickSearchStateKey: LauncherHotkeyRegistrationState.active.rawValue
         ])
diff --git a/Apptag/AppDragCoordinator.swift b/Apptag/AppDragCoordinator.swift
index 070e2ad..e767f95 100644
--- a/Apptag/AppDragCoordinator.swift
+++ b/Apptag/AppDragCoordinator.swift
@@ -10,7 +10,12 @@
         var tag: String
     }
 
+    struct EmptyDropTarget {
+        weak var view: AppEmptyDropReceivingView?
+    }
+
     private var targets: [UUID: DropTarget] = [:]
+    private var emptyTargets: [UUID: EmptyDropTarget] = [:]
     private weak var dragHostWindow: NSWindow?
     private weak var dragLayerHostView: NSView?
     private var dragLayer: CALayer?
@@ -39,6 +44,17 @@
 
     func unregister(id: UUID) {
         targets.removeValue(forKey: id)
+    }
+
+    func registerEmptyDropTarget(id: UUID, view: AppEmptyDropReceivingView) {
+        if emptyTargets.count > 64 {
+            pruneDeadTargets()
+        }
+        emptyTargets[id] = EmptyDropTarget(view: view)
+    }
+
+    func unregisterEmptyDropTarget(id: UUID) {
+        emptyTargets.removeValue(forKey: id)
     }
 
     func beginDrag(image: NSImage, payload: String, at screenPoint: NSPoint, copy: Bool, in hostWindow: NSWindow?) {
@@ -142,7 +158,23 @@
             .sorted { $0.1 < $1.1 }
             .first?.0
 
-        hitTarget?.performDrop(path: path, source: source, copy: copy)
+        if let hitTarget {
+            hitTarget.performDrop(path: path, source: source, copy: copy)
+            return
+        }
+
+        let emptyTarget = emptyTargets.values
+            .compactMap { target -> (AppEmptyDropReceivingView, CGFloat)? in
+                guard let view = target.view,
+                      let frame = view.screenFrame(),
+                      frame.contains(screenPoint)
+                else { return nil }
+                return (view, frame.width * frame.height)
+            }
+            .sorted { $0.1 < $1.1 }
+            .first?.0
+
+        emptyTarget?.performEmptyDrop(path: path, source: source, screenPoint: screenPoint, copy: copy)
     }
 
     func cancelDrag() {
@@ -174,6 +206,7 @@
 
     private func pruneDeadTargets() {
         targets = targets.filter { $0.value.view != nil }
+        emptyTargets = emptyTargets.filter { $0.value.view != nil }
     }
 
     private static func cgImage(from image: NSImage) -> CGImage? {
@@ -231,6 +264,11 @@
     func performDrop(path: String, source: String, copy: Bool)
 }
 
+protocol AppEmptyDropReceivingView: AnyObject {
+    func screenFrame() -> NSRect?
+    func performEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool)
+}
+
 extension AppDropTargetReceivingView where Self: NSView {
     func screenFrame() -> NSRect? {
         guard let window else { return nil }
@@ -239,6 +277,14 @@
     }
 }
 
+extension AppEmptyDropReceivingView where Self: NSView {
+    func screenFrame() -> NSRect? {
+        guard let window else { return nil }
+        let rectInWindow = convert(bounds, to: nil)
+        return window.convertToScreen(rectInWindow)
+    }
+}
+
 struct AppDropTargetView: NSViewRepresentable {
     let targetTag: String
     let onDropApp: (String, String, Bool) -> Void
diff --git a/Apptag/AppGridCollectionView.swift b/Apptag/AppGridCollectionView.swift
index 9fdd8af..b2884d7 100644
--- a/Apptag/AppGridCollectionView.swift
+++ b/Apptag/AppGridCollectionView.swift
@@ -45,6 +45,7 @@
     let onBubbleHover: (AppInfo, CGRect, AppBubbleHoverEvent) -> Void
     let onEditNote: (AppInfo, CGRect) -> Void
     let onDropApp: (String, String, String, Bool) -> Void
+    let onDropOutsideGroup: (String, String, Bool) -> Void
     let onGroupActivate: (String) -> Void
     let onScrollActivity: () -> Void
     let onDragModeChange: (Bool) -> Void
@@ -76,6 +77,7 @@
             onBubbleHover: onBubbleHover,
             onEditNote: onEditNote,
             onDropApp: onDropApp,
+            onDropOutsideGroup: onDropOutsideGroup,
             onGroupActivate: onGroupActivate,
             onScrollActivity: onScrollActivity,
             onDragModeChange: onDragModeChange
@@ -104,6 +106,7 @@
         var onBubbleHover: (AppInfo, CGRect, AppBubbleHoverEvent) -> Void = { _, _, _ in }
         var onEditNote: (AppInfo, CGRect) -> Void = { _, _ in }
         var onDropApp: (String, String, String, Bool) -> Void = { _, _, _, _ in }
+        var onDropOutsideGroup: (String, String, Bool) -> Void = { _, _, _ in }
         var onGroupActivate: (String) -> Void = { _ in }
         var onScrollActivity: () -> Void = {}
         var onDragModeChange: (Bool) -> Void = { _ in }
@@ -124,6 +127,7 @@
             onBubbleHover: @escaping (AppInfo, CGRect, AppBubbleHoverEvent) -> Void,
             onEditNote: @escaping (AppInfo, CGRect) -> Void,
             onDropApp: @escaping (String, String, String, Bool) -> Void,
+            onDropOutsideGroup: @escaping (String, String, Bool) -> Void,
             onGroupActivate: @escaping (String) -> Void,
             onScrollActivity: @escaping () -> Void,
             onDragModeChange: @escaping (Bool) -> Void
@@ -143,6 +147,7 @@
             self.onBubbleHover = onBubbleHover
             self.onEditNote = onEditNote
             self.onDropApp = onDropApp
+            self.onDropOutsideGroup = onDropOutsideGroup
             self.onGroupActivate = onGroupActivate
             self.onScrollActivity = onScrollActivity
             self.onDragModeChange = onDragModeChange
@@ -250,7 +255,8 @@
     }
 }
 
-final class AppGridCollectionHostView: NSView {
+final class AppGridCollectionHostView: NSView, AppEmptyDropReceivingView {
+    private let emptyDropTargetID = UUID()
     private let scrollView = NSScrollView()
     private let collectionView = NSCollectionView()
     private let gridLayout = AppGridContainerCollectionLayout()
@@ -277,6 +283,7 @@
             NotificationCenter.default.removeObserver(scrollObserver)
         }
         scrollUnfreezeWorkItem?.cancel()
+        AppDragCoordinator.shared.unregisterEmptyDropTarget(id: emptyDropTargetID)
     }
 
     func configure(coordinator: AppGridCollectionView.Coordinator) {
@@ -319,6 +326,22 @@
         }
     }
 
+    override func viewDidMoveToWindow() {
+        super.viewDidMoveToWindow()
+        if window == nil {
+            AppDragCoordinator.shared.unregisterEmptyDropTarget(id: emptyDropTargetID)
+        } else {
+            AppDragCoordinator.shared.registerEmptyDropTarget(id: emptyDropTargetID, view: self)
+        }
+    }
+
+    func performEmptyDrop(path: String, source: String, screenPoint: NSPoint, copy: Bool) {
+        guard let coordinator,
+              coordinator.displayStyle != .flat
+        else { return }
+        coordinator.onDropOutsideGroup(path, source, copy)
+    }
+
     private func setup() {
         wantsLayer = true
         layer?.backgroundColor = NSColor.clear.cgColor
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index c163918..d6ca151 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -317,6 +317,12 @@
     let removableTags: [String]
 }
 
+private struct PendingTagRemovalDrop: Identifiable {
+    let id = UUID()
+    let app: AppInfo
+    let tagName: String
+}
+
 private enum SmartStartNoticeMode {
     case autoApplied
     case suggestionOnly
@@ -371,6 +377,8 @@
     @State private var hoveredBubble: AppBubbleContext? = nil
     @State private var editingBubble: AppBubbleContext? = nil
     @State private var pendingUncategorizedDrop: PendingUncategorizedDrop? = nil
+    @State private var pendingTagRemovalDrop: PendingTagRemovalDrop? = nil
+    @State private var tagRemovalDropSuppressFuturePrompt = false
     @State private var appDragResetToken = 0
     @State private var bubbleDraftNote = ""
     @FocusState private var bubbleNoteFocused: Bool
@@ -406,13 +414,14 @@
     @AppStorage("displayMode") private var displayMode = AppDefaults.displayMode
     @AppStorage("hideAppNames") private var hideAppNames = AppDefaults.hideAppNames
     @AppStorage("showUncommonAppBubbles") private var showUncommonAppBubbles = AppDefaults.showUncommonAppBubbles
+    @AppStorage("skipTagRemovalDropConfirm") private var skipTagRemovalDropConfirm = false
 
     private let editSidebarWidth: CGFloat = 188
     private let editSidebarHorizontalInset: CGFloat = 12
     private let floatingControlsTrailingInset: CGFloat = 20
     private let floatingControlsReservedWidth: CGFloat = 120
     private var appBubbleDisabled: Bool {
-        appDragModeActive || pendingUncategorizedDrop != nil
+        appDragModeActive || pendingUncategorizedDrop != nil || pendingTagRemovalDrop != nil
     }
     private let rightSidebarFloatingClearance: CGFloat = 44
 
@@ -470,6 +479,7 @@
                 smartStartNoticeOverlay
                 editActionFeedbackOverlay
                 uncategorizedDropConfirmOverlay
+                tagRemovalDropConfirmOverlay
             }
 
             quickSearchOverlay
@@ -959,6 +969,9 @@
                     onDropApp: { path, source, target, copy in
                         dropApp(path: path, sourceTag: source, targetTag: target, copy: copy)
                     },
+                    onDropOutsideGroup: { path, source, copy in
+                        dropAppOutsideGroup(path: path, sourceTag: source, copy: copy)
+                    },
                     onGroupActivate: { groupName in
                         if isColorlessContainerMode {
                             toggleColorlessFill(groupName)
@@ -1434,6 +1447,34 @@
         }
         .ignoresSafeArea()
         .allowsHitTesting(pendingUncategorizedDrop != nil)
+    }
+
+    private var tagRemovalDropConfirmOverlay: some View {
+        GeometryReader { proxy in
+            if let pendingDrop = pendingTagRemovalDrop {
+                ZStack {
+                    Color.black.opacity(0.14)
+                        .ignoresSafeArea()
+
+                    TagRemovalDropConfirmBubble(
+                        title: tr("drop.removeTagConfirmTitle"),
+                        message: tagRemovalConfirmMessage(for: pendingDrop),
+                        doNotRemindTitle: tr("drop.removeTagDoNotAskAgain"),
+                        doNotRemind: $tagRemovalDropSuppressFuturePrompt,
+                        cancelTitle: tr("drop.removeTagConfirmNo"),
+                        confirmTitle: tr("drop.removeTagConfirmYes"),
+                        onCancel: dismissTagRemovalDropConfirm,
+                        onConfirm: confirmPendingTagRemovalDrop
+                    )
+                    .frame(width: min(560, max(350, proxy.size.width - 120)))
+                    .position(x: proxy.size.width / 2, y: proxy.size.height / 2)
+                    .transition(.scale(scale: 0.94).combined(with: .opacity))
+                }
+                .zIndex(711)
+            }
+        }
+        .ignoresSafeArea()
+        .allowsHitTesting(pendingTagRemovalDrop != nil)
     }
 
     private func buildEditActionFeedback(for selectedApps: [AppInfo], tags: [String]) -> EditActionFeedback {
@@ -1970,6 +2011,25 @@
         refreshApps(forceLayoutRefresh: true)
     }
 
+    private func dropAppOutsideGroup(path: String, sourceTag: String, copy: Bool) {
+        resetTransientDragState(keepingPendingTagRemovalDrop: true)
+        guard isRemovableRegularTag(sourceTag) else { return }
+        guard let app = allApps.first(where: { $0.path.path == path }),
+              appHasTag(app, tagName: sourceTag)
+        else { return }
+
+        if skipTagRemovalDropConfirm {
+            removeTagFromDroppedApp(app: app, tagName: sourceTag)
+            return
+        }
+
+        clearAppBubbleState()
+        tagRemovalDropSuppressFuturePrompt = false
+        withAnimation(.spring(response: 0.24, dampingFraction: 0.84)) {
+            pendingTagRemovalDrop = PendingTagRemovalDrop(app: app, tagName: sourceTag)
+        }
+    }
+
     private func confirmAndMoveAppToUncategorized(path: String) {
         guard let app = allApps.first(where: { $0.path.path == path }) else { return }
         let removableTags = removableRegularTags(for: app)
@@ -2019,6 +2079,16 @@
         )
     }
 
+    private func tagRemovalConfirmMessage(for pendingDrop: PendingTagRemovalDrop) -> String {
+        formattedFeedbackMessage(
+            forKey: "drop.removeTagConfirmMessage",
+            replacements: [
+                "%appName%": pendingDrop.app.displayName,
+                "%tagName%": displayTagName(pendingDrop.tagName)
+            ]
+        )
+    }
+
     private func dismissUncategorizedDropConfirm() {
         resetTransientDragState(keepingPendingUncategorizedDrop: true)
         withAnimation(.easeOut(duration: 0.18)) {
@@ -2036,6 +2106,38 @@
         }
         guard !tags.isEmpty else { return }
         TagEditor.removeTags(tags, from: [path])
+        showDropRefresh()
+        refreshApps(forceLayoutRefresh: true)
+    }
+
+    private func dismissTagRemovalDropConfirm() {
+        resetTransientDragState(keepingPendingTagRemovalDrop: true)
+        tagRemovalDropSuppressFuturePrompt = false
+        withAnimation(.easeOut(duration: 0.18)) {
+            pendingTagRemovalDrop = nil
+        }
+    }
+
+    private func confirmPendingTagRemovalDrop() {
+        guard let pendingDrop = pendingTagRemovalDrop else { return }
+        let app = pendingDrop.app
+        let tagName = pendingDrop.tagName
+        if tagRemovalDropSuppressFuturePrompt {
+            skipTagRemovalDropConfirm = true
+        }
+        resetTransientDragState(keepingPendingTagRemovalDrop: true)
+        tagRemovalDropSuppressFuturePrompt = false
+        withAnimation(.easeOut(duration: 0.16)) {
+            pendingTagRemovalDrop = nil
+        }
+        removeTagFromDroppedApp(app: app, tagName: tagName)
+    }
+
+    private func removeTagFromDroppedApp(app: AppInfo, tagName: String) {
+        guard isRemovableRegularTag(tagName),
+              appHasTag(app, tagName: tagName)
+        else { return }
+        TagEditor.removeTags([tagName], from: [app.path.path])
         showDropRefresh()
         refreshApps(forceLayoutRefresh: true)
     }
@@ -2114,7 +2216,10 @@
         }
     }
 
-    private func resetTransientDragState(keepingPendingUncategorizedDrop: Bool = false) {
+    private func resetTransientDragState(
+        keepingPendingUncategorizedDrop: Bool = false,
+        keepingPendingTagRemovalDrop: Bool = false
+    ) {
         let hadAppDragState = appDragModeActive
         AppDragCoordinator.shared.cancelDrag()
         if appDragModeActive {
@@ -2139,6 +2244,9 @@
         if !keepingPendingUncategorizedDrop, pendingUncategorizedDrop != nil {
             pendingUncategorizedDrop = nil
         }
+        if !keepingPendingTagRemovalDrop, pendingTagRemovalDrop != nil {
+            pendingTagRemovalDrop = nil
+        }
     }
 
     func refreshApps(forceLayoutRefresh: Bool = false) {
diff --git a/Apptag/EditModeViews.swift b/Apptag/EditModeViews.swift
index aca3c77..2a2c60e 100644
--- a/Apptag/EditModeViews.swift
+++ b/Apptag/EditModeViews.swift
@@ -387,3 +387,87 @@
         .onExitCommand(perform: onCancel)
     }
 }
+
+struct TagRemovalDropConfirmBubble: View {
+    let title: String
+    let message: String
+    let doNotRemindTitle: String
+    @Binding var doNotRemind: Bool
+    let cancelTitle: String
+    let confirmTitle: String
+    let onCancel: () -> Void
+    let onConfirm: () -> Void
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 18) {
+            HStack(alignment: .top, spacing: 12) {
+                Text(title)
+                    .font(.system(size: 22, weight: .bold))
+                    .foregroundStyle(.white)
+                    .frame(maxWidth: .infinity, alignment: .leading)
+
+                Button(action: onCancel) {
+                    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)
+            }
+
+            Text(message)
+                .font(.system(size: 15, weight: .medium))
+                .lineSpacing(4)
+                .foregroundStyle(.white.opacity(0.84))
+                .multilineTextAlignment(.leading)
+                .fixedSize(horizontal: false, vertical: true)
+                .frame(maxWidth: .infinity, alignment: .leading)
+
+            Toggle(doNotRemindTitle, isOn: $doNotRemind)
+                .toggleStyle(.checkbox)
+                .font(.system(size: 13, weight: .medium))
+                .foregroundStyle(.white.opacity(0.80))
+
+            HStack(spacing: 12) {
+                Spacer(minLength: 0)
+
+                Button(action: onCancel) {
+                    Text(cancelTitle)
+                        .font(.system(size: 13, weight: .semibold))
+                        .foregroundStyle(.white.opacity(0.86))
+                        .frame(width: 92, height: 32)
+                        .background(
+                            RoundedRectangle(cornerRadius: 8, style: .continuous)
+                                .fill(Color.white.opacity(0.13))
+                        )
+                }
+                .buttonStyle(.plain)
+
+                Button(action: onConfirm) {
+                    Text(confirmTitle)
+                        .font(.system(size: 13, weight: .semibold))
+                        .foregroundStyle(.white)
+                        .frame(width: 92, height: 32)
+                        .background(
+                            RoundedRectangle(cornerRadius: 8, style: .continuous)
+                                .fill(Color.accentColor)
+                        )
+                }
+                .buttonStyle(.plain)
+            }
+        }
+        .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)
+        )
+        .onExitCommand(perform: onCancel)
+    }
+}
diff --git a/Apptag/Localization/ar-Najdi.json b/Apptag/Localization/ar-Najdi.json
index c7bced3..a1bd1e7 100644
--- a/Apptag/Localization/ar-Najdi.json
+++ b/Apptag/Localization/ar-Najdi.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "لا تسحب التطبيقات لتصنيفات النظام الافتراضية",
   "drop.uncategorizedConfirmTitle": "نقل إلى غير مصنف؟",
   "drop.uncategorizedConfirmMessage": "نقل %appName% إلى غير مصنف بيزيل ارتباطاته الحالية بعدد %tagCount% من الوسوم: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "تصميم",
   "tag.development": "تطوير",
   "tag.writing": "كتابة",
diff --git a/Apptag/Localization/ar.json b/Apptag/Localization/ar.json
index 44c32f4..8768c95 100644
--- a/Apptag/Localization/ar.json
+++ b/Apptag/Localization/ar.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "لا تسحب التطبيقات إلى التصنيفات الافتراضية للنظام",
   "drop.uncategorizedConfirmTitle": "نقل إلى غير مصنف؟",
   "drop.uncategorizedConfirmMessage": "سيؤدي نقل %appName% إلى غير مصنف إلى إزالة ارتباطاته الحالية بعدد %tagCount% من الوسوم: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "تصميم",
   "tag.development": "تطوير",
   "tag.writing": "كتابة",
diff --git a/Apptag/Localization/cs.json b/Apptag/Localization/cs.json
index 026d51b..9300dda 100644
--- a/Apptag/Localization/cs.json
+++ b/Apptag/Localization/cs.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Nepřetahujte aplikace do výchozích systémových kategorií",
   "drop.uncategorizedConfirmTitle": "Přesunout do Nezařazeno?",
   "drop.uncategorizedConfirmMessage": "Přesunutí %appName% do Nezařazeno odstraní aktuální přiřazení štítků (%tagCount%): %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Vývoj",
   "tag.writing": "Psaní",
diff --git a/Apptag/Localization/da.json b/Apptag/Localization/da.json
index b735d62..ed057b2 100644
--- a/Apptag/Localization/da.json
+++ b/Apptag/Localization/da.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Træk ikke apps til systemets standardkategorier",
   "drop.uncategorizedConfirmTitle": "Flyt til Ikke kategoriseret?",
   "drop.uncategorizedConfirmMessage": "Hvis %appName% flyttes til Ikke kategoriseret, fjernes de nuværende %tagCount% tagtilknytninger: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Udvikling",
   "tag.writing": "Skrivning",
diff --git a/Apptag/Localization/de.json b/Apptag/Localization/de.json
index 9e71a18..4e80a9f 100644
--- a/Apptag/Localization/de.json
+++ b/Apptag/Localization/de.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Ziehe keine Apps in die Standard-Systemkategorien",
   "drop.uncategorizedConfirmTitle": "Nach Nicht kategorisiert verschieben?",
   "drop.uncategorizedConfirmMessage": "Wenn %appName% nach Nicht kategorisiert verschoben wird, werden die aktuellen %tagCount% Tag-Zuordnungen entfernt: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Entwicklung",
   "tag.writing": "Schreiben",
diff --git a/Apptag/Localization/en.json b/Apptag/Localization/en.json
index b58f2e0..64a1216 100644
--- a/Apptag/Localization/en.json
+++ b/Apptag/Localization/en.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Do not drag apps into system default categories",
   "drop.uncategorizedConfirmTitle": "Move to Uncategorized?",
   "drop.uncategorizedConfirmMessage": "Moving %appName% to Uncategorized will remove its current %tagCount% tag associations: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Development",
   "tag.writing": "Writing",
diff --git a/Apptag/Localization/es.json b/Apptag/Localization/es.json
index 34582bc..8d7332e 100644
--- a/Apptag/Localization/es.json
+++ b/Apptag/Localization/es.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "No arrastres a las categorías predeterminadas del sistema",
   "drop.uncategorizedConfirmTitle": "¿Mover a Sin clasificar?",
   "drop.uncategorizedConfirmMessage": "Mover %appName% a Sin clasificar eliminará sus %tagCount% asociaciones de etiquetas actuales: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Diseño",
   "tag.development": "Desarrollo",
   "tag.writing": "Escritura",
diff --git a/Apptag/Localization/fr.json b/Apptag/Localization/fr.json
index 1fb07a8..4425b41 100644
--- a/Apptag/Localization/fr.json
+++ b/Apptag/Localization/fr.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Ne déposez pas dans les catégories système par défaut",
   "drop.uncategorizedConfirmTitle": "Déplacer vers Non classé ?",
   "drop.uncategorizedConfirmMessage": "Déplacer %appName% vers Non classé supprimera ses %tagCount% associations de tags actuelles : %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Développement",
   "tag.writing": "Écriture",
diff --git a/Apptag/Localization/id.json b/Apptag/Localization/id.json
index f896777..36e72bf 100644
--- a/Apptag/Localization/id.json
+++ b/Apptag/Localization/id.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Jangan seret app ke kategori bawaan sistem",
   "drop.uncategorizedConfirmTitle": "Pindahkan ke Tanpa kategori?",
   "drop.uncategorizedConfirmMessage": "Memindahkan %appName% ke Tanpa kategori akan menghapus %tagCount% hubungan tag saat ini: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Desain",
   "tag.development": "Pengembangan",
   "tag.writing": "Menulis",
diff --git a/Apptag/Localization/it.json b/Apptag/Localization/it.json
index 9bd86d7..8b2af3e 100644
--- a/Apptag/Localization/it.json
+++ b/Apptag/Localization/it.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Non trascinare nelle categorie di sistema predefinite",
   "drop.uncategorizedConfirmTitle": "Spostare in Non classificato?",
   "drop.uncategorizedConfirmMessage": "Spostare %appName% in Non classificato rimuoverà le %tagCount% associazioni tag attuali: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Sviluppo",
   "tag.writing": "Scrittura",
diff --git a/Apptag/Localization/ja.json b/Apptag/Localization/ja.json
index adf98d7..6a94e5d 100644
--- a/Apptag/Localization/ja.json
+++ b/Apptag/Localization/ja.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "システム既定カテゴリにはドラッグできません",
   "drop.uncategorizedConfirmTitle": "未分類に移動しますか?",
   "drop.uncategorizedConfirmMessage": "%appName% を未分類に移動すると、現在関連付けられている %tagCount% 個のタグが解除されます:%tagNames%。",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "デザイン",
   "tag.development": "プログラミング",
   "tag.writing": "ライティング",
diff --git a/Apptag/Localization/ko.json b/Apptag/Localization/ko.json
index 17be2b2..17d1a1b 100644
--- a/Apptag/Localization/ko.json
+++ b/Apptag/Localization/ko.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "시스템 기본 분류로 드래그하지 마세요",
   "drop.uncategorizedConfirmTitle": "분류 없음으로 이동할까요?",
   "drop.uncategorizedConfirmMessage": "%appName%을(를) 분류 없음으로 이동하면 현재 연결된 태그 %tagCount%개가 해제됩니다: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "디자인",
   "tag.development": "프로그래밍",
   "tag.writing": "글쓰기",
diff --git a/Apptag/Localization/ms.json b/Apptag/Localization/ms.json
index 879bc24..2cf6390 100644
--- a/Apptag/Localization/ms.json
+++ b/Apptag/Localization/ms.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Jangan seret app ke kategori bawaan sistem",
   "drop.uncategorizedConfirmTitle": "Alih ke Tiada kategori?",
   "drop.uncategorizedConfirmMessage": "Mengalih %appName% ke Tiada kategori akan membuang %tagCount% kaitan tag semasa: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Desain",
   "tag.development": "Pengembangan",
   "tag.writing": "Menulis",
diff --git a/Apptag/Localization/nb.json b/Apptag/Localization/nb.json
index fcecc95..79df780 100644
--- a/Apptag/Localization/nb.json
+++ b/Apptag/Localization/nb.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
   "drop.uncategorizedConfirmTitle": "Flytt til Ukategorisert?",
   "drop.uncategorizedConfirmMessage": "Hvis %appName% flyttes til Ukategorisert, fjernes de nåværende %tagCount% taggtilknytningene: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Utvikling",
   "tag.writing": "Skriving",
diff --git a/Apptag/Localization/nl.json b/Apptag/Localization/nl.json
index df36c21..d7fc3d3 100644
--- a/Apptag/Localization/nl.json
+++ b/Apptag/Localization/nl.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Sleep apps niet naar standaard systeemcategorieën",
   "drop.uncategorizedConfirmTitle": "Naar Niet gecategoriseerd verplaatsen?",
   "drop.uncategorizedConfirmMessage": "Als je %appName% naar Niet gecategoriseerd verplaatst, worden de huidige %tagCount% tagkoppelingen verwijderd: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Ontwerp",
   "tag.development": "Ontwikkeling",
   "tag.writing": "Schrijven",
diff --git a/Apptag/Localization/nn.json b/Apptag/Localization/nn.json
index d9150e1..dfd61f3 100644
--- a/Apptag/Localization/nn.json
+++ b/Apptag/Localization/nn.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
   "drop.uncategorizedConfirmTitle": "Flytt til Ukategorisert?",
   "drop.uncategorizedConfirmMessage": "Viss %appName% blir flytta til Ukategorisert, blir dei noverande %tagCount% taggkoplingane fjerna: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Utvikling",
   "tag.writing": "Skriving",
diff --git a/Apptag/Localization/no.json b/Apptag/Localization/no.json
index fcecc95..79df780 100644
--- a/Apptag/Localization/no.json
+++ b/Apptag/Localization/no.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Ikke dra apper til systemets standardkategorier",
   "drop.uncategorizedConfirmTitle": "Flytt til Ukategorisert?",
   "drop.uncategorizedConfirmMessage": "Hvis %appName% flyttes til Ukategorisert, fjernes de nåværende %tagCount% taggtilknytningene: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Utvikling",
   "tag.writing": "Skriving",
diff --git a/Apptag/Localization/pl.json b/Apptag/Localization/pl.json
index 91a48d1..1cedf9f 100644
--- a/Apptag/Localization/pl.json
+++ b/Apptag/Localization/pl.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Nie przeciągaj aplikacji do domyślnych kategorii systemu",
   "drop.uncategorizedConfirmTitle": "Przenieść do Bez kategorii?",
   "drop.uncategorizedConfirmMessage": "Przeniesienie %appName% do Bez kategorii usunie obecne powiązania tagów (%tagCount%): %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Projektowanie",
   "tag.development": "Programowanie",
   "tag.writing": "Pisanie",
diff --git a/Apptag/Localization/pt-BR.json b/Apptag/Localization/pt-BR.json
index 171c548..034e939 100644
--- a/Apptag/Localization/pt-BR.json
+++ b/Apptag/Localization/pt-BR.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Não arraste apps para categorias padrão do sistema",
   "drop.uncategorizedConfirmTitle": "Mover para Sem categoria?",
   "drop.uncategorizedConfirmMessage": "Mover %appName% para Sem categoria removerá suas %tagCount% associações de tags atuais: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Desenvolvimento",
   "tag.writing": "Escrita",
diff --git a/Apptag/Localization/ro.json b/Apptag/Localization/ro.json
index 31c0a6c..4e8cdd2 100644
--- a/Apptag/Localization/ro.json
+++ b/Apptag/Localization/ro.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Nu trage aplicații în categoriile implicite ale sistemului",
   "drop.uncategorizedConfirmTitle": "Muți la Necategorizat?",
   "drop.uncategorizedConfirmMessage": "Mutarea %appName% la Necategorizat va elimina cele %tagCount% asocieri curente de etichete: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Dezvoltare",
   "tag.writing": "Scriere",
diff --git a/Apptag/Localization/ru.json b/Apptag/Localization/ru.json
index ef528e9..dd304cb 100644
--- a/Apptag/Localization/ru.json
+++ b/Apptag/Localization/ru.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Не перетаскивайте в системные категории по умолчанию",
   "drop.uncategorizedConfirmTitle": "Переместить в Без категории?",
   "drop.uncategorizedConfirmMessage": "Перемещение %appName% в Без категории удалит текущие связи с тегами (%tagCount%): %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Дизайн",
   "tag.development": "Разработка",
   "tag.writing": "Текст",
diff --git a/Apptag/Localization/sr-Cyrl.json b/Apptag/Localization/sr-Cyrl.json
index e9cdacb..df051dc 100644
--- a/Apptag/Localization/sr-Cyrl.json
+++ b/Apptag/Localization/sr-Cyrl.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Не превлачите апликације у подразумеване системске категорије",
   "drop.uncategorizedConfirmTitle": "Преместити у Некатегорисано?",
   "drop.uncategorizedConfirmMessage": "Премештање %appName% у Некатегорисано уклониће тренутне везе са ознакама (%tagCount%): %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Дизајн",
   "tag.development": "Развој",
   "tag.writing": "Писање",
diff --git a/Apptag/Localization/sv.json b/Apptag/Localization/sv.json
index 1b1af94..f13d8da 100644
--- a/Apptag/Localization/sv.json
+++ b/Apptag/Localization/sv.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Dra inte appar till systemets standardkategorier",
   "drop.uncategorizedConfirmTitle": "Flytta till Okategoriserat?",
   "drop.uncategorizedConfirmMessage": "Om %appName% flyttas till Okategoriserat tas de nuvarande %tagCount% taggkopplingarna bort: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Design",
   "tag.development": "Utveckling",
   "tag.writing": "Skrivande",
diff --git a/Apptag/Localization/th.json b/Apptag/Localization/th.json
index 5c9402b..013d0c6 100644
--- a/Apptag/Localization/th.json
+++ b/Apptag/Localization/th.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "อย่าลากแอปไปยังหมวดหมู่เริ่มต้นของระบบ",
   "drop.uncategorizedConfirmTitle": "ย้ายไปยังไม่จัดหมวดหมู่หรือไม่?",
   "drop.uncategorizedConfirmMessage": "การย้าย %appName% ไปยังไม่จัดหมวดหมู่จะลบการเชื่อมโยงแท็กปัจจุบัน %tagCount% รายการ: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "ออกแบบ",
   "tag.development": "พัฒนา",
   "tag.writing": "เขียน",
diff --git a/Apptag/Localization/tr.json b/Apptag/Localization/tr.json
index a72f78f..512ad6f 100644
--- a/Apptag/Localization/tr.json
+++ b/Apptag/Localization/tr.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Uygulamaları sistem varsayılan kategorilerine sürüklemeyin",
   "drop.uncategorizedConfirmTitle": "Sınıflandırılmamışa taşınsın mı?",
   "drop.uncategorizedConfirmMessage": "%appName% Sınıflandırılmamışa taşınırsa mevcut %tagCount% etiket ilişkisi kaldırılır: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Tasarım",
   "tag.development": "Geliştirme",
   "tag.writing": "Yazma",
diff --git a/Apptag/Localization/uk.json b/Apptag/Localization/uk.json
index 4f81a68..82eaa52 100644
--- a/Apptag/Localization/uk.json
+++ b/Apptag/Localization/uk.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Не перетягуйте застосунки в системні категорії за замовчуванням",
   "drop.uncategorizedConfirmTitle": "Перемістити до Без категорії?",
   "drop.uncategorizedConfirmMessage": "Переміщення %appName% до Без категорії видалить поточні зв’язки з тегами (%tagCount%): %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Дизайн",
   "tag.development": "Розробка",
   "tag.writing": "Письмо",
diff --git a/Apptag/Localization/vi.json b/Apptag/Localization/vi.json
index 9be41a9..3b5749c 100644
--- a/Apptag/Localization/vi.json
+++ b/Apptag/Localization/vi.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "Không kéo ứng dụng vào danh mục mặc định của hệ thống",
   "drop.uncategorizedConfirmTitle": "Chuyển sang Chưa phân loại?",
   "drop.uncategorizedConfirmMessage": "Chuyển %appName% sang Chưa phân loại sẽ gỡ %tagCount% liên kết thẻ hiện tại: %tagNames%.",
+  "drop.removeTagConfirmTitle": "Remove tag?",
+  "drop.removeTagConfirmMessage": "Remove %appName% from the tag %tagName%?",
+  "drop.removeTagDoNotAskAgain": "Got it, don't remind me again",
+  "drop.removeTagConfirmYes": "Yes",
+  "drop.removeTagConfirmNo": "No",
   "tag.design": "Thiết kế",
   "tag.development": "Phát triển",
   "tag.writing": "Viết",
diff --git a/Apptag/Localization/zh-Hans.json b/Apptag/Localization/zh-Hans.json
index 5e99fc9..0966901 100644
--- a/Apptag/Localization/zh-Hans.json
+++ b/Apptag/Localization/zh-Hans.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "请勿拖动至系统默认分类",
   "drop.uncategorizedConfirmTitle": "归类为未分类?",
   "drop.uncategorizedConfirmMessage": "将 %appName% 归类为未分类,会解除它当前关联的 %tagCount% 个标签:%tagNames%。",
+  "drop.removeTagConfirmTitle": "要移除这个标签吗?",
+  "drop.removeTagConfirmMessage": "要将 %appName% 移出“%tagName%”标签吗?",
+  "drop.removeTagDoNotAskAgain": "知道了,以后不用再提醒我",
+  "drop.removeTagConfirmYes": "是",
+  "drop.removeTagConfirmNo": "否",
   "tag.design": "设计",
   "tag.development": "编程",
   "tag.writing": "写作",
diff --git a/Apptag/Localization/zh-Hant.json b/Apptag/Localization/zh-Hant.json
index 11b6eb9..cf1ac2c 100644
--- a/Apptag/Localization/zh-Hant.json
+++ b/Apptag/Localization/zh-Hant.json
@@ -63,6 +63,11 @@
   "drop.systemDefaultWarning": "請勿拖動至系統預設分類",
   "drop.uncategorizedConfirmTitle": "歸類為未分類?",
   "drop.uncategorizedConfirmMessage": "將 %appName% 歸類為未分類,會解除它目前關聯的 %tagCount% 個標籤:%tagNames%。",
+  "drop.removeTagConfirmTitle": "要移除這個標籤嗎?",
+  "drop.removeTagConfirmMessage": "要將 %appName% 移出「%tagName%」標籤嗎?",
+  "drop.removeTagDoNotAskAgain": "知道了,以後不用再提醒我",
+  "drop.removeTagConfirmYes": "是",
+  "drop.removeTagConfirmNo": "否",
   "tag.design": "設計",
   "tag.development": "程式設計",
   "tag.writing": "寫作",

--
Gitblit v1.9.3