ソースを参照

feat(admin-ui): Add support for bulk collection actions

Relates to #853
Michael Bromley 3 年 前
コミット
220cf1c3fa
33 ファイル変更602 行追加80 行削除
  1. 26 26
      packages/admin-ui/i18n-coverage.json
  2. 9 0
      packages/admin-ui/src/lib/catalog/src/catalog.module.ts
  3. 202 0
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list-bulk-actions.ts
  4. 20 5
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.html
  5. 8 0
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.scss
  6. 19 5
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.ts
  7. 9 0
      packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.html
  8. 15 3
      packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.ts
  9. 1 0
      packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree.component.html
  10. 2 1
      packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree.component.ts
  11. 11 2
      packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list-bulk-actions.ts
  12. 14 2
      packages/admin-ui/src/lib/catalog/src/components/product-list/product-list-bulk-actions.ts
  13. 55 0
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  14. 27 0
      packages/admin-ui/src/lib/core/src/data/definitions/collection-definitions.ts
  15. 38 0
      packages/admin-ui/src/lib/core/src/data/providers/collection-data.service.ts
  16. 1 1
      packages/admin-ui/src/lib/core/src/providers/bulk-action-registry/bulk-action-types.ts
  17. 32 19
      packages/admin-ui/src/lib/core/src/shared/components/bulk-action-menu/bulk-action-menu.component.html
  18. 6 0
      packages/admin-ui/src/lib/core/src/shared/components/facet-value-selector/facet-value-selector.component.html
  19. 1 1
      packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html
  20. 1 1
      packages/admin-ui/src/lib/settings/src/components/zone-list/zone-list.component.html
  21. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/cs.json
  22. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/de.json
  23. 9 2
      packages/admin-ui/src/lib/static/i18n-messages/en.json
  24. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/es.json
  25. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/fr.json
  26. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/it.json
  27. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/pl.json
  28. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json
  29. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json
  30. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/ru.json
  31. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/uk.json
  32. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json
  33. 8 1
      packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

+ 26 - 26
packages/admin-ui/i18n-coverage.json

@@ -1,69 +1,69 @@
 {
-  "generatedOn": "2022-09-27T19:19:06.936Z",
-  "lastCommit": "647857ce5a6927afa80efe00182a3c25858eeaea",
+  "generatedOn": "2022-09-28T08:45:00.955Z",
+  "lastCommit": "87fb318908aa2fbdf8fc0d142924ef37b9ecc687",
   "translationStatus": {
     "cs": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 593,
-      "percentage": 89
+      "percentage": 88
     },
     "de": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 572,
-      "percentage": 86
+      "percentage": 85
     },
     "en": {
-      "tokenCount": 665,
-      "translatedCount": 665,
+      "tokenCount": 672,
+      "translatedCount": 671,
       "percentage": 100
     },
     "es": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 624,
-      "percentage": 94
+      "percentage": 93
     },
     "fr": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 614,
-      "percentage": 92
+      "percentage": 91
     },
     "it": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 622,
-      "percentage": 94
+      "percentage": 93
     },
     "pl": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 407,
       "percentage": 61
     },
     "pt_BR": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 591,
-      "percentage": 89
+      "percentage": 88
     },
     "pt_PT": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 635,
-      "percentage": 95
+      "percentage": 94
     },
     "ru": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 621,
-      "percentage": 93
+      "percentage": 92
     },
     "uk": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 621,
-      "percentage": 93
+      "percentage": 92
     },
     "zh_Hans": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 559,
-      "percentage": 84
+      "percentage": 83
     },
     "zh_Hant": {
-      "tokenCount": 665,
+      "tokenCount": 672,
       "translatedCount": 387,
       "percentage": 58
     }

+ 9 - 0
packages/admin-ui/src/lib/catalog/src/catalog.module.ts

@@ -12,6 +12,11 @@ import { AssignToChannelDialogComponent } from './components/assign-to-channel-d
 import { BulkAddFacetValuesDialogComponent } from './components/bulk-add-facet-values-dialog/bulk-add-facet-values-dialog.component';
 import { CollectionContentsComponent } from './components/collection-contents/collection-contents.component';
 import { CollectionDetailComponent } from './components/collection-detail/collection-detail.component';
+import {
+    assignCollectionsToChannelBulkAction,
+    deleteCollectionsBulkAction,
+    removeCollectionsFromChannelBulkAction,
+} from './components/collection-list/collection-list-bulk-actions';
 import { CollectionListComponent } from './components/collection-list/collection-list.component';
 import { CollectionTreeNodeComponent } from './components/collection-tree/collection-tree-node.component';
 import { CollectionTreeComponent } from './components/collection-tree/collection-tree.component';
@@ -83,5 +88,9 @@ export class CatalogModule {
         bulkActionRegistryService.registerBulkAction(assignFacetsToChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(removeFacetsFromChannelBulkAction);
         bulkActionRegistryService.registerBulkAction(deleteFacetsBulkAction);
+
+        bulkActionRegistryService.registerBulkAction(assignCollectionsToChannelBulkAction);
+        bulkActionRegistryService.registerBulkAction(removeCollectionsFromChannelBulkAction);
+        bulkActionRegistryService.registerBulkAction(deleteCollectionsBulkAction);
     }
 }

+ 202 - 0
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list-bulk-actions.ts

@@ -0,0 +1,202 @@
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { CollectionListComponent, CollectionPartial, ProductListComponent } from '@vendure/admin-ui/catalog';
+import {
+    BulkAction,
+    DataService,
+    DeletionResult,
+    ModalService,
+    NotificationService,
+    Permission,
+    SearchProducts,
+} from '@vendure/admin-ui/core';
+import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
+import { unique } from '@vendure/common/lib/unique';
+import { EMPTY, from, of } from 'rxjs';
+import { mapTo, switchMap } from 'rxjs/operators';
+
+import { getChannelCodeFromUserStatus } from '../../../../core/src/common/utilities/get-channel-code-from-user-status';
+import { AssignToChannelDialogComponent } from '../assign-to-channel-dialog/assign-to-channel-dialog.component';
+
+export const deleteCollectionsBulkAction: BulkAction<CollectionPartial, CollectionListComponent> = {
+    location: 'collection-list',
+    label: _('common.delete'),
+    icon: 'trash',
+    iconClass: 'is-danger',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.DeleteCollection) ||
+        userPermissions.includes(Permission.DeleteCatalog),
+    onClick: ({ injector, selection, hostComponent, clearSelection }) => {
+        const modalService = injector.get(ModalService);
+        const dataService = injector.get(DataService);
+        const notificationService = injector.get(NotificationService);
+
+        modalService
+            .dialog({
+                title: _('catalog.confirm-bulk-delete-collections'),
+                translationVars: {
+                    count: selection.length,
+                },
+                buttons: [
+                    { type: 'secondary', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(
+                switchMap(response =>
+                    response
+                        ? dataService.collection.deleteCollections(unique(selection.map(c => c.id)))
+                        : EMPTY,
+                ),
+            )
+            .subscribe(result => {
+                let deleted = 0;
+                const errors: string[] = [];
+                for (const item of result.deleteCollections) {
+                    if (item.result === DeletionResult.DELETED) {
+                        deleted++;
+                    } else if (item.message) {
+                        errors.push(item.message);
+                    }
+                }
+                if (0 < deleted) {
+                    notificationService.success(_('catalog.notify-bulk-delete-collections-success'), {
+                        count: deleted,
+                    });
+                }
+                if (0 < errors.length) {
+                    notificationService.error(errors.join('\n'));
+                }
+                hostComponent.refresh();
+                clearSelection();
+            });
+    },
+};
+
+export const assignCollectionsToChannelBulkAction: BulkAction<CollectionPartial, CollectionListComponent> = {
+    location: 'collection-list',
+    label: _('catalog.assign-to-channel'),
+    icon: 'layers',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateCatalog) ||
+        userPermissions.includes(Permission.UpdateProduct),
+    isVisible: ({ injector }) => {
+        return injector
+            .get(DataService)
+            .client.userStatus()
+            .mapSingle(({ userStatus }) => 1 < userStatus.channels.length)
+            .toPromise();
+    },
+    onClick: ({ injector, selection, hostComponent, clearSelection }) => {
+        const modalService = injector.get(ModalService);
+        const dataService = injector.get(DataService);
+        const notificationService = injector.get(NotificationService);
+        modalService
+            .fromComponent(AssignToChannelDialogComponent, {
+                size: 'md',
+                locals: {},
+            })
+            .pipe(
+                switchMap(result => {
+                    if (result) {
+                        return dataService.collection
+                            .assignCollectionsToChannel({
+                                collectionIds: selection.map(c => c.id),
+                                channelId: result.id,
+                            })
+                            .pipe(mapTo(result));
+                    } else {
+                        return EMPTY;
+                    }
+                }),
+            )
+            .subscribe(result => {
+                notificationService.success(_('catalog.assign-collections-to-channel-success'), {
+                    count: selection.length,
+                    channelCode: result.code,
+                });
+                clearSelection();
+            });
+    },
+};
+
+export const removeCollectionsFromChannelBulkAction: BulkAction<CollectionPartial, CollectionListComponent> =
+    {
+        location: 'collection-list',
+        label: _('catalog.remove-from-channel'),
+        requiresPermission: userPermissions =>
+            userPermissions.includes(Permission.UpdateChannel) ||
+            userPermissions.includes(Permission.UpdateProduct),
+        getTranslationVars: ({ injector }) => getChannelCodeFromUserStatus(injector.get(DataService)),
+        icon: 'layers',
+        iconClass: 'is-warning',
+        isVisible: ({ injector }) => {
+            return injector
+                .get(DataService)
+                .client.userStatus()
+                .mapSingle(({ userStatus }) => {
+                    if (userStatus.channels.length === 1) {
+                        return false;
+                    }
+                    const defaultChannelId = userStatus.channels.find(
+                        c => c.code === DEFAULT_CHANNEL_CODE,
+                    )?.id;
+                    return userStatus.activeChannelId !== defaultChannelId;
+                })
+                .toPromise();
+        },
+        onClick: ({ injector, selection, hostComponent, clearSelection }) => {
+            const modalService = injector.get(ModalService);
+            const dataService = injector.get(DataService);
+            const notificationService = injector.get(NotificationService);
+            const activeChannelId$ = dataService.client
+                .userStatus()
+                .mapSingle(({ userStatus }) => userStatus.activeChannelId);
+
+            from(getChannelCodeFromUserStatus(injector.get(DataService)))
+                .pipe(
+                    switchMap(({ channelCode }) =>
+                        modalService.dialog({
+                            title: _('catalog.remove-from-channel'),
+                            translationVars: {
+                                channelCode,
+                            },
+                            buttons: [
+                                { type: 'secondary', label: _('common.cancel') },
+                                {
+                                    type: 'danger',
+                                    label: _('common.remove'),
+                                    returnValue: true,
+                                },
+                            ],
+                        }),
+                    ),
+                    switchMap(res =>
+                        res
+                            ? activeChannelId$.pipe(
+                                  switchMap(activeChannelId =>
+                                      activeChannelId
+                                          ? dataService.collection.removeCollectionsFromChannel({
+                                                channelId: activeChannelId,
+                                                collectionIds: selection.map(c => c.id),
+                                            })
+                                          : EMPTY,
+                                  ),
+                                  mapTo(true),
+                              )
+                            : of(false),
+                    ),
+                )
+                .subscribe(removed => {
+                    if (removed) {
+                        clearSelection();
+                        notificationService.success(
+                            _('catalog.notify-remove-collections-from-channel-success'),
+                            {
+                                count: selection.length,
+                            },
+                        );
+                        hostComponent.refresh();
+                    }
+                });
+        },
+    };

+ 20 - 5
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.html

@@ -11,15 +11,15 @@
                 class="expand-all-toggle ml3"
                 [ngClass]="(availableLanguages$ | async)?.length === 1 ? 'mt3' : 'mt1'"
             >
-                <input type="checkbox" clrCheckbox [(ngModel)]="expandAll" (change)="toggleExpandAll()"/>
+                <input type="checkbox" clrCheckbox [(ngModel)]="expandAll" (change)="toggleExpandAll()" />
                 <label>{{ 'catalog.expand-all-collections' | translate }}</label>
             </clr-checkbox-wrapper>
             <input
-                type='text'
-                name='searchTerm'
-                [formControl]='filterTermControl'
+                type="text"
+                name="searchTerm"
+                [formControl]="filterTermControl"
                 [placeholder]="'catalog.filter-by-name' | translate"
-                class='clr-input search-input ml4'
+                class="clr-input search-input ml4"
             />
         </div>
     </vdr-ab-left>
@@ -35,12 +35,27 @@
         </a>
     </vdr-ab-right>
 </vdr-action-bar>
+<div class="bulk-select-controls">
+    <input
+        type="checkbox"
+        clrCheckbox
+        [checked]="selectionManager.areAllCurrentItemsSelected()"
+        (click)="selectionManager.toggleSelectAll()"
+    />
+    <vdr-bulk-action-menu
+        class="ml2"
+        locationId="collection-list"
+        [hostComponent]="this"
+        [selectionManager]="selectionManager"
+    ></vdr-bulk-action-menu>
+</div>
 <div class="collection-wrapper">
     <vdr-collection-tree
         [collections]="items$ | async"
         [activeCollectionId]="activeCollectionId$ | async"
         [expandAll]="expandAll"
         [expandedIds]="expandedIds"
+        [selectionManager]="selectionManager"
         (rearrange)="onRearrange($event)"
         (deleteCollection)="deleteCollection($event)"
     ></vdr-collection-tree>

+ 8 - 0
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.scss

@@ -5,6 +5,14 @@
     flex-direction: column;
 }
 
+.bulk-select-controls {
+    min-height: 36px;
+    padding-left: 14px;
+    display: flex;
+    align-items: center;
+    border-bottom: 1px solid var(--color-component-border-100);
+}
+
 .expand-all-toggle {
     display: block;
 }

+ 19 - 5
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.ts

@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
 import { FormControl } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@@ -9,6 +9,7 @@ import {
     ModalService,
     NotificationService,
     QueryResult,
+    SelectionManager,
     ServerConfigService,
 } from '@vendure/admin-ui/core';
 import { combineLatest, EMPTY, Observable, Subject } from 'rxjs';
@@ -23,7 +24,7 @@ import {
     tap,
 } from 'rxjs/operators';
 
-import { RearrangeEvent } from '../collection-tree/collection-tree.component';
+import { CollectionPartial, RearrangeEvent } from '../collection-tree/collection-tree.component';
 
 @Component({
     selector: 'vdr-collection-list',
@@ -40,6 +41,7 @@ export class CollectionListComponent implements OnInit, OnDestroy {
     contentLanguage$: Observable<LanguageCode>;
     expandAll = false;
     expandedIds: string[] = [];
+    selectionManager: SelectionManager<CollectionPartial>;
     private queryResult: QueryResult<any>;
     private destroy$ = new Subject<void>();
 
@@ -50,11 +52,23 @@ export class CollectionListComponent implements OnInit, OnDestroy {
         private router: Router,
         private route: ActivatedRoute,
         private serverConfigService: ServerConfigService,
-    ) {}
+        private changeDetectorRef: ChangeDetectorRef,
+    ) {
+        this.selectionManager = new SelectionManager({
+            additiveMode: true,
+            multiSelect: true,
+            itemsAreEqual: (a, b) => a.id === b.id,
+        });
+    }
 
     ngOnInit() {
         this.queryResult = this.dataService.collection.getCollections(1000, 0).refetchOnChannelChange();
-        this.items$ = this.queryResult.mapStream(data => data.collections.items).pipe(shareReplay(1));
+        this.items$ = this.queryResult
+            .mapStream(data => data.collections.items)
+            .pipe(
+                tap(items => this.selectionManager.setCurrentItems(items)),
+                shareReplay(1),
+            );
         this.activeCollectionId$ = this.route.paramMap.pipe(
             map(pm => pm.get('contents')),
             distinctUntilChanged(),
@@ -171,7 +185,7 @@ export class CollectionListComponent implements OnInit, OnDestroy {
         this.dataService.client.setContentLanguage(code).subscribe();
     }
 
-    private refresh() {
+    refresh() {
         const filterTerm = this.route.snapshot.queryParamMap.get('q');
         this.queryResult.ref.refetch({
             options: {

+ 9 - 0
packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.html

@@ -18,6 +18,14 @@
             [ngClass]="'depth-' + depth"
             [class.active]="collection.id === activeCollectionId"
         >
+            <div>
+                <input
+                    type="checkbox"
+                    clrCheckbox
+                    [checked]="selectionManager.isSelected(collection)"
+                    (click)="selectionManager.toggleSelection(collection, $event)"
+                />
+            </div>
             <div class="name">
                 <button
                     class="icon-button folder-button"
@@ -119,6 +127,7 @@
             [expandAll]="expandAll"
             [collectionTree]="collection"
             [activeCollectionId]="activeCollectionId"
+            [selectionManager]="selectionManager"
         ></vdr-collection-tree-node>
     </div>
 </div>

+ 15 - 3
packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree-node.component.ts

@@ -1,17 +1,19 @@
 import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
 import {
     ChangeDetectionStrategy,
+    ChangeDetectorRef,
     Component,
     Input,
     OnChanges,
+    OnDestroy,
     OnInit,
     Optional,
     SimpleChanges,
     SkipSelf,
 } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
-import { DataService, Permission } from '@vendure/admin-ui/core';
-import { Observable } from 'rxjs';
+import { DataService, Permission, SelectionManager } from '@vendure/admin-ui/core';
+import { Observable, Subscription } from 'rxjs';
 import { map, shareReplay } from 'rxjs/operators';
 
 import { RootNode, TreeNode } from './array-to-tree';
@@ -23,15 +25,17 @@ import { CollectionPartial, CollectionTreeComponent } from './collection-tree.co
     styleUrls: ['./collection-tree-node.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class CollectionTreeNodeComponent implements OnInit, OnChanges {
+export class CollectionTreeNodeComponent implements OnInit, OnChanges, OnDestroy {
     depth = 0;
     parentName: string;
     @Input() collectionTree: TreeNode<CollectionPartial>;
     @Input() activeCollectionId: string;
     @Input() expandAll = false;
+    @Input() selectionManager: SelectionManager<CollectionPartial>;
     hasUpdatePermission$: Observable<boolean>;
     hasDeletePermission$: Observable<boolean>;
     moveListItems: Array<{ path: string; id: string }> = [];
+    private subscription: Subscription;
 
     constructor(
         @SkipSelf() @Optional() private parent: CollectionTreeNodeComponent,
@@ -39,6 +43,7 @@ export class CollectionTreeNodeComponent implements OnInit, OnChanges {
         private dataService: DataService,
         private router: Router,
         private route: ActivatedRoute,
+        private changeDetectorRef: ChangeDetectorRef,
     ) {
         if (parent) {
             this.depth = parent.depth + 1;
@@ -63,6 +68,9 @@ export class CollectionTreeNodeComponent implements OnInit, OnChanges {
                     perms.includes(Permission.DeleteCatalog) || perms.includes(Permission.DeleteCollection),
             ),
         );
+        this.subscription = this.selectionManager?.selectionChanges$.subscribe(() =>
+            this.changeDetectorRef.markForCheck(),
+        );
     }
 
     ngOnChanges(changes: SimpleChanges) {
@@ -74,6 +82,10 @@ export class CollectionTreeNodeComponent implements OnInit, OnChanges {
         }
     }
 
+    ngOnDestroy() {
+        this.subscription?.unsubscribe();
+    }
+
     trackByFn(index: number, item: CollectionPartial) {
         return item.id;
     }

+ 1 - 0
packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree.component.html

@@ -3,5 +3,6 @@
     cdkDropListGroup
     [expandAll]="expandAll"
     [collectionTree]="collectionTree"
+    [selectionManager]="selectionManager"
     [activeCollectionId]="activeCollectionId"
 ></vdr-collection-tree-node>

+ 2 - 1
packages/admin-ui/src/lib/catalog/src/components/collection-tree/collection-tree.component.ts

@@ -8,7 +8,7 @@ import {
     Output,
     SimpleChanges,
 } from '@angular/core';
-import { Collection } from '@vendure/admin-ui/core';
+import { Collection, SelectionManager } from '@vendure/admin-ui/core';
 
 import { arrayToTree, HasParent, RootNode, TreeNode } from './array-to-tree';
 
@@ -26,6 +26,7 @@ export class CollectionTreeComponent implements OnChanges {
     @Input() activeCollectionId: string;
     @Input() expandAll = false;
     @Input() expandedIds: string[] = [];
+    @Input() selectionManager: SelectionManager<CollectionPartial>;
     @Output() rearrange = new EventEmitter<RearrangeEvent>();
     @Output() deleteCollection = new EventEmitter<string>();
     collectionTree: RootNode<CollectionPartial>;

+ 11 - 2
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list-bulk-actions.ts

@@ -7,6 +7,7 @@ import {
     GetFacetList,
     ModalService,
     NotificationService,
+    Permission,
 } from '@vendure/admin-ui/core';
 import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
 import { unique } from '@vendure/common/lib/unique';
@@ -21,6 +22,9 @@ export const deleteFacetsBulkAction: BulkAction<GetFacetList.Items, FacetListCom
     label: _('common.delete'),
     icon: 'trash',
     iconClass: 'is-danger',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.DeleteFacet) ||
+        userPermissions.includes(Permission.DeleteCatalog),
     onClick: ({ injector, selection, hostComponent, clearSelection }) => {
         const modalService = injector.get(ModalService);
         const dataService = injector.get(DataService);
@@ -89,9 +93,8 @@ export const deleteFacetsBulkAction: BulkAction<GetFacetList.Items, FacetListCom
                 if (deletedCount) {
                     hostComponent.refresh();
                     clearSelection();
-                    notificationService.success(_('common.notify-bulk-delete-success'), {
+                    notificationService.success(_('catalog.notify-bulk-delete-facets-success'), {
                         count: deletedCount,
-                        entity: 'Facets',
                     });
                 }
             });
@@ -102,6 +105,9 @@ export const assignFacetsToChannelBulkAction: BulkAction<GetFacetList.Items, Fac
     location: 'facet-list',
     label: _('catalog.assign-to-channel'),
     icon: 'layers',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateFacet) ||
+        userPermissions.includes(Permission.UpdateCatalog),
     isVisible: ({ injector }) => {
         return injector
             .get(DataService)
@@ -148,6 +154,9 @@ export const removeFacetsFromChannelBulkAction: BulkAction<GetFacetList.Items, F
     getTranslationVars: ({ injector }) => getChannelCodeFromUserStatus(injector.get(DataService)),
     icon: 'layers',
     iconClass: 'is-warning',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateFacet) ||
+        userPermissions.includes(Permission.UpdateCatalog),
     isVisible: ({ injector }) => {
         return injector
             .get(DataService)

+ 14 - 2
packages/admin-ui/src/lib/catalog/src/components/product-list/product-list-bulk-actions.ts

@@ -6,6 +6,7 @@ import {
     GetFacetList,
     ModalService,
     NotificationService,
+    Permission,
     SearchProducts,
 } from '@vendure/admin-ui/core';
 import { DEFAULT_CHANNEL_CODE } from '@vendure/common/lib/shared-constants';
@@ -25,6 +26,9 @@ export const deleteProductsBulkAction: BulkAction<SearchProducts.Items, ProductL
     label: _('common.delete'),
     icon: 'trash',
     iconClass: 'is-danger',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.DeleteProduct) ||
+        userPermissions.includes(Permission.DeleteCatalog),
     onClick: ({ injector, selection, hostComponent, clearSelection }) => {
         const modalService = injector.get(ModalService);
         const dataService = injector.get(DataService);
@@ -58,9 +62,8 @@ export const deleteProductsBulkAction: BulkAction<SearchProducts.Items, ProductL
                     }
                 }
                 if (0 < deleted) {
-                    notificationService.success(_('common.notify-bulk-delete-success'), {
+                    notificationService.success(_('catalog.notify-bulk-delete-products-success'), {
                         count: deleted,
-                        entity: 'Products',
                     });
                 }
                 if (0 < errors.length) {
@@ -76,6 +79,9 @@ export const assignProductsToChannelBulkAction: BulkAction<SearchProducts.Items,
     location: 'product-list',
     label: _('catalog.assign-to-channel'),
     icon: 'layers',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateCatalog) ||
+        userPermissions.includes(Permission.UpdateProduct),
     isVisible: ({ injector }) => {
         return injector
             .get(DataService)
@@ -106,6 +112,9 @@ export const assignProductsToChannelBulkAction: BulkAction<SearchProducts.Items,
 export const removeProductsFromChannelBulkAction: BulkAction<SearchProducts.Items, ProductListComponent> = {
     location: 'product-list',
     label: _('catalog.remove-from-channel'),
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateChannel) ||
+        userPermissions.includes(Permission.UpdateProduct),
     getTranslationVars: ({ injector }) => getChannelCodeFromUserStatus(injector.get(DataService)),
     icon: 'layers',
     iconClass: 'is-warning',
@@ -180,6 +189,9 @@ export const assignFacetValuesToProductsBulkAction: BulkAction<SearchProducts.It
     location: 'product-list',
     label: _('catalog.edit-facet-values'),
     icon: 'tag',
+    requiresPermission: userPermissions =>
+        userPermissions.includes(Permission.UpdateCatalog) ||
+        userPermissions.includes(Permission.UpdateProduct),
     onClick: ({ injector, selection, hostComponent, clearSelection }) => {
         const modalService = injector.get(ModalService);
         const dataService = injector.get(DataService);

+ 55 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -2435,6 +2435,8 @@ export type Mutation = {
   deleteChannel: DeletionResponse;
   /** Delete a Collection and all of its descendants */
   deleteCollection: DeletionResponse;
+  /** Delete a Collection and all of its descendants */
+  deleteCollections: Array<DeletionResponse>;
   /** Delete a Country */
   deleteCountry: DeletionResponse;
   /** Delete a Customer */
@@ -2811,6 +2813,11 @@ export type MutationDeleteCollectionArgs = {
 };
 
 
+export type MutationDeleteCollectionsArgs = {
+  ids: Array<Scalars['ID']>;
+};
+
+
 export type MutationDeleteCountryArgs = {
   id: Scalars['ID'];
 };
@@ -6014,6 +6021,16 @@ export type DeleteCollectionMutation = { deleteCollection: (
     & Pick<DeletionResponse, 'result' | 'message'>
   ) };
 
+export type DeleteCollectionsMutationVariables = Exact<{
+  ids: Array<Scalars['ID']> | Scalars['ID'];
+}>;
+
+
+export type DeleteCollectionsMutation = { deleteCollections: Array<(
+    { __typename?: 'DeletionResponse' }
+    & Pick<DeletionResponse, 'result' | 'message'>
+  )> };
+
 export type GetCollectionContentsQueryVariables = Exact<{
   id: Scalars['ID'];
   options?: Maybe<ProductVariantListOptions>;
@@ -6048,6 +6065,26 @@ export type PreviewCollectionContentsQuery = { previewCollectionVariants: (
     )> }
   ) };
 
+export type AssignCollectionsToChannelMutationVariables = Exact<{
+  input: AssignCollectionsToChannelInput;
+}>;
+
+
+export type AssignCollectionsToChannelMutation = { assignCollectionsToChannel: Array<(
+    { __typename?: 'Collection' }
+    & Pick<Collection, 'id' | 'name'>
+  )> };
+
+export type RemoveCollectionsFromChannelMutationVariables = Exact<{
+  input: RemoveCollectionsFromChannelInput;
+}>;
+
+
+export type RemoveCollectionsFromChannelMutation = { removeCollectionsFromChannel: Array<(
+    { __typename?: 'Collection' }
+    & Pick<Collection, 'id' | 'name'>
+  )> };
+
 export type AddressFragment = (
   { __typename?: 'Address' }
   & Pick<Address, 'id' | 'createdAt' | 'updatedAt' | 'fullName' | 'company' | 'streetLine1' | 'streetLine2' | 'city' | 'province' | 'postalCode' | 'phoneNumber' | 'defaultShippingAddress' | 'defaultBillingAddress'>
@@ -9731,6 +9768,12 @@ export namespace DeleteCollection {
   export type DeleteCollection = (NonNullable<DeleteCollectionMutation['deleteCollection']>);
 }
 
+export namespace DeleteCollections {
+  export type Variables = DeleteCollectionsMutationVariables;
+  export type Mutation = DeleteCollectionsMutation;
+  export type DeleteCollections = NonNullable<(NonNullable<DeleteCollectionsMutation['deleteCollections']>)[number]>;
+}
+
 export namespace GetCollectionContents {
   export type Variables = GetCollectionContentsQueryVariables;
   export type Query = GetCollectionContentsQuery;
@@ -9746,6 +9789,18 @@ export namespace PreviewCollectionContents {
   export type Items = NonNullable<(NonNullable<(NonNullable<PreviewCollectionContentsQuery['previewCollectionVariants']>)['items']>)[number]>;
 }
 
+export namespace AssignCollectionsToChannel {
+  export type Variables = AssignCollectionsToChannelMutationVariables;
+  export type Mutation = AssignCollectionsToChannelMutation;
+  export type AssignCollectionsToChannel = NonNullable<(NonNullable<AssignCollectionsToChannelMutation['assignCollectionsToChannel']>)[number]>;
+}
+
+export namespace RemoveCollectionsFromChannel {
+  export type Variables = RemoveCollectionsFromChannelMutationVariables;
+  export type Mutation = RemoveCollectionsFromChannelMutation;
+  export type RemoveCollectionsFromChannel = NonNullable<(NonNullable<RemoveCollectionsFromChannelMutation['removeCollectionsFromChannel']>)[number]>;
+}
+
 export namespace Address {
   export type Fragment = AddressFragment;
   export type Country = (NonNullable<AddressFragment['country']>);

+ 27 - 0
packages/admin-ui/src/lib/core/src/data/definitions/collection-definitions.ts

@@ -118,6 +118,15 @@ export const DELETE_COLLECTION = gql`
     }
 `;
 
+export const DELETE_COLLECTIONS = gql`
+    mutation DeleteCollections($ids: [ID!]!) {
+        deleteCollections(ids: $ids) {
+            result
+            message
+        }
+    }
+`;
+
 export const GET_COLLECTION_CONTENTS = gql`
     query GetCollectionContents($id: ID!, $options: ProductVariantListOptions) {
         collection(id: $id) {
@@ -152,3 +161,21 @@ export const PREVIEW_COLLECTION_CONTENTS = gql`
         }
     }
 `;
+
+export const ASSIGN_COLLECTIONS_TO_CHANNEL = gql`
+    mutation AssignCollectionsToChannel($input: AssignCollectionsToChannelInput!) {
+        assignCollectionsToChannel(input: $input) {
+            id
+            name
+        }
+    }
+`;
+
+export const REMOVE_COLLECTIONS_FROM_CHANNEL = gql`
+    mutation RemoveCollectionsFromChannel($input: RemoveCollectionsFromChannelInput!) {
+        removeCollectionsFromChannel(input: $input) {
+            id
+            name
+        }
+    }
+`;

+ 38 - 0
packages/admin-ui/src/lib/core/src/data/providers/collection-data.service.ts

@@ -3,10 +3,15 @@ import { from } from 'rxjs';
 import { bufferCount, concatMap } from 'rxjs/operators';
 
 import {
+    AssignCollectionsToChannelInput,
+    AssignCollectionsToChannelMutation,
+    AssignCollectionsToChannelMutationVariables,
     CollectionFilterParameter,
     CreateCollection,
     CreateCollectionInput,
     DeleteCollection,
+    DeleteCollectionsMutation,
+    DeleteCollectionsMutationVariables,
     GetCollection,
     GetCollectionContents,
     GetCollectionFilters,
@@ -17,18 +22,24 @@ import {
     PreviewCollectionContentsQueryVariables,
     PreviewCollectionVariantsInput,
     ProductVariantListOptions,
+    RemoveCollectionsFromChannelInput,
+    RemoveCollectionsFromChannelMutation,
+    RemoveCollectionsFromChannelMutationVariables,
     UpdateCollection,
     UpdateCollectionInput,
 } from '../../common/generated-types';
 import {
+    ASSIGN_COLLECTIONS_TO_CHANNEL,
     CREATE_COLLECTION,
     DELETE_COLLECTION,
+    DELETE_COLLECTIONS,
     GET_COLLECTION,
     GET_COLLECTION_CONTENTS,
     GET_COLLECTION_FILTERS,
     GET_COLLECTION_LIST,
     MOVE_COLLECTION,
     PREVIEW_COLLECTION_CONTENTS,
+    REMOVE_COLLECTIONS_FROM_CHANNEL,
     UPDATE_COLLECTION,
 } from '../definitions/collection-definitions';
 
@@ -113,6 +124,15 @@ export class CollectionDataService {
         );
     }
 
+    deleteCollections(ids: string[]) {
+        return this.baseDataService.mutate<DeleteCollectionsMutation, DeleteCollectionsMutationVariables>(
+            DELETE_COLLECTIONS,
+            {
+                ids,
+            },
+        );
+    }
+
     previewCollectionVariants(input: PreviewCollectionVariantsInput, options: ProductVariantListOptions) {
         return this.baseDataService.query<
             PreviewCollectionContentsQuery,
@@ -136,4 +156,22 @@ export class CollectionDataService {
             },
         );
     }
+
+    assignCollectionsToChannel(input: AssignCollectionsToChannelInput) {
+        return this.baseDataService.mutate<
+            AssignCollectionsToChannelMutation,
+            AssignCollectionsToChannelMutationVariables
+        >(ASSIGN_COLLECTIONS_TO_CHANNEL, {
+            input,
+        });
+    }
+
+    removeCollectionsFromChannel(input: RemoveCollectionsFromChannelInput) {
+        return this.baseDataService.mutate<
+            RemoveCollectionsFromChannelMutation,
+            RemoveCollectionsFromChannelMutationVariables
+        >(REMOVE_COLLECTIONS_FROM_CHANNEL, {
+            input,
+        });
+    }
 }

+ 1 - 1
packages/admin-ui/src/lib/core/src/providers/bulk-action-registry/bulk-action-types.ts

@@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
  * @docsCategory bulk-actions
  * @docsPage BulkAction
  */
-export type BulkActionLocationId = 'product-list' | 'facet-list' | 'order-list' | string;
+export type BulkActionLocationId = 'product-list' | 'facet-list' | 'collection-list' | string;
 
 /**
  * @description

+ 32 - 19
packages/admin-ui/src/lib/core/src/shared/components/bulk-action-menu/bulk-action-menu.component.html

@@ -1,29 +1,42 @@
 <vdr-dropdown *ngIf="actions$ | async as actions">
-    <button class="btn btn-sm btn-outline" vdrDropdownTrigger [disabled]="!selectionManager.selection?.length" [class.hidden]="!selectionManager.selection?.length">
+    <button
+        class="btn btn-sm btn-outline mr1"
+        vdrDropdownTrigger
+        [disabled]="!selectionManager.selection?.length"
+        [class.hidden]="!selectionManager.selection?.length"
+    >
         <clr-icon shape="file-group"></clr-icon>
-        {{ 'common.with-selected' | translate }}
+        {{ 'common.with-selected' | translate: { count:selectionManager.selection.length } }}
     </button>
     <vdr-dropdown-menu vdrPosition="bottom-left">
-        <ng-container *ngFor="let action of actions">
-            <button
-                *ngIf="action.display"
-                [disabled]="!hasPermissions(action)"
-                type="button"
-                vdrDropdownItem
-                (click)="actionClick($event, action)"
-            >
-                <clr-icon
-                    *ngIf="action.icon"
-                    [attr.shape]="action.icon"
-                    [ngClass]="action.iconClass || ''"
-                ></clr-icon>
-                {{ action.label | translate: action.translationVars }}
-            </button>
+        <ng-container *ngIf="actions.length; else noActions">
+            <ng-container *ngFor="let action of actions">
+                <button
+                    *ngIf="action.display"
+                    [disabled]="!hasPermissions(action)"
+                    type="button"
+                    vdrDropdownItem
+                    (click)="actionClick($event, action)"
+                >
+                    <clr-icon
+                        *ngIf="action.icon"
+                        [attr.shape]="action.icon"
+                        [ngClass]="action.iconClass || ''"
+                    ></clr-icon>
+                    {{ action.label | translate: action.translationVars }}
+                </button>
+            </ng-container>
         </ng-container>
+        <ng-template #noActions>
+            <button type="button" disabled vdrDropdownItem>{{ 'common.no-bulk-actions-available' | translate }}</button>
+        </ng-template>
     </vdr-dropdown-menu>
 </vdr-dropdown>
-<button class="btn btn-sm btn-link" (click)="clearSelection()"
-        [class.hidden]="!selectionManager.selection?.length">
+<button
+    class="btn btn-sm btn-link"
+    (click)="clearSelection()"
+    [class.hidden]="!selectionManager.selection?.length"
+>
     <clr-icon shape="times"></clr-icon>
     {{ 'common.clear-selection' | translate }}
 </button>

+ 6 - 0
packages/admin-ui/src/lib/core/src/shared/components/facet-value-selector/facet-value-selector.component.html

@@ -12,10 +12,16 @@
 >
     <ng-template ng-label-tmp let-item="item" let-clear="clear">
         <vdr-facet-value-chip
+            *ngIf="item.value; else facetNotFound"
             [facetValue]="item.value"
             [removable]="!readonly"
             (remove)="clear(item)"
         ></vdr-facet-value-chip>
+        <ng-template #facetNotFound>
+            <vdr-chip colorType="error" icon="times" (iconClick)="clear(item)">{{
+                'catalog.facet-value-not-available' | translate: { id: item.id }
+            }}</vdr-chip>
+        </ng-template>
     </ng-template>
     <ng-template ng-option-tmp let-item="item">
         <vdr-facet-value-chip [facetValue]="item.value" [removable]="false"></vdr-facet-value-chip>

+ 1 - 1
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html

@@ -92,7 +92,7 @@
                         vdrDropdownTrigger
                         [disabled]="selectedCustomerIds.length === 0"
                     >
-                        {{ 'common.with-selected' | translate }}
+                        {{ 'common.with-selected' | translate: { count: selectedCustomerIds.length } }}
                         <clr-icon shape="caret down"></clr-icon>
                     </button>
                     <vdr-dropdown-menu vdrPosition="bottom-right">

+ 1 - 1
packages/admin-ui/src/lib/settings/src/components/zone-list/zone-list.component.html

@@ -94,7 +94,7 @@
                                 vdrDropdownTrigger
                                 [disabled]="selectedMemberIds.length === 0"
                             >
-                                {{ 'common.with-selected' | translate }}
+                                {{ 'common.with-selected' | translate: { count: selectedMemberIds.length } }}
                                 <clr-icon shape="caret down"></clr-icon>
                             </button>
                             <vdr-dropdown-menu vdrPosition="bottom-right">

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/cs.json

@@ -56,6 +56,7 @@
     "add-facets": "Přidat atribut",
     "add-option": "Přidat možnost",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Produkt byl úspěšně přiřazen do \"{ channel }\"",
     "assign-products-to-channel": "Přiřadit produkty do kanálu",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Automaticky aktualizovat jména variant",
     "channel-price-preview": "Náhled ceny v kanálu",
     "collection-contents": "Obsah kolekce",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Rozevřít všechny kolekce",
+    "facet-value-not-available": "",
     "facet-values": "Hodnoty atributů",
     "filter-by-name": "Filtrovat dle jména",
     "filter-by-name-or-sku": "Filtrovat dle jména nebo SKU",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Žádný kanál nevybrán",
     "no-featured-asset": "Žádné zvýrazněné médium",
     "no-selection": "Žádný výběr",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Produkt se nepovedlo odebrat z kanálu",
     "notify-remove-product-from-channel-success": "Produkt byl úspěšně odebrán z kanálu",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Více...",
     "name": "jméno",
+    "no-bulk-actions-available": "",
     "no-results": "Žádné výsledky",
     "not-applicable": "",
     "not-set": "Nenastaveno",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Vyskytla se chyba, nebylo vytvořeno: { entity }",
     "notify-create-success": "Vytvořeno: { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -56,6 +56,7 @@
     "add-facets": "Facetten hinzufügen",
     "add-option": "Option hinzufügen",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Produkt erfolgreich an \"{ channel }\" zugewiesen",
     "assign-products-to-channel": "Produkte dem Kanal zuweisen",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Automatisch Namen der Produktvariante aktualisieren",
     "channel-price-preview": "Kanal-Preisvorschau",
     "collection-contents": "Inhalt der Sammlung",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Alle Sammlungen erweitern",
+    "facet-value-not-available": "",
     "facet-values": "Facettenwerte",
     "filter-by-name": "Nach Name filtern",
     "filter-by-name-or-sku": "Nach Name oder Artikelnummer filtern",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Kein Kanal ausgewählt",
     "no-featured-asset": "Kein \"Featured Asset\"",
     "no-selection": "Keine Auswahl",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Das Produkt konnte nicht aus dem Kanal entfernt werden",
     "notify-remove-product-from-channel-success": "Das Produkt wurde erfolgreich aus dem Kanal entfernt",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Mehr...",
     "name": "Name",
+    "no-bulk-actions-available": "",
     "no-results": "Keine Ergebnisse",
     "not-applicable": "",
     "not-set": "Nicht festgelegt",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Ein Fehler ist aufgetreten, { entity } konnte nicht erstellt werden",
     "notify-create-success": "{ entity } erstellt",

+ 9 - 2
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -56,6 +56,7 @@
     "add-facets": "Add facets",
     "add-option": "Add option",
     "asset-preview-links": "Asset preview links",
+    "assign-collections-to-channel-success": "Successfully assigned {count, plural, one {1 collection} other {{count} collections}} to { channelCode }",
     "assign-facets-to-channel-success": "Successfully assigned {count, plural, one {1 facet} other {{count} facets}} to { channelCode }",
     "assign-product-to-channel-success": "Successfully assigned {count, plural, one {1 product} other {{count} products}} to { channel }",
     "assign-products-to-channel": "Assign products to channel",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Automatically update the names of ProductVariants",
     "channel-price-preview": "Channel price preview",
     "collection-contents": "Collection contents",
+    "confirm-bulk-delete-collections": "Delete {count} collections?",
     "confirm-bulk-delete-facets": "Delete {count} facets?",
     "confirm-bulk-delete-products": "Delete {count} products?",
     "confirm-cancel": "Cancel?",
@@ -100,6 +102,7 @@
     "edit-facet-values": "Edit facet values",
     "edit-options": "Edit options",
     "expand-all-collections": "Expand all collections",
+    "facet-value-not-available": "Facet value \"{ id }\" not available",
     "facet-values": "Facet values",
     "filter-by-name": "Filter by name",
     "filter-by-name-or-sku": "Filter by name or SKU",
@@ -113,6 +116,10 @@
     "no-channel-selected": "No channel selected",
     "no-featured-asset": "No featured asset",
     "no-selection": "No selection",
+    "notify-bulk-delete-collections-success": "Successfully deleted {count, plural, one {1 collection} other {{count} collections}}",
+    "notify-bulk-delete-facets-success": "Successfully deleted {count, plural, one {1 facet} other {{count} facets}}",
+    "notify-bulk-delete-products-success": "Successfully deleted {count, plural, one {1 product} other {{count} products}}",
+    "notify-remove-collections-from-channel-success": "Successfully removed {count, plural, one {1 collection} other {{count} collections}} from { channelCode }",
     "notify-remove-facets-from-channel-success": "Successfully removed {count, plural, one {1 facet} other {{count} facets}} from { channelCode }",
     "notify-remove-product-from-channel-error": "Could not remove product from channel",
     "notify-remove-product-from-channel-success": "Successfully removed product from channel",
@@ -231,10 +238,10 @@
     "medium-date": "Medium date",
     "more": "More...",
     "name": "Name",
+    "no-bulk-actions-available": "No bulk actions available",
     "no-results": "No results",
     "not-applicable": "Not applicable",
     "not-set": "Not set",
-    "notify-bulk-delete-success": "Deleted { count } { entity }",
     "notify-bulk-update-success": "Updated { count } { entity }",
     "notify-create-error": "An error occurred, could not create { entity }",
     "notify-create-success": "Created new { entity }",
@@ -276,7 +283,7 @@
     "username": "Username",
     "view-next-month": "View next month",
     "view-previous-month": "View previous month",
-    "with-selected": "With selected..."
+    "with-selected": "With {count} selected..."
   },
   "customer": {
     "add-customer-to-group": "Add customer to group",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/es.json

@@ -56,6 +56,7 @@
     "add-facets": "Añadir facetas",
     "add-option": "Añadir opción",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Producto asignado a \"{ channel }\" con éxito",
     "assign-products-to-channel": "Asignar productos a canal de ventas",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Actualiza los nombres de las variantes de producto automáticamente",
     "channel-price-preview": "Vista previa de precio para el canal de ventas",
     "collection-contents": "Contenidos de la colección",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "Editar opciones",
     "expand-all-collections": "Expandir todas las colecciones",
+    "facet-value-not-available": "",
     "facet-values": "Valores de faceta",
     "filter-by-name": "Filtrar por nombre",
     "filter-by-name-or-sku": "Filtrar por código de referencia",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Ninún canal seleccionado",
     "no-featured-asset": "Sin recurso destacado",
     "no-selection": "Sin selección",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "No fue posible eliminar el producto del canal",
     "notify-remove-product-from-channel-success": "Producto eliminado del canal con éxito",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Más...",
     "name": "Nombre",
+    "no-bulk-actions-available": "",
     "no-results": "Sin resultados",
     "not-applicable": "",
     "not-set": "Sin fijar",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Ha ocurrido un problema, imposible de crear { entity }",
     "notify-create-success": "Creado nuevo { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/fr.json

@@ -56,6 +56,7 @@
     "add-facets": "Ajout composant",
     "add-option": "Ajout option",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "produit attribué au canal \"{ channel }\"",
     "assign-products-to-channel": "Attribuer les produits au canal",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Mettre à jour automatiquement les noms de variations du produit ",
     "channel-price-preview": "Prévisualisation du prix du canal",
     "collection-contents": "Contenu de la Collection",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Etendre toutes les collections",
+    "facet-value-not-available": "",
     "facet-values": "Valeurs de composant",
     "filter-by-name": "Filtrer par nom",
     "filter-by-name-or-sku": "Filtrer par nom ou UGS",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Pas de canal sélectionné",
     "no-featured-asset": "Pas de fichier vedette",
     "no-selection": "Pas de sélection",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Retrait du produit du canal échoué",
     "notify-remove-product-from-channel-success": "Retrait du produit du canal réussi",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Plus...",
     "name": "Nom",
+    "no-bulk-actions-available": "",
     "no-results": "Aucun resultat",
     "not-applicable": "",
     "not-set": "Non défini",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Une erreur est survenue, création de { entity } échouée",
     "notify-create-success": "Nouveau { entity } créé",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/it.json

@@ -56,6 +56,7 @@
     "add-facets": "Aggiungi attributi",
     "add-option": "Aggiungi opzione",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Prodotto assegnato correttamente a \"{ channel }\"",
     "assign-products-to-channel": "Assegna prodotto al canale",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Aggiorna automaticamente i nomi delle Varianti",
     "channel-price-preview": "Anteprima prezzo canale",
     "collection-contents": "Contenuti della Collezione",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Espandi le collezioni",
+    "facet-value-not-available": "",
     "facet-values": "Valori attributo",
     "filter-by-name": "Filtra per nome",
     "filter-by-name-or-sku": "Filtra per nome o SKU",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Nessun canale selezionato",
     "no-featured-asset": "Nessun media in evidenza",
     "no-selection": "Nessuna selezione",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Impossibile rimuovere il prodotto dal canale",
     "notify-remove-product-from-channel-success": "Prodotto rimosso dal canale",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Altri...",
     "name": "Nome",
+    "no-bulk-actions-available": "",
     "no-results": "Nessun risultato",
     "not-applicable": "",
     "not-set": "Non impostato",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Si è verificato un errore, impossibile creare { entity }",
     "notify-create-success": "Creato nuovo { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/pl.json

@@ -56,6 +56,7 @@
     "add-facets": "Dodaj faset",
     "add-option": "Dodaj opcje",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Pomyślnie przypisano produkt do \"{ channel }\"",
     "assign-products-to-channel": "Przypisz produkt do kanału",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "",
     "channel-price-preview": "Podgląd cen kanału",
     "collection-contents": "Zawartość kolekcji",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Rozwiń wszystkie kolekcje",
+    "facet-value-not-available": "",
     "facet-values": "Wartości faseta",
     "filter-by-name": "Filtruj po nazwie",
     "filter-by-name-or-sku": "",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Brak zaznaczonego kanału",
     "no-featured-asset": "Brak polecanego zasobu",
     "no-selection": "Brak zaznaczenia",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Błąd usuwania produktu z kanału",
     "notify-remove-product-from-channel-success": "Produkt pomyślnie usunięty z kanału",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Więcej...",
     "name": "Nazwa",
+    "no-bulk-actions-available": "",
     "no-results": "Brak wyników",
     "not-applicable": "",
     "not-set": "Nie ustawione",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Wystąpił błąd, nie można utworzyć { entity }",
     "notify-create-success": "Utworzono { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/pt_BR.json

@@ -56,6 +56,7 @@
     "add-facets": "Adiciona etiqueta",
     "add-option": "Adiciona opção",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Produto atribuído com sucesso a \"{ channel }\"",
     "assign-products-to-channel": "Atribuir produtos ao canal",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Atualizar automaticamente os nomes das variações do produto",
     "channel-price-preview": "Visualizar preço do canal",
     "collection-contents": "Conteúdo da categoria",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Expandir todas as categorias",
+    "facet-value-not-available": "",
     "facet-values": "Valor da Etiqueta",
     "filter-by-name": "Filtrar por nome",
     "filter-by-name-or-sku": "Filtrar por nome ou SKU",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Nenhum canal selecionado",
     "no-featured-asset": "Nenhum recurso em destaque",
     "no-selection": "Nenhuma seleção",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Não foi possível remover o produto do canal",
     "notify-remove-product-from-channel-success": "Produto removido com sucesso do canal",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Mais...",
     "name": "Nome",
+    "no-bulk-actions-available": "",
     "no-results": "Sem resultados",
     "not-applicable": "",
     "not-set": "Não configurado",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Ocorreu um erro, não foi possível criar { entity }",
     "notify-create-success": "Criado novo { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/pt_PT.json

@@ -56,6 +56,7 @@
     "add-facets": "Adicionar etiqueta",
     "add-option": "Adicionar opção",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Produto atribuído com sucesso a \"{ channel }\"",
     "assign-products-to-channel": "Atribuir produtos ao canal",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Actualizar automaticamente os nomes das variantes do produto",
     "channel-price-preview": "Visualizar preço do canal",
     "collection-contents": "Conteúdo da categoria",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "Editar opções",
     "expand-all-collections": "Expandir todas as categorias",
+    "facet-value-not-available": "",
     "facet-values": "Valor da Etiqueta",
     "filter-by-name": "Filtrar por nome",
     "filter-by-name-or-sku": "Filtrar por nome ou SKU",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Nenhum canal seleccionado",
     "no-featured-asset": "Nenhum recurso em destaque",
     "no-selection": "Nenhuma imagem seleccionada",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Não foi possível remover o produto do canal",
     "notify-remove-product-from-channel-success": "Produto removido do canal com sucesso",
@@ -231,10 +238,10 @@
     "medium-date": "Data média",
     "more": "Mais...",
     "name": "Nome",
+    "no-bulk-actions-available": "",
     "no-results": "Nenhum resultado encontrado",
     "not-applicable": "",
     "not-set": "Não configurado",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Ocorreu um erro. Não foi possível criar { entity }",
     "notify-create-success": "Novo(a) { entity } adicionado(a)",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/ru.json

@@ -56,6 +56,7 @@
     "add-facets": "Добавить тег",
     "add-option": "Добавить опции",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Товар успешно добавлен в канал \"{ channel }\"",
     "assign-products-to-channel": "Добавить товары в канал",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Автоматически обновлять названия вариантов товара",
     "channel-price-preview": "Предварительный просмотр цен канала",
     "collection-contents": "Содержание коллекции",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Развернуть все коллекции",
+    "facet-value-not-available": "",
     "facet-values": "Значения тега",
     "filter-by-name": "Фильтр по имени",
     "filter-by-name-or-sku": "Фильтр по имени или артикулу (SKU)",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Канал не выбран",
     "no-featured-asset": "Нет избранного медиа-объекта",
     "no-selection": "Не выбрано",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Не удалось удалить товар из канала",
     "notify-remove-product-from-channel-success": "Товар успешно удален из канала",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Больше...",
     "name": "Имя",
+    "no-bulk-actions-available": "",
     "no-results": "Нет результатов",
     "not-applicable": "",
     "not-set": "Не задано",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Ошибка, не удалось создать { entity }",
     "notify-create-success": "Создано новое { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/uk.json

@@ -56,6 +56,7 @@
     "add-facets": "Додати тег",
     "add-option": "Додати опцію",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "Товар успішно доданий в канал \"{ channel }\"",
     "assign-products-to-channel": "Додати товари в канал",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "Автоматично оновлювати назви варіантів товару",
     "channel-price-preview": "Попередній перегляд цін каналу",
     "collection-contents": "Зміст колекції",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "Розгорнути всі колекції",
+    "facet-value-not-available": "",
     "facet-values": "Значення тегу",
     "filter-by-name": "Фільтр по імені",
     "filter-by-name-or-sku": "Фільтр по імені або артикулу (SKU)",
@@ -113,6 +116,10 @@
     "no-channel-selected": "Канал не вибрано",
     "no-featured-asset": "Немає обраного медіа-об'єкта",
     "no-selection": "Не вибрано",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "Не вдалося видалити товар з каналу",
     "notify-remove-product-from-channel-success": "Товар успішно видалений з каналу",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "Більше...",
     "name": "Ім'я",
+    "no-bulk-actions-available": "",
     "no-results": "Немає результатів",
     "not-applicable": "",
     "not-set": "Не задано",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "Помилка, не вдалося створити { entity }",
     "notify-create-success": "Створено нове { entity }",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hans.json

@@ -56,6 +56,7 @@
     "add-facets": "添加特征",
     "add-option": "添加规格组",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "成功将产品添加至销售渠道\"{ channel }\"",
     "assign-products-to-channel": "分配产品到销售渠道",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "自动更新不同商品变体名称",
     "channel-price-preview": "渠道价格预览",
     "collection-contents": "系列产品",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "展开所有系列",
+    "facet-value-not-available": "",
     "facet-values": "特征值列表",
     "filter-by-name": "按名字过滤",
     "filter-by-name-or-sku": "按名字或商品编码过滤",
@@ -113,6 +116,10 @@
     "no-channel-selected": "未选择销售渠道",
     "no-featured-asset": "无特征图片",
     "no-selection": "尚未选择",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "从渠道中移除商品失败",
     "notify-remove-product-from-channel-success": "成功从渠道中移除商品",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "更多...",
     "name": "名称",
+    "no-bulk-actions-available": "",
     "no-results": "没找到任何结果",
     "not-applicable": "",
     "not-set": "未设置",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "添加{ entity }失败",
     "notify-create-success": "{ entity }已添加",

+ 8 - 1
packages/admin-ui/src/lib/static/i18n-messages/zh_Hant.json

@@ -56,6 +56,7 @@
     "add-facets": "新增特徵",
     "add-option": "新增規格選項",
     "asset-preview-links": "",
+    "assign-collections-to-channel-success": "",
     "assign-facets-to-channel-success": "",
     "assign-product-to-channel-success": "成功將產品新增至渠道\"{ channel }\"",
     "assign-products-to-channel": "分配產品到渠道",
@@ -67,6 +68,7 @@
     "auto-update-product-variant-name": "",
     "channel-price-preview": "渠道價格覽",
     "collection-contents": "系列產品",
+    "confirm-bulk-delete-collections": "",
     "confirm-bulk-delete-facets": "",
     "confirm-bulk-delete-products": "",
     "confirm-cancel": "",
@@ -100,6 +102,7 @@
     "edit-facet-values": "",
     "edit-options": "",
     "expand-all-collections": "展開所有系列",
+    "facet-value-not-available": "",
     "facet-values": "特徵值列表",
     "filter-by-name": "按名字篩選",
     "filter-by-name-or-sku": "",
@@ -113,6 +116,10 @@
     "no-channel-selected": "並未選擇渠道",
     "no-featured-asset": "並無特徵圖片",
     "no-selection": "尚未選擇",
+    "notify-bulk-delete-collections-success": "",
+    "notify-bulk-delete-facets-success": "",
+    "notify-bulk-delete-products-success": "",
+    "notify-remove-collections-from-channel-success": "",
     "notify-remove-facets-from-channel-success": "",
     "notify-remove-product-from-channel-error": "從渠道中移除商品失敗",
     "notify-remove-product-from-channel-success": "成功從渠道中移除商品",
@@ -231,10 +238,10 @@
     "medium-date": "",
     "more": "更多...",
     "name": "名稱",
+    "no-bulk-actions-available": "",
     "no-results": "没找到任何結果",
     "not-applicable": "",
     "not-set": "未設定",
-    "notify-bulk-delete-success": "",
     "notify-bulk-update-success": "",
     "notify-create-error": "新增{ entity }失敗",
     "notify-create-success": "{ entity }已新增",