diff --git a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html
index 28c5f21869e..504b20c328b 100644
--- a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html
+++ b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.html
@@ -1,27 +1,3 @@
-
-
{{ 'collection.edit.template.label' | translate}}
-
- @if (!itemTemplateRD?.payload) {
-
- }
- @if (itemTemplateRD?.payload) {
-
- }
- @if (itemTemplateRD?.payload) {
-
- }
-
-
{
let comp: CollectionMetadataComponent;
let fixture: ComponentFixture;
- let router: Router;
- let itemTemplateService: ItemTemplateDataService;
- const template = Object.assign(new Item(), {
- _links: {
- self: { href: 'template-selflink' },
- },
- });
const collection = Object.assign(new Collection(), {
uuid: 'collection-id',
id: 'collection-id',
@@ -53,56 +32,27 @@ describe('CollectionMetadataComponent', () => {
self: { href: 'collection-selflink' },
},
});
- const collectionTemplateHref = 'rest/api/test/collections/template';
-
- const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', {
- findByCollectionID: createSuccessfulRemoteDataObject$(template),
- createByCollectionID: createSuccessfulRemoteDataObject$(template),
- delete: of(true),
- getCollectionEndpoint: of(collectionTemplateHref),
- });
-
- const notificationsService = jasmine.createSpyObj('notificationsService', {
- success: {},
- error: {},
- });
- const requestService = jasmine.createSpyObj('requestService', {
- setStaleByHrefSubstring: {},
- });
-
- const routerMock = {
- events: of(new NavigationEnd(1, 'url', 'url')),
- navigate: jasmine.createSpy('navigate'),
- };
beforeEach(waitForAsync(() => {
- TestBed.configureTestingModule({
+ void TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), CommonModule, RouterTestingModule, CollectionMetadataComponent],
providers: [
{ provide: CollectionDataService, useValue: {} },
- { provide: ItemTemplateDataService, useValue: itemTemplateServiceStub },
{ provide: ActivatedRoute, useValue: { parent: { data: of({ dso: createSuccessfulRemoteDataObject(collection) }) } } },
- { provide: NotificationsService, useValue: notificationsService },
- { provide: RequestService, useValue: requestService },
- { provide: Router, useValue: routerMock },
- { provide: AuthService, useValue: new AuthServiceMock() },
- { provide: CommunityDataService, useValue: {} },
- { provide: ObjectCacheService, useValue: {} },
- { provide: APP_DATA_SERVICES_MAP, useValue: {} },
- { provide: APP_CONFIG, useValue: {} },
+ { provide: NotificationsService, useValue: {} },
+ { provide: Router, useValue: {} },
],
schemas: [NO_ERRORS_SCHEMA],
- }).compileComponents();
+ })
+ .overrideComponent(CollectionMetadataComponent, {
+ remove: { imports: [CollectionFormComponent] },
+ })
+ .compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CollectionMetadataComponent);
comp = fixture.componentInstance;
- itemTemplateService = (comp as any).itemTemplateService;
- spyOn(comp, 'ngOnInit');
- spyOn(comp, 'initTemplateItem');
-
- routerMock.events = of(new NavigationEnd(1, 'url', 'url'));
fixture.detectChanges();
});
@@ -111,39 +61,4 @@ describe('CollectionMetadataComponent', () => {
expect((comp as any).frontendURL).toEqual('/collections/');
});
});
-
- describe('addItemTemplate', () => {
- it('should navigate to the collection\'s itemtemplate page', () => {
- comp.addItemTemplate();
- expect(routerMock.navigate).toHaveBeenCalledWith([getCollectionItemTemplateRoute(collection.uuid)]);
- });
- });
-
- describe('deleteItemTemplate', () => {
- beforeEach(() => {
- (itemTemplateService.delete as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$({}));
- comp.deleteItemTemplate();
- });
-
- it('should call ItemTemplateService.delete', () => {
- expect(itemTemplateService.delete).toHaveBeenCalledWith(template.uuid);
- });
-
- describe('when delete returns a success', () => {
- it('should display a success notification', () => {
- expect(notificationsService.success).toHaveBeenCalled();
- });
- });
-
- describe('when delete returns a failure', () => {
- beforeEach(() => {
- (itemTemplateService.delete as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
- comp.deleteItemTemplate();
- });
-
- it('should display an error notification', () => {
- expect(notificationsService.error).toHaveBeenCalled();
- });
- });
- });
});
diff --git a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts
index c4af7af522c..a850121f241 100644
--- a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts
+++ b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts
@@ -1,46 +1,16 @@
import { AsyncPipe } from '@angular/common';
-import {
- ChangeDetectorRef,
- Component,
- OnInit,
-} from '@angular/core';
+import { Component } from '@angular/core';
import {
ActivatedRoute,
- NavigationEnd,
Router,
- RouterLink,
- Scroll,
} from '@angular/router';
import { CollectionDataService } from '@dspace/core/data/collection-data.service';
-import { ItemTemplateDataService } from '@dspace/core/data/item-template-data.service';
-import { RemoteData } from '@dspace/core/data/remote-data';
-import { RequestService } from '@dspace/core/data/request.service';
import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
import { Collection } from '@dspace/core/shared/collection.model';
-import { Item } from '@dspace/core/shared/item.model';
-import { NoContent } from '@dspace/core/shared/NoContent.model';
-import {
- getFirstCompletedRemoteData,
- getFirstSucceededRemoteDataPayload,
-} from '@dspace/core/shared/operators';
-import { hasValue } from '@dspace/shared/utils/empty.util';
-import {
- TranslateModule,
- TranslateService,
-} from '@ngx-translate/core';
-import {
- combineLatest as combineLatestObservable,
- Observable,
-} from 'rxjs';
-import {
- map,
- switchMap,
-} from 'rxjs/operators';
+import { TranslateService } from '@ngx-translate/core';
import { ComcolMetadataComponent } from '../../../shared/comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
-import { VarDirective } from '../../../shared/utils/var.directive';
import { CollectionFormComponent } from '../../collection-form/collection-form.component';
-import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
/**
* Component for editing a collection's metadata
@@ -51,103 +21,19 @@ import { getCollectionItemTemplateRoute } from '../../collection-page-routing-pa
imports: [
AsyncPipe,
CollectionFormComponent,
- RouterLink,
- TranslateModule,
- VarDirective,
],
})
-export class CollectionMetadataComponent extends ComcolMetadataComponent implements OnInit {
+export class CollectionMetadataComponent extends ComcolMetadataComponent {
protected frontendURL = '/collections/';
protected type = Collection.type;
- /**
- * The collection's item template
- */
- itemTemplateRD$: Observable>;
-
public constructor(
protected collectionDataService: CollectionDataService,
- protected itemTemplateService: ItemTemplateDataService,
protected router: Router,
protected route: ActivatedRoute,
protected notificationsService: NotificationsService,
protected translate: TranslateService,
- protected requestService: RequestService,
- protected chd: ChangeDetectorRef,
) {
super(collectionDataService, router, route, notificationsService, translate);
}
-
- /**
- * Checking if the navigation is done and if so, initialize the collection's item template,
- * to ensure that the item template is always up to date.
- * Check when a NavigationEnd event (URL change) or a Scroll event followed by a NavigationEnd event (refresh event), occurs
- */
- ngOnInit(): void {
- this.router.events.subscribe((event) => {
- if (
- event instanceof NavigationEnd ||
- (event instanceof Scroll && event.routerEvent instanceof NavigationEnd)
- ) {
- super.ngOnInit();
- this.initTemplateItem();
- this.chd.detectChanges();
- }
- });
- }
-
- /**
- * Initialize the collection's item template
- */
- initTemplateItem() {
- this.itemTemplateRD$ = this.dsoRD$.pipe(
- getFirstSucceededRemoteDataPayload(),
- switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
- );
- }
-
- /**
- * Add a new item template to the collection and redirect to the item template edit page
- */
- addItemTemplate() {
- const collection$ = this.dsoRD$.pipe(
- getFirstSucceededRemoteDataPayload(),
- );
- const template$ = collection$.pipe(
- switchMap((collection: Collection) => this.itemTemplateService.createByCollectionID(new Item(), collection.uuid).pipe(
- getFirstSucceededRemoteDataPayload(),
- )),
- );
- const templateHref$ = collection$.pipe(
- switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)),
- );
-
- combineLatestObservable(collection$, template$, templateHref$).subscribe(([collection, template, templateHref]) => {
- this.requestService.setStaleByHrefSubstring(templateHref);
- this.router.navigate([getCollectionItemTemplateRoute(collection.uuid)]);
- });
- }
-
- /**
- * Delete the item template from the collection
- */
- deleteItemTemplate() {
- this.dsoRD$.pipe(
- getFirstSucceededRemoteDataPayload(),
- switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
- getFirstSucceededRemoteDataPayload(),
- switchMap((template) => {
- return this.itemTemplateService.delete(template.uuid);
- }),
- getFirstCompletedRemoteData(),
- map((response: RemoteData) => hasValue(response) && response.hasSucceeded),
- ).subscribe((success: boolean) => {
- if (success) {
- this.notificationsService.success(null, this.translate.get('collection.edit.template.notifications.delete.success'));
- } else {
- this.notificationsService.error(null, this.translate.get('collection.edit.template.notifications.delete.error'));
- }
- this.initTemplateItem();
- });
- }
}
diff --git a/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.html b/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.html
new file mode 100644
index 00000000000..7a05f133ff0
--- /dev/null
+++ b/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.html
@@ -0,0 +1,34 @@
+
+ @if (!editing) {
+
{{ 'collection.edit.template.label' | translate }}
+
+ @if (!itemTemplateRD?.payload) {
+
+ }
+ @if (itemTemplateRD?.payload) {
+
+ }
+ @if (itemTemplateRD?.payload) {
+
+ }
+
+ }
+
+ @if (editing && itemTemplateRD?.payload) {
+
+ {{ 'collection.edit.template.head' | translate: { collection: dsoNameService.getName((dsoRD$ | async)?.payload) } }}
+
+
+
+ }
+
\ No newline at end of file
diff --git a/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.spec.ts
new file mode 100644
index 00000000000..f09a84bfca7
--- /dev/null
+++ b/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.spec.ts
@@ -0,0 +1,160 @@
+import { CommonModule } from '@angular/common';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import {
+ ComponentFixture,
+ TestBed,
+ waitForAsync,
+} from '@angular/core/testing';
+import {
+ ActivatedRoute,
+ Router,
+} from '@angular/router';
+import { RouterTestingModule } from '@angular/router/testing';
+import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service';
+import { ItemTemplateDataService } from '@dspace/core/data/item-template-data.service';
+import { RequestService } from '@dspace/core/data/request.service';
+import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
+import { Collection } from '@dspace/core/shared/collection.model';
+import { Item } from '@dspace/core/shared/item.model';
+import { DSONameServiceMock } from '@dspace/core/testing/dso-name.service.mock';
+import {
+ createFailedRemoteDataObject$,
+ createSuccessfulRemoteDataObject,
+ createSuccessfulRemoteDataObject$,
+} from '@dspace/core/utilities/remote-data.utils';
+import { TranslateModule } from '@ngx-translate/core';
+import { of } from 'rxjs';
+
+import { ThemedDsoEditMetadataComponent } from '../../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component';
+import { CollectionTemplateItemComponent } from './collection-template-item.component';
+
+describe('CollectionTemplateItemComponent', () => {
+ let comp: CollectionTemplateItemComponent;
+ let fixture: ComponentFixture;
+ let itemTemplateService: ItemTemplateDataService;
+
+ const template = Object.assign(new Item(), {
+ uuid: 'template-uuid',
+ _links: {
+ self: { href: 'template-selflink' },
+ },
+ });
+ const collection = Object.assign(new Collection(), {
+ uuid: 'collection-id',
+ id: 'collection-id',
+ name: 'Fake Collection',
+ _links: {
+ self: { href: 'collection-selflink' },
+ },
+ });
+ const collectionTemplateHref = 'rest/api/test/collections/template';
+
+ const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', {
+ findByCollectionID: createSuccessfulRemoteDataObject$(template),
+ createByCollectionID: createSuccessfulRemoteDataObject$(template),
+ delete: of(true),
+ getCollectionEndpoint: of(collectionTemplateHref),
+ });
+
+ const notificationsService = jasmine.createSpyObj('notificationsService', {
+ success: {},
+ error: {},
+ });
+ const requestService = jasmine.createSpyObj('requestService', {
+ setStaleByHrefSubstring: {},
+ });
+
+ beforeEach(waitForAsync(() => {
+ void TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), CommonModule, RouterTestingModule, CollectionTemplateItemComponent],
+ providers: [
+ { provide: ItemTemplateDataService, useValue: itemTemplateServiceStub },
+ { provide: ActivatedRoute, useValue: { parent: { data: of({ dso: createSuccessfulRemoteDataObject(collection) }) } } },
+ { provide: NotificationsService, useValue: notificationsService },
+ { provide: RequestService, useValue: requestService },
+ { provide: Router, useValue: {} },
+ { provide: DSONameService, useValue: new DSONameServiceMock() },
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+ })
+ .overrideComponent(CollectionTemplateItemComponent, {
+ remove: { imports: [ThemedDsoEditMetadataComponent] },
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CollectionTemplateItemComponent);
+ comp = fixture.componentInstance;
+ itemTemplateService = (comp as any).itemTemplateService;
+ fixture.detectChanges();
+ });
+
+ describe('ngOnInit', () => {
+ it('should initialize the collection\'s item template', () => {
+ comp.itemTemplateRD$.subscribe((rd) => {
+ expect(rd.payload).toEqual(template);
+ });
+ });
+ });
+
+ describe('addItemTemplate', () => {
+ beforeEach(() => {
+ comp.addItemTemplate();
+ });
+
+ it('should create an item template for the collection', () => {
+ expect(itemTemplateService.createByCollectionID).toHaveBeenCalledWith(jasmine.any(Item), collection.uuid);
+ });
+
+ it('should start editing the item template', () => {
+ expect(comp.editing).toBeTrue();
+ });
+ });
+
+ describe('editItemTemplate', () => {
+ it('should start editing the item template', () => {
+ comp.editItemTemplate();
+ expect(comp.editing).toBeTrue();
+ });
+ });
+
+ describe('cancelEdit', () => {
+ beforeEach(() => {
+ comp.editing = true;
+ });
+
+ it('should stop editing the item template', () => {
+ comp.cancelEdit();
+ expect(comp.editing).toBeFalse();
+ });
+ });
+
+ describe('deleteItemTemplate', () => {
+ beforeEach(() => {
+ (itemTemplateService.delete as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$({}));
+ comp.deleteItemTemplate();
+ });
+
+ it('should call ItemTemplateService.delete', () => {
+ expect(itemTemplateService.delete).toHaveBeenCalledWith(template.uuid);
+ });
+
+ describe('when delete returns a success', () => {
+ it('should display a success notification', () => {
+ expect(notificationsService.success).toHaveBeenCalled();
+ });
+ });
+
+ describe('when delete returns a failure', () => {
+ beforeEach(() => {
+ (itemTemplateService.delete as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
+ comp.deleteItemTemplate();
+ });
+
+ it('should display an error notification', () => {
+ expect(notificationsService.error).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.ts b/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.ts
new file mode 100644
index 00000000000..27c4d5e85eb
--- /dev/null
+++ b/src/app/collection-page/edit-collection-page/collection-template-item/collection-template-item.component.ts
@@ -0,0 +1,150 @@
+import { AsyncPipe } from '@angular/common';
+import {
+ Component,
+ OnInit,
+} from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { DSONameService } from '@dspace/core/breadcrumbs/dso-name.service';
+import { ItemTemplateDataService } from '@dspace/core/data/item-template-data.service';
+import { RemoteData } from '@dspace/core/data/remote-data';
+import { RequestService } from '@dspace/core/data/request.service';
+import { NotificationsService } from '@dspace/core/notification-system/notifications.service';
+import { Collection } from '@dspace/core/shared/collection.model';
+import { Item } from '@dspace/core/shared/item.model';
+import { NoContent } from '@dspace/core/shared/NoContent.model';
+import {
+ getFirstCompletedRemoteData,
+ getFirstSucceededRemoteDataPayload,
+} from '@dspace/core/shared/operators';
+import { hasValue } from '@dspace/shared/utils/empty.util';
+import {
+ TranslateModule,
+ TranslateService,
+} from '@ngx-translate/core';
+import {
+ combineLatest,
+ Observable,
+} from 'rxjs';
+import {
+ map,
+ switchMap,
+} from 'rxjs/operators';
+
+import { ThemedDsoEditMetadataComponent } from '../../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component';
+import { VarDirective } from '../../../shared/utils/var.directive';
+
+/**
+ * Component for managing the item template of a collection
+ */
+@Component({
+ selector: 'ds-collection-template-item',
+ templateUrl: './collection-template-item.component.html',
+ imports: [
+ AsyncPipe,
+ ThemedDsoEditMetadataComponent,
+ TranslateModule,
+ VarDirective,
+ ],
+})
+export class CollectionTemplateItemComponent implements OnInit {
+ /**
+ * The collection to manage the item template for
+ */
+ dsoRD$: Observable>;
+
+ /**
+ * The collection's item template
+ */
+ itemTemplateRD$: Observable>;
+
+ /**
+ * Whether the user is currently editing the item template
+ */
+ editing = false;
+
+ public constructor(
+ protected itemTemplateService: ItemTemplateDataService,
+ protected route: ActivatedRoute,
+ protected notificationsService: NotificationsService,
+ protected translate: TranslateService,
+ protected requestService: RequestService,
+ public dsoNameService: DSONameService,
+ ) {
+ }
+
+ ngOnInit(): void {
+ this.dsoRD$ = this.route.parent.data.pipe(map((data) => data.dso));
+ this.initTemplateItem();
+ }
+
+ /**
+ * Initialize the collection's item template
+ */
+ initTemplateItem() {
+ this.itemTemplateRD$ = this.dsoRD$.pipe(
+ getFirstSucceededRemoteDataPayload(),
+ switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
+ );
+ }
+
+ /**
+ * Add a new item template to the collection and start editing it
+ */
+ addItemTemplate() {
+ const collection$ = this.dsoRD$.pipe(
+ getFirstSucceededRemoteDataPayload(),
+ );
+ const template$ = collection$.pipe(
+ switchMap((collection: Collection) => this.itemTemplateService.createByCollectionID(new Item(), collection.uuid).pipe(
+ getFirstSucceededRemoteDataPayload(),
+ )),
+ );
+ const templateHref$ = collection$.pipe(
+ switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)),
+ );
+
+ combineLatest([collection$, template$, templateHref$]).subscribe(([, , templateHref]) => {
+ this.requestService.setStaleByHrefSubstring(templateHref);
+ this.editing = true;
+ this.initTemplateItem();
+ });
+ }
+
+ /**
+ * Start editing the collection's item template
+ */
+ editItemTemplate() {
+ this.editing = true;
+ }
+
+ /**
+ * Stop editing the collection's item template
+ */
+ cancelEdit() {
+ this.editing = false;
+ }
+
+ /**
+ * Delete the item template from the collection
+ */
+ deleteItemTemplate() {
+ this.dsoRD$.pipe(
+ getFirstSucceededRemoteDataPayload(),
+ switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
+ getFirstSucceededRemoteDataPayload(),
+ switchMap((template) => {
+ return this.itemTemplateService.delete(template.uuid);
+ }),
+ getFirstCompletedRemoteData(),
+ map((response: RemoteData) => hasValue(response) && response.hasSucceeded),
+ ).subscribe((success: boolean) => {
+ if (success) {
+ this.notificationsService.success(null, this.translate.get('collection.edit.template.notifications.delete.success'));
+ } else {
+ this.notificationsService.error(null, this.translate.get('collection.edit.template.notifications.delete.error'));
+ }
+ this.editing = false;
+ this.initTemplateItem();
+ });
+ }
+}
diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts b/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts
index 49c0a32e8b7..b05db03ac6a 100644
--- a/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts
+++ b/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts
@@ -13,6 +13,7 @@ import { CollectionCurateComponent } from './collection-curate/collection-curate
import { CollectionMetadataComponent } from './collection-metadata/collection-metadata.component';
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
import { CollectionSourceComponent } from './collection-source/collection-source.component';
+import { CollectionTemplateItemComponent } from './collection-template-item/collection-template-item.component';
import { EditCollectionPageComponent } from './edit-collection-page.component';
/**
@@ -95,6 +96,11 @@ export const ROUTES: Route[] = [
component: CollectionItemMapperComponent,
data: { title: 'collection.edit.tabs.item-mapper.title', hideReturnButton: true, showBreadcrumbs: true },
},
+ {
+ path: 'template',
+ component: CollectionTemplateItemComponent,
+ data: { title: 'collection.edit.tabs.template.title', hideReturnButton: true, showBreadcrumbs: true },
+ },
],
},
];
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index 6551143ff13..0bc738b2c1b 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -1273,6 +1273,10 @@
"collection.edit.tabs.item-mapper.title": "Collection Edit - Item Mapper",
+ "collection.edit.tabs.template.head": "Template Item",
+
+ "collection.edit.tabs.template.title": "Collection Edit - Template Item",
+
"collection.edit.item-mapper.cancel": "Cancel",
"collection.edit.item-mapper.collection": "Collection: \"{{name}}\"",