From 9e2a682a516d4804ed545a32c0bfba12d0e6e094 Mon Sep 17 00:00:00 2001 From: nikunj59 Date: Fri, 24 Jun 2022 19:26:30 +0530 Subject: [PATCH 01/65] CST-6153 changes for current password field introduce --- ...ofile-page-security-form.component.spec.ts | 13 +++++++++++ .../profile-page-security-form.component.ts | 21 +++++++++++++----- .../profile-page/profile-page.component.html | 1 + .../profile-page.component.spec.ts | 9 ++++++-- .../profile-page/profile-page.component.ts | 22 ++++++++++++++----- src/assets/i18n/en.json5 | 2 ++ 6 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts index 213a83b86e..ba758b1203 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts @@ -74,6 +74,19 @@ describe('ProfilePageSecurityFormComponent', () => { expect(component.passwordValue.emit).toHaveBeenCalledWith('new-password'); })); + + it('should emit the value on password change with current password for profile-page', fakeAsync(() => { + spyOn(component.passwordValue, 'emit'); + spyOn(component.currentPasswordValue, 'emit'); + component.FORM_PREFIX = 'profile.security.form.'; + component.ngOnInit(); + component.formGroup.patchValue({password: 'new-password'}); + component.formGroup.patchValue({'current-password': 'current-password'}); + tick(300); + + expect(component.passwordValue.emit).toHaveBeenCalledWith('new-password'); + expect(component.currentPasswordValue.emit).toHaveBeenCalledWith('current-password'); + })) }); }); }); diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts index 4f310204e3..ab39ca1929 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.ts @@ -27,6 +27,10 @@ export class ProfilePageSecurityFormComponent implements OnInit { * Emits the value of the password */ @Output() passwordValue = new EventEmitter(); + /** + * Emits the value of the current-password + */ + @Output() currentPasswordValue = new EventEmitter(); /** * The form's input models @@ -69,6 +73,14 @@ export class ProfilePageSecurityFormComponent implements OnInit { } ngOnInit(): void { + if (this.FORM_PREFIX === 'profile.security.form.') { + this.formModel.unshift(new DynamicInputModel({ + id: 'current-password', + name: 'current-password', + inputType: 'password', + required: true + })); + } if (this.passwordCanBeEmpty) { this.formGroup = this.formService.createFormGroup(this.formModel, {validators: [this.checkPasswordsEqual, this.checkPasswordLength]}); @@ -85,11 +97,7 @@ export class ProfilePageSecurityFormComponent implements OnInit { this.subs.push(this.formGroup.statusChanges.pipe( debounceTime(300), map((status: string) => { - if (status !== 'VALID') { - return true; - } else { - return false; - } + return status !== 'VALID'; })).subscribe((status) => this.isInvalid.emit(status)) ); @@ -97,6 +105,9 @@ export class ProfilePageSecurityFormComponent implements OnInit { debounceTime(300), ).subscribe((valueChange) => { this.passwordValue.emit(valueChange.password); + if (this.FORM_PREFIX === 'profile.security.form.') { + this.currentPasswordValue.emit(valueChange['current-password']); + } })); } diff --git a/src/app/profile-page/profile-page.component.html b/src/app/profile-page/profile-page.component.html index 6e22f73a75..b6f53b08bf 100644 --- a/src/app/profile-page/profile-page.component.html +++ b/src/app/profile-page/profile-page.component.html @@ -24,6 +24,7 @@ [FORM_PREFIX]="'profile.security.form.'" (isInvalid)="setInvalid($event)" (passwordValue)="setPasswordValue($event)" + (currentPasswordValue)="setCurrentPasswordValue($event)" > diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 6893ac2437..71537e9c32 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -180,7 +180,7 @@ describe('ProfilePageComponent', () => { beforeEach(() => { component.setPasswordValue(''); - + component.setCurrentPasswordValue('current-password'); result = component.updateSecurity(); }); @@ -199,6 +199,7 @@ describe('ProfilePageComponent', () => { beforeEach(() => { component.setPasswordValue('test'); component.setInvalid(true); + component.setCurrentPasswordValue('current-password'); result = component.updateSecurity(); }); @@ -215,8 +216,12 @@ describe('ProfilePageComponent', () => { beforeEach(() => { component.setPasswordValue('testest'); component.setInvalid(false); + component.setCurrentPasswordValue('current-password'); - operations = [{ op: 'add', path: '/password', value: 'testest' }]; + operations = [ + { op: 'add', path: '/password', value: 'testest' }, + { op: 'add', path: '/challenge', value: 'current-password' } + ]; result = component.updateSecurity(); }); diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 5629a1ae18..15811ee77f 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -67,6 +67,10 @@ export class ProfilePageComponent implements OnInit { * The password filled in, in the security form */ private password: string; + /** + * The current-password filled in, in the security form + */ + private currentPassword: string; /** * The authenticated user @@ -138,15 +142,15 @@ export class ProfilePageComponent implements OnInit { */ updateSecurity() { const passEntered = isNotEmpty(this.password); - if (this.invalidSecurity) { this.notificationsService.error(this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.general')); } if (!this.invalidSecurity && passEntered) { - const operation = {op: 'add', path: '/password', value: this.password} as Operation; - this.epersonService.patch(this.currentUser, [operation]).pipe( - getFirstCompletedRemoteData() - ).subscribe((response: RemoteData) => { + const operations = [ + { op: 'add', path: '/password', value: this.password }, + { op: 'add', path: '/challenge', value: this.currentPassword } + ] as Operation[]; + this.epersonService.patch(this.currentUser, operations).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationsService.success( this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'success.title'), @@ -170,6 +174,14 @@ export class ProfilePageComponent implements OnInit { this.password = $event; } + /** + * Set the current-password value based on the value emitted from the security form + * @param $event + */ + setCurrentPasswordValue($event: string) { + this.currentPassword = $event; + } + /** * Submit of the security form that triggers the updateProfile method */ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 3d5f15b4f2..860db5aac6 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3046,6 +3046,8 @@ "profile.security.form.label.passwordrepeat": "Retype to confirm", + "profile.security.form.label.current-password": "Current password", + "profile.security.form.notifications.success.content": "Your changes to the password were saved.", "profile.security.form.notifications.success.title": "Password saved", From 36560e0e6500d646c2ec1608f7b6a23800c22ce6 Mon Sep 17 00:00:00 2001 From: nikunj59 Date: Mon, 27 Jun 2022 20:24:46 +0530 Subject: [PATCH 02/65] CST-6153 added test case --- .../profile-page.component.spec.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 71537e9c32..31332a5fd7 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -233,6 +233,29 @@ describe('ProfilePageComponent', () => { expect(epersonService.patch).toHaveBeenCalledWith(user, operations); }); }); + + describe('when password is filled in, and is valid but return 403', () => { + let result; + let operations; + + it('should return call epersonService.patch', (done) => { + epersonService.patch.and.returnValue(observableOf(Object.assign(new RestResponse(false, 403, 'Error')))); + component.setPasswordValue('testest'); + component.setInvalid(false); + component.setCurrentPasswordValue('current-password'); + operations = [ + { op: 'add', path: '/password', value: 'testest' }, + { op: 'add', path: '/challenge', value: 'current-password' } + ]; + result = component.updateSecurity(); + epersonService.patch(user, operations).subscribe((response) => { + expect(response.statusCode).toEqual(403); + done(); + }); + expect(epersonService.patch).toHaveBeenCalledWith(user, operations); + expect(result).toEqual(true); + }); + }); }); describe('canChangePassword$', () => { From 10bbb01a442d03725b5825b9372a834ba809bd25 Mon Sep 17 00:00:00 2001 From: nikunj59 Date: Thu, 30 Jun 2022 14:24:51 +0530 Subject: [PATCH 03/65] CST-6153 added error msg for required field of security form --- .../profile-page-security-form.component.spec.ts | 2 +- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts index ba758b1203..88f50e3dea 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.spec.ts @@ -86,7 +86,7 @@ describe('ProfilePageSecurityFormComponent', () => { expect(component.passwordValue.emit).toHaveBeenCalledWith('new-password'); expect(component.currentPasswordValue.emit).toHaveBeenCalledWith('current-password'); - })) + })); }); }); }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 1abcd551a6..4a6fb33e25 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3060,6 +3060,8 @@ "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.", + "profile.security.form.notifications.error.general": "Please fill required fields of security form.", + "profile.title": "Update Profile", "profile.card.researcher": "Researcher Profile", From 2d7b5768bf438cc4598599453dedfd8cca779e5e Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Tue, 9 Aug 2022 13:25:03 +0530 Subject: [PATCH 04/65] CST-6153 changes for challange sign update --- src/app/profile-page/profile-page.component.spec.ts | 6 ++---- src/app/profile-page/profile-page.component.ts | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 31332a5fd7..66351144d5 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -219,8 +219,7 @@ describe('ProfilePageComponent', () => { component.setCurrentPasswordValue('current-password'); operations = [ - { op: 'add', path: '/password', value: 'testest' }, - { op: 'add', path: '/challenge', value: 'current-password' } + { "op": "add", "path": "/password", "value": { "password": "testest", "challenge": "current-password" } } ]; result = component.updateSecurity(); }); @@ -244,8 +243,7 @@ describe('ProfilePageComponent', () => { component.setInvalid(false); component.setCurrentPasswordValue('current-password'); operations = [ - { op: 'add', path: '/password', value: 'testest' }, - { op: 'add', path: '/challenge', value: 'current-password' } + { "op": "add", "path": "/password", "value": {"password": "testest", "challenge": "current-password" }} ]; result = component.updateSecurity(); epersonService.patch(user, operations).subscribe((response) => { diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 15811ee77f..c171502d33 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -147,8 +147,7 @@ export class ProfilePageComponent implements OnInit { } if (!this.invalidSecurity && passEntered) { const operations = [ - { op: 'add', path: '/password', value: this.password }, - { op: 'add', path: '/challenge', value: this.currentPassword } + { "op": "add", "path": "/password", "value": { "password": this.password, "challenge": this.currentPassword } } ] as Operation[]; this.epersonService.patch(this.currentUser, operations).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData) => { if (response.hasSucceeded) { From fa20e9e5c0d5ef5da5b5ce5f439817539939f3b5 Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Fri, 26 Aug 2022 13:34:27 -0400 Subject: [PATCH 05/65] Adding missing language parameters --- src/assets/i18n/fr.json5 | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 5cfb7ff099..ff778eb9f8 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -1215,6 +1215,17 @@ // "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", "collection.edit.tabs.authorizations.title": "Édition de collection - Autorisations", + //"collection.edit.item.authorizations.load-bundle-button": "Load more bundles", + "collection.edit.item.authorizations.load-bundle-button": "Charger plus de bundles", + + //"collection.edit.item.authorizations.load-more-button": "Load more", + "collection.edit.item.authorizations.load-more-button": "Charger plus", + + //"collection.edit.item.authorizations.show-bitstreams-button": "Show bitstream policies for bundle", + "collection.edit.item.authorizations.show-bitstreams-button": "Afficher les politiques de bitstream policies pour le bundle", + + + // "collection.edit.tabs.metadata.head": "Edit Metadata", "collection.edit.tabs.metadata.head": "Éditer les Métadonnées", @@ -2909,6 +2920,13 @@ // "item.search.title": "Item Search", "item.search.title": "Recherche d'Items", + + // "item.truncatable-part.show-more": "Show more", + "item.truncatable-part.show-more": "Voir plus", + + //"item.truncatable-part.show-less": "Collapse", + "item.truncatable-part.show-less": "Réduire", + // "item.page.abstract": "Abstract", "item.page.abstract": "Résumé", @@ -3572,6 +3590,9 @@ // "menu.section.processes": "Processes", "menu.section.processes": "Processus", + + // "menu.section.health": "Health", + "menu.section.health": "Santé du système", // "menu.section.registries": "Registries", "menu.section.registries": "Registres", @@ -4808,6 +4829,9 @@ // "default.search.results.head": "Search Results", "default.search.results.head": "Résultats de recherche", + + // "default-relationships.search.results.head": "Search Results", + "default-relationships.search.results.head": "Résultats de recherche", // "search.sidebar.close": "Back to results", "search.sidebar.close": "Retour aux résultats", From 66380857c9cd2ff45c694d1699b626be360e6fdd Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Tue, 6 Sep 2022 17:02:25 +0530 Subject: [PATCH 06/65] CST-6153 updated lint error --- src/app/profile-page/profile-page.component.spec.ts | 4 ++-- src/app/profile-page/profile-page.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 66351144d5..c1bfe8e6bb 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -219,7 +219,7 @@ describe('ProfilePageComponent', () => { component.setCurrentPasswordValue('current-password'); operations = [ - { "op": "add", "path": "/password", "value": { "password": "testest", "challenge": "current-password" } } + { 'op': 'add', 'path': '/password', 'value': { 'password': 'testest', 'challenge': 'current-password' } } ]; result = component.updateSecurity(); }); @@ -243,7 +243,7 @@ describe('ProfilePageComponent', () => { component.setInvalid(false); component.setCurrentPasswordValue('current-password'); operations = [ - { "op": "add", "path": "/password", "value": {"password": "testest", "challenge": "current-password" }} + { 'op': 'add', 'path': '/password', 'value': {'password': 'testest', 'challenge': 'current-password' }} ]; result = component.updateSecurity(); epersonService.patch(user, operations).subscribe((response) => { diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index c171502d33..5432a0303c 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -147,7 +147,7 @@ export class ProfilePageComponent implements OnInit { } if (!this.invalidSecurity && passEntered) { const operations = [ - { "op": "add", "path": "/password", "value": { "password": this.password, "challenge": this.currentPassword } } + { 'op': 'add', 'path': '/password', 'value': { 'password': this.password, 'challenge': this.currentPassword } } ] as Operation[]; this.epersonService.patch(this.currentUser, operations).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData) => { if (response.hasSucceeded) { From d40f163c4916c4fdad7c859980ef195f6976bc97 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Thu, 8 Sep 2022 13:25:41 +0200 Subject: [PATCH 07/65] 94273: Implement dependencies between requests When an object is invalidated, its dependent requests are invalidated as well --- src/app/core/cache/object-cache.actions.ts | 48 ++++++- .../core/cache/object-cache.reducer.spec.ts | 28 +++- src/app/core/cache/object-cache.reducer.ts | 66 ++++++++- .../core/cache/object-cache.service.spec.ts | 133 +++++++++++++++++- src/app/core/cache/object-cache.service.ts | 107 ++++++++++++-- src/app/core/data/data.service.spec.ts | 58 +++++++- src/app/core/data/data.service.ts | 45 +++++- .../core/eperson/group-data.service.spec.ts | 29 ++-- .../shared/mocks/object-cache.service.mock.ts | 2 + 9 files changed, 468 insertions(+), 48 deletions(-) diff --git a/src/app/core/cache/object-cache.actions.ts b/src/app/core/cache/object-cache.actions.ts index 88b4730b3f..8c83f6104e 100644 --- a/src/app/core/cache/object-cache.actions.ts +++ b/src/app/core/cache/object-cache.actions.ts @@ -13,7 +13,9 @@ export const ObjectCacheActionTypes = { REMOVE: type('dspace/core/cache/object/REMOVE'), RESET_TIMESTAMPS: type('dspace/core/cache/object/RESET_TIMESTAMPS'), ADD_PATCH: type('dspace/core/cache/object/ADD_PATCH'), - APPLY_PATCH: type('dspace/core/cache/object/APPLY_PATCH') + APPLY_PATCH: type('dspace/core/cache/object/APPLY_PATCH'), + ADD_DEPENDENTS: type('dspace/core/cache/object/ADD_DEPENDENTS'), + REMOVE_DEPENDENTS: type('dspace/core/cache/object/REMOVE_DEPENDENTS') }; /** @@ -126,13 +128,49 @@ export class ApplyPatchObjectCacheAction implements Action { } } +export class AddDependentsObjectCacheAction implements Action { + type = ObjectCacheActionTypes.ADD_DEPENDENTS; + payload: { + href: string; + dependentRequestUUIDs: string[]; + }; + + /** + * Create a new AddDependencyObjectCacheAction + * + * @param href the self link of a cached object + * @param dependentRequestUUIDs the UUID of the request that depends on this object + */ + constructor(href: string, dependentRequestUUIDs: string[]) { + this.payload = { + href, + dependentRequestUUIDs, + }; + } +} + +export class RemoveDependentsObjectCacheAction implements Action { + type = ObjectCacheActionTypes.REMOVE_DEPENDENTS; + payload: string; + + /** + * Create a new AddDependencyObjectCacheAction + * + * @param href the self link of a cached object for which to remove all dependent request UUIDs + */ + constructor(href: string) { + this.payload = href; + } +} /** * A type to encompass all ObjectCacheActions */ export type ObjectCacheAction = AddToObjectCacheAction - | RemoveFromObjectCacheAction - | ResetObjectCacheTimestampsAction - | AddPatchObjectCacheAction - | ApplyPatchObjectCacheAction; + | RemoveFromObjectCacheAction + | ResetObjectCacheTimestampsAction + | AddPatchObjectCacheAction + | ApplyPatchObjectCacheAction + | AddDependentsObjectCacheAction + | RemoveDependentsObjectCacheAction; diff --git a/src/app/core/cache/object-cache.reducer.spec.ts b/src/app/core/cache/object-cache.reducer.spec.ts index 82e2da58b1..e346a00b61 100644 --- a/src/app/core/cache/object-cache.reducer.spec.ts +++ b/src/app/core/cache/object-cache.reducer.spec.ts @@ -2,11 +2,13 @@ import * as deepFreeze from 'deep-freeze'; import { Operation } from 'fast-json-patch'; import { Item } from '../shared/item.model'; import { + AddDependentsObjectCacheAction, AddPatchObjectCacheAction, AddToObjectCacheAction, ApplyPatchObjectCacheAction, + RemoveDependentsObjectCacheAction, RemoveFromObjectCacheAction, - ResetObjectCacheTimestampsAction + ResetObjectCacheTimestampsAction, } from './object-cache.actions'; import { objectCacheReducer } from './object-cache.reducer'; @@ -42,20 +44,22 @@ describe('objectCacheReducer', () => { timeCompleted: new Date().getTime(), msToLive: 900000, requestUUIDs: [requestUUID1], + dependentRequestUUIDs: [], patches: [], isDirty: false, }, [selfLink2]: { data: { type: Item.type, - self: requestUUID2, + self: selfLink2, foo: 'baz', - _links: { self: { href: requestUUID2 } } + _links: { self: { href: selfLink2 } } }, alternativeLinks: [altLink3, altLink4], timeCompleted: new Date().getTime(), msToLive: 900000, - requestUUIDs: [selfLink2], + requestUUIDs: [requestUUID2], + dependentRequestUUIDs: [requestUUID1], patches: [], isDirty: false } @@ -189,4 +193,20 @@ describe('objectCacheReducer', () => { expect((newState[selfLink1].data as any).name).toEqual(newName); }); + it('should add dependent requests on ADD_DEPENDENTS', () => { + let newState = objectCacheReducer(testState, new AddDependentsObjectCacheAction(selfLink1, ['new', 'newer', 'newest'])); + expect(newState[selfLink1].dependentRequestUUIDs).toEqual(['new', 'newer', 'newest']); + + newState = objectCacheReducer(newState, new AddDependentsObjectCacheAction(selfLink2, ['more'])); + expect(newState[selfLink2].dependentRequestUUIDs).toEqual([requestUUID1, 'more']); + }); + + it('should clear dependent requests on REMOVE_DEPENDENTS', () => { + let newState = objectCacheReducer(testState, new RemoveDependentsObjectCacheAction(selfLink1)); + expect(newState[selfLink1].dependentRequestUUIDs).toEqual([]); + + newState = objectCacheReducer(newState, new RemoveDependentsObjectCacheAction(selfLink2)); + expect(newState[selfLink2].dependentRequestUUIDs).toEqual([]); + }); + }); diff --git a/src/app/core/cache/object-cache.reducer.ts b/src/app/core/cache/object-cache.reducer.ts index 1a42408f72..dc3f50db68 100644 --- a/src/app/core/cache/object-cache.reducer.ts +++ b/src/app/core/cache/object-cache.reducer.ts @@ -1,12 +1,13 @@ /* eslint-disable max-classes-per-file */ import { + AddDependentsObjectCacheAction, AddPatchObjectCacheAction, AddToObjectCacheAction, ApplyPatchObjectCacheAction, ObjectCacheAction, - ObjectCacheActionTypes, + ObjectCacheActionTypes, RemoveDependentsObjectCacheAction, RemoveFromObjectCacheAction, - ResetObjectCacheTimestampsAction + ResetObjectCacheTimestampsAction, } from './object-cache.actions'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { CacheEntry } from './cache-entry'; @@ -69,6 +70,12 @@ export class ObjectCacheEntry implements CacheEntry { */ requestUUIDs: string[]; + /** + * A list of UUIDs for the requests that depend on this object. + * When this object is invalidated, these requests will be invalidated as well. + */ + dependentRequestUUIDs: string[]; + /** * An array of patches that were made on the client side to this entry, but haven't been sent to the server yet */ @@ -134,6 +141,14 @@ export function objectCacheReducer(state = initialState, action: ObjectCacheActi return applyPatchObjectCache(state, action as ApplyPatchObjectCacheAction); } + case ObjectCacheActionTypes.ADD_DEPENDENTS: { + return addDependentsObjectCacheState(state, action as AddDependentsObjectCacheAction); + } + + case ObjectCacheActionTypes.REMOVE_DEPENDENTS: { + return removeDependentsObjectCacheState(state, action as RemoveDependentsObjectCacheAction); + } + default: { return state; } @@ -159,6 +174,7 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio timeCompleted: action.payload.timeCompleted, msToLive: action.payload.msToLive, requestUUIDs: [action.payload.requestUUID, ...(existing.requestUUIDs || [])], + dependentRequestUUIDs: existing.dependentRequestUUIDs || [], isDirty: isNotEmpty(existing.patches), patches: existing.patches || [], alternativeLinks: [...(existing.alternativeLinks || []), ...newAltLinks] @@ -252,3 +268,49 @@ function applyPatchObjectCache(state: ObjectCacheState, action: ApplyPatchObject } return newState; } + +/** + * Add a list of dependent request UUIDs to a cached object, used when defining new dependencies + * + * @param state the current state + * @param action an AddDependentsObjectCacheAction + * @return the new state, with the dependent requests of the cached object updated + */ +function addDependentsObjectCacheState(state: ObjectCacheState, action: AddDependentsObjectCacheAction): ObjectCacheState { + const href = action.payload.href; + const newState = Object.assign({}, state); + + if (hasValue(newState[href])) { + newState[href] = Object.assign({}, newState[href], { + dependentRequestUUIDs: [ + ...new Set([ + ...newState[href]?.dependentRequestUUIDs || [], + ...action.payload.dependentRequestUUIDs, + ]) + ] + }); + } + + return newState; +} + + +/** + * Remove all dependent request UUIDs from a cached object, used to clear out-of-date depedencies + * + * @param state the current state + * @param action an AddDependentsObjectCacheAction + * @return the new state, with the dependent requests of the cached object updated + */ +function removeDependentsObjectCacheState(state: ObjectCacheState, action: RemoveDependentsObjectCacheAction): ObjectCacheState { + const href = action.payload; + const newState = Object.assign({}, state); + + if (hasValue(newState[href])) { + newState[href] = Object.assign({}, newState[href], { + dependentRequestUUIDs: [] + }); + } + + return newState; +} diff --git a/src/app/core/cache/object-cache.service.spec.ts b/src/app/core/cache/object-cache.service.spec.ts index f18c262524..6af797be29 100644 --- a/src/app/core/cache/object-cache.service.spec.ts +++ b/src/app/core/cache/object-cache.service.spec.ts @@ -11,10 +11,12 @@ import { coreReducers} from '../core.reducers'; import { RestRequestMethod } from '../data/rest-request-method'; import { Item } from '../shared/item.model'; import { + AddDependentsObjectCacheAction, + RemoveDependentsObjectCacheAction, AddPatchObjectCacheAction, AddToObjectCacheAction, ApplyPatchObjectCacheAction, - RemoveFromObjectCacheAction + RemoveFromObjectCacheAction, } from './object-cache.actions'; import { Patch } from './object-cache.reducer'; import { ObjectCacheService } from './object-cache.service'; @@ -25,6 +27,7 @@ import { storeModuleConfig } from '../../app.reducer'; import { TestColdObservable } from 'jasmine-marbles/src/test-observables'; import { IndexName } from '../index/index-name.model'; import { CoreState } from '../core-state.model'; +import { TestScheduler } from 'rxjs/testing'; describe('ObjectCacheService', () => { let service: ObjectCacheService; @@ -38,6 +41,7 @@ describe('ObjectCacheService', () => { let altLink1; let altLink2; let requestUUID; + let requestUUID2; let alternativeLink; let timestamp; let timestamp2; @@ -55,6 +59,7 @@ describe('ObjectCacheService', () => { altLink1 = 'https://alternative.link/endpoint/1234'; altLink2 = 'https://alternative.link/endpoint/5678'; requestUUID = '4d3a4ce8-a375-4b98-859b-39f0a014d736'; + requestUUID2 = 'c0f486c1-c4d3-4a03-b293-ca5b71ff0054'; alternativeLink = 'https://rest.api/endpoint/5e4f8a5-be98-4c51-9fd8-6bfedcbd59b7/item'; timestamp = new Date().getTime(); timestamp2 = new Date().getTime() - 200; @@ -71,13 +76,17 @@ describe('ObjectCacheService', () => { data: objectToCache, timeCompleted: timestamp, msToLive: msToLive, - alternativeLinks: [altLink1, altLink2] + alternativeLinks: [altLink1, altLink2], + requestUUIDs: [requestUUID], + dependentRequestUUIDs: [], }; cacheEntry2 = { data: objectToCache, timeCompleted: timestamp2, msToLive: msToLive2, - alternativeLinks: [altLink2] + alternativeLinks: [altLink2], + requestUUIDs: [requestUUID2], + dependentRequestUUIDs: [], }; invalidCacheEntry = Object.assign({}, cacheEntry, { msToLive: -1 }); operations = [{ op: 'replace', path: '/name', value: 'random string' } as Operation]; @@ -343,4 +352,122 @@ describe('ObjectCacheService', () => { expect(store.dispatch).toHaveBeenCalledWith(new ApplyPatchObjectCacheAction(selfLink)); }); }); + + describe('request dependencies', () => { + beforeEach(() => { + const state = Object.assign({}, initialState, { + core: Object.assign({}, initialState.core, { + 'cache/object': { + ['objectWithoutDependents']: { + dependentRequestUUIDs: [], + }, + ['objectWithDependents']: { + dependentRequestUUIDs: [requestUUID], + }, + [selfLink]: cacheEntry, + }, + 'index': { + 'object/alt-link-to-self-link': { + [anotherLink]: selfLink, + ['objectWithoutDependentsAlt']: 'objectWithoutDependents', + ['objectWithDependentsAlt']: 'objectWithDependents', + } + } + }) + }); + mockStore.setState(state); + }); + + describe('addDependency', () => { + it('should dispatch an ADD_DEPENDENTS action', () => { + service.addDependency(selfLink, 'objectWithoutDependents'); + expect(store.dispatch).toHaveBeenCalledOnceWith(new AddDependentsObjectCacheAction('objectWithoutDependents', [requestUUID])); + }); + + it('should resolve alt links', () => { + service.addDependency(anotherLink, 'objectWithoutDependentsAlt'); + expect(store.dispatch).toHaveBeenCalledOnceWith(new AddDependentsObjectCacheAction('objectWithoutDependents', [requestUUID])); + }); + + it('should not dispatch if either href cannot be resolved to a cached self link', () => { + service.addDependency(selfLink, 'unknown'); + service.addDependency('unknown', 'objectWithoutDependents'); + service.addDependency('nothing', 'matches'); + expect(store.dispatch).not.toHaveBeenCalled(); + }); + + it('should not dispatch if either href is undefined', () => { + service.addDependency(selfLink, undefined); + service.addDependency(undefined, 'objectWithoutDependents'); + service.addDependency(undefined, undefined); + expect(store.dispatch).not.toHaveBeenCalled(); + }); + + it('should not dispatch if the dependency exists already', () => { + service.addDependency(selfLink, 'objectWithDependents'); + expect(store.dispatch).not.toHaveBeenCalled(); + }); + + it('should work with observable hrefs', () => { + service.addDependency(observableOf(selfLink), observableOf('objectWithoutDependents')); + expect(store.dispatch).toHaveBeenCalledOnceWith(new AddDependentsObjectCacheAction('objectWithoutDependents', [requestUUID])); + }); + + it('should only dispatch once for the first value of either observable href', () => { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + + testScheduler.run(({ cold: tsCold, flush }) => { + const href$ = tsCold('--y-n-n', { + y: selfLink, + n: 'NOPE' + }); + const dependsOnHref$ = tsCold('-y-n-n', { + y: 'objectWithoutDependents', + n: 'NOPE' + }); + + service.addDependency(href$, dependsOnHref$); + flush(); + + expect(store.dispatch).toHaveBeenCalledOnceWith(new AddDependentsObjectCacheAction('objectWithoutDependents', [requestUUID])); + }); + }); + + it('should not dispatch if either of the hrefs emits undefined', () => { + const testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + + testScheduler.run(({ cold: tsCold, flush }) => { + const undefined$ = tsCold('--u'); + + service.addDependency(selfLink, undefined$); + service.addDependency(undefined$, 'objectWithoutDependents'); + service.addDependency(undefined$, undefined$); + flush(); + + expect(store.dispatch).not.toHaveBeenCalled(); + }); + }); + }); + + describe('removeDependents', () => { + it('should dispatch a REMOVE_DEPENDENTS action', () => { + service.removeDependents('objectWithDependents'); + expect(store.dispatch).toHaveBeenCalledOnceWith(new RemoveDependentsObjectCacheAction('objectWithDependents')); + }); + + it('should resolve alt links', () => { + service.removeDependents('objectWithDependentsAlt'); + expect(store.dispatch).toHaveBeenCalledOnceWith(new RemoveDependentsObjectCacheAction('objectWithDependents')); + }); + + it('should not dispatch if the href cannot be resolved to a cached self link', () => { + service.removeDependents('unknown'); + expect(store.dispatch).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index cdf87e5c1a..9ca0216210 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -4,23 +4,15 @@ import { applyPatch, Operation } from 'fast-json-patch'; import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, switchMap, take } from 'rxjs/operators'; -import { hasValue, isNotEmpty, isEmpty } from '../../shared/empty.util'; +import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { CoreState } from '../core-state.model'; import { coreSelector } from '../core.selectors'; import { RestRequestMethod } from '../data/rest-request-method'; -import { - selfLinkFromAlternativeLinkSelector, - selfLinkFromUuidSelector -} from '../index/index.selectors'; +import { selfLinkFromAlternativeLinkSelector, selfLinkFromUuidSelector } from '../index/index.selectors'; import { GenericConstructor } from '../shared/generic-constructor'; import { getClassForType } from './builders/build-decorators'; import { LinkService } from './builders/link.service'; -import { - AddPatchObjectCacheAction, - AddToObjectCacheAction, - ApplyPatchObjectCacheAction, - RemoveFromObjectCacheAction -} from './object-cache.actions'; +import { AddDependentsObjectCacheAction, AddPatchObjectCacheAction, AddToObjectCacheAction, ApplyPatchObjectCacheAction, RemoveDependentsObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions'; import { ObjectCacheEntry, ObjectCacheState } from './object-cache.reducer'; import { AddToSSBAction } from './server-sync-buffer.actions'; @@ -339,4 +331,97 @@ export class ObjectCacheService { this.store.dispatch(new ApplyPatchObjectCacheAction(selfLink)); } + /** + * Add a new dependency between two cached objects. + * When {@link dependsOnHref$} is invalidated, {@link href$} will be invalidated as well. + * + * This method should be called _after_ requests have been sent; + * it will only work if both objects are already present in the cache. + * + * If either object is undefined, the dependency will not be added + * + * @param href$ the href of an object to add a dependency to + * @param dependsOnHref$ the href of the new dependency + */ + addDependency(href$: string | Observable, dependsOnHref$: string | Observable) { + if (hasNoValue(href$) || hasNoValue(dependsOnHref$)) { + return; + } + + if (typeof href$ === 'string') { + href$ = observableOf(href$); + } + if (typeof dependsOnHref$ === 'string') { + dependsOnHref$ = observableOf(dependsOnHref$); + } + + observableCombineLatest([ + href$, + dependsOnHref$.pipe( + switchMap(dependsOnHref => this.resolveSelfLink(dependsOnHref)) + ), + ]).pipe( + switchMap(([href, dependsOnSelfLink]: [string, string]) => { + const dependsOnSelfLink$ = observableOf(dependsOnSelfLink); + + return observableCombineLatest([ + dependsOnSelfLink$, + dependsOnSelfLink$.pipe( + switchMap(selfLink => this.getBySelfLink(selfLink)), + map(oce => oce?.dependentRequestUUIDs || []), + ), + this.getByHref(href).pipe( + // only add the latest request to keep dependency index from growing indefinitely + map((entry: ObjectCacheEntry) => entry?.requestUUIDs?.[0]), + ) + ]); + }), + take(1), + ).subscribe(([dependsOnSelfLink, currentDependents, newDependent]: [string, string[], string]) => { + // don't dispatch if either href is invalid or if the new dependency already exists + if (hasValue(dependsOnSelfLink) && hasValue(newDependent) && !currentDependents.includes(newDependent)) { + this.store.dispatch(new AddDependentsObjectCacheAction(dependsOnSelfLink, [newDependent])); + } + }); + } + + /** + * Clear all dependent requests associated with a cache entry. + * + * @href the href of a cached object + */ + removeDependents(href: string) { + this.resolveSelfLink(href).pipe( + take(1), + ).subscribe((selfLink: string) => { + if (hasValue(selfLink)) { + this.store.dispatch(new RemoveDependentsObjectCacheAction(selfLink)); + } + }); + } + + + /** + * Resolve the self link of an existing cached object from an arbitrary href + * + * @param href any href + * @return an observable of the self link corresponding to the given href. + * Will emit the given href if it was a self link, another href + * if the given href was an alt link, or undefined if there is no + * cached object for this href. + */ + private resolveSelfLink(href: string): Observable { + return this.getBySelfLink(href).pipe( + switchMap((oce: ObjectCacheEntry) => { + if (isNotEmpty(oce)) { + return [href]; + } else { + return this.store.pipe( + select(selfLinkFromAlternativeLinkSelector(href)), + ); + } + }), + ); + } + } diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index b8c41dee9d..cdac1f2855 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; import { compare, Operation } from 'fast-json-patch'; -import { Observable, of as observableOf } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { followLink } from '../../shared/utils/follow-link-config.model'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -13,6 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Item } from '../shared/item.model'; import { createFailedRemoteDataObject, + createFailedRemoteDataObject$, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$, } from '../../shared/remote-data.utils'; @@ -96,7 +97,13 @@ describe('DataService', () => { }, getByHref: () => { /* empty */ - } + }, + addDependency: () => { + /* empty */ + }, + removeDependents: () => { + /* empty */ + }, } as any; store = {} as Store; selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; @@ -849,7 +856,8 @@ describe('DataService', () => { beforeEach(() => { getByHrefSpy = spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ - requestUUIDs: ['request1', 'request2', 'request3'] + requestUUIDs: ['request1', 'request2', 'request3'], + dependentRequestUUIDs: [] })); }); @@ -898,9 +906,9 @@ describe('DataService', () => { it('should only fire for the current state of the object (instead of tracking it)', () => { testScheduler.run(({ cold, flush }) => { getByHrefSpy.and.returnValue(cold('a---b---c---', { - a: { requestUUIDs: ['request1'] }, // this is the state at the moment we're invalidating the cache - b: { requestUUIDs: ['request2'] }, // we shouldn't keep tracking the state - c: { requestUUIDs: ['request3'] }, // because we may invalidate when we shouldn't + a: { requestUUIDs: ['request1'], dependentRequestUUIDs: [] }, // this is the state at the moment we're invalidating the cache + b: { requestUUIDs: ['request2'], dependentRequestUUIDs: [] }, // we shouldn't keep tracking the state + c: { requestUUIDs: ['request3'], dependentRequestUUIDs: [] }, // because we may invalidate when we shouldn't })); service.invalidateByHref('some-href'); @@ -970,4 +978,42 @@ describe('DataService', () => { }); }); }); + + describe('addDependency', () => { + let addDependencySpy; + + beforeEach(() => { + addDependencySpy = spyOn(objectCache, 'addDependency'); + }); + + it('should call objectCache.addDependency with the object\'s self link', () => { + addDependencySpy.and.callFake((href$: Observable, dependsOn$: Observable) => { + observableCombineLatest([href$, dependsOn$]).subscribe(([href, dependsOn]) => { + expect(href).toBe('object-href'); + expect(dependsOn).toBe('dependsOnHref'); + }); + }); + + (service as any).addDependency( + createSuccessfulRemoteDataObject$({ _links: { self: { href: 'object-href' } } }), + observableOf('dependsOnHref') + ); + expect(addDependencySpy).toHaveBeenCalled(); + }); + + it('should call objectCache.addDependency without an href if request failed', () => { + addDependencySpy.and.callFake((href$: Observable, dependsOn$: Observable) => { + observableCombineLatest([href$, dependsOn$]).subscribe(([href, dependsOn]) => { + expect(href).toBe(undefined); + expect(dependsOn).toBe('dependsOnHref'); + }); + }); + + (service as any).addDependency( + createFailedRemoteDataObject$('something went wrong'), + observableOf('dependsOnHref') + ); + expect(addDependencySpy).toHaveBeenCalled(); + }); + }); }); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index c17fccb198..6f260bae25 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -27,7 +27,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceSerializer } from '../dspace-rest/dspace.serializer'; import { DSpaceObject } from '../shared/dspace-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../shared/operators'; +import { getFirstSucceededRemoteData, getRemoteDataPayload, getFirstCompletedRemoteData } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; import { ChangeAnalyzer } from './change-analyzer'; import { PaginatedList } from './paginated-list.model'; @@ -576,6 +576,35 @@ export abstract class DataService implements UpdateDa return result$; } + /** + * Shorthand method to add a dependency to a cached object + * ``` + * const out$ = this.findByHref(...); // or another method that sends a request + * this.addDependency(out$, dependsOnHref); + * ``` + * When {@link dependsOnHref$} is invalidated, {@link object$} will be invalidated as well. + * + * + * @param object$ the cached object + * @param dependsOnHref$ the href of the object it should depend on + */ + protected addDependency(object$: Observable>>, dependsOnHref$: string | Observable) { + this.objectCache.addDependency( + object$.pipe( + getFirstCompletedRemoteData(), + switchMap((rd: RemoteData) => { + if (rd.hasSucceeded) { + return [rd.payload._links.self.href]; + } else { + // undefined href will be skipped in objectCache.addDependency + return [undefined]; + } + }), + ), + dependsOnHref$ + ); + } + /** * Invalidate an existing DSpaceObject by marking all requests it is included in as stale * @param objectId The id of the object to be invalidated @@ -597,11 +626,17 @@ export abstract class DataService implements UpdateDa this.objectCache.getByHref(href).pipe( take(1), - switchMap((oce: ObjectCacheEntry) => observableFrom(oce.requestUUIDs).pipe( - mergeMap((requestUUID: string) => this.requestService.setStaleByUUID(requestUUID)), - toArray(), - )), + switchMap((oce: ObjectCacheEntry) => { + return observableFrom([ + ...oce.requestUUIDs, + ...oce.dependentRequestUUIDs + ]).pipe( + mergeMap((requestUUID: string) => this.requestService.setStaleByUUID(requestUUID)), + toArray(), + ); + }), ).subscribe(() => { + this.objectCache.removeDependents(href); done$.next(true); done$.complete(); }); diff --git a/src/app/core/eperson/group-data.service.spec.ts b/src/app/core/eperson/group-data.service.spec.ts index c1b9e59d5b..8f5e92aaf6 100644 --- a/src/app/core/eperson/group-data.service.spec.ts +++ b/src/app/core/eperson/group-data.service.spec.ts @@ -27,11 +27,10 @@ import { createPaginatedList, createRequestEntry$ } from '../../shared/testing/u import { CoreState } from '../core-state.model'; import { FindListOptions } from '../data/find-list-options.model'; import { DataService } from '../data/data.service'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { getMockLinkService } from '../../shared/mocks/link-service.mock'; import { DataServiceStub } from '../../shared/testing/data-service.stub'; import { of as observableOf } from 'rxjs'; import { ObjectCacheEntry } from '../cache/object-cache.reducer'; +import { getMockObjectCacheService } from '../../shared/mocks/object-cache.service.mock'; describe('GroupDataService', () => { let service: GroupDataService; @@ -44,7 +43,7 @@ describe('GroupDataService', () => { let groups$; let halService; let rdbService; - let objectCache: ObjectCacheService; + let objectCache; let dataService: DataServiceStub; function init() { @@ -54,7 +53,9 @@ describe('GroupDataService', () => { groups$ = createSuccessfulRemoteDataObject$(createPaginatedList(groups)); rdbService = getMockRemoteDataBuildServiceHrefMap(undefined, { 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups': groups$ }); halService = new HALEndpointServiceStub(restEndpointURL); - objectCache = new ObjectCacheService(store, getMockLinkService()); + + objectCache = getMockObjectCacheService(); + dataService = new DataServiceStub(); TestBed.configureTestingModule({ imports: [ @@ -122,8 +123,9 @@ describe('GroupDataService', () => { describe('addSubGroupToGroup', () => { beforeEach(() => { - spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ - requestUUIDs: ['request1', 'request2'] + objectCache.getByHref.and.returnValue(observableOf({ + requestUUIDs: ['request1', 'request2'], + dependentRequestUUIDs: [], } as ObjectCacheEntry)); spyOn(dataService, 'invalidateByHref'); service.addSubGroupToGroup(GroupMock, GroupMock2).subscribe(); @@ -151,8 +153,9 @@ describe('GroupDataService', () => { describe('deleteSubGroupFromGroup', () => { beforeEach(() => { - spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ - requestUUIDs: ['request1', 'request2'] + objectCache.getByHref.and.returnValue(observableOf({ + requestUUIDs: ['request1', 'request2'], + dependentRequestUUIDs: [], } as ObjectCacheEntry)); spyOn(dataService, 'invalidateByHref'); service.deleteSubGroupFromGroup(GroupMock, GroupMock2).subscribe(); @@ -176,8 +179,9 @@ describe('GroupDataService', () => { describe('addMemberToGroup', () => { beforeEach(() => { - spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ - requestUUIDs: ['request1', 'request2'] + objectCache.getByHref.and.returnValue(observableOf({ + requestUUIDs: ['request1', 'request2'], + dependentRequestUUIDs: [], } as ObjectCacheEntry)); spyOn(dataService, 'invalidateByHref'); service.addMemberToGroup(GroupMock, EPersonMock2).subscribe(); @@ -206,8 +210,9 @@ describe('GroupDataService', () => { describe('deleteMemberFromGroup', () => { beforeEach(() => { - spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ - requestUUIDs: ['request1', 'request2'] + objectCache.getByHref.and.returnValue(observableOf({ + requestUUIDs: ['request1', 'request2'], + dependentRequestUUIDs: [], } as ObjectCacheEntry)); spyOn(dataService, 'invalidateByHref'); service.deleteMemberFromGroup(GroupMock, EPersonMock).subscribe(); diff --git a/src/app/shared/mocks/object-cache.service.mock.ts b/src/app/shared/mocks/object-cache.service.mock.ts index 515c2abe86..8cceaefc3d 100644 --- a/src/app/shared/mocks/object-cache.service.mock.ts +++ b/src/app/shared/mocks/object-cache.service.mock.ts @@ -13,6 +13,8 @@ export function getMockObjectCacheService(): ObjectCacheService { 'hasByUUID', 'hasByHref', 'getRequestUUIDBySelfLink', + 'addDependency', + 'removeDependents', ]); } From 7c13db2f89701039a3aa2d699268389a91062155 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Thu, 8 Sep 2022 13:26:01 +0200 Subject: [PATCH 08/65] 94273: Invalidate object on successful patch --- src/app/core/data/data.service.spec.ts | 24 ++++++++++++++++++++---- src/app/core/data/data.service.ts | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index cdac1f2855..77d5771b91 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -351,7 +351,12 @@ describe('DataService', () => { describe('patch', () => { const dso = { - uuid: 'dso-uuid' + uuid: 'dso-uuid', + _links: { + self: { + href: 'dso-href', + } + } }; const operations = [ Object.assign({ @@ -361,12 +366,23 @@ describe('DataService', () => { }) as Operation ]; - beforeEach(() => { + it('should send a PatchRequest', () => { service.patch(dso, operations); + expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PatchRequest)); }); - it('should send a PatchRequest', () => { - expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PatchRequest)); + it('should invalidate the cached object if successfully patched', () => { + spyOn(rdbService, 'buildFromRequestUUIDAndAwait'); + spyOn(service, 'invalidateByHref'); + + service.patch(dso, operations); + + expect(rdbService.buildFromRequestUUIDAndAwait).toHaveBeenCalled(); + expect((rdbService.buildFromRequestUUIDAndAwait as jasmine.Spy).calls.argsFor(0)[0]).toBe(requestService.generateRequestId()); + const callback = (rdbService.buildFromRequestUUIDAndAwait as jasmine.Spy).calls.argsFor(0)[1]; + callback(); + + expect(service.invalidateByHref).toHaveBeenCalledWith('dso-href'); }); }); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 6f260bae25..b69d219c2e 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -484,7 +484,7 @@ export abstract class DataService implements UpdateDa this.requestService.send(request); }); - return this.rdbService.buildFromRequestUUID(requestId); + return this.rdbService.buildFromRequestUUIDAndAwait(requestId, () => this.invalidateByHref(object._links.self.href)); } createPatchFromCache(object: T): Observable { From 293ba8408e63d04e595695f01056156eef3f749a Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Thu, 8 Sep 2022 13:27:34 +0200 Subject: [PATCH 09/65] 94273: Make isAuthorized depend on the target object This ensures that a successful ItemDataService.setWithdrawn call invalidates all related authorizations --- .../authorization-data.service.spec.ts | 46 ++++++++++++++++++- .../authorization-data.service.ts | 24 ++++++++-- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/app/core/data/feature-authorization/authorization-data.service.spec.ts b/src/app/core/data/feature-authorization/authorization-data.service.spec.ts index df46d3f0a1..255ce8f741 100644 --- a/src/app/core/data/feature-authorization/authorization-data.service.spec.ts +++ b/src/app/core/data/feature-authorization/authorization-data.service.spec.ts @@ -3,7 +3,7 @@ import { SiteDataService } from '../site-data.service'; import { AuthService } from '../../auth/auth.service'; import { Site } from '../../shared/site.model'; import { EPerson } from '../../eperson/models/eperson.model'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { FeatureID } from './feature-id'; import { hasValue } from '../../../shared/empty.util'; import { RequestParam } from '../../cache/models/request-param.model'; @@ -17,6 +17,7 @@ describe('AuthorizationDataService', () => { let service: AuthorizationDataService; let siteService: SiteDataService; let authService: AuthService; + let objectCache; let site: Site; let ePerson: EPerson; @@ -43,7 +44,11 @@ describe('AuthorizationDataService', () => { isAuthenticated: () => observableOf(true), getAuthenticatedUserFromStore: () => observableOf(ePerson) } as AuthService; - service = new AuthorizationDataService(requestService, undefined, undefined, undefined, undefined, undefined, undefined, undefined, authService, siteService); + objectCache = jasmine.createSpyObj('objectCache', { + addDependency: undefined, + removeDependents: undefined, + }); + service = new AuthorizationDataService(requestService, undefined, undefined, objectCache, undefined, undefined, undefined, undefined, authService, siteService); } beforeEach(() => { @@ -110,6 +115,43 @@ describe('AuthorizationDataService', () => { expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePersonUuid, FeatureID.LoginOnBehalfOf), true, true); }); }); + + describe('dependencies', () => { + let addDependencySpy; + + beforeEach(() => { + (service.searchBy as any).and.returnValue(observableOf('searchBy RD$')); + addDependencySpy = spyOn(service as any, 'addDependency'); + }); + + it('should add a dependency on the objectUrl', (done) => { + addDependencySpy.and.callFake((href$: Observable, dependsOn$: Observable) => { + observableCombineLatest([href$, dependsOn$]).subscribe(([href, dependsOn]) => { + expect(href).toBe('searchBy RD$'); + expect(dependsOn).toBe('object-href'); + }); + }); + + service.searchByObject(FeatureID.AdministratorOf, 'object-href').subscribe(() => { + expect(addDependencySpy).toHaveBeenCalled(); + done(); + }); + }); + + it('should add a dependency on the Site object if no objectUrl is given', (done) => { + addDependencySpy.and.callFake((object$: Observable, dependsOn$: Observable) => { + observableCombineLatest([object$, dependsOn$]).subscribe(([object, dependsOn]) => { + expect(object).toBe('searchBy RD$'); + expect(dependsOn).toBe('test-site-href'); + }); + }); + + service.searchByObject(FeatureID.AdministratorOf).subscribe(() => { + expect(addDependencySpy).toHaveBeenCalled(); + done(); + }); + }); + }); }); describe('isAuthorized', () => { diff --git a/src/app/core/data/feature-authorization/authorization-data.service.ts b/src/app/core/data/feature-authorization/authorization-data.service.ts index f27919844d..008e69b119 100644 --- a/src/app/core/data/feature-authorization/authorization-data.service.ts +++ b/src/app/core/data/feature-authorization/authorization-data.service.ts @@ -18,10 +18,10 @@ import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link- import { RemoteData } from '../remote-data'; import { PaginatedList } from '../paginated-list.model'; import { catchError, map, switchMap } from 'rxjs/operators'; -import { hasValue, isNotEmpty } from '../../../shared/empty.util'; +import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util'; import { RequestParam } from '../../cache/models/request-param.model'; import { AuthorizationSearchParams } from './authorization-search-params'; -import { addSiteObjectUrlIfEmpty, oneAuthorizationMatchesFeature } from './authorization-utils'; +import { oneAuthorizationMatchesFeature } from './authorization-utils'; import { FeatureID } from './feature-id'; import { getFirstCompletedRemoteData } from '../../shared/operators'; import { CoreState } from '../../core-state.model'; @@ -102,12 +102,28 @@ export class AuthorizationDataService extends DataService { * {@link HALLink}s should be automatically resolved */ searchByObject(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - return observableOf(new AuthorizationSearchParams(objectUrl, ePersonUuid, featureId)).pipe( - addSiteObjectUrlIfEmpty(this.siteService), + const objectUrl$ = observableOf(objectUrl).pipe( + switchMap((url) => { + if (hasNoValue(url)) { + return this.siteService.find().pipe( + map((site) => site.self) + ); + } else { + return observableOf(url); + } + }), + ); + + const out$ = objectUrl$.pipe( + map((url: string) => new AuthorizationSearchParams(url, ePersonUuid, featureId)), switchMap((params: AuthorizationSearchParams) => { return this.searchBy(this.searchByObjectPath, this.createSearchOptions(params.objectUrl, options, params.ePersonUuid, params.featureId), useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); }) ); + + this.addDependency(out$, objectUrl$); + + return out$; } /** From c1498424f3605eb61cb90cc7d853ebf86305ee8f Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Tue, 13 Sep 2022 16:18:32 +0530 Subject: [PATCH 10/65] CST-6685 changes for import batch --- .../batch-import-page.component.html | 35 ++++ .../batch-import-page.component.spec.ts | 149 ++++++++++++++++++ .../batch-import-page.component.ts | 123 +++++++++++++++ src/app/admin/admin-routing.module.ts | 7 + src/app/admin/admin.module.ts | 4 +- .../data/processes/script-data.service.ts | 1 + src/app/menu.resolver.spec.ts | 3 + src/app/menu.resolver.ts | 27 ++-- .../dso-selector-modal-wrapper.component.ts | 1 + .../import-batch-selector.component.spec.ts | 100 ++++++++++++ .../import-batch-selector.component.ts | 45 ++++++ src/app/shared/shared.module.ts | 5 + src/assets/i18n/en.json5 | 27 +++- 13 files changed, 508 insertions(+), 19 deletions(-) create mode 100644 src/app/admin/admin-import-batch-page/batch-import-page.component.html create mode 100644 src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts create mode 100644 src/app/admin/admin-import-batch-page/batch-import-page.component.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.html b/src/app/admin/admin-import-batch-page/batch-import-page.component.html new file mode 100644 index 0000000000..dbc8c74437 --- /dev/null +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.html @@ -0,0 +1,35 @@ +
+ +

{{'admin.batch-import.page.help' | translate}}

+

+ selected collection: {{getDspaceObjectName()}}  + {{'admin.batch-import.page.remove' | translate}} +

+

+ +

+
+
+ + +
+ + {{'admin.batch-import.page.validateOnly.hint' | translate}} + +
+ + + + +
+ + +
+
diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts new file mode 100644 index 0000000000..eecf05f543 --- /dev/null +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts @@ -0,0 +1,149 @@ +import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; +import { BatchImportPageComponent } from './batch-import-page.component'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { FormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive'; +import { FileValidator } from '../../shared/utils/require-file.validator'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { + BATCH_IMPORT_SCRIPT_NAME, + ScriptDataService +} from '../../core/data/processes/script-data.service'; +import { Router } from '@angular/router'; +import { Location } from '@angular/common'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; + +describe('BatchImportPageComponent', () => { + let component: BatchImportPageComponent; + let fixture: ComponentFixture; + + let notificationService: NotificationsServiceStub; + let scriptService: any; + let router; + let locationStub; + + function init() { + notificationService = new NotificationsServiceStub(); + scriptService = jasmine.createSpyObj('scriptService', + { + invoke: createSuccessfulRemoteDataObject$({ processId: '46' }) + } + ); + router = jasmine.createSpyObj('router', { + navigateByUrl: jasmine.createSpy('navigateByUrl') + }); + locationStub = jasmine.createSpyObj('location', { + back: jasmine.createSpy('back') + }); + } + + beforeEach(waitForAsync(() => { + init(); + TestBed.configureTestingModule({ + imports: [ + FormsModule, + TranslateModule.forRoot(), + RouterTestingModule.withRoutes([]) + ], + declarations: [BatchImportPageComponent, FileValueAccessorDirective, FileValidator], + providers: [ + { provide: NotificationsService, useValue: notificationService }, + { provide: ScriptDataService, useValue: scriptService }, + { provide: Router, useValue: router }, + { provide: Location, useValue: locationStub }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BatchImportPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('if back button is pressed', () => { + beforeEach(fakeAsync(() => { + const proceed = fixture.debugElement.query(By.css('#backButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('should do location.back', () => { + expect(locationStub.back).toHaveBeenCalled(); + }); + }); + + describe('if file is set', () => { + let fileMock: File; + + beforeEach(() => { + fileMock = new File([''], 'filename.zip', { type: 'application/zip' }); + component.setFile(fileMock); + }); + + describe('if proceed button is pressed without validate only', () => { + beforeEach(fakeAsync(() => { + component.validateOnly = false; + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('metadata-import script is invoked with --zip fileName and the mockFile', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); + }); + it('success notification is shown', () => { + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46'); + }); + }); + + describe('if proceed button is pressed with validate only', () => { + beforeEach(fakeAsync(() => { + component.validateOnly = true; + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), + Object.assign(new ProcessParameter(), { name: '-v', value: true }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); + }); + it('success notification is shown', () => { + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46'); + }); + }); + + describe('if proceed is pressed; but script invoke fails', () => { + beforeEach(fakeAsync(() => { + jasmine.getEnv().allowRespy(true); + spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500)); + const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement; + proceed.click(); + fixture.detectChanges(); + })); + it('error notification is shown', () => { + expect(notificationService.error).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts new file mode 100644 index 0000000000..2985aff3aa --- /dev/null +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts @@ -0,0 +1,123 @@ +import { Component } from '@angular/core'; +import { Location } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { BATCH_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service'; +import { Router } from '@angular/router'; +import { ProcessParameter } from '../../process-page/processes/process-parameter.model'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { Process } from '../../process-page/processes/process.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths'; +import { + ImportBatchSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { take } from 'rxjs/operators'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; + +@Component({ + selector: 'ds-batch-import-page', + templateUrl: './batch-import-page.component.html' +}) +export class BatchImportPageComponent { + /** + * The current value of the file + */ + fileObject: File; + + /** + * The validate only flag + */ + validateOnly = true; + /** + * dso object for community or collection + */ + dso: DSpaceObject = null; + + public constructor(private location: Location, + protected translate: TranslateService, + protected notificationsService: NotificationsService, + private scriptDataService: ScriptDataService, + private router: Router, + private modalService: NgbModal, + private dsoNameService: DSONameService) { + } + + /** + * Set file + * @param file + */ + setFile(file) { + this.fileObject = file; + } + + /** + * When return button is pressed go to previous location + */ + public onReturn() { + this.location.back(); + } + + public selectCollection() { + const modalRef = this.modalService.open(ImportBatchSelectorComponent); + modalRef.componentInstance.response.pipe(take(1)).subscribe((dso) => { + this.dso = dso || null; + }); + } + + /** + * Starts import-metadata script with --zip fileName (and the selected file) + */ + public importMetadata() { + if (this.fileObject == null) { + this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile')); + } else { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }), + ]; + if (this.dso) { + parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid })); + } + if (this.validateOnly) { + parameterValues.push(Object.assign(new ProcessParameter(), { name: '-v', value: true })); + } + + this.scriptDataService.invoke(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]).pipe( + getFirstCompletedRemoteData(), + ).subscribe((rd: RemoteData) => { + if (rd.hasSucceeded) { + const title = this.translate.get('process.new.notification.success.title'); + const content = this.translate.get('process.new.notification.success.content'); + this.notificationsService.success(title, content); + if (isNotEmpty(rd.payload)) { + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } + } else { + const title = this.translate.get('process.new.notification.error.title'); + const content = this.translate.get('process.new.notification.error.content'); + this.notificationsService.error(title, content); + } + }); + } + } + + /** + * return selected dspace object name + */ + getDspaceObjectName(): string { + if (this.dso) { + return this.dsoNameService.getName(this.dso); + } + return null; + } + + /** + * remove selected dso object + */ + removeDspaceObject(): void { + this.dso = null; + } +} diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index ee5cb8737b..1ea20bc9a0 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -7,6 +7,7 @@ import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service'; import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component'; import { REGISTRIES_MODULE_PATH } from './admin-routing-paths'; +import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; @NgModule({ imports: [ @@ -40,6 +41,12 @@ import { REGISTRIES_MODULE_PATH } from './admin-routing-paths'; component: MetadataImportPageComponent, data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' } }, + { + path: 'batch-import', + resolve: { breadcrumb: I18nBreadcrumbResolver }, + component: BatchImportPageComponent, + data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' } + }, ]) ], providers: [ diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index b28a0cf89e..0ddbefd253 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -9,6 +9,7 @@ import { AdminWorkflowModuleModule } from './admin-workflow-page/admin-workflow. import { AdminSearchModule } from './admin-search-page/admin-search.module'; import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; +import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -28,7 +29,8 @@ const ENTRY_COMPONENTS = [ ], declarations: [ AdminCurationTasksComponent, - MetadataImportPageComponent + MetadataImportPageComponent, + BatchImportPageComponent ] }) export class AdminModule { diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts index 75a66c822a..91f8d591af 100644 --- a/src/app/core/data/processes/script-data.service.ts +++ b/src/app/core/data/processes/script-data.service.ts @@ -25,6 +25,7 @@ import { CoreState } from '../../core-state.model'; export const METADATA_IMPORT_SCRIPT_NAME = 'metadata-import'; export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export'; +export const BATCH_IMPORT_SCRIPT_NAME = 'batch-import'; @Injectable() @dataService(SCRIPT) diff --git a/src/app/menu.resolver.spec.ts b/src/app/menu.resolver.spec.ts index db90b7ea00..f39075ad27 100644 --- a/src/app/menu.resolver.spec.ts +++ b/src/app/menu.resolver.spec.ts @@ -259,6 +259,9 @@ describe('MenuResolver', () => { expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ id: 'import', visible: true, })); + expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ + id: 'import_batch', parentID: 'import', visible: true, + })); expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ id: 'export', visible: true, })); diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index f12079f737..a7c1fe415e 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -448,20 +448,7 @@ export class MenuResolver implements Resolve { * the import scripts exist and the current user is allowed to execute them */ createImportMenuSections() { - const menuList = [ - // TODO: enable this menu item once the feature has been implemented - // { - // id: 'import_batch', - // parentID: 'import', - // active: false, - // visible: true, - // model: { - // type: MenuItemType.LINK, - // text: 'menu.section.import_batch', - // link: '' - // } as LinkMenuItemModel, - // } - ]; + const menuList = []; menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection)); observableCombineLatest([ @@ -498,6 +485,18 @@ export class MenuResolver implements Resolve { } as LinkMenuItemModel, shouldPersistOnRouteChange: true }); + this.menuService.addSection(MenuID.ADMIN, { + id: 'import_batch', + parentID: 'import', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.import_batch', + link: '/admin/batch-import' + } as LinkMenuItemModel, + shouldPersistOnRouteChange: true + }); }); } diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index ca8343cfad..937608a2c6 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -10,6 +10,7 @@ export enum SelectorActionType { CREATE = 'create', EDIT = 'edit', EXPORT_METADATA = 'export-metadata', + IMPORT_BATCH = 'import-batch', SET_SCOPE = 'set-scope' } diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts new file mode 100644 index 0000000000..576c1ea140 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts @@ -0,0 +1,100 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { Collection } from '../../../../core/shared/collection.model'; +import { Community } from '../../../../core/shared/community.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ImportBatchSelectorComponent } from './import-batch-selector.component'; + +describe('ImportBatchSelectorComponent', () => { + let component: ImportBatchSelectorComponent; + let fixture: ComponentFixture; + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + uuid: 'fake-id', + handle: 'fake/handle', + lastModified: '2018' + }); + const mockCollection: Collection = Object.assign(new Collection(), { + id: 'test-collection-1-1', + uuid: 'test-collection-1-1', + name: 'test-collection-1', + metadata: { + 'dc.identifier.uri': [ + { + language: null, + value: 'fake/test-collection-1' + } + ] + } + }); + const mockCommunity = Object.assign(new Community(), { + id: 'test-uuid', + uuid: 'test-uuid', + metadata: { + 'dc.identifier.uri': [ + { + language: null, + value: 'fake/test-community-1' + } + ] + } + }); + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + declarations: [ImportBatchSelectorComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImportBatchSelectorComponent); + component = fixture.componentInstance; + spyOn(component.response, 'emit'); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('if item is selected', () => { + beforeEach((done) => { + component.navigate(mockItem).subscribe(() => { + done(); + }); + }); + it('should emit null value', () => { + expect(component.response.emit).toHaveBeenCalledWith(null); + }); + }); + + describe('if collection is selected', () => { + beforeEach((done) => { + component.navigate(mockCollection).subscribe(() => { + done(); + }); + }); + it('should emit collection value', () => { + expect(component.response.emit).toHaveBeenCalledWith(mockCollection); + }); + }); + + describe('if community is selected', () => { + beforeEach((done) => { + component.navigate(mockCommunity).subscribe(() => { + done(); + }); + }); + it('should emit community value', () => { + expect(component.response.emit).toHaveBeenCalledWith(mockCommunity); + }); + }); +}); diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts new file mode 100644 index 0000000000..2fdae23388 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts @@ -0,0 +1,45 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Collection } from '../../../../core/shared/collection.model'; +import { Community } from '../../../../core/shared/community.model'; +import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; +import { Observable, of } from 'rxjs'; + +/** + * Component to wrap a list of existing dso's inside a modal + * Used to choose a dso from to import metadata of + */ +@Component({ + selector: 'ds-import-batch-selector', + templateUrl: '../dso-selector-modal-wrapper.component.html', +}) +export class ImportBatchSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { + objectType = DSpaceObjectType.DSPACEOBJECT; + selectorTypes = [DSpaceObjectType.COLLECTION, DSpaceObjectType.COMMUNITY]; + action = SelectorActionType.IMPORT_BATCH; + /** + * An event fired when the modal is closed + */ + @Output() + response = new EventEmitter(); + + constructor(protected activeModal: NgbActiveModal, + protected route: ActivatedRoute) { + super(activeModal, route); + } + + /** + * If the dso is a collection or community: + */ + navigate(dso: DSpaceObject): Observable { + if (dso instanceof Collection || dso instanceof Community) { + this.response.emit(dso); + return of(null); + } + this.response.emit(null); + return of(null); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 9036ff98c5..5e78796e24 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -24,6 +24,9 @@ import { ConfirmationModalComponent } from './confirmation-modal/confirmation-mo import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; +import { + ImportBatchSelectorComponent +} from './dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component'; import { FileDropzoneNoUploaderComponent } from './file-dropzone-no-uploader/file-dropzone-no-uploader.component'; import { ItemListElementComponent } from './object-list/item-list-element/item-types/item/item-list-element.component'; import { EnumKeysPipe } from './utils/enum-keys-pipe'; @@ -468,6 +471,7 @@ const COMPONENTS = [ CollectionDropdownComponent, EntityDropdownComponent, ExportMetadataSelectorComponent, + ImportBatchSelectorComponent, ConfirmationModalComponent, VocabularyTreeviewComponent, AuthorizedCollectionSelectorComponent, @@ -545,6 +549,7 @@ const ENTRY_COMPONENTS = [ BitstreamRequestACopyPageComponent, CurationFormComponent, ExportMetadataSelectorComponent, + ImportBatchSelectorComponent, ConfirmationModalComponent, VocabularyTreeviewComponent, SidebarSearchListElementComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bd40bc642c..171dbaa332 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -542,28 +542,45 @@ "admin.metadata-import.breadcrumbs": "Import Metadata", + "admin.batch-import.breadcrumbs": "Import Batch", + "admin.metadata-import.title": "Import Metadata", + "admin.batch-import.title": "Import Batch", + "admin.metadata-import.page.header": "Import Metadata", + "admin.batch-import.page.header": "Import Batch", + "admin.metadata-import.page.help": "You can drop or browse CSV files that contain batch metadata operations on files here", + "admin.batch-import.page.help": "You can drop or browse ZIP files that contain batch operations on files here", + "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", + "admin.batch-import.page.dropMsg": "Drop a batch ZIP to import", + "admin.metadata-import.page.dropMsgReplace": "Drop to replace the metadata CSV to import", + "admin.batch-import.page.dropMsgReplace": "Drop to replace the batch ZIP to import", + "admin.metadata-import.page.button.return": "Back", "admin.metadata-import.page.button.proceed": "Proceed", + "admin.metadata-import.page.button.select-collection": "Select Collection", + "admin.metadata-import.page.error.addFile": "Select file first!", + "admin.batch-import.page.error.addFile": "Select Zip file first!", + "admin.metadata-import.page.validateOnly": "Validate Only", "admin.metadata-import.page.validateOnly.hint": "When selected, the uploaded CSV will be validated. You will receive a report of detected changes, but no changes will be saved.", + "admin.batch-import.page.validateOnly.hint": "When selected, the uploaded ZIP will be validated. You will receive a report of detected changes, but no changes will be saved.", - + "admin.batch-import.page.remove": "remove", "auth.errors.invalid-user": "Invalid email address or password.", @@ -640,7 +657,7 @@ "bitstream-request-a-copy.alert.canDownload2": "here", "bitstream-request-a-copy.header": "Request a copy of the file", - + "bitstream-request-a-copy.intro": "Enter the following information to request a copy for the following item: ", "bitstream-request-a-copy.intro.bitstream.one": "Requesting the following file: ", @@ -1345,6 +1362,8 @@ "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", + "dso-selector.import-batch.dspaceobject.head": "Import batch from", + "dso-selector.no-results": "No {{ type }} found", "dso-selector.placeholder": "Search for a {{ type }}", @@ -2984,7 +3003,7 @@ "process.detail.create" : "Create similar process", "process.detail.actions": "Actions", - + "process.detail.delete.button": "Delete process", "process.detail.delete.header": "Delete process", @@ -3037,7 +3056,7 @@ "process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted", - + "profile.breadcrumbs": "Update Profile", From 70c6eac88d3de14cd70b4a9a83616f63785ebf57 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 15 Sep 2022 14:30:59 +0200 Subject: [PATCH 11/65] [CST-6753] Change google-analytics.service in order to use GoogleTagManager instead of GoogleAnalytics --- .../google-analytics.service.spec.ts | 25 +++++++++++-------- .../statistics/google-analytics.service.ts | 18 +++++++------ src/modules/app/server-app.module.ts | 5 ++-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts index 0c6bc2bc51..24c5345260 100644 --- a/src/app/statistics/google-analytics.service.spec.ts +++ b/src/app/statistics/google-analytics.service.spec.ts @@ -1,20 +1,19 @@ import { GoogleAnalyticsService } from './google-analytics.service'; -import { Angulartics2GoogleAnalytics } from 'angulartics2'; +import { Angulartics2GoogleTagManager } from 'angulartics2'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; -import { - createFailedRemoteDataObject$, - createSuccessfulRemoteDataObject$ -} from '../shared/remote-data.utils'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; describe('GoogleAnalyticsService', () => { const trackingIdProp = 'google.analytics.key'; const trackingIdTestValue = 'mock-tracking-id'; const innerHTMLTestValue = 'mock-script-inner-html'; + const srcTestValue = 'mock-script-src'; let service: GoogleAnalyticsService; - let angularticsSpy: Angulartics2GoogleAnalytics; + let angularticsSpy: Angulartics2GoogleTagManager; let configSpy: ConfigurationDataService; let scriptElementMock: any; + let srcSpy: any; let innerHTMLSpy: any; let bodyElementSpy: HTMLBodyElement; let documentSpy: Document; @@ -28,18 +27,21 @@ describe('GoogleAnalyticsService', () => { }); beforeEach(() => { - angularticsSpy = jasmine.createSpyObj('angulartics2GoogleAnalytics', [ + angularticsSpy = jasmine.createSpyObj('Angulartics2GoogleTagManager', [ 'startTracking', ]); configSpy = createConfigSuccessSpy(trackingIdTestValue); scriptElementMock = { + set src(newVal) { /* noop */ }, + get src() { return innerHTMLTestValue; }, set innerHTML(newVal) { /* noop */ }, - get innerHTML() { return innerHTMLTestValue; } + get innerHTML() { return srcTestValue; } }; innerHTMLSpy = spyOnProperty(scriptElementMock, 'innerHTML', 'set'); + srcSpy = spyOnProperty(scriptElementMock, 'src', 'set'); bodyElementSpy = jasmine.createSpyObj('body', { appendChild: scriptElementMock, @@ -106,19 +108,22 @@ describe('GoogleAnalyticsService', () => { describe('when the tracking id is non-empty', () => { it('should create a script tag whose innerHTML contains the tracking id', () => { service.addTrackingIdToPage(); - expect(documentSpy.createElement).toHaveBeenCalledTimes(1); + expect(documentSpy.createElement).toHaveBeenCalledTimes(2); expect(documentSpy.createElement).toHaveBeenCalledWith('script'); // sanity check expect(documentSpy.createElement('script')).toBe(scriptElementMock); + expect(srcSpy).toHaveBeenCalledTimes(1); + expect(srcSpy.calls.argsFor(0)[0]).toContain(trackingIdTestValue); + expect(innerHTMLSpy).toHaveBeenCalledTimes(1); expect(innerHTMLSpy.calls.argsFor(0)[0]).toContain(trackingIdTestValue); }); it('should add a script to the body', () => { service.addTrackingIdToPage(); - expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(1); + expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(2); }); it('should start tracking', () => { diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts index 0b52f54c4f..6bdff53d52 100644 --- a/src/app/statistics/google-analytics.service.ts +++ b/src/app/statistics/google-analytics.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@angular/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2'; +import { Angulartics2GoogleTagManager } from 'angulartics2'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { isEmpty } from '../shared/empty.util'; @@ -13,7 +13,8 @@ import { DOCUMENT } from '@angular/common'; export class GoogleAnalyticsService { constructor( - private angulartics: Angulartics2GoogleAnalytics, + // private angulartics: Angulartics2GoogleAnalytics, + private angulartics: Angulartics2GoogleTagManager, private configService: ConfigurationDataService, @Inject(DOCUMENT) private document: any, ) { } @@ -36,15 +37,16 @@ export class GoogleAnalyticsService { // make sure we received a tracking id if (isEmpty(trackingId)) { return; } - // add trackingId snippet to page + // add GTag snippet to page const keyScript = this.document.createElement('script'); - keyScript.innerHTML = `(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); - ga('create', '${trackingId}', 'auto');`; + keyScript.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`; this.document.body.appendChild(keyScript); + const libScript = this.document.createElement('script'); + libScript.innerHTML = `window.dataLayer = window.dataLayer || [];function gtag(){window.dataLayer.push(arguments);} + gtag('js', new Date());gtag('config', '${trackingId}');`; + this.document.body.appendChild(libScript); + // start tracking this.angulartics.startTracking(); }); diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 35fa050d6f..8565db3e23 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -6,8 +6,7 @@ import { ServerModule, ServerTransferStateModule } from '@angular/platform-serve import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Angulartics2 } from 'angulartics2'; -import { Angulartics2GoogleAnalytics } from 'angulartics2'; +import { Angulartics2, Angulartics2GoogleTagManager } from 'angulartics2'; import { AppComponent } from '../../app/app.component'; @@ -60,7 +59,7 @@ export function createTranslateLoader(transferState: TransferState) { useClass: Angulartics2Mock }, { - provide: Angulartics2GoogleAnalytics, + provide: Angulartics2GoogleTagManager, useClass: AngularticsProviderMock }, { From 1df41deb8f071b58beaeb4ee88b3f1397d6851c0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 15 Sep 2022 14:36:09 +0200 Subject: [PATCH 12/65] [CST-6753] Remove unused occurrences of GoogleAnalytics --- src/app/app.component.spec.ts | 4 +--- src/app/root/root.component.spec.ts | 2 -- src/app/root/root.component.ts | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 8bec7edc80..422ead99e1 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,10 +1,9 @@ import { Store, StoreModule } from '@ngrx/store'; import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { CommonModule, DOCUMENT } from '@angular/common'; +import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2'; // Load the implementations that should be tested import { AppComponent } from './app.component'; @@ -73,7 +72,6 @@ describe('App component', () => { providers: [ { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: MetadataService, useValue: new MetadataServiceMock() }, - { provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() }, { provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() }, { provide: AuthService, useValue: new AuthServiceMock() }, { provide: Router, useValue: new RouterMock() }, diff --git a/src/app/root/root.component.spec.ts b/src/app/root/root.component.spec.ts index 64a15a3087..504bc34e34 100644 --- a/src/app/root/root.component.spec.ts +++ b/src/app/root/root.component.spec.ts @@ -9,7 +9,6 @@ import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; import { NativeWindowRef, NativeWindowService } from '../core/services/window.service'; import { MetadataService } from '../core/metadata/metadata.service'; import { MetadataServiceMock } from '../shared/mocks/metadata-service.mock'; -import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { AngularticsProviderMock } from '../shared/mocks/angulartics-provider.service.mock'; import { Angulartics2DSpace } from '../statistics/angulartics/dspace-provider'; import { AuthService } from '../core/auth/auth.service'; @@ -50,7 +49,6 @@ describe('RootComponent', () => { providers: [ { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: MetadataService, useValue: new MetadataServiceMock() }, - { provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() }, { provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() }, { provide: AuthService, useValue: new AuthServiceMock() }, { provide: Router, useValue: new RouterMock() }, diff --git a/src/app/root/root.component.ts b/src/app/root/root.component.ts index 3dc555919c..472ba440c9 100644 --- a/src/app/root/root.component.ts +++ b/src/app/root/root.component.ts @@ -5,7 +5,6 @@ import { Router } from '@angular/router'; import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { Angulartics2GoogleAnalytics } from 'angulartics2'; import { MetadataService } from '../core/metadata/metadata.service'; import { HostWindowState } from '../shared/search/host-window.reducer'; @@ -51,7 +50,6 @@ export class RootComponent implements OnInit { private translate: TranslateService, private store: Store, private metadata: MetadataService, - private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics, private angulartics2DSpace: Angulartics2DSpace, private authService: AuthService, private router: Router, From dd2317699acb7ec50db084647e3c5b13c3ff94d2 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 19 Sep 2022 10:11:40 +0200 Subject: [PATCH 13/65] make search-settings.component themed --- .../themed-search-settings.component.ts | 33 +++++++++++++++++++ src/app/shared/search/search.module.ts | 2 ++ .../search-settings.component.html | 0 .../search-settings.component.scss | 0 .../search-settings.component.ts | 31 +++++++++++++++++ src/themes/custom/theme.module.ts | 4 ++- 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/search/search-settings/themed-search-settings.component.ts create mode 100644 src/themes/custom/app/shared/search-settings/search-settings.component.html create mode 100644 src/themes/custom/app/shared/search-settings/search-settings.component.scss create mode 100644 src/themes/custom/app/shared/search-settings/search-settings.component.ts diff --git a/src/app/shared/search/search-settings/themed-search-settings.component.ts b/src/app/shared/search/search-settings/themed-search-settings.component.ts new file mode 100644 index 0000000000..b1f1d08264 --- /dev/null +++ b/src/app/shared/search/search-settings/themed-search-settings.component.ts @@ -0,0 +1,33 @@ +import { Component, Input } from '@angular/core'; +import { ThemedComponent } from '../../theme-support/themed.component'; +import { SearchSettingsComponent } from './search-settings.component'; +import { SortOptions } from '../../../core/cache/models/sort-options.model'; + +/** + * Themed wrapper for SearchSettingsComponent + */ +@Component({ + selector: 'ds-themed-search-settings', + styleUrls: [], + templateUrl: '../../theme-support/themed.component.html', +}) +export class ThemedSearchSettingsComponent extends ThemedComponent { + @Input() currentSortOption: SortOptions; + @Input() sortOptionsList: SortOptions[]; + + + protected inAndOutputNames: (keyof SearchSettingsComponent & keyof this)[] = [ + 'currentSortOption', 'sortOptionsList']; + + protected getComponentName(): string { + return 'SearchSettingsComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../../themes/${themeName}/app/shared/search/search-settings/search-settings.component`); + } + + protected importUnthemedComponent(): Promise { + return import('./search-settings.component'); + } +} diff --git a/src/app/shared/search/search.module.ts b/src/app/shared/search/search.module.ts index 668d260c23..51ba17c6cf 100644 --- a/src/app/shared/search/search.module.ts +++ b/src/app/shared/search/search.module.ts @@ -28,12 +28,14 @@ import { MissingTranslationHelper } from '../translate/missing-translation.helpe import { SharedModule } from '../shared.module'; import { SearchResultsComponent } from './search-results/search-results.component'; import { SearchComponent } from './search.component'; +import { ThemedSearchSettingsComponent } from './search-settings/themed-search-settings.component'; const COMPONENTS = [ SearchComponent, SearchResultsComponent, SearchSidebarComponent, SearchSettingsComponent, + ThemedSearchSettingsComponent, SearchFiltersComponent, SearchFilterComponent, SearchFacetFilterComponent, diff --git a/src/themes/custom/app/shared/search-settings/search-settings.component.html b/src/themes/custom/app/shared/search-settings/search-settings.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/search-settings/search-settings.component.scss b/src/themes/custom/app/shared/search-settings/search-settings.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/search-settings/search-settings.component.ts b/src/themes/custom/app/shared/search-settings/search-settings.component.ts new file mode 100644 index 0000000000..db27ac95f4 --- /dev/null +++ b/src/themes/custom/app/shared/search-settings/search-settings.component.ts @@ -0,0 +1,31 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE_ATMIRE and NOTICE_ATMIRE files at the root of the source + * tree and available online at + * + * https://www.atmire.com/software-license/ + */ +import { Component } from '@angular/core'; +import { + SearchSettingsComponent as BaseComponent, +} from '../../../../../app/shared/search/search-settings/search-settings.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../../app/my-dspace-page/my-dspace-page.component'; +import { SearchConfigurationService } from '../../../../../app/core/shared/search/search-configuration.service'; + + +@Component({ + selector: 'ds-search-settings', + // styleUrls: ['./search-settings.component.scss'], + styleUrls: ['../../../../../app/shared/search/search-settings/search-settings.component.scss'], + // templateUrl: './search-settings.component.html', + templateUrl: '../../../../../app/shared/search/search-settings/search-settings.component.html', + providers: [ + { + provide: SEARCH_CONFIG_SERVICE, + useClass: SearchConfigurationService + } + ] + +}) + +export class SearchSettingsComponent extends BaseComponent {} diff --git a/src/themes/custom/theme.module.ts b/src/themes/custom/theme.module.ts index e2e97b9087..3457259792 100644 --- a/src/themes/custom/theme.module.ts +++ b/src/themes/custom/theme.module.ts @@ -84,6 +84,7 @@ import { SearchModule } from '../../app/shared/search/search.module'; import { ResourcePoliciesModule } from '../../app/shared/resource-policies/resource-policies.module'; import { ComcolModule } from '../../app/shared/comcol/comcol.module'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; +import { SearchSettingsComponent } from './app/shared/search-settings/search-settings.component'; const DECLARATIONS = [ FileSectionComponent, @@ -126,7 +127,8 @@ const DECLARATIONS = [ NavbarComponent, HeaderNavbarWrapperComponent, BreadcrumbsComponent, - FeedbackComponent + FeedbackComponent, + SearchSettingsComponent ]; @NgModule({ From a17ee028dfebdcce95f115a275d3b906215ee5e3 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 19 Sep 2022 11:56:04 +0200 Subject: [PATCH 14/65] make search-settings.component themed --- .../shared/search/search-sidebar/search-sidebar.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.html b/src/app/shared/search/search-sidebar/search-sidebar.component.html index e17fe941ba..863bd2d71f 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.html +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.html @@ -21,7 +21,8 @@ [currentConfiguration]="configuration" [refreshFilters]="refreshFilters" [inPlaceSearch]="inPlaceSearch"> - + From f84e4d6146c000ffe8da36f0541ffd6347af0e97 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Mon, 19 Sep 2022 12:35:21 +0200 Subject: [PATCH 15/65] 89685: Resolve wrong directory --- .../search-settings/search-settings.component.html | 0 .../search-settings/search-settings.component.scss | 0 .../search-settings/search-settings.component.ts | 10 +++++----- src/themes/custom/lazy-theme.module.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/themes/custom/app/shared/{ => search}/search-settings/search-settings.component.html (100%) rename src/themes/custom/app/shared/{ => search}/search-settings/search-settings.component.scss (100%) rename src/themes/custom/app/shared/{ => search}/search-settings/search-settings.component.ts (56%) diff --git a/src/themes/custom/app/shared/search-settings/search-settings.component.html b/src/themes/custom/app/shared/search/search-settings/search-settings.component.html similarity index 100% rename from src/themes/custom/app/shared/search-settings/search-settings.component.html rename to src/themes/custom/app/shared/search/search-settings/search-settings.component.html diff --git a/src/themes/custom/app/shared/search-settings/search-settings.component.scss b/src/themes/custom/app/shared/search/search-settings/search-settings.component.scss similarity index 100% rename from src/themes/custom/app/shared/search-settings/search-settings.component.scss rename to src/themes/custom/app/shared/search/search-settings/search-settings.component.scss diff --git a/src/themes/custom/app/shared/search-settings/search-settings.component.ts b/src/themes/custom/app/shared/search/search-settings/search-settings.component.ts similarity index 56% rename from src/themes/custom/app/shared/search-settings/search-settings.component.ts rename to src/themes/custom/app/shared/search/search-settings/search-settings.component.ts index db27ac95f4..e17c2425b5 100644 --- a/src/themes/custom/app/shared/search-settings/search-settings.component.ts +++ b/src/themes/custom/app/shared/search/search-settings/search-settings.component.ts @@ -8,17 +8,17 @@ import { Component } from '@angular/core'; import { SearchSettingsComponent as BaseComponent, -} from '../../../../../app/shared/search/search-settings/search-settings.component'; -import { SEARCH_CONFIG_SERVICE } from '../../../../../app/my-dspace-page/my-dspace-page.component'; -import { SearchConfigurationService } from '../../../../../app/core/shared/search/search-configuration.service'; +} from '../../../../../../app/shared/search/search-settings/search-settings.component'; +import { SEARCH_CONFIG_SERVICE } from '../../../../../../app/my-dspace-page/my-dspace-page.component'; +import { SearchConfigurationService } from '../../../../../../app/core/shared/search/search-configuration.service'; @Component({ selector: 'ds-search-settings', // styleUrls: ['./search-settings.component.scss'], - styleUrls: ['../../../../../app/shared/search/search-settings/search-settings.component.scss'], + styleUrls: ['../../../../../../app/shared/search/search-settings/search-settings.component.scss'], // templateUrl: './search-settings.component.html', - templateUrl: '../../../../../app/shared/search/search-settings/search-settings.component.html', + templateUrl: '../../../../../../app/shared/search/search-settings/search-settings.component.html', providers: [ { provide: SEARCH_CONFIG_SERVICE, diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 7015270363..c25460b576 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -99,7 +99,7 @@ import { import { LoadingComponent } from './app/shared/loading/loading.component'; import { SearchResultsComponent } from './app/shared/search/search-results/search-results.component'; import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component'; -import { SearchSettingsComponent } from './app/shared/search-settings/search-settings.component'; +import { SearchSettingsComponent } from './app/shared/search/search-settings/search-settings.component'; const DECLARATIONS = [ FileSectionComponent, From f787491af4b2f516d7e31bdd14b8c094ecd383e3 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 20 Sep 2022 11:33:37 +0200 Subject: [PATCH 16/65] [CST-6153] Fix issue with forgot password page --- src/app/core/eperson/eperson-data.service.spec.ts | 2 +- src/app/core/eperson/eperson-data.service.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index ce0b1f3ee4..d8a4a546e6 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -307,7 +307,7 @@ describe('EPersonDataService', () => { it('should sent a patch request with an uuid, token and new password to the epersons endpoint', () => { service.patchPasswordWithToken('test-uuid', 'test-token', 'test-password'); - const operation = Object.assign({ op: 'add', path: '/password', value: 'test-password' }); + const operation = Object.assign({ op: 'add', path: '/password', value: { password: 'test-password' } }); const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/test-uuid?token=test-token', [operation]); expect(requestService.send).toHaveBeenCalledWith(expected); diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index 8f9312d732..9eab566bf4 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -3,7 +3,10 @@ import { createSelector, select, Store } from '@ngrx/store'; import { Operation } from 'fast-json-patch'; import { Observable } from 'rxjs'; import { find, map, take } from 'rxjs/operators'; -import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from '../../access-control/epeople-registry/epeople-registry.actions'; +import { + EPeopleRegistryCancelEPersonAction, + EPeopleRegistryEditEPersonAction +} from '../../access-control/epeople-registry/epeople-registry.actions'; import { EPeopleRegistryState } from '../../access-control/epeople-registry/epeople-registry.reducers'; import { AppState } from '../../app.reducer'; import { hasNoValue, hasValue } from '../../shared/empty.util'; @@ -318,7 +321,7 @@ export class EPersonDataService extends IdentifiableDataService impleme patchPasswordWithToken(uuid: string, token: string, password: string): Observable> { const requestId = this.requestService.generateRequestId(); - const operation = Object.assign({ op: 'add', path: '/password', value: password }); + const operation = Object.assign({ op: 'add', path: '/password', value: { 'password': password } }); const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getIDHref(endpoint, uuid)), From 5099d0b18a4fdd7f0df9a8bae31edb4d558f5210 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 20 Sep 2022 11:41:09 +0200 Subject: [PATCH 17/65] [CST-6153] Show i18n message instead of server error response --- src/app/profile-page/profile-page.component.ts | 3 ++- src/assets/i18n/en.json5 | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 5432a0303c..63a3f3f00f 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -157,7 +157,8 @@ export class ProfilePageComponent implements OnInit { ); } else { this.notificationsService.error( - this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.title'), response.errorMessage + this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.title'), + this.translate.instant(this.PASSWORD_NOTIFICATIONS_PREFIX + 'error.change-failed') ); } }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 3707903823..0c43ccdc73 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -640,7 +640,7 @@ "bitstream-request-a-copy.alert.canDownload2": "here", "bitstream-request-a-copy.header": "Request a copy of the file", - + "bitstream-request-a-copy.intro": "Enter the following information to request a copy for the following item: ", "bitstream-request-a-copy.intro.bitstream.one": "Requesting the following file: ", @@ -2984,7 +2984,7 @@ "process.detail.create" : "Create similar process", "process.detail.actions": "Actions", - + "process.detail.delete.button": "Delete process", "process.detail.delete.header": "Delete process", @@ -3037,7 +3037,7 @@ "process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted", - + "profile.breadcrumbs": "Update Profile", @@ -3093,6 +3093,8 @@ "profile.security.form.notifications.error.title": "Error changing passwords", + "profile.security.form.notifications.error.change-failed": "An error occurred while trying to change the password. Please check if the current password is correct.", + "profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.", "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.", From 81d3b1708f5018777f94731d40ccbce042f97589 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Tue, 20 Sep 2022 12:38:13 +0200 Subject: [PATCH 18/65] 94474: Clarify processing message --- src/app/shared/uploader/uploader.component.html | 2 +- src/assets/i18n/ar.json5 | 4 ++-- src/assets/i18n/cs.json5 | 4 ++-- src/assets/i18n/de.json5 | 3 ++- src/assets/i18n/en.json5 | 2 +- src/assets/i18n/es.json5 | 3 ++- src/assets/i18n/fi.json5 | 3 ++- src/assets/i18n/fr.json5 | 3 ++- src/assets/i18n/hu.json5 | 3 ++- src/assets/i18n/ja.json5 | 4 ++-- src/assets/i18n/lv.json5 | 3 ++- src/assets/i18n/nl.json5 | 3 ++- src/assets/i18n/pl.json5 | 4 ++-- src/assets/i18n/pt-BR.json5 | 3 ++- src/assets/i18n/pt-PT.json5 | 3 ++- src/assets/i18n/sw.json5 | 4 ++-- src/assets/i18n/tr.json5 | 2 +- 17 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/app/shared/uploader/uploader.component.html b/src/app/shared/uploader/uploader.component.html index caa94d1709..eaf2949996 100644 --- a/src/app/shared/uploader/uploader.component.html +++ b/src/app/shared/uploader/uploader.component.html @@ -37,7 +37,7 @@ {{ uploader.progress }}% - {{'uploader.processing' | translate}}... + {{'uploader.processing' | translate}}
Date: Tue, 20 Sep 2022 12:48:27 +0200 Subject: [PATCH 19/65] string changes after merge --- src/assets/i18n/bn.json5 | 3 ++- src/assets/i18n/de.json5 | 3 ++- src/assets/i18n/el.json5 | 2 ++ src/assets/i18n/es.json5 | 3 ++- src/assets/i18n/gd.json5 | 3 ++- src/assets/i18n/kk.json5 | 3 ++- src/assets/i18n/lv.json5 | 3 ++- src/assets/i18n/sv.json5 | 4 ++-- src/assets/i18n/tr.json5 | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5 index 1bc26d2da4..a29a9a9c40 100644 --- a/src/assets/i18n/bn.json5 +++ b/src/assets/i18n/bn.json5 @@ -6026,7 +6026,8 @@ // "uploader.or": ", or ", "uploader.or": "অথবা", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "প্রক্রিয়াকরণ", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c699ee2575..cbd11e5c3d 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5381,7 +5381,8 @@ // "uploader.or": ", or ", "uploader.or": ", oder", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "In Arbeit...", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/el.json5 b/src/assets/i18n/el.json5 index 4d3b57897a..d99856272f 100644 --- a/src/assets/i18n/el.json5 +++ b/src/assets/i18n/el.json5 @@ -2203,6 +2203,8 @@ "uploader.delete.btn-title": "Διαγραφή", "uploader.drag-message": "Σύρετε και αποθέστε τα αρχεία σας εδώ", "uploader.or": ", ή", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Επεξεργασία", "uploader.queue-length": "Μέγεθος ουράς", "virtual-metadata.delete-item.info": "Επιλέξτε τους τύπους για τους οποίους θέλετε να αποθηκεύσετε τα εικονικά μεταδεδομένα ως πραγματικά μεταδεδομένα", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index b738578fc6..c641d683c4 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -6515,7 +6515,8 @@ // "uploader.or": ", or ", "uploader.or": ", o ", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Procesando", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5 index f72c3aa1c8..09938fcf5d 100644 --- a/src/assets/i18n/gd.json5 +++ b/src/assets/i18n/gd.json5 @@ -5980,7 +5980,8 @@ // "uploader.or": ", or ", "uploader.or": ", no ", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "A' pròiseasadh", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5 index 56651ceaae..63e7374ed0 100644 --- a/src/assets/i18n/kk.json5 +++ b/src/assets/i18n/kk.json5 @@ -7135,7 +7135,8 @@ // "uploader.or": ", or ", "uploader.or": ", немесе ", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Өңдеу", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5 index d6c409b783..8aea8da6e5 100644 --- a/src/assets/i18n/lv.json5 +++ b/src/assets/i18n/lv.json5 @@ -5534,7 +5534,8 @@ // TODO Source message changed - Revise the translation "uploader.or": ", vai", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Datu apstrāde", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index 0339969b31..220c5a7f67 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -7945,8 +7945,8 @@ // TODO New key - Add a translation "uploader.or": ", eller ", - // "uploader.processing": "Processing", - // TODO New key - Add a translation + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Bearbetar", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5 index d04a6eda2a..84e5e0f34b 100644 --- a/src/assets/i18n/tr.json5 +++ b/src/assets/i18n/tr.json5 @@ -5052,7 +5052,8 @@ // "uploader.or": ", or ", "uploader.or": ", veya", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "İşleniyor", // "uploader.queue-length": "Queue length", From 2642ed35b78af773324541d2703dba2dc2cb6eff Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 21 Sep 2022 17:47:58 +0200 Subject: [PATCH 20/65] [CST-6153] Use new params name --- src/app/core/eperson/eperson-data.service.spec.ts | 2 +- src/app/core/eperson/eperson-data.service.ts | 2 +- src/app/profile-page/profile-page.component.spec.ts | 4 ++-- src/app/profile-page/profile-page.component.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index d8a4a546e6..b4b939eebf 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -307,7 +307,7 @@ describe('EPersonDataService', () => { it('should sent a patch request with an uuid, token and new password to the epersons endpoint', () => { service.patchPasswordWithToken('test-uuid', 'test-token', 'test-password'); - const operation = Object.assign({ op: 'add', path: '/password', value: { password: 'test-password' } }); + const operation = Object.assign({ op: 'add', path: '/password', value: { new_password: 'test-password' } }); const expected = new PatchRequest(requestService.generateRequestId(), epersonsEndpoint + '/test-uuid?token=test-token', [operation]); expect(requestService.send).toHaveBeenCalledWith(expected); diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts index 9eab566bf4..d30030365c 100644 --- a/src/app/core/eperson/eperson-data.service.ts +++ b/src/app/core/eperson/eperson-data.service.ts @@ -321,7 +321,7 @@ export class EPersonDataService extends IdentifiableDataService impleme patchPasswordWithToken(uuid: string, token: string, password: string): Observable> { const requestId = this.requestService.generateRequestId(); - const operation = Object.assign({ op: 'add', path: '/password', value: { 'password': password } }); + const operation = Object.assign({ op: 'add', path: '/password', value: { 'new_password': password } }); const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getIDHref(endpoint, uuid)), diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index c1bfe8e6bb..709ed56790 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -219,7 +219,7 @@ describe('ProfilePageComponent', () => { component.setCurrentPasswordValue('current-password'); operations = [ - { 'op': 'add', 'path': '/password', 'value': { 'password': 'testest', 'challenge': 'current-password' } } + { 'op': 'add', 'path': '/password', 'value': { 'new_password': 'testest', 'current_password': 'current-password' } } ]; result = component.updateSecurity(); }); @@ -243,7 +243,7 @@ describe('ProfilePageComponent', () => { component.setInvalid(false); component.setCurrentPasswordValue('current-password'); operations = [ - { 'op': 'add', 'path': '/password', 'value': {'password': 'testest', 'challenge': 'current-password' }} + { 'op': 'add', 'path': '/password', 'value': {'new_password': 'testest', 'current_password': 'current-password' }} ]; result = component.updateSecurity(); epersonService.patch(user, operations).subscribe((response) => { diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 63a3f3f00f..109b2663f4 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -147,7 +147,7 @@ export class ProfilePageComponent implements OnInit { } if (!this.invalidSecurity && passEntered) { const operations = [ - { 'op': 'add', 'path': '/password', 'value': { 'password': this.password, 'challenge': this.currentPassword } } + { 'op': 'add', 'path': '/password', 'value': { 'new_password': this.password, 'current_password': this.currentPassword } } ] as Operation[]; this.epersonService.patch(this.currentUser, operations).pipe(getFirstCompletedRemoteData()).subscribe((response: RemoteData) => { if (response.hasSucceeded) { From 68def61f20ab8655b2c5720a36025ea84bcbb0ee Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Wed, 21 Sep 2022 19:20:49 +0200 Subject: [PATCH 21/65] Test invalidation of dependent requests --- src/app/core/data/base/base-data.service.spec.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/core/data/base/base-data.service.spec.ts b/src/app/core/data/base/base-data.service.spec.ts index acda298d3b..17532f477a 100644 --- a/src/app/core/data/base/base-data.service.spec.ts +++ b/src/app/core/data/base/base-data.service.spec.ts @@ -566,7 +566,7 @@ describe('BaseDataService', () => { beforeEach(() => { getByHrefSpy = spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ requestUUIDs: ['request1', 'request2', 'request3'], - dependentRequestUUIDs: [] + dependentRequestUUIDs: ['request4', 'request5'] })); }); @@ -578,6 +578,8 @@ describe('BaseDataService', () => { expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request1'); expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request2'); expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request3'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request4'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request5'); done(); }); }); @@ -590,6 +592,8 @@ describe('BaseDataService', () => { expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request1'); expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request2'); expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request3'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request4'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request5'); })); it('should return an Observable that only emits true once all requests are stale', () => { @@ -599,9 +603,13 @@ describe('BaseDataService', () => { case 'request1': return cold('--(t|)', BOOLEAN); case 'request2': - return cold('----(t|)', BOOLEAN); - case 'request3': return cold('------(t|)', BOOLEAN); + case 'request3': + return cold('---(t|)', BOOLEAN); + case 'request4': + return cold('-(t|)', BOOLEAN); + case 'request5': + return cold('----(t|)', BOOLEAN); } }); From d4107e3f9aea1761b5c7fb01e14b42f4b0bbf142 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 21 Sep 2022 12:13:10 -0700 Subject: [PATCH 22/65] Initial update for multiple bundle check. --- .../mirador-viewer.component.spec.ts | 5 ++ .../mirador-viewer.component.ts | 10 ++- .../mirador-viewer/mirador-viewer.service.ts | 74 +++++++++++++------ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.component.spec.ts b/src/app/item-page/mirador-viewer/mirador-viewer.component.spec.ts index 645d2af91d..40ad0fd5d0 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.component.spec.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.component.spec.ts @@ -12,6 +12,7 @@ import { createPaginatedList } from '../../shared/testing/utils.test'; import { of as observableOf } from 'rxjs'; import { MiradorViewerService } from './mirador-viewer.service'; import { HostWindowService } from '../../shared/host-window.service'; +import { BundleDataService } from '../../core/data/bundle-data.service'; function getItem(metadata: MetadataMap) { @@ -46,6 +47,7 @@ describe('MiradorViewerComponent with search', () => { declarations: [MiradorViewerComponent], providers: [ { provide: BitstreamDataService, useValue: {} }, + { provide: BundleDataService, useValue: {} }, { provide: HostWindowService, useValue: mockHostWindowService } ], schemas: [NO_ERRORS_SCHEMA] @@ -108,6 +110,7 @@ describe('MiradorViewerComponent with multiple images', () => { declarations: [MiradorViewerComponent], providers: [ { provide: BitstreamDataService, useValue: {} }, + { provide: BundleDataService, useValue: {} }, { provide: HostWindowService, useValue: mockHostWindowService } ], schemas: [NO_ERRORS_SCHEMA] @@ -167,6 +170,7 @@ describe('MiradorViewerComponent with a single image', () => { declarations: [MiradorViewerComponent], providers: [ { provide: BitstreamDataService, useValue: {} }, + { provide: BundleDataService, useValue: {} }, { provide: HostWindowService, useValue: mockHostWindowService } ], schemas: [NO_ERRORS_SCHEMA] @@ -225,6 +229,7 @@ describe('MiradorViewerComponent in development mode', () => { set: { providers: [ { provide: MiradorViewerService, useValue: viewerService }, + { provide: BundleDataService, useValue: {} }, { provide: HostWindowService, useValue: mockHostWindowService } ] } diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.component.ts b/src/app/item-page/mirador-viewer/mirador-viewer.component.ts index 8876d2cea0..bcc50c5ed4 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.component.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.component.ts @@ -8,6 +8,7 @@ import { map, take } from 'rxjs/operators'; import { isPlatformBrowser } from '@angular/common'; import { MiradorViewerService } from './mirador-viewer.service'; import { HostWindowService, WidthCategory } from '../../shared/host-window.service'; +import { BundleDataService } from '../../core/data/bundle-data.service'; @Component({ selector: 'ds-mirador-viewer', @@ -55,6 +56,7 @@ export class MiradorViewerComponent implements OnInit { constructor(private sanitizer: DomSanitizer, private viewerService: MiradorViewerService, private bitstreamDataService: BitstreamDataService, + private bundleDataService: BundleDataService, private hostWindowService: HostWindowService, @Inject(PLATFORM_ID) private platformId: any) { } @@ -120,8 +122,12 @@ export class MiradorViewerComponent implements OnInit { }) ); } else { - // Sets the multi value based on the image count. - this.iframeViewerUrl = this.viewerService.getImageCount(this.object, this.bitstreamDataService).pipe( + // Sets the multi value based on the image count. Any count greater than 1 + // will add the right thumbnail navigation panel to the viewer. + this.iframeViewerUrl = this.viewerService.getImageCount( + this.object, + this.bitstreamDataService, + this.bundleDataService).pipe( map(c => { if (c > 1) { this.multi = true; diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts index 4bb095b89f..639c8b3d4e 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts @@ -2,13 +2,15 @@ import { Injectable, isDevMode } from '@angular/core'; import { Observable } from 'rxjs'; import { Item } from '../../core/shared/item.model'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; -import { last, map, switchMap } from 'rxjs/operators'; +import { filter, last, map, switchMap } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { Bitstream } from '../../core/shared/bitstream.model'; import { BitstreamFormat } from '../../core/shared/bitstream-format.model'; import { BitstreamDataService } from '../../core/data/bitstream-data.service'; import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { Bundle } from '../../core/shared/bundle.model'; +import { BundleDataService } from '../../core/data/bundle-data.service'; @Injectable() export class MiradorViewerService { @@ -26,32 +28,56 @@ export class MiradorViewerService { } /** - * Returns observable of the number of images in the ORIGINAL bundle + * Returns observable of the number of images found in eligible IIIF bundles. This checks + * only the first 5 bitstreams in each bundle since any count greater than one is + * enough to set the IIIF viewer to use the "multi" image layout. * @param item * @param bitstreamDataService + * @param bundleDataService */ - getImageCount(item: Item, bitstreamDataService: BitstreamDataService): Observable { + getImageCount(item: Item, bitstreamDataService: BitstreamDataService, bundleDataService: BundleDataService): + Observable { let count = 0; - return bitstreamDataService.findAllByItemAndBundleName(item, 'ORIGINAL', { - currentPage: 1, - elementsPerPage: 10 - }, true, true, ...this.LINKS_TO_FOLLOW) - .pipe( - getFirstCompletedRemoteData(), - map((bitstreamsRD: RemoteData>) => bitstreamsRD.payload), - map((paginatedList: PaginatedList) => paginatedList.page), - switchMap((bitstreams: Bitstream[]) => bitstreams), - switchMap((bitstream: Bitstream) => bitstream.format.pipe( - getFirstSucceededRemoteDataPayload(), - map((format: BitstreamFormat) => format) - )), - map((format: BitstreamFormat) => { - if (format.mimetype.includes('image')) { - count++; - } - return count; - }), - last() - ); + return bundleDataService.findAllByItem(item).pipe( + getFirstCompletedRemoteData(), + map((bundlesRD: RemoteData>) => bundlesRD.payload), + map((paginatedList: PaginatedList) => paginatedList.page), + switchMap((bundles: Bundle[]) => bundles), + filter((b: Bundle) => this.isIiifBundle(b.name)), + switchMap((bundle: Bundle) => { + return bitstreamDataService.findAllByItemAndBundleName(item, bundle.name, { + currentPage: 1, + elementsPerPage: 5 + }, true, true, ...this.LINKS_TO_FOLLOW).pipe( + getFirstCompletedRemoteData(), + map((bitstreamsRD: RemoteData>) => bitstreamsRD.payload), + map((paginatedList: PaginatedList) => paginatedList.page), + switchMap((bitstreams: Bitstream[]) => bitstreams), + switchMap((bitstream: Bitstream) => bitstream.format.pipe( + getFirstSucceededRemoteDataPayload(), + map((format: BitstreamFormat) => format) + )), + map((format: BitstreamFormat) => { + if (format.mimetype.includes('image')) { + count++; + } + return count; + }), + last() + ); + })); } + + isIiifBundle(bundleName: string): boolean { + return !( + bundleName === 'OtherContent' || + bundleName === 'LICENSE' || + bundleName === 'THUMBNAIL' || + bundleName === 'TEXT' || + bundleName === 'METADATA' || + bundleName === 'CC-LICENSE' || + bundleName === 'BRANDED_PREVIEW' + ); + } + } From cc3f570da7357c4cb9d5a3484119c9a6fa8aaad1 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Wed, 21 Sep 2022 12:16:22 -0700 Subject: [PATCH 23/65] Updated comments. --- .../mirador-viewer/mirador-viewer.component.ts | 12 ++++++------ .../mirador-viewer/mirador-viewer.service.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.component.ts b/src/app/item-page/mirador-viewer/mirador-viewer.component.ts index bcc50c5ed4..fee8046272 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.component.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.component.ts @@ -109,10 +109,10 @@ export class MiradorViewerComponent implements OnInit { this.notMobile = !(category === WidthCategory.XS || category === WidthCategory.SM); }); - // We need to set the multi property to true if the - // item is searchable or when the ORIGINAL bundle contains more - // than 1 image. (The multi property determines whether the - // Mirador side thumbnail navigation panel is shown.) + // Set the multi property. The default mirador configuration adds a right + // thumbnail navigation panel to the viewer when multi is 'true'. + + // Set the multi property to 'true' if the item is searchable. if (this.searchable) { this.multi = true; const observable = of(''); @@ -122,8 +122,8 @@ export class MiradorViewerComponent implements OnInit { }) ); } else { - // Sets the multi value based on the image count. Any count greater than 1 - // will add the right thumbnail navigation panel to the viewer. + // Set the multi property based on the image count in IIIF-eligible bundles. + // Any count greater than 1 sets the value to 'true'. this.iframeViewerUrl = this.viewerService.getImageCount( this.object, this.bitstreamDataService, diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts index 639c8b3d4e..68be928cfa 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts @@ -28,12 +28,12 @@ export class MiradorViewerService { } /** - * Returns observable of the number of images found in eligible IIIF bundles. This checks - * only the first 5 bitstreams in each bundle since any count greater than one is - * enough to set the IIIF viewer to use the "multi" image layout. + * Returns observable of the number of images found in eligible IIIF bundles. Checks + * the mimetype of the first 5 bitstreams in each bundle. * @param item * @param bitstreamDataService * @param bundleDataService + * @returns the total image count */ getImageCount(item: Item, bitstreamDataService: BitstreamDataService, bundleDataService: BundleDataService): Observable { From 9de6f38ea62a0d1aff76d7b5a4a0988e2460f632 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 23 Sep 2022 10:37:52 +0200 Subject: [PATCH 24/65] [CST-6153] fix merge --- src/assets/i18n/en.json5 | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bcb5615d1a..df5fd27062 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3091,8 +3091,6 @@ "profile.security.form.notifications.error.change-failed": "An error occurred while trying to change the password. Please check if the current password is correct.", - "profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.", - "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.", "profile.security.form.notifications.error.general": "Please fill required fields of security form.", From 5b1e0d3e0d3967b24d8cf236c6ee137ffc95a62b Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Fri, 23 Sep 2022 16:16:22 +0530 Subject: [PATCH 25/65] CST-6685 updated changes --- .../admin-import-batch-page/batch-import-page.component.spec.ts | 2 ++ .../admin-import-batch-page/batch-import-page.component.ts | 1 + src/app/core/data/processes/script-data.service.ts | 2 +- .../import-batch-selector/import-batch-selector.component.ts | 2 +- src/assets/i18n/en.json5 | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts index eecf05f543..6c9d84123b 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts @@ -101,6 +101,7 @@ describe('BatchImportPageComponent', () => { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), ]; + parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add', value: 'filename.zip' })); expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); }); it('success notification is shown', () => { @@ -121,6 +122,7 @@ describe('BatchImportPageComponent', () => { it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), + Object.assign(new ProcessParameter(), { name: '--add', value: 'filename.zip' }), Object.assign(new ProcessParameter(), { name: '-v', value: true }), ]; expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts index 2985aff3aa..8f49ed64b6 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts @@ -78,6 +78,7 @@ export class BatchImportPageComponent { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }), ]; + parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add', value: this.fileObject.name })); if (this.dso) { parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid })); } diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts index f362a9c676..58674d4005 100644 --- a/src/app/core/data/processes/script-data.service.ts +++ b/src/app/core/data/processes/script-data.service.ts @@ -24,7 +24,7 @@ import { dataService } from '../base/data-service.decorator'; export const METADATA_IMPORT_SCRIPT_NAME = 'metadata-import'; export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export'; -export const BATCH_IMPORT_SCRIPT_NAME = 'batch-import'; +export const BATCH_IMPORT_SCRIPT_NAME = 'import'; @Injectable() @dataService(SCRIPT) diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts index 2fdae23388..a88aeaff3c 100644 --- a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts @@ -18,7 +18,7 @@ import { Observable, of } from 'rxjs'; }) export class ImportBatchSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { objectType = DSpaceObjectType.DSPACEOBJECT; - selectorTypes = [DSpaceObjectType.COLLECTION, DSpaceObjectType.COMMUNITY]; + selectorTypes = [DSpaceObjectType.COLLECTION]; action = SelectorActionType.IMPORT_BATCH; /** * An event fired when the modal is closed diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4dd963a5fe..3e48254a6e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -554,7 +554,7 @@ "admin.metadata-import.page.help": "You can drop or browse CSV files that contain batch metadata operations on files here", - "admin.batch-import.page.help": "You can drop or browse ZIP files that contain batch operations on files here", + "admin.batch-import.page.help": "Select the Collection to import into. Then, drop or browse to a Simple Archive Format (SAF) zip file that includes the Items to import", "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", From 5e5e04cb1bd145fb56b8106d9a51036cdd8c733f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Fernandes?= Date: Sun, 25 Sep 2022 11:57:53 -0300 Subject: [PATCH 26/65] Updated the Brazilian Portuguese translation. --- src/assets/i18n/pt-BR.json5 | 6750 ++++++++++++++++++++++++----------- 1 file changed, 4621 insertions(+), 2129 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index fbe06877fb..f1774a06ae 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -1,5948 +1,8440 @@ { - + // "401.help": "You're not authorized to access this page. You can use the button below to get back to the home page.", - // TODO New key - Add a translation - "401.help": "You're not authorized to access this page. You can use the button below to get back to the home page.", - + "401.help": "Você não está autorizado a acessar esta página. Você pode usar o botão abaixo para voltar à página inicial.", + // "401.link.home-page": "Take me to the home page", - // TODO New key - Add a translation - "401.link.home-page": "Take me to the home page", - + "401.link.home-page": "Leve-me para a página inicial", + // "401.unauthorized": "unauthorized", - // TODO New key - Add a translation - "401.unauthorized": "unauthorized", - - - + "401.unauthorized": "não autorizado", + + + // "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", - // TODO New key - Add a translation - "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", - + "403.help": "Você não tem permissão para acessar esta página. Você pode usar o botão abaixo para voltar à página inicial.", + // "403.link.home-page": "Take me to the home page", - // TODO New key - Add a translation - "403.link.home-page": "Take me to the home page", - + "403.link.home-page": "Leve-me para a página inicial", + // "403.forbidden": "forbidden", - // TODO New key - Add a translation - "403.forbidden": "forbidden", - - - + "403.forbidden": "proibido", + + // "500.page-internal-server-error": "Service Unavailable", + "500.page-internal-server-error": "Serviço Indisponível", + + // "500.help": "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.", + "500.help": "O servidor está temporariamente impossibilitado de atender sua solicitação devido a um período de manutenção ou problemas no servidor. Por favor, tente novamente mais tarde.", + + // "500.link.home-page": "Take me to the home page", + "500.link.home-page": "Leve-me para a página inicial", + + // "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", "404.help": "Não pudemos encontrar a página pela qual procura. A página pode ter sido movida ou apagada. Você pode utilizar o botão abaixo para voltar a página inicial. ", - + // "404.link.home-page": "Take me to the home page", "404.link.home-page": "Leve-me a página inicial", - + // "404.page-not-found": "page not found", "404.page-not-found": "página não encontrada", - + + // "error-page.description.401": "unauthorized", + "error-page.description.401": "não autorizado", + + // "error-page.description.403": "forbidden", + "error-page.description.403": "proibido", + + // "error-page.description.500": "Service Unavailable", + "error-page.description.500": "Serviço Indisponível", + + // "error-page.description.404": "page not found", + "error-page.description.404": "pagína não encontrada", + + // "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", + // TODO New key - Add a translation + "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", + + // "access-status.embargo.listelement.badge": "Embargo", + // TODO New key - Add a translation + "access-status.embargo.listelement.badge": "Embargo", + + // "access-status.metadata.only.listelement.badge": "Metadata only", + // TODO New key - Add a translation + "access-status.metadata.only.listelement.badge": "Metadata only", + + // "access-status.open.access.listelement.badge": "Open Access", + // TODO New key - Add a translation + "access-status.open.access.listelement.badge": "Open Access", + + // "access-status.restricted.listelement.badge": "Restricted", + // TODO New key - Add a translation + "access-status.restricted.listelement.badge": "Restricted", + + // "access-status.unknown.listelement.badge": "Unknown", + // TODO New key - Add a translation + "access-status.unknown.listelement.badge": "Unknown", + // "admin.curation-tasks.breadcrumbs": "System curation tasks", // TODO New key - Add a translation "admin.curation-tasks.breadcrumbs": "System curation tasks", - + // "admin.curation-tasks.title": "System curation tasks", // TODO New key - Add a translation "admin.curation-tasks.title": "System curation tasks", - + // "admin.curation-tasks.header": "System curation tasks", // TODO New key - Add a translation "admin.curation-tasks.header": "System curation tasks", - + // "admin.registries.bitstream-formats.breadcrumbs": "Format registry", // TODO New key - Add a translation "admin.registries.bitstream-formats.breadcrumbs": "Format registry", - + // "admin.registries.bitstream-formats.create.breadcrumbs": "Bitstream format", // TODO New key - Add a translation "admin.registries.bitstream-formats.create.breadcrumbs": "Bitstream format", - + // "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.", "admin.registries.bitstream-formats.create.failure.content": "Um erro ocorreu durante a criação do novo formato de bitstream.", - + // "admin.registries.bitstream-formats.create.failure.head": "Failure", "admin.registries.bitstream-formats.create.failure.head": "Falha", - + // "admin.registries.bitstream-formats.create.head": "Create Bitstream format", "admin.registries.bitstream-formats.create.head": "Criar formato de Bitstream", - + // "admin.registries.bitstream-formats.create.new": "Add a new bitstream format", "admin.registries.bitstream-formats.create.new": "Adicionar um novo formato de bitstream", - + // "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.", "admin.registries.bitstream-formats.create.success.content": "O novo formato de bitstream foi criado com sucesso.", - + // "admin.registries.bitstream-formats.create.success.head": "Success", "admin.registries.bitstream-formats.create.success.head": "Sucesso", - + // "admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)", "admin.registries.bitstream-formats.delete.failure.amount": "Falha ao remover {{ amount }} formato(s)", - + // "admin.registries.bitstream-formats.delete.failure.head": "Failure", "admin.registries.bitstream-formats.delete.failure.head": "Falha", - + // "admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)", "admin.registries.bitstream-formats.delete.success.amount": "Removeu {{ amount }} formato(s) com sucesso", - + // "admin.registries.bitstream-formats.delete.success.head": "Success", "admin.registries.bitstream-formats.delete.success.head": "Sucesso", - + // "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.", "admin.registries.bitstream-formats.description": "Esta lista de formatos de bitstream provê informações sobre formatos conhecidos e seus níveis de suporte.", - + // "admin.registries.bitstream-formats.edit.breadcrumbs": "Bitstream format", // TODO New key - Add a translation "admin.registries.bitstream-formats.edit.breadcrumbs": "Bitstream format", - + // "admin.registries.bitstream-formats.edit.description.hint": "", "admin.registries.bitstream-formats.edit.description.hint": "", - + // "admin.registries.bitstream-formats.edit.description.label": "Description", "admin.registries.bitstream-formats.edit.description.label": "Descrição", - + // "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.", "admin.registries.bitstream-formats.edit.extensions.hint": "Extensões são extensões de arquivo que são usadas para identificar automaticamente o formato dos arquivo enviados. Você pode informar várias extensões para cada formato.", - + // "admin.registries.bitstream-formats.edit.extensions.label": "File extensions", "admin.registries.bitstream-formats.edit.extensions.label": "Extensões de arquivo", - + // "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extension without the dot", - // TODO Source message changed - Revise the translation - "admin.registries.bitstream-formats.edit.extensions.placeholder": "Informe uma extenção e arquivo sem o ponto", - + "admin.registries.bitstream-formats.edit.extensions.placeholder": "Informe uma extenção de arquivo sem o ponto", + // "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.", "admin.registries.bitstream-formats.edit.failure.content": "Ocorreu um erro ao editar o formato de bitstream.", - + // "admin.registries.bitstream-formats.edit.failure.head": "Failure", "admin.registries.bitstream-formats.edit.failure.head": "Falha", - + // "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}", "admin.registries.bitstream-formats.edit.head": "Formato de bitstream: {{ format }}", - + // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are hidden from the user, and used for administrative purposes.", "admin.registries.bitstream-formats.edit.internal.hint": "Formatos marcados como interno são ocultos para o usuário, e utilizados por motivos administrativos.", - + // "admin.registries.bitstream-formats.edit.internal.label": "Internal", "admin.registries.bitstream-formats.edit.internal.label": "Interno", - + // "admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.", "admin.registries.bitstream-formats.edit.mimetype.hint": "O MIME type associado à este formato não tem que ser único.", - + // "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type", "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type", - + // "admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)", "admin.registries.bitstream-formats.edit.shortDescription.hint": "Um nome único para este formato (exemplo. Microsoft Word XP ou Microsoft Word 2000)", - + // "admin.registries.bitstream-formats.edit.shortDescription.label": "Name", "admin.registries.bitstream-formats.edit.shortDescription.label": "Nome", - + // "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.", "admin.registries.bitstream-formats.edit.success.content": "O formato de bitstream foi editedo com sucesso.", - + // "admin.registries.bitstream-formats.edit.success.head": "Success", "admin.registries.bitstream-formats.edit.success.head": "Sucesso", - + // "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.", "admin.registries.bitstream-formats.edit.supportLevel.hint": "O nível de suporte que a sua instituição promete para este formato.", - + // "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level", "admin.registries.bitstream-formats.edit.supportLevel.label": "Nível de suporte", - + // "admin.registries.bitstream-formats.head": "Bitstream Format Registry", "admin.registries.bitstream-formats.head": "Registro de Formato de Bitstream", - + // "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.", "admin.registries.bitstream-formats.no-items": "Nenhum formato de bitstream para exibir.", - + // "admin.registries.bitstream-formats.table.delete": "Delete selected", "admin.registries.bitstream-formats.table.delete": "Apagar selecionado(s)", - + // "admin.registries.bitstream-formats.table.deselect-all": "Deselect all", "admin.registries.bitstream-formats.table.deselect-all": "Desselecionar todos", - + // "admin.registries.bitstream-formats.table.internal": "internal", "admin.registries.bitstream-formats.table.internal": "Interno", - + // "admin.registries.bitstream-formats.table.mimetype": "MIME Type", "admin.registries.bitstream-formats.table.mimetype": "MIME Type", - + // "admin.registries.bitstream-formats.table.name": "Name", "admin.registries.bitstream-formats.table.name": "Nome", - - // "admin.registries.bitstream-formats.table.return": "Return", + + // "admin.registries.bitstream-formats.table.return": "Back", + // TODO Source message changed - Revise the translation "admin.registries.bitstream-formats.table.return": "Voltar", - + // "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known", "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Conhecido", - + // "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported", "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Com suporte", - + // "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown", "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Desconhecido", - + // "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level", "admin.registries.bitstream-formats.table.supportLevel.head": "Nível de Suporte", - - // "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry", + + // "admin.registries.bitstream-formats.title": "Bitstream Format Registry", + // TODO Source message changed - Revise the translation "admin.registries.bitstream-formats.title": "DSpace Angular :: Registro de Formato de Bitstream", - - - + + + // "admin.registries.metadata.breadcrumbs": "Metadata registry", // TODO New key - Add a translation "admin.registries.metadata.breadcrumbs": "Metadata registry", - + // "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.", "admin.registries.metadata.description": "O registro de metadados mantém a lista de todos os campos de metadados disponíveis no repositório. Estes campos podêm ser divididos em multiplos esquemas. Entretanto, o DSpace requer esquemas de Dublin Core qualificados.", - + // "admin.registries.metadata.form.create": "Create metadata schema", "admin.registries.metadata.form.create": "Criar esquema de metadados", - + // "admin.registries.metadata.form.edit": "Edit metadata schema", "admin.registries.metadata.form.edit": "Editar esquema de metadados", - + // "admin.registries.metadata.form.name": "Name", "admin.registries.metadata.form.name": "Nome", - + // "admin.registries.metadata.form.namespace": "Namespace", "admin.registries.metadata.form.namespace": "Namespace", - + // "admin.registries.metadata.head": "Metadata Registry", "admin.registries.metadata.head": "Registro de Metadados", - + // "admin.registries.metadata.schemas.no-items": "No metadata schemas to show.", "admin.registries.metadata.schemas.no-items": "Nenhum esquema de metadados a mostrar.", - + // "admin.registries.metadata.schemas.table.delete": "Delete selected", "admin.registries.metadata.schemas.table.delete": "Apagar selecionado(s)", - + // "admin.registries.metadata.schemas.table.id": "ID", "admin.registries.metadata.schemas.table.id": "ID", - + // "admin.registries.metadata.schemas.table.name": "Name", "admin.registries.metadata.schemas.table.name": "Nome", - + // "admin.registries.metadata.schemas.table.namespace": "Namespace", "admin.registries.metadata.schemas.table.namespace": "Namespace", - - // "admin.registries.metadata.title": "DSpace Angular :: Metadata Registry", + + // "admin.registries.metadata.title": "Metadata Registry", + // TODO Source message changed - Revise the translation "admin.registries.metadata.title": "DSpace Angular :: Registro de Metadados", - - - + + + // "admin.registries.schema.breadcrumbs": "Metadata schema", // TODO New key - Add a translation "admin.registries.schema.breadcrumbs": "Metadata schema", - + // "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".", "admin.registries.schema.description": "Este é o esquema de metadados para \"{{namespace}}\".", - + // "admin.registries.schema.fields.head": "Schema metadata fields", "admin.registries.schema.fields.head": "Campos do esquema de metadados", - + // "admin.registries.schema.fields.no-items": "No metadata fields to show.", "admin.registries.schema.fields.no-items": "Nenhum campo de metadado a exibir.", - + // "admin.registries.schema.fields.table.delete": "Delete selected", "admin.registries.schema.fields.table.delete": "Apagar selecionado(s)", - + // "admin.registries.schema.fields.table.field": "Field", "admin.registries.schema.fields.table.field": "Campo", - + // "admin.registries.schema.fields.table.scopenote": "Scope Note", "admin.registries.schema.fields.table.scopenote": "Nota de escopo", - + // "admin.registries.schema.form.create": "Create metadata field", "admin.registries.schema.form.create": "Criar campo de metadado", - + // "admin.registries.schema.form.edit": "Edit metadata field", "admin.registries.schema.form.edit": "Editar campo de metadado", - + // "admin.registries.schema.form.element": "Element", "admin.registries.schema.form.element": "Elemento", - + // "admin.registries.schema.form.qualifier": "Qualifier", "admin.registries.schema.form.qualifier": "Qualificador", - + // "admin.registries.schema.form.scopenote": "Scope Note", "admin.registries.schema.form.scopenote": "Nota de Escopo", - + // "admin.registries.schema.head": "Metadata Schema", "admin.registries.schema.head": "Esquema de Metadados", - + // "admin.registries.schema.notification.created": "Successfully created metadata schema \"{{prefix}}\"", "admin.registries.schema.notification.created": "Criou o esquema de metadados \"{{prefix}}\" com sucesso", - + // "admin.registries.schema.notification.deleted.failure": "Failed to delete {{amount}} metadata schemas", "admin.registries.schema.notification.deleted.failure": "Falhou ao apagar {{amount}} esquema(s) de metadados", - + // "admin.registries.schema.notification.deleted.success": "Successfully deleted {{amount}} metadata schemas", "admin.registries.schema.notification.deleted.success": "Apagou {{amount}} esquema(s) de metadados com sucesso", - + // "admin.registries.schema.notification.edited": "Successfully edited metadata schema \"{{prefix}}\"", "admin.registries.schema.notification.edited": "Editou o esquema de metadados \"{{prefix}}\" com sucesso", - + // "admin.registries.schema.notification.failure": "Error", "admin.registries.schema.notification.failure": "Erro", - + // "admin.registries.schema.notification.field.created": "Successfully created metadata field \"{{field}}\"", "admin.registries.schema.notification.field.created": "Criou o campo de medado \"{{field}}\" com sucesso", - + // "admin.registries.schema.notification.field.deleted.failure": "Failed to delete {{amount}} metadata fields", "admin.registries.schema.notification.field.deleted.failure": "Falhou ao apagar {{amount}} campo(s) de metadados", - + // "admin.registries.schema.notification.field.deleted.success": "Successfully deleted {{amount}} metadata fields", "admin.registries.schema.notification.field.deleted.success": "Apagou {{amount}} campo(s) de metadados com sucesso", - + // "admin.registries.schema.notification.field.edited": "Successfully edited metadata field \"{{field}}\"", "admin.registries.schema.notification.field.edited": "Editou o campo de metadodo \"{{field}}\" com sucesso", - + // "admin.registries.schema.notification.success": "Success", "admin.registries.schema.notification.success": "Sucesso", - - // "admin.registries.schema.return": "Return", + + // "admin.registries.schema.return": "Back", + // TODO Source message changed - Revise the translation "admin.registries.schema.return": "Voltar", - - // "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry", + + // "admin.registries.schema.title": "Metadata Schema Registry", + // TODO Source message changed - Revise the translation "admin.registries.schema.title": "DSpace Angular :: Registro de Esquema de Metadados", - - - + + + // "admin.access-control.epeople.actions.delete": "Delete EPerson", // TODO New key - Add a translation "admin.access-control.epeople.actions.delete": "Delete EPerson", - + // "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", // TODO New key - Add a translation "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", - + // "admin.access-control.epeople.actions.reset": "Reset password", // TODO New key - Add a translation "admin.access-control.epeople.actions.reset": "Reset password", - + // "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", // TODO New key - Add a translation "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", - - // "admin.access-control.epeople.title": "DSpace Angular :: EPeople", + + // "admin.access-control.epeople.breadcrumbs": "EPeople", // TODO New key - Add a translation - "admin.access-control.epeople.title": "DSpace Angular :: EPeople", - + "admin.access-control.epeople.breadcrumbs": "EPeople", + + // "admin.access-control.epeople.title": "EPeople", + // TODO New key - Add a translation + "admin.access-control.epeople.title": "EPeople", + // "admin.access-control.epeople.head": "EPeople", // TODO New key - Add a translation "admin.access-control.epeople.head": "EPeople", - + // "admin.access-control.epeople.search.head": "Search", // TODO New key - Add a translation "admin.access-control.epeople.search.head": "Search", - + // "admin.access-control.epeople.button.see-all": "Browse All", "admin.access-control.epeople.button.see-all": "Pesquisar Todos", - + // "admin.access-control.epeople.search.scope.metadata": "Metadata", // TODO New key - Add a translation "admin.access-control.epeople.search.scope.metadata": "Metadata", - + // "admin.access-control.epeople.search.scope.email": "E-mail (exact)", - // TODO New key - Add a translation - "admin.access-control.epeople.search.scope.email": "E-mail (exact)", - + "admin.access-control.epeople.search.scope.email": "Email (exato)", + // "admin.access-control.epeople.search.button": "Search", + "admin.access-control.epeople.search.button": "Procurar", + + // "admin.access-control.epeople.search.placeholder": "Search people...", // TODO New key - Add a translation - "admin.access-control.epeople.search.button": "Search", - + "admin.access-control.epeople.search.placeholder": "Search people...", + // "admin.access-control.epeople.button.add": "Add EPerson", // TODO New key - Add a translation "admin.access-control.epeople.button.add": "Add EPerson", - + // "admin.access-control.epeople.table.id": "ID", // TODO New key - Add a translation "admin.access-control.epeople.table.id": "ID", - + // "admin.access-control.epeople.table.name": "Name", "admin.access-control.epeople.table.name": "Nome", - + // "admin.access-control.epeople.table.email": "E-mail (exact)", // TODO New key - Add a translation "admin.access-control.epeople.table.email": "E-mail (exact)", - + // "admin.access-control.epeople.table.edit": "Edit", - // TODO New key - Add a translation - "admin.access-control.epeople.table.edit": "Edit", - + "admin.access-control.epeople.table.edit": "Editar", + // "admin.access-control.epeople.table.edit.buttons.edit": "Edit \"{{name}}\"", + "admin.access-control.epeople.table.edit.buttons.edit": "Editar \"{{name}}\"", + + // "admin.access-control.epeople.table.edit.buttons.edit-disabled": "You are not authorized to edit this group", // TODO New key - Add a translation - "admin.access-control.epeople.table.edit.buttons.edit": "Edit \"{{name}}\"", - + "admin.access-control.epeople.table.edit.buttons.edit-disabled": "You are not authorized to edit this group", + // "admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"", - + // "admin.access-control.epeople.no-items": "No EPeople to show.", // TODO New key - Add a translation "admin.access-control.epeople.no-items": "No EPeople to show.", - + // "admin.access-control.epeople.form.create": "Create EPerson", // TODO New key - Add a translation "admin.access-control.epeople.form.create": "Create EPerson", - + // "admin.access-control.epeople.form.edit": "Edit EPerson", // TODO New key - Add a translation "admin.access-control.epeople.form.edit": "Edit EPerson", - + // "admin.access-control.epeople.form.firstName": "First name", // TODO New key - Add a translation "admin.access-control.epeople.form.firstName": "First name", - + // "admin.access-control.epeople.form.lastName": "Last name", // TODO New key - Add a translation "admin.access-control.epeople.form.lastName": "Last name", - + // "admin.access-control.epeople.form.email": "E-mail", // TODO New key - Add a translation "admin.access-control.epeople.form.email": "E-mail", - + // "admin.access-control.epeople.form.emailHint": "Must be valid e-mail address", // TODO New key - Add a translation "admin.access-control.epeople.form.emailHint": "Must be valid e-mail address", - + // "admin.access-control.epeople.form.canLogIn": "Can log in", // TODO New key - Add a translation "admin.access-control.epeople.form.canLogIn": "Can log in", - + // "admin.access-control.epeople.form.requireCertificate": "Requires certificate", // TODO New key - Add a translation "admin.access-control.epeople.form.requireCertificate": "Requires certificate", - + + // "admin.access-control.epeople.form.return": "Back", + // TODO New key - Add a translation + "admin.access-control.epeople.form.return": "Back", + // "admin.access-control.epeople.form.notification.created.success": "Successfully created EPerson \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.created.success": "Successfully created EPerson \"{{name}}\"", - + // "admin.access-control.epeople.form.notification.created.failure": "Failed to create EPerson \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.created.failure": "Failed to create EPerson \"{{name}}\"", - + // "admin.access-control.epeople.form.notification.created.failure.emailInUse": "Failed to create EPerson \"{{name}}\", email \"{{email}}\" already in use.", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.created.failure.emailInUse": "Failed to create EPerson \"{{name}}\", email \"{{email}}\" already in use.", - + // "admin.access-control.epeople.form.notification.edited.failure.emailInUse": "Failed to edit EPerson \"{{name}}\", email \"{{email}}\" already in use.", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.edited.failure.emailInUse": "Failed to edit EPerson \"{{name}}\", email \"{{email}}\" already in use.", - + // "admin.access-control.epeople.form.notification.edited.success": "Successfully edited EPerson \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.edited.success": "Successfully edited EPerson \"{{name}}\"", - + // "admin.access-control.epeople.form.notification.edited.failure": "Failed to edit EPerson \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.edited.failure": "Failed to edit EPerson \"{{name}}\"", - + // "admin.access-control.epeople.form.notification.deleted.success": "Successfully deleted EPerson \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.deleted.success": "Successfully deleted EPerson \"{{name}}\"", - + // "admin.access-control.epeople.form.notification.deleted.failure": "Failed to delete EPerson \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.form.notification.deleted.failure": "Failed to delete EPerson \"{{name}}\"", - + // "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Member of these groups:", // TODO New key - Add a translation "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Member of these groups:", - + // "admin.access-control.epeople.form.table.id": "ID", // TODO New key - Add a translation "admin.access-control.epeople.form.table.id": "ID", - + // "admin.access-control.epeople.form.table.name": "Name", "admin.access-control.epeople.form.table.name": "Nome", - + + // "admin.access-control.epeople.form.table.collectionOrCommunity": "Collection/Community", + // TODO New key - Add a translation + "admin.access-control.epeople.form.table.collectionOrCommunity": "Collection/Community", + // "admin.access-control.epeople.form.memberOfNoGroups": "This EPerson is not a member of any groups", // TODO New key - Add a translation "admin.access-control.epeople.form.memberOfNoGroups": "This EPerson is not a member of any groups", - + // "admin.access-control.epeople.form.goToGroups": "Add to groups", // TODO New key - Add a translation "admin.access-control.epeople.form.goToGroups": "Add to groups", - + // "admin.access-control.epeople.notification.deleted.failure": "Failed to delete EPerson: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.notification.deleted.failure": "Failed to delete EPerson: \"{{name}}\"", - + // "admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"", - - - - // "admin.access-control.groups.title": "DSpace Angular :: Groups", + + + + // "admin.access-control.groups.title": "Groups", // TODO New key - Add a translation - "admin.access-control.groups.title": "DSpace Angular :: Groups", - - // "admin.access-control.groups.title.singleGroup": "DSpace Angular :: Edit Group", + "admin.access-control.groups.title": "Groups", + + // "admin.access-control.groups.breadcrumbs": "Groups", // TODO New key - Add a translation - "admin.access-control.groups.title.singleGroup": "DSpace Angular :: Edit Group", - - // "admin.access-control.groups.title.addGroup": "DSpace Angular :: New Group", + "admin.access-control.groups.breadcrumbs": "Groups", + + // "admin.access-control.groups.singleGroup.breadcrumbs": "Edit Group", // TODO New key - Add a translation - "admin.access-control.groups.title.addGroup": "DSpace Angular :: New Group", - + "admin.access-control.groups.singleGroup.breadcrumbs": "Edit Group", + + // "admin.access-control.groups.title.singleGroup": "Edit Group", + // TODO New key - Add a translation + "admin.access-control.groups.title.singleGroup": "Edit Group", + + // "admin.access-control.groups.title.addGroup": "New Group", + // TODO New key - Add a translation + "admin.access-control.groups.title.addGroup": "New Group", + + // "admin.access-control.groups.addGroup.breadcrumbs": "New Group", + // TODO New key - Add a translation + "admin.access-control.groups.addGroup.breadcrumbs": "New Group", + // "admin.access-control.groups.head": "Groups", "admin.access-control.groups.head": "Grupos", - + // "admin.access-control.groups.button.add": "Add group", "admin.access-control.groups.button.add": "Adicionar grupo", - + // "admin.access-control.groups.search.head": "Search groups", "admin.access-control.groups.search.head": "Pesquisar grupos", - + // "admin.access-control.groups.button.see-all": "Browse all", // TODO New key - Add a translation "admin.access-control.groups.button.see-all": "Browse all", - + // "admin.access-control.groups.search.button": "Search", // TODO New key - Add a translation "admin.access-control.groups.search.button": "Search", - + + // "admin.access-control.groups.search.placeholder": "Search groups...", + // TODO New key - Add a translation + "admin.access-control.groups.search.placeholder": "Search groups...", + // "admin.access-control.groups.table.id": "ID", // TODO New key - Add a translation "admin.access-control.groups.table.id": "ID", - + // "admin.access-control.groups.table.name": "Name", "admin.access-control.groups.table.name": "Nome", - + + // "admin.access-control.groups.table.collectionOrCommunity": "Collection/Community", + // TODO New key - Add a translation + "admin.access-control.groups.table.collectionOrCommunity": "Collection/Community", + // "admin.access-control.groups.table.members": "Members", // TODO New key - Add a translation "admin.access-control.groups.table.members": "Members", - + // "admin.access-control.groups.table.edit": "Edit", "admin.access-control.groups.table.edit": "Editar", - + // "admin.access-control.groups.table.edit.buttons.edit": "Edit \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.table.edit.buttons.edit": "Edit \"{{name}}\"", - + // "admin.access-control.groups.table.edit.buttons.remove": "Delete \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.table.edit.buttons.remove": "Delete \"{{name}}\"", - + // "admin.access-control.groups.no-items": "No groups found with this in their name or this as UUID", // TODO New key - Add a translation "admin.access-control.groups.no-items": "No groups found with this in their name or this as UUID", - + // "admin.access-control.groups.notification.deleted.success": "Successfully deleted group \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.notification.deleted.success": "Successfully deleted group \"{{name}}\"", - + // "admin.access-control.groups.notification.deleted.failure.title": "Failed to delete group \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.notification.deleted.failure.title": "Failed to delete group \"{{name}}\"", - + // "admin.access-control.groups.notification.deleted.failure.content": "Cause: \"{{cause}}\"", // TODO New key - Add a translation "admin.access-control.groups.notification.deleted.failure.content": "Cause: \"{{cause}}\"", - - - + + + // "admin.access-control.groups.form.alert.permanent": "This group is permanent, so it can't be edited or deleted. You can still add and remove group members using this page.", // TODO New key - Add a translation "admin.access-control.groups.form.alert.permanent": "This group is permanent, so it can't be edited or deleted. You can still add and remove group members using this page.", - + // "admin.access-control.groups.form.alert.workflowGroup": "This group can’t be modified or deleted because it corresponds to a role in the submission and workflow process in the \"{{name}}\" {{comcol}}. You can delete it from the \"assign roles\" tab on the edit {{comcol}} page. You can still add and remove group members using this page.", // TODO New key - Add a translation "admin.access-control.groups.form.alert.workflowGroup": "This group can’t be modified or deleted because it corresponds to a role in the submission and workflow process in the \"{{name}}\" {{comcol}}. You can delete it from the \"assign roles\" tab on the edit {{comcol}} page. You can still add and remove group members using this page.", - + // "admin.access-control.groups.form.head.create": "Create group", "admin.access-control.groups.form.head.create": "Criar grupo", - + // "admin.access-control.groups.form.head.edit": "Edit group", "admin.access-control.groups.form.head.edit": "Editar grupo", - + // "admin.access-control.groups.form.groupName": "Group name", "admin.access-control.groups.form.groupName": "Nome do grupo", - + + // "admin.access-control.groups.form.groupCommunity": "Community or Collection", + "admin.access-control.groups.form.groupCommunity": "Comunidade ou Coleção", + // "admin.access-control.groups.form.groupDescription": "Description", "admin.access-control.groups.form.groupDescription": "Descrição", - + // "admin.access-control.groups.form.notification.created.success": "Successfully created Group \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.created.success": "Successfully created Group \"{{name}}\"", - + // "admin.access-control.groups.form.notification.created.failure": "Failed to create Group \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.created.failure": "Failed to create Group \"{{name}}\"", - + // "admin.access-control.groups.form.notification.created.failure.groupNameInUse": "Failed to create Group with name: \"{{name}}\", make sure the name is not already in use.", // TODO New key - Add a translation "admin.access-control.groups.form.notification.created.failure.groupNameInUse": "Failed to create Group with name: \"{{name}}\", make sure the name is not already in use.", - + // "admin.access-control.groups.form.notification.edited.failure": "Failed to edit Group \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.edited.failure": "Failed to edit Group \"{{name}}\"", - + // "admin.access-control.groups.form.notification.edited.failure.groupNameInUse": "Name \"{{name}}\" already in use!", // TODO New key - Add a translation "admin.access-control.groups.form.notification.edited.failure.groupNameInUse": "Name \"{{name}}\" already in use!", - + // "admin.access-control.groups.form.notification.edited.success": "Successfully edited Group \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.edited.success": "Successfully edited Group \"{{name}}\"", - + // "admin.access-control.groups.form.actions.delete": "Delete Group", // TODO New key - Add a translation "admin.access-control.groups.form.actions.delete": "Delete Group", - + // "admin.access-control.groups.form.delete-group.modal.header": "Delete Group \"{{ dsoName }}\"", // TODO New key - Add a translation "admin.access-control.groups.form.delete-group.modal.header": "Delete Group \"{{ dsoName }}\"", - + // "admin.access-control.groups.form.delete-group.modal.info": "Are you sure you want to delete Group \"{{ dsoName }}\"", // TODO New key - Add a translation "admin.access-control.groups.form.delete-group.modal.info": "Are you sure you want to delete Group \"{{ dsoName }}\"", - + // "admin.access-control.groups.form.delete-group.modal.cancel": "Cancel", // TODO New key - Add a translation "admin.access-control.groups.form.delete-group.modal.cancel": "Cancel", - + // "admin.access-control.groups.form.delete-group.modal.confirm": "Delete", // TODO New key - Add a translation "admin.access-control.groups.form.delete-group.modal.confirm": "Delete", - + // "admin.access-control.groups.form.notification.deleted.success": "Successfully deleted group \"{{ name }}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.deleted.success": "Successfully deleted group \"{{ name }}\"", - + // "admin.access-control.groups.form.notification.deleted.failure.title": "Failed to delete group \"{{ name }}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.deleted.failure.title": "Failed to delete group \"{{ name }}\"", - + // "admin.access-control.groups.form.notification.deleted.failure.content": "Cause: \"{{ cause }}\"", // TODO New key - Add a translation "admin.access-control.groups.form.notification.deleted.failure.content": "Cause: \"{{ cause }}\"", - + // "admin.access-control.groups.form.members-list.head": "EPeople", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.head": "EPeople", - + // "admin.access-control.groups.form.members-list.search.head": "Add EPeople", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.search.head": "Add EPeople", - + // "admin.access-control.groups.form.members-list.button.see-all": "Browse All", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.button.see-all": "Browse All", - + // "admin.access-control.groups.form.members-list.headMembers": "Current Members", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.headMembers": "Current Members", - + // "admin.access-control.groups.form.members-list.search.scope.metadata": "Metadata", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.search.scope.metadata": "Metadata", - + // "admin.access-control.groups.form.members-list.search.scope.email": "E-mail (exact)", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.search.scope.email": "E-mail (exact)", - + // "admin.access-control.groups.form.members-list.search.button": "Search", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.search.button": "Search", - + // "admin.access-control.groups.form.members-list.table.id": "ID", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.table.id": "ID", - + // "admin.access-control.groups.form.members-list.table.name": "Name", "admin.access-control.groups.form.members-list.table.name": "Nome", - + + // "admin.access-control.groups.form.members-list.table.identity": "Identity", + // TODO New key - Add a translation + "admin.access-control.groups.form.members-list.table.identity": "Identity", + + // "admin.access-control.groups.form.members-list.table.email": "Email", + // TODO New key - Add a translation + "admin.access-control.groups.form.members-list.table.email": "Email", + + // "admin.access-control.groups.form.members-list.table.netid": "NetID", + // TODO New key - Add a translation + "admin.access-control.groups.form.members-list.table.netid": "NetID", + // "admin.access-control.groups.form.members-list.table.edit": "Remove / Add", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.table.edit": "Remove / Add", - + // "admin.access-control.groups.form.members-list.table.edit.buttons.remove": "Remove member with name \"{{name}}\"", "admin.access-control.groups.form.members-list.table.edit.buttons.remove": "Remover o membro com nome \"{{name}}\"", - + // "admin.access-control.groups.form.members-list.notification.success.addMember": "Successfully added member: \"{{name}}\"", "admin.access-control.groups.form.members-list.notification.success.addMember": "Membro adicionado com sucesso: \"{{name}}\"", - + // "admin.access-control.groups.form.members-list.notification.failure.addMember": "Failed to add member: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.notification.failure.addMember": "Failed to add member: \"{{name}}\"", - + // "admin.access-control.groups.form.members-list.notification.success.deleteMember": "Successfully deleted member: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.notification.success.deleteMember": "Successfully deleted member: \"{{name}}\"", - + // "admin.access-control.groups.form.members-list.notification.failure.deleteMember": "Failed to delete member: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.notification.failure.deleteMember": "Failed to delete member: \"{{name}}\"", - + // "admin.access-control.groups.form.members-list.table.edit.buttons.add": "Add member with name \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.table.edit.buttons.add": "Add member with name \"{{name}}\"", - + // "admin.access-control.groups.form.members-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", - + // "admin.access-control.groups.form.members-list.no-members-yet": "No members in group yet, search and add.", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.no-members-yet": "No members in group yet, search and add.", - + // "admin.access-control.groups.form.members-list.no-items": "No EPeople found in that search", // TODO New key - Add a translation "admin.access-control.groups.form.members-list.no-items": "No EPeople found in that search", - + // "admin.access-control.groups.form.subgroups-list.notification.failure": "Something went wrong: \"{{cause}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.failure": "Something went wrong: \"{{cause}}\"", - + // "admin.access-control.groups.form.subgroups-list.head": "Groups", "admin.access-control.groups.form.subgroups-list.head": "Grupos", - + // "admin.access-control.groups.form.subgroups-list.search.head": "Add Subgroup", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.search.head": "Add Subgroup", - + // "admin.access-control.groups.form.subgroups-list.button.see-all": "Browse All", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.button.see-all": "Browse All", - + // "admin.access-control.groups.form.subgroups-list.headSubgroups": "Current Subgroups", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.headSubgroups": "Current Subgroups", - + // "admin.access-control.groups.form.subgroups-list.search.button": "Search", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.search.button": "Search", - + // "admin.access-control.groups.form.subgroups-list.table.id": "ID", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.table.id": "ID", - + // "admin.access-control.groups.form.subgroups-list.table.name": "Name", "admin.access-control.groups.form.subgroups-list.table.name": "Nome", - + + // "admin.access-control.groups.form.subgroups-list.table.collectionOrCommunity": "Collection/Community", + // TODO New key - Add a translation + "admin.access-control.groups.form.subgroups-list.table.collectionOrCommunity": "Collection/Community", + // "admin.access-control.groups.form.subgroups-list.table.edit": "Remove / Add", "admin.access-control.groups.form.subgroups-list.table.edit": "Remover / Adicionar", - + // "admin.access-control.groups.form.subgroups-list.table.edit.buttons.remove": "Remove subgroup with name \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.table.edit.buttons.remove": "Remove subgroup with name \"{{name}}\"", - + // "admin.access-control.groups.form.subgroups-list.table.edit.buttons.add": "Add subgroup with name \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.table.edit.buttons.add": "Add subgroup with name \"{{name}}\"", - + // "admin.access-control.groups.form.subgroups-list.table.edit.currentGroup": "Current group", "admin.access-control.groups.form.subgroups-list.table.edit.currentGroup": "Grupo atual", - + // "admin.access-control.groups.form.subgroups-list.notification.success.addSubgroup": "Successfully added subgroup: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.success.addSubgroup": "Successfully added subgroup: \"{{name}}\"", - + // "admin.access-control.groups.form.subgroups-list.notification.failure.addSubgroup": "Failed to add subgroup: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.failure.addSubgroup": "Failed to add subgroup: \"{{name}}\"", - + // "admin.access-control.groups.form.subgroups-list.notification.success.deleteSubgroup": "Successfully deleted subgroup: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.success.deleteSubgroup": "Successfully deleted subgroup: \"{{name}}\"", - + // "admin.access-control.groups.form.subgroups-list.notification.failure.deleteSubgroup": "Failed to delete subgroup: \"{{name}}\"", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.failure.deleteSubgroup": "Failed to delete subgroup: \"{{name}}\"", - + // "admin.access-control.groups.form.subgroups-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", - + // "admin.access-control.groups.form.subgroups-list.notification.failure.subgroupToAddIsActiveGroup": "This is the current group, can't be added.", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.notification.failure.subgroupToAddIsActiveGroup": "This is the current group, can't be added.", - + // "admin.access-control.groups.form.subgroups-list.no-items": "No groups found with this in their name or this as UUID", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.no-items": "No groups found with this in their name or this as UUID", - + // "admin.access-control.groups.form.subgroups-list.no-subgroups-yet": "No subgroups in group yet.", // TODO New key - Add a translation "admin.access-control.groups.form.subgroups-list.no-subgroups-yet": "No subgroups in group yet.", - - // "admin.access-control.groups.form.return": "Return to groups", + + // "admin.access-control.groups.form.return": "Back", + // TODO Source message changed - Revise the translation "admin.access-control.groups.form.return": "Retornar aos grupos", - - - + + + // "admin.search.breadcrumbs": "Administrative Search", // TODO New key - Add a translation "admin.search.breadcrumbs": "Administrative Search", - + // "admin.search.collection.edit": "Edit", "admin.search.collection.edit": "Editar", - + // "admin.search.community.edit": "Edit", "admin.search.community.edit": "Editar", - + // "admin.search.item.delete": "Delete", // TODO New key - Add a translation "admin.search.item.delete": "Delete", - + // "admin.search.item.edit": "Edit", "admin.search.item.edit": "Editar", - - // "admin.search.item.make-private": "Make Private", + + // "admin.search.item.make-private": "Make non-discoverable", + // TODO Source message changed - Revise the translation "admin.search.item.make-private": "Tornar Privado", - - // "admin.search.item.make-public": "Make Public", + + // "admin.search.item.make-public": "Make discoverable", + // TODO Source message changed - Revise the translation "admin.search.item.make-public": "Tornar Público", - + // "admin.search.item.move": "Move", "admin.search.item.move": "Mover", - + // "admin.search.item.reinstate": "Reinstate", // TODO New key - Add a translation "admin.search.item.reinstate": "Reinstate", - + // "admin.search.item.withdraw": "Withdraw", // TODO New key - Add a translation "admin.search.item.withdraw": "Withdraw", - + // "admin.search.title": "Administrative Search", "admin.search.title": "Pesquisa Administrativa", - + // "administrativeView.search.results.head": "Administrative Search", // TODO New key - Add a translation "administrativeView.search.results.head": "Administrative Search", - - - - + + + + // "admin.workflow.breadcrumbs": "Administer Workflow", // TODO New key - Add a translation "admin.workflow.breadcrumbs": "Administer Workflow", - + // "admin.workflow.title": "Administer Workflow", // TODO New key - Add a translation "admin.workflow.title": "Administer Workflow", - + // "admin.workflow.item.workflow": "Workflow", // TODO New key - Add a translation "admin.workflow.item.workflow": "Workflow", - + // "admin.workflow.item.delete": "Delete", // TODO New key - Add a translation "admin.workflow.item.delete": "Delete", - + // "admin.workflow.item.send-back": "Send back", // TODO New key - Add a translation "admin.workflow.item.send-back": "Send back", - - - + + + // "admin.metadata-import.breadcrumbs": "Import Metadata", // TODO New key - Add a translation "admin.metadata-import.breadcrumbs": "Import Metadata", - + // "admin.metadata-import.title": "Import Metadata", // TODO New key - Add a translation "admin.metadata-import.title": "Import Metadata", - + // "admin.metadata-import.page.header": "Import Metadata", // TODO New key - Add a translation "admin.metadata-import.page.header": "Import Metadata", - + // "admin.metadata-import.page.help": "You can drop or browse CSV files that contain batch metadata operations on files here", // TODO New key - Add a translation "admin.metadata-import.page.help": "You can drop or browse CSV files that contain batch metadata operations on files here", - + // "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", // TODO New key - Add a translation "admin.metadata-import.page.dropMsg": "Drop a metadata CSV to import", - + // "admin.metadata-import.page.dropMsgReplace": "Drop to replace the metadata CSV to import", // TODO New key - Add a translation "admin.metadata-import.page.dropMsgReplace": "Drop to replace the metadata CSV to import", - - // "admin.metadata-import.page.button.return": "Return", + + // "admin.metadata-import.page.button.return": "Back", // TODO New key - Add a translation - "admin.metadata-import.page.button.return": "Return", - + "admin.metadata-import.page.button.return": "Back", + // "admin.metadata-import.page.button.proceed": "Proceed", // TODO New key - Add a translation "admin.metadata-import.page.button.proceed": "Proceed", - + // "admin.metadata-import.page.error.addFile": "Select file first!", // TODO New key - Add a translation "admin.metadata-import.page.error.addFile": "Select file first!", - - - - + + // "admin.metadata-import.page.validateOnly": "Validate Only", + // TODO New key - Add a translation + "admin.metadata-import.page.validateOnly": "Validate Only", + + // "admin.metadata-import.page.validateOnly.hint": "When selected, the uploaded CSV will be validated. You will receive a report of detected changes, but no changes will be saved.", + // TODO New key - Add a translation + "admin.metadata-import.page.validateOnly.hint": "When selected, the uploaded CSV will be validated. You will receive a report of detected changes, but no changes will be saved.", + + + + // "auth.errors.invalid-user": "Invalid email address or password.", "auth.errors.invalid-user": "Endereço de email ou senha inválidos.", - + // "auth.messages.expired": "Your session has expired. Please log in again.", "auth.messages.expired": "Sua sessão expirou. Por favor entre novamente.", - - - + + // "auth.messages.token-refresh-failed": "Refreshing your session token failed. Please log in again.", + // TODO New key - Add a translation + "auth.messages.token-refresh-failed": "Refreshing your session token failed. Please log in again.", + + + + // "bitstream.download.page": "Now downloading {{bitstream}}..." , + // TODO New key - Add a translation + "bitstream.download.page": "Now downloading {{bitstream}}..." , + + // "bitstream.download.page.back": "Back" , + // TODO New key - Add a translation + "bitstream.download.page.back": "Back" , + + + // "bitstream.edit.authorizations.link": "Edit bitstream's Policies", + // TODO New key - Add a translation + "bitstream.edit.authorizations.link": "Edit bitstream's Policies", + + // "bitstream.edit.authorizations.title": "Edit bitstream's Policies", + // TODO New key - Add a translation + "bitstream.edit.authorizations.title": "Edit bitstream's Policies", + + // "bitstream.edit.return": "Back", + // TODO New key - Add a translation + "bitstream.edit.return": "Back", + // "bitstream.edit.bitstream": "Bitstream: ", // TODO New key - Add a translation "bitstream.edit.bitstream": "Bitstream: ", - + // "bitstream.edit.form.description.hint": "Optionally, provide a brief description of the file, for example \"Main article\" or \"Experiment data readings\".", // TODO New key - Add a translation "bitstream.edit.form.description.hint": "Optionally, provide a brief description of the file, for example \"Main article\" or \"Experiment data readings\".", - + // "bitstream.edit.form.description.label": "Description", "bitstream.edit.form.description.label": "Descrição", - + // "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", // TODO New key - Add a translation "bitstream.edit.form.embargo.hint": "The first day from which access is allowed. This date cannot be modified on this form. To set an embargo date for a bitstream, go to the Item Status tab, click Authorizations..., create or edit the bitstream's READ policy, and set the Start Date as desired.", - + // "bitstream.edit.form.embargo.label": "Embargo until specific date", // TODO New key - Add a translation "bitstream.edit.form.embargo.label": "Embargo until specific date", - + // "bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.", // TODO New key - Add a translation "bitstream.edit.form.fileName.hint": "Change the filename for the bitstream. Note that this will change the display bitstream URL, but old links will still resolve as long as the sequence ID does not change.", - + // "bitstream.edit.form.fileName.label": "Filename", "bitstream.edit.form.fileName.label": "Nome do arquivo", - + // "bitstream.edit.form.newFormat.label": "Describe new format", "bitstream.edit.form.newFormat.label": "Descreva o novo formato", - + // "bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"ACMESoft SuperApp version 1.5\").", // TODO New key - Add a translation "bitstream.edit.form.newFormat.hint": "The application you used to create the file, and the version number (for example, \"ACMESoft SuperApp version 1.5\").", - + // "bitstream.edit.form.primaryBitstream.label": "Primary bitstream", // TODO New key - Add a translation "bitstream.edit.form.primaryBitstream.label": "Primary bitstream", - + // "bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".", // TODO New key - Add a translation "bitstream.edit.form.selectedFormat.hint": "If the format is not in the above list, select \"format not in list\" above and describe it under \"Describe new format\".", - + // "bitstream.edit.form.selectedFormat.label": "Selected Format", "bitstream.edit.form.selectedFormat.label": "Formato Selecionado", - + // "bitstream.edit.form.selectedFormat.unknown": "Format not in list", // TODO New key - Add a translation "bitstream.edit.form.selectedFormat.unknown": "Format not in list", - + // "bitstream.edit.notifications.error.format.title": "An error occurred saving the bitstream's format", // TODO New key - Add a translation "bitstream.edit.notifications.error.format.title": "An error occurred saving the bitstream's format", - + + // "bitstream.edit.form.iiifLabel.label": "IIIF Label", + // TODO New key - Add a translation + "bitstream.edit.form.iiifLabel.label": "IIIF Label", + + // "bitstream.edit.form.iiifLabel.hint": "Canvas label for this image. If not provided default label will be used.", + // TODO New key - Add a translation + "bitstream.edit.form.iiifLabel.hint": "Canvas label for this image. If not provided default label will be used.", + + // "bitstream.edit.form.iiifToc.label": "IIIF Table of Contents", + // TODO New key - Add a translation + "bitstream.edit.form.iiifToc.label": "IIIF Table of Contents", + + // "bitstream.edit.form.iiifToc.hint": "Adding text here makes this the start of a new table of contents range.", + // TODO New key - Add a translation + "bitstream.edit.form.iiifToc.hint": "Adding text here makes this the start of a new table of contents range.", + + // "bitstream.edit.form.iiifWidth.label": "IIIF Canvas Width", + // TODO New key - Add a translation + "bitstream.edit.form.iiifWidth.label": "IIIF Canvas Width", + + // "bitstream.edit.form.iiifWidth.hint": "The canvas width should usually match the image width.", + // TODO New key - Add a translation + "bitstream.edit.form.iiifWidth.hint": "The canvas width should usually match the image width.", + + // "bitstream.edit.form.iiifHeight.label": "IIIF Canvas Height", + // TODO New key - Add a translation + "bitstream.edit.form.iiifHeight.label": "IIIF Canvas Height", + + // "bitstream.edit.form.iiifHeight.hint": "The canvas height should usually match the image height.", + // TODO New key - Add a translation + "bitstream.edit.form.iiifHeight.hint": "The canvas height should usually match the image height.", + + // "bitstream.edit.notifications.saved.content": "Your changes to this bitstream were saved.", // TODO New key - Add a translation "bitstream.edit.notifications.saved.content": "Your changes to this bitstream were saved.", - + // "bitstream.edit.notifications.saved.title": "Bitstream saved", // TODO New key - Add a translation "bitstream.edit.notifications.saved.title": "Bitstream saved", - + // "bitstream.edit.title": "Edit bitstream", // TODO New key - Add a translation "bitstream.edit.title": "Edit bitstream", - - - + + // "bitstream-request-a-copy.alert.canDownload1": "You already have access to this file. If you want to download the file, click ", + // TODO New key - Add a translation + "bitstream-request-a-copy.alert.canDownload1": "You already have access to this file. If you want to download the file, click ", + + // "bitstream-request-a-copy.alert.canDownload2": "here", + // TODO New key - Add a translation + "bitstream-request-a-copy.alert.canDownload2": "here", + + // "bitstream-request-a-copy.header": "Request a copy of the file", + // TODO New key - Add a translation + "bitstream-request-a-copy.header": "Request a copy of the file", + + // "bitstream-request-a-copy.intro": "Enter the following information to request a copy for the following item: ", + // TODO New key - Add a translation + "bitstream-request-a-copy.intro": "Enter the following information to request a copy for the following item: ", + + // "bitstream-request-a-copy.intro.bitstream.one": "Requesting the following file: ", + // TODO New key - Add a translation + "bitstream-request-a-copy.intro.bitstream.one": "Requesting the following file: ", + // "bitstream-request-a-copy.intro.bitstream.all": "Requesting all files. ", + // TODO New key - Add a translation + "bitstream-request-a-copy.intro.bitstream.all": "Requesting all files. ", + + // "bitstream-request-a-copy.name.label": "Name *", + // TODO New key - Add a translation + "bitstream-request-a-copy.name.label": "Name *", + + // "bitstream-request-a-copy.name.error": "The name is required", + // TODO New key - Add a translation + "bitstream-request-a-copy.name.error": "The name is required", + + // "bitstream-request-a-copy.email.label": "Your e-mail address *", + // TODO New key - Add a translation + "bitstream-request-a-copy.email.label": "Your e-mail address *", + + // "bitstream-request-a-copy.email.hint": "This email address is used for sending the file.", + // TODO New key - Add a translation + "bitstream-request-a-copy.email.hint": "This email address is used for sending the file.", + + // "bitstream-request-a-copy.email.error": "Please enter a valid email address.", + // TODO New key - Add a translation + "bitstream-request-a-copy.email.error": "Please enter a valid email address.", + + // "bitstream-request-a-copy.allfiles.label": "Files", + // TODO New key - Add a translation + "bitstream-request-a-copy.allfiles.label": "Files", + + // "bitstream-request-a-copy.files-all-false.label": "Only the requested file", + // TODO New key - Add a translation + "bitstream-request-a-copy.files-all-false.label": "Only the requested file", + + // "bitstream-request-a-copy.files-all-true.label": "All files (of this item) in restricted access", + // TODO New key - Add a translation + "bitstream-request-a-copy.files-all-true.label": "All files (of this item) in restricted access", + + // "bitstream-request-a-copy.message.label": "Message", + // TODO New key - Add a translation + "bitstream-request-a-copy.message.label": "Message", + + // "bitstream-request-a-copy.return": "Back", + // TODO New key - Add a translation + "bitstream-request-a-copy.return": "Back", + + // "bitstream-request-a-copy.submit": "Request copy", + // TODO New key - Add a translation + "bitstream-request-a-copy.submit": "Request copy", + + // "bitstream-request-a-copy.submit.success": "The item request was submitted successfully.", + // TODO New key - Add a translation + "bitstream-request-a-copy.submit.success": "The item request was submitted successfully.", + + // "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.", + // TODO New key - Add a translation + "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.", + + + + // "browse.back.all-results": "All browse results", + // TODO New key - Add a translation + "browse.back.all-results": "All browse results", + // "browse.comcol.by.author": "By Author", "browse.comcol.by.author": "Por Autor", - + // "browse.comcol.by.dateissued": "By Issue Date", "browse.comcol.by.dateissued": "Por Data de Publicação", - + // "browse.comcol.by.subject": "By Subject", "browse.comcol.by.subject": "Por Assunto", - + // "browse.comcol.by.title": "By Title", "browse.comcol.by.title": "Por Título", - + // "browse.comcol.head": "Browse", "browse.comcol.head": "Navegar", - + // "browse.empty": "No items to show.", "browse.empty": "Sem itens a exibir.", - + // "browse.metadata.author": "Author", "browse.metadata.author": "Autor", - + // "browse.metadata.dateissued": "Issue Date", "browse.metadata.dateissued": "Data de Publicação", - + // "browse.metadata.subject": "Subject", "browse.metadata.subject": "Assunto", - + // "browse.metadata.title": "Title", "browse.metadata.title": "Título", - + // "browse.metadata.author.breadcrumbs": "Browse by Author", "browse.metadata.author.breadcrumbs": "Pesquisar por Autor", - + // "browse.metadata.dateissued.breadcrumbs": "Browse by Date", "browse.metadata.dateissued.breadcrumbs": "Pesquisar por Data", - + // "browse.metadata.subject.breadcrumbs": "Browse by Subject", "browse.metadata.subject.breadcrumbs": "Pesquisar por Assunto", - + // "browse.metadata.title.breadcrumbs": "Browse by Title", "browse.metadata.title.breadcrumbs": "Pesquisar por Título", - + + // "pagination.next.button": "Next", + // TODO New key - Add a translation + "pagination.next.button": "Next", + + // "pagination.previous.button": "Previous", + // TODO New key - Add a translation + "pagination.previous.button": "Previous", + + // "pagination.next.button.disabled.tooltip": "No more pages of results", + // TODO New key - Add a translation + "pagination.next.button.disabled.tooltip": "No more pages of results", + + // "browse.startsWith": ", starting with {{ startsWith }}", + // TODO New key - Add a translation + "browse.startsWith": ", starting with {{ startsWith }}", + // "browse.startsWith.choose_start": "(Choose start)", "browse.startsWith.choose_start": "(Escolha o início)", - + // "browse.startsWith.choose_year": "(Choose year)", "browse.startsWith.choose_year": "(Escolha o ano)", - - // "browse.startsWith.jump": "Jump to a point in the index:", + + // "browse.startsWith.choose_year.label": "Choose the issue year", + // TODO New key - Add a translation + "browse.startsWith.choose_year.label": "Choose the issue year", + + // "browse.startsWith.jump": "Filter results by year or month", + // TODO Source message changed - Revise the translation "browse.startsWith.jump": "Pular para um ponto do índice:", - + // "browse.startsWith.months.april": "April", "browse.startsWith.months.april": "Abril", - + // "browse.startsWith.months.august": "August", "browse.startsWith.months.august": "Agosto", - + // "browse.startsWith.months.december": "December", "browse.startsWith.months.december": "Dezembro", - + // "browse.startsWith.months.february": "February", "browse.startsWith.months.february": "Fevereiro", - + // "browse.startsWith.months.january": "January", "browse.startsWith.months.january": "Janeiro", - + // "browse.startsWith.months.july": "July", "browse.startsWith.months.july": "Julho", - + // "browse.startsWith.months.june": "June", "browse.startsWith.months.june": "Junho", - + // "browse.startsWith.months.march": "March", "browse.startsWith.months.march": "Março", - + // "browse.startsWith.months.may": "May", "browse.startsWith.months.may": "Maio", - + // "browse.startsWith.months.none": "(Choose month)", "browse.startsWith.months.none": "(escolha o mês)", - + + // "browse.startsWith.months.none.label": "Choose the issue month", + // TODO New key - Add a translation + "browse.startsWith.months.none.label": "Choose the issue month", + // "browse.startsWith.months.november": "November", "browse.startsWith.months.november": "Novembro", - + // "browse.startsWith.months.october": "October", "browse.startsWith.months.october": "Outubro", - + // "browse.startsWith.months.september": "September", "browse.startsWith.months.september": "Setembro", - - // "browse.startsWith.submit": "Go", + + // "browse.startsWith.submit": "Browse", + // TODO Source message changed - Revise the translation "browse.startsWith.submit": "Ir", - - // "browse.startsWith.type_date": "Or type in a date (year-month):", + + // "browse.startsWith.type_date": "Filter results by date", + // TODO Source message changed - Revise the translation "browse.startsWith.type_date": "Ou informe uma data (ano-mês):", - - // "browse.startsWith.type_text": "Or enter first few letters:", + + // "browse.startsWith.type_date.label": "Or type in a date (year-month) and click on the Browse button", + // TODO New key - Add a translation + "browse.startsWith.type_date.label": "Or type in a date (year-month) and click on the Browse button", + + // "browse.startsWith.type_text": "Filter results by typing the first few letters", + // TODO Source message changed - Revise the translation "browse.startsWith.type_text": "Ou informe as primeiras letras:", - - // "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}", + + // "browse.title": "Browsing {{ collection }} by {{ field }}{{ startsWith }} {{ value }}", + // TODO Source message changed - Revise the translation "browse.title": "Navegando {{ collection }} por {{ field }} {{ value }}", - - + + // "browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}", + // TODO New key - Add a translation + "browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}", + + // "chips.remove": "Remove chip", "chips.remove": "Remover chip", - - - + + + // "collection.create.head": "Create a Collection", "collection.create.head": "Criar uma coleção", - + // "collection.create.notifications.success": "Successfully created the Collection", // TODO New key - Add a translation "collection.create.notifications.success": "Successfully created the Collection", - + // "collection.create.sub-head": "Create a Collection for Community {{ parent }}", "collection.create.sub-head": "Criar uma Coleção na Comunidade {{ parent }}", - + // "collection.curate.header": "Curate Collection: {{collection}}", // TODO New key - Add a translation "collection.curate.header": "Curate Collection: {{collection}}", - + // "collection.delete.cancel": "Cancel", "collection.delete.cancel": "Cancelar", - + // "collection.delete.confirm": "Confirm", "collection.delete.confirm": "Confirmar", - + + // "collection.delete.processing": "Deleting", + // TODO New key - Add a translation + "collection.delete.processing": "Deleting", + // "collection.delete.head": "Delete Collection", "collection.delete.head": "Apagar Coleção", - + // "collection.delete.notification.fail": "Collection could not be deleted", "collection.delete.notification.fail": "Coleção não pôde ser apagada", - + // "collection.delete.notification.success": "Successfully deleted collection", "collection.delete.notification.success": "Apagou a coleção com sucesso", - + // "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"", "collection.delete.text": "Você tem certeza que deseja apagar a coleção \"{{ dso }}?\"", - - - + + + // "collection.edit.delete": "Delete this collection", "collection.edit.delete": "Apagar esta coleção", - + // "collection.edit.head": "Edit Collection", "collection.edit.head": "Editar Coleção", - + // "collection.edit.breadcrumbs": "Edit Collection", // TODO New key - Add a translation "collection.edit.breadcrumbs": "Edit Collection", - - - + + + // "collection.edit.tabs.mapper.head": "Item Mapper", // TODO New key - Add a translation "collection.edit.tabs.mapper.head": "Item Mapper", - + // "collection.edit.tabs.item-mapper.title": "Collection Edit - Item Mapper", // TODO New key - Add a translation "collection.edit.tabs.item-mapper.title": "Collection Edit - Item Mapper", - + // "collection.edit.item-mapper.cancel": "Cancel", "collection.edit.item-mapper.cancel": "Cancelar", - + // "collection.edit.item-mapper.collection": "Collection: \"{{name}}\"", "collection.edit.item-mapper.collection": "Coleção: \"{{name}}\"", - + // "collection.edit.item-mapper.confirm": "Map selected items", "collection.edit.item-mapper.confirm": "Mapear itens selecionados", - + // "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.", "collection.edit.item-mapper.description": "Esta é a ferramenta de mapeação de itens que permite administradores de coleções a mapear itens de outras coleções nesta. VoCẽ pode busca-los em outras coleções para mapeá-los, ou navegar na lista dos itens atualmente mapeados.", - + // "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections", "collection.edit.item-mapper.head": "Mapeador de Itens - Mapear itens em Outras Coleções", - + // "collection.edit.item-mapper.no-search": "Please enter a query to search", "collection.edit.item-mapper.no-search": "Por favor informe uma consulta para buscar", - + // "collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.", "collection.edit.item-mapper.notifications.map.error.content": "Ocorreu erros ao mapear {{amount}} itens.", - + // "collection.edit.item-mapper.notifications.map.error.head": "Mapping errors", "collection.edit.item-mapper.notifications.map.error.head": "Erros de mapeamento", - + // "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.", "collection.edit.item-mapper.notifications.map.success.content": "Mapeou {{amount}} itens com sucesso.", - + // "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed", "collection.edit.item-mapper.notifications.map.success.head": "Mapeamento completo", - + // "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.", "collection.edit.item-mapper.notifications.unmap.error.content": "Ocorreram erros ao tentar remover os mapeamentos de {{amount}} item(ns).", - + // "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors", "collection.edit.item-mapper.notifications.unmap.error.head": "Erros de remoção de mapeamento", - + // "collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.", "collection.edit.item-mapper.notifications.unmap.success.content": "Removeu os mapeamentps de {{amount}} item(ns) com sucesso.", - + // "collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed", "collection.edit.item-mapper.notifications.unmap.success.head": "Remoção de mapeamentos completa", - + // "collection.edit.item-mapper.remove": "Remove selected item mappings", "collection.edit.item-mapper.remove": "Remover mapeamentos selecionados", - + + // "collection.edit.item-mapper.search-form.placeholder": "Search items...", + // TODO New key - Add a translation + "collection.edit.item-mapper.search-form.placeholder": "Search items...", + // "collection.edit.item-mapper.tabs.browse": "Browse mapped items", "collection.edit.item-mapper.tabs.browse": "Navegar por itens mapeados", - + // "collection.edit.item-mapper.tabs.map": "Map new items", "collection.edit.item-mapper.tabs.map": "Mapear novos itens", - - - + + + // "collection.edit.logo.delete.title": "Delete logo", + // TODO New key - Add a translation + "collection.edit.logo.delete.title": "Delete logo", + + // "collection.edit.logo.delete-undo.title": "Undo delete", + // TODO New key - Add a translation + "collection.edit.logo.delete-undo.title": "Undo delete", + // "collection.edit.logo.label": "Collection logo", // TODO New key - Add a translation "collection.edit.logo.label": "Collection logo", - + // "collection.edit.logo.notifications.add.error": "Uploading Collection logo failed. Please verify the content before retrying.", // TODO New key - Add a translation "collection.edit.logo.notifications.add.error": "Uploading Collection logo failed. Please verify the content before retrying.", - + // "collection.edit.logo.notifications.add.success": "Upload Collection logo successful.", // TODO New key - Add a translation "collection.edit.logo.notifications.add.success": "Upload Collection logo successful.", - + // "collection.edit.logo.notifications.delete.success.title": "Logo deleted", "collection.edit.logo.notifications.delete.success.title": "Logo removido", - + // "collection.edit.logo.notifications.delete.success.content": "Successfully deleted the collection's logo", // TODO New key - Add a translation "collection.edit.logo.notifications.delete.success.content": "Successfully deleted the collection's logo", - + // "collection.edit.logo.notifications.delete.error.title": "Error deleting logo", // TODO New key - Add a translation "collection.edit.logo.notifications.delete.error.title": "Error deleting logo", - + // "collection.edit.logo.upload": "Drop a Collection Logo to upload", // TODO New key - Add a translation "collection.edit.logo.upload": "Drop a Collection Logo to upload", - - - + + + // "collection.edit.notifications.success": "Successfully edited the Collection", // TODO New key - Add a translation "collection.edit.notifications.success": "Successfully edited the Collection", - - // "collection.edit.return": "Return", + + // "collection.edit.return": "Back", // TODO New key - Add a translation - "collection.edit.return": "Return", - - - + "collection.edit.return": "Back", + + + // "collection.edit.tabs.curate.head": "Curate", // TODO New key - Add a translation "collection.edit.tabs.curate.head": "Curate", - + // "collection.edit.tabs.curate.title": "Collection Edit - Curate", // TODO New key - Add a translation "collection.edit.tabs.curate.title": "Collection Edit - Curate", - + // "collection.edit.tabs.authorizations.head": "Authorizations", // TODO New key - Add a translation "collection.edit.tabs.authorizations.head": "Authorizations", - + // "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", // TODO New key - Add a translation "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", - + + // "collection.edit.item.authorizations.load-bundle-button": "Load more bundles", + // TODO New key - Add a translation + "collection.edit.item.authorizations.load-bundle-button": "Load more bundles", + + // "collection.edit.item.authorizations.load-more-button": "Load more", + // TODO New key - Add a translation + "collection.edit.item.authorizations.load-more-button": "Load more", + + // "collection.edit.item.authorizations.show-bitstreams-button": "Show bitstream policies for bundle", + // TODO New key - Add a translation + "collection.edit.item.authorizations.show-bitstreams-button": "Show bitstream policies for bundle", + // "collection.edit.tabs.metadata.head": "Edit Metadata", "collection.edit.tabs.metadata.head": "Editar Metadados", - + // "collection.edit.tabs.metadata.title": "Collection Edit - Metadata", // TODO New key - Add a translation "collection.edit.tabs.metadata.title": "Collection Edit - Metadata", - + // "collection.edit.tabs.roles.head": "Assign Roles", // TODO New key - Add a translation "collection.edit.tabs.roles.head": "Assign Roles", - + // "collection.edit.tabs.roles.title": "Collection Edit - Roles", // TODO New key - Add a translation "collection.edit.tabs.roles.title": "Collection Edit - Roles", - + // "collection.edit.tabs.source.external": "This collection harvests its content from an external source", // TODO New key - Add a translation "collection.edit.tabs.source.external": "This collection harvests its content from an external source", - + // "collection.edit.tabs.source.form.errors.oaiSource.required": "You must provide a set id of the target collection.", // TODO New key - Add a translation "collection.edit.tabs.source.form.errors.oaiSource.required": "You must provide a set id of the target collection.", - + // "collection.edit.tabs.source.form.harvestType": "Content being harvested", // TODO New key - Add a translation "collection.edit.tabs.source.form.harvestType": "Content being harvested", - + // "collection.edit.tabs.source.form.head": "Configure an external source", // TODO New key - Add a translation "collection.edit.tabs.source.form.head": "Configure an external source", - + // "collection.edit.tabs.source.form.metadataConfigId": "Metadata Format", // TODO New key - Add a translation "collection.edit.tabs.source.form.metadataConfigId": "Metadata Format", - + // "collection.edit.tabs.source.form.oaiSetId": "OAI specific set id", // TODO New key - Add a translation "collection.edit.tabs.source.form.oaiSetId": "OAI specific set id", - + // "collection.edit.tabs.source.form.oaiSource": "OAI Provider", // TODO New key - Add a translation "collection.edit.tabs.source.form.oaiSource": "OAI Provider", - + // "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_BITSTREAMS": "Harvest metadata and bitstreams (requires ORE support)", // TODO New key - Add a translation "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_BITSTREAMS": "Harvest metadata and bitstreams (requires ORE support)", - + // "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_REF": "Harvest metadata and references to bitstreams (requires ORE support)", // TODO New key - Add a translation "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_REF": "Harvest metadata and references to bitstreams (requires ORE support)", - + // "collection.edit.tabs.source.form.options.harvestType.METADATA_ONLY": "Harvest metadata only", // TODO New key - Add a translation "collection.edit.tabs.source.form.options.harvestType.METADATA_ONLY": "Harvest metadata only", - + // "collection.edit.tabs.source.head": "Content Source", // TODO New key - Add a translation "collection.edit.tabs.source.head": "Content Source", - + // "collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", // TODO New key - Add a translation "collection.edit.tabs.source.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - - // "collection.edit.tabs.source.notifications.discarded.title": "Changed discarded", + + // "collection.edit.tabs.source.notifications.discarded.title": "Changes discarded", + // TODO Source message changed - Revise the translation "collection.edit.tabs.source.notifications.discarded.title": "Alterações descartadas", - + // "collection.edit.tabs.source.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.", // TODO New key - Add a translation "collection.edit.tabs.source.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.", - + // "collection.edit.tabs.source.notifications.invalid.title": "Metadata invalid", // TODO New key - Add a translation "collection.edit.tabs.source.notifications.invalid.title": "Metadata invalid", - + // "collection.edit.tabs.source.notifications.saved.content": "Your changes to this collection's content source were saved.", // TODO New key - Add a translation "collection.edit.tabs.source.notifications.saved.content": "Your changes to this collection's content source were saved.", - + // "collection.edit.tabs.source.notifications.saved.title": "Content Source saved", // TODO New key - Add a translation "collection.edit.tabs.source.notifications.saved.title": "Content Source saved", - + // "collection.edit.tabs.source.title": "Collection Edit - Content Source", // TODO New key - Add a translation "collection.edit.tabs.source.title": "Collection Edit - Content Source", - - - + + + // "collection.edit.template.add-button": "Add", // TODO New key - Add a translation "collection.edit.template.add-button": "Add", - + // "collection.edit.template.breadcrumbs": "Item template", // TODO New key - Add a translation "collection.edit.template.breadcrumbs": "Item template", - + // "collection.edit.template.cancel": "Cancel", // TODO New key - Add a translation "collection.edit.template.cancel": "Cancel", - + // "collection.edit.template.delete-button": "Delete", // TODO New key - Add a translation "collection.edit.template.delete-button": "Delete", - + // "collection.edit.template.edit-button": "Edit", + "collection.edit.template.edit-button": "Editar", + + // "collection.edit.template.error": "An error occurred retrieving the template item", // TODO New key - Add a translation - "collection.edit.template.edit-button": "Edit", - + "collection.edit.template.error": "An error occurred retrieving the template item", + // "collection.edit.template.head": "Edit Template Item for Collection \"{{ collection }}\"", // TODO New key - Add a translation "collection.edit.template.head": "Edit Template Item for Collection \"{{ collection }}\"", - + // "collection.edit.template.label": "Template item", // TODO New key - Add a translation "collection.edit.template.label": "Template item", - + + // "collection.edit.template.loading": "Loading template item...", + // TODO New key - Add a translation + "collection.edit.template.loading": "Loading template item...", + // "collection.edit.template.notifications.delete.error": "Failed to delete the item template", // TODO New key - Add a translation "collection.edit.template.notifications.delete.error": "Failed to delete the item template", - + // "collection.edit.template.notifications.delete.success": "Successfully deleted the item template", // TODO New key - Add a translation "collection.edit.template.notifications.delete.success": "Successfully deleted the item template", - + // "collection.edit.template.title": "Edit Template Item", // TODO New key - Add a translation "collection.edit.template.title": "Edit Template Item", - - - + + + // "collection.form.abstract": "Short Description", "collection.form.abstract": "Descrição curta", - + // "collection.form.description": "Introductory text (HTML)", "collection.form.description": "Texto introdutório (HTML)", - + // "collection.form.errors.title.required": "Please enter a collection name", "collection.form.errors.title.required": "Por favor informe um nome de coleção", - + // "collection.form.license": "License", "collection.form.license": "Licença", - + // "collection.form.provenance": "Provenance", "collection.form.provenance": "Proveniência", - + // "collection.form.rights": "Copyright text (HTML)", "collection.form.rights": "Texto de direito de cópia (HTML)", - + // "collection.form.tableofcontents": "News (HTML)", "collection.form.tableofcontents": "Notícias (HTML)", - + // "collection.form.title": "Name", "collection.form.title": "Nome", - - - - // "collection.listelement.badge": "Collection", + + // "collection.form.entityType": "Entity Type", // TODO New key - Add a translation - "collection.listelement.badge": "Collection", - - - + "collection.form.entityType": "Entity Type", + + + + // "collection.listelement.badge": "Collection", + "collection.listelement.badge": "Coleção", + + + // "collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.head": "Submissões Recentes", - + // "collection.page.browse.recent.empty": "No items to show", "collection.page.browse.recent.empty": "Nenhum item a exibir", - + // "collection.page.edit": "Edit this collection", // TODO New key - Add a translation "collection.page.edit": "Edit this collection", - + // "collection.page.handle": "Permanent URI for this collection", "collection.page.handle": "URI Permanente para esta coleção", - + // "collection.page.license": "License", "collection.page.license": "Licença", - + // "collection.page.news": "News", "collection.page.news": "Notícias", - - - + + + // "collection.select.confirm": "Confirm selected", "collection.select.confirm": "Confirmar seleção", - + // "collection.select.empty": "No collections to show", "collection.select.empty": "Nenhuma coleção a mostrar", - + // "collection.select.table.title": "Title", "collection.select.table.title": "Título", - - - + + + // "collection.source.controls.head": "Harvest Controls", + // TODO New key - Add a translation + "collection.source.controls.head": "Harvest Controls", + // "collection.source.controls.test.submit.error": "Something went wrong with initiating the testing of the settings", + // TODO New key - Add a translation + "collection.source.controls.test.submit.error": "Something went wrong with initiating the testing of the settings", + // "collection.source.controls.test.failed": "The script to test the settings has failed", + // TODO New key - Add a translation + "collection.source.controls.test.failed": "The script to test the settings has failed", + // "collection.source.controls.test.completed": "The script to test the settings has successfully finished", + // TODO New key - Add a translation + "collection.source.controls.test.completed": "The script to test the settings has successfully finished", + // "collection.source.controls.test.submit": "Test configuration", + // TODO New key - Add a translation + "collection.source.controls.test.submit": "Test configuration", + // "collection.source.controls.test.running": "Testing configuration...", + // TODO New key - Add a translation + "collection.source.controls.test.running": "Testing configuration...", + // "collection.source.controls.import.submit.success": "The import has been successfully initiated", + // TODO New key - Add a translation + "collection.source.controls.import.submit.success": "The import has been successfully initiated", + // "collection.source.controls.import.submit.error": "Something went wrong with initiating the import", + // TODO New key - Add a translation + "collection.source.controls.import.submit.error": "Something went wrong with initiating the import", + // "collection.source.controls.import.submit": "Import now", + // TODO New key - Add a translation + "collection.source.controls.import.submit": "Import now", + // "collection.source.controls.import.running": "Importing...", + // TODO New key - Add a translation + "collection.source.controls.import.running": "Importing...", + // "collection.source.controls.import.failed": "An error occurred during the import", + // TODO New key - Add a translation + "collection.source.controls.import.failed": "An error occurred during the import", + // "collection.source.controls.import.completed": "The import completed", + // TODO New key - Add a translation + "collection.source.controls.import.completed": "The import completed", + // "collection.source.controls.reset.submit.success": "The reset and reimport has been successfully initiated", + // TODO New key - Add a translation + "collection.source.controls.reset.submit.success": "The reset and reimport has been successfully initiated", + // "collection.source.controls.reset.submit.error": "Something went wrong with initiating the reset and reimport", + // TODO New key - Add a translation + "collection.source.controls.reset.submit.error": "Something went wrong with initiating the reset and reimport", + // "collection.source.controls.reset.failed": "An error occurred during the reset and reimport", + // TODO New key - Add a translation + "collection.source.controls.reset.failed": "An error occurred during the reset and reimport", + // "collection.source.controls.reset.completed": "The reset and reimport completed", + // TODO New key - Add a translation + "collection.source.controls.reset.completed": "The reset and reimport completed", + // "collection.source.controls.reset.submit": "Reset and reimport", + // TODO New key - Add a translation + "collection.source.controls.reset.submit": "Reset and reimport", + // "collection.source.controls.reset.running": "Resetting and reimporting...", + // TODO New key - Add a translation + "collection.source.controls.reset.running": "Resetting and reimporting...", + // "collection.source.controls.harvest.status": "Harvest status:", + // TODO New key - Add a translation + "collection.source.controls.harvest.status": "Harvest status:", + // "collection.source.controls.harvest.start": "Harvest start time:", + // TODO New key - Add a translation + "collection.source.controls.harvest.start": "Harvest start time:", + // "collection.source.controls.harvest.last": "Last time harvested:", + // TODO New key - Add a translation + "collection.source.controls.harvest.last": "Last time harvested:", + // "collection.source.controls.harvest.message": "Harvest info:", + // TODO New key - Add a translation + "collection.source.controls.harvest.message": "Harvest info:", + // "collection.source.controls.harvest.no-information": "N/A", + // TODO New key - Add a translation + "collection.source.controls.harvest.no-information": "N/A", + + // "collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.", // TODO New key - Add a translation "collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.", - + // "collection.source.update.notifications.error.title": "Server Error", "collection.source.update.notifications.error.title": "Erro no Servidor", - - - - // "communityList.tabTitle": "DSpace - Community List", - // TODO New key - Add a translation - "communityList.tabTitle": "DSpace - Community List", - + + + + // "communityList.breadcrumbs": "Community List", + "communityList.breadcrumbs": "Lista da Comunidade", + + // "communityList.tabTitle": "Community List", + "communityList.tabTitle": "Lista da Comunidade", + // "communityList.title": "List of Communities", "communityList.title": "Lista de Comunidades", - + // "communityList.showMore": "Show More", "communityList.showMore": "Mostrar Mais", - - - + + + // "community.create.head": "Create a Community", - "community.create.head": "Criar uma comunidade", - + "community.create.head": "Criar uma Comunidade", + // "community.create.notifications.success": "Successfully created the Community", // TODO New key - Add a translation "community.create.notifications.success": "Successfully created the Community", - + // "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", "community.create.sub-head": "Criar uma Sub-Comunidade para Comunidade {{ parent }}", - + // "community.curate.header": "Curate Community: {{community}}", // TODO New key - Add a translation "community.curate.header": "Curate Community: {{community}}", - + // "community.delete.cancel": "Cancel", "community.delete.cancel": "Cancelar", - + // "community.delete.confirm": "Confirm", "community.delete.confirm": "Confirmar", - + + // "community.delete.processing": "Deleting...", + // TODO New key - Add a translation + "community.delete.processing": "Deleting...", + // "community.delete.head": "Delete Community", "community.delete.head": "Apagar Comunidade", - + // "community.delete.notification.fail": "Community could not be deleted", "community.delete.notification.fail": "Comunidade não pôde ser apagada", - + // "community.delete.notification.success": "Successfully deleted community", "community.delete.notification.success": "Comunidade apagada com sucesso", - + // "community.delete.text": "Are you sure you want to delete community \"{{ dso }}\"", "community.delete.text": "Você tem certeza que quer apagar a comunidade \"{{ dso }}\"?", - + // "community.edit.delete": "Delete this community", "community.edit.delete": "Apagar esta comunidade", - + // "community.edit.head": "Edit Community", "community.edit.head": "Editar Comunidade", - + // "community.edit.breadcrumbs": "Edit Community", "community.edit.breadcrumbs": "Editar Comunidade", - - + + + // "community.edit.logo.delete.title": "Delete logo", + // TODO New key - Add a translation + "community.edit.logo.delete.title": "Delete logo", + + // "community.edit.logo.delete-undo.title": "Undo delete", + // TODO New key - Add a translation + "community.edit.logo.delete-undo.title": "Undo delete", + // "community.edit.logo.label": "Community logo", "community.edit.logo.label": "Logo da Comunidade", - + // "community.edit.logo.notifications.add.error": "Uploading Community logo failed. Please verify the content before retrying.", // TODO New key - Add a translation "community.edit.logo.notifications.add.error": "Uploading Community logo failed. Please verify the content before retrying.", - + // "community.edit.logo.notifications.add.success": "Upload Community logo successful.", // TODO New key - Add a translation "community.edit.logo.notifications.add.success": "Upload Community logo successful.", - + // "community.edit.logo.notifications.delete.success.title": "Logo deleted", // TODO New key - Add a translation "community.edit.logo.notifications.delete.success.title": "Logo deleted", - + // "community.edit.logo.notifications.delete.success.content": "Successfully deleted the community's logo", // TODO New key - Add a translation "community.edit.logo.notifications.delete.success.content": "Successfully deleted the community's logo", - + // "community.edit.logo.notifications.delete.error.title": "Error deleting logo", // TODO New key - Add a translation "community.edit.logo.notifications.delete.error.title": "Error deleting logo", - + // "community.edit.logo.upload": "Drop a Community Logo to upload", // TODO New key - Add a translation "community.edit.logo.upload": "Drop a Community Logo to upload", - - - + + + // "community.edit.notifications.success": "Successfully edited the Community", // TODO New key - Add a translation "community.edit.notifications.success": "Successfully edited the Community", - + // "community.edit.notifications.unauthorized": "You do not have privileges to make this change", // TODO New key - Add a translation "community.edit.notifications.unauthorized": "You do not have privileges to make this change", - + // "community.edit.notifications.error": "An error occured while editing the Community", // TODO New key - Add a translation "community.edit.notifications.error": "An error occured while editing the Community", - - // "community.edit.return": "Return", + + // "community.edit.return": "Back", // TODO New key - Add a translation - "community.edit.return": "Return", - - - + "community.edit.return": "Back", + + + // "community.edit.tabs.curate.head": "Curate", // TODO New key - Add a translation "community.edit.tabs.curate.head": "Curate", - + // "community.edit.tabs.curate.title": "Community Edit - Curate", // TODO New key - Add a translation "community.edit.tabs.curate.title": "Community Edit - Curate", - + // "community.edit.tabs.metadata.head": "Edit Metadata", // TODO New key - Add a translation "community.edit.tabs.metadata.head": "Edit Metadata", - + // "community.edit.tabs.metadata.title": "Community Edit - Metadata", // TODO New key - Add a translation "community.edit.tabs.metadata.title": "Community Edit - Metadata", - + // "community.edit.tabs.roles.head": "Assign Roles", // TODO New key - Add a translation "community.edit.tabs.roles.head": "Assign Roles", - + // "community.edit.tabs.roles.title": "Community Edit - Roles", // TODO New key - Add a translation "community.edit.tabs.roles.title": "Community Edit - Roles", - + // "community.edit.tabs.authorizations.head": "Authorizations", // TODO New key - Add a translation "community.edit.tabs.authorizations.head": "Authorizations", - + // "community.edit.tabs.authorizations.title": "Community Edit - Authorizations", // TODO New key - Add a translation "community.edit.tabs.authorizations.title": "Community Edit - Authorizations", - - - + + + // "community.listelement.badge": "Community", - // TODO New key - Add a translation - "community.listelement.badge": "Community", - - - + "community.listelement.badge": "Comunidade", + + + // "comcol-role.edit.no-group": "None", // TODO New key - Add a translation "comcol-role.edit.no-group": "None", - + // "comcol-role.edit.create": "Create", // TODO New key - Add a translation "comcol-role.edit.create": "Create", - + + // "comcol-role.edit.create.error.title": "Failed to create a group for the '{{ role }}' role", + // TODO New key - Add a translation + "comcol-role.edit.create.error.title": "Failed to create a group for the '{{ role }}' role", + // "comcol-role.edit.restrict": "Restrict", // TODO New key - Add a translation "comcol-role.edit.restrict": "Restrict", - + // "comcol-role.edit.delete": "Delete", // TODO New key - Add a translation "comcol-role.edit.delete": "Delete", - - + + // "comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group", + // TODO New key - Add a translation + "comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group", + + // "comcol-role.edit.community-admin.name": "Administrators", // TODO New key - Add a translation "comcol-role.edit.community-admin.name": "Administrators", - + // "comcol-role.edit.collection-admin.name": "Administrators", // TODO New key - Add a translation "comcol-role.edit.collection-admin.name": "Administrators", - - + + // "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", // TODO New key - Add a translation "comcol-role.edit.community-admin.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", - + // "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", // TODO New key - Add a translation "comcol-role.edit.collection-admin.description": "Collection administrators decide who can submit items to the collection, edit item metadata (after submission), and add (map) existing items from other collections to this collection (subject to authorization for that collection).", - - + + // "comcol-role.edit.submitters.name": "Submitters", // TODO New key - Add a translation "comcol-role.edit.submitters.name": "Submitters", - + // "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", // TODO New key - Add a translation "comcol-role.edit.submitters.description": "The E-People and Groups that have permission to submit new items to this collection.", - - + + // "comcol-role.edit.item_read.name": "Default item read access", // TODO New key - Add a translation "comcol-role.edit.item_read.name": "Default item read access", - + // "comcol-role.edit.item_read.description": "E-People and Groups that can read new items submitted to this collection. Changes to this role are not retroactive. Existing items in the system will still be viewable by those who had read access at the time of their addition.", // TODO New key - Add a translation "comcol-role.edit.item_read.description": "E-People and Groups that can read new items submitted to this collection. Changes to this role are not retroactive. Existing items in the system will still be viewable by those who had read access at the time of their addition.", - + // "comcol-role.edit.item_read.anonymous-group": "Default read for incoming items is currently set to Anonymous.", // TODO New key - Add a translation "comcol-role.edit.item_read.anonymous-group": "Default read for incoming items is currently set to Anonymous.", - - + + // "comcol-role.edit.bitstream_read.name": "Default bitstream read access", // TODO New key - Add a translation "comcol-role.edit.bitstream_read.name": "Default bitstream read access", - + // "comcol-role.edit.bitstream_read.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", // TODO New key - Add a translation "comcol-role.edit.bitstream_read.description": "Community administrators can create sub-communities or collections, and manage or assign management for those sub-communities or collections. In addition, they decide who can submit items to any sub-collections, edit item metadata (after submission), and add (map) existing items from other collections (subject to authorization).", - + // "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", // TODO New key - Add a translation "comcol-role.edit.bitstream_read.anonymous-group": "Default read for incoming bitstreams is currently set to Anonymous.", - - + + // "comcol-role.edit.editor.name": "Editors", // TODO New key - Add a translation "comcol-role.edit.editor.name": "Editors", - + // "comcol-role.edit.editor.description": "Editors are able to edit the metadata of incoming submissions, and then accept or reject them.", // TODO New key - Add a translation "comcol-role.edit.editor.description": "Editors are able to edit the metadata of incoming submissions, and then accept or reject them.", - - + + // "comcol-role.edit.finaleditor.name": "Final editors", // TODO New key - Add a translation "comcol-role.edit.finaleditor.name": "Final editors", - + // "comcol-role.edit.finaleditor.description": "Final editors are able to edit the metadata of incoming submissions, but will not be able to reject them.", // TODO New key - Add a translation "comcol-role.edit.finaleditor.description": "Final editors are able to edit the metadata of incoming submissions, but will not be able to reject them.", - - + + // "comcol-role.edit.reviewer.name": "Reviewers", // TODO New key - Add a translation "comcol-role.edit.reviewer.name": "Reviewers", - + // "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", // TODO New key - Add a translation "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", - - - + + + // "community.form.abstract": "Short Description", "community.form.abstract": "Descrição curta", - + // "community.form.description": "Introductory text (HTML)", "community.form.description": "Texto introdutório (HTML)", - + // "community.form.errors.title.required": "Please enter a community name", "community.form.errors.title.required": "Por favor informe um nome para comunidade", - + // "community.form.rights": "Copyright text (HTML)", "community.form.rights": "Texto de direito de cópia (HTML)", - + // "community.form.tableofcontents": "News (HTML)", "community.form.tableofcontents": "Notícias (HTML)", - + // "community.form.title": "Name", "community.form.title": "Nome", - + // "community.page.edit": "Edit this community", // TODO New key - Add a translation "community.page.edit": "Edit this community", - + // "community.page.handle": "Permanent URI for this community", "community.page.handle": "URI Permanente desta comunidade", - + // "community.page.license": "License", "community.page.license": "Licença", - + // "community.page.news": "News", "community.page.news": "Notícias", - + // "community.all-lists.head": "Subcommunities and Collections", "community.all-lists.head": "Sub-Comunidade e Coleções", - + // "community.sub-collection-list.head": "Collections of this Community", "community.sub-collection-list.head": "Coleções desta Comunidade", - + // "community.sub-community-list.head": "Communities of this Community", "community.sub-community-list.head": "Comunidades desta Comunidade", - - - + + + // "cookies.consent.accept-all": "Accept all", // TODO New key - Add a translation "cookies.consent.accept-all": "Accept all", - + // "cookies.consent.accept-selected": "Accept selected", // TODO New key - Add a translation "cookies.consent.accept-selected": "Accept selected", - + // "cookies.consent.app.opt-out.description": "This app is loaded by default (but you can opt out)", // TODO New key - Add a translation "cookies.consent.app.opt-out.description": "This app is loaded by default (but you can opt out)", - + // "cookies.consent.app.opt-out.title": "(opt-out)", // TODO New key - Add a translation "cookies.consent.app.opt-out.title": "(opt-out)", - + // "cookies.consent.app.purpose": "purpose", // TODO New key - Add a translation "cookies.consent.app.purpose": "purpose", - + // "cookies.consent.app.required.description": "This application is always required", // TODO New key - Add a translation "cookies.consent.app.required.description": "This application is always required", - + // "cookies.consent.app.required.title": "(always required)", // TODO New key - Add a translation "cookies.consent.app.required.title": "(always required)", - + // "cookies.consent.update": "There were changes since your last visit, please update your consent.", // TODO New key - Add a translation "cookies.consent.update": "There were changes since your last visit, please update your consent.", - + // "cookies.consent.close": "Close", // TODO New key - Add a translation "cookies.consent.close": "Close", - + // "cookies.consent.decline": "Decline", // TODO New key - Add a translation "cookies.consent.decline": "Decline", - + // "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.
To learn more, please read our {privacyPolicy}.", // TODO New key - Add a translation "cookies.consent.content-notice.description": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.
To learn more, please read our {privacyPolicy}.", - + + // "cookies.consent.content-notice.description.no-privacy": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.", + // TODO New key - Add a translation + "cookies.consent.content-notice.description.no-privacy": "We collect and process your personal information for the following purposes: Authentication, Preferences, Acknowledgement and Statistics.", + // "cookies.consent.content-notice.learnMore": "Customize", // TODO New key - Add a translation "cookies.consent.content-notice.learnMore": "Customize", - + // "cookies.consent.content-modal.description": "Here you can see and customize the information that we collect about you.", // TODO New key - Add a translation "cookies.consent.content-modal.description": "Here you can see and customize the information that we collect about you.", - + // "cookies.consent.content-modal.privacy-policy.name": "privacy policy", // TODO New key - Add a translation "cookies.consent.content-modal.privacy-policy.name": "privacy policy", - + // "cookies.consent.content-modal.privacy-policy.text": "To learn more, please read our {privacyPolicy}.", // TODO New key - Add a translation "cookies.consent.content-modal.privacy-policy.text": "To learn more, please read our {privacyPolicy}.", - + // "cookies.consent.content-modal.title": "Information that we collect", // TODO New key - Add a translation "cookies.consent.content-modal.title": "Information that we collect", - - - + + + // "cookies.consent.app.title.authentication": "Authentication", // TODO New key - Add a translation "cookies.consent.app.title.authentication": "Authentication", - + // "cookies.consent.app.description.authentication": "Required for signing you in", // TODO New key - Add a translation "cookies.consent.app.description.authentication": "Required for signing you in", - - + + // "cookies.consent.app.title.preferences": "Preferences", // TODO New key - Add a translation "cookies.consent.app.title.preferences": "Preferences", - + // "cookies.consent.app.description.preferences": "Required for saving your preferences", // TODO New key - Add a translation "cookies.consent.app.description.preferences": "Required for saving your preferences", - - - + + + // "cookies.consent.app.title.acknowledgement": "Acknowledgement", // TODO New key - Add a translation "cookies.consent.app.title.acknowledgement": "Acknowledgement", - + // "cookies.consent.app.description.acknowledgement": "Required for saving your acknowledgements and consents", // TODO New key - Add a translation "cookies.consent.app.description.acknowledgement": "Required for saving your acknowledgements and consents", - - - + + + // "cookies.consent.app.title.google-analytics": "Google Analytics", // TODO New key - Add a translation "cookies.consent.app.title.google-analytics": "Google Analytics", - + // "cookies.consent.app.description.google-analytics": "Allows us to track statistical data", // TODO New key - Add a translation "cookies.consent.app.description.google-analytics": "Allows us to track statistical data", - - - + + + // "cookies.consent.purpose.functional": "Functional", // TODO New key - Add a translation "cookies.consent.purpose.functional": "Functional", - + // "cookies.consent.purpose.statistical": "Statistical", // TODO New key - Add a translation "cookies.consent.purpose.statistical": "Statistical", - - + + // "curation-task.task.citationpage.label": "Generate Citation Page", + // TODO New key - Add a translation + "curation-task.task.citationpage.label": "Generate Citation Page", + // "curation-task.task.checklinks.label": "Check Links in Metadata", // TODO New key - Add a translation "curation-task.task.checklinks.label": "Check Links in Metadata", - + // "curation-task.task.noop.label": "NOOP", // TODO New key - Add a translation "curation-task.task.noop.label": "NOOP", - + // "curation-task.task.profileformats.label": "Profile Bitstream Formats", // TODO New key - Add a translation "curation-task.task.profileformats.label": "Profile Bitstream Formats", - + // "curation-task.task.requiredmetadata.label": "Check for Required Metadata", // TODO New key - Add a translation "curation-task.task.requiredmetadata.label": "Check for Required Metadata", - + // "curation-task.task.translate.label": "Microsoft Translator", // TODO New key - Add a translation "curation-task.task.translate.label": "Microsoft Translator", - + // "curation-task.task.vscan.label": "Virus Scan", // TODO New key - Add a translation "curation-task.task.vscan.label": "Virus Scan", - - - + + + // "curation.form.task-select.label": "Task:", // TODO New key - Add a translation "curation.form.task-select.label": "Task:", - + // "curation.form.submit": "Start", // TODO New key - Add a translation "curation.form.submit": "Start", - + // "curation.form.submit.success.head": "The curation task has been started successfully", // TODO New key - Add a translation "curation.form.submit.success.head": "The curation task has been started successfully", - + // "curation.form.submit.success.content": "You will be redirected to the corresponding process page.", // TODO New key - Add a translation "curation.form.submit.success.content": "You will be redirected to the corresponding process page.", - + // "curation.form.submit.error.head": "Running the curation task failed", // TODO New key - Add a translation "curation.form.submit.error.head": "Running the curation task failed", - + // "curation.form.submit.error.content": "An error occured when trying to start the curation task.", // TODO New key - Add a translation "curation.form.submit.error.content": "An error occured when trying to start the curation task.", - + + // "curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object", + // TODO New key - Add a translation + "curation.form.submit.error.invalid-handle": "Couldn't determine the handle for this object", + // "curation.form.handle.label": "Handle:", // TODO New key - Add a translation "curation.form.handle.label": "Handle:", - + // "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", // TODO New key - Add a translation "curation.form.handle.hint": "Hint: Enter [your-handle-prefix]/0 to run a task across entire site (not all tasks may support this capability)", - - - + + + + // "deny-request-copy.email.message": "Dear {{ recipientName }},\nIn response to your request I regret to inform you that it's not possible to send you a copy of the file(s) you have requested, concerning the document: \"{{ itemUrl }}\" ({{ itemName }}), of which I am an author.\n\nBest regards,\n{{ authorName }} <{{ authorEmail }}>", + // TODO New key - Add a translation + "deny-request-copy.email.message": "Dear {{ recipientName }},\nIn response to your request I regret to inform you that it's not possible to send you a copy of the file(s) you have requested, concerning the document: \"{{ itemUrl }}\" ({{ itemName }}), of which I am an author.\n\nBest regards,\n{{ authorName }} <{{ authorEmail }}>", + + // "deny-request-copy.email.subject": "Request copy of document", + // TODO New key - Add a translation + "deny-request-copy.email.subject": "Request copy of document", + + // "deny-request-copy.error": "An error occurred", + // TODO New key - Add a translation + "deny-request-copy.error": "An error occurred", + + // "deny-request-copy.header": "Deny document copy request", + // TODO New key - Add a translation + "deny-request-copy.header": "Deny document copy request", + + // "deny-request-copy.intro": "This message will be sent to the applicant of the request", + // TODO New key - Add a translation + "deny-request-copy.intro": "This message will be sent to the applicant of the request", + + // "deny-request-copy.success": "Successfully denied item request", + // TODO New key - Add a translation + "deny-request-copy.success": "Successfully denied item request", + + + + // "dso.name.untitled": "Untitled", + // TODO New key - Add a translation + "dso.name.untitled": "Untitled", + + + // "dso-selector.create.collection.head": "New collection", "dso-selector.create.collection.head": "Nova coleção", - + // "dso-selector.create.collection.sub-level": "Create a new collection in", // TODO New key - Add a translation "dso-selector.create.collection.sub-level": "Create a new collection in", - + // "dso-selector.create.community.head": "New community", "dso-selector.create.community.head": "Nova comunidade", - + // "dso-selector.create.community.sub-level": "Create a new community in", "dso-selector.create.community.sub-level": "Criar uma nova coleção em", - + // "dso-selector.create.community.top-level": "Create a new top-level community", "dso-selector.create.community.top-level": "Criar uma nova comunidade no nível superior", - + // "dso-selector.create.item.head": "New item", "dso-selector.create.item.head": "Novo item", - + // "dso-selector.create.item.sub-level": "Create a new item in", - // TODO New key - Add a translation - "dso-selector.create.item.sub-level": "Create a new item in", - + "dso-selector.create.item.sub-level": "Adicionar um novo item em", + // "dso-selector.create.submission.head": "New submission", // TODO New key - Add a translation "dso-selector.create.submission.head": "New submission", - + // "dso-selector.edit.collection.head": "Edit collection", "dso-selector.edit.collection.head": "Editar coleção", - + // "dso-selector.edit.community.head": "Edit community", "dso-selector.edit.community.head": "Editar comunidade", - + // "dso-selector.edit.item.head": "Edit item", "dso-selector.edit.item.head": "Editar item", - + + // "dso-selector.error.title": "An error occurred searching for a {{ type }}", + // TODO New key - Add a translation + "dso-selector.error.title": "An error occurred searching for a {{ type }}", + // "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", // TODO New key - Add a translation "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", - + // "dso-selector.no-results": "No {{ type }} found", "dso-selector.no-results": "Nenhum(a) {{ type }} encontrado(a)", - + // "dso-selector.placeholder": "Search for a {{ type }}", "dso-selector.placeholder": "Buscar por um(a) {{ type }}", - - - + + // "dso-selector.select.collection.head": "Select a collection", + "dso-selector.select.collection.head": "Selecione uma coleção", + + // "dso-selector.set-scope.community.head": "Select a search scope", + "dso-selector.set-scope.community.head": "Selecione um escopo de pesquisa", + + // "dso-selector.set-scope.community.button": "Search all of DSpace", + "dso-selector.set-scope.community.button": "Pesquisar em todo o DSpace", + + // "dso-selector.set-scope.community.input-header": "Search for a community or collection", + "dso-selector.set-scope.community.input-header": "Pesquisar uma comunidade ou coleção", + + // "dso-selector.claim.item.head": "Profile tips", + "dso-selector.claim.item.head": "Dicas de perfil", + + // "dso-selector.claim.item.body": "These are existing profiles that may be related to you. If you recognize yourself in one of these profiles, select it and on the detail page, among the options, choose to claim it. Otherwise you can create a new profile from scratch using the button below.", + // TODO New key - Add a translation + "dso-selector.claim.item.body": "These are existing profiles that may be related to you. If you recognize yourself in one of these profiles, select it and on the detail page, among the options, choose to claim it. Otherwise you can create a new profile from scratch using the button below.", + + // "dso-selector.claim.item.not-mine-label": "None of these are mine", + // TODO New key - Add a translation + "dso-selector.claim.item.not-mine-label": "None of these are mine", + + // "dso-selector.claim.item.create-from-scratch": "Create a new one", + // TODO New key - Add a translation + "dso-selector.claim.item.create-from-scratch": "Create a new one", + // "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", // TODO New key - Add a translation "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", - + // "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", // TODO New key - Add a translation "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", - + // "confirmation-modal.export-metadata.cancel": "Cancel", // TODO New key - Add a translation "confirmation-modal.export-metadata.cancel": "Cancel", - + // "confirmation-modal.export-metadata.confirm": "Export", // TODO New key - Add a translation "confirmation-modal.export-metadata.confirm": "Export", - + // "confirmation-modal.delete-eperson.header": "Delete EPerson \"{{ dsoName }}\"", // TODO New key - Add a translation "confirmation-modal.delete-eperson.header": "Delete EPerson \"{{ dsoName }}\"", - + // "confirmation-modal.delete-eperson.info": "Are you sure you want to delete EPerson \"{{ dsoName }}\"", // TODO New key - Add a translation "confirmation-modal.delete-eperson.info": "Are you sure you want to delete EPerson \"{{ dsoName }}\"", - + // "confirmation-modal.delete-eperson.cancel": "Cancel", // TODO New key - Add a translation "confirmation-modal.delete-eperson.cancel": "Cancel", - + // "confirmation-modal.delete-eperson.confirm": "Delete", // TODO New key - Add a translation "confirmation-modal.delete-eperson.confirm": "Delete", - - + + // "confirmation-modal.delete-profile.header": "Delete Profile", + // TODO New key - Add a translation + "confirmation-modal.delete-profile.header": "Delete Profile", + + // "confirmation-modal.delete-profile.info": "Are you sure you want to delete your profile", + // TODO New key - Add a translation + "confirmation-modal.delete-profile.info": "Are you sure you want to delete your profile", + + // "confirmation-modal.delete-profile.cancel": "Cancel", + // TODO New key - Add a translation + "confirmation-modal.delete-profile.cancel": "Cancel", + + // "confirmation-modal.delete-profile.confirm": "Delete", + // TODO New key - Add a translation + "confirmation-modal.delete-profile.confirm": "Delete", + + // "error.bitstream": "Error fetching bitstream", // TODO New key - Add a translation "error.bitstream": "Error fetching bitstream", - + // "error.browse-by": "Error fetching items", "error.browse-by": "Erro ao carregar itens", - + // "error.collection": "Error fetching collection", "error.collection": "Erro ao carregar coleção", - + // "error.collections": "Error fetching collections", "error.collections": "Erro ao carregar coleções", - + // "error.community": "Error fetching community", "error.community": "Erro ao carregar comunidade", - + // "error.identifier": "No item found for the identifier", "error.identifier": "Nenhum item encontrado para o identificador", - + // "error.default": "Error", "error.default": "Erro", - + // "error.item": "Error fetching item", "error.item": "Erro ao carregar item", - + // "error.items": "Error fetching items", "error.items": "Erro ao carregar itens", - + // "error.objects": "Error fetching objects", "error.objects": "Erro ao carregar objetos", - + // "error.recent-submissions": "Error fetching recent submissions", "error.recent-submissions": "Erro ao carregar as submissões recentes", - + // "error.search-results": "Error fetching search results", "error.search-results": "Erro ao carregar os resultados de busca", - + + // "error.invalid-search-query": "Search query is not valid. Please check Solr query syntax best practices for further information about this error.", + // TODO New key - Add a translation + "error.invalid-search-query": "Search query is not valid. Please check Solr query syntax best practices for further information about this error.", + // "error.sub-collections": "Error fetching sub-collections", "error.sub-collections": "Erro ao carregar sub-coleções", - + // "error.sub-communities": "Error fetching sub-communities", "error.sub-communities": "Erro ao carregar sub-comunidade", - + // "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :

", "error.submission.sections.init-form-error": "Ocorreu um erro durante a seção de inicialização, por favor verifique sua configuração de input-form. Detalhes estão abaixo :

", - + // "error.top-level-communities": "Error fetching top-level communities", "error.top-level-communities": "Erro ao carregar as comunidade de nível superior", - + // "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", "error.validation.license.notgranted": "Você deve concordar com esta licença para completar sua submissão. Se vocẽ não estiver de acordo com esta licença neste momento você pode salvar seu trabalho para continuar depois ou remover a submissão.", - + // "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", "error.validation.pattern": "Este campo está restrito ao seguinte padrão: {{ pattern }}.", - + // "error.validation.filerequired": "The file upload is mandatory", // TODO New key - Add a translation "error.validation.filerequired": "The file upload is mandatory", - - - + + // "error.validation.required": "This field is required", + // TODO New key - Add a translation + "error.validation.required": "This field is required", + + // "error.validation.NotValidEmail": "This E-mail is not a valid email", + // TODO New key - Add a translation + "error.validation.NotValidEmail": "This E-mail is not a valid email", + + // "error.validation.emailTaken": "This E-mail is already taken", + // TODO New key - Add a translation + "error.validation.emailTaken": "This E-mail is already taken", + + // "error.validation.groupExists": "This group already exists", + // TODO New key - Add a translation + "error.validation.groupExists": "This group already exists", + + + // "feed.description": "Syndication feed", + // TODO New key - Add a translation + "feed.description": "Syndication feed", + + // "file-section.error.header": "Error obtaining files for this item", // TODO New key - Add a translation "file-section.error.header": "Error obtaining files for this item", - - - + + + // "footer.copyright": "copyright © 2002-{{ year }}", "footer.copyright": "copyright © 2002-{{ year }}", - + // "footer.link.dspace": "DSpace software", "footer.link.dspace": "DSpace software", - + // "footer.link.lyrasis": "LYRASIS", "footer.link.lyrasis": "LYRASIS", - + // "footer.link.cookies": "Cookie settings", - // TODO New key - Add a translation - "footer.link.cookies": "Cookie settings", - + "footer.link.cookies": "Configurações de Cookies", + // "footer.link.privacy-policy": "Privacy policy", - // TODO New key - Add a translation - "footer.link.privacy-policy": "Privacy policy", - + "footer.link.privacy-policy": "Política de Privacidade", + // "footer.link.end-user-agreement":"End User Agreement", - // TODO New key - Add a translation - "footer.link.end-user-agreement":"End User Agreement", - - - + "footer.link.end-user-agreement":"Termos de Uso", + + // "footer.link.feedback":"Send Feedback", + "footer.link.feedback":"Enviar uma Sugestão", + + + // "forgot-email.form.header": "Forgot Password", // TODO New key - Add a translation "forgot-email.form.header": "Forgot Password", - - // "forgot-email.form.info": "Enter Register an account to subscribe to collections for email updates, and submit new items to DSpace.", + + // "forgot-email.form.info": "Enter the email address associated with the account.", // TODO New key - Add a translation - "forgot-email.form.info": "Enter Register an account to subscribe to collections for email updates, and submit new items to DSpace.", - + "forgot-email.form.info": "Enter the email address associated with the account.", + // "forgot-email.form.email": "Email Address *", // TODO New key - Add a translation "forgot-email.form.email": "Email Address *", - + // "forgot-email.form.email.error.required": "Please fill in an email address", // TODO New key - Add a translation "forgot-email.form.email.error.required": "Please fill in an email address", - + // "forgot-email.form.email.error.pattern": "Please fill in a valid email address", // TODO New key - Add a translation "forgot-email.form.email.error.pattern": "Please fill in a valid email address", - - // "forgot-email.form.email.hint": "This address will be verified and used as your login name.", + + // "forgot-email.form.email.hint": "An email will be sent to this address with a further instructions.", // TODO New key - Add a translation - "forgot-email.form.email.hint": "This address will be verified and used as your login name.", - - // "forgot-email.form.submit": "Submit", + "forgot-email.form.email.hint": "An email will be sent to this address with a further instructions.", + + // "forgot-email.form.submit": "Reset password", // TODO New key - Add a translation - "forgot-email.form.submit": "Submit", - - // "forgot-email.form.success.head": "Verification email sent", + "forgot-email.form.submit": "Reset password", + + // "forgot-email.form.success.head": "Password reset email sent", // TODO New key - Add a translation - "forgot-email.form.success.head": "Verification email sent", - + "forgot-email.form.success.head": "Password reset email sent", + // "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", // TODO New key - Add a translation "forgot-email.form.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", - - // "forgot-email.form.error.head": "Error when trying to register email", + + // "forgot-email.form.error.head": "Error when trying to reset password", // TODO New key - Add a translation - "forgot-email.form.error.head": "Error when trying to register email", - - // "forgot-email.form.error.content": "An error occured when registering the following email address: {{ email }}", + "forgot-email.form.error.head": "Error when trying to reset password", + + // "forgot-email.form.error.content": "An error occured when attempting to reset the password for the account associated with the following email address: {{ email }}", // TODO New key - Add a translation - "forgot-email.form.error.content": "An error occured when registering the following email address: {{ email }}", - - - + "forgot-email.form.error.content": "An error occured when attempting to reset the password for the account associated with the following email address: {{ email }}", + + + // "forgot-password.title": "Forgot Password", // TODO New key - Add a translation "forgot-password.title": "Forgot Password", - + // "forgot-password.form.head": "Forgot Password", // TODO New key - Add a translation "forgot-password.form.head": "Forgot Password", - - // "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", + + // "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box.", // TODO New key - Add a translation - "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - + "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box.", + // "forgot-password.form.card.security": "Security", // TODO New key - Add a translation "forgot-password.form.card.security": "Security", - + // "forgot-password.form.identification.header": "Identify", // TODO New key - Add a translation "forgot-password.form.identification.header": "Identify", - + // "forgot-password.form.identification.email": "Email address: ", // TODO New key - Add a translation "forgot-password.form.identification.email": "Email address: ", - + // "forgot-password.form.label.password": "Password", // TODO New key - Add a translation "forgot-password.form.label.password": "Password", - + // "forgot-password.form.label.passwordrepeat": "Retype to confirm", // TODO New key - Add a translation "forgot-password.form.label.passwordrepeat": "Retype to confirm", - + // "forgot-password.form.error.empty-password": "Please enter a password in the box below.", // TODO New key - Add a translation "forgot-password.form.error.empty-password": "Please enter a password in the box below.", - + // "forgot-password.form.error.matching-passwords": "The passwords do not match.", // TODO New key - Add a translation "forgot-password.form.error.matching-passwords": "The passwords do not match.", - - // "forgot-password.form.error.password-length": "The password should be at least 6 characters long.", - // TODO New key - Add a translation - "forgot-password.form.error.password-length": "The password should be at least 6 characters long.", - + // "forgot-password.form.notification.error.title": "Error when trying to submit new password", // TODO New key - Add a translation "forgot-password.form.notification.error.title": "Error when trying to submit new password", - + // "forgot-password.form.notification.success.content": "The password reset was successful. You have been logged in as the created user.", // TODO New key - Add a translation "forgot-password.form.notification.success.content": "The password reset was successful. You have been logged in as the created user.", - + // "forgot-password.form.notification.success.title": "Password reset completed", // TODO New key - Add a translation "forgot-password.form.notification.success.title": "Password reset completed", - + // "forgot-password.form.submit": "Submit password", // TODO New key - Add a translation "forgot-password.form.submit": "Submit password", - - - - // "form.add": "Add", + + + // "form.add": "Add more", // TODO New key - Add a translation - "form.add": "Add", - + "form.add": "Add more", + // "form.add-help": "Click here to add the current entry and to add another one", // TODO New key - Add a translation "form.add-help": "Click here to add the current entry and to add another one", - + // "form.cancel": "Cancel", "form.cancel": "Cancelar", - + // "form.clear": "Clear", "form.clear": "Limpar", - + // "form.clear-help": "Click here to remove the selected value", "form.clear-help": "Clique aqui para apagar o valor selecionado", - + + // "form.discard": "Discard", + // TODO New key - Add a translation + "form.discard": "Discard", + + // "form.drag": "Drag", + // TODO New key - Add a translation + "form.drag": "Drag", + // "form.edit": "Edit", "form.edit": "Editar", - + // "form.edit-help": "Click here to edit the selected value", "form.edit-help": "Clique aqui para editar o valor selecionado", - + // "form.first-name": "First name", "form.first-name": "Primeiro nome", - + // "form.group-collapse": "Collapse", "form.group-collapse": "Esconder", - + // "form.group-collapse-help": "Click here to collapse", "form.group-collapse-help": "Clique aqui para esconder", - + // "form.group-expand": "Expand", "form.group-expand": "Expandir", - + // "form.group-expand-help": "Click here to expand and add more elements", "form.group-expand-help": "Clique aqui para expandir e adicionar mais elementos", - + // "form.last-name": "Last name", "form.last-name": "Último nome", - + // "form.loading": "Loading...", "form.loading": "Carregando...", - + // "form.lookup": "Lookup", // TODO New key - Add a translation "form.lookup": "Lookup", - + // "form.lookup-help": "Click here to look up an existing relation", // TODO New key - Add a translation "form.lookup-help": "Click here to look up an existing relation", - + // "form.no-results": "No results found", "form.no-results": "Nenhum resultado encontrado", - + // "form.no-value": "No value entered", "form.no-value": "Nenhum valor informado", - + // "form.other-information": {}, "form.other-information": {}, - + // "form.remove": "Remove", "form.remove": "Apagar", - + // "form.save": "Save", "form.save": "Salvar", - + // "form.save-help": "Save changes", "form.save-help": "Salvar alterações", - + // "form.search": "Search", "form.search": "Buscar", - + // "form.search-help": "Click here to look for an existing correspondence", // TODO Source message changed - Revise the translation "form.search-help": "Clique aqui para procurar por uma correspondência existente", - - // "form.submit": "Submit", + + // "form.submit": "Save", + // TODO Source message changed - Revise the translation "form.submit": "Submeter", - - - + + // "form.repeatable.sort.tip": "Drop the item in the new position", + // TODO New key - Add a translation + "form.repeatable.sort.tip": "Drop the item in the new position", + + + + // "grant-deny-request-copy.deny": "Don't send copy", + // TODO New key - Add a translation + "grant-deny-request-copy.deny": "Don't send copy", + + // "grant-deny-request-copy.email.back": "Back", + // TODO New key - Add a translation + "grant-deny-request-copy.email.back": "Back", + + // "grant-deny-request-copy.email.message": "Message", + // TODO New key - Add a translation + "grant-deny-request-copy.email.message": "Message", + + // "grant-deny-request-copy.email.message.empty": "Please enter a message", + // TODO New key - Add a translation + "grant-deny-request-copy.email.message.empty": "Please enter a message", + + // "grant-deny-request-copy.email.permissions.info": "You may use this occasion to reconsider the access restrictions on the document, to avoid having to respond to these requests. If you’d like to ask the repository administrators to remove these restrictions, please check the box below.", + // TODO New key - Add a translation + "grant-deny-request-copy.email.permissions.info": "You may use this occasion to reconsider the access restrictions on the document, to avoid having to respond to these requests. If you’d like to ask the repository administrators to remove these restrictions, please check the box below.", + + // "grant-deny-request-copy.email.permissions.label": "Change to open access", + // TODO New key - Add a translation + "grant-deny-request-copy.email.permissions.label": "Change to open access", + + // "grant-deny-request-copy.email.send": "Send", + // TODO New key - Add a translation + "grant-deny-request-copy.email.send": "Send", + + // "grant-deny-request-copy.email.subject": "Subject", + // TODO New key - Add a translation + "grant-deny-request-copy.email.subject": "Subject", + + // "grant-deny-request-copy.email.subject.empty": "Please enter a subject", + // TODO New key - Add a translation + "grant-deny-request-copy.email.subject.empty": "Please enter a subject", + + // "grant-deny-request-copy.grant": "Send copy", + // TODO New key - Add a translation + "grant-deny-request-copy.grant": "Send copy", + + // "grant-deny-request-copy.header": "Document copy request", + // TODO New key - Add a translation + "grant-deny-request-copy.header": "Document copy request", + + // "grant-deny-request-copy.home-page": "Take me to the home page", + // TODO New key - Add a translation + "grant-deny-request-copy.home-page": "Take me to the home page", + + // "grant-deny-request-copy.intro1": "If you are one of the authors of the document {{ name }}, then please use one of the options below to respond to the user's request.", + // TODO New key - Add a translation + "grant-deny-request-copy.intro1": "If you are one of the authors of the document {{ name }}, then please use one of the options below to respond to the user's request.", + + // "grant-deny-request-copy.intro2": "After choosing an option, you will be presented with a suggested email reply which you may edit.", + // TODO New key - Add a translation + "grant-deny-request-copy.intro2": "After choosing an option, you will be presented with a suggested email reply which you may edit.", + + // "grant-deny-request-copy.processed": "This request has already been processed. You can use the button below to get back to the home page.", + // TODO New key - Add a translation + "grant-deny-request-copy.processed": "This request has already been processed. You can use the button below to get back to the home page.", + + + + // "grant-request-copy.email.message": "Dear {{ recipientName }},\nIn response to your request I have the pleasure to send you in attachment a copy of the file(s) concerning the document: \"{{ itemUrl }}\" ({{ itemName }}), of which I am an author.\n\nBest regards,\n{{ authorName }} <{{ authorEmail }}>", + // TODO New key - Add a translation + "grant-request-copy.email.message": "Dear {{ recipientName }},\nIn response to your request I have the pleasure to send you in attachment a copy of the file(s) concerning the document: \"{{ itemUrl }}\" ({{ itemName }}), of which I am an author.\n\nBest regards,\n{{ authorName }} <{{ authorEmail }}>", + + // "grant-request-copy.email.subject": "Request copy of document", + // TODO New key - Add a translation + "grant-request-copy.email.subject": "Request copy of document", + + // "grant-request-copy.error": "An error occurred", + // TODO New key - Add a translation + "grant-request-copy.error": "An error occurred", + + // "grant-request-copy.header": "Grant document copy request", + // TODO New key - Add a translation + "grant-request-copy.header": "Grant document copy request", + + // "grant-request-copy.intro": "This message will be sent to the applicant of the request. The requested document(s) will be attached.", + // TODO New key - Add a translation + "grant-request-copy.intro": "This message will be sent to the applicant of the request. The requested document(s) will be attached.", + + // "grant-request-copy.success": "Successfully granted item request", + // TODO New key - Add a translation + "grant-request-copy.success": "Successfully granted item request", + + + // "health.breadcrumbs": "Health", + // TODO New key - Add a translation + "health.breadcrumbs": "Health", + + // "health-page.heading" : "Health", + // TODO New key - Add a translation + "health-page.heading" : "Health", + + // "health-page.info-tab" : "Info", + // TODO New key - Add a translation + "health-page.info-tab" : "Info", + + // "health-page.status-tab" : "Status", + // TODO New key - Add a translation + "health-page.status-tab" : "Status", + + // "health-page.error.msg": "The health check service is temporarily unavailable", + // TODO New key - Add a translation + "health-page.error.msg": "The health check service is temporarily unavailable", + + // "health-page.property.status": "Status code", + // TODO New key - Add a translation + "health-page.property.status": "Status code", + + // "health-page.section.db.title": "Database", + // TODO New key - Add a translation + "health-page.section.db.title": "Database", + + // "health-page.section.geoIp.title": "GeoIp", + // TODO New key - Add a translation + "health-page.section.geoIp.title": "GeoIp", + + // "health-page.section.solrAuthorityCore.title": "Sor: authority core", + // TODO New key - Add a translation + "health-page.section.solrAuthorityCore.title": "Sor: authority core", + + // "health-page.section.solrOaiCore.title": "Sor: oai core", + // TODO New key - Add a translation + "health-page.section.solrOaiCore.title": "Sor: oai core", + + // "health-page.section.solrSearchCore.title": "Sor: search core", + // TODO New key - Add a translation + "health-page.section.solrSearchCore.title": "Sor: search core", + + // "health-page.section.solrStatisticsCore.title": "Sor: statistics core", + // TODO New key - Add a translation + "health-page.section.solrStatisticsCore.title": "Sor: statistics core", + + // "health-page.section-info.app.title": "Application Backend", + // TODO New key - Add a translation + "health-page.section-info.app.title": "Application Backend", + + // "health-page.section-info.java.title": "Java", + // TODO New key - Add a translation + "health-page.section-info.java.title": "Java", + + // "health-page.status": "Status", + // TODO New key - Add a translation + "health-page.status": "Status", + + // "health-page.status.ok.info": "Operational", + // TODO New key - Add a translation + "health-page.status.ok.info": "Operational", + + // "health-page.status.error.info": "Problems detected", + // TODO New key - Add a translation + "health-page.status.error.info": "Problems detected", + + // "health-page.status.warning.info": "Possible issues detected", + // TODO New key - Add a translation + "health-page.status.warning.info": "Possible issues detected", + + // "health-page.title": "Health", + // TODO New key - Add a translation + "health-page.title": "Health", + + // "health-page.section.no-issues": "No issues detected", + // TODO New key - Add a translation + "health-page.section.no-issues": "No issues detected", + + // "home.description": "", // TODO New key - Add a translation "home.description": "", - + // "home.breadcrumbs": "Home", // TODO New key - Add a translation "home.breadcrumbs": "Home", - - // "home.title": "DSpace Angular :: Home", + + // "home.search-form.placeholder": "Search the repository ...", + "home.search-form.placeholder": "Pesquise no repositório...", + + // "home.title": "Home", + // TODO Source message changed - Revise the translation "home.title": "DSpace Angular :: Início", - + // "home.top-level-communities.head": "Communities in DSpace", "home.top-level-communities.head": "Comunidades no DSpace", - + // "home.top-level-communities.help": "Select a community to browse its collections.", "home.top-level-communities.help": "Selecione uma comunidade para navegar por suas coleções", - - - + + + // "info.end-user-agreement.accept": "I have read and I agree to the End User Agreement", // TODO New key - Add a translation "info.end-user-agreement.accept": "I have read and I agree to the End User Agreement", - + // "info.end-user-agreement.accept.error": "An error occurred accepting the End User Agreement", // TODO New key - Add a translation "info.end-user-agreement.accept.error": "An error occurred accepting the End User Agreement", - + // "info.end-user-agreement.accept.success": "Successfully updated the End User Agreement", // TODO New key - Add a translation "info.end-user-agreement.accept.success": "Successfully updated the End User Agreement", - + // "info.end-user-agreement.breadcrumbs": "End User Agreement", // TODO New key - Add a translation "info.end-user-agreement.breadcrumbs": "End User Agreement", - + // "info.end-user-agreement.buttons.cancel": "Cancel", // TODO New key - Add a translation "info.end-user-agreement.buttons.cancel": "Cancel", - + // "info.end-user-agreement.buttons.save": "Save", // TODO New key - Add a translation "info.end-user-agreement.buttons.save": "Save", - + // "info.end-user-agreement.head": "End User Agreement", // TODO New key - Add a translation "info.end-user-agreement.head": "End User Agreement", - + // "info.end-user-agreement.title": "End User Agreement", // TODO New key - Add a translation "info.end-user-agreement.title": "End User Agreement", - + // "info.privacy.breadcrumbs": "Privacy Statement", // TODO New key - Add a translation "info.privacy.breadcrumbs": "Privacy Statement", - + // "info.privacy.head": "Privacy Statement", // TODO New key - Add a translation "info.privacy.head": "Privacy Statement", - + // "info.privacy.title": "Privacy Statement", // TODO New key - Add a translation "info.privacy.title": "Privacy Statement", - - - - // "item.alerts.private": "This item is private", + + // "info.feedback.breadcrumbs": "Feedback", // TODO New key - Add a translation - "item.alerts.private": "This item is private", - + "info.feedback.breadcrumbs": "Feedback", + + // "info.feedback.head": "Feedback", + // TODO New key - Add a translation + "info.feedback.head": "Feedback", + + // "info.feedback.title": "Feedback", + // TODO New key - Add a translation + "info.feedback.title": "Feedback", + + // "info.feedback.info": "Thanks for sharing your feedback about the DSpace system. Your comments are appreciated!", + // TODO New key - Add a translation + "info.feedback.info": "Thanks for sharing your feedback about the DSpace system. Your comments are appreciated!", + + // "info.feedback.email_help": "This address will be used to follow up on your feedback.", + // TODO New key - Add a translation + "info.feedback.email_help": "This address will be used to follow up on your feedback.", + + // "info.feedback.send": "Send Feedback", + // TODO New key - Add a translation + "info.feedback.send": "Send Feedback", + + // "info.feedback.comments": "Comments", + // TODO New key - Add a translation + "info.feedback.comments": "Comments", + + // "info.feedback.email-label": "Your Email", + // TODO New key - Add a translation + "info.feedback.email-label": "Your Email", + + // "info.feedback.create.success" : "Feedback Sent Successfully!", + // TODO New key - Add a translation + "info.feedback.create.success" : "Feedback Sent Successfully!", + + // "info.feedback.error.email.required" : "A valid email address is required", + // TODO New key - Add a translation + "info.feedback.error.email.required" : "A valid email address is required", + + // "info.feedback.error.message.required" : "A comment is required", + // TODO New key - Add a translation + "info.feedback.error.message.required" : "A comment is required", + + // "info.feedback.page-label" : "Page", + // TODO New key - Add a translation + "info.feedback.page-label" : "Page", + + // "info.feedback.page_help" : "Tha page related to your feedback", + // TODO New key - Add a translation + "info.feedback.page_help" : "Tha page related to your feedback", + + + + // "item.alerts.private": "This item is non-discoverable", + // TODO New key - Add a translation + "item.alerts.private": "This item is non-discoverable", + // "item.alerts.withdrawn": "This item has been withdrawn", // TODO New key - Add a translation "item.alerts.withdrawn": "This item has been withdrawn", - - - + + + // "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", // TODO New key - Add a translation "item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.", - + // "item.edit.authorizations.title": "Edit item's Policies", // TODO New key - Add a translation "item.edit.authorizations.title": "Edit item's Policies", - - - - // "item.badge.private": "Private", + + + + // "item.badge.private": "Non-discoverable", // TODO New key - Add a translation - "item.badge.private": "Private", - + "item.badge.private": "Non-discoverable", + // "item.badge.withdrawn": "Withdrawn", // TODO New key - Add a translation "item.badge.withdrawn": "Withdrawn", - - - + + + // "item.bitstreams.upload.bundle": "Bundle", // TODO New key - Add a translation "item.bitstreams.upload.bundle": "Bundle", - - // "item.bitstreams.upload.bundle.placeholder": "Select a bundle", + + // "item.bitstreams.upload.bundle.placeholder": "Select a bundle or input new bundle name", // TODO New key - Add a translation - "item.bitstreams.upload.bundle.placeholder": "Select a bundle", - + "item.bitstreams.upload.bundle.placeholder": "Select a bundle or input new bundle name", + // "item.bitstreams.upload.bundle.new": "Create bundle", // TODO New key - Add a translation "item.bitstreams.upload.bundle.new": "Create bundle", - + // "item.bitstreams.upload.bundles.empty": "This item doesn\'t contain any bundles to upload a bitstream to.", // TODO New key - Add a translation "item.bitstreams.upload.bundles.empty": "This item doesn\'t contain any bundles to upload a bitstream to.", - + // "item.bitstreams.upload.cancel": "Cancel", // TODO New key - Add a translation "item.bitstreams.upload.cancel": "Cancel", - + // "item.bitstreams.upload.drop-message": "Drop a file to upload", // TODO New key - Add a translation "item.bitstreams.upload.drop-message": "Drop a file to upload", - + // "item.bitstreams.upload.item": "Item: ", // TODO New key - Add a translation "item.bitstreams.upload.item": "Item: ", - + // "item.bitstreams.upload.notifications.bundle.created.content": "Successfully created new bundle.", // TODO New key - Add a translation "item.bitstreams.upload.notifications.bundle.created.content": "Successfully created new bundle.", - + // "item.bitstreams.upload.notifications.bundle.created.title": "Created bundle", // TODO New key - Add a translation "item.bitstreams.upload.notifications.bundle.created.title": "Created bundle", - + // "item.bitstreams.upload.notifications.upload.failed": "Upload failed. Please verify the content before retrying.", // TODO New key - Add a translation "item.bitstreams.upload.notifications.upload.failed": "Upload failed. Please verify the content before retrying.", - + // "item.bitstreams.upload.title": "Upload bitstream", // TODO New key - Add a translation "item.bitstreams.upload.title": "Upload bitstream", - - - + + + // "item.edit.bitstreams.bundle.edit.buttons.upload": "Upload", // TODO New key - Add a translation "item.edit.bitstreams.bundle.edit.buttons.upload": "Upload", - + // "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", // TODO New key - Add a translation "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", - + // "item.edit.bitstreams.bundle.load.all": "Load all ({{ total }})", // TODO New key - Add a translation "item.edit.bitstreams.bundle.load.all": "Load all ({{ total }})", - + // "item.edit.bitstreams.bundle.load.more": "Load more", // TODO New key - Add a translation "item.edit.bitstreams.bundle.load.more": "Load more", - + // "item.edit.bitstreams.bundle.name": "BUNDLE: {{ name }}", // TODO New key - Add a translation "item.edit.bitstreams.bundle.name": "BUNDLE: {{ name }}", - + // "item.edit.bitstreams.discard-button": "Discard", // TODO New key - Add a translation "item.edit.bitstreams.discard-button": "Discard", - + // "item.edit.bitstreams.edit.buttons.download": "Download", // TODO New key - Add a translation "item.edit.bitstreams.edit.buttons.download": "Download", - + // "item.edit.bitstreams.edit.buttons.drag": "Drag", // TODO New key - Add a translation "item.edit.bitstreams.edit.buttons.drag": "Drag", - + // "item.edit.bitstreams.edit.buttons.edit": "Edit", - // TODO New key - Add a translation - "item.edit.bitstreams.edit.buttons.edit": "Edit", - + "item.edit.bitstreams.edit.buttons.edit": "Editar", + // "item.edit.bitstreams.edit.buttons.remove": "Remove", // TODO New key - Add a translation "item.edit.bitstreams.edit.buttons.remove": "Remove", - + // "item.edit.bitstreams.edit.buttons.undo": "Undo changes", // TODO New key - Add a translation "item.edit.bitstreams.edit.buttons.undo": "Undo changes", - + // "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", // TODO New key - Add a translation "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", - + // "item.edit.bitstreams.headers.actions": "Actions", // TODO New key - Add a translation "item.edit.bitstreams.headers.actions": "Actions", - + // "item.edit.bitstreams.headers.bundle": "Bundle", // TODO New key - Add a translation "item.edit.bitstreams.headers.bundle": "Bundle", - + // "item.edit.bitstreams.headers.description": "Description", // TODO New key - Add a translation "item.edit.bitstreams.headers.description": "Description", - + // "item.edit.bitstreams.headers.format": "Format", // TODO New key - Add a translation "item.edit.bitstreams.headers.format": "Format", - + // "item.edit.bitstreams.headers.name": "Name", // TODO New key - Add a translation "item.edit.bitstreams.headers.name": "Name", - + // "item.edit.bitstreams.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", // TODO New key - Add a translation "item.edit.bitstreams.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", - + // "item.edit.bitstreams.notifications.discarded.title": "Changes discarded", // TODO New key - Add a translation "item.edit.bitstreams.notifications.discarded.title": "Changes discarded", - + // "item.edit.bitstreams.notifications.move.failed.title": "Error moving bitstreams", // TODO New key - Add a translation "item.edit.bitstreams.notifications.move.failed.title": "Error moving bitstreams", - + // "item.edit.bitstreams.notifications.move.saved.content": "Your move changes to this item's bitstreams and bundles have been saved.", // TODO New key - Add a translation "item.edit.bitstreams.notifications.move.saved.content": "Your move changes to this item's bitstreams and bundles have been saved.", - + // "item.edit.bitstreams.notifications.move.saved.title": "Move changes saved", // TODO New key - Add a translation "item.edit.bitstreams.notifications.move.saved.title": "Move changes saved", - + // "item.edit.bitstreams.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", // TODO New key - Add a translation "item.edit.bitstreams.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", - + // "item.edit.bitstreams.notifications.outdated.title": "Changes outdated", // TODO New key - Add a translation "item.edit.bitstreams.notifications.outdated.title": "Changes outdated", - + // "item.edit.bitstreams.notifications.remove.failed.title": "Error deleting bitstream", // TODO New key - Add a translation "item.edit.bitstreams.notifications.remove.failed.title": "Error deleting bitstream", - + // "item.edit.bitstreams.notifications.remove.saved.content": "Your removal changes to this item's bitstreams have been saved.", // TODO New key - Add a translation "item.edit.bitstreams.notifications.remove.saved.content": "Your removal changes to this item's bitstreams have been saved.", - + // "item.edit.bitstreams.notifications.remove.saved.title": "Removal changes saved", // TODO New key - Add a translation "item.edit.bitstreams.notifications.remove.saved.title": "Removal changes saved", - + // "item.edit.bitstreams.reinstate-button": "Undo", // TODO New key - Add a translation "item.edit.bitstreams.reinstate-button": "Undo", - + // "item.edit.bitstreams.save-button": "Save", // TODO New key - Add a translation "item.edit.bitstreams.save-button": "Save", - + // "item.edit.bitstreams.upload-button": "Upload", // TODO New key - Add a translation "item.edit.bitstreams.upload-button": "Upload", - - - + + + // "item.edit.delete.cancel": "Cancel", "item.edit.delete.cancel": "Cancelar", - + // "item.edit.delete.confirm": "Delete", "item.edit.delete.confirm": "Apagar", - + // "item.edit.delete.description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.", "item.edit.delete.description": "Você tem certeza que deseja apagar completamento este item? Atenção: No momento, nenhum vestígio restará.", - + // "item.edit.delete.error": "An error occurred while deleting the item", "item.edit.delete.error": "Ocorreu um erro ao apagar o item", - + // "item.edit.delete.header": "Delete item: {{ id }}", "item.edit.delete.header": "Apagar item: {{ id }}", - + // "item.edit.delete.success": "The item has been deleted", "item.edit.delete.success": "O item foi apagado", - + // "item.edit.head": "Edit Item", "item.edit.head": "Editar Item", - + // "item.edit.breadcrumbs": "Edit Item", // TODO New key - Add a translation "item.edit.breadcrumbs": "Edit Item", - - + + // "item.edit.tabs.disabled.tooltip": "You're not authorized to access this tab", + // TODO New key - Add a translation + "item.edit.tabs.disabled.tooltip": "You're not authorized to access this tab", + + // "item.edit.tabs.mapper.head": "Collection Mapper", // TODO New key - Add a translation "item.edit.tabs.mapper.head": "Collection Mapper", - + // "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper", // TODO New key - Add a translation "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper", - + // "item.edit.item-mapper.buttons.add": "Map item to selected collections", "item.edit.item-mapper.buttons.add": "Mapear item na(s) coleção(ões) seleciona(s)", - + // "item.edit.item-mapper.buttons.remove": "Remove item's mapping for selected collections", "item.edit.item-mapper.buttons.remove": "Remover mapeamento(s) do item da(s) coleção(ões) seleciona(s)", - + // "item.edit.item-mapper.cancel": "Cancel", "item.edit.item-mapper.cancel": "Cancelar", - + // "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", "item.edit.item-mapper.description": "Essa é a ferramenta de mapeamento de itens que permite que os administradores mapeiem esse item para outras coleções. Você pode procurar coleções e mapeá-las ou navegar na lista de coleções para as quais o item está atualmente mapeado.", - + // "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.head": "Mapeador de Item - Mapear Itens em Coleções", - + // "item.edit.item-mapper.item": "Item: \"{{name}}\"", "item.edit.item-mapper.item": "Item: \"{{name}}\"", - + // "item.edit.item-mapper.no-search": "Please enter a query to search", "item.edit.item-mapper.no-search": "Por favor informe uma consulta para buscar", - + // "item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.add.error.content": "Ocorreram erros ao mapear o item em {{amount}} coleções.", - + // "item.edit.item-mapper.notifications.add.error.head": "Mapping errors", "item.edit.item-mapper.notifications.add.error.head": "Erros de mapeamento", - + // "item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.", "item.edit.item-mapper.notifications.add.success.content": "Mapeou o item em {{amount}} coleções com sucesso.", - + // "item.edit.item-mapper.notifications.add.success.head": "Mapping completed", "item.edit.item-mapper.notifications.add.success.head": "Mapeamento complesto", - + // "item.edit.item-mapper.notifications.remove.error.content": "Errors occurred for the removal of the mapping to {{amount}} collections.", "item.edit.item-mapper.notifications.remove.error.content": "Ocorreram erros ao remover mapeamento do item em {{amount}} coleções.", - + // "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors", "item.edit.item-mapper.notifications.remove.error.head": "Erros de remoção de mapeamento", - + // "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", - + // "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed", "item.edit.item-mapper.notifications.remove.success.head": "Completou a remoção de mapeamento", - + + // "item.edit.item-mapper.search-form.placeholder": "Search collections...", + // TODO New key - Add a translation + "item.edit.item-mapper.search-form.placeholder": "Search collections...", + // "item.edit.item-mapper.tabs.browse": "Browse mapped collections", "item.edit.item-mapper.tabs.browse": "Navegar nas coleções mapeadas", - + // "item.edit.item-mapper.tabs.map": "Map new collections", "item.edit.item-mapper.tabs.map": "Mapear novas coleções", - - - + + + // "item.edit.metadata.add-button": "Add", "item.edit.metadata.add-button": "Adicionar", - + // "item.edit.metadata.discard-button": "Discard", "item.edit.metadata.discard-button": "Descartar", - + // "item.edit.metadata.edit.buttons.edit": "Edit", "item.edit.metadata.edit.buttons.edit": "Editar", - + // "item.edit.metadata.edit.buttons.remove": "Remove", "item.edit.metadata.edit.buttons.remove": "Apagar", - + // "item.edit.metadata.edit.buttons.undo": "Undo changes", "item.edit.metadata.edit.buttons.undo": "Desfazer alterações", - + // "item.edit.metadata.edit.buttons.unedit": "Stop editing", "item.edit.metadata.edit.buttons.unedit": "Parar edição", - + // "item.edit.metadata.empty": "The item currently doesn't contain any metadata. Click Add to start adding a metadata value.", // TODO New key - Add a translation "item.edit.metadata.empty": "The item currently doesn't contain any metadata. Click Add to start adding a metadata value.", - + // "item.edit.metadata.headers.edit": "Edit", "item.edit.metadata.headers.edit": "Editar", - + // "item.edit.metadata.headers.field": "Field", "item.edit.metadata.headers.field": "Campo", - + // "item.edit.metadata.headers.language": "Lang", "item.edit.metadata.headers.language": "Idioma", - + // "item.edit.metadata.headers.value": "Value", "item.edit.metadata.headers.value": "Valor", - + // "item.edit.metadata.metadatafield.invalid": "Please choose a valid metadata field", "item.edit.metadata.metadatafield.invalid": "Por favor escolha um campo de metadados válido", - + // "item.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", "item.edit.metadata.notifications.discarded.content": "Suas alterações foram descartadas. Para restabelecer suas alterações, clique no botão 'Desfazer'", - - // "item.edit.metadata.notifications.discarded.title": "Changed discarded", + + // "item.edit.metadata.notifications.discarded.title": "Changes discarded", + // TODO Source message changed - Revise the translation "item.edit.metadata.notifications.discarded.title": "Mudança descartada", - + // "item.edit.metadata.notifications.error.title": "An error occurred", // TODO New key - Add a translation "item.edit.metadata.notifications.error.title": "An error occurred", - + // "item.edit.metadata.notifications.invalid.content": "Your changes were not saved. Please make sure all fields are valid before you save.", "item.edit.metadata.notifications.invalid.content": "Suas alterações não foram salvas. Verifique se todos os campos são válidos antes de salvar.", - + // "item.edit.metadata.notifications.invalid.title": "Metadata invalid", "item.edit.metadata.notifications.invalid.title": "Metadado inválido", - + // "item.edit.metadata.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", "item.edit.metadata.notifications.outdated.content": "O item em que você está trabalhando foi alterado por outro usuário. Suas alterações atuais são descartadas para evitar conflitos", - - // "item.edit.metadata.notifications.outdated.title": "Changed outdated", + + // "item.edit.metadata.notifications.outdated.title": "Changes outdated", + // TODO Source message changed - Revise the translation "item.edit.metadata.notifications.outdated.title": "Alteração desatualizada", - + // "item.edit.metadata.notifications.saved.content": "Your changes to this item's metadata were saved.", "item.edit.metadata.notifications.saved.content": "Suas alterações nos metadados deste item foram salvas.", - + // "item.edit.metadata.notifications.saved.title": "Metadata saved", "item.edit.metadata.notifications.saved.title": "Metadados salvos", - + // "item.edit.metadata.reinstate-button": "Undo", "item.edit.metadata.reinstate-button": "Desfazer", - + // "item.edit.metadata.save-button": "Save", "item.edit.metadata.save-button": "Salvar", - - - + + + // "item.edit.modify.overview.field": "Field", "item.edit.modify.overview.field": "Campo", - + // "item.edit.modify.overview.language": "Language", "item.edit.modify.overview.language": "Idioma", - + // "item.edit.modify.overview.value": "Value", "item.edit.modify.overview.value": "Valor", - - - - // "item.edit.move.cancel": "Cancel", + + + + // "item.edit.move.cancel": "Back", + // TODO Source message changed - Revise the translation "item.edit.move.cancel": "Cancelar", - + + // "item.edit.move.save-button": "Save", + // TODO New key - Add a translation + "item.edit.move.save-button": "Save", + + // "item.edit.move.discard-button": "Discard", + // TODO New key - Add a translation + "item.edit.move.discard-button": "Discard", + // "item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.", "item.edit.move.description": "Selecione a coleção para a qual você deseja mover este item. Para restringir a lista de coleções exibidas, você pode inserir uma consulta de pesquisa na caixa.", - + // "item.edit.move.error": "An error occurred when attempting to move the item", "item.edit.move.error": "Ocorreu um erro ao tentar mover o item", - + // "item.edit.move.head": "Move item: {{id}}", "item.edit.move.head": "Mover item: {{id}}", - + // "item.edit.move.inheritpolicies.checkbox": "Inherit policies", "item.edit.move.inheritpolicies.checkbox": "Herdar politicas", - + // "item.edit.move.inheritpolicies.description": "Inherit the default policies of the destination collection", "item.edit.move.inheritpolicies.description": "Herdar as politicas padrões da coleção destino", - + // "item.edit.move.move": "Move", "item.edit.move.move": "Mover", - + // "item.edit.move.processing": "Moving...", "item.edit.move.processing": "Movendo...", - + // "item.edit.move.search.placeholder": "Enter a search query to look for collections", "item.edit.move.search.placeholder": "Insira uma consulta para procurar coleções", - + // "item.edit.move.success": "The item has been moved successfully", "item.edit.move.success": "O item foi movido com sucesso", - + // "item.edit.move.title": "Move item", "item.edit.move.title": "Mover item", - - - + + + // "item.edit.private.cancel": "Cancel", "item.edit.private.cancel": "Cancelar", - - // "item.edit.private.confirm": "Make it Private", + + // "item.edit.private.confirm": "Make it non-discoverable", + // TODO Source message changed - Revise the translation "item.edit.private.confirm": "Tornar Privado", - - // "item.edit.private.description": "Are you sure this item should be made private in the archive?", + + // "item.edit.private.description": "Are you sure this item should be made non-discoverable in the archive?", + // TODO Source message changed - Revise the translation "item.edit.private.description": "Tem certeza de que este item deve ser tornado privado no arquivo?", - - // "item.edit.private.error": "An error occurred while making the item private", + + // "item.edit.private.error": "An error occurred while making the item non-discoverable", + // TODO Source message changed - Revise the translation "item.edit.private.error": "Ocorreu um erro ao tornar o item privado", - - // "item.edit.private.header": "Make item private: {{ id }}", - "item.edit.private.header": "Tornar privado o item: {{ id }}", - - // "item.edit.private.success": "The item is now private", + + // "item.edit.private.header": "Make item non-discoverable: {{ id }}", + // TODO New key - Add a translation + "item.edit.private.header": "Make item non-discoverable: {{ id }}", + + // "item.edit.private.success": "The item is now non-discoverable", + // TODO Source message changed - Revise the translation "item.edit.private.success": "O item agora é privado", - - - + + + // "item.edit.public.cancel": "Cancel", "item.edit.public.cancel": "Cancelar", - - // "item.edit.public.confirm": "Make it Public", + + // "item.edit.public.confirm": "Make it discoverable", + // TODO Source message changed - Revise the translation "item.edit.public.confirm": "Tornar público", - - // "item.edit.public.description": "Are you sure this item should be made public in the archive?", + + // "item.edit.public.description": "Are you sure this item should be made discoverable in the archive?", + // TODO Source message changed - Revise the translation "item.edit.public.description": "Você tem certeza que deseja tornar este item público no arquivo?", - - // "item.edit.public.error": "An error occurred while making the item public", + + // "item.edit.public.error": "An error occurred while making the item discoverable", + // TODO Source message changed - Revise the translation "item.edit.public.error": "Ocorreu um erro ao tornar o item público", - - // "item.edit.public.header": "Make item public: {{ id }}", - "item.edit.public.header": "Tornar público o item: {{ id }}", - - // "item.edit.public.success": "The item is now public", + + // "item.edit.public.header": "Make item discoverable: {{ id }}", + // TODO New key - Add a translation + "item.edit.public.header": "Make item discoverable: {{ id }}", + + // "item.edit.public.success": "The item is now discoverable", + // TODO Source message changed - Revise the translation "item.edit.public.success": "O item agora é público", - - - + + + // "item.edit.reinstate.cancel": "Cancel", "item.edit.reinstate.cancel": "Cancelar", - + // "item.edit.reinstate.confirm": "Reinstate", "item.edit.reinstate.confirm": "Restabelecer", - + // "item.edit.reinstate.description": "Are you sure this item should be reinstated to the archive?", "item.edit.reinstate.description": "Tem certeza de que este item deve ser restabelecido no arquivo?", - + // "item.edit.reinstate.error": "An error occurred while reinstating the item", "item.edit.reinstate.error": "Ocorreu um erro ao restabelecer o item", - + // "item.edit.reinstate.header": "Reinstate item: {{ id }}", "item.edit.reinstate.header": "Restabelecer item: {{ id }}", - + // "item.edit.reinstate.success": "The item was reinstated successfully", "item.edit.reinstate.success": "O item foi restabelecido com sucesso", - - - + + + // "item.edit.relationships.discard-button": "Discard", "item.edit.relationships.discard-button": "Descartar", - + // "item.edit.relationships.edit.buttons.add": "Add", - // TODO New key - Add a translation - "item.edit.relationships.edit.buttons.add": "Add", - + "item.edit.relationships.edit.buttons.add": "Adicionar", + // "item.edit.relationships.edit.buttons.remove": "Remove", "item.edit.relationships.edit.buttons.remove": "Apagar", - + // "item.edit.relationships.edit.buttons.undo": "Undo changes", "item.edit.relationships.edit.buttons.undo": "Desfazer alterações", - + // "item.edit.relationships.no-relationships": "No relationships", // TODO New key - Add a translation "item.edit.relationships.no-relationships": "No relationships", - + // "item.edit.relationships.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button", "item.edit.relationships.notifications.discarded.content": "Suas alterações foram descartadas. Para restabelecer suas alterações, clique no botão 'Desfazer'", - + // "item.edit.relationships.notifications.discarded.title": "Changes discarded", "item.edit.relationships.notifications.discarded.title": "Alterações descartadas", - + // "item.edit.relationships.notifications.failed.title": "Error editing relationships", // TODO Source message changed - Revise the translation "item.edit.relationships.notifications.failed.title": "Erro ao apagar relacionamento", - + // "item.edit.relationships.notifications.outdated.content": "The item you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts", "item.edit.relationships.notifications.outdated.content": "O item em que você está trabalhando foi alterado por outro usuário. Suas alterações atuais são descartadas para evitar conflitos", - + // "item.edit.relationships.notifications.outdated.title": "Changes outdated", "item.edit.relationships.notifications.outdated.title": "Alterações desatualizadas", - + // "item.edit.relationships.notifications.saved.content": "Your changes to this item's relationships were saved.", "item.edit.relationships.notifications.saved.content": "Suas alterações nos relacionamentos deste item foram salvas.", - + // "item.edit.relationships.notifications.saved.title": "Relationships saved", "item.edit.relationships.notifications.saved.title": "Relacionamentos salvos", - + // "item.edit.relationships.reinstate-button": "Undo", "item.edit.relationships.reinstate-button": "Desfazer", - + // "item.edit.relationships.save-button": "Save", "item.edit.relationships.save-button": "Salvar", - + // "item.edit.relationships.no-entity-type": "Add 'dspace.entity.type' metadata to enable relationships for this item", // TODO New key - Add a translation "item.edit.relationships.no-entity-type": "Add 'dspace.entity.type' metadata to enable relationships for this item", - - - + + + // "item.edit.return": "Back", + "item.edit.return": "Voltar", + + // "item.edit.tabs.bitstreams.head": "Bitstreams", - // TODO Source message changed - Revise the translation - "item.edit.tabs.bitstreams.head": "Bitstreams do Item", - + "item.edit.tabs.bitstreams.head": "Bitstreams", + // "item.edit.tabs.bitstreams.title": "Item Edit - Bitstreams", "item.edit.tabs.bitstreams.title": "Editar Item - Bitstreams", - + // "item.edit.tabs.curate.head": "Curate", "item.edit.tabs.curate.head": "Curadoria", - + // "item.edit.tabs.curate.title": "Item Edit - Curate", "item.edit.tabs.curate.title": "Editar Item - Curadoria", - + // "item.edit.tabs.metadata.head": "Metadata", - // TODO Source message changed - Revise the translation - "item.edit.tabs.metadata.head": "Metadados do Item", - + "item.edit.tabs.metadata.head": "Metadados", + // "item.edit.tabs.metadata.title": "Item Edit - Metadata", "item.edit.tabs.metadata.title": "Editar Item - Metadados", - + // "item.edit.tabs.relationships.head": "Relationships", - // TODO Source message changed - Revise the translation - "item.edit.tabs.relationships.head": "Relacionamentos do Item", - + "item.edit.tabs.relationships.head": "Relacionamentos", + // "item.edit.tabs.relationships.title": "Item Edit - Relationships", "item.edit.tabs.relationships.title": "Editar Item - Relacionamentos", - + // "item.edit.tabs.status.buttons.authorizations.button": "Authorizations...", "item.edit.tabs.status.buttons.authorizations.button": "Autorizações...", - + // "item.edit.tabs.status.buttons.authorizations.label": "Edit item's authorization policies", "item.edit.tabs.status.buttons.authorizations.label": "Editar politicas de autorizações de item", - + // "item.edit.tabs.status.buttons.delete.button": "Permanently delete", "item.edit.tabs.status.buttons.delete.button": "Apagar permanentemente", - + // "item.edit.tabs.status.buttons.delete.label": "Completely expunge item", "item.edit.tabs.status.buttons.delete.label": "Eliminar completamente o item", - + // "item.edit.tabs.status.buttons.mappedCollections.button": "Mapped collections", "item.edit.tabs.status.buttons.mappedCollections.button": "Coleções mapeadas", - + // "item.edit.tabs.status.buttons.mappedCollections.label": "Manage mapped collections", "item.edit.tabs.status.buttons.mappedCollections.label": "Gerenciar coleções mapeadas", - + // "item.edit.tabs.status.buttons.move.button": "Move...", "item.edit.tabs.status.buttons.move.button": "Mover...", - + // "item.edit.tabs.status.buttons.move.label": "Move item to another collection", "item.edit.tabs.status.buttons.move.label": "Mover item para outra coleção", - - // "item.edit.tabs.status.buttons.private.button": "Make it private...", - "item.edit.tabs.status.buttons.private.button": "Tornar privado o item...", - - // "item.edit.tabs.status.buttons.private.label": "Make item private", - "item.edit.tabs.status.buttons.private.label": "Tornar privado o item", - - // "item.edit.tabs.status.buttons.public.button": "Make it public...", - "item.edit.tabs.status.buttons.public.button": "Tornar público o item...", - - // "item.edit.tabs.status.buttons.public.label": "Make item public", - "item.edit.tabs.status.buttons.public.label": "Tornar público o item", - + + // "item.edit.tabs.status.buttons.private.button": "Make it non-discoverable...", + "item.edit.tabs.status.buttons.private.button": "Tornar o item privado...", + + // "item.edit.tabs.status.buttons.private.label": "Make item non-discoverable", + "item.edit.tabs.status.buttons.private.label": "Tornar item privado", + + // "item.edit.tabs.status.buttons.public.button": "Make it discoverable...", + "item.edit.tabs.status.buttons.public.button": "Tornar item público...", + + // "item.edit.tabs.status.buttons.public.label": "Make item discoverable", + "item.edit.tabs.status.buttons.public.label": "Tornar item público", + // "item.edit.tabs.status.buttons.reinstate.button": "Reinstate...", "item.edit.tabs.status.buttons.reinstate.button": "Restabelecer...", - + // "item.edit.tabs.status.buttons.reinstate.label": "Reinstate item into the repository", "item.edit.tabs.status.buttons.reinstate.label": "Restabelecer item no repositório", - + + // "item.edit.tabs.status.buttons.unauthorized": "You're not authorized to perform this action", + // TODO New key - Add a translation + "item.edit.tabs.status.buttons.unauthorized": "You're not authorized to perform this action", + // "item.edit.tabs.status.buttons.withdraw.button": "Withdraw...", "item.edit.tabs.status.buttons.withdraw.button": "Retirar...", - + // "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", "item.edit.tabs.status.buttons.withdraw.label": "Retirar item do repositório", - + // "item.edit.tabs.status.description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.", "item.edit.tabs.status.description": "Bem-vindo à página de gerenciamento de itens. A partir daqui, você pode retirar, restabelecer, mover ou apagar o item. Você também pode atualizar ou adicionar novos metadados / bitstream nas outras guias.", - + // "item.edit.tabs.status.head": "Status", - // TODO Source message changed - Revise the translation - "item.edit.tabs.status.head": "Estado do Item", - + "item.edit.tabs.status.head": "Situação", + // "item.edit.tabs.status.labels.handle": "Handle", "item.edit.tabs.status.labels.handle": "Handle", - + // "item.edit.tabs.status.labels.id": "Item Internal ID", "item.edit.tabs.status.labels.id": "ID Interno do Item", - + // "item.edit.tabs.status.labels.itemPage": "Item Page", "item.edit.tabs.status.labels.itemPage": "Página do Item", - + // "item.edit.tabs.status.labels.lastModified": "Last Modified", "item.edit.tabs.status.labels.lastModified": "Ultima alteração", - + // "item.edit.tabs.status.title": "Item Edit - Status", "item.edit.tabs.status.title": "Editar Item - Estado", - + // "item.edit.tabs.versionhistory.head": "Version History", // TODO New key - Add a translation "item.edit.tabs.versionhistory.head": "Version History", - + // "item.edit.tabs.versionhistory.title": "Item Edit - Version History", // TODO New key - Add a translation "item.edit.tabs.versionhistory.title": "Item Edit - Version History", - + // "item.edit.tabs.versionhistory.under-construction": "Editing or adding new versions is not yet possible in this user interface.", // TODO New key - Add a translation "item.edit.tabs.versionhistory.under-construction": "Editing or adding new versions is not yet possible in this user interface.", - + // "item.edit.tabs.view.head": "View Item", "item.edit.tabs.view.head": "Visualizar Item", - + // "item.edit.tabs.view.title": "Item Edit - View", "item.edit.tabs.view.title": "Editar Item - Visualizar", - - - + + + // "item.edit.withdraw.cancel": "Cancel", "item.edit.withdraw.cancel": "Cancelar", - + // "item.edit.withdraw.confirm": "Withdraw", "item.edit.withdraw.confirm": "Retirar", - + // "item.edit.withdraw.description": "Are you sure this item should be withdrawn from the archive?", "item.edit.withdraw.description": "Tem certeza de que este item deve ser retirado do arquivo?", - + // "item.edit.withdraw.error": "An error occurred while withdrawing the item", "item.edit.withdraw.error": "Ocorreu um erro ao retirar o item", - + // "item.edit.withdraw.header": "Withdraw item: {{ id }}", "item.edit.withdraw.header": "Retirar item: {{ id }}", - + // "item.edit.withdraw.success": "The item was withdrawn successfully", "item.edit.withdraw.success": "O item foi retirado com sucesso", - - - + + // "item.orcid.return": "Back", + // TODO New key - Add a translation + "item.orcid.return": "Back", + + // "item.listelement.badge": "Item", // TODO New key - Add a translation "item.listelement.badge": "Item", - + // "item.page.description": "Description", // TODO New key - Add a translation "item.page.description": "Description", - - // "item.page.edit": "Edit this item", - // TODO New key - Add a translation - "item.page.edit": "Edit this item", - + // "item.page.journal-issn": "Journal ISSN", // TODO New key - Add a translation "item.page.journal-issn": "Journal ISSN", - + // "item.page.journal-title": "Journal Title", // TODO New key - Add a translation "item.page.journal-title": "Journal Title", - + // "item.page.publisher": "Publisher", // TODO New key - Add a translation "item.page.publisher": "Publisher", - + // "item.page.titleprefix": "Item: ", // TODO New key - Add a translation "item.page.titleprefix": "Item: ", - + // "item.page.volume-title": "Volume Title", // TODO New key - Add a translation "item.page.volume-title": "Volume Title", - + // "item.search.results.head": "Item Search Results", // TODO New key - Add a translation "item.search.results.head": "Item Search Results", - - // "item.search.title": "DSpace Angular :: Item Search", + + // "item.search.title": "Item Search", // TODO New key - Add a translation - "item.search.title": "DSpace Angular :: Item Search", - - - + "item.search.title": "Item Search", + + // "item.truncatable-part.show-more": "Show more", + // TODO New key - Add a translation + "item.truncatable-part.show-more": "Show more", + + // "item.truncatable-part.show-less": "Collapse", + // TODO New key - Add a translation + "item.truncatable-part.show-less": "Collapse", + + + // "item.page.abstract": "Abstract", "item.page.abstract": "Resumo", - + // "item.page.author": "Authors", "item.page.author": "Autores", - + // "item.page.citation": "Citation", "item.page.citation": "Citação", - + // "item.page.collections": "Collections", "item.page.collections": "Coleções", - + + // "item.page.collections.loading": "Loading...", + // TODO New key - Add a translation + "item.page.collections.loading": "Loading...", + + // "item.page.collections.load-more": "Load more", + // TODO New key - Add a translation + "item.page.collections.load-more": "Load more", + // "item.page.date": "Date", "item.page.date": "Data", - + // "item.page.edit": "Edit this item", // TODO New key - Add a translation "item.page.edit": "Edit this item", - + // "item.page.files": "Files", "item.page.files": "Arquivos", - + // "item.page.filesection.description": "Description:", "item.page.filesection.description": "Descrição:", - + // "item.page.filesection.download": "Download", "item.page.filesection.download": "Baixar", - + // "item.page.filesection.format": "Format:", "item.page.filesection.format": "Formato:", - + // "item.page.filesection.name": "Name:", "item.page.filesection.name": "Nome:", - + // "item.page.filesection.size": "Size:", "item.page.filesection.size": "Tamanho:", - + // "item.page.journal.search.title": "Articles in this journal", "item.page.journal.search.title": "Articles in this journal", - + // "item.page.link.full": "Full item page", "item.page.link.full": "Página do item completo", - + // "item.page.link.simple": "Simple item page", "item.page.link.simple": "Página do item simplificado", - + + // "item.page.orcid.title": "ORCID", + // TODO New key - Add a translation + "item.page.orcid.title": "ORCID", + + // "item.page.orcid.tooltip": "Open ORCID setting page", + // TODO New key - Add a translation + "item.page.orcid.tooltip": "Open ORCID setting page", + // "item.page.person.search.title": "Articles by this author", "item.page.person.search.title": "Artigos deste autor", - + // "item.page.related-items.view-more": "Show {{ amount }} more", - // TODO Source message changed - Revise the translation - "item.page.related-items.view-more": "Mostrar mais", - + "item.page.related-items.view-more": "Mostrar mais {{ amount }}", + // "item.page.related-items.view-less": "Hide last {{ amount }}", - // TODO Source message changed - Revise the translation - "item.page.related-items.view-less": "Mostrar menos", - + "item.page.related-items.view-less": "Ocultar o último {{ amount }}", + // "item.page.relationships.isAuthorOfPublication": "Publications", // TODO New key - Add a translation "item.page.relationships.isAuthorOfPublication": "Publications", - + // "item.page.relationships.isJournalOfPublication": "Publications", // TODO New key - Add a translation "item.page.relationships.isJournalOfPublication": "Publications", - + // "item.page.relationships.isOrgUnitOfPerson": "Authors", // TODO New key - Add a translation "item.page.relationships.isOrgUnitOfPerson": "Authors", - + // "item.page.relationships.isOrgUnitOfProject": "Research Projects", // TODO New key - Add a translation "item.page.relationships.isOrgUnitOfProject": "Research Projects", - + // "item.page.subject": "Keywords", "item.page.subject": "Palavras-chave", - + // "item.page.uri": "URI", "item.page.uri": "URI", - + // "item.page.bitstreams.view-more": "Show more", // TODO New key - Add a translation "item.page.bitstreams.view-more": "Show more", - + // "item.page.bitstreams.collapse": "Collapse", // TODO New key - Add a translation "item.page.bitstreams.collapse": "Collapse", - + // "item.page.filesection.original.bundle" : "Original bundle", // TODO New key - Add a translation "item.page.filesection.original.bundle" : "Original bundle", - + // "item.page.filesection.license.bundle" : "License bundle", // TODO New key - Add a translation "item.page.filesection.license.bundle" : "License bundle", - + + // "item.page.return": "Back", + // TODO New key - Add a translation + "item.page.return": "Back", + + // "item.page.version.create": "Create new version", + // TODO New key - Add a translation + "item.page.version.create": "Create new version", + + // "item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", + // TODO New key - Add a translation + "item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", + + // "item.page.claim.button": "Claim", + // TODO New key - Add a translation + "item.page.claim.button": "Claim", + + // "item.page.claim.tooltip": "Claim this item as profile", + // TODO New key - Add a translation + "item.page.claim.tooltip": "Claim this item as profile", + // "item.preview.dc.identifier.uri": "Identifier:", // TODO New key - Add a translation "item.preview.dc.identifier.uri": "Identifier:", - + // "item.preview.dc.contributor.author": "Authors:", // TODO New key - Add a translation "item.preview.dc.contributor.author": "Authors:", - + // "item.preview.dc.date.issued": "Published date:", // TODO New key - Add a translation "item.preview.dc.date.issued": "Published date:", - + // "item.preview.dc.description.abstract": "Abstract:", // TODO New key - Add a translation "item.preview.dc.description.abstract": "Abstract:", - + // "item.preview.dc.identifier.other": "Other identifier:", // TODO New key - Add a translation "item.preview.dc.identifier.other": "Other identifier:", - + // "item.preview.dc.language.iso": "Language:", // TODO New key - Add a translation "item.preview.dc.language.iso": "Language:", - + // "item.preview.dc.subject": "Subjects:", // TODO New key - Add a translation "item.preview.dc.subject": "Subjects:", - + // "item.preview.dc.title": "Title:", // TODO New key - Add a translation "item.preview.dc.title": "Title:", - + + // "item.preview.dc.type": "Type:", + // TODO New key - Add a translation + "item.preview.dc.type": "Type:", + + // "item.preview.oaire.citation.issue" : "Issue", + // TODO New key - Add a translation + "item.preview.oaire.citation.issue" : "Issue", + + // "item.preview.oaire.citation.volume" : "Volume", + // TODO New key - Add a translation + "item.preview.oaire.citation.volume" : "Volume", + + // "item.preview.dc.relation.issn" : "ISSN", + // TODO New key - Add a translation + "item.preview.dc.relation.issn" : "ISSN", + + // "item.preview.dc.identifier.isbn" : "ISBN", + // TODO New key - Add a translation + "item.preview.dc.identifier.isbn" : "ISBN", + + // "item.preview.dc.identifier": "Identifier:", + // TODO New key - Add a translation + "item.preview.dc.identifier": "Identifier:", + + // "item.preview.dc.relation.ispartof" : "Journal or Serie", + // TODO New key - Add a translation + "item.preview.dc.relation.ispartof" : "Journal or Serie", + + // "item.preview.dc.identifier.doi" : "DOI", + // TODO New key - Add a translation + "item.preview.dc.identifier.doi" : "DOI", + // "item.preview.person.familyName": "Surname:", // TODO New key - Add a translation "item.preview.person.familyName": "Surname:", - + // "item.preview.person.givenName": "Name:", // TODO New key - Add a translation "item.preview.person.givenName": "Name:", - + // "item.preview.person.identifier.orcid": "ORCID:", // TODO New key - Add a translation "item.preview.person.identifier.orcid": "ORCID:", - - + + // "item.preview.project.funder.name": "Funder:", + // TODO New key - Add a translation + "item.preview.project.funder.name": "Funder:", + + // "item.preview.project.funder.identifier": "Funder Identifier:", + // TODO New key - Add a translation + "item.preview.project.funder.identifier": "Funder Identifier:", + + // "item.preview.oaire.awardNumber": "Funding ID:", + // TODO New key - Add a translation + "item.preview.oaire.awardNumber": "Funding ID:", + + // "item.preview.dc.title.alternative": "Acronym:", + // TODO New key - Add a translation + "item.preview.dc.title.alternative": "Acronym:", + + // "item.preview.dc.coverage.spatial": "Jurisdiction:", + // TODO New key - Add a translation + "item.preview.dc.coverage.spatial": "Jurisdiction:", + + // "item.preview.oaire.fundingStream": "Funding Stream:", + // TODO New key - Add a translation + "item.preview.oaire.fundingStream": "Funding Stream:", + + + // "item.select.confirm": "Confirm selected", "item.select.confirm": "Confirmar seleção", - + // "item.select.empty": "No items to show", "item.select.empty": "Nenhum itme a mostrar", - + // "item.select.table.author": "Author", "item.select.table.author": "Autor", - + // "item.select.table.collection": "Collection", "item.select.table.collection": "Coleção", - + // "item.select.table.title": "Title", "item.select.table.title": "Título", - - + + // "item.version.history.empty": "There are no other versions for this item yet.", // TODO New key - Add a translation "item.version.history.empty": "There are no other versions for this item yet.", - + // "item.version.history.head": "Version History", // TODO New key - Add a translation "item.version.history.head": "Version History", - - // "item.version.history.return": "Return", + + // "item.version.history.return": "Back", // TODO New key - Add a translation - "item.version.history.return": "Return", - + "item.version.history.return": "Back", + // "item.version.history.selected": "Selected version", // TODO New key - Add a translation "item.version.history.selected": "Selected version", - + + // "item.version.history.selected.alert": "You are currently viewing version {{version}} of the item.", + // TODO New key - Add a translation + "item.version.history.selected.alert": "You are currently viewing version {{version}} of the item.", + // "item.version.history.table.version": "Version", // TODO New key - Add a translation "item.version.history.table.version": "Version", - + // "item.version.history.table.item": "Item", // TODO New key - Add a translation "item.version.history.table.item": "Item", - + // "item.version.history.table.editor": "Editor", // TODO New key - Add a translation "item.version.history.table.editor": "Editor", - + // "item.version.history.table.date": "Date", // TODO New key - Add a translation "item.version.history.table.date": "Date", - + // "item.version.history.table.summary": "Summary", // TODO New key - Add a translation "item.version.history.table.summary": "Summary", - - - + + // "item.version.history.table.workspaceItem": "Workspace item", + // TODO New key - Add a translation + "item.version.history.table.workspaceItem": "Workspace item", + + // "item.version.history.table.workflowItem": "Workflow item", + // TODO New key - Add a translation + "item.version.history.table.workflowItem": "Workflow item", + + // "item.version.history.table.actions": "Action", + // TODO New key - Add a translation + "item.version.history.table.actions": "Action", + + // "item.version.history.table.action.editWorkspaceItem": "Edit workspace item", + // TODO New key - Add a translation + "item.version.history.table.action.editWorkspaceItem": "Edit workspace item", + + // "item.version.history.table.action.editSummary": "Edit summary", + // TODO New key - Add a translation + "item.version.history.table.action.editSummary": "Edit summary", + + // "item.version.history.table.action.saveSummary": "Save summary edits", + // TODO New key - Add a translation + "item.version.history.table.action.saveSummary": "Save summary edits", + + // "item.version.history.table.action.discardSummary": "Discard summary edits", + // TODO New key - Add a translation + "item.version.history.table.action.discardSummary": "Discard summary edits", + + // "item.version.history.table.action.newVersion": "Create new version from this one", + // TODO New key - Add a translation + "item.version.history.table.action.newVersion": "Create new version from this one", + + // "item.version.history.table.action.deleteVersion": "Delete version", + // TODO New key - Add a translation + "item.version.history.table.action.deleteVersion": "Delete version", + + // "item.version.history.table.action.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", + // TODO New key - Add a translation + "item.version.history.table.action.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", + + // "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", // TODO New key - Add a translation "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", - - - + + + // "item.version.create.modal.header": "New version", + // TODO New key - Add a translation + "item.version.create.modal.header": "New version", + + // "item.version.create.modal.text": "Create a new version for this item", + // TODO New key - Add a translation + "item.version.create.modal.text": "Create a new version for this item", + + // "item.version.create.modal.text.startingFrom": "starting from version {{version}}", + // TODO New key - Add a translation + "item.version.create.modal.text.startingFrom": "starting from version {{version}}", + + // "item.version.create.modal.button.confirm": "Create", + // TODO New key - Add a translation + "item.version.create.modal.button.confirm": "Create", + + // "item.version.create.modal.button.confirm.tooltip": "Create new version", + // TODO New key - Add a translation + "item.version.create.modal.button.confirm.tooltip": "Create new version", + + // "item.version.create.modal.button.cancel": "Cancel", + // TODO New key - Add a translation + "item.version.create.modal.button.cancel": "Cancel", + + // "item.version.create.modal.button.cancel.tooltip": "Do not create new version", + // TODO New key - Add a translation + "item.version.create.modal.button.cancel.tooltip": "Do not create new version", + + // "item.version.create.modal.form.summary.label": "Summary", + // TODO New key - Add a translation + "item.version.create.modal.form.summary.label": "Summary", + + // "item.version.create.modal.form.summary.placeholder": "Insert the summary for the new version", + // TODO New key - Add a translation + "item.version.create.modal.form.summary.placeholder": "Insert the summary for the new version", + + // "item.version.create.modal.submitted.header": "Creating new version...", + // TODO New key - Add a translation + "item.version.create.modal.submitted.header": "Creating new version...", + + // "item.version.create.modal.submitted.text": "The new version is being created. This may take some time if the item has a lot of relationships.", + // TODO New key - Add a translation + "item.version.create.modal.submitted.text": "The new version is being created. This may take some time if the item has a lot of relationships.", + + // "item.version.create.notification.success" : "New version has been created with version number {{version}}", + // TODO New key - Add a translation + "item.version.create.notification.success" : "New version has been created with version number {{version}}", + + // "item.version.create.notification.failure" : "New version has not been created", + // TODO New key - Add a translation + "item.version.create.notification.failure" : "New version has not been created", + + // "item.version.create.notification.inProgress" : "A new version cannot be created because there is an inprogress submission in the version history", + // TODO New key - Add a translation + "item.version.create.notification.inProgress" : "A new version cannot be created because there is an inprogress submission in the version history", + + + // "item.version.delete.modal.header": "Delete version", + // TODO New key - Add a translation + "item.version.delete.modal.header": "Delete version", + + // "item.version.delete.modal.text": "Do you want to delete version {{version}}?", + // TODO New key - Add a translation + "item.version.delete.modal.text": "Do you want to delete version {{version}}?", + + // "item.version.delete.modal.button.confirm": "Delete", + // TODO New key - Add a translation + "item.version.delete.modal.button.confirm": "Delete", + + // "item.version.delete.modal.button.confirm.tooltip": "Delete this version", + // TODO New key - Add a translation + "item.version.delete.modal.button.confirm.tooltip": "Delete this version", + + // "item.version.delete.modal.button.cancel": "Cancel", + // TODO New key - Add a translation + "item.version.delete.modal.button.cancel": "Cancel", + + // "item.version.delete.modal.button.cancel.tooltip": "Do not delete this version", + // TODO New key - Add a translation + "item.version.delete.modal.button.cancel.tooltip": "Do not delete this version", + + // "item.version.delete.notification.success" : "Version number {{version}} has been deleted", + // TODO New key - Add a translation + "item.version.delete.notification.success" : "Version number {{version}} has been deleted", + + // "item.version.delete.notification.failure" : "Version number {{version}} has not been deleted", + // TODO New key - Add a translation + "item.version.delete.notification.failure" : "Version number {{version}} has not been deleted", + + + // "item.version.edit.notification.success" : "The summary of version number {{version}} has been changed", + // TODO New key - Add a translation + "item.version.edit.notification.success" : "The summary of version number {{version}} has been changed", + + // "item.version.edit.notification.failure" : "The summary of version number {{version}} has not been changed", + // TODO New key - Add a translation + "item.version.edit.notification.failure" : "The summary of version number {{version}} has not been changed", + + + // "journal.listelement.badge": "Journal", "journal.listelement.badge": "Periódico", - + // "journal.page.description": "Description", "journal.page.description": "Descrição", - + // "journal.page.edit": "Edit this item", // TODO New key - Add a translation "journal.page.edit": "Edit this item", - + // "journal.page.editor": "Editor-in-Chief", "journal.page.editor": "Editor Chefe", - + // "journal.page.issn": "ISSN", "journal.page.issn": "ISSN", - + // "journal.page.publisher": "Publisher", "journal.page.publisher": "Editora", - + // "journal.page.titleprefix": "Journal: ", "journal.page.titleprefix": "Periódico: ", - + // "journal.search.results.head": "Journal Search Results", "journal.search.results.head": "Resultado da Busca de Periódicos", - - // "journal.search.title": "DSpace Angular :: Journal Search", - "journal.search.title": "DSpace Angular :: Busca de Periódicos", - - - + + // "journal-relationships.search.results.head": "Journal Search Results", + // TODO New key - Add a translation + "journal-relationships.search.results.head": "Journal Search Results", + + // "journal.search.title": "Journal Search", + "journal.search.title": "Busca de Periódicos", + + + // "journalissue.listelement.badge": "Journal Issue", "journalissue.listelement.badge": "Fascículo", - + // "journalissue.page.description": "Description", "journalissue.page.description": "Descrição", - + // "journalissue.page.edit": "Edit this item", // TODO New key - Add a translation "journalissue.page.edit": "Edit this item", - + // "journalissue.page.issuedate": "Issue Date", "journalissue.page.issuedate": "Data de Publicação", - + // "journalissue.page.journal-issn": "Journal ISSN", "journalissue.page.journal-issn": "ISSN do Periódico", - + // "journalissue.page.journal-title": "Journal Title", "journalissue.page.journal-title": "Título do Periódico", - + // "journalissue.page.keyword": "Keywords", "journalissue.page.keyword": "Palavras-chave", - + // "journalissue.page.number": "Number", "journalissue.page.number": "Número", - + // "journalissue.page.titleprefix": "Journal Issue: ", "journalissue.page.titleprefix": "Fascículo: ", - - - + + + // "journalvolume.listelement.badge": "Journal Volume", "journalvolume.listelement.badge": "Volume do Periódico", - + // "journalvolume.page.description": "Description", "journalvolume.page.description": "Descrição", - + // "journalvolume.page.edit": "Edit this item", // TODO New key - Add a translation "journalvolume.page.edit": "Edit this item", - + // "journalvolume.page.issuedate": "Issue Date", "journalvolume.page.issuedate": "Data de Publicação", - + // "journalvolume.page.titleprefix": "Journal Volume: ", "journalvolume.page.titleprefix": "Volume do Periódico: ", - + // "journalvolume.page.volume": "Volume", "journalvolume.page.volume": "Volume", - - - + + + // "iiifsearchable.listelement.badge": "Document Media", + // TODO New key - Add a translation + "iiifsearchable.listelement.badge": "Document Media", + + // "iiifsearchable.page.titleprefix": "Document: ", + // TODO New key - Add a translation + "iiifsearchable.page.titleprefix": "Document: ", + + // "iiifsearchable.page.doi": "Permanent Link: ", + // TODO New key - Add a translation + "iiifsearchable.page.doi": "Permanent Link: ", + + // "iiifsearchable.page.issue": "Issue: ", + // TODO New key - Add a translation + "iiifsearchable.page.issue": "Issue: ", + + // "iiifsearchable.page.description": "Description: ", + // TODO New key - Add a translation + "iiifsearchable.page.description": "Description: ", + + // "iiifviewer.fullscreen.notice": "Use full screen for better viewing.", + // TODO New key - Add a translation + "iiifviewer.fullscreen.notice": "Use full screen for better viewing.", + + // "iiif.listelement.badge": "Image Media", + // TODO New key - Add a translation + "iiif.listelement.badge": "Image Media", + + // "iiif.page.titleprefix": "Image: ", + // TODO New key - Add a translation + "iiif.page.titleprefix": "Image: ", + + // "iiif.page.doi": "Permanent Link: ", + // TODO New key - Add a translation + "iiif.page.doi": "Permanent Link: ", + + // "iiif.page.issue": "Issue: ", + // TODO New key - Add a translation + "iiif.page.issue": "Issue: ", + + // "iiif.page.description": "Description: ", + // TODO New key - Add a translation + "iiif.page.description": "Description: ", + + // "loading.bitstream": "Loading bitstream...", // TODO New key - Add a translation "loading.bitstream": "Loading bitstream...", - + // "loading.bitstreams": "Loading bitstreams...", // TODO New key - Add a translation "loading.bitstreams": "Loading bitstreams...", - + // "loading.browse-by": "Loading items...", "loading.browse-by": "Carregando itens...", - + // "loading.browse-by-page": "Loading page...", "loading.browse-by-page": "Carregando página...", - + // "loading.collection": "Loading collection...", "loading.collection": "Carregando coleção...", - + // "loading.collections": "Loading collections...", "loading.collections": "Carregando coleções...", - + // "loading.content-source": "Loading content source...", // TODO New key - Add a translation "loading.content-source": "Loading content source...", - + // "loading.community": "Loading community...", "loading.community": "Carregando comunidade...", - + // "loading.default": "Loading...", "loading.default": "Carregando...", - + // "loading.item": "Loading item...", "loading.item": "Carregando item...", - + // "loading.items": "Loading items...", "loading.items": "Carregando itens...", - + // "loading.mydspace-results": "Loading items...", "loading.mydspace-results": "Carregando itens...", - + // "loading.objects": "Loading...", "loading.objects": "Carregando...", - + // "loading.recent-submissions": "Loading recent submissions...", "loading.recent-submissions": "Carregando submissões recentes...", - + // "loading.search-results": "Loading search results...", "loading.search-results": "Carregando resultados de busca...", - + // "loading.sub-collections": "Loading sub-collections...", "loading.sub-collections": "Carregando sub-coleções...", - + // "loading.sub-communities": "Loading sub-communities...", "loading.sub-communities": "Carregando sub-comunidades...", - + // "loading.top-level-communities": "Loading top-level communities...", "loading.top-level-communities": "Carregando comunidades de nível superior...", - - - + + + // "login.form.email": "Email address", "login.form.email": "Endereço de email", - + // "login.form.forgot-password": "Have you forgotten your password?", "login.form.forgot-password": "Esqueceu sua senha?", - + // "login.form.header": "Please log in to DSpace", "login.form.header": "Por favor entre no DSpace", - + // "login.form.new-user": "New user? Click here to register.", "login.form.new-user": "Novo usuário? Clique aqui para cadastrar.", - + // "login.form.or-divider": "or", // TODO New key - Add a translation "login.form.or-divider": "or", - + + // "login.form.oidc": "Log in with OIDC", + // TODO New key - Add a translation + "login.form.oidc": "Log in with OIDC", + + // "login.form.orcid": "Log in with ORCID", + // TODO New key - Add a translation + "login.form.orcid": "Log in with ORCID", + // "login.form.password": "Password", "login.form.password": "Senha", - + // "login.form.shibboleth": "Log in with Shibboleth", // TODO New key - Add a translation "login.form.shibboleth": "Log in with Shibboleth", - + // "login.form.submit": "Log in", "login.form.submit": "Entrar", - + // "login.title": "Login", "login.title": "Entrar", - + // "login.breadcrumbs": "Login", // TODO New key - Add a translation "login.breadcrumbs": "Login", - - - + + + // "logout.form.header": "Log out from DSpace", "logout.form.header": "Sair do DSpace", - + // "logout.form.submit": "Log out", "logout.form.submit": "Sair", - + // "logout.title": "Logout", "logout.title": "Sair", - - - - // "menu.header.admin": "Admin", + + + + // "menu.header.admin": "Management", "menu.header.admin": "Administração", - + // "menu.header.image.logo": "Repository logo", "menu.header.image.logo": "Logo do repositório", - - - + + // "menu.header.admin.description": "Management menu", + // TODO New key - Add a translation + "menu.header.admin.description": "Management menu", + + + // "menu.section.access_control": "Access Control", "menu.section.access_control": "Controle de Acesso", - + // "menu.section.access_control_authorizations": "Authorizations", "menu.section.access_control_authorizations": "Autorizações", - + // "menu.section.access_control_groups": "Groups", "menu.section.access_control_groups": "Grupos", - + // "menu.section.access_control_people": "People", "menu.section.access_control_people": "Pessoas", - - - + + + // "menu.section.admin_search": "Admin Search", // TODO New key - Add a translation "menu.section.admin_search": "Admin Search", - - - + + + // "menu.section.browse_community": "This Community", "menu.section.browse_community": "Esta Comunidade", - + // "menu.section.browse_community_by_author": "By Author", "menu.section.browse_community_by_author": "Por Autor", - + // "menu.section.browse_community_by_issue_date": "By Issue Date", "menu.section.browse_community_by_issue_date": "Por Data de Publicação", - + // "menu.section.browse_community_by_title": "By Title", "menu.section.browse_community_by_title": "Por Título", - + // "menu.section.browse_global": "All of DSpace", "menu.section.browse_global": "Tudo no DSpace", - + // "menu.section.browse_global_by_author": "By Author", "menu.section.browse_global_by_author": "Por Autor", - + // "menu.section.browse_global_by_dateissued": "By Issue Date", "menu.section.browse_global_by_dateissued": "Por Data de Publicação", - + // "menu.section.browse_global_by_subject": "By Subject", "menu.section.browse_global_by_subject": "Por Assunto", - + // "menu.section.browse_global_by_title": "By Title", "menu.section.browse_global_by_title": "Por Título", - + // "menu.section.browse_global_communities_and_collections": "Communities & Collections", "menu.section.browse_global_communities_and_collections": "Comunidades e Coleções", - - - + + + // "menu.section.control_panel": "Control Panel", "menu.section.control_panel": "Painel de Controle", - + // "menu.section.curation_task": "Curation Task", "menu.section.curation_task": "Tarefas de Curadoria", - - - + + + // "menu.section.edit": "Edit", "menu.section.edit": "Editar", - + // "menu.section.edit_collection": "Collection", "menu.section.edit_collection": "Coleção", - + // "menu.section.edit_community": "Community", "menu.section.edit_community": "Comunidade", - + // "menu.section.edit_item": "Item", "menu.section.edit_item": "Item", - - - + + + // "menu.section.export": "Export", "menu.section.export": "Exportar", - + // "menu.section.export_collection": "Collection", "menu.section.export_collection": "Coleção", - + // "menu.section.export_community": "Community", "menu.section.export_community": "Comunidade", - + // "menu.section.export_item": "Item", "menu.section.export_item": "Item", - + // "menu.section.export_metadata": "Metadata", "menu.section.export_metadata": "Metadados", - - - + + + // "menu.section.icon.access_control": "Access Control menu section", "menu.section.icon.access_control": "Seção do menu Controle de Acesso", - + // "menu.section.icon.admin_search": "Admin search menu section", // TODO New key - Add a translation "menu.section.icon.admin_search": "Admin search menu section", - + // "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.control_panel": "Seção do menu Painel de Controle", - - // "menu.section.icon.curation_task": "Curation Task menu section", - "menu.section.icon.curation_task": "Seção do menu Tarefas de Curadoria", - + + // "menu.section.icon.curation_tasks": "Curation Task menu section", + // TODO New key - Add a translation + "menu.section.icon.curation_tasks": "Curation Task menu section", + // "menu.section.icon.edit": "Edit menu section", "menu.section.icon.edit": "Seção do menu Editar", - + // "menu.section.icon.export": "Export menu section", "menu.section.icon.export": "Seção do menu Exportar", - + // "menu.section.icon.find": "Find menu section", "menu.section.icon.find": "Seção do menu Buscar", - + + // "menu.section.icon.health": "Health check menu section", + // TODO New key - Add a translation + "menu.section.icon.health": "Health check menu section", + // "menu.section.icon.import": "Import menu section", "menu.section.icon.import": "Seção do menu Importar", - + // "menu.section.icon.new": "New menu section", "menu.section.icon.new": "Seção do menu Novo", - + // "menu.section.icon.pin": "Pin sidebar", "menu.section.icon.pin": "Fixar barra lateral", - - // "menu.section.icon.processes": "Processes menu section", + + // "menu.section.icon.processes": "Processes Health", // TODO New key - Add a translation - "menu.section.icon.processes": "Processes menu section", - + "menu.section.icon.processes": "Processes Health", + // "menu.section.icon.registries": "Registries menu section", "menu.section.icon.registries": "Seção do menu Registros", - + // "menu.section.icon.statistics_task": "Statistics Task menu section", "menu.section.icon.statistics_task": "Seção do menu Tarefas de Estatísticas", - + + // "menu.section.icon.workflow": "Administer workflow menu section", + // TODO New key - Add a translation + "menu.section.icon.workflow": "Administer workflow menu section", + // "menu.section.icon.unpin": "Unpin sidebar", "menu.section.icon.unpin": "Soltar barra lateral", - - - + + + // "menu.section.import": "Import", "menu.section.import": "Importar", - + // "menu.section.import_batch": "Batch Import (ZIP)", "menu.section.import_batch": "Importação em Lote (ZIP)", - + // "menu.section.import_metadata": "Metadata", "menu.section.import_metadata": "Metadados", - - - + + + // "menu.section.new": "New", - "menu.section.new": "Nova", - + "menu.section.new": "Novo", + // "menu.section.new_collection": "Collection", "menu.section.new_collection": "Coleção", - + // "menu.section.new_community": "Community", "menu.section.new_community": "Comunidade", - + // "menu.section.new_item": "Item", "menu.section.new_item": "Item", - + // "menu.section.new_item_version": "Item Version", "menu.section.new_item_version": "Versão do Item", - + // "menu.section.new_process": "Process", - // TODO New key - Add a translation - "menu.section.new_process": "Process", - - - + "menu.section.new_process": "Processo", + + + // "menu.section.pin": "Pin sidebar", "menu.section.pin": "Fixar barra lateral", - + // "menu.section.unpin": "Unpin sidebar", "menu.section.unpin": "Soltar barra lateral", - - - + + + // "menu.section.processes": "Processes", // TODO New key - Add a translation "menu.section.processes": "Processes", - - - + + // "menu.section.health": "Health", + // TODO New key - Add a translation + "menu.section.health": "Health", + + + // "menu.section.registries": "Registries", "menu.section.registries": "Registros", - + // "menu.section.registries_format": "Format", "menu.section.registries_format": "Formatos", - + // "menu.section.registries_metadata": "Metadata", "menu.section.registries_metadata": "Metadados", - - - + + + // "menu.section.statistics": "Statistics", "menu.section.statistics": "Estatísticas", - + // "menu.section.statistics_task": "Statistics Task", "menu.section.statistics_task": "Tarefas de Estatísticas", - - - + + + // "menu.section.toggle.access_control": "Toggle Access Control section", "menu.section.toggle.access_control": "Alternar Seção Controle de Acesso", - + // "menu.section.toggle.control_panel": "Toggle Control Panel section", "menu.section.toggle.control_panel": "Alternar Seção Painel de COntrole", - + // "menu.section.toggle.curation_task": "Toggle Curation Task section", "menu.section.toggle.curation_task": "Alternar Seção Tarefas de Curadoria", - + // "menu.section.toggle.edit": "Toggle Edit section", "menu.section.toggle.edit": "Alternar Seção Editar", - + // "menu.section.toggle.export": "Toggle Export section", "menu.section.toggle.export": "Alternar Seção Exportar", - + // "menu.section.toggle.find": "Toggle Find section", "menu.section.toggle.find": "Alternar Seção Pesquisa", - + // "menu.section.toggle.import": "Toggle Import section", "menu.section.toggle.import": "Alternar Seção Importar", - + // "menu.section.toggle.new": "Toggle New section", "menu.section.toggle.new": "Alternar Nova Seção", - + // "menu.section.toggle.registries": "Toggle Registries section", "menu.section.toggle.registries": "Alternar Seção Registros", - + // "menu.section.toggle.statistics_task": "Toggle Statistics Task section", "menu.section.toggle.statistics_task": "Alternar Seção Tarefas de Estatísticas", - - + + // "menu.section.workflow": "Administer Workflow", // TODO New key - Add a translation "menu.section.workflow": "Administer Workflow", - - + + + // "metadata-export-search.tooltip": "Export search results as CSV", + // TODO New key - Add a translation + "metadata-export-search.tooltip": "Export search results as CSV", + // "metadata-export-search.submit.success": "The export was started successfully", + // TODO New key - Add a translation + "metadata-export-search.submit.success": "The export was started successfully", + // "metadata-export-search.submit.error": "Starting the export has failed", + // TODO New key - Add a translation + "metadata-export-search.submit.error": "Starting the export has failed", + + + // "mydspace.breadcrumbs": "MyDSpace", + // TODO New key - Add a translation + "mydspace.breadcrumbs": "MyDSpace", + // "mydspace.description": "", "mydspace.description": "", - + // "mydspace.general.text-here": "here", - // TODO Source message changed - Revise the translation - "mydspace.general.text-here": "AQUI", - + "mydspace.general.text-here": "aqui", + // "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", "mydspace.messages.controller-help": "Selecione esta opção para enviar uma mensagem para o submetedor do item.", - + // "mydspace.messages.description-placeholder": "Insert your message here...", "mydspace.messages.description-placeholder": "Insira sua mensagem aqui...", - + // "mydspace.messages.hide-msg": "Hide message", "mydspace.messages.hide-msg": "Ocultar mensagem", - + // "mydspace.messages.mark-as-read": "Mark as read", "mydspace.messages.mark-as-read": "Marcar como lida", - + // "mydspace.messages.mark-as-unread": "Mark as unread", "mydspace.messages.mark-as-unread": "Marcar como não lida", - + // "mydspace.messages.no-content": "No content.", "mydspace.messages.no-content": "Sem conteúdo", - + // "mydspace.messages.no-messages": "No messages yet.", "mydspace.messages.no-messages": "Nenhuma mensagem ainda", - + // "mydspace.messages.send-btn": "Send", "mydspace.messages.send-btn": "Enviar", - + // "mydspace.messages.show-msg": "Show message", "mydspace.messages.show-msg": "Mostrar mensagem", - + // "mydspace.messages.subject-placeholder": "Subject...", "mydspace.messages.subject-placeholder": "Assunto...", - + // "mydspace.messages.submitter-help": "Select this option to send a message to controller.", "mydspace.messages.submitter-help": "Selecione esta opção para enviar uma mensagem ao controlador.", - + // "mydspace.messages.title": "Messages", "mydspace.messages.title": "Mensagens", - + // "mydspace.messages.to": "To", "mydspace.messages.to": "Para", - + // "mydspace.new-submission": "New submission", "mydspace.new-submission": "Nova submissão", - + // "mydspace.new-submission-external": "Import metadata from external source", // TODO New key - Add a translation "mydspace.new-submission-external": "Import metadata from external source", - + // "mydspace.new-submission-external-short": "Import metadata", // TODO New key - Add a translation "mydspace.new-submission-external-short": "Import metadata", - + // "mydspace.results.head": "Your submissions", "mydspace.results.head": "Minhas submissões", - + // "mydspace.results.no-abstract": "No Abstract", "mydspace.results.no-abstract": "Sem Resumo", - + // "mydspace.results.no-authors": "No Authors", "mydspace.results.no-authors": "Sem Autores", - + // "mydspace.results.no-collections": "No Collections", "mydspace.results.no-collections": "Sem Coleções", - + // "mydspace.results.no-date": "No Date", "mydspace.results.no-date": "Sem Data", - + // "mydspace.results.no-files": "No Files", "mydspace.results.no-files": "Sem arquivos", - + // "mydspace.results.no-results": "There were no items to show", "mydspace.results.no-results": "Não havia itens a mostrar", - + // "mydspace.results.no-title": "No title", "mydspace.results.no-title": "Sem título", - + // "mydspace.results.no-uri": "No Uri", "mydspace.results.no-uri": "Sem Uri", - - // "mydspace.show.workflow": "All tasks", + + // "mydspace.search-form.placeholder": "Search in mydspace...", + // TODO New key - Add a translation + "mydspace.search-form.placeholder": "Search in mydspace...", + + // "mydspace.show.workflow": "Workflow tasks", + // TODO Source message changed - Revise the translation "mydspace.show.workflow": "Todas as tarefas", - + // "mydspace.show.workspace": "Your Submissions", "mydspace.show.workspace": "Minhas Submissões", - + // "mydspace.status.archived": "Archived", "mydspace.status.archived": "Arquivado", - + // "mydspace.status.validation": "Validation", "mydspace.status.validation": "Validação", - + // "mydspace.status.waiting-for-controller": "Waiting for controller", "mydspace.status.waiting-for-controller": "Esperando pelo controlador", - + // "mydspace.status.workflow": "Workflow", "mydspace.status.workflow": "Fluxo de trabalho", - + // "mydspace.status.workspace": "Workspace", "mydspace.status.workspace": "Espaço de trabalho", - + // "mydspace.title": "MyDSpace", "mydspace.title": "MyDSpace", - + // "mydspace.upload.upload-failed": "Error creating new workspace. Please verify the content uploaded before retry.", "mydspace.upload.upload-failed": "Erro ao criar novo espaço de trabalho. Por favor verifique o conteúdo enviado antes de tentar novamente.", - + // "mydspace.upload.upload-failed-manyentries": "Unprocessable file. Detected too many entries but allowed only one for file.", // TODO New key - Add a translation "mydspace.upload.upload-failed-manyentries": "Unprocessable file. Detected too many entries but allowed only one for file.", - + // "mydspace.upload.upload-failed-moreonefile": "Unprocessable request. Only one file is allowed.", // TODO New key - Add a translation "mydspace.upload.upload-failed-moreonefile": "Unprocessable request. Only one file is allowed.", - + // "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", "mydspace.upload.upload-multiple-successful": "{{qty}} novo(s) item(ns) de espaço de trabalho criados.", - + // "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", "mydspace.upload.upload-successful": "Novo item de espaço de trabalho criado. Clique {{here}} para o editar.", - + // "mydspace.view-btn": "View", "mydspace.view-btn": "Ver", - - - + + + // "nav.browse.header": "All of DSpace", "nav.browse.header": "Tudo no DSpace", - + // "nav.community-browse.header": "By Community", "nav.community-browse.header": "Por Comunidade", - + // "nav.language": "Language switch", - "nav.language": "Seletor de idioma", - + "nav.language": "Selecionar um idioma", + // "nav.login": "Log In", "nav.login": "Entrar", - - // "nav.logout": "Log Out", - "nav.logout": "Sair", - + + // "nav.logout": "User profile menu and Log Out", + "nav.logout": "Menu do Usuário e Sair", + + // "nav.main.description": "Main navigation bar", + "nav.main.description": "Barra de navegação principal", + // "nav.mydspace": "MyDSpace", "nav.mydspace": "MyDSpace", - + // "nav.profile": "Profile", - // TODO New key - Add a translation - "nav.profile": "Profile", - + "nav.profile": "Perfil", + // "nav.search": "Search", "nav.search": "Buscar", - + // "nav.statistics.header": "Statistics", "nav.statistics.header": "Estatísticas", - + // "nav.stop-impersonating": "Stop impersonating EPerson", // TODO New key - Add a translation "nav.stop-impersonating": "Stop impersonating EPerson", - - - + + // "nav.toggle" : "Toggle navigation", + // TODO New key - Add a translation + "nav.toggle" : "Toggle navigation", + + // "nav.user.description" : "User profile bar", + // TODO New key - Add a translation + "nav.user.description" : "User profile bar", + + // "none.listelement.badge": "Item", + // TODO New key - Add a translation + "none.listelement.badge": "Item", + + // "orgunit.listelement.badge": "Organizational Unit", "orgunit.listelement.badge": "Unidade Organizacional", - + // "orgunit.page.city": "City", "orgunit.page.city": "Cidade", - + // "orgunit.page.country": "Country", "orgunit.page.country": "País", - + // "orgunit.page.dateestablished": "Date established", "orgunit.page.dateestablished": "Data estabelecida", - + // "orgunit.page.description": "Description", "orgunit.page.description": "Descrição", - + // "orgunit.page.edit": "Edit this item", // TODO New key - Add a translation "orgunit.page.edit": "Edit this item", - + // "orgunit.page.id": "ID", "orgunit.page.id": "ID", - + // "orgunit.page.titleprefix": "Organizational Unit: ", "orgunit.page.titleprefix": "Unidade Organizacional: ", - - - + + + + // "pagination.options.description": "Pagination options", + // TODO New key - Add a translation + "pagination.options.description": "Pagination options", + // "pagination.results-per-page": "Results Per Page", "pagination.results-per-page": "Resultados por página", - + // "pagination.showing.detail": "{{ range }} of {{ total }}", "pagination.showing.detail": "{{ range }} de {{ total }}", - + // "pagination.showing.label": "Now showing ", "pagination.showing.label": "Agora exibindo ", - + // "pagination.sort-direction": "Sort Options", "pagination.sort-direction": "Opções de Ordenação", - - - + + + // "person.listelement.badge": "Person", "person.listelement.badge": "Pessoa", - + // "person.listelement.no-title": "No name found", // TODO New key - Add a translation "person.listelement.no-title": "No name found", - + // "person.page.birthdate": "Birth Date", "person.page.birthdate": "Data de nascimento", - + // "person.page.edit": "Edit this item", // TODO New key - Add a translation "person.page.edit": "Edit this item", - + // "person.page.email": "Email Address", "person.page.email": "Endereço de Email", - + // "person.page.firstname": "First Name", "person.page.firstname": "Primeiro Nome", - + // "person.page.jobtitle": "Job Title", "person.page.jobtitle": "Cargo", - + // "person.page.lastname": "Last Name", "person.page.lastname": "Último Nome", - + + // "person.page.name": "Name", + // TODO New key - Add a translation + "person.page.name": "Name", + // "person.page.link.full": "Show all metadata", "person.page.link.full": "Mostrar todos os metadados", - + // "person.page.orcid": "ORCID", "person.page.orcid": "ORCID", - + // "person.page.staffid": "Staff ID", "person.page.staffid": "ID de Equipe", - + // "person.page.titleprefix": "Person: ", "person.page.titleprefix": "Pessoa: ", - + // "person.search.results.head": "Person Search Results", "person.search.results.head": "Resultado da Busca de Pessoa", - - // "person.search.title": "DSpace Angular :: Person Search", - "person.search.title": "DSpace Angular :: Buscar Pessoa", - - - + + // "person-relationships.search.results.head": "Person Search Results", + // TODO New key - Add a translation + "person-relationships.search.results.head": "Person Search Results", + + // "person.search.title": "Person Search", + "person.search.title": "Buscar Pessoa", + + + // "process.new.select-parameters": "Parameters", // TODO New key - Add a translation "process.new.select-parameters": "Parameters", - + // "process.new.cancel": "Cancel", // TODO New key - Add a translation "process.new.cancel": "Cancel", - - // "process.new.submit": "Submit", + + // "process.new.submit": "Save", // TODO New key - Add a translation - "process.new.submit": "Submit", - + "process.new.submit": "Save", + // "process.new.select-script": "Script", // TODO New key - Add a translation "process.new.select-script": "Script", - + // "process.new.select-script.placeholder": "Choose a script...", // TODO New key - Add a translation "process.new.select-script.placeholder": "Choose a script...", - + // "process.new.select-script.required": "Script is required", // TODO New key - Add a translation "process.new.select-script.required": "Script is required", - + // "process.new.parameter.file.upload-button": "Select file...", // TODO New key - Add a translation "process.new.parameter.file.upload-button": "Select file...", - + // "process.new.parameter.file.required": "Please select a file", // TODO New key - Add a translation "process.new.parameter.file.required": "Please select a file", - + // "process.new.parameter.string.required": "Parameter value is required", // TODO New key - Add a translation "process.new.parameter.string.required": "Parameter value is required", - + // "process.new.parameter.type.value": "value", // TODO New key - Add a translation "process.new.parameter.type.value": "value", - + // "process.new.parameter.type.file": "file", // TODO New key - Add a translation "process.new.parameter.type.file": "file", - + // "process.new.parameter.required.missing": "The following parameters are required but still missing:", // TODO New key - Add a translation "process.new.parameter.required.missing": "The following parameters are required but still missing:", - + // "process.new.notification.success.title": "Success", // TODO New key - Add a translation "process.new.notification.success.title": "Success", - + // "process.new.notification.success.content": "The process was successfully created", // TODO New key - Add a translation "process.new.notification.success.content": "The process was successfully created", - + // "process.new.notification.error.title": "Error", // TODO New key - Add a translation "process.new.notification.error.title": "Error", - + // "process.new.notification.error.content": "An error occurred while creating this process", // TODO New key - Add a translation "process.new.notification.error.content": "An error occurred while creating this process", - + // "process.new.header": "Create a new process", // TODO New key - Add a translation "process.new.header": "Create a new process", - + // "process.new.title": "Create a new process", // TODO New key - Add a translation "process.new.title": "Create a new process", - + // "process.new.breadcrumbs": "Create a new process", // TODO New key - Add a translation "process.new.breadcrumbs": "Create a new process", - - - + + + // "process.detail.arguments" : "Arguments", // TODO New key - Add a translation "process.detail.arguments" : "Arguments", - + // "process.detail.arguments.empty" : "This process doesn't contain any arguments", // TODO New key - Add a translation "process.detail.arguments.empty" : "This process doesn't contain any arguments", - + // "process.detail.back" : "Back", // TODO New key - Add a translation "process.detail.back" : "Back", - + // "process.detail.output" : "Process Output", // TODO New key - Add a translation "process.detail.output" : "Process Output", - + // "process.detail.logs.button": "Retrieve process output", // TODO New key - Add a translation "process.detail.logs.button": "Retrieve process output", - + // "process.detail.logs.loading": "Retrieving", // TODO New key - Add a translation "process.detail.logs.loading": "Retrieving", - + // "process.detail.logs.none": "This process has no output", // TODO New key - Add a translation "process.detail.logs.none": "This process has no output", - + // "process.detail.output-files" : "Output Files", // TODO New key - Add a translation "process.detail.output-files" : "Output Files", - + // "process.detail.output-files.empty" : "This process doesn't contain any output files", // TODO New key - Add a translation "process.detail.output-files.empty" : "This process doesn't contain any output files", - + // "process.detail.script" : "Script", // TODO New key - Add a translation "process.detail.script" : "Script", - + // "process.detail.title" : "Process: {{ id }} - {{ name }}", // TODO New key - Add a translation "process.detail.title" : "Process: {{ id }} - {{ name }}", - + // "process.detail.start-time" : "Start time", // TODO New key - Add a translation "process.detail.start-time" : "Start time", - + // "process.detail.end-time" : "Finish time", // TODO New key - Add a translation "process.detail.end-time" : "Finish time", - + // "process.detail.status" : "Status", // TODO New key - Add a translation "process.detail.status" : "Status", - + // "process.detail.create" : "Create similar process", // TODO New key - Add a translation "process.detail.create" : "Create similar process", - - - - // "process.overview.table.finish" : "Finish time", + + // "process.detail.actions": "Actions", // TODO New key - Add a translation - "process.overview.table.finish" : "Finish time", - + "process.detail.actions": "Actions", + + // "process.detail.delete.button": "Delete process", + // TODO New key - Add a translation + "process.detail.delete.button": "Delete process", + + // "process.detail.delete.header": "Delete process", + // TODO New key - Add a translation + "process.detail.delete.header": "Delete process", + + // "process.detail.delete.body": "Are you sure you want to delete the current process?", + // TODO New key - Add a translation + "process.detail.delete.body": "Are you sure you want to delete the current process?", + + // "process.detail.delete.cancel": "Cancel", + // TODO New key - Add a translation + "process.detail.delete.cancel": "Cancel", + + // "process.detail.delete.confirm": "Delete process", + // TODO New key - Add a translation + "process.detail.delete.confirm": "Delete process", + + // "process.detail.delete.success": "The process was successfully deleted.", + // TODO New key - Add a translation + "process.detail.delete.success": "The process was successfully deleted.", + + // "process.detail.delete.error": "Something went wrong when deleting the process", + // TODO New key - Add a translation + "process.detail.delete.error": "Something went wrong when deleting the process", + + + + // "process.overview.table.finish" : "Finish time (UTC)", + // TODO New key - Add a translation + "process.overview.table.finish" : "Finish time (UTC)", + // "process.overview.table.id" : "Process ID", // TODO New key - Add a translation "process.overview.table.id" : "Process ID", - + // "process.overview.table.name" : "Name", // TODO New key - Add a translation "process.overview.table.name" : "Name", - - // "process.overview.table.start" : "Start time", + + // "process.overview.table.start" : "Start time (UTC)", // TODO New key - Add a translation - "process.overview.table.start" : "Start time", - + "process.overview.table.start" : "Start time (UTC)", + // "process.overview.table.status" : "Status", // TODO New key - Add a translation "process.overview.table.status" : "Status", - + // "process.overview.table.user" : "User", // TODO New key - Add a translation "process.overview.table.user" : "User", - + // "process.overview.title": "Processes Overview", // TODO New key - Add a translation "process.overview.title": "Processes Overview", - + // "process.overview.breadcrumbs": "Processes Overview", // TODO New key - Add a translation "process.overview.breadcrumbs": "Processes Overview", - + // "process.overview.new": "New", // TODO New key - Add a translation "process.overview.new": "New", - - - // "profile.breadcrumbs": "Update Profile", + + // "process.overview.table.actions": "Actions", // TODO New key - Add a translation - "profile.breadcrumbs": "Update Profile", - + "process.overview.table.actions": "Actions", + + // "process.overview.delete": "Delete {{count}} processes", + // TODO New key - Add a translation + "process.overview.delete": "Delete {{count}} processes", + + // "process.overview.delete.clear": "Clear delete selection", + // TODO New key - Add a translation + "process.overview.delete.clear": "Clear delete selection", + + // "process.overview.delete.processing": "{{count}} process(es) are being deleted. Please wait for the deletion to fully complete. Note that this can take a while.", + // TODO New key - Add a translation + "process.overview.delete.processing": "{{count}} process(es) are being deleted. Please wait for the deletion to fully complete. Note that this can take a while.", + + // "process.overview.delete.body": "Are you sure you want to delete {{count}} process(es)?", + // TODO New key - Add a translation + "process.overview.delete.body": "Are you sure you want to delete {{count}} process(es)?", + + // "process.overview.delete.header": "Delete processes", + // TODO New key - Add a translation + "process.overview.delete.header": "Delete processes", + + // "process.bulk.delete.error.head": "Error on deleteing process", + // TODO New key - Add a translation + "process.bulk.delete.error.head": "Error on deleteing process", + + // "process.bulk.delete.error.body": "The process with ID {{processId}} could not be deleted. The remaining processes will continue being deleted. ", + // TODO New key - Add a translation + "process.bulk.delete.error.body": "The process with ID {{processId}} could not be deleted. The remaining processes will continue being deleted. ", + + // "process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted", + // TODO New key - Add a translation + "process.bulk.delete.success": "{{count}} process(es) have been succesfully deleted", + + + + // "profile.breadcrumbs": "Update Profile", + "profile.breadcrumbs": "Atualizar Perfil", + // "profile.card.identify": "Identify", // TODO New key - Add a translation "profile.card.identify": "Identify", - + // "profile.card.security": "Security", // TODO New key - Add a translation "profile.card.security": "Security", - - // "profile.form.submit": "Update Profile", + + // "profile.form.submit": "Save", // TODO New key - Add a translation - "profile.form.submit": "Update Profile", - + "profile.form.submit": "Save", + // "profile.groups.head": "Authorization groups you belong to", // TODO New key - Add a translation "profile.groups.head": "Authorization groups you belong to", - + + // "profile.special.groups.head": "Authorization special groups you belong to", + // TODO New key - Add a translation + "profile.special.groups.head": "Authorization special groups you belong to", + // "profile.head": "Update Profile", - // TODO New key - Add a translation - "profile.head": "Update Profile", - + "profile.head": "Atualizar Perfil", + // "profile.metadata.form.error.firstname.required": "First Name is required", - // TODO New key - Add a translation - "profile.metadata.form.error.firstname.required": "First Name is required", - - // "profile.metadata.form.error.lastname.required": "Last Name is required", - // TODO New key - Add a translation + "profile.metadata.form.error.firstname.required": "Primeiro Nome é obrigatório", + + // "profile.metadata.form.error.lastname.required": "Sobrenome é obrigatório", "profile.metadata.form.error.lastname.required": "Last Name is required", - + // "profile.metadata.form.label.email": "Email Address", - // TODO New key - Add a translation - "profile.metadata.form.label.email": "Email Address", - + "profile.metadata.form.label.email": "Endereço de Email", + // "profile.metadata.form.label.firstname": "First Name", - // TODO New key - Add a translation - "profile.metadata.form.label.firstname": "First Name", - + "profile.metadata.form.label.firstname": "Primeiro Nome", + // "profile.metadata.form.label.language": "Language", - // TODO New key - Add a translation - "profile.metadata.form.label.language": "Language", - + "profile.metadata.form.label.language": "Idioma", + // "profile.metadata.form.label.lastname": "Last Name", - // TODO New key - Add a translation - "profile.metadata.form.label.lastname": "Last Name", - + "profile.metadata.form.label.lastname": "Sobrenome", + // "profile.metadata.form.label.phone": "Contact Telephone", - // TODO New key - Add a translation - "profile.metadata.form.label.phone": "Contact Telephone", - + "profile.metadata.form.label.phone": "Telefone", + // "profile.metadata.form.notifications.success.content": "Your changes to the profile were saved.", - // TODO New key - Add a translation - "profile.metadata.form.notifications.success.content": "Your changes to the profile were saved.", - + "profile.metadata.form.notifications.success.content": "Suas mudanças no perfil foram salvas..", + // "profile.metadata.form.notifications.success.title": "Profile saved", - // TODO New key - Add a translation - "profile.metadata.form.notifications.success.title": "Profile saved", - + "profile.metadata.form.notifications.success.title": "Perfil salvo", + // "profile.notifications.warning.no-changes.content": "No changes were made to the Profile.", - // TODO New key - Add a translation "profile.notifications.warning.no-changes.content": "No changes were made to the Profile.", - + // "profile.notifications.warning.no-changes.title": "No changes", - // TODO New key - Add a translation - "profile.notifications.warning.no-changes.title": "No changes", - + "profile.notifications.warning.no-changes.title": "Sem mudanças", + // "profile.security.form.error.matching-passwords": "The passwords do not match.", // TODO New key - Add a translation "profile.security.form.error.matching-passwords": "The passwords do not match.", - - // "profile.security.form.error.password-length": "The password should be at least 6 characters long.", + + // "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box.", // TODO New key - Add a translation - "profile.security.form.error.password-length": "The password should be at least 6 characters long.", - - // "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - // TODO New key - Add a translation - "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - + "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box.", + // "profile.security.form.label.password": "Password", - // TODO New key - Add a translation - "profile.security.form.label.password": "Password", - + "profile.security.form.label.password": "Senha", + // "profile.security.form.label.passwordrepeat": "Retype to confirm", // TODO New key - Add a translation "profile.security.form.label.passwordrepeat": "Retype to confirm", - + // "profile.security.form.notifications.success.content": "Your changes to the password were saved.", // TODO New key - Add a translation "profile.security.form.notifications.success.content": "Your changes to the password were saved.", - + // "profile.security.form.notifications.success.title": "Password saved", - // TODO New key - Add a translation - "profile.security.form.notifications.success.title": "Password saved", - + "profile.security.form.notifications.success.title": "Senha salva", + // "profile.security.form.notifications.error.title": "Error changing passwords", // TODO New key - Add a translation "profile.security.form.notifications.error.title": "Error changing passwords", - - // "profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.", - // TODO New key - Add a translation - "profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.", - + // "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.", // TODO New key - Add a translation "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.", - - // "profile.title": "Update Profile", + + // "profile.security.form.notifications.error.general": "Please fill required fields of security form.", // TODO New key - Add a translation - "profile.title": "Update Profile", - - - + "profile.security.form.notifications.error.general": "Please fill required fields of security form.", + + // "profile.title": "Update Profile", + "profile.title": "Atualizar Perfil", + + // "profile.card.researcher": "Researcher Profile", + // TODO New key - Add a translation + "profile.card.researcher": "Researcher Profile", + // "project.listelement.badge": "Research Project", "project.listelement.badge": "Projeto de Pesquisa", - + // "project.page.contributor": "Contributors", "project.page.contributor": "Contribuidores", - + // "project.page.description": "Description", "project.page.description": "Descrição", - + // "project.page.edit": "Edit this item", // TODO New key - Add a translation "project.page.edit": "Edit this item", - + // "project.page.expectedcompletion": "Expected Completion", "project.page.expectedcompletion": "Conclusão esperada", - + // "project.page.funder": "Funders", "project.page.funder": "Financiadores", - + // "project.page.id": "ID", "project.page.id": "ID", - + // "project.page.keyword": "Keywords", "project.page.keyword": "Palavras-chave", - + // "project.page.status": "Status", "project.page.status": "Estado", - + // "project.page.titleprefix": "Research Project: ", "project.page.titleprefix": "Projeto de Pesquisa: ", - + // "project.search.results.head": "Project Search Results", // TODO New key - Add a translation "project.search.results.head": "Project Search Results", - - - + + // "project-relationships.search.results.head": "Project Search Results", + // TODO New key - Add a translation + "project-relationships.search.results.head": "Project Search Results", + + + // "publication.listelement.badge": "Publication", "publication.listelement.badge": "Publicação", - + // "publication.page.description": "Description", "publication.page.description": "Descrição", - + // "publication.page.edit": "Edit this item", // TODO New key - Add a translation "publication.page.edit": "Edit this item", - + // "publication.page.journal-issn": "Journal ISSN", "publication.page.journal-issn": "ISSN do Periódico", - + // "publication.page.journal-title": "Journal Title", "publication.page.journal-title": "Título do Periódico", - + // "publication.page.publisher": "Publisher", "publication.page.publisher": "Editora", - + // "publication.page.titleprefix": "Publication: ", "publication.page.titleprefix": "Publicação: ", - + // "publication.page.volume-title": "Volume Title", "publication.page.volume-title": "Título do Volume", - + // "publication.search.results.head": "Publication Search Results", "publication.search.results.head": "Resultados da Busca de Publicação", - - // "publication.search.title": "DSpace Angular :: Publication Search", - "publication.search.title": "DSpace Angular :: Busca de Publicações", - - + + // "publication-relationships.search.results.head": "Publication Search Results", + // TODO New key - Add a translation + "publication-relationships.search.results.head": "Publication Search Results", + + // "publication.search.title": "Publication Search", + "publication.search.title": "Buscar Publicação", + + + // "media-viewer.next": "Next", + // TODO New key - Add a translation + "media-viewer.next": "Next", + + // "media-viewer.previous": "Previous", + // TODO New key - Add a translation + "media-viewer.previous": "Previous", + + // "media-viewer.playlist": "Playlist", + // TODO New key - Add a translation + "media-viewer.playlist": "Playlist", + + // "register-email.title": "New user registration", // TODO New key - Add a translation "register-email.title": "New user registration", - + // "register-page.create-profile.header": "Create Profile", // TODO New key - Add a translation "register-page.create-profile.header": "Create Profile", - + // "register-page.create-profile.identification.header": "Identify", // TODO New key - Add a translation "register-page.create-profile.identification.header": "Identify", - + // "register-page.create-profile.identification.email": "Email Address", - // TODO New key - Add a translation - "register-page.create-profile.identification.email": "Email Address", - + "register-page.create-profile.identification.email": "Endereço de Email", + // "register-page.create-profile.identification.first-name": "First Name *", // TODO New key - Add a translation "register-page.create-profile.identification.first-name": "First Name *", - + // "register-page.create-profile.identification.first-name.error": "Please fill in a First Name", // TODO New key - Add a translation "register-page.create-profile.identification.first-name.error": "Please fill in a First Name", - + // "register-page.create-profile.identification.last-name": "Last Name *", // TODO New key - Add a translation "register-page.create-profile.identification.last-name": "Last Name *", - + // "register-page.create-profile.identification.last-name.error": "Please fill in a Last Name", // TODO New key - Add a translation "register-page.create-profile.identification.last-name.error": "Please fill in a Last Name", - + // "register-page.create-profile.identification.contact": "Contact Telephone", // TODO New key - Add a translation "register-page.create-profile.identification.contact": "Contact Telephone", - + // "register-page.create-profile.identification.language": "Language", // TODO New key - Add a translation "register-page.create-profile.identification.language": "Language", - + // "register-page.create-profile.security.header": "Security", // TODO New key - Add a translation "register-page.create-profile.security.header": "Security", - - // "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", + + // "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box.", // TODO New key - Add a translation - "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - + "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box.", + // "register-page.create-profile.security.label.password": "Password *", // TODO New key - Add a translation "register-page.create-profile.security.label.password": "Password *", - + // "register-page.create-profile.security.label.passwordrepeat": "Retype to confirm *", // TODO New key - Add a translation "register-page.create-profile.security.label.passwordrepeat": "Retype to confirm *", - + // "register-page.create-profile.security.error.empty-password": "Please enter a password in the box below.", // TODO New key - Add a translation "register-page.create-profile.security.error.empty-password": "Please enter a password in the box below.", - + // "register-page.create-profile.security.error.matching-passwords": "The passwords do not match.", // TODO New key - Add a translation "register-page.create-profile.security.error.matching-passwords": "The passwords do not match.", - - // "register-page.create-profile.security.error.password-length": "The password should be at least 6 characters long.", - // TODO New key - Add a translation - "register-page.create-profile.security.error.password-length": "The password should be at least 6 characters long.", - + // "register-page.create-profile.submit": "Complete Registration", // TODO New key - Add a translation "register-page.create-profile.submit": "Complete Registration", - + // "register-page.create-profile.submit.error.content": "Something went wrong while registering a new user.", // TODO New key - Add a translation "register-page.create-profile.submit.error.content": "Something went wrong while registering a new user.", - + // "register-page.create-profile.submit.error.head": "Registration failed", // TODO New key - Add a translation "register-page.create-profile.submit.error.head": "Registration failed", - + // "register-page.create-profile.submit.success.content": "The registration was successful. You have been logged in as the created user.", // TODO New key - Add a translation "register-page.create-profile.submit.success.content": "The registration was successful. You have been logged in as the created user.", - + // "register-page.create-profile.submit.success.head": "Registration completed", // TODO New key - Add a translation "register-page.create-profile.submit.success.head": "Registration completed", - - + + // "register-page.registration.header": "New user registration", // TODO New key - Add a translation "register-page.registration.header": "New user registration", - + // "register-page.registration.info": "Register an account to subscribe to collections for email updates, and submit new items to DSpace.", // TODO New key - Add a translation "register-page.registration.info": "Register an account to subscribe to collections for email updates, and submit new items to DSpace.", - + // "register-page.registration.email": "Email Address *", // TODO New key - Add a translation "register-page.registration.email": "Email Address *", - + // "register-page.registration.email.error.required": "Please fill in an email address", // TODO New key - Add a translation "register-page.registration.email.error.required": "Please fill in an email address", - + // "register-page.registration.email.error.pattern": "Please fill in a valid email address", // TODO New key - Add a translation "register-page.registration.email.error.pattern": "Please fill in a valid email address", - + // "register-page.registration.email.hint": "This address will be verified and used as your login name.", - // TODO New key - Add a translation - "register-page.registration.email.hint": "This address will be verified and used as your login name.", - + "register-page.registration.email.hint": "Este endereço será verificado e usado como seu nome de login.", + // "register-page.registration.submit": "Register", - // TODO New key - Add a translation - "register-page.registration.submit": "Register", - + "register-page.registration.submit": "Cadastrar", + // "register-page.registration.success.head": "Verification email sent", // TODO New key - Add a translation - "register-page.registration.success.head": "Verification email sent", - + "register-page.registration.success.head": "E-mail de verificação enviado", + // "register-page.registration.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", // TODO New key - Add a translation "register-page.registration.success.content": "An email has been sent to {{ email }} containing a special URL and further instructions.", - + // "register-page.registration.error.head": "Error when trying to register email", // TODO New key - Add a translation "register-page.registration.error.head": "Error when trying to register email", - + // "register-page.registration.error.content": "An error occured when registering the following email address: {{ email }}", // TODO New key - Add a translation "register-page.registration.error.content": "An error occured when registering the following email address: {{ email }}", - - - + + + // "relationships.add.error.relationship-type.content": "No suitable match could be found for relationship type {{ type }} between the two items", // TODO New key - Add a translation "relationships.add.error.relationship-type.content": "No suitable match could be found for relationship type {{ type }} between the two items", - + // "relationships.add.error.server.content": "The server returned an error", // TODO New key - Add a translation "relationships.add.error.server.content": "The server returned an error", - + // "relationships.add.error.title": "Unable to add relationship", // TODO New key - Add a translation "relationships.add.error.title": "Unable to add relationship", - + // "relationships.isAuthorOf": "Authors", "relationships.isAuthorOf": "Autores", - + // "relationships.isAuthorOf.Person": "Authors (persons)", // TODO New key - Add a translation "relationships.isAuthorOf.Person": "Authors (persons)", - + // "relationships.isAuthorOf.OrgUnit": "Authors (organizational units)", // TODO New key - Add a translation "relationships.isAuthorOf.OrgUnit": "Authors (organizational units)", - + // "relationships.isIssueOf": "Journal Issues", "relationships.isIssueOf": "Fascículo", - + // "relationships.isJournalIssueOf": "Journal Issue", "relationships.isJournalIssueOf": "Fascículo", - + // "relationships.isJournalOf": "Journals", "relationships.isJournalOf": "Periódicos", - + // "relationships.isOrgUnitOf": "Organizational Units", "relationships.isOrgUnitOf": "Unidades Organizacionais", - + // "relationships.isPersonOf": "Authors", "relationships.isPersonOf": "Autores", - + // "relationships.isProjectOf": "Research Projects", "relationships.isProjectOf": "Projetos de Pesquisa", - + // "relationships.isPublicationOf": "Publications", "relationships.isPublicationOf": "Publicações", - + // "relationships.isPublicationOfJournalIssue": "Articles", "relationships.isPublicationOfJournalIssue": "Artigos", - + // "relationships.isSingleJournalOf": "Journal", "relationships.isSingleJournalOf": "Periódico", - + // "relationships.isSingleVolumeOf": "Journal Volume", "relationships.isSingleVolumeOf": "Volume do Periódico", - + // "relationships.isVolumeOf": "Journal Volumes", "relationships.isVolumeOf": "Volumes do Periódico", - + // "relationships.isContributorOf": "Contributors", // TODO New key - Add a translation "relationships.isContributorOf": "Contributors", - - - + + // "relationships.isContributorOf.OrgUnit": "Contributor (Organizational Unit)", + // TODO New key - Add a translation + "relationships.isContributorOf.OrgUnit": "Contributor (Organizational Unit)", + + // "relationships.isContributorOf.Person": "Contributor", + // TODO New key - Add a translation + "relationships.isContributorOf.Person": "Contributor", + + // "relationships.isFundingAgencyOf.OrgUnit": "Funder", + // TODO New key - Add a translation + "relationships.isFundingAgencyOf.OrgUnit": "Funder", + + + // "repository.image.logo": "Repository logo", + // TODO New key - Add a translation + "repository.image.logo": "Repository logo", + + // "repository.title.prefix": "DSpace Angular :: ", + // TODO New key - Add a translation + "repository.title.prefix": "DSpace Angular :: ", + + // "repository.title.prefixDSpace": "DSpace Angular ::", + // TODO New key - Add a translation + "repository.title.prefixDSpace": "DSpace Angular ::", + + // "resource-policies.add.button": "Add", // TODO New key - Add a translation "resource-policies.add.button": "Add", - + // "resource-policies.add.for.": "Add a new policy", // TODO New key - Add a translation "resource-policies.add.for.": "Add a new policy", - + // "resource-policies.add.for.bitstream": "Add a new Bitstream policy", // TODO New key - Add a translation "resource-policies.add.for.bitstream": "Add a new Bitstream policy", - + // "resource-policies.add.for.bundle": "Add a new Bundle policy", // TODO New key - Add a translation "resource-policies.add.for.bundle": "Add a new Bundle policy", - + // "resource-policies.add.for.item": "Add a new Item policy", // TODO New key - Add a translation "resource-policies.add.for.item": "Add a new Item policy", - + // "resource-policies.add.for.community": "Add a new Community policy", // TODO New key - Add a translation "resource-policies.add.for.community": "Add a new Community policy", - + // "resource-policies.add.for.collection": "Add a new Collection policy", // TODO New key - Add a translation "resource-policies.add.for.collection": "Add a new Collection policy", - + // "resource-policies.create.page.heading": "Create new resource policy for ", // TODO New key - Add a translation "resource-policies.create.page.heading": "Create new resource policy for ", - + // "resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.", // TODO New key - Add a translation "resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.", - + // "resource-policies.create.page.success.content": "Operation successful", // TODO New key - Add a translation "resource-policies.create.page.success.content": "Operation successful", - + // "resource-policies.create.page.title": "Create new resource policy", // TODO New key - Add a translation "resource-policies.create.page.title": "Create new resource policy", - + // "resource-policies.delete.btn": "Delete selected", // TODO New key - Add a translation "resource-policies.delete.btn": "Delete selected", - + // "resource-policies.delete.btn.title": "Delete selected resource policies", // TODO New key - Add a translation "resource-policies.delete.btn.title": "Delete selected resource policies", - + // "resource-policies.delete.failure.content": "An error occurred while deleting selected resource policies.", // TODO New key - Add a translation "resource-policies.delete.failure.content": "An error occurred while deleting selected resource policies.", - + // "resource-policies.delete.success.content": "Operation successful", // TODO New key - Add a translation "resource-policies.delete.success.content": "Operation successful", - + // "resource-policies.edit.page.heading": "Edit resource policy ", // TODO New key - Add a translation "resource-policies.edit.page.heading": "Edit resource policy ", - + // "resource-policies.edit.page.failure.content": "An error occurred while editing the resource policy.", // TODO New key - Add a translation "resource-policies.edit.page.failure.content": "An error occurred while editing the resource policy.", - + + // "resource-policies.edit.page.target-failure.content": "An error occurred while editing the target (ePerson or group) of the resource policy.", + // TODO New key - Add a translation + "resource-policies.edit.page.target-failure.content": "An error occurred while editing the target (ePerson or group) of the resource policy.", + + // "resource-policies.edit.page.other-failure.content": "An error occurred while editing the resource policy. The target (ePerson or group) has been successfully updated.", + // TODO New key - Add a translation + "resource-policies.edit.page.other-failure.content": "An error occurred while editing the resource policy. The target (ePerson or group) has been successfully updated.", + // "resource-policies.edit.page.success.content": "Operation successful", // TODO New key - Add a translation "resource-policies.edit.page.success.content": "Operation successful", - + // "resource-policies.edit.page.title": "Edit resource policy", // TODO New key - Add a translation "resource-policies.edit.page.title": "Edit resource policy", - + // "resource-policies.form.action-type.label": "Select the action type", // TODO New key - Add a translation "resource-policies.form.action-type.label": "Select the action type", - + // "resource-policies.form.action-type.required": "You must select the resource policy action.", // TODO New key - Add a translation "resource-policies.form.action-type.required": "You must select the resource policy action.", - + // "resource-policies.form.eperson-group-list.label": "The eperson or group that will be granted the permission", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.label": "The eperson or group that will be granted the permission", - + // "resource-policies.form.eperson-group-list.select.btn": "Select", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.select.btn": "Select", - + // "resource-policies.form.eperson-group-list.tab.eperson": "Search for a ePerson", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.tab.eperson": "Search for a ePerson", - + // "resource-policies.form.eperson-group-list.tab.group": "Search for a group", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.tab.group": "Search for a group", - + // "resource-policies.form.eperson-group-list.table.headers.action": "Action", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.table.headers.action": "Action", - + // "resource-policies.form.eperson-group-list.table.headers.id": "ID", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.table.headers.id": "ID", - + // "resource-policies.form.eperson-group-list.table.headers.name": "Name", // TODO New key - Add a translation "resource-policies.form.eperson-group-list.table.headers.name": "Name", - + + // "resource-policies.form.eperson-group-list.modal.header": "Cannot change type", + // TODO New key - Add a translation + "resource-policies.form.eperson-group-list.modal.header": "Cannot change type", + + // "resource-policies.form.eperson-group-list.modal.text1.toGroup": "It is not possible to replace an ePerson with a group.", + // TODO New key - Add a translation + "resource-policies.form.eperson-group-list.modal.text1.toGroup": "It is not possible to replace an ePerson with a group.", + + // "resource-policies.form.eperson-group-list.modal.text1.toEPerson": "It is not possible to replace a group with an ePerson.", + // TODO New key - Add a translation + "resource-policies.form.eperson-group-list.modal.text1.toEPerson": "It is not possible to replace a group with an ePerson.", + + // "resource-policies.form.eperson-group-list.modal.text2": "Delete the current resource policy and create a new one with the desired type.", + // TODO New key - Add a translation + "resource-policies.form.eperson-group-list.modal.text2": "Delete the current resource policy and create a new one with the desired type.", + + // "resource-policies.form.eperson-group-list.modal.close": "Ok", + // TODO New key - Add a translation + "resource-policies.form.eperson-group-list.modal.close": "Ok", + // "resource-policies.form.date.end.label": "End Date", // TODO New key - Add a translation "resource-policies.form.date.end.label": "End Date", - + // "resource-policies.form.date.start.label": "Start Date", // TODO New key - Add a translation "resource-policies.form.date.start.label": "Start Date", - + // "resource-policies.form.description.label": "Description", // TODO New key - Add a translation "resource-policies.form.description.label": "Description", - + // "resource-policies.form.name.label": "Name", // TODO New key - Add a translation "resource-policies.form.name.label": "Name", - + // "resource-policies.form.policy-type.label": "Select the policy type", // TODO New key - Add a translation "resource-policies.form.policy-type.label": "Select the policy type", - + // "resource-policies.form.policy-type.required": "You must select the resource policy type.", // TODO New key - Add a translation "resource-policies.form.policy-type.required": "You must select the resource policy type.", - + // "resource-policies.table.headers.action": "Action", // TODO New key - Add a translation "resource-policies.table.headers.action": "Action", - + // "resource-policies.table.headers.date.end": "End Date", // TODO New key - Add a translation "resource-policies.table.headers.date.end": "End Date", - + // "resource-policies.table.headers.date.start": "Start Date", // TODO New key - Add a translation "resource-policies.table.headers.date.start": "Start Date", - + // "resource-policies.table.headers.edit": "Edit", - // TODO New key - Add a translation - "resource-policies.table.headers.edit": "Edit", - + "resource-policies.table.headers.edit": "Editar", + // "resource-policies.table.headers.edit.group": "Edit group", // TODO New key - Add a translation "resource-policies.table.headers.edit.group": "Edit group", - + // "resource-policies.table.headers.edit.policy": "Edit policy", // TODO New key - Add a translation "resource-policies.table.headers.edit.policy": "Edit policy", - + // "resource-policies.table.headers.eperson": "EPerson", // TODO New key - Add a translation "resource-policies.table.headers.eperson": "EPerson", - + // "resource-policies.table.headers.group": "Group", // TODO New key - Add a translation "resource-policies.table.headers.group": "Group", - + // "resource-policies.table.headers.id": "ID", // TODO New key - Add a translation "resource-policies.table.headers.id": "ID", - + // "resource-policies.table.headers.name": "Name", // TODO New key - Add a translation "resource-policies.table.headers.name": "Name", - + // "resource-policies.table.headers.policyType": "type", // TODO New key - Add a translation "resource-policies.table.headers.policyType": "type", - + // "resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream", // TODO New key - Add a translation "resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream", - + // "resource-policies.table.headers.title.for.bundle": "Policies for Bundle", // TODO New key - Add a translation "resource-policies.table.headers.title.for.bundle": "Policies for Bundle", - + // "resource-policies.table.headers.title.for.item": "Policies for Item", // TODO New key - Add a translation "resource-policies.table.headers.title.for.item": "Policies for Item", - + // "resource-policies.table.headers.title.for.community": "Policies for Community", // TODO New key - Add a translation "resource-policies.table.headers.title.for.community": "Policies for Community", - + // "resource-policies.table.headers.title.for.collection": "Policies for Collection", // TODO New key - Add a translation "resource-policies.table.headers.title.for.collection": "Policies for Collection", - - - + + + // "search.description": "", "search.description": "", - + // "search.switch-configuration.title": "Show", "search.switch-configuration.title": "Mostrar", - - // "search.title": "DSpace Angular :: Search", - "search.title": "DSpace Angular :: Busca", - + + // "search.title": "Search", + "search.title": "Procurar", + // "search.breadcrumbs": "Search", - // TODO New key - Add a translation - "search.breadcrumbs": "Search", - - + "search.breadcrumbs": "Procurar", + + // "search.search-form.placeholder": "Search the repository ...", + "search.search-form.placeholder": "Pesquise no repositório...", + + // "search.filters.applied.f.author": "Author", "search.filters.applied.f.author": "Autor", - + // "search.filters.applied.f.dateIssued.max": "End date", "search.filters.applied.f.dateIssued.max": "Data final", - + // "search.filters.applied.f.dateIssued.min": "Start date", "search.filters.applied.f.dateIssued.min": "Data inicial", - + // "search.filters.applied.f.dateSubmitted": "Date submitted", - "search.filters.applied.f.dateSubmitted": "Data de submissão", - - // "search.filters.applied.f.discoverable": "Private", + "search.filters.applied.f.dateSubmitted": "Data de envio", + + // "search.filters.applied.f.discoverable": "Non-discoverable", // TODO New key - Add a translation - "search.filters.applied.f.discoverable": "Private", - + "search.filters.applied.f.discoverable": "Non-discoverable", + // "search.filters.applied.f.entityType": "Item Type", "search.filters.applied.f.entityType": "Tipo de Item", - + // "search.filters.applied.f.has_content_in_original_bundle": "Has files", "search.filters.applied.f.has_content_in_original_bundle": "Tem arquivos", - + // "search.filters.applied.f.itemtype": "Type", "search.filters.applied.f.itemtype": "Tipo", - + // "search.filters.applied.f.namedresourcetype": "Status", "search.filters.applied.f.namedresourcetype": "Estado", - + // "search.filters.applied.f.subject": "Subject", "search.filters.applied.f.subject": "Assunto", - + // "search.filters.applied.f.submitter": "Submitter", "search.filters.applied.f.submitter": "Submetedor", - + // "search.filters.applied.f.jobTitle": "Job Title", // TODO New key - Add a translation "search.filters.applied.f.jobTitle": "Job Title", - + // "search.filters.applied.f.birthDate.max": "End birth date", // TODO New key - Add a translation "search.filters.applied.f.birthDate.max": "End birth date", - + // "search.filters.applied.f.birthDate.min": "Start birth date", // TODO New key - Add a translation "search.filters.applied.f.birthDate.min": "Start birth date", - + // "search.filters.applied.f.withdrawn": "Withdrawn", // TODO New key - Add a translation "search.filters.applied.f.withdrawn": "Withdrawn", - - - + + + // "search.filters.filter.author.head": "Author", "search.filters.filter.author.head": "Autor", - + // "search.filters.filter.author.placeholder": "Author name", "search.filters.filter.author.placeholder": "Nome do autor", - + + // "search.filters.filter.author.label": "Search author name", + // TODO New key - Add a translation + "search.filters.filter.author.label": "Search author name", + // "search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Data de nascimento", - + // "search.filters.filter.birthDate.placeholder": "Birth Date", "search.filters.filter.birthDate.placeholder": "Data de nascimento", - + + // "search.filters.filter.birthDate.label": "Search birth date", + // TODO New key - Add a translation + "search.filters.filter.birthDate.label": "Search birth date", + + // "search.filters.filter.collapse": "Collapse filter", + // TODO New key - Add a translation + "search.filters.filter.collapse": "Collapse filter", + // "search.filters.filter.creativeDatePublished.head": "Date Published", "search.filters.filter.creativeDatePublished.head": "Data de publicação", - + // "search.filters.filter.creativeDatePublished.placeholder": "Date Published", "search.filters.filter.creativeDatePublished.placeholder": "Data de publicação", - + + // "search.filters.filter.creativeDatePublished.label": "Search date published", + // TODO New key - Add a translation + "search.filters.filter.creativeDatePublished.label": "Search date published", + // "search.filters.filter.creativeWorkEditor.head": "Editor", "search.filters.filter.creativeWorkEditor.head": "Editor", - + // "search.filters.filter.creativeWorkEditor.placeholder": "Editor", "search.filters.filter.creativeWorkEditor.placeholder": "Editor", - + + // "search.filters.filter.creativeWorkEditor.label": "Search editor", + // TODO New key - Add a translation + "search.filters.filter.creativeWorkEditor.label": "Search editor", + // "search.filters.filter.creativeWorkKeywords.head": "Subject", "search.filters.filter.creativeWorkKeywords.head": "Assunto", - + // "search.filters.filter.creativeWorkKeywords.placeholder": "Subject", "search.filters.filter.creativeWorkKeywords.placeholder": "Assunto", - + + // "search.filters.filter.creativeWorkKeywords.label": "Search subject", + // TODO New key - Add a translation + "search.filters.filter.creativeWorkKeywords.label": "Search subject", + // "search.filters.filter.creativeWorkPublisher.head": "Publisher", "search.filters.filter.creativeWorkPublisher.head": "Editora", - + // "search.filters.filter.creativeWorkPublisher.placeholder": "Publisher", "search.filters.filter.creativeWorkPublisher.placeholder": "Editora", - + + // "search.filters.filter.creativeWorkPublisher.label": "Search publisher", + // TODO New key - Add a translation + "search.filters.filter.creativeWorkPublisher.label": "Search publisher", + // "search.filters.filter.dateIssued.head": "Date", "search.filters.filter.dateIssued.head": "Data", - - // "search.filters.filter.dateIssued.max.placeholder": "Minimum Date", - "search.filters.filter.dateIssued.max.placeholder": "Data Mínima", - - // "search.filters.filter.dateIssued.min.placeholder": "Maximum Date", - "search.filters.filter.dateIssued.min.placeholder": "Data Máxima", - + + // "search.filters.filter.dateIssued.max.placeholder": "Maximum Date", + "search.filters.filter.dateIssued.max.placeholder": "Data Máxima", + + // "search.filters.filter.dateIssued.max.label": "End", + "search.filters.filter.dateIssued.max.label": "Fim", + + // "search.filters.filter.dateIssued.min.placeholder": "Minimum Date", + "search.filters.filter.dateIssued.min.placeholder": "Data Mínima", + + // "search.filters.filter.dateIssued.min.label": "Start", + // TODO New key - Add a translation + "search.filters.filter.dateIssued.min.label": "Start", + // "search.filters.filter.dateSubmitted.head": "Date submitted", "search.filters.filter.dateSubmitted.head": "Data de submissão", - + // "search.filters.filter.dateSubmitted.placeholder": "Date submitted", "search.filters.filter.dateSubmitted.placeholder": "Data de submissão", - - // "search.filters.filter.discoverable.head": "Private", + + // "search.filters.filter.dateSubmitted.label": "Search date submitted", // TODO New key - Add a translation - "search.filters.filter.discoverable.head": "Private", - + "search.filters.filter.dateSubmitted.label": "Search date submitted", + + // "search.filters.filter.discoverable.head": "Non-discoverable", + // TODO New key - Add a translation + "search.filters.filter.discoverable.head": "Non-discoverable", + // "search.filters.filter.withdrawn.head": "Withdrawn", // TODO New key - Add a translation "search.filters.filter.withdrawn.head": "Withdrawn", - + // "search.filters.filter.entityType.head": "Item Type", "search.filters.filter.entityType.head": "Tipo de Item", - + // "search.filters.filter.entityType.placeholder": "Item Type", "search.filters.filter.entityType.placeholder": "Tipo de Item", - + + // "search.filters.filter.entityType.label": "Search item type", + // TODO New key - Add a translation + "search.filters.filter.entityType.label": "Search item type", + + // "search.filters.filter.expand": "Expand filter", + // TODO New key - Add a translation + "search.filters.filter.expand": "Expand filter", + // "search.filters.filter.has_content_in_original_bundle.head": "Has files", "search.filters.filter.has_content_in_original_bundle.head": "Tem arquivos", - + // "search.filters.filter.itemtype.head": "Type", "search.filters.filter.itemtype.head": "Tipo", - + // "search.filters.filter.itemtype.placeholder": "Type", "search.filters.filter.itemtype.placeholder": "Tipo", - + + // "search.filters.filter.itemtype.label": "Search type", + // TODO New key - Add a translation + "search.filters.filter.itemtype.label": "Search type", + // "search.filters.filter.jobTitle.head": "Job Title", "search.filters.filter.jobTitle.head": "Cargo", - + // "search.filters.filter.jobTitle.placeholder": "Job Title", "search.filters.filter.jobTitle.placeholder": "Cargo", - + + // "search.filters.filter.jobTitle.label": "Search job title", + // TODO New key - Add a translation + "search.filters.filter.jobTitle.label": "Search job title", + // "search.filters.filter.knowsLanguage.head": "Known language", "search.filters.filter.knowsLanguage.head": "Idioma conhecido", - + // "search.filters.filter.knowsLanguage.placeholder": "Known language", "search.filters.filter.knowsLanguage.placeholder": "Idioma conhecido", - + + // "search.filters.filter.knowsLanguage.label": "Search known language", + // TODO New key - Add a translation + "search.filters.filter.knowsLanguage.label": "Search known language", + // "search.filters.filter.namedresourcetype.head": "Status", "search.filters.filter.namedresourcetype.head": "Estado", - + // "search.filters.filter.namedresourcetype.placeholder": "Status", "search.filters.filter.namedresourcetype.placeholder": "Estado", - + + // "search.filters.filter.namedresourcetype.label": "Search status", + // TODO New key - Add a translation + "search.filters.filter.namedresourcetype.label": "Search status", + // "search.filters.filter.objectpeople.head": "People", "search.filters.filter.objectpeople.head": "Pessoas", - + // "search.filters.filter.objectpeople.placeholder": "People", "search.filters.filter.objectpeople.placeholder": "Pessoas", - + + // "search.filters.filter.objectpeople.label": "Search people", + // TODO New key - Add a translation + "search.filters.filter.objectpeople.label": "Search people", + // "search.filters.filter.organizationAddressCountry.head": "Country", "search.filters.filter.organizationAddressCountry.head": "País", - + // "search.filters.filter.organizationAddressCountry.placeholder": "Country", "search.filters.filter.organizationAddressCountry.placeholder": "País", - + + // "search.filters.filter.organizationAddressCountry.label": "Search country", + // TODO New key - Add a translation + "search.filters.filter.organizationAddressCountry.label": "Search country", + // "search.filters.filter.organizationAddressLocality.head": "City", "search.filters.filter.organizationAddressLocality.head": "Cidade", - + // "search.filters.filter.organizationAddressLocality.placeholder": "City", "search.filters.filter.organizationAddressLocality.placeholder": "Cidade", - + + // "search.filters.filter.organizationAddressLocality.label": "Search city", + // TODO New key - Add a translation + "search.filters.filter.organizationAddressLocality.label": "Search city", + // "search.filters.filter.organizationFoundingDate.head": "Date Founded", "search.filters.filter.organizationFoundingDate.head": "Data de Fundação", - + // "search.filters.filter.organizationFoundingDate.placeholder": "Date Founded", "search.filters.filter.organizationFoundingDate.placeholder": "Data de Fundação", - + + // "search.filters.filter.organizationFoundingDate.label": "Search date founded", + // TODO New key - Add a translation + "search.filters.filter.organizationFoundingDate.label": "Search date founded", + // "search.filters.filter.scope.head": "Scope", "search.filters.filter.scope.head": "Escopo", - + // "search.filters.filter.scope.placeholder": "Scope filter", "search.filters.filter.scope.placeholder": "Filtrar escopo", - + + // "search.filters.filter.scope.label": "Search scope filter", + // TODO New key - Add a translation + "search.filters.filter.scope.label": "Search scope filter", + // "search.filters.filter.show-less": "Collapse", "search.filters.filter.show-less": "Mostrar menos", - + // "search.filters.filter.show-more": "Show more", "search.filters.filter.show-more": "Mostrar mais", - + // "search.filters.filter.subject.head": "Subject", "search.filters.filter.subject.head": "Assunto", - + // "search.filters.filter.subject.placeholder": "Subject", "search.filters.filter.subject.placeholder": "Assunto", - + + // "search.filters.filter.subject.label": "Search subject", + // TODO New key - Add a translation + "search.filters.filter.subject.label": "Search subject", + // "search.filters.filter.submitter.head": "Submitter", "search.filters.filter.submitter.head": "Submetedor", - + // "search.filters.filter.submitter.placeholder": "Submitter", "search.filters.filter.submitter.placeholder": "Submetedor", - - - + + // "search.filters.filter.submitter.label": "Search submitter", + // TODO New key - Add a translation + "search.filters.filter.submitter.label": "Search submitter", + + + // "search.filters.entityType.JournalIssue": "Journal Issue", // TODO New key - Add a translation "search.filters.entityType.JournalIssue": "Journal Issue", - + // "search.filters.entityType.JournalVolume": "Journal Volume", // TODO New key - Add a translation "search.filters.entityType.JournalVolume": "Journal Volume", - + // "search.filters.entityType.OrgUnit": "Organizational Unit", "search.filters.entityType.OrgUnit": "Unidade Organizacional", - + // "search.filters.has_content_in_original_bundle.true": "Yes", "search.filters.has_content_in_original_bundle.true": "Sim", - + // "search.filters.has_content_in_original_bundle.false": "No", "search.filters.has_content_in_original_bundle.false": "Não", - + // "search.filters.discoverable.true": "No", "search.filters.discoverable.true": "Não", - + // "search.filters.discoverable.false": "Yes", "search.filters.discoverable.false": "Sim", - + // "search.filters.withdrawn.true": "Yes", "search.filters.withdrawn.true": "Sim", - + // "search.filters.withdrawn.false": "No", "search.filters.withdrawn.false": "Não", - - + + // "search.filters.head": "Filters", "search.filters.head": "Filtros", - + // "search.filters.reset": "Reset filters", "search.filters.reset": "Limpar filtros", - - - + + // "search.filters.search.submit": "Submit", + // TODO New key - Add a translation + "search.filters.search.submit": "Submit", + + + // "search.form.search": "Search", "search.form.search": "Buscar", - - // "search.form.search_dspace": "Search DSpace", - "search.form.search_dspace": "Buscar no DSpace", - - // "search.form.search_mydspace": "Search MyDSpace", - "search.form.search_mydspace": "Buscar no MyDSpace", - - - + + // "search.form.search_dspace": "All repository", + "search.form.search_dspace": "Todo o repositório", + + // "search.form.scope.all": "All of DSpace", + "search.form.scope.all": "Todo o DSpace", + + + // "search.results.head": "Search Results", "search.results.head": "Resultados de Busca", - + // "search.results.no-results": "Your search returned no results. Having trouble finding what you're looking for? Try putting", "search.results.no-results": "Sua busca não trouxe resultados. Tendo problema em localizar o que está buscando? Tente", - + // "search.results.no-results-link": "quotes around it", "search.results.no-results-link": "envolver entre aspas", - + // "search.results.empty": "Your search returned no results.", "search.results.empty": "Sua pesquisa não retornou resultados.", - - - + + // "search.results.view-result": "View", + // TODO New key - Add a translation + "search.results.view-result": "View", + + // "search.results.response.500": "An error occurred during query execution, please try again later", + // TODO New key - Add a translation + "search.results.response.500": "An error occurred during query execution, please try again later", + + // "default.search.results.head": "Search Results", + // TODO New key - Add a translation + "default.search.results.head": "Search Results", + + // "default-relationships.search.results.head": "Search Results", + // TODO New key - Add a translation + "default-relationships.search.results.head": "Search Results", + + // "search.sidebar.close": "Back to results", "search.sidebar.close": "Voltar para os resultados", - + // "search.sidebar.filters.title": "Filters", "search.sidebar.filters.title": "Filtros", - + // "search.sidebar.open": "Search Tools", "search.sidebar.open": "Ferramentas de busca", - + // "search.sidebar.results": "results", "search.sidebar.results": "resultados", - + // "search.sidebar.settings.rpp": "Results per page", "search.sidebar.settings.rpp": "Resultados por página", - + // "search.sidebar.settings.sort-by": "Sort By", "search.sidebar.settings.sort-by": "Ordenar por", - + // "search.sidebar.settings.title": "Settings", "search.sidebar.settings.title": "Configurações", - - - + + + // "search.view-switch.show-detail": "Show detail", "search.view-switch.show-detail": "Mostrar detalhes", - + // "search.view-switch.show-grid": "Show as grid", "search.view-switch.show-grid": "Mostrar como grade", - + // "search.view-switch.show-list": "Show as list", "search.view-switch.show-list": "Mostrar como lista", - - - + + + // "sorting.ASC": "Ascending", // TODO New key - Add a translation "sorting.ASC": "Ascending", - + // "sorting.DESC": "Descending", // TODO New key - Add a translation "sorting.DESC": "Descending", - + // "sorting.dc.title.ASC": "Title Ascending", "sorting.dc.title.ASC": "Título Ascendente", - + // "sorting.dc.title.DESC": "Title Descending", "sorting.dc.title.DESC": "Título Descendente", - - // "sorting.score.DESC": "Relevance", - "sorting.score.DESC": "Relevância", - - - + + // "sorting.score.ASC": "Least Relevant", + // TODO New key - Add a translation + "sorting.score.ASC": "Least Relevant", + + // "sorting.score.DESC": "Most Relevant", + "sorting.score.DESC": "Mais Relevante", + + // "sorting.dc.date.issued.ASC": "Date Issued Ascending", + // TODO New key - Add a translation + "sorting.dc.date.issued.ASC": "Date Issued Ascending", + + // "sorting.dc.date.issued.DESC": "Date Issued Descending", + // TODO New key - Add a translation + "sorting.dc.date.issued.DESC": "Date Issued Descending", + + // "sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending", + // TODO New key - Add a translation + "sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending", + + // "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", + // TODO New key - Add a translation + "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", + + // "sorting.lastModified.ASC": "Last modified Ascending", + // TODO New key - Add a translation + "sorting.lastModified.ASC": "Last modified Ascending", + + // "sorting.lastModified.DESC": "Last modified Descending", + // TODO New key - Add a translation + "sorting.lastModified.DESC": "Last modified Descending", + + // "statistics.title": "Statistics", // TODO New key - Add a translation "statistics.title": "Statistics", - + // "statistics.header": "Statistics for {{ scope }}", // TODO New key - Add a translation "statistics.header": "Statistics for {{ scope }}", - + // "statistics.breadcrumbs": "Statistics", // TODO New key - Add a translation "statistics.breadcrumbs": "Statistics", - + // "statistics.page.no-data": "No data available", // TODO New key - Add a translation "statistics.page.no-data": "No data available", - + // "statistics.table.no-data": "No data available", // TODO New key - Add a translation "statistics.table.no-data": "No data available", - + // "statistics.table.title.TotalVisits": "Total visits", // TODO New key - Add a translation "statistics.table.title.TotalVisits": "Total visits", - + // "statistics.table.title.TotalVisitsPerMonth": "Total visits per month", // TODO New key - Add a translation "statistics.table.title.TotalVisitsPerMonth": "Total visits per month", - + // "statistics.table.title.TotalDownloads": "File Visits", // TODO New key - Add a translation "statistics.table.title.TotalDownloads": "File Visits", - + // "statistics.table.title.TopCountries": "Top country views", // TODO New key - Add a translation "statistics.table.title.TopCountries": "Top country views", - + // "statistics.table.title.TopCities": "Top city views", // TODO New key - Add a translation "statistics.table.title.TopCities": "Top city views", - + // "statistics.table.header.views": "Views", // TODO New key - Add a translation "statistics.table.header.views": "Views", - - - + + + + // "submission.edit.breadcrumbs": "Edit Submission", + // TODO New key - Add a translation + "submission.edit.breadcrumbs": "Edit Submission", + // "submission.edit.title": "Edit Submission", "submission.edit.title": "Editar Submissão", - + + // "submission.general.cancel": "Cancel", + // TODO New key - Add a translation + "submission.general.cancel": "Cancel", + // "submission.general.cannot_submit": "You have not the privilege to make a new submission.", "submission.general.cannot_submit": "Você mão tem privilégios para fazer uma nova submissão.", - + // "submission.general.deposit": "Deposit", "submission.general.deposit": "Depositar", - + // "submission.general.discard.confirm.cancel": "Cancel", "submission.general.discard.confirm.cancel": "Cancelar", - + // "submission.general.discard.confirm.info": "This operation can't be undone. Are you sure?", "submission.general.discard.confirm.info": "Esta operação não pode ser desfeita. Tem certeza?", - + // "submission.general.discard.confirm.submit": "Yes, I'm sure", "submission.general.discard.confirm.submit": "Sim, tenho certeza", - + // "submission.general.discard.confirm.title": "Discard submission", "submission.general.discard.confirm.title": "Descartar submissão", - + // "submission.general.discard.submit": "Discard", "submission.general.discard.submit": "Descartar", - + + // "submission.general.info.saved": "Saved", + // TODO New key - Add a translation + "submission.general.info.saved": "Saved", + + // "submission.general.info.pending-changes": "Unsaved changes", + // TODO New key - Add a translation + "submission.general.info.pending-changes": "Unsaved changes", + // "submission.general.save": "Save", "submission.general.save": "Salvar", - + // "submission.general.save-later": "Save for later", "submission.general.save-later": "Salvar para continuar depois", - - + + // "submission.import-external.page.title": "Import metadata from an external source", // TODO New key - Add a translation "submission.import-external.page.title": "Import metadata from an external source", - + // "submission.import-external.title": "Import metadata from an external source", // TODO New key - Add a translation "submission.import-external.title": "Import metadata from an external source", - + + // "submission.import-external.title.Journal": "Import a journal from an external source", + // TODO New key - Add a translation + "submission.import-external.title.Journal": "Import a journal from an external source", + + // "submission.import-external.title.JournalIssue": "Import a journal issue from an external source", + // TODO New key - Add a translation + "submission.import-external.title.JournalIssue": "Import a journal issue from an external source", + + // "submission.import-external.title.JournalVolume": "Import a journal volume from an external source", + // TODO New key - Add a translation + "submission.import-external.title.JournalVolume": "Import a journal volume from an external source", + + // "submission.import-external.title.OrgUnit": "Import a publisher from an external source", + // TODO New key - Add a translation + "submission.import-external.title.OrgUnit": "Import a publisher from an external source", + + // "submission.import-external.title.Person": "Import a person from an external source", + // TODO New key - Add a translation + "submission.import-external.title.Person": "Import a person from an external source", + + // "submission.import-external.title.Project": "Import a project from an external source", + // TODO New key - Add a translation + "submission.import-external.title.Project": "Import a project from an external source", + + // "submission.import-external.title.Publication": "Import a publication from an external source", + // TODO New key - Add a translation + "submission.import-external.title.Publication": "Import a publication from an external source", + + // "submission.import-external.title.none": "Import metadata from an external source", + // TODO New key - Add a translation + "submission.import-external.title.none": "Import metadata from an external source", + // "submission.import-external.page.hint": "Enter a query above to find items from the web to import in to DSpace.", // TODO New key - Add a translation "submission.import-external.page.hint": "Enter a query above to find items from the web to import in to DSpace.", - + // "submission.import-external.back-to-my-dspace": "Back to MyDSpace", // TODO New key - Add a translation "submission.import-external.back-to-my-dspace": "Back to MyDSpace", - + // "submission.import-external.search.placeholder": "Search the external source", // TODO New key - Add a translation "submission.import-external.search.placeholder": "Search the external source", - + // "submission.import-external.search.button": "Search", // TODO New key - Add a translation "submission.import-external.search.button": "Search", - + // "submission.import-external.search.button.hint": "Write some words to search", // TODO New key - Add a translation "submission.import-external.search.button.hint": "Write some words to search", - + // "submission.import-external.search.source.hint": "Pick an external source", // TODO New key - Add a translation "submission.import-external.search.source.hint": "Pick an external source", - + // "submission.import-external.source.arxiv": "arXiv", // TODO New key - Add a translation "submission.import-external.source.arxiv": "arXiv", - + + // "submission.import-external.source.ads": "NASA/ADS", + // TODO New key - Add a translation + "submission.import-external.source.ads": "NASA/ADS", + + // "submission.import-external.source.cinii": "CiNii", + // TODO New key - Add a translation + "submission.import-external.source.cinii": "CiNii", + + // "submission.import-external.source.crossref": "CrossRef", + // TODO New key - Add a translation + "submission.import-external.source.crossref": "CrossRef", + + // "submission.import-external.source.scielo": "SciELO", + // TODO New key - Add a translation + "submission.import-external.source.scielo": "SciELO", + + // "submission.import-external.source.scopus": "Scopus", + // TODO New key - Add a translation + "submission.import-external.source.scopus": "Scopus", + + // "submission.import-external.source.vufind": "VuFind", + // TODO New key - Add a translation + "submission.import-external.source.vufind": "VuFind", + + // "submission.import-external.source.wos": "Web Of Science", + // TODO New key - Add a translation + "submission.import-external.source.wos": "Web Of Science", + + // "submission.import-external.source.orcidWorks": "ORCID", + // TODO New key - Add a translation + "submission.import-external.source.orcidWorks": "ORCID", + + // "submission.import-external.source.epo": "European Patent Office (EPO)", + // TODO New key - Add a translation + "submission.import-external.source.epo": "European Patent Office (EPO)", + // "submission.import-external.source.loading": "Loading ...", // TODO New key - Add a translation "submission.import-external.source.loading": "Loading ...", - + // "submission.import-external.source.sherpaJournal": "SHERPA Journals", // TODO New key - Add a translation "submission.import-external.source.sherpaJournal": "SHERPA Journals", - + + // "submission.import-external.source.sherpaJournalIssn": "SHERPA Journals by ISSN", + // TODO New key - Add a translation + "submission.import-external.source.sherpaJournalIssn": "SHERPA Journals by ISSN", + // "submission.import-external.source.sherpaPublisher": "SHERPA Publishers", // TODO New key - Add a translation "submission.import-external.source.sherpaPublisher": "SHERPA Publishers", - + + // "submission.import-external.source.openAIREFunding": "Funding OpenAIRE API", + // TODO New key - Add a translation + "submission.import-external.source.openAIREFunding": "Funding OpenAIRE API", + // "submission.import-external.source.orcid": "ORCID", // TODO New key - Add a translation "submission.import-external.source.orcid": "ORCID", - + // "submission.import-external.source.pubmed": "Pubmed", // TODO New key - Add a translation "submission.import-external.source.pubmed": "Pubmed", - + + // "submission.import-external.source.pubmedeu": "Pubmed Europe", + // TODO New key - Add a translation + "submission.import-external.source.pubmedeu": "Pubmed Europe", + // "submission.import-external.source.lcname": "Library of Congress Names", // TODO New key - Add a translation "submission.import-external.source.lcname": "Library of Congress Names", - + // "submission.import-external.preview.title": "Item Preview", // TODO New key - Add a translation "submission.import-external.preview.title": "Item Preview", - + + // "submission.import-external.preview.title.Publication": "Publication Preview", + // TODO New key - Add a translation + "submission.import-external.preview.title.Publication": "Publication Preview", + + // "submission.import-external.preview.title.none": "Item Preview", + // TODO New key - Add a translation + "submission.import-external.preview.title.none": "Item Preview", + + // "submission.import-external.preview.title.Journal": "Journal Preview", + // TODO New key - Add a translation + "submission.import-external.preview.title.Journal": "Journal Preview", + + // "submission.import-external.preview.title.OrgUnit": "Organizational Unit Preview", + // TODO New key - Add a translation + "submission.import-external.preview.title.OrgUnit": "Organizational Unit Preview", + + // "submission.import-external.preview.title.Person": "Person Preview", + // TODO New key - Add a translation + "submission.import-external.preview.title.Person": "Person Preview", + + // "submission.import-external.preview.title.Project": "Project Preview", + // TODO New key - Add a translation + "submission.import-external.preview.title.Project": "Project Preview", + // "submission.import-external.preview.subtitle": "The metadata below was imported from an external source. It will be pre-filled when you start the submission.", // TODO New key - Add a translation "submission.import-external.preview.subtitle": "The metadata below was imported from an external source. It will be pre-filled when you start the submission.", - + // "submission.import-external.preview.button.import": "Start submission", // TODO New key - Add a translation "submission.import-external.preview.button.import": "Start submission", - + // "submission.import-external.preview.error.import.title": "Submission error", // TODO New key - Add a translation "submission.import-external.preview.error.import.title": "Submission error", - + // "submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.", // TODO New key - Add a translation "submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.", - + // "submission.sections.describe.relationship-lookup.close": "Close", "submission.sections.describe.relationship-lookup.close": "Fechar", - + // "submission.sections.describe.relationship-lookup.external-source.added": "Successfully added local entry to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.added": "Successfully added local entry to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.isAuthorOfPublication": "Import remote author", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-button-title.isAuthorOfPublication": "Import remote author", - + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal": "Import remote journal", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal": "Import remote journal", - + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Issue": "Import remote journal issue", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Issue": "Import remote journal issue", - + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Volume": "Import remote journal volume", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal Volume": "Import remote journal volume", - + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Project", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Project", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.none": "Import remote item", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.none": "Import remote item", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Event": "Import remote event", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Event": "Import remote event", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Product": "Import remote product", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Product": "Import remote product", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Equipment": "Import remote equipment", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Equipment": "Import remote equipment", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.OrgUnit": "Import remote organizational unit", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.OrgUnit": "Import remote organizational unit", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Funding": "Import remote fund", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Funding": "Import remote fund", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Person": "Import remote person", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Person": "Import remote person", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Patent": "Import remote patent", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Patent": "Import remote patent", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Project": "Import remote project", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Project": "Import remote project", + + // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication": "Import remote publication", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication": "Import remote publication", + + // "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.added.new-entity": "New Entity Added!", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.added.new-entity": "New Entity Added!", + + // "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.title": "Project", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.title": "Project", + + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.openAIREFunding": "Funding OpenAIRE API", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.external-source.import-modal.head.openAIREFunding": "Funding OpenAIRE API", + // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Import Remote Author", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.title": "Import Remote Author", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Successfully added local author to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.local-entity": "Successfully added local author to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Successfully imported and added external author to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.isAuthorOfPublication.added.new-entity": "Successfully imported and added external author to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Authority", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.authority": "Authority", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.authority.new": "Import as a new local authority entry", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.authority.new": "Import as a new local authority entry", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.cancel": "Cancel", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.cancel": "Cancel", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.collection": "Select a collection to import new entries to", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.collection": "Select a collection to import new entries to", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.entities": "Entities", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.entities": "Entities", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.entities.new": "Import as a new local entity", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.entities.new": "Import as a new local entity", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.lcname": "Importing from LC Name", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.head.lcname": "Importing from LC Name", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.orcid": "Importing from ORCID", "submission.sections.describe.relationship-lookup.external-source.import-modal.head.orcid": "Importando do ORCID", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.sherpaJournal": "Importing from Sherpa Journal", "submission.sections.describe.relationship-lookup.external-source.import-modal.head.sherpaJournal": "Importando do Sherpa Journal", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.sherpaPublisher": "Importing from Sherpa Publisher", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.head.sherpaPublisher": "Importing from Sherpa Publisher", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.pubmed": "Importing from PubMed", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.head.pubmed": "Importing from PubMed", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.arxiv": "Importing from arXiv", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.head.arxiv": "Importing from arXiv", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.import": "Import", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.import": "Import", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.title": "Import Remote Journal", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.title": "Import Remote Journal", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.local-entity": "Successfully added local journal to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.local-entity": "Successfully added local journal to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.new-entity": "Successfully imported and added external journal to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.new-entity": "Successfully imported and added external journal to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.title": "Import Remote Journal Issue", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.title": "Import Remote Journal Issue", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.added.local-entity": "Successfully added local journal issue to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.added.local-entity": "Successfully added local journal issue to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.added.new-entity": "Successfully imported and added external journal issue to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.added.new-entity": "Successfully imported and added external journal issue to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.title": "Import Remote Journal Volume", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.title": "Import Remote Journal Volume", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.added.local-entity": "Successfully added local journal volume to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.added.local-entity": "Successfully added local journal volume to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.added.new-entity": "Successfully imported and added external journal volume to the selection", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Volume.added.new-entity": "Successfully imported and added external journal volume to the selection", - + // "submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Select a local match:", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Select a local match:", - + // "submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all", - + // "submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page", - + // "submission.sections.describe.relationship-lookup.search-tab.loading": "Loading...", "submission.sections.describe.relationship-lookup.search-tab.loading": "Carregando...", - + // "submission.sections.describe.relationship-lookup.search-tab.placeholder": "Search query", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.placeholder": "Search query", - + // "submission.sections.describe.relationship-lookup.search-tab.search": "Go", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.search": "Go", - + + // "submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder": "Search...", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.search-tab.search-form.placeholder": "Search...", + // "submission.sections.describe.relationship-lookup.search-tab.select-all": "Select all", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.select-all": "Select all", - + // "submission.sections.describe.relationship-lookup.search-tab.select-page": "Select page", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.select-page": "Select page", - + // "submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Local Authors ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Local Authors ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Local Journals ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalOfPublication": "Local Journals ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Local Projects ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Local Projects ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Publication": "Local Publications ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.Publication": "Local Publications ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Local Authors ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.Person": "Local Authors ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Local Organizational Units ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.OrgUnit": "Local Organizational Units ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataPackage": "Local Data Packages ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataPackage": "Local Data Packages ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataFile": "Local Data Files ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataFile": "Local Data Files ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Local Journals ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Local Journals ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Local Journal Issues ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalIssueOfPublication": "Local Journal Issues ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Local Journal Issues ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalIssue": "Local Journal Issues ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Local Journal Volumes ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isJournalVolumeOfPublication": "Local Journal Volumes ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Local Journal Volumes ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.JournalVolume": "Local Journal Volumes ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Sherpa Publishers ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Sherpa Publishers ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.orcid": "ORCID ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.orcid": "ORCID ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.lcname": "LC Names ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.lcname": "LC Names ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.pubmed": "PubMed ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.pubmed": "PubMed ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.arxiv": "arXiv ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.arxiv": "arXiv ({{ count }})", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfPublication": "Search for Funding Agencies", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfPublication": "Search for Funding Agencies", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Search for Funding", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Search for Funding", - + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isChildOrgUnitOf": "Search for Organizational Units", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.tab-title.isChildOrgUnitOf": "Search for Organizational Units", - + + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.openAIREFunding": "Funding OpenAIRE API", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.search-tab.tab-title.openAIREFunding": "Funding OpenAIRE API", + + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isProjectOfPublication": "Projects", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isProjectOfPublication": "Projects", + + // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfProject": "Funder of the Project", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfProject": "Funder of the Project", + + // "submission.sections.describe.relationship-lookup.selection-tab.title.openAIREFunding": "Funding OpenAIRE API", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.openAIREFunding": "Funding OpenAIRE API", + + // "submission.sections.describe.relationship-lookup.selection-tab.title.isProjectOfPublication": "Project", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.isProjectOfPublication": "Project", + + // "submission.sections.describe.relationship-lookup.title.isProjectOfPublication": "Projects", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.title.isProjectOfPublication": "Projects", + + // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfProject": "Funder of the Project", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfProject": "Funder of the Project", + + + + + // "submission.sections.describe.relationship-lookup.selection-tab.search-form.placeholder": "Search...", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.search-form.placeholder": "Search...", + // "submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})", - + // "submission.sections.describe.relationship-lookup.title.isJournalIssueOfPublication": "Journal Issues", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isJournalIssueOfPublication": "Journal Issues", // "submission.sections.describe.relationship-lookup.title.JournalIssue": "Journal Issues", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.JournalIssue": "Journal Issues", - + // "submission.sections.describe.relationship-lookup.title.isJournalVolumeOfPublication": "Journal Volumes", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isJournalVolumeOfPublication": "Journal Volumes", // "submission.sections.describe.relationship-lookup.title.JournalVolume": "Journal Volumes", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.JournalVolume": "Journal Volumes", - + // "submission.sections.describe.relationship-lookup.title.isJournalOfPublication": "Journals", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isJournalOfPublication": "Journals", - + // "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Authors", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Authors", - + // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Funding Agency", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Funding Agency", // "submission.sections.describe.relationship-lookup.title.Project": "Projects", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.Project": "Projects", - + // "submission.sections.describe.relationship-lookup.title.Publication": "Publications", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.Publication": "Publications", - + // "submission.sections.describe.relationship-lookup.title.Person": "Authors", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.Person": "Authors", - + // "submission.sections.describe.relationship-lookup.title.OrgUnit": "Organizational Units", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.OrgUnit": "Organizational Units", - + // "submission.sections.describe.relationship-lookup.title.DataPackage": "Data Packages", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.DataPackage": "Data Packages", - + // "submission.sections.describe.relationship-lookup.title.DataFile": "Data Files", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.DataFile": "Data Files", - + // "submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency", - + // "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Funding", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isFundingOfPublication": "Funding", - + // "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Parent Organizational Unit", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.title.isChildOrgUnitOf": "Parent Organizational Unit", - + // "submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown", - + // "submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings", - + // "submission.sections.describe.relationship-lookup.selection-tab.no-selection": "Your selection is currently empty.", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.no-selection": "Your selection is currently empty.", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isAuthorOfPublication": "Selected Authors", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isAuthorOfPublication": "Selected Authors", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalOfPublication": "Selected Journals", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalOfPublication": "Selected Journals", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalVolumeOfPublication": "Selected Journal Volume", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalVolumeOfPublication": "Selected Journal Volume", // "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Selected Projects", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Selected Projects", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.Publication": "Selected Publications", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.Publication": "Selected Publications", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Selected Authors", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Selected Authors", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.OrgUnit": "Selected Organizational Units", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.OrgUnit": "Selected Organizational Units", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.DataPackage": "Selected Data Packages", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.DataPackage": "Selected Data Packages", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Selected Data Files", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Selected Data Files", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Selected Journals", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Selected Journals", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Selected Issue", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Selected Issue", // "submission.sections.describe.relationship-lookup.selection-tab.title.JournalVolume": "Selected Journal Volume", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.JournalVolume": "Selected Journal Volume", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingAgencyOfPublication": "Selected Funding Agency", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingAgencyOfPublication": "Selected Funding Agency", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Selected Funding", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingOfPublication": "Selected Funding", // "submission.sections.describe.relationship-lookup.selection-tab.title.JournalIssue": "Selected Issue", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.JournalIssue": "Selected Issue", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.isChildOrgUnitOf": "Selected Organizational Unit", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.isChildOrgUnitOf": "Selected Organizational Unit", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.sherpaJournal": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.sherpaJournal": "Search Results", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.sherpaPublisher": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.sherpaPublisher": "Search Results", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.orcid": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.orcid": "Search Results", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.orcidv2": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.orcidv2": "Search Results", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.lcname": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.lcname": "Search Results", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.pubmed": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.pubmed": "Search Results", - + // "submission.sections.describe.relationship-lookup.selection-tab.title.arxiv": "Search Results", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.selection-tab.title.arxiv": "Search Results", - + + // "submission.sections.describe.relationship-lookup.selection-tab.title.crossref": "Search Results", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.crossref": "Search Results", + + // "submission.sections.describe.relationship-lookup.selection-tab.title.epo": "Search Results", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.epo": "Search Results", + + // "submission.sections.describe.relationship-lookup.selection-tab.title.scopus": "Search Results", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.scopus": "Search Results", + + // "submission.sections.describe.relationship-lookup.selection-tab.title.scielo": "Search Results", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.scielo": "Search Results", + + // "submission.sections.describe.relationship-lookup.selection-tab.title.wos": "Search Results", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title.wos": "Search Results", + + // "submission.sections.describe.relationship-lookup.selection-tab.title": "Search Results", + // TODO New key - Add a translation + "submission.sections.describe.relationship-lookup.selection-tab.title": "Search Results", + // "submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.", - + // "submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant", - + // "submission.sections.describe.relationship-lookup.name-variant.notification.decline": "Use only for this submission", // TODO New key - Add a translation "submission.sections.describe.relationship-lookup.name-variant.notification.decline": "Use only for this submission", - + // "submission.sections.ccLicense.type": "License Type", // TODO New key - Add a translation "submission.sections.ccLicense.type": "License Type", - + // "submission.sections.ccLicense.select": "Select a license type…", // TODO New key - Add a translation "submission.sections.ccLicense.select": "Select a license type…", - + // "submission.sections.ccLicense.change": "Change your license type…", // TODO New key - Add a translation "submission.sections.ccLicense.change": "Change your license type…", - + // "submission.sections.ccLicense.none": "No licenses available", // TODO New key - Add a translation "submission.sections.ccLicense.none": "No licenses available", - + // "submission.sections.ccLicense.option.select": "Select an option…", // TODO New key - Add a translation "submission.sections.ccLicense.option.select": "Select an option…", - + // "submission.sections.ccLicense.link": "You’ve selected the following license:", // TODO New key - Add a translation "submission.sections.ccLicense.link": "You’ve selected the following license:", - + // "submission.sections.ccLicense.confirmation": "I grant the license above", // TODO New key - Add a translation "submission.sections.ccLicense.confirmation": "I grant the license above", - + // "submission.sections.general.add-more": "Add more", "submission.sections.general.add-more": "Adicionar mais", - + + // "submission.sections.general.cannot_deposit": "Deposit cannot be completed due to errors in the form.
Please fill out all required fields to complete the deposit.", + // TODO New key - Add a translation + "submission.sections.general.cannot_deposit": "Deposit cannot be completed due to errors in the form.
Please fill out all required fields to complete the deposit.", + // "submission.sections.general.collection": "Collection", "submission.sections.general.collection": "Coleção", - + // "submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.", "submission.sections.general.deposit_error_notice": "Houve um problema durante a submissão do item, por favor tente novamente mais tarde.", - + // "submission.sections.general.deposit_success_notice": "Submission deposited successfully.", "submission.sections.general.deposit_success_notice": "Submissão depositada com sucesso.", - + // "submission.sections.general.discard_error_notice": "There was an issue when discarding the item, please try again later.", "submission.sections.general.discard_error_notice": "Houve um problema ao descartar o item, por favor tente novamente mais tarde.", - + // "submission.sections.general.discard_success_notice": "Submission discarded successfully.", "submission.sections.general.discard_success_notice": "Submissão descartada com sucesso.", - + // "submission.sections.general.metadata-extracted": "New metadata have been extracted and added to the {{sectionId}} section.", "submission.sections.general.metadata-extracted": "Novos metadados foram extraídos e adicionados a seção {{sectionId}}.", - + // "submission.sections.general.metadata-extracted-new-section": "New {{sectionId}} section has been added to submission.", "submission.sections.general.metadata-extracted-new-section": "Nova seção {{sectionId}} foi adicionada a dubmissão.", - + // "submission.sections.general.no-collection": "No collection found", "submission.sections.general.no-collection": "Nenhuma coleção encontrada", - + // "submission.sections.general.no-sections": "No options available", "submission.sections.general.no-sections": "Sem opções disponíveis", - + // "submission.sections.general.save_error_notice": "There was an issue when saving the item, please try again later.", "submission.sections.general.save_error_notice": "Houve um problema ao salvar o item, por favor tente novamente mais tarde.", - + // "submission.sections.general.save_success_notice": "Submission saved successfully.", "submission.sections.general.save_success_notice": "Submissão salva com sucesso.", - + // "submission.sections.general.search-collection": "Search for a collection", "submission.sections.general.search-collection": "Buscar uma coleção", - + // "submission.sections.general.sections_not_valid": "There are incomplete sections.", "submission.sections.general.sections_not_valid": "Há seções incompletas.", - - - + + + + // "submission.sections.submit.progressbar.accessCondition": "Item access conditions", + // TODO New key - Add a translation + "submission.sections.submit.progressbar.accessCondition": "Item access conditions", + // "submission.sections.submit.progressbar.CClicense": "Creative commons license", // TODO New key - Add a translation "submission.sections.submit.progressbar.CClicense": "Creative commons license", - + // "submission.sections.submit.progressbar.describe.recycle": "Recycle", "submission.sections.submit.progressbar.describe.recycle": "Reciclar", - + // "submission.sections.submit.progressbar.describe.stepcustom": "Describe", "submission.sections.submit.progressbar.describe.stepcustom": "Descrever", - + // "submission.sections.submit.progressbar.describe.stepone": "Describe", "submission.sections.submit.progressbar.describe.stepone": "Descrever", - + // "submission.sections.submit.progressbar.describe.steptwo": "Describe", "submission.sections.submit.progressbar.describe.steptwo": "Descrever", - + // "submission.sections.submit.progressbar.detect-duplicate": "Potential duplicates", "submission.sections.submit.progressbar.detect-duplicate": "Duplicados em potencial", - + // "submission.sections.submit.progressbar.license": "Deposit license", "submission.sections.submit.progressbar.license": "Depositar licença", - + + // "submission.sections.submit.progressbar.sherpapolicy": "Sherpa policies", + // TODO New key - Add a translation + "submission.sections.submit.progressbar.sherpapolicy": "Sherpa policies", + // "submission.sections.submit.progressbar.upload": "Upload files", "submission.sections.submit.progressbar.upload": "Enviar arquivos", - - - + + // "submission.sections.submit.progressbar.sherpaPolicies": "Publisher open access policy information", + // TODO New key - Add a translation + "submission.sections.submit.progressbar.sherpaPolicies": "Publisher open access policy information", + + + // "submission.sections.sherpa-policy.title-empty": "No publisher policy information available. If your work has an associated ISSN, please enter it above to see any related publisher open access policies.", + // TODO New key - Add a translation + "submission.sections.sherpa-policy.title-empty": "No publisher policy information available. If your work has an associated ISSN, please enter it above to see any related publisher open access policies.", + + // "submission.sections.status.errors.title": "Errors", + // TODO New key - Add a translation + "submission.sections.status.errors.title": "Errors", + + // "submission.sections.status.valid.title": "Valid", + // TODO New key - Add a translation + "submission.sections.status.valid.title": "Valid", + + // "submission.sections.status.warnings.title": "Warnings", + // TODO New key - Add a translation + "submission.sections.status.warnings.title": "Warnings", + + // "submission.sections.status.errors.aria": "has errors", + // TODO New key - Add a translation + "submission.sections.status.errors.aria": "has errors", + + // "submission.sections.status.valid.aria": "is valid", + // TODO New key - Add a translation + "submission.sections.status.valid.aria": "is valid", + + // "submission.sections.status.warnings.aria": "has warnings", + // TODO New key - Add a translation + "submission.sections.status.warnings.aria": "has warnings", + + // "submission.sections.status.info.title": "Additional Information", + // TODO New key - Add a translation + "submission.sections.status.info.title": "Additional Information", + + // "submission.sections.status.info.aria": "Additional Information", + // TODO New key - Add a translation + "submission.sections.status.info.aria": "Additional Information", + + // "submission.sections.toggle.open": "Open section", + // TODO New key - Add a translation + "submission.sections.toggle.open": "Open section", + + // "submission.sections.toggle.close": "Close section", + // TODO New key - Add a translation + "submission.sections.toggle.close": "Close section", + + // "submission.sections.toggle.aria.open": "Expand {{sectionHeader}} section", + // TODO New key - Add a translation + "submission.sections.toggle.aria.open": "Expand {{sectionHeader}} section", + + // "submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section", + // TODO New key - Add a translation + "submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section", + // "submission.sections.upload.delete.confirm.cancel": "Cancel", "submission.sections.upload.delete.confirm.cancel": "Cancelar", - + // "submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?", "submission.sections.upload.delete.confirm.info": "Esta operação é irreversível. Tem certeza?", - + // "submission.sections.upload.delete.confirm.submit": "Yes, I'm sure", "submission.sections.upload.delete.confirm.submit": "Sim, tenho certeza", - + // "submission.sections.upload.delete.confirm.title": "Delete bitstream", "submission.sections.upload.delete.confirm.title": "Remover bitstream", - + // "submission.sections.upload.delete.submit": "Delete", "submission.sections.upload.delete.submit": "Remover", - + + // "submission.sections.upload.download.title": "Download bitstream", + // TODO New key - Add a translation + "submission.sections.upload.download.title": "Download bitstream", + // "submission.sections.upload.drop-message": "Drop files to attach them to the item", "submission.sections.upload.drop-message": "Arraste arquivos para anexá-los ao item", - + + // "submission.sections.upload.edit.title": "Edit bitstream", + // TODO New key - Add a translation + "submission.sections.upload.edit.title": "Edit bitstream", + // "submission.sections.upload.form.access-condition-label": "Access condition type", "submission.sections.upload.form.access-condition-label": "Tipo de condição de acesso", - + + // "submission.sections.upload.form.access-condition-hint": "Select an access condition to apply on the bitstream once the item is deposited", + // TODO New key - Add a translation + "submission.sections.upload.form.access-condition-hint": "Select an access condition to apply on the bitstream once the item is deposited", + // "submission.sections.upload.form.date-required": "Date is required.", "submission.sections.upload.form.date-required": "Data necessária.", - + + // "submission.sections.upload.form.date-required-from": "Grant access from date is required.", + // TODO New key - Add a translation + "submission.sections.upload.form.date-required-from": "Grant access from date is required.", + + // "submission.sections.upload.form.date-required-until": "Grant access until date is required.", + // TODO New key - Add a translation + "submission.sections.upload.form.date-required-until": "Grant access until date is required.", + // "submission.sections.upload.form.from-label": "Grant access from", - // TODO Source message changed - Revise the translation "submission.sections.upload.form.from-label": "Acesso permitido a partir de", - + + // "submission.sections.upload.form.from-hint": "Select the date from which the related access condition is applied", + // TODO New key - Add a translation + "submission.sections.upload.form.from-hint": "Select the date from which the related access condition is applied", + // "submission.sections.upload.form.from-placeholder": "From", "submission.sections.upload.form.from-placeholder": "De", - + // "submission.sections.upload.form.group-label": "Group", "submission.sections.upload.form.group-label": "Grupo", - + // "submission.sections.upload.form.group-required": "Group is required.", "submission.sections.upload.form.group-required": "Grupo é necessário.", - + // "submission.sections.upload.form.until-label": "Grant access until", - // TODO Source message changed - Revise the translation "submission.sections.upload.form.until-label": "Acesso permitido até", - + + // "submission.sections.upload.form.until-hint": "Select the date until which the related access condition is applied", + // TODO New key - Add a translation + "submission.sections.upload.form.until-hint": "Select the date until which the related access condition is applied", + // "submission.sections.upload.form.until-placeholder": "Until", "submission.sections.upload.form.until-placeholder": "Até", - + // "submission.sections.upload.header.policy.default.nolist": "Uploaded files in the {{collectionName}} collection will be accessible according to the following group(s):", "submission.sections.upload.header.policy.default.nolist": "Arquivos enviados na coleção {{collectionName}} serão acessiveis de acordo com o(s) seguinte(s) grupo(s):", - + // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Por favor note que arquivos enviados a coleção {{collectionName}} serão acessíveis, de acordo com o que está explicitamente definido no arquivo, no(s) seguinte(s) grupo(s):", - + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", "submission.sections.upload.info": "Aqui voCẽ encontra todos os arquivos que estão atualmente no item. Você pode atualizar os metadados do arquivo e condições de acesso ou enviar arquivos adicionais apenas arrastando os arquivos em qualquer lugar da página", - + // "submission.sections.upload.no-entry": "No", "submission.sections.upload.no-entry": "Não", - + // "submission.sections.upload.no-file-uploaded": "No file uploaded yet.", "submission.sections.upload.no-file-uploaded": "Nenhum arquivo enviado ainda.", - + // "submission.sections.upload.save-metadata": "Save metadata", "submission.sections.upload.save-metadata": "Salvar metadados", - + // "submission.sections.upload.undo": "Cancel", "submission.sections.upload.undo": "Cancelar", - + // "submission.sections.upload.upload-failed": "Upload failed", "submission.sections.upload.upload-failed": "Falha no envio", - + // "submission.sections.upload.upload-successful": "Upload successful", "submission.sections.upload.upload-successful": "Enviado com sucesso", - - - - // "submission.submit.title": "Submission", - "submission.submit.title": "Submissão", - - - + + // "submission.sections.accesses.form.discoverable-description": "When checked, this item will be discoverable in search/browse. When unchecked, the item will only be available via a direct link and will never appear in search/browse.", + // TODO New key - Add a translation + "submission.sections.accesses.form.discoverable-description": "When checked, this item will be discoverable in search/browse. When unchecked, the item will only be available via a direct link and will never appear in search/browse.", + + // "submission.sections.accesses.form.discoverable-label": "Discoverable", + // TODO New key - Add a translation + "submission.sections.accesses.form.discoverable-label": "Discoverable", + + // "submission.sections.accesses.form.access-condition-label": "Access condition type", + // TODO New key - Add a translation + "submission.sections.accesses.form.access-condition-label": "Access condition type", + + // "submission.sections.accesses.form.access-condition-hint": "Select an access condition to apply on the item once it is deposited", + // TODO New key - Add a translation + "submission.sections.accesses.form.access-condition-hint": "Select an access condition to apply on the item once it is deposited", + + // "submission.sections.accesses.form.date-required": "Date is required.", + // TODO New key - Add a translation + "submission.sections.accesses.form.date-required": "Date is required.", + + // "submission.sections.accesses.form.date-required-from": "Grant access from date is required.", + // TODO New key - Add a translation + "submission.sections.accesses.form.date-required-from": "Grant access from date is required.", + + // "submission.sections.accesses.form.date-required-until": "Grant access until date is required.", + // TODO New key - Add a translation + "submission.sections.accesses.form.date-required-until": "Grant access until date is required.", + + // "submission.sections.accesses.form.from-label": "Grant access from", + // TODO New key - Add a translation + "submission.sections.accesses.form.from-label": "Grant access from", + + // "submission.sections.accesses.form.from-hint": "Select the date from which the related access condition is applied", + // TODO New key - Add a translation + "submission.sections.accesses.form.from-hint": "Select the date from which the related access condition is applied", + + // "submission.sections.accesses.form.from-placeholder": "From", + // TODO New key - Add a translation + "submission.sections.accesses.form.from-placeholder": "From", + + // "submission.sections.accesses.form.group-label": "Group", + // TODO New key - Add a translation + "submission.sections.accesses.form.group-label": "Group", + + // "submission.sections.accesses.form.group-required": "Group is required.", + // TODO New key - Add a translation + "submission.sections.accesses.form.group-required": "Group is required.", + + // "submission.sections.accesses.form.until-label": "Grant access until", + // TODO New key - Add a translation + "submission.sections.accesses.form.until-label": "Grant access until", + + // "submission.sections.accesses.form.until-hint": "Select the date until which the related access condition is applied", + // TODO New key - Add a translation + "submission.sections.accesses.form.until-hint": "Select the date until which the related access condition is applied", + + // "submission.sections.accesses.form.until-placeholder": "Until", + // TODO New key - Add a translation + "submission.sections.accesses.form.until-placeholder": "Until", + + // "submission.sections.license.granted-label": "I confirm the license above", + // TODO New key - Add a translation + "submission.sections.license.granted-label": "I confirm the license above", + + // "submission.sections.license.required": "You must accept the license", + // TODO New key - Add a translation + "submission.sections.license.required": "You must accept the license", + + // "submission.sections.license.notgranted": "You must accept the license", + // TODO New key - Add a translation + "submission.sections.license.notgranted": "You must accept the license", + + + // "submission.sections.sherpa.publication.information": "Publication information", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information": "Publication information", + + // "submission.sections.sherpa.publication.information.title": "Title", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information.title": "Title", + + // "submission.sections.sherpa.publication.information.issns": "ISSNs", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information.issns": "ISSNs", + + // "submission.sections.sherpa.publication.information.url": "URL", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information.url": "URL", + + // "submission.sections.sherpa.publication.information.publishers": "Publisher", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information.publishers": "Publisher", + + // "submission.sections.sherpa.publication.information.romeoPub": "Romeo Pub", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information.romeoPub": "Romeo Pub", + + // "submission.sections.sherpa.publication.information.zetoPub": "Zeto Pub", + // TODO New key - Add a translation + "submission.sections.sherpa.publication.information.zetoPub": "Zeto Pub", + + // "submission.sections.sherpa.publisher.policy": "Publisher Policy", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy": "Publisher Policy", + + // "submission.sections.sherpa.publisher.policy.description": "The below information was found via Sherpa Romeo. Based on the policies of your publisher, it provides advice regarding whether an embargo may be necessary and/or which files you are allowed to upload. If you have questions, please contact your site administrator via the feedback form in the footer.", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.description": "The below information was found via Sherpa Romeo. Based on the policies of your publisher, it provides advice regarding whether an embargo may be necessary and/or which files you are allowed to upload. If you have questions, please contact your site administrator via the feedback form in the footer.", + + // "submission.sections.sherpa.publisher.policy.openaccess": "Open Access pathways permitted by this journal's policy are listed below by article version. Click on a pathway for a more detailed view", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.openaccess": "Open Access pathways permitted by this journal's policy are listed below by article version. Click on a pathway for a more detailed view", + + // "submission.sections.sherpa.publisher.policy.more.information": "For more information, please see the following links:", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.more.information": "For more information, please see the following links:", + + // "submission.sections.sherpa.publisher.policy.version": "Version", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.version": "Version", + + // "submission.sections.sherpa.publisher.policy.embargo": "Embargo", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.embargo": "Embargo", + + // "submission.sections.sherpa.publisher.policy.noembargo": "No Embargo", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.noembargo": "No Embargo", + + // "submission.sections.sherpa.publisher.policy.nolocation": "None", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.nolocation": "None", + + // "submission.sections.sherpa.publisher.policy.license": "License", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.license": "License", + + // "submission.sections.sherpa.publisher.policy.prerequisites": "Prerequisites", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.prerequisites": "Prerequisites", + + // "submission.sections.sherpa.publisher.policy.location": "Location", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.location": "Location", + + // "submission.sections.sherpa.publisher.policy.conditions": "Conditions", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.conditions": "Conditions", + + // "submission.sections.sherpa.publisher.policy.refresh": "Refresh", + // TODO New key - Add a translation + "submission.sections.sherpa.publisher.policy.refresh": "Refresh", + + // "submission.sections.sherpa.record.information": "Record Information", + // TODO New key - Add a translation + "submission.sections.sherpa.record.information": "Record Information", + + // "submission.sections.sherpa.record.information.id": "ID", + // TODO New key - Add a translation + "submission.sections.sherpa.record.information.id": "ID", + + // "submission.sections.sherpa.record.information.date.created": "Date Created", + // TODO New key - Add a translation + "submission.sections.sherpa.record.information.date.created": "Date Created", + + // "submission.sections.sherpa.record.information.date.modified": "Last Modified", + // TODO New key - Add a translation + "submission.sections.sherpa.record.information.date.modified": "Last Modified", + + // "submission.sections.sherpa.record.information.uri": "URI", + // TODO New key - Add a translation + "submission.sections.sherpa.record.information.uri": "URI", + + // "submission.sections.sherpa.error.message": "There was an error retrieving sherpa informations", + // TODO New key - Add a translation + "submission.sections.sherpa.error.message": "There was an error retrieving sherpa informations", + + + + // "submission.submit.breadcrumbs": "New submission", + "submission.submit.breadcrumbs": "Novo envio", + + // "submission.submit.title": "New submission", + "submission.submit.title": "Novo envio", + + + // "submission.workflow.generic.delete": "Delete", "submission.workflow.generic.delete": "Apagar", - + // "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", "submission.workflow.generic.delete-help": "Se você gostaria de descartar este item, selecione \"Apagar\". Você será questionado para confirmar.", - + // "submission.workflow.generic.edit": "Edit", "submission.workflow.generic.edit": "Editar", - + // "submission.workflow.generic.edit-help": "Select this option to change the item's metadata.", "submission.workflow.generic.edit-help": "Selecione esta opção para modificar os metadados do item.", - + // "submission.workflow.generic.view": "View", "submission.workflow.generic.view": "Visualizar", - + // "submission.workflow.generic.view-help": "Select this option to view the item's metadata.", "submission.workflow.generic.view-help": "Selecione esta opção para ver o metadados do item.", - - - + + + // "submission.workflow.tasks.claimed.approve": "Approve", "submission.workflow.tasks.claimed.approve": "Aprovar", - + // "submission.workflow.tasks.claimed.approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".", "submission.workflow.tasks.claimed.approve_help": "Se você revisou o item e este está adequado para inclusão na coleção, selecione \"Aprovar\".", - + // "submission.workflow.tasks.claimed.edit": "Edit", "submission.workflow.tasks.claimed.edit": "Editar", - + // "submission.workflow.tasks.claimed.edit_help": "Select this option to change the item's metadata.", "submission.workflow.tasks.claimed.edit_help": "Selecione esta opção para modificar os metadados do item.", - + // "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", "submission.workflow.tasks.claimed.reject.reason.info": "Por favor informe o motivo pela rejeição da submissão na caixa abaixo, indicando se o submetedor pode corrigir um problema e reenviar.", - + // "submission.workflow.tasks.claimed.reject.reason.placeholder": "Describe the reason of reject", "submission.workflow.tasks.claimed.reject.reason.placeholder": "Descreva o motivo da rejeição", - + // "submission.workflow.tasks.claimed.reject.reason.submit": "Reject item", "submission.workflow.tasks.claimed.reject.reason.submit": "Rejeitar item", - + // "submission.workflow.tasks.claimed.reject.reason.title": "Reason", "submission.workflow.tasks.claimed.reject.reason.title": "Motivo", - + // "submission.workflow.tasks.claimed.reject.submit": "Reject", "submission.workflow.tasks.claimed.reject.submit": "Rejeitar", - + // "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", "submission.workflow.tasks.claimed.reject_help": "Se você revisou o item e achou que ele não é adequado para inclusão na coleção, selecione \"Rejeitar\". Você será questionado a informar uma mensagem indicando porque o item está inadequado e se o submetedor deve modificar algo e reenviar.", - + // "submission.workflow.tasks.claimed.return": "Return to pool", "submission.workflow.tasks.claimed.return": "Retornar para o conjunto", - + // "submission.workflow.tasks.claimed.return_help": "Return the task to the pool so that another user may perform the task.", "submission.workflow.tasks.claimed.return_help": "Retornar a tarefa para o conjunto para que outra pessoa possa a fazer.", - - - + + + // "submission.workflow.tasks.generic.error": "Error occurred during operation...", "submission.workflow.tasks.generic.error": "Ocorreu um erro durante a operação...", - + // "submission.workflow.tasks.generic.processing": "Processing...", "submission.workflow.tasks.generic.processing": "Processando...", - + // "submission.workflow.tasks.generic.submitter": "Submitter", "submission.workflow.tasks.generic.submitter": "Submetedor", - + // "submission.workflow.tasks.generic.success": "Operation successful", "submission.workflow.tasks.generic.success": "Operação realizada com sucesso", - - - + + + // "submission.workflow.tasks.pool.claim": "Claim", "submission.workflow.tasks.pool.claim": "Requerer", - + // "submission.workflow.tasks.pool.claim_help": "Assign this task to yourself.", "submission.workflow.tasks.pool.claim_help": "Atribua esta tarefa a si mesmo.", - + // "submission.workflow.tasks.pool.hide-detail": "Hide detail", "submission.workflow.tasks.pool.hide-detail": "Ocultar detalhes", - + // "submission.workflow.tasks.pool.show-detail": "Show detail", "submission.workflow.tasks.pool.show-detail": "Mostrar detalhes", - - - + + + // "submission.workspace.generic.view": "View", + // TODO New key - Add a translation + "submission.workspace.generic.view": "View", + + // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", + // TODO New key - Add a translation + "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", + + + // "thumbnail.default.alt": "Thumbnail Image", + // TODO New key - Add a translation + "thumbnail.default.alt": "Thumbnail Image", + + // "thumbnail.default.placeholder": "No Thumbnail Available", + // TODO New key - Add a translation + "thumbnail.default.placeholder": "No Thumbnail Available", + + // "thumbnail.project.alt": "Project Logo", + // TODO New key - Add a translation + "thumbnail.project.alt": "Project Logo", + + // "thumbnail.project.placeholder": "Project Placeholder Image", + // TODO New key - Add a translation + "thumbnail.project.placeholder": "Project Placeholder Image", + + // "thumbnail.orgunit.alt": "OrgUnit Logo", + // TODO New key - Add a translation + "thumbnail.orgunit.alt": "OrgUnit Logo", + + // "thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image", + // TODO New key - Add a translation + "thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image", + + // "thumbnail.person.alt": "Profile Picture", + // TODO New key - Add a translation + "thumbnail.person.alt": "Profile Picture", + + // "thumbnail.person.placeholder": "No Profile Picture Available", + // TODO New key - Add a translation + "thumbnail.person.placeholder": "No Profile Picture Available", + + + // "title": "DSpace", "title": "DSpace", - - - + + + // "vocabulary-treeview.header": "Hierarchical tree view", // TODO New key - Add a translation "vocabulary-treeview.header": "Hierarchical tree view", - + // "vocabulary-treeview.load-more": "Load more", // TODO New key - Add a translation "vocabulary-treeview.load-more": "Load more", - + // "vocabulary-treeview.search.form.reset": "Reset", // TODO New key - Add a translation "vocabulary-treeview.search.form.reset": "Reset", - + // "vocabulary-treeview.search.form.search": "Search", // TODO New key - Add a translation "vocabulary-treeview.search.form.search": "Search", - + // "vocabulary-treeview.search.no-result": "There were no items to show", // TODO New key - Add a translation "vocabulary-treeview.search.no-result": "There were no items to show", - + // "vocabulary-treeview.tree.description.nsi": "The Norwegian Science Index", // TODO New key - Add a translation "vocabulary-treeview.tree.description.nsi": "The Norwegian Science Index", - + // "vocabulary-treeview.tree.description.srsc": "Research Subject Categories", // TODO New key - Add a translation "vocabulary-treeview.tree.description.srsc": "Research Subject Categories", - - - + + + // "uploader.browse": "browse", "uploader.browse": "Navegar", - + // "uploader.drag-message": "Drag & Drop your files here", "uploader.drag-message": "Clique e arraste seus arquivos aqui", - + + // "uploader.delete.btn-title": "Delete", + // TODO New key - Add a translation + "uploader.delete.btn-title": "Delete", + // "uploader.or": ", or ", - // TODO Source message changed - Revise the translation - "uploader.or": ", ou", - + "uploader.or": ", ou ", + // "uploader.processing": "Processing", "uploader.processing": "Processando", - + // "uploader.queue-length": "Queue length", "uploader.queue-length": "Tamanho da fila", - + // "virtual-metadata.delete-item.info": "Select the types for which you want to save the virtual metadata as real metadata", // TODO New key - Add a translation "virtual-metadata.delete-item.info": "Select the types for which you want to save the virtual metadata as real metadata", - + // "virtual-metadata.delete-item.modal-head": "The virtual metadata of this relation", // TODO New key - Add a translation "virtual-metadata.delete-item.modal-head": "The virtual metadata of this relation", - + // "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata", // TODO New key - Add a translation "virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata", - - - + + + + // "workspace.search.results.head": "Your submissions", + // TODO New key - Add a translation + "workspace.search.results.head": "Your submissions", + // "workflowAdmin.search.results.head": "Administer Workflow", // TODO New key - Add a translation "workflowAdmin.search.results.head": "Administer Workflow", - - - + + // "workflow.search.results.head": "Workflow tasks", + // TODO New key - Add a translation + "workflow.search.results.head": "Workflow tasks", + + + + // "workflow-item.edit.breadcrumbs": "Edit workflowitem", + // TODO New key - Add a translation + "workflow-item.edit.breadcrumbs": "Edit workflowitem", + + // "workflow-item.edit.title": "Edit workflowitem", + // TODO New key - Add a translation + "workflow-item.edit.title": "Edit workflowitem", + // "workflow-item.delete.notification.success.title": "Deleted", // TODO New key - Add a translation "workflow-item.delete.notification.success.title": "Deleted", - + // "workflow-item.delete.notification.success.content": "This workflow item was successfully deleted", // TODO New key - Add a translation "workflow-item.delete.notification.success.content": "This workflow item was successfully deleted", - + // "workflow-item.delete.notification.error.title": "Something went wrong", // TODO New key - Add a translation "workflow-item.delete.notification.error.title": "Something went wrong", - + // "workflow-item.delete.notification.error.content": "The workflow item could not be deleted", // TODO New key - Add a translation "workflow-item.delete.notification.error.content": "The workflow item could not be deleted", - + // "workflow-item.delete.title": "Delete workflow item", // TODO New key - Add a translation "workflow-item.delete.title": "Delete workflow item", - + // "workflow-item.delete.header": "Delete workflow item", // TODO New key - Add a translation "workflow-item.delete.header": "Delete workflow item", - + // "workflow-item.delete.button.cancel": "Cancel", // TODO New key - Add a translation "workflow-item.delete.button.cancel": "Cancel", - + // "workflow-item.delete.button.confirm": "Delete", // TODO New key - Add a translation "workflow-item.delete.button.confirm": "Delete", - - + + // "workflow-item.send-back.notification.success.title": "Sent back to submitter", // TODO New key - Add a translation "workflow-item.send-back.notification.success.title": "Sent back to submitter", - + // "workflow-item.send-back.notification.success.content": "This workflow item was successfully sent back to the submitter", // TODO New key - Add a translation "workflow-item.send-back.notification.success.content": "This workflow item was successfully sent back to the submitter", - + // "workflow-item.send-back.notification.error.title": "Something went wrong", // TODO New key - Add a translation "workflow-item.send-back.notification.error.title": "Something went wrong", - + // "workflow-item.send-back.notification.error.content": "The workflow item could not be sent back to the submitter", // TODO New key - Add a translation "workflow-item.send-back.notification.error.content": "The workflow item could not be sent back to the submitter", - + // "workflow-item.send-back.title": "Send workflow item back to submitter", // TODO New key - Add a translation "workflow-item.send-back.title": "Send workflow item back to submitter", - + // "workflow-item.send-back.header": "Send workflow item back to submitter", // TODO New key - Add a translation "workflow-item.send-back.header": "Send workflow item back to submitter", - + // "workflow-item.send-back.button.cancel": "Cancel", // TODO New key - Add a translation "workflow-item.send-back.button.cancel": "Cancel", - - // "workflow-item.send-back.button.confirm": "Send back" - // TODO New key - Add a translation - "workflow-item.send-back.button.confirm": "Send back" - -} \ No newline at end of file + // "workflow-item.send-back.button.confirm": "Send back", + // TODO New key - Add a translation + "workflow-item.send-back.button.confirm": "Send back", + + // "workflow-item.view.breadcrumbs": "Workflow View", + // TODO New key - Add a translation + "workflow-item.view.breadcrumbs": "Workflow View", + + // "workspace-item.view.breadcrumbs": "Workspace View", + // TODO New key - Add a translation + "workspace-item.view.breadcrumbs": "Workspace View", + + // "workspace-item.view.title": "Workspace View", + // TODO New key - Add a translation + "workspace-item.view.title": "Workspace View", + + // "idle-modal.header": "Session will expire soon", + // TODO New key - Add a translation + "idle-modal.header": "Session will expire soon", + + // "idle-modal.info": "For security reasons, user sessions expire after {{ timeToExpire }} minutes of inactivity. Your session will expire soon. Would you like to extend it or log out?", + // TODO New key - Add a translation + "idle-modal.info": "For security reasons, user sessions expire after {{ timeToExpire }} minutes of inactivity. Your session will expire soon. Would you like to extend it or log out?", + + // "idle-modal.log-out": "Log out", + // TODO New key - Add a translation + "idle-modal.log-out": "Log out", + + // "idle-modal.extend-session": "Extend session", + // TODO New key - Add a translation + "idle-modal.extend-session": "Extend session", + + // "researcher.profile.action.processing" : "Processing...", + // TODO New key - Add a translation + "researcher.profile.action.processing" : "Processing...", + + // "researcher.profile.associated": "Researcher profile associated", + // TODO New key - Add a translation + "researcher.profile.associated": "Researcher profile associated", + + // "researcher.profile.change-visibility.fail": "An unexpected error occurs while changing the profile visibility", + // TODO New key - Add a translation + "researcher.profile.change-visibility.fail": "An unexpected error occurs while changing the profile visibility", + + // "researcher.profile.create.new": "Create new", + // TODO New key - Add a translation + "researcher.profile.create.new": "Create new", + + // "researcher.profile.create.success": "Researcher profile created successfully", + // TODO New key - Add a translation + "researcher.profile.create.success": "Researcher profile created successfully", + + // "researcher.profile.create.fail": "An error occurs during the researcher profile creation", + // TODO New key - Add a translation + "researcher.profile.create.fail": "An error occurs during the researcher profile creation", + + // "researcher.profile.delete": "Delete", + // TODO New key - Add a translation + "researcher.profile.delete": "Delete", + + // "researcher.profile.expose": "Expose", + // TODO New key - Add a translation + "researcher.profile.expose": "Expose", + + // "researcher.profile.hide": "Hide", + // TODO New key - Add a translation + "researcher.profile.hide": "Hide", + + // "researcher.profile.not.associated": "Researcher profile not yet associated", + // TODO New key - Add a translation + "researcher.profile.not.associated": "Researcher profile not yet associated", + + // "researcher.profile.view": "View", + // TODO New key - Add a translation + "researcher.profile.view": "View", + + // "researcher.profile.private.visibility" : "PRIVATE", + // TODO New key - Add a translation + "researcher.profile.private.visibility" : "PRIVATE", + + // "researcher.profile.public.visibility" : "PUBLIC", + // TODO New key - Add a translation + "researcher.profile.public.visibility" : "PUBLIC", + + // "researcher.profile.status": "Status:", + // TODO New key - Add a translation + "researcher.profile.status": "Status:", + + // "researcherprofile.claim.not-authorized": "You are not authorized to claim this item. For more details contact the administrator(s).", + // TODO New key - Add a translation + "researcherprofile.claim.not-authorized": "You are not authorized to claim this item. For more details contact the administrator(s).", + + // "researcherprofile.error.claim.body" : "An error occurred while claiming the profile, please try again later", + // TODO New key - Add a translation + "researcherprofile.error.claim.body" : "An error occurred while claiming the profile, please try again later", + + // "researcherprofile.error.claim.title" : "Error", + // TODO New key - Add a translation + "researcherprofile.error.claim.title" : "Error", + + // "researcherprofile.success.claim.body" : "Profile claimed with success", + // TODO New key - Add a translation + "researcherprofile.success.claim.body" : "Profile claimed with success", + + // "researcherprofile.success.claim.title" : "Success", + // TODO New key - Add a translation + "researcherprofile.success.claim.title" : "Success", + + // "person.page.orcid.create": "Create an ORCID ID", + // TODO New key - Add a translation + "person.page.orcid.create": "Create an ORCID ID", + + // "person.page.orcid.granted-authorizations": "Granted authorizations", + // TODO New key - Add a translation + "person.page.orcid.granted-authorizations": "Granted authorizations", + + // "person.page.orcid.grant-authorizations" : "Grant authorizations", + // TODO New key - Add a translation + "person.page.orcid.grant-authorizations" : "Grant authorizations", + + // "person.page.orcid.link": "Connect to ORCID ID", + // TODO New key - Add a translation + "person.page.orcid.link": "Connect to ORCID ID", + + // "person.page.orcid.link.processing": "Linking profile to ORCID...", + // TODO New key - Add a translation + "person.page.orcid.link.processing": "Linking profile to ORCID...", + + // "person.page.orcid.link.error.message": "Something went wrong while connecting the profile with ORCID. If the problem persists, contact the administrator.", + // TODO New key - Add a translation + "person.page.orcid.link.error.message": "Something went wrong while connecting the profile with ORCID. If the problem persists, contact the administrator.", + + // "person.page.orcid.orcid-not-linked-message": "The ORCID iD of this profile ({{ orcid }}) has not yet been connected to an account on the ORCID registry or the connection is expired.", + // TODO New key - Add a translation + "person.page.orcid.orcid-not-linked-message": "The ORCID iD of this profile ({{ orcid }}) has not yet been connected to an account on the ORCID registry or the connection is expired.", + + // "person.page.orcid.unlink": "Disconnect from ORCID", + // TODO New key - Add a translation + "person.page.orcid.unlink": "Disconnect from ORCID", + + // "person.page.orcid.unlink.processing": "Processing...", + // TODO New key - Add a translation + "person.page.orcid.unlink.processing": "Processing...", + + // "person.page.orcid.missing-authorizations": "Missing authorizations", + // TODO New key - Add a translation + "person.page.orcid.missing-authorizations": "Missing authorizations", + + // "person.page.orcid.missing-authorizations-message": "The following authorizations are missing:", + // TODO New key - Add a translation + "person.page.orcid.missing-authorizations-message": "The following authorizations are missing:", + + // "person.page.orcid.no-missing-authorizations-message": "Great! This box is empty, so you have granted all access rights to use all functions offers by your institution.", + // TODO New key - Add a translation + "person.page.orcid.no-missing-authorizations-message": "Great! This box is empty, so you have granted all access rights to use all functions offers by your institution.", + + // "person.page.orcid.no-orcid-message": "No ORCID iD associated yet. By clicking on the button below it is possible to link this profile with an ORCID account.", + // TODO New key - Add a translation + "person.page.orcid.no-orcid-message": "No ORCID iD associated yet. By clicking on the button below it is possible to link this profile with an ORCID account.", + + // "person.page.orcid.profile-preferences": "Profile preferences", + // TODO New key - Add a translation + "person.page.orcid.profile-preferences": "Profile preferences", + + // "person.page.orcid.funding-preferences": "Funding preferences", + // TODO New key - Add a translation + "person.page.orcid.funding-preferences": "Funding preferences", + + // "person.page.orcid.publications-preferences": "Publication preferences", + // TODO New key - Add a translation + "person.page.orcid.publications-preferences": "Publication preferences", + + // "person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", + // TODO New key - Add a translation + "person.page.orcid.remove-orcid-message": "If you need to remove your ORCID, please contact the repository administrator", + + // "person.page.orcid.save.preference.changes": "Update settings", + // TODO New key - Add a translation + "person.page.orcid.save.preference.changes": "Update settings", + + // "person.page.orcid.sync-profile.affiliation" : "Affiliation", + // TODO New key - Add a translation + "person.page.orcid.sync-profile.affiliation" : "Affiliation", + + // "person.page.orcid.sync-profile.biographical" : "Biographical data", + // TODO New key - Add a translation + "person.page.orcid.sync-profile.biographical" : "Biographical data", + + // "person.page.orcid.sync-profile.education" : "Education", + // TODO New key - Add a translation + "person.page.orcid.sync-profile.education" : "Education", + + // "person.page.orcid.sync-profile.identifiers" : "Identifiers", + // TODO New key - Add a translation + "person.page.orcid.sync-profile.identifiers" : "Identifiers", + + // "person.page.orcid.sync-fundings.all" : "All fundings", + // TODO New key - Add a translation + "person.page.orcid.sync-fundings.all" : "All fundings", + + // "person.page.orcid.sync-fundings.mine" : "My fundings", + // TODO New key - Add a translation + "person.page.orcid.sync-fundings.mine" : "My fundings", + + // "person.page.orcid.sync-fundings.my_selected" : "Selected fundings", + // TODO New key - Add a translation + "person.page.orcid.sync-fundings.my_selected" : "Selected fundings", + + // "person.page.orcid.sync-fundings.disabled" : "Disabled", + // TODO New key - Add a translation + "person.page.orcid.sync-fundings.disabled" : "Disabled", + + // "person.page.orcid.sync-publications.all" : "All publications", + // TODO New key - Add a translation + "person.page.orcid.sync-publications.all" : "All publications", + + // "person.page.orcid.sync-publications.mine" : "My publications", + // TODO New key - Add a translation + "person.page.orcid.sync-publications.mine" : "My publications", + + // "person.page.orcid.sync-publications.my_selected" : "Selected publications", + // TODO New key - Add a translation + "person.page.orcid.sync-publications.my_selected" : "Selected publications", + + // "person.page.orcid.sync-publications.disabled" : "Disabled", + // TODO New key - Add a translation + "person.page.orcid.sync-publications.disabled" : "Disabled", + + // "person.page.orcid.sync-queue.discard" : "Discard the change and do not synchronize with the ORCID registry", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.discard" : "Discard the change and do not synchronize with the ORCID registry", + + // "person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.discard.error": "The discarding of the ORCID queue record failed", + + // "person.page.orcid.sync-queue.discard.success": "The ORCID queue record have been discarded successfully", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.discard.success": "The ORCID queue record have been discarded successfully", + + // "person.page.orcid.sync-queue.empty-message": "The ORCID queue registry is empty", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.empty-message": "The ORCID queue registry is empty", + + // "person.page.orcid.sync-queue.table.header.type" : "Type", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.table.header.type" : "Type", + + // "person.page.orcid.sync-queue.table.header.description" : "Description", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.table.header.description" : "Description", + + // "person.page.orcid.sync-queue.table.header.action" : "Action", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.table.header.action" : "Action", + + // "person.page.orcid.sync-queue.description.affiliation": "Affiliations", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.affiliation": "Affiliations", + + // "person.page.orcid.sync-queue.description.country": "Country", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.country": "Country", + + // "person.page.orcid.sync-queue.description.education": "Educations", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.education": "Educations", + + // "person.page.orcid.sync-queue.description.external_ids": "External ids", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.external_ids": "External ids", + + // "person.page.orcid.sync-queue.description.other_names": "Other names", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.other_names": "Other names", + + // "person.page.orcid.sync-queue.description.qualification": "Qualifications", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.qualification": "Qualifications", + + // "person.page.orcid.sync-queue.description.researcher_urls": "Researcher urls", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.researcher_urls": "Researcher urls", + + // "person.page.orcid.sync-queue.description.keywords": "Keywords", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.description.keywords": "Keywords", + + // "person.page.orcid.sync-queue.tooltip.insert": "Add a new entry in the ORCID registry", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.insert": "Add a new entry in the ORCID registry", + + // "person.page.orcid.sync-queue.tooltip.update": "Update this entry on the ORCID registry", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.update": "Update this entry on the ORCID registry", + + // "person.page.orcid.sync-queue.tooltip.delete": "Remove this entry from the ORCID registry", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.delete": "Remove this entry from the ORCID registry", + + // "person.page.orcid.sync-queue.tooltip.publication": "Publication", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.publication": "Publication", + + // "person.page.orcid.sync-queue.tooltip.project": "Project", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.project": "Project", + + // "person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.affiliation": "Affiliation", + + // "person.page.orcid.sync-queue.tooltip.education": "Education", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.education": "Education", + + // "person.page.orcid.sync-queue.tooltip.qualification": "Qualification", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.qualification": "Qualification", + + // "person.page.orcid.sync-queue.tooltip.other_names": "Other name", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.other_names": "Other name", + + // "person.page.orcid.sync-queue.tooltip.country": "Country", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.country": "Country", + + // "person.page.orcid.sync-queue.tooltip.keywords": "Keyword", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.keywords": "Keyword", + + // "person.page.orcid.sync-queue.tooltip.external_ids": "External identifier", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.external_ids": "External identifier", + + // "person.page.orcid.sync-queue.tooltip.researcher_urls": "Researcher url", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.tooltip.researcher_urls": "Researcher url", + + // "person.page.orcid.sync-queue.send" : "Synchronize with ORCID registry", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send" : "Synchronize with ORCID registry", + + // "person.page.orcid.sync-queue.send.unauthorized-error.title": "The submission to ORCID failed for missing authorizations.", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.unauthorized-error.title": "The submission to ORCID failed for missing authorizations.", + + // "person.page.orcid.sync-queue.send.unauthorized-error.content": "Click here to grant again the required permissions. If the problem persists, contact the administrator", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.unauthorized-error.content": "Click here to grant again the required permissions. If the problem persists, contact the administrator", + + // "person.page.orcid.sync-queue.send.bad-request-error": "The submission to ORCID failed because the resource sent to ORCID registry is not valid", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.bad-request-error": "The submission to ORCID failed because the resource sent to ORCID registry is not valid", + + // "person.page.orcid.sync-queue.send.error": "The submission to ORCID failed", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.error": "The submission to ORCID failed", + + // "person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.conflict-error": "The submission to ORCID failed because the resource is already present on the ORCID registry", + + // "person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.not-found-warning": "The resource does not exists anymore on the ORCID registry.", + + // "person.page.orcid.sync-queue.send.success": "The submission to ORCID was completed successfully", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.success": "The submission to ORCID was completed successfully", + + // "person.page.orcid.sync-queue.send.validation-error": "The data that you want to synchronize with ORCID is not valid", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error": "The data that you want to synchronize with ORCID is not valid", + + // "person.page.orcid.sync-queue.send.validation-error.amount-currency.required": "The amount's currency is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.amount-currency.required": "The amount's currency is required", + + // "person.page.orcid.sync-queue.send.validation-error.external-id.required": "The resource to be sent requires at least one identifier", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.external-id.required": "The resource to be sent requires at least one identifier", + + // "person.page.orcid.sync-queue.send.validation-error.title.required": "The title is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.title.required": "The title is required", + + // "person.page.orcid.sync-queue.send.validation-error.type.required": "The dc.type is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.type.required": "The dc.type is required", + + // "person.page.orcid.sync-queue.send.validation-error.start-date.required": "The start date is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.start-date.required": "The start date is required", + + // "person.page.orcid.sync-queue.send.validation-error.funder.required": "The funder is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.funder.required": "The funder is required", + + // "person.page.orcid.sync-queue.send.validation-error.country.invalid": "Invalid 2 digits ISO 3166 country", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.country.invalid": "Invalid 2 digits ISO 3166 country", + + // "person.page.orcid.sync-queue.send.validation-error.organization.required": "The organization is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.organization.required": "The organization is required", + + // "person.page.orcid.sync-queue.send.validation-error.organization.name-required": "The organization's name is required", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.organization.name-required": "The organization's name is required", + + // "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid" : "The publication date must be one year after 1900", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.publication.date-invalid" : "The publication date must be one year after 1900", + + // "person.page.orcid.sync-queue.send.validation-error.organization.address-required": "The organization to be sent requires an address", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.organization.address-required": "The organization to be sent requires an address", + + // "person.page.orcid.sync-queue.send.validation-error.organization.city-required": "The address of the organization to be sent requires a city", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.organization.city-required": "The address of the organization to be sent requires a city", + + // "person.page.orcid.sync-queue.send.validation-error.organization.country-required": "The address of the organization to be sent requires a valid 2 digits ISO 3166 country", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.organization.country-required": "The address of the organization to be sent requires a valid 2 digits ISO 3166 country", + + // "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.required": "An identifier to disambiguate organizations is required. Supported ids are GRID, Ringgold, Legal Entity identifiers (LEIs) and Crossref Funder Registry identifiers", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.required": "An identifier to disambiguate organizations is required. Supported ids are GRID, Ringgold, Legal Entity identifiers (LEIs) and Crossref Funder Registry identifiers", + + // "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.value-required": "The organization's identifiers requires a value", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.value-required": "The organization's identifiers requires a value", + + // "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "The organization's identifiers requires a source", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.required": "The organization's identifiers requires a source", + + // "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "The source of one of the organization identifiers is invalid. Supported sources are RINGGOLD, GRID, LEI and FUNDREF", + // TODO New key - Add a translation + "person.page.orcid.sync-queue.send.validation-error.disambiguation-source.invalid": "The source of one of the organization identifiers is invalid. Supported sources are RINGGOLD, GRID, LEI and FUNDREF", + + // "person.page.orcid.synchronization-mode": "Synchronization mode", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode": "Synchronization mode", + + // "person.page.orcid.synchronization-mode.batch": "Batch", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode.batch": "Batch", + + // "person.page.orcid.synchronization-mode.label": "Synchronization mode", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode.label": "Synchronization mode", + + // "person.page.orcid.synchronization-mode-message": "Please select how you would like synchronization to ORCID to occur. The options include \"Manual\" (you must send your data to ORCID manually), or \"Batch\" (the system will send your data to ORCID via a scheduled script).", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode-message": "Please select how you would like synchronization to ORCID to occur. The options include \"Manual\" (you must send your data to ORCID manually), or \"Batch\" (the system will send your data to ORCID via a scheduled script).", + + // "person.page.orcid.synchronization-mode-funding-message": "Select whether to send your linked Project entities to your ORCID record's list of funding information.", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode-funding-message": "Select whether to send your linked Project entities to your ORCID record's list of funding information.", + + // "person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode-publication-message": "Select whether to send your linked Publication entities to your ORCID record's list of works.", + + // "person.page.orcid.synchronization-mode-profile-message": "Select whether to send your biographical data or personal identifiers to your ORCID record.", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode-profile-message": "Select whether to send your biographical data or personal identifiers to your ORCID record.", + + // "person.page.orcid.synchronization-settings-update.success": "The synchronization settings have been updated successfully", + // TODO New key - Add a translation + "person.page.orcid.synchronization-settings-update.success": "The synchronization settings have been updated successfully", + + // "person.page.orcid.synchronization-settings-update.error": "The update of the synchronization settings failed", + // TODO New key - Add a translation + "person.page.orcid.synchronization-settings-update.error": "The update of the synchronization settings failed", + + // "person.page.orcid.synchronization-mode.manual": "Manual", + // TODO New key - Add a translation + "person.page.orcid.synchronization-mode.manual": "Manual", + + // "person.page.orcid.scope.authenticate": "Get your ORCID iD", + // TODO New key - Add a translation + "person.page.orcid.scope.authenticate": "Get your ORCID iD", + + // "person.page.orcid.scope.read-limited": "Read your information with visibility set to Trusted Parties", + // TODO New key - Add a translation + "person.page.orcid.scope.read-limited": "Read your information with visibility set to Trusted Parties", + + // "person.page.orcid.scope.activities-update": "Add/update your research activities", + // TODO New key - Add a translation + "person.page.orcid.scope.activities-update": "Add/update your research activities", + + // "person.page.orcid.scope.person-update": "Add/update other information about you", + // TODO New key - Add a translation + "person.page.orcid.scope.person-update": "Add/update other information about you", + + // "person.page.orcid.unlink.success": "The disconnection between the profile and the ORCID registry was successful", + // TODO New key - Add a translation + "person.page.orcid.unlink.success": "The disconnection between the profile and the ORCID registry was successful", + + // "person.page.orcid.unlink.error": "An error occurred while disconnecting between the profile and the ORCID registry. Try again", + // TODO New key - Add a translation + "person.page.orcid.unlink.error": "An error occurred while disconnecting between the profile and the ORCID registry. Try again", + + // "person.orcid.sync.setting": "ORCID Synchronization settings", + // TODO New key - Add a translation + "person.orcid.sync.setting": "ORCID Synchronization settings", + + // "person.orcid.registry.queue": "ORCID Registry Queue", + // TODO New key - Add a translation + "person.orcid.registry.queue": "ORCID Registry Queue", + + // "person.orcid.registry.auth": "ORCID Authorizations", + // TODO New key - Add a translation + "person.orcid.registry.auth": "ORCID Authorizations", + // "home.recent-submissions.head": "Recent Submissions", + // TODO New key - Add a translation + "home.recent-submissions.head": "Recent Submissions", + + + +} From b89640e4d239b9a3b9d96f1353205222e48e6d2b Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Mon, 26 Sep 2022 23:58:03 +0530 Subject: [PATCH 27/65] [CST-6685] removed value param with --add --- .../batch-import-page.component.spec.ts | 4 ++-- .../admin-import-batch-page/batch-import-page.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts index 6c9d84123b..36ba1137c9 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.spec.ts @@ -101,7 +101,7 @@ describe('BatchImportPageComponent', () => { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), ]; - parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add', value: 'filename.zip' })); + parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add' })); expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); }); it('success notification is shown', () => { @@ -122,7 +122,7 @@ describe('BatchImportPageComponent', () => { it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }), - Object.assign(new ProcessParameter(), { name: '--add', value: 'filename.zip' }), + Object.assign(new ProcessParameter(), { name: '--add' }), Object.assign(new ProcessParameter(), { name: '-v', value: true }), ]; expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]); diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts index 8f49ed64b6..03bbbe8af9 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts @@ -78,7 +78,7 @@ export class BatchImportPageComponent { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }), ]; - parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add', value: this.fileObject.name })); + parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add' })); if (this.dso) { parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid })); } From a011c300aa398ec0ddc568246d1f3e8ae899a876 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 27 Sep 2022 13:23:13 +0530 Subject: [PATCH 28/65] [CST-6932] behavior on the registration page should be the same as the behavior on the change password page --- .../create-profile/create-profile.component.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/app/register-page/create-profile/create-profile.component.ts b/src/app/register-page/create-profile/create-profile.component.ts index d042e63ece..0e8e0e8742 100644 --- a/src/app/register-page/create-profile/create-profile.component.ts +++ b/src/app/register-page/create-profile/create-profile.component.ts @@ -41,6 +41,11 @@ export class CreateProfileComponent implements OnInit { userInfoForm: FormGroup; activeLangs: LangConfig[]; + /** + * Prefix for the notification messages of this security form + */ + NOTIFICATIONS_PREFIX = 'register-page.create-profile.submit.'; + constructor( private translateService: TranslateService, private ePersonDataService: EPersonDataService, @@ -161,13 +166,12 @@ export class CreateProfileComponent implements OnInit { getFirstCompletedRemoteData(), ).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get('register-page.create-profile.submit.success.head'), - this.translateService.get('register-page.create-profile.submit.success.content')); + this.notificationsService.success(this.translateService.get(this.NOTIFICATIONS_PREFIX + 'success.head'), + this.translateService.get(this.NOTIFICATIONS_PREFIX + 'success.content')); this.store.dispatch(new AuthenticateAction(this.email, this.password)); this.router.navigate(['/home']); } else { - this.notificationsService.error(this.translateService.get('register-page.create-profile.submit.error.head'), - this.translateService.get('register-page.create-profile.submit.error.content')); + this.notificationsService.error(this.translateService.get(this.NOTIFICATIONS_PREFIX + 'error.head'), rd.errorMessage); } }); } From 6b0513aba169e7bd125a689381ce5530eaa6cd19 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Mon, 26 Sep 2022 17:40:38 +0200 Subject: [PATCH 29/65] Fix thumbnail placeholder font scaling in ds-recent-item-list --- .../recent-item-list.component.html | 4 +- .../recent-item-list.component.spec.ts | 7 ++- .../recent-item-list.component.ts | 54 +++++++++++++++---- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.html b/src/app/home-page/recent-item-list/recent-item-list.component.html index 8314580f1d..919b8646bb 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.html +++ b/src/app/home-page/recent-item-list/recent-item-list.component.html @@ -1,5 +1,5 @@ -
+

{{'home.recent-submissions.head' | translate}}

@@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.spec.ts b/src/app/home-page/recent-item-list/recent-item-list.component.spec.ts index 87152f53ef..edcb4f84f8 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.spec.ts +++ b/src/app/home-page/recent-item-list/recent-item-list.component.spec.ts @@ -10,8 +10,11 @@ import { SearchConfigurationService } from '../../core/shared/search/search-conf import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { ViewMode } from 'src/app/core/shared/view-mode.model'; import { of as observableOf } from 'rxjs'; +import { APP_CONFIG } from '../../../config/app-config.interface'; +import { environment } from '../../../environments/environment'; +import { PLATFORM_ID } from '@angular/core'; + describe('RecentItemListComponent', () => { let component: RecentItemListComponent; let fixture: ComponentFixture; @@ -42,6 +45,8 @@ describe('RecentItemListComponent', () => { { provide: SearchService, useValue: searchServiceStub }, { provide: PaginationService, useValue: paginationService }, { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, + { provide: APP_CONFIG, useValue: environment }, + { provide: PLATFORM_ID, useValue: 'browser' }, ], }) .compileComponents(); diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.ts b/src/app/home-page/recent-item-list/recent-item-list.component.ts index df5359386a..f0e5803b71 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.ts +++ b/src/app/home-page/recent-item-list/recent-item-list.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, Inject, OnInit, PLATFORM_ID } from '@angular/core'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; import { RemoteData } from '../../core/data/remote-data'; @@ -11,12 +11,13 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { environment } from '../../../environments/environment'; import { ViewMode } from '../../core/shared/view-mode.model'; import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; -import { - toDSpaceObjectListRD -} from '../../core/shared/operators'; -import { - Observable, -} from 'rxjs'; +import { toDSpaceObjectListRD } from '../../core/shared/operators'; +import { Observable } from 'rxjs'; +import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; +import { isPlatformBrowser } from '@angular/common'; +import { setPlaceHolderAttributes } from '../../shared/utils/object-list-utils'; + @Component({ selector: 'ds-recent-item-list', templateUrl: './recent-item-list.component.html', @@ -31,14 +32,22 @@ export class RecentItemListComponent implements OnInit { itemRD$: Observable>>; paginationConfig: PaginationComponentOptions; sortConfig: SortOptions; + /** * The view-mode we're currently on * @type {ViewMode} */ viewMode = ViewMode.ListElement; - constructor(private searchService: SearchService, + + private _placeholderFontClass: string; + + constructor( + private searchService: SearchService, private paginationService: PaginationService, - public searchConfigurationService: SearchConfigurationService + public searchConfigurationService: SearchConfigurationService, + protected elementRef: ElementRef, + @Inject(APP_CONFIG) private appConfig: AppConfig, + @Inject(PLATFORM_ID) private platformId: Object, ) { this.paginationConfig = Object.assign(new PaginationComponentOptions(), { @@ -50,16 +59,29 @@ export class RecentItemListComponent implements OnInit { this.sortConfig = new SortOptions(environment.homePage.recentSubmissions.sortField, SortDirection.DESC); } ngOnInit(): void { + const linksToFollow: FollowLinkConfig[] = []; + if (this.appConfig.browseBy.showThumbnails) { + linksToFollow.push(followLink('thumbnail')); + } + this.itemRD$ = this.searchService.search( new PaginatedSearchOptions({ pagination: this.paginationConfig, sort: this.sortConfig, }), - ).pipe(toDSpaceObjectListRD()) as Observable>>; + undefined, + undefined, + undefined, + ...linksToFollow, + ).pipe( + toDSpaceObjectListRD() + ) as Observable>>; } + ngOnDestroy(): void { this.paginationService.clearPagination(this.paginationConfig.id); } + onLoadMore(): void { this.paginationService.updateRouteWithUrl(this.searchConfigurationService.paginationID, ['search'], { sortField: environment.homePage.recentSubmissions.sortField, @@ -68,5 +90,17 @@ export class RecentItemListComponent implements OnInit { }); } + get placeholderFontClass(): string { + if (this._placeholderFontClass === undefined) { + if (isPlatformBrowser(this.platformId)) { + const width = this.elementRef.nativeElement.offsetWidth; + this._placeholderFontClass = setPlaceHolderAttributes(width); + } else { + this._placeholderFontClass = 'hide-placeholder-text'; + } + } + return this._placeholderFontClass; + } + } From 05d15547408c51717c3b44ee23cda153351137e5 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Mon, 26 Sep 2022 17:41:15 +0200 Subject: [PATCH 30/65] Ensure entity list objects are used on the home page --- src/app/home-page/home-page.module.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app/home-page/home-page.module.ts b/src/app/home-page/home-page.module.ts index 26fde41d8d..3418437d3c 100644 --- a/src/app/home-page/home-page.module.ts +++ b/src/app/home-page/home-page.module.ts @@ -10,6 +10,9 @@ import { StatisticsModule } from '../statistics/statistics.module'; import { ThemedHomeNewsComponent } from './home-news/themed-home-news.component'; import { ThemedHomePageComponent } from './themed-home-page.component'; import { RecentItemListComponent } from './recent-item-list/recent-item-list.component'; +import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; +import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; + const DECLARATIONS = [ HomePageComponent, ThemedHomePageComponent, @@ -22,7 +25,9 @@ const DECLARATIONS = [ @NgModule({ imports: [ CommonModule, - SharedModule, + SharedModule.withEntryComponents(), + JournalEntitiesModule.withEntryComponents(), + ResearchEntitiesModule.withEntryComponents(), HomePageRoutingModule, StatisticsModule.forRoot() ], From 396bbd4f319d0d0892091f95ebcdd6f1c67ceb93 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Mon, 26 Sep 2022 17:41:47 +0200 Subject: [PATCH 31/65] Fix default image logic in ds-thumbnail --- src/app/thumbnail/thumbnail.component.spec.ts | 94 ++++++++++--------- src/app/thumbnail/thumbnail.component.ts | 55 +++++------ 2 files changed, 77 insertions(+), 72 deletions(-) diff --git a/src/app/thumbnail/thumbnail.component.spec.ts b/src/app/thumbnail/thumbnail.component.spec.ts index 5b313d61d4..29aebe03fc 100644 --- a/src/app/thumbnail/thumbnail.component.spec.ts +++ b/src/app/thumbnail/thumbnail.component.spec.ts @@ -127,19 +127,18 @@ describe('ThumbnailComponent', () => { }); const errorHandler = () => { - let fallbackSpy; + let setSrcSpy; beforeEach(() => { - fallbackSpy = spyOn(comp, 'showFallback').and.callThrough(); + // disconnect error handler to be sure it's only called once + const img = fixture.debugElement.query(By.css('img.thumbnail-content')); + img.nativeNode.onerror = null; + + comp.ngOnChanges(); + setSrcSpy = spyOn(comp, 'setSrc').and.callThrough(); }); describe('retry with authentication token', () => { - beforeEach(() => { - // disconnect error handler to be sure it's only called once - const img = fixture.debugElement.query(By.css('img.thumbnail-content')); - img.nativeNode.onerror = null; - }); - it('should remember that it already retried once', () => { expect(comp.retriedWithToken).toBeFalse(); comp.errorHandler(); @@ -153,7 +152,7 @@ describe('ThumbnailComponent', () => { it('should fall back to default', () => { comp.errorHandler(); - expect(fallbackSpy).toHaveBeenCalled(); + expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage); }); }); @@ -172,11 +171,9 @@ describe('ThumbnailComponent', () => { if ((comp.thumbnail as RemoteData)?.hasFailed) { // If we failed to retrieve the Bitstream in the first place, fall back to the default - expect(comp.src$.getValue()).toBe(null); - expect(fallbackSpy).toHaveBeenCalled(); + expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage); } else { - expect(comp.src$.getValue()).toBe(CONTENT + '?authentication-token=fake'); - expect(fallbackSpy).not.toHaveBeenCalled(); + expect(setSrcSpy).toHaveBeenCalledWith(CONTENT + '?authentication-token=fake'); } }); }); @@ -189,8 +186,7 @@ describe('ThumbnailComponent', () => { it('should fall back to default', () => { comp.errorHandler(); - expect(comp.src$.getValue()).toBe(null); - expect(fallbackSpy).toHaveBeenCalled(); + expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage); // We don't need to check authorization if we failed to retrieve the Bitstreamin the first place if (!(comp.thumbnail as RemoteData)?.hasFailed) { @@ -210,7 +206,7 @@ describe('ThumbnailComponent', () => { comp.errorHandler(); expect(authService.isAuthenticated).not.toHaveBeenCalled(); expect(fileService.retrieveFileDownloadLink).not.toHaveBeenCalled(); - expect(fallbackSpy).toHaveBeenCalled(); + expect(setSrcSpy).toHaveBeenCalledWith(comp.defaultImage); }); }); }; @@ -263,21 +259,23 @@ describe('ThumbnailComponent', () => { comp.thumbnail = thumbnail; }); - it('should display an image', () => { - comp.ngOnChanges(); - fixture.detectChanges(); - const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement; - expect(image.getAttribute('src')).toBe(thumbnail._links.content.href); + describe('if content can be loaded', () => { + it('should display an image', () => { + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement; + expect(image.getAttribute('src')).toBe(thumbnail._links.content.href); + }); + + it('should include the alt text', () => { + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement; + expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); + }); }); - it('should include the alt text', () => { - comp.ngOnChanges(); - fixture.detectChanges(); - const image: HTMLElement = fixture.debugElement.query(By.css('img')).nativeElement; - expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); - }); - - describe('when there is no thumbnail', () => { + describe('if content can\'t be loaded', () => { errorHandler(); }); }); @@ -296,36 +294,42 @@ describe('ThumbnailComponent', () => { }; }); - describe('when there is a thumbnail', () => { + describe('if RemoteData succeeded', () => { beforeEach(() => { comp.thumbnail = createSuccessfulRemoteDataObject(thumbnail); }); - it('should display an image', () => { - comp.ngOnChanges(); - fixture.detectChanges(); - const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('src')).toBe(thumbnail._links.content.href); + describe('if content can be loaded', () => { + it('should display an image', () => { + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('src')).toBe(thumbnail._links.content.href); + }); + + it('should display the alt text', () => { + comp.ngOnChanges(); + fixture.detectChanges(); + const image: HTMLElement = de.query(By.css('img')).nativeElement; + expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); + }); }); - it('should display the alt text', () => { - comp.ngOnChanges(); - fixture.detectChanges(); - const image: HTMLElement = de.query(By.css('img')).nativeElement; - expect(image.getAttribute('alt')).toBe('TRANSLATED ' + comp.alt); - }); - - describe('but it can\'t be loaded', () => { + describe('if content can\'t be loaded', () => { errorHandler(); }); }); - describe('when there is no thumbnail', () => { + describe('if RemoteData failed', () => { beforeEach(() => { comp.thumbnail = createFailedRemoteDataObject(); }); - errorHandler(); + it('should show the default image', () => { + comp.defaultImage = 'default/image.jpg'; + comp.ngOnChanges(); + expect(comp.src$.getValue()).toBe('default/image.jpg'); + }); }); }); }); diff --git a/src/app/thumbnail/thumbnail.component.ts b/src/app/thumbnail/thumbnail.component.ts index ac0992c345..5a7557def1 100644 --- a/src/app/thumbnail/thumbnail.component.ts +++ b/src/app/thumbnail/thumbnail.component.ts @@ -12,7 +12,7 @@ import { FileService } from '../core/shared/file.service'; /** * This component renders a given Bitstream as a thumbnail. * One input parameter of type Bitstream is expected. - * If no Bitstream is provided, a HTML placeholder will be rendered instead. + * If no Bitstream is provided, an HTML placeholder will be rendered instead. */ @Component({ selector: 'ds-thumbnail', @@ -75,11 +75,11 @@ export class ThumbnailComponent implements OnChanges { return; } - const thumbnail = this.bitstream; - if (hasValue(thumbnail?._links?.content?.href)) { - this.setSrc(thumbnail?._links?.content?.href); + const src = this.contentHref; + if (hasValue(src)) { + this.setSrc(src); } else { - this.showFallback(); + this.setSrc(this.defaultImage); } } @@ -95,22 +95,33 @@ export class ThumbnailComponent implements OnChanges { } } + private get contentHref(): string | undefined { + if (this.thumbnail instanceof Bitstream) { + return this.thumbnail?._links?.content?.href; + } else if (this.thumbnail instanceof RemoteData) { + return this.thumbnail?.payload?._links?.content?.href; + } + } + /** * Handle image download errors. * If the image can't be loaded, try re-requesting it with an authorization token in case it's a restricted Bitstream * Otherwise, fall back to the default image or a HTML placeholder */ errorHandler() { - if (!this.retriedWithToken && hasValue(this.thumbnail)) { + const src = this.src$.getValue(); + const thumbnail = this.bitstream; + const thumbnailSrc = thumbnail?._links?.content?.href; + + if (!this.retriedWithToken && hasValue(thumbnailSrc) && src === thumbnailSrc) { // the thumbnail may have failed to load because it's restricted // → retry with an authorization token // only do this once; fall back to the default if it still fails this.retriedWithToken = true; - const thumbnail = this.bitstream; this.auth.isAuthenticated().pipe( switchMap((isLoggedIn) => { - if (isLoggedIn && hasValue(thumbnail)) { + if (isLoggedIn) { return this.authorizationService.isAuthorized(FeatureID.CanDownload, thumbnail.self); } else { return observableOf(false); @@ -118,7 +129,7 @@ export class ThumbnailComponent implements OnChanges { }), switchMap((isAuthorized) => { if (isAuthorized) { - return this.fileService.retrieveFileDownloadLink(thumbnail._links.content.href); + return this.fileService.retrieveFileDownloadLink(thumbnailSrc); } else { return observableOf(null); } @@ -130,27 +141,17 @@ export class ThumbnailComponent implements OnChanges { // Otherwise, fall back to the default image right now this.setSrc(url); } else { - this.showFallback(); + this.setSrc(this.defaultImage); } }); } else { - this.showFallback(); - } - } - - /** - * To be called when the requested thumbnail could not be found - * - If the current src is not the default image, try that first - * - If this was already the case and the default image could not be found either, - * show an HTML placecholder by setting src to null - * - * Also stops the loading animation. - */ - showFallback() { - if (this.src$.getValue() !== this.defaultImage) { - this.setSrc(this.defaultImage); - } else { - this.setSrc(null); + if (src !== this.defaultImage) { + // we failed to get thumbnail (possibly retried with a token but failed again) + this.setSrc(this.defaultImage); + } else { + // we have failed to retrieve the default image, fall back to the placeholder + this.setSrc(null); + } } } From fc624a406f398225a6a0d72b12ba96a19a8b43d7 Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Tue, 27 Sep 2022 15:19:00 +0200 Subject: [PATCH 32/65] Workaround missing navbar @768px for dspace and custom templates The treatment of the breakpoints is inconsistent. md is defined as 768px<=md<992px. The statement @media screen and (max-width: map-get($grid-breakpoints, md)) {} ,which is in some instances used at the moment for referencing break intervals BELOW md includes the lower md border which should be excluded. The sass mixin @include media-breakpoint-down($name) {} or function breakpoint-max($name) for some unknown reason don't work, so this is a workaround. --- src/app/navbar/navbar.component.scss | 4 ++-- src/themes/dspace/app/navbar/navbar.component.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/navbar/navbar.component.scss b/src/app/navbar/navbar.component.scss index d131bf95bf..3a9a302b06 100644 --- a/src/app/navbar/navbar.component.scss +++ b/src/app/navbar/navbar.component.scss @@ -4,7 +4,7 @@ nav.navbar { } /** Mobile menu styling **/ -@media screen and (max-width: map-get($grid-breakpoints, md)) { +@media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { .navbar { width: 100vw; background-color: var(--bs-white); @@ -26,7 +26,7 @@ nav.navbar { /* TODO remove when https://github.com/twbs/bootstrap/issues/24726 is fixed */ .navbar-expand-md.navbar-container { - @media screen and (max-width: map-get($grid-breakpoints, md)) { + @media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { > .container { padding: 0 var(--bs-spacer); } diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss index 210847c1d9..1ad95cb8aa 100644 --- a/src/themes/dspace/app/navbar/navbar.component.scss +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -6,7 +6,7 @@ nav.navbar { } /** Mobile menu styling **/ -@media screen and (max-width: map-get($grid-breakpoints, md)) { +@media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { .navbar { width: 100%; background-color: var(--bs-white); @@ -28,7 +28,7 @@ nav.navbar { /* TODO remove when https://github.com/twbs/bootstrap/issues/24726 is fixed */ .navbar-expand-md.navbar-container { - @media screen and (max-width: map-get($grid-breakpoints, md)) { + @media screen and (max-width: map-get($grid-breakpoints, md)-0.02) { > .container { padding: 0 var(--bs-spacer); a.navbar-brand { From 98ee0751ece8146e7ee17ed0f685f92f3171bdc4 Mon Sep 17 00:00:00 2001 From: Nikunj Sharma Date: Tue, 27 Sep 2022 19:35:37 +0530 Subject: [PATCH 33/65] [CST-6685] added new batch export functionality --- .../batch-import-page.component.ts | 2 +- .../data/processes/script-data.service.ts | 1 + src/app/menu.resolver.spec.ts | 3 + src/app/menu.resolver.ts | 17 ++ .../dso-selector-modal-wrapper.component.ts | 3 +- .../export-batch-selector.component.spec.ts | 211 ++++++++++++++++++ .../export-batch-selector.component.ts | 114 ++++++++++ .../import-batch-selector.component.spec.ts | 23 -- .../import-batch-selector.component.ts | 5 +- src/app/shared/shared.module.ts | 5 + src/assets/i18n/en.json5 | 11 + 11 files changed, 367 insertions(+), 28 deletions(-) create mode 100644 src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts diff --git a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts index 03bbbe8af9..7171c67585 100644 --- a/src/app/admin/admin-import-batch-page/batch-import-page.component.ts +++ b/src/app/admin/admin-import-batch-page/batch-import-page.component.ts @@ -77,8 +77,8 @@ export class BatchImportPageComponent { } else { const parameterValues: ProcessParameter[] = [ Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }), + Object.assign(new ProcessParameter(), { name: '--add' }) ]; - parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add' })); if (this.dso) { parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid })); } diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts index 58674d4005..d9c92cb1d2 100644 --- a/src/app/core/data/processes/script-data.service.ts +++ b/src/app/core/data/processes/script-data.service.ts @@ -25,6 +25,7 @@ import { dataService } from '../base/data-service.decorator'; export const METADATA_IMPORT_SCRIPT_NAME = 'metadata-import'; export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export'; export const BATCH_IMPORT_SCRIPT_NAME = 'import'; +export const BATCH_EXPORT_SCRIPT_NAME = 'export'; @Injectable() @dataService(SCRIPT) diff --git a/src/app/menu.resolver.spec.ts b/src/app/menu.resolver.spec.ts index f39075ad27..4fd44efe66 100644 --- a/src/app/menu.resolver.spec.ts +++ b/src/app/menu.resolver.spec.ts @@ -265,6 +265,9 @@ describe('MenuResolver', () => { expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ id: 'export', visible: true, })); + expect(menuService.addSection).toHaveBeenCalledWith(MenuID.ADMIN, jasmine.objectContaining({ + id: 'export_batch', parentID: 'export', visible: true, + })); }); }); diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index a7c1fe415e..8630150c58 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -44,6 +44,9 @@ import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from './core/data/processes/script-data.service'; +import { + ExportBatchSelectorComponent +} from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; /** * Creates all of the app's menus @@ -440,6 +443,20 @@ export class MenuResolver implements Resolve { } as OnClickMenuItemModel, shouldPersistOnRouteChange: true }); + this.menuService.addSection(MenuID.ADMIN, { + id: 'export_batch', + parentID: 'export', + active: false, + visible: true, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.export_batch', + function: () => { + this.modalService.open(ExportBatchSelectorComponent); + } + } as OnClickMenuItemModel, + shouldPersistOnRouteChange: true + }); }); } diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index 937608a2c6..113ca518fd 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -11,7 +11,8 @@ export enum SelectorActionType { EDIT = 'edit', EXPORT_METADATA = 'export-metadata', IMPORT_BATCH = 'import-batch', - SET_SCOPE = 'set-scope' + SET_SCOPE = 'set-scope', + EXPORT_BATCH = 'export-batch' } /** diff --git a/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.spec.ts new file mode 100644 index 0000000000..bfd10346b3 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.spec.ts @@ -0,0 +1,211 @@ +import { of as observableOf } from 'rxjs'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { DebugElement, NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbActiveModal, NgbModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute, Router } from '@angular/router'; +import { BATCH_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service'; +import { Collection } from '../../../../core/shared/collection.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { + createFailedRemoteDataObject$, + createSuccessfulRemoteDataObject, + createSuccessfulRemoteDataObject$ +} from '../../../remote-data.utils'; +import { ExportBatchSelectorComponent } from './export-batch-selector.component'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; + +// No way to add entryComponents yet to testbed; alternative implemented; source: https://stackoverflow.com/questions/41689468/how-to-shallow-test-a-component-with-an-entrycomponents +@NgModule({ + imports: [NgbModalModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + exports: [], + declarations: [ConfirmationModalComponent], + providers: [] +}) +class ModelTestModule { +} + +describe('ExportBatchSelectorComponent', () => { + let component: ExportBatchSelectorComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + let modalRef; + + let router; + let notificationService: NotificationsServiceStub; + let scriptService; + let authorizationDataService; + + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + uuid: 'fake-id', + handle: 'fake/handle', + lastModified: '2018' + }); + + const mockCollection: Collection = Object.assign(new Collection(), { + id: 'test-collection-1-1', + uuid: 'test-collection-1-1', + name: 'test-collection-1', + metadata: { + 'dc.identifier.uri': [ + { + language: null, + value: 'fake/test-collection-1' + } + ] + } + }); + const itemRD = createSuccessfulRemoteDataObject(mockItem); + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + + beforeEach(waitForAsync(() => { + notificationService = new NotificationsServiceStub(); + router = jasmine.createSpyObj('router', { + navigateByUrl: jasmine.createSpy('navigateByUrl') + }); + scriptService = jasmine.createSpyObj('scriptService', + { + invoke: createSuccessfulRemoteDataObject$({ processId: '45' }) + } + ); + authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: observableOf(true) + }); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), ModelTestModule], + declarations: [ExportBatchSelectorComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub }, + { provide: NotificationsService, useValue: notificationService }, + { provide: ScriptDataService, useValue: scriptService }, + { provide: AuthorizationDataService, useValue: authorizationDataService }, + { + provide: ActivatedRoute, + useValue: { + root: { + snapshot: { + data: { + dso: itemRD, + }, + }, + } + }, + }, + { + provide: Router, useValue: router + } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ExportBatchSelectorComponent); + component = fixture.componentInstance; + debugElement = fixture.debugElement; + const modalService = TestBed.inject(NgbModal); + modalRef = modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.response = observableOf(true); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('if item is selected', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + component.navigate(mockItem).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('should not invoke batch-export script', () => { + expect(scriptService.invoke).not.toHaveBeenCalled(); + }); + }); + + describe('if collection is selected and is admin', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + spyOn((component as any).modalService, 'open').and.returnValue(modalRef); + component.navigate(mockCollection).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('should invoke the batch-export script with option --id uuid and -a option', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--id', value: mockCollection.uuid }), + Object.assign(new ProcessParameter(), { name: '--type', value: 'COLLECTION' }), + Object.assign(new ProcessParameter(), { name: '-a' }), + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_EXPORT_SCRIPT_NAME, parameterValues, []); + }); + it('success notification is shown', () => { + expect(scriptRequestSucceeded).toBeTrue(); + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/45'); + }); + }); + describe('if collection is selected and is not admin', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + (authorizationDataService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + spyOn((component as any).modalService, 'open').and.returnValue(modalRef); + component.navigate(mockCollection).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('should invoke the Batch-export script with option --id uuid without the -a option', () => { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--id', value: mockCollection.uuid }), + Object.assign(new ProcessParameter(), { name: '--type', value: 'COLLECTION' }) + ]; + expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_EXPORT_SCRIPT_NAME, parameterValues, []); + }); + it('success notification is shown', () => { + expect(scriptRequestSucceeded).toBeTrue(); + expect(notificationService.success).toHaveBeenCalled(); + }); + it('redirected to process page', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/45'); + }); + }); + + describe('if collection is selected; but script invoke fails', () => { + let scriptRequestSucceeded; + beforeEach((done) => { + spyOn((component as any).modalService, 'open').and.returnValue(modalRef); + jasmine.getEnv().allowRespy(true); + spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500)); + component.navigate(mockCollection).subscribe((succeeded: boolean) => { + scriptRequestSucceeded = succeeded; + done(); + }); + }); + it('error notification is shown', () => { + expect(scriptRequestSucceeded).toBeFalse(); + expect(notificationService.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts new file mode 100644 index 0000000000..cb09b34c58 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component.ts @@ -0,0 +1,114 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of as observableOf } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { BATCH_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service'; +import { Collection } from '../../../../core/shared/collection.model'; +import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { ProcessParameter } from '../../../../process-page/processes/process-parameter.model'; +import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; +import { isNotEmpty } from '../../../empty.util'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; +import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../dso-selector-modal-wrapper.component'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { Process } from '../../../../process-page/processes/process.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { getProcessDetailRoute } from '../../../../process-page/process-page-routing.paths'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; + +/** + * Component to wrap a list of existing dso's inside a modal + * Used to choose a dso from to export metadata of + */ +@Component({ + selector: 'ds-export-metadata-selector', + templateUrl: '../dso-selector-modal-wrapper.component.html', +}) +export class ExportBatchSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { + objectType = DSpaceObjectType.DSPACEOBJECT; + selectorTypes = [DSpaceObjectType.COLLECTION]; + action = SelectorActionType.EXPORT_BATCH; + + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router, + protected notificationsService: NotificationsService, protected translationService: TranslateService, + protected scriptDataService: ScriptDataService, + protected authorizationDataService: AuthorizationDataService, + private modalService: NgbModal) { + super(activeModal, route); + } + + /** + * If the dso is a collection or community: start export-metadata script & navigate to process if successful + * Otherwise show error message + */ + navigate(dso: DSpaceObject): Observable { + if (dso instanceof Collection) { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = dso; + modalRef.componentInstance.headerLabel = 'confirmation-modal.export-batch.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.export-batch.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.export-batch.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.export-batch.confirm'; + modalRef.componentInstance.confirmIcon = 'fas fa-file-export'; + const resp$ = modalRef.componentInstance.response.pipe(switchMap((confirm: boolean) => { + if (confirm) { + const startScriptSucceeded$ = this.startScriptNotifyAndRedirect(dso); + return startScriptSucceeded$.pipe( + switchMap((r: boolean) => { + return observableOf(r); + }) + ); + } else { + const modalRefExport = this.modalService.open(ExportBatchSelectorComponent); + modalRefExport.componentInstance.dsoRD = createSuccessfulRemoteDataObject(dso); + } + })); + resp$.subscribe(); + return resp$; + } else { + return observableOf(false); + } + } + + /** + * Start export-metadata script of dso & navigate to process if successful + * Otherwise show error message + * @param dso Dso to export + */ + private startScriptNotifyAndRedirect(dso: DSpaceObject): Observable { + const parameterValues: ProcessParameter[] = [ + Object.assign(new ProcessParameter(), { name: '--id', value: dso.uuid }), + Object.assign(new ProcessParameter(), { name: '--type', value: 'COLLECTION' }) + ]; + return this.authorizationDataService.isAuthorized(FeatureID.AdministratorOf).pipe( + switchMap((isAdmin) => { + if (isAdmin) { + parameterValues.push(Object.assign(new ProcessParameter(), {name: '-a'})); + } + return this.scriptDataService.invoke(BATCH_EXPORT_SCRIPT_NAME, parameterValues, []); + }), + getFirstCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasSucceeded) { + const title = this.translationService.get('process.new.notification.success.title'); + const content = this.translationService.get('process.new.notification.success.content'); + this.notificationsService.success(title, content); + if (isNotEmpty(rd.payload)) { + this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId)); + } + return true; + } else { + const title = this.translationService.get('process.new.notification.error.title'); + const content = this.translationService.get('process.new.notification.error.content'); + this.notificationsService.error(title, content); + return false; + } + }) + ); + } +} diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts index 576c1ea140..6ed3bf28be 100644 --- a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.spec.ts @@ -4,7 +4,6 @@ import { TranslateModule } from '@ngx-translate/core'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { Collection } from '../../../../core/shared/collection.model'; -import { Community } from '../../../../core/shared/community.model'; import { Item } from '../../../../core/shared/item.model'; import { ImportBatchSelectorComponent } from './import-batch-selector.component'; @@ -30,18 +29,6 @@ describe('ImportBatchSelectorComponent', () => { ] } }); - const mockCommunity = Object.assign(new Community(), { - id: 'test-uuid', - uuid: 'test-uuid', - metadata: { - 'dc.identifier.uri': [ - { - language: null, - value: 'fake/test-community-1' - } - ] - } - }); const modalStub = jasmine.createSpyObj('modalStub', ['close']); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -87,14 +74,4 @@ describe('ImportBatchSelectorComponent', () => { }); }); - describe('if community is selected', () => { - beforeEach((done) => { - component.navigate(mockCommunity).subscribe(() => { - done(); - }); - }); - it('should emit community value', () => { - expect(component.response.emit).toHaveBeenCalledWith(mockCommunity); - }); - }); }); diff --git a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts index a88aeaff3c..4696e42e2d 100644 --- a/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component.ts @@ -1,7 +1,6 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Collection } from '../../../../core/shared/collection.model'; -import { Community } from '../../../../core/shared/community.model'; import { DSpaceObjectType } from '../../../../core/shared/dspace-object-type.model'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @@ -32,10 +31,10 @@ export class ImportBatchSelectorComponent extends DSOSelectorModalWrapperCompone } /** - * If the dso is a collection or community: + * If the dso is a collection: */ navigate(dso: DSpaceObject): Observable { - if (dso instanceof Collection || dso instanceof Community) { + if (dso instanceof Collection) { this.response.emit(dso); return of(null); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index fc65fb6882..9d4ca55e51 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -24,6 +24,9 @@ import { ConfirmationModalComponent } from './confirmation-modal/confirmation-mo import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; +import { + ExportBatchSelectorComponent +} from './dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; import { ImportBatchSelectorComponent } from './dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component'; @@ -475,6 +478,7 @@ const COMPONENTS = [ EntityDropdownComponent, ExportMetadataSelectorComponent, ImportBatchSelectorComponent, + ExportBatchSelectorComponent, ConfirmationModalComponent, VocabularyTreeviewComponent, AuthorizedCollectionSelectorComponent, @@ -554,6 +558,7 @@ const ENTRY_COMPONENTS = [ CurationFormComponent, ExportMetadataSelectorComponent, ImportBatchSelectorComponent, + ExportBatchSelectorComponent, ConfirmationModalComponent, VocabularyTreeviewComponent, SidebarSearchListElementComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 93ab5302fb..10565b4d39 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1363,6 +1363,8 @@ "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", + "dso-selector.export-batch.dspaceobject.head": "Export Batch(ZIP) from", + "dso-selector.import-batch.dspaceobject.head": "Import batch from", "dso-selector.no-results": "No {{ type }} found", @@ -1393,6 +1395,14 @@ "confirmation-modal.export-metadata.confirm": "Export", + "confirmation-modal.export-batch.header": "Export batch(ZIP) for {{ dsoName }}", + + "confirmation-modal.export-batch.info": "Are you sure you want to export batch(ZIP) for {{ dsoName }}", + + "confirmation-modal.export-batch.cancel": "Cancel", + + "confirmation-modal.export-batch.confirm": "Export", + "confirmation-modal.delete-eperson.header": "Delete EPerson \"{{ dsoName }}\"", "confirmation-modal.delete-eperson.info": "Are you sure you want to delete EPerson \"{{ dsoName }}\"", @@ -2636,6 +2646,7 @@ "menu.section.export_metadata": "Metadata", + "menu.section.export_batch": "Batch Export(ZIP)", "menu.section.icon.access_control": "Access Control menu section", From e9a8526b1b2712c24eb42294de2c389865ed0271 Mon Sep 17 00:00:00 2001 From: Nicolas Boulay Date: Wed, 14 Sep 2022 13:52:27 -0400 Subject: [PATCH 34/65] Redirect the user and remove the notification --- .../my-dspace-new-submission.component.ts | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts index 127d266138..f8fe264a27 100644 --- a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts +++ b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts @@ -8,15 +8,14 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AuthService } from '../../core/auth/auth.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; import { UploaderOptions } from '../../shared/uploader/uploader-options.model'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; -import { NotificationType } from '../../shared/notifications/models/notification-type'; import { hasValue } from '../../shared/empty.util'; import { SearchResult } from '../../shared/search/models/search-result.model'; import { CollectionSelectorComponent } from '../collection-selector/collection-selector.component'; import { UploaderComponent } from '../../shared/uploader/uploader.component'; import { UploaderError } from '../../shared/uploader/uploader-error.model'; +import { Router } from '@angular/router'; /** * This component represents the whole mydspace page header @@ -62,7 +61,8 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit { private halService: HALEndpointService, private notificationsService: NotificationsService, private translate: TranslateService, - private modalService: NgbModal) { + private modalService: NgbModal, + private router: Router) { } /** @@ -87,16 +87,9 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit { this.uploadEnd.emit(workspaceitems); if (workspaceitems.length === 1) { - const options = new NotificationOptions(); - options.timeOut = 0; const link = '/workspaceitems/' + workspaceitems[0].id + '/edit'; - this.notificationsService.notificationWithAnchor( - NotificationType.Success, - options, - link, - 'mydspace.general.text-here', - 'mydspace.upload.upload-successful', - 'here'); + // To avoid confusion and ambiguity, redirect the user on the publication page. + this.router.navigateByUrl(link); } else if (workspaceitems.length > 1) { this.notificationsService.success(null, this.translate.get('mydspace.upload.upload-multiple-successful', {qty: workspaceitems.length})); } From 6cd01ec4716155f57319f2dd82dceda5cdb151c8 Mon Sep 17 00:00:00 2001 From: Nicolas Boulay Date: Wed, 14 Sep 2022 14:17:50 -0400 Subject: [PATCH 35/65] Add missing documentation --- .../my-dspace-new-submission.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts index f8fe264a27..b2ba6fe2af 100644 --- a/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts +++ b/src/app/my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts @@ -55,6 +55,7 @@ export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit { * @param {NotificationsService} notificationsService * @param {TranslateService} translate * @param {NgbModal} modalService + * @param {Router} router */ constructor(private authService: AuthService, private changeDetectorRef: ChangeDetectorRef, From 9140fb6106ec26859db29a32aa85b69432c878c7 Mon Sep 17 00:00:00 2001 From: Nicolas Boulay Date: Wed, 14 Sep 2022 14:19:28 -0400 Subject: [PATCH 36/65] Remove unused translations --- src/assets/i18n/en.json5 | 4 ---- src/assets/i18n/fr.json5 | 6 ------ 2 files changed, 10 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 1bdfbedfdc..a8243ead0e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2734,8 +2734,6 @@ "mydspace.description": "", - "mydspace.general.text-here": "here", - "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", "mydspace.messages.description-placeholder": "Insert your message here...", @@ -2812,8 +2810,6 @@ "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", - "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", - "mydspace.view-btn": "View", diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 5cfb7ff099..8cce148c9f 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -3627,9 +3627,6 @@ // "mydspace.description": "", "mydspace.description": "", - // "mydspace.general.text-here": "here", - "mydspace.general.text-here": "ici", - // "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", "mydspace.messages.controller-help": "Sélectionner cette option pour envoyer un message au déposant.", @@ -3744,9 +3741,6 @@ // "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", "mydspace.upload.upload-multiple-successful": "{{qty}} nouveaux Items créés dans l'espace de travail.", - // "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", - "mydspace.upload.upload-successful": "Nouvel item créé dans l'espace de travail. Cliquer {{here}} pour l'éditer.", - // "mydspace.view-btn": "View", "mydspace.view-btn": "Afficher", From f1888f718cca61c23ad9c5d4b1ca6f9460fcc6ca Mon Sep 17 00:00:00 2001 From: Pierre Lasou Date: Tue, 27 Sep 2022 14:35:09 -0400 Subject: [PATCH 37/65] Changes to 2 french translations 2 changes that override those of a previous commit fa20e9e5c0d5ef5da5b5ce5f439817539939f3b5 --- src/assets/i18n/fr.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index ff778eb9f8..19f2cd0b2b 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -1216,13 +1216,13 @@ "collection.edit.tabs.authorizations.title": "Édition de collection - Autorisations", //"collection.edit.item.authorizations.load-bundle-button": "Load more bundles", - "collection.edit.item.authorizations.load-bundle-button": "Charger plus de bundles", + "collection.edit.item.authorizations.load-bundle-button": "Charger plus de Bundles", //"collection.edit.item.authorizations.load-more-button": "Load more", "collection.edit.item.authorizations.load-more-button": "Charger plus", //"collection.edit.item.authorizations.show-bitstreams-button": "Show bitstream policies for bundle", - "collection.edit.item.authorizations.show-bitstreams-button": "Afficher les politiques de bitstream policies pour le bundle", + "collection.edit.item.authorizations.show-bitstreams-button": "Afficher les politiques de Bitstream pour le Bundle", From f3f89b3dc19fd375808bc56c7e3759a298667bad Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 27 Sep 2022 21:47:15 +0200 Subject: [PATCH 38/65] [CST-6753] track Google Analytics statistic only if user accepts cookie consents --- .../cookies/browser-klaro.service.spec.ts | 62 ++++++++++++--- .../shared/cookies/browser-klaro.service.ts | 19 ++++- src/app/shared/cookies/klaro-configuration.ts | 4 +- src/app/shared/cookies/klaro.service.ts | 9 ++- .../google-analytics.service.spec.ts | 78 +++++++++++++++++-- .../statistics/google-analytics.service.ts | 58 +++++++++----- 6 files changed, 190 insertions(+), 40 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.spec.ts b/src/app/shared/cookies/browser-klaro.service.spec.ts index 5806148d94..c7b08a45c9 100644 --- a/src/app/shared/cookies/browser-klaro.service.spec.ts +++ b/src/app/shared/cookies/browser-klaro.service.spec.ts @@ -10,10 +10,12 @@ import { AuthService } from '../../core/auth/auth.service'; import { CookieService } from '../../core/services/cookie.service'; import { getTestScheduler } from 'jasmine-marbles'; import { MetadataValue } from '../../core/shared/metadata.models'; -import {clone, cloneDeep} from 'lodash'; +import { clone, cloneDeep } from 'lodash'; import { ConfigurationDataService } from '../../core/data/configuration-data.service'; -import {createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$} from '../remote-data.utils'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; import { ConfigurationProperty } from '../../core/shared/configuration-property.model'; +import { ANONYMOUS_STORAGE_NAME_KLARO } from './klaro-configuration'; +import { TestScheduler } from 'rxjs/testing'; describe('BrowserKlaroService', () => { const trackingIdProp = 'google.analytics.key'; @@ -29,7 +31,7 @@ describe('BrowserKlaroService', () => { let configurationDataService: ConfigurationDataService; const createConfigSuccessSpy = (...values: string[]) => jasmine.createSpyObj('configurationDataService', { findByPropertyName: createSuccessfulRemoteDataObject$({ - ... new ConfigurationProperty(), + ...new ConfigurationProperty(), name: trackingIdProp, values: values, }), @@ -42,7 +44,9 @@ describe('BrowserKlaroService', () => { let findByPropertyName; beforeEach(() => { - user = new EPerson(); + user = Object.assign(new EPerson(), { + uuid: 'test-user' + }); translateService = getMockTranslateService(); ePersonService = jasmine.createSpyObj('ePersonService', { @@ -104,7 +108,7 @@ describe('BrowserKlaroService', () => { services: [{ name: appName, purposes: [purpose] - },{ + }, { name: googleAnalytics, purposes: [purpose] }], @@ -219,6 +223,40 @@ describe('BrowserKlaroService', () => { }); }); + describe('getSavedPreferences', () => { + let scheduler: TestScheduler; + beforeEach(() => { + scheduler = getTestScheduler(); + }); + + describe('when no user is autheticated', () => { + beforeEach(() => { + spyOn(service as any, 'getUser$').and.returnValue(observableOf(undefined)); + }); + + it('should return the cookie consents object', () => { + scheduler.schedule(() => service.getSavedPreferences().subscribe()); + scheduler.flush(); + + expect(cookieService.get).toHaveBeenCalledWith(ANONYMOUS_STORAGE_NAME_KLARO); + }); + }); + + describe('when user is autheticated', () => { + beforeEach(() => { + spyOn(service as any, 'getUser$').and.returnValue(observableOf(user)); + }); + + it('should return the cookie consents object', () => { + scheduler.schedule(() => service.getSavedPreferences().subscribe()); + scheduler.flush(); + + expect(cookieService.get).toHaveBeenCalledWith('klaro-' + user.uuid); + }); + }); + }); + + describe('setSettingsForUser when there are changes', () => { const cookieConsent = { test: 'testt' }; const cookieConsentString = '{test: \'testt\'}'; @@ -271,40 +309,40 @@ describe('BrowserKlaroService', () => { }); it('should not filter googleAnalytics when servicesToHide are empty', () => { const filteredConfig = (service as any).filterConfigServices([]); - expect(filteredConfig).toContain(jasmine.objectContaining({name: googleAnalytics})); + expect(filteredConfig).toContain(jasmine.objectContaining({ name: googleAnalytics })); }); it('should filter services using names passed as servicesToHide', () => { const filteredConfig = (service as any).filterConfigServices([googleAnalytics]); - expect(filteredConfig).not.toContain(jasmine.objectContaining({name: googleAnalytics})); + expect(filteredConfig).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); }); it('should have been initialized with googleAnalytics', () => { service.initialize(); - expect(service.klaroConfig.services).toContain(jasmine.objectContaining({name: googleAnalytics})); + expect(service.klaroConfig.services).toContain(jasmine.objectContaining({ name: googleAnalytics })); }); it('should filter googleAnalytics when empty configuration is retrieved', () => { configurationDataService.findByPropertyName = jasmine.createSpy().withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue( createSuccessfulRemoteDataObject$({ - ... new ConfigurationProperty(), + ...new ConfigurationProperty(), name: googleAnalytics, values: [], })); service.initialize(); - expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({name: googleAnalytics})); + expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); }); it('should filter googleAnalytics when an error occurs', () => { configurationDataService.findByPropertyName = jasmine.createSpy().withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue( createFailedRemoteDataObject$('Erro while loading GA') ); service.initialize(); - expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({name: googleAnalytics})); + expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); }); it('should filter googleAnalytics when an invalid payload is retrieved', () => { configurationDataService.findByPropertyName = jasmine.createSpy().withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue( createSuccessfulRemoteDataObject$(null) ); service.initialize(); - expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({name: googleAnalytics})); + expect(service.klaroConfig.services).not.toContain(jasmine.objectContaining({ name: googleAnalytics })); }); }); }); diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 6929fb0695..0648afd17a 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -13,7 +13,7 @@ import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { cloneDeep, debounce } from 'lodash'; import { ANONYMOUS_STORAGE_NAME_KLARO, klaroConfiguration } from './klaro-configuration'; import { Operation } from 'fast-json-patch'; -import { getFirstCompletedRemoteData} from '../../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { ConfigurationDataService } from '../../core/data/configuration-data.service'; /** @@ -121,6 +121,23 @@ export class BrowserKlaroService extends KlaroService { }); } + /** + * Return saved preferences stored in the klaro cookie + */ + getSavedPreferences(): Observable { + return this.getUser$().pipe( + map((user: EPerson) => { + let storageName; + if (isEmpty(user)) { + storageName = ANONYMOUS_STORAGE_NAME_KLARO; + } else { + storageName = this.getStorageName(user.uuid); + } + return this.cookieService.get(storageName); + }) + ); + } + /** * Initialize configuration for the logged in user * @param user The authenticated user diff --git a/src/app/shared/cookies/klaro-configuration.ts b/src/app/shared/cookies/klaro-configuration.ts index 659583ad87..fb7c660322 100644 --- a/src/app/shared/cookies/klaro-configuration.ts +++ b/src/app/shared/cookies/klaro-configuration.ts @@ -12,6 +12,8 @@ export const HAS_AGREED_END_USER = 'dsHasAgreedEndUser'; */ export const ANONYMOUS_STORAGE_NAME_KLARO = 'klaro-anonymous'; +export const GOOGLE_ANALYTICS_KLARO_KEY = 'google-analytics'; + /** * Klaro configuration * For more information see https://kiprotect.com/docs/klaro/annotated-config @@ -113,7 +115,7 @@ export const klaroConfiguration: any = { ] }, { - name: 'google-analytics', + name: GOOGLE_ANALYTICS_KLARO_KEY, purposes: ['statistical'], required: false, cookies: [ diff --git a/src/app/shared/cookies/klaro.service.ts b/src/app/shared/cookies/klaro.service.ts index 64dee85b65..d54fed8b30 100644 --- a/src/app/shared/cookies/klaro.service.ts +++ b/src/app/shared/cookies/klaro.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + /** * Abstract class representing a service for handling Klaro consent preferences and UI */ @@ -11,7 +13,12 @@ export abstract class KlaroService { abstract initialize(); /** - * Shows a the dialog with the current consent preferences + * Shows a dialog with the current consent preferences */ abstract showSettings(); + + /** + * Return saved preferences stored in the klaro cookie + */ + abstract getSavedPreferences(): Observable; } diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts index 24c5345260..5953f3d8cc 100644 --- a/src/app/statistics/google-analytics.service.spec.ts +++ b/src/app/statistics/google-analytics.service.spec.ts @@ -1,8 +1,12 @@ -import { GoogleAnalyticsService } from './google-analytics.service'; import { Angulartics2GoogleTagManager } from 'angulartics2'; +import { of } from 'rxjs'; + +import { GoogleAnalyticsService } from './google-analytics.service'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; +import { KlaroService } from '../shared/cookies/klaro.service'; +import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuration'; describe('GoogleAnalyticsService', () => { const trackingIdProp = 'google.analytics.key'; @@ -12,6 +16,7 @@ describe('GoogleAnalyticsService', () => { let service: GoogleAnalyticsService; let angularticsSpy: Angulartics2GoogleTagManager; let configSpy: ConfigurationDataService; + let klaroServiceSpy: jasmine.SpyObj; let scriptElementMock: any; let srcSpy: any; let innerHTMLSpy: any; @@ -31,6 +36,10 @@ describe('GoogleAnalyticsService', () => { 'startTracking', ]); + klaroServiceSpy = jasmine.createSpyObj('KlaroService', { + 'getSavedPreferences': jasmine.createSpy('getSavedPreferences') + }); + configSpy = createConfigSuccessSpy(trackingIdTestValue); scriptElementMock = { @@ -53,7 +62,11 @@ describe('GoogleAnalyticsService', () => { body: bodyElementSpy, }); - service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({ + GOOGLE_ANALYTICS_KLARO_KEY: true + })); + + service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy ); }); it('should be created', () => { @@ -73,7 +86,11 @@ describe('GoogleAnalyticsService', () => { findByPropertyName: createFailedRemoteDataObject$(), }); - service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({ + GOOGLE_ANALYTICS_KLARO_KEY: true + })); + + service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should NOT add a script to the body', () => { @@ -91,7 +108,10 @@ describe('GoogleAnalyticsService', () => { describe('when the tracking id is empty', () => { beforeEach(() => { configSpy = createConfigSuccessSpy(); - service = new GoogleAnalyticsService(angularticsSpy, configSpy, documentSpy); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({ + [GOOGLE_ANALYTICS_KLARO_KEY]: true + })); + service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should NOT add a script to the body', () => { @@ -105,7 +125,55 @@ describe('GoogleAnalyticsService', () => { }); }); - describe('when the tracking id is non-empty', () => { + describe('when google-analytics cookie preferences are not existing', () => { + beforeEach(() => { + configSpy = createConfigSuccessSpy(trackingIdTestValue); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({})); + service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + }); + + it('should NOT add a script to the body', () => { + service.addTrackingIdToPage(); + expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0); + }); + + it('should NOT start tracking', () => { + service.addTrackingIdToPage(); + expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0); + }); + }); + + + describe('when google-analytics cookie preferences are set to false', () => { + beforeEach(() => { + configSpy = createConfigSuccessSpy(trackingIdTestValue); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({ + [GOOGLE_ANALYTICS_KLARO_KEY]: false + })); + service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + }); + + it('should NOT add a script to the body', () => { + service.addTrackingIdToPage(); + expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(0); + }); + + it('should NOT start tracking', () => { + service.addTrackingIdToPage(); + expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0); + }); + }); + + describe('when both google-analytics cookie and the tracking id are non-empty', () => { + + beforeEach(() => { + configSpy = createConfigSuccessSpy(trackingIdTestValue); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({ + [GOOGLE_ANALYTICS_KLARO_KEY]: true + })); + service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + }); + it('should create a script tag whose innerHTML contains the tracking id', () => { service.addTrackingIdToPage(); expect(documentSpy.createElement).toHaveBeenCalledTimes(2); diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts index 6bdff53d52..d8a9c65959 100644 --- a/src/app/statistics/google-analytics.service.ts +++ b/src/app/statistics/google-analytics.service.ts @@ -1,9 +1,14 @@ +import { DOCUMENT } from '@angular/common'; import { Inject, Injectable } from '@angular/core'; + import { Angulartics2GoogleTagManager } from 'angulartics2'; +import { combineLatest } from 'rxjs'; + import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { isEmpty } from '../shared/empty.util'; -import { DOCUMENT } from '@angular/common'; +import { KlaroService } from '../shared/cookies/klaro.service'; +import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuration'; /** * Set up Google Analytics on the client side. @@ -15,9 +20,11 @@ export class GoogleAnalyticsService { constructor( // private angulartics: Angulartics2GoogleAnalytics, private angulartics: Angulartics2GoogleTagManager, + private klaroService: KlaroService, private configService: ConfigurationDataService, @Inject(DOCUMENT) private document: any, - ) { } + ) { + } /** * Call this method once when Angular initializes on the client side. @@ -26,29 +33,40 @@ export class GoogleAnalyticsService { * page and starts tracking. */ addTrackingIdToPage(): void { - this.configService.findByPropertyName('google.analytics.key').pipe( + const googleKey$ = this.configService.findByPropertyName('google.analytics.key').pipe( getFirstCompletedRemoteData(), - ).subscribe((remoteData) => { - // make sure we got a success response from the backend - if (!remoteData.hasSucceeded) { return; } + ); + combineLatest([this.klaroService.getSavedPreferences(), googleKey$]) + .subscribe(([preferences, remoteData]) => { + // make sure user has accepted Google Analytics consents + if (isEmpty(preferences) || isEmpty(preferences[GOOGLE_ANALYTICS_KLARO_KEY]) || !preferences[GOOGLE_ANALYTICS_KLARO_KEY]) { + return; + } - const trackingId = remoteData.payload.values[0]; + // make sure we got a success response from the backend + if (!remoteData.hasSucceeded) { + return; + } - // make sure we received a tracking id - if (isEmpty(trackingId)) { return; } + const trackingId = remoteData.payload.values[0]; - // add GTag snippet to page - const keyScript = this.document.createElement('script'); - keyScript.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`; - this.document.body.appendChild(keyScript); + // make sure we received a tracking id + if (isEmpty(trackingId)) { + return; + } - const libScript = this.document.createElement('script'); - libScript.innerHTML = `window.dataLayer = window.dataLayer || [];function gtag(){window.dataLayer.push(arguments);} - gtag('js', new Date());gtag('config', '${trackingId}');`; - this.document.body.appendChild(libScript); + // add GTag snippet to page + const keyScript = this.document.createElement('script'); + keyScript.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`; + this.document.body.appendChild(keyScript); - // start tracking - this.angulartics.startTracking(); - }); + const libScript = this.document.createElement('script'); + libScript.innerHTML = `window.dataLayer = window.dataLayer || [];function gtag(){window.dataLayer.push(arguments);} + gtag('js', new Date());gtag('config', '${trackingId}');`; + this.document.body.appendChild(libScript); + + // start tracking + this.angulartics.startTracking(); + }); } } From aa53e2fff684e102d74a4f5c580d1a7e97be70ee Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Sep 2022 09:54:25 +0200 Subject: [PATCH 39/65] [CST-6685] fix label --- src/assets/i18n/en.json5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 10565b4d39..8451c3b45e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1363,7 +1363,7 @@ "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", - "dso-selector.export-batch.dspaceobject.head": "Export Batch(ZIP) from", + "dso-selector.export-batch.dspaceobject.head": "Export Batch (ZIP) from", "dso-selector.import-batch.dspaceobject.head": "Import batch from", @@ -1395,9 +1395,9 @@ "confirmation-modal.export-metadata.confirm": "Export", - "confirmation-modal.export-batch.header": "Export batch(ZIP) for {{ dsoName }}", + "confirmation-modal.export-batch.header": "Export batch (ZIP) for {{ dsoName }}", - "confirmation-modal.export-batch.info": "Are you sure you want to export batch(ZIP) for {{ dsoName }}", + "confirmation-modal.export-batch.info": "Are you sure you want to export batch (ZIP) for {{ dsoName }}", "confirmation-modal.export-batch.cancel": "Cancel", @@ -2646,7 +2646,7 @@ "menu.section.export_metadata": "Metadata", - "menu.section.export_batch": "Batch Export(ZIP)", + "menu.section.export_batch": "Batch Export (ZIP)", "menu.section.icon.access_control": "Access Control menu section", From 241816e8369b2b0dde3dbee603ff1947ec2ff0d5 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 22 Sep 2022 12:39:58 +0200 Subject: [PATCH 40/65] use location.replace to ensure the browser can track the redirect in its history --- .../browser-hard-redirect.service.spec.ts | 30 ++++++++++++------- .../services/browser-hard-redirect.service.ts | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/app/core/services/browser-hard-redirect.service.spec.ts b/src/app/core/services/browser-hard-redirect.service.spec.ts index b9745906c3..1d8666d259 100644 --- a/src/app/core/services/browser-hard-redirect.service.spec.ts +++ b/src/app/core/services/browser-hard-redirect.service.spec.ts @@ -2,17 +2,25 @@ import { TestBed } from '@angular/core/testing'; import { BrowserHardRedirectService } from './browser-hard-redirect.service'; describe('BrowserHardRedirectService', () => { - const origin = 'https://test-host.com:4000'; - const mockLocation = { - href: undefined, - pathname: '/pathname', - search: '/search', - origin - } as Location; - - const service: BrowserHardRedirectService = new BrowserHardRedirectService(mockLocation); + let origin: string; + let mockLocation: Location; + let service: BrowserHardRedirectService; beforeEach(() => { + origin = 'https://test-host.com:4000'; + mockLocation = { + href: undefined, + pathname: '/pathname', + search: '/search', + origin, + replace: (url: string) => { + mockLocation.href = url; + } + } as Location; + spyOn(mockLocation, 'replace'); + + service = new BrowserHardRedirectService(mockLocation); + TestBed.configureTestingModule({}); }); @@ -28,8 +36,8 @@ describe('BrowserHardRedirectService', () => { service.redirect(redirect); }); - it('should update the location', () => { - expect(mockLocation.href).toEqual(redirect); + it('should call location.replace with the new url', () => { + expect(mockLocation.replace).toHaveBeenCalledWith(redirect); }); }); diff --git a/src/app/core/services/browser-hard-redirect.service.ts b/src/app/core/services/browser-hard-redirect.service.ts index eeb9006039..4ef9548899 100644 --- a/src/app/core/services/browser-hard-redirect.service.ts +++ b/src/app/core/services/browser-hard-redirect.service.ts @@ -24,7 +24,7 @@ export class BrowserHardRedirectService extends HardRedirectService { * @param url */ redirect(url: string) { - this.location.href = url; + this.location.replace(url); } /** From 941e71a75b991b8612d0a0f7244f9a4f71911357 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 28 Sep 2022 13:10:46 +0200 Subject: [PATCH 41/65] add useProxies config to support x-forwarded headers in express --- server.ts | 4 ++++ src/config/config.util.spec.ts | 4 ++++ src/config/default-app-config.ts | 5 ++++- src/config/ui-server-config.interface.ts | 2 ++ src/environments/environment.test.ts | 3 ++- 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/server.ts b/server.ts index 9fe03fe5b5..8f37c551f0 100644 --- a/server.ts +++ b/server.ts @@ -75,6 +75,10 @@ export function app() { */ const server = express(); + // Tell Express to trust X-FORWARDED-* headers from proxies + // See https://expressjs.com/en/guide/behind-proxies.html + server.set('trust proxy', environment.ui.useProxies); + /* * If production mode is enabled in the environment file: * - Enable Angular's production mode diff --git a/src/config/config.util.spec.ts b/src/config/config.util.spec.ts index 7896b1f5c2..2d1d8e1be7 100644 --- a/src/config/config.util.spec.ts +++ b/src/config/config.util.spec.ts @@ -10,6 +10,7 @@ describe('Config Util', () => { expect(appConfig.cache.msToLive.default).toEqual(15 * 60 * 1000); // 15 minute expect(appConfig.ui.rateLimiter.windowMs).toEqual(1 * 60 * 1000); // 1 minute expect(appConfig.ui.rateLimiter.max).toEqual(500); + expect(appConfig.ui.useProxies).toEqual(true); expect(appConfig.submission.autosave.metadata).toEqual([]); @@ -25,6 +26,8 @@ describe('Config Util', () => { }; appConfig.ui.rateLimiter = rateLimiter; + appConfig.ui.useProxies = false; + const autoSaveMetadata = [ 'dc.author', 'dc.title' @@ -44,6 +47,7 @@ describe('Config Util', () => { expect(environment.cache.msToLive.default).toEqual(msToLive); expect(environment.ui.rateLimiter.windowMs).toEqual(rateLimiter.windowMs); expect(environment.ui.rateLimiter.max).toEqual(rateLimiter.max); + expect(environment.ui.useProxies).toEqual(false); expect(environment.submission.autosave.metadata[0]).toEqual(autoSaveMetadata[0]); expect(environment.submission.autosave.metadata[1]).toEqual(autoSaveMetadata[1]); diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index b1b64d1c87..146c7a57c4 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -37,7 +37,10 @@ export class DefaultAppConfig implements AppConfig { rateLimiter: { windowMs: 1 * 60 * 1000, // 1 minute max: 500 // limit each IP to 500 requests per windowMs - } + }, + + // Trust X-FORWARDED-* headers from proxies + useProxies: true, }; // The REST API server settings diff --git a/src/config/ui-server-config.interface.ts b/src/config/ui-server-config.interface.ts index 93f90c345c..70e2fa3e26 100644 --- a/src/config/ui-server-config.interface.ts +++ b/src/config/ui-server-config.interface.ts @@ -11,4 +11,6 @@ export class UIServerConfig extends ServerConfig { max: number; }; + // Trust X-FORWARDED-* headers from proxies + useProxies: boolean; } diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 6fe4dd6516..7838a351bf 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -25,7 +25,8 @@ export const environment: BuildConfig = { rateLimiter: { windowMs: 1 * 60 * 1000, // 1 minute max: 500 // limit each IP to 500 requests per windowMs - } + }, + useProxies: true, }, // The REST API server settings. From 171ac62beb41c2320644e43d5ce926e6cb7c0bb2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Sep 2022 13:30:22 +0200 Subject: [PATCH 42/65] [CST-6753] Maintain compatibility with version 3 --- .../google-analytics.service.spec.ts | 90 ++++++++++++++----- .../statistics/google-analytics.service.ts | 47 +++++++--- src/modules/app/browser-app.module.ts | 12 ++- src/modules/app/server-app.module.ts | 6 +- 4 files changed, 117 insertions(+), 38 deletions(-) diff --git a/src/app/statistics/google-analytics.service.spec.ts b/src/app/statistics/google-analytics.service.spec.ts index 5953f3d8cc..9e2b1c7edf 100644 --- a/src/app/statistics/google-analytics.service.spec.ts +++ b/src/app/statistics/google-analytics.service.spec.ts @@ -1,4 +1,4 @@ -import { Angulartics2GoogleTagManager } from 'angulartics2'; +import { Angulartics2GoogleAnalytics, Angulartics2GoogleTagManager } from 'angulartics2'; import { of } from 'rxjs'; import { GoogleAnalyticsService } from './google-analytics.service'; @@ -10,11 +10,13 @@ import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuratio describe('GoogleAnalyticsService', () => { const trackingIdProp = 'google.analytics.key'; - const trackingIdTestValue = 'mock-tracking-id'; + const trackingIdV4TestValue = 'G-mock-tracking-id'; + const trackingIdV3TestValue = 'UA-mock-tracking-id'; const innerHTMLTestValue = 'mock-script-inner-html'; const srcTestValue = 'mock-script-src'; let service: GoogleAnalyticsService; - let angularticsSpy: Angulartics2GoogleTagManager; + let googleAnalyticsSpy: Angulartics2GoogleAnalytics; + let googleTagManagerSpy: Angulartics2GoogleTagManager; let configSpy: ConfigurationDataService; let klaroServiceSpy: jasmine.SpyObj; let scriptElementMock: any; @@ -32,7 +34,10 @@ describe('GoogleAnalyticsService', () => { }); beforeEach(() => { - angularticsSpy = jasmine.createSpyObj('Angulartics2GoogleTagManager', [ + googleAnalyticsSpy = jasmine.createSpyObj('Angulartics2GoogleAnalytics', [ + 'startTracking', + ]); + googleTagManagerSpy = jasmine.createSpyObj('Angulartics2GoogleTagManager', [ 'startTracking', ]); @@ -40,7 +45,7 @@ describe('GoogleAnalyticsService', () => { 'getSavedPreferences': jasmine.createSpy('getSavedPreferences') }); - configSpy = createConfigSuccessSpy(trackingIdTestValue); + configSpy = createConfigSuccessSpy(trackingIdV4TestValue); scriptElementMock = { set src(newVal) { /* noop */ }, @@ -66,7 +71,7 @@ describe('GoogleAnalyticsService', () => { GOOGLE_ANALYTICS_KLARO_KEY: true })); - service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy ); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy ); }); it('should be created', () => { @@ -90,7 +95,7 @@ describe('GoogleAnalyticsService', () => { GOOGLE_ANALYTICS_KLARO_KEY: true })); - service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should NOT add a script to the body', () => { @@ -100,7 +105,8 @@ describe('GoogleAnalyticsService', () => { it('should NOT start tracking', () => { service.addTrackingIdToPage(); - expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleAnalyticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleTagManagerSpy.startTracking).toHaveBeenCalledTimes(0); }); }); @@ -111,7 +117,7 @@ describe('GoogleAnalyticsService', () => { klaroServiceSpy.getSavedPreferences.and.returnValue(of({ [GOOGLE_ANALYTICS_KLARO_KEY]: true })); - service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should NOT add a script to the body', () => { @@ -121,15 +127,16 @@ describe('GoogleAnalyticsService', () => { it('should NOT start tracking', () => { service.addTrackingIdToPage(); - expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleAnalyticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleTagManagerSpy.startTracking).toHaveBeenCalledTimes(0); }); }); describe('when google-analytics cookie preferences are not existing', () => { beforeEach(() => { - configSpy = createConfigSuccessSpy(trackingIdTestValue); + configSpy = createConfigSuccessSpy(trackingIdV4TestValue); klaroServiceSpy.getSavedPreferences.and.returnValue(of({})); - service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should NOT add a script to the body', () => { @@ -139,18 +146,19 @@ describe('GoogleAnalyticsService', () => { it('should NOT start tracking', () => { service.addTrackingIdToPage(); - expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleAnalyticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleTagManagerSpy.startTracking).toHaveBeenCalledTimes(0); }); }); describe('when google-analytics cookie preferences are set to false', () => { beforeEach(() => { - configSpy = createConfigSuccessSpy(trackingIdTestValue); + configSpy = createConfigSuccessSpy(trackingIdV4TestValue); klaroServiceSpy.getSavedPreferences.and.returnValue(of({ [GOOGLE_ANALYTICS_KLARO_KEY]: false })); - service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should NOT add a script to the body', () => { @@ -160,18 +168,19 @@ describe('GoogleAnalyticsService', () => { it('should NOT start tracking', () => { service.addTrackingIdToPage(); - expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleAnalyticsSpy.startTracking).toHaveBeenCalledTimes(0); + expect(googleTagManagerSpy.startTracking).toHaveBeenCalledTimes(0); }); }); - describe('when both google-analytics cookie and the tracking id are non-empty', () => { + describe('when both google-analytics cookie and the tracking v4 id are non-empty', () => { beforeEach(() => { - configSpy = createConfigSuccessSpy(trackingIdTestValue); + configSpy = createConfigSuccessSpy(trackingIdV4TestValue); klaroServiceSpy.getSavedPreferences.and.returnValue(of({ [GOOGLE_ANALYTICS_KLARO_KEY]: true })); - service = new GoogleAnalyticsService(angularticsSpy, klaroServiceSpy, configSpy, documentSpy); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy); }); it('should create a script tag whose innerHTML contains the tracking id', () => { @@ -183,10 +192,10 @@ describe('GoogleAnalyticsService', () => { expect(documentSpy.createElement('script')).toBe(scriptElementMock); expect(srcSpy).toHaveBeenCalledTimes(1); - expect(srcSpy.calls.argsFor(0)[0]).toContain(trackingIdTestValue); + expect(srcSpy.calls.argsFor(0)[0]).toContain(trackingIdV4TestValue); expect(innerHTMLSpy).toHaveBeenCalledTimes(1); - expect(innerHTMLSpy.calls.argsFor(0)[0]).toContain(trackingIdTestValue); + expect(innerHTMLSpy.calls.argsFor(0)[0]).toContain(trackingIdV4TestValue); }); it('should add a script to the body', () => { @@ -196,9 +205,46 @@ describe('GoogleAnalyticsService', () => { it('should start tracking', () => { service.addTrackingIdToPage(); - expect(angularticsSpy.startTracking).toHaveBeenCalledTimes(1); + expect(googleAnalyticsSpy.startTracking).not.toHaveBeenCalled(); + expect(googleTagManagerSpy.startTracking).toHaveBeenCalledTimes(1); }); }); + + describe('when both google-analytics cookie and the tracking id v3 are non-empty', () => { + + beforeEach(() => { + configSpy = createConfigSuccessSpy(trackingIdV3TestValue); + klaroServiceSpy.getSavedPreferences.and.returnValue(of({ + [GOOGLE_ANALYTICS_KLARO_KEY]: true + })); + service = new GoogleAnalyticsService(googleAnalyticsSpy, googleTagManagerSpy, klaroServiceSpy, configSpy, documentSpy); + }); + + it('should create a script tag whose innerHTML contains the tracking id', () => { + service.addTrackingIdToPage(); + expect(documentSpy.createElement).toHaveBeenCalledTimes(1); + expect(documentSpy.createElement).toHaveBeenCalledWith('script'); + + // sanity check + expect(documentSpy.createElement('script')).toBe(scriptElementMock); + + expect(innerHTMLSpy).toHaveBeenCalledTimes(1); + expect(innerHTMLSpy.calls.argsFor(0)[0]).toContain(trackingIdV3TestValue); + }); + + it('should add a script to the body', () => { + service.addTrackingIdToPage(); + expect(bodyElementSpy.appendChild).toHaveBeenCalledTimes(1); + }); + + it('should start tracking', () => { + service.addTrackingIdToPage(); + expect(googleAnalyticsSpy.startTracking).toHaveBeenCalledTimes(1); + expect(googleTagManagerSpy.startTracking).not.toHaveBeenCalled(); + }); + }); + }); + }); }); diff --git a/src/app/statistics/google-analytics.service.ts b/src/app/statistics/google-analytics.service.ts index d8a9c65959..9c5883d183 100644 --- a/src/app/statistics/google-analytics.service.ts +++ b/src/app/statistics/google-analytics.service.ts @@ -1,7 +1,7 @@ import { DOCUMENT } from '@angular/common'; import { Inject, Injectable } from '@angular/core'; -import { Angulartics2GoogleTagManager } from 'angulartics2'; +import { Angulartics2GoogleAnalytics, Angulartics2GoogleTagManager } from 'angulartics2'; import { combineLatest } from 'rxjs'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; @@ -18,8 +18,8 @@ import { GOOGLE_ANALYTICS_KLARO_KEY } from '../shared/cookies/klaro-configuratio export class GoogleAnalyticsService { constructor( - // private angulartics: Angulartics2GoogleAnalytics, - private angulartics: Angulartics2GoogleTagManager, + private googleAnalytics: Angulartics2GoogleAnalytics, + private googleTagManager: Angulartics2GoogleTagManager, private klaroService: KlaroService, private configService: ConfigurationDataService, @Inject(DOCUMENT) private document: any, @@ -36,7 +36,9 @@ export class GoogleAnalyticsService { const googleKey$ = this.configService.findByPropertyName('google.analytics.key').pipe( getFirstCompletedRemoteData(), ); - combineLatest([this.klaroService.getSavedPreferences(), googleKey$]) + const preferences$ = this.klaroService.getSavedPreferences(); + + combineLatest([preferences$, googleKey$]) .subscribe(([preferences, remoteData]) => { // make sure user has accepted Google Analytics consents if (isEmpty(preferences) || isEmpty(preferences[GOOGLE_ANALYTICS_KLARO_KEY]) || !preferences[GOOGLE_ANALYTICS_KLARO_KEY]) { @@ -55,18 +57,37 @@ export class GoogleAnalyticsService { return; } - // add GTag snippet to page - const keyScript = this.document.createElement('script'); - keyScript.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`; - this.document.body.appendChild(keyScript); + if (this.isGTagVersion(trackingId)) { - const libScript = this.document.createElement('script'); - libScript.innerHTML = `window.dataLayer = window.dataLayer || [];function gtag(){window.dataLayer.push(arguments);} + // add GTag snippet to page + const keyScript = this.document.createElement('script'); + keyScript.src = `https://www.googletagmanager.com/gtag/js?id=${trackingId}`; + this.document.body.appendChild(keyScript); + + const libScript = this.document.createElement('script'); + libScript.innerHTML = `window.dataLayer = window.dataLayer || [];function gtag(){window.dataLayer.push(arguments);} gtag('js', new Date());gtag('config', '${trackingId}');`; - this.document.body.appendChild(libScript); + this.document.body.appendChild(libScript); - // start tracking - this.angulartics.startTracking(); + // start tracking + this.googleTagManager.startTracking(); + } else { + // add trackingId snippet to page + const keyScript = this.document.createElement('script'); + keyScript.innerHTML = `(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); + ga('create', '${trackingId}', 'auto');`; + this.document.body.appendChild(keyScript); + + // start tracking + this.googleAnalytics.startTracking(); + } }); } + + private isGTagVersion(trackingId: string) { + return trackingId && trackingId.startsWith('G-'); + } } diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 404c624a8a..6e3ebf5d37 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -15,13 +15,17 @@ import { AppModule } from '../../app/app.module'; import { ClientCookieService } from '../../app/core/services/client-cookie.service'; import { CookieService } from '../../app/core/services/cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; -import { Angulartics2RouterlessModule } from 'angulartics2'; +import { Angulartics2GoogleTagManager, Angulartics2RouterlessModule } from 'angulartics2'; import { SubmissionService } from '../../app/submission/submission.service'; import { StatisticsModule } from '../../app/statistics/statistics.module'; import { BrowserKlaroService } from '../../app/shared/cookies/browser-klaro.service'; import { KlaroService } from '../../app/shared/cookies/klaro.service'; import { HardRedirectService } from '../../app/core/services/hard-redirect.service'; -import { BrowserHardRedirectService, locationProvider, LocationToken } from '../../app/core/services/browser-hard-redirect.service'; +import { + BrowserHardRedirectService, + locationProvider, + LocationToken +} from '../../app/core/services/browser-hard-redirect.service'; import { LocaleService } from '../../app/core/locale/locale.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; import { AuthRequestService } from '../../app/core/auth/auth-request.service'; @@ -95,6 +99,10 @@ export function getRequest(transferState: TransferState): any { provide: GoogleAnalyticsService, useClass: GoogleAnalyticsService, }, + { + provide: Angulartics2GoogleTagManager, + useClass: Angulartics2GoogleTagManager + }, { provide: AuthRequestService, useClass: BrowserAuthRequestService, diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 8565db3e23..fa529c4ad9 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -6,7 +6,7 @@ import { ServerModule, ServerTransferStateModule } from '@angular/platform-serve import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Angulartics2, Angulartics2GoogleTagManager } from 'angulartics2'; +import { Angulartics2, Angulartics2GoogleAnalytics, Angulartics2GoogleTagManager } from 'angulartics2'; import { AppComponent } from '../../app/app.component'; @@ -58,6 +58,10 @@ export function createTranslateLoader(transferState: TransferState) { provide: Angulartics2, useClass: Angulartics2Mock }, + { + provide: Angulartics2GoogleAnalytics, + useClass: AngularticsProviderMock + }, { provide: Angulartics2GoogleTagManager, useClass: AngularticsProviderMock From d98a2619859c9b6e7593f1741bf100d6a7f65159 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Wed, 28 Sep 2022 14:38:16 +0200 Subject: [PATCH 43/65] Add typedocs & fix copy/paste errors --- src/app/core/cache/object-cache.actions.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/core/cache/object-cache.actions.ts b/src/app/core/cache/object-cache.actions.ts index 8c83f6104e..c18a20ffd6 100644 --- a/src/app/core/cache/object-cache.actions.ts +++ b/src/app/core/cache/object-cache.actions.ts @@ -128,6 +128,9 @@ export class ApplyPatchObjectCacheAction implements Action { } } +/** + * An NgRx action to add dependent request UUIDs to a cached object + */ export class AddDependentsObjectCacheAction implements Action { type = ObjectCacheActionTypes.ADD_DEPENDENTS; payload: { @@ -136,7 +139,7 @@ export class AddDependentsObjectCacheAction implements Action { }; /** - * Create a new AddDependencyObjectCacheAction + * Create a new AddDependentsObjectCacheAction * * @param href the self link of a cached object * @param dependentRequestUUIDs the UUID of the request that depends on this object @@ -149,12 +152,15 @@ export class AddDependentsObjectCacheAction implements Action { } } +/** + * An NgRx action to remove all dependent request UUIDs from a cached object + */ export class RemoveDependentsObjectCacheAction implements Action { type = ObjectCacheActionTypes.REMOVE_DEPENDENTS; payload: string; /** - * Create a new AddDependencyObjectCacheAction + * Create a new RemoveDependentsObjectCacheAction * * @param href the self link of a cached object for which to remove all dependent request UUIDs */ From 8bcbf02ed1ef7ea77d6bd7379aa3f876b6e39e03 Mon Sep 17 00:00:00 2001 From: Jens Vannerum Date: Wed, 28 Sep 2022 18:06:52 +0200 Subject: [PATCH 44/65] 94474: Strings after merge --- src/assets/i18n/pt-BR.json5 | 3 ++- src/assets/i18n/sv.json5 | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index f1774a06ae..28f4d9d225 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -7809,7 +7809,8 @@ // "uploader.or": ", or ", "uploader.or": ", ou ", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Processando", // "uploader.queue-length": "Queue length", diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index c81482d110..96008b96a6 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -1499,7 +1499,7 @@ "communityList.tabTitle": "Enheter", // "communityList.title": "List of Communities", - "communityList.title": "Lista med enheter", + "communityList.title": "Lista med enheter", // "communityList.showMore": "Show More", "communityList.showMore": "Visa fler", @@ -5468,7 +5468,7 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.local-entity": "Lokal tidskrift har lagts till", // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.new-entity": "Successfully imported and added external journal to the selection", - "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.new-entity": "Extern tidskrift har importerats och lagts till", + "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.added.new-entity": "Extern tidskrift har importerats och lagts till", // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.title": "Import Remote Journal Issue", "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.title": "Importera externt tidskriftsexemplar", @@ -6179,7 +6179,8 @@ // "uploader.or": ", or ", "uploader.or": ", eller ", - // "uploader.processing": "Processing", + // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", + // TODO Source message changed - Revise the translation "uploader.processing": "Bearbetar", // "uploader.queue-length": "Queue length", From 60bcfe8cf57f43413336889642813d51a8d0b2b3 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Sep 2022 09:11:03 -0700 Subject: [PATCH 45/65] Correction in the image count method. --- .../mirador-viewer/mirador-viewer.service.ts | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts index 68be928cfa..06e5aa4ced 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts @@ -1,8 +1,10 @@ import { Injectable, isDevMode } from '@angular/core'; import { Observable } from 'rxjs'; import { Item } from '../../core/shared/item.model'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; -import { filter, last, map, switchMap } from 'rxjs/operators'; +import { + getFirstCompletedRemoteData, +} from '../../core/shared/operators'; +import { filter, last, map, mergeMap, switchMap } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { Bitstream } from '../../core/shared/bitstream.model'; @@ -36,36 +38,44 @@ export class MiradorViewerService { * @returns the total image count */ getImageCount(item: Item, bitstreamDataService: BitstreamDataService, bundleDataService: BundleDataService): - Observable { - let count = 0; - return bundleDataService.findAllByItem(item).pipe( - getFirstCompletedRemoteData(), - map((bundlesRD: RemoteData>) => bundlesRD.payload), - map((paginatedList: PaginatedList) => paginatedList.page), - switchMap((bundles: Bundle[]) => bundles), - filter((b: Bundle) => this.isIiifBundle(b.name)), - switchMap((bundle: Bundle) => { - return bitstreamDataService.findAllByItemAndBundleName(item, bundle.name, { - currentPage: 1, - elementsPerPage: 5 - }, true, true, ...this.LINKS_TO_FOLLOW).pipe( + Observable { + let count = 0; + return bundleDataService.findAllByItem(item).pipe( getFirstCompletedRemoteData(), - map((bitstreamsRD: RemoteData>) => bitstreamsRD.payload), - map((paginatedList: PaginatedList) => paginatedList.page), - switchMap((bitstreams: Bitstream[]) => bitstreams), - switchMap((bitstream: Bitstream) => bitstream.format.pipe( - getFirstSucceededRemoteDataPayload(), - map((format: BitstreamFormat) => format) - )), - map((format: BitstreamFormat) => { - if (format.mimetype.includes('image')) { - count++; - } - return count; + map((bundlesRD: RemoteData>) => { + return bundlesRD.payload + }), + map((paginatedList: PaginatedList) => paginatedList.page), + switchMap((bundles: Bundle[]) => bundles), + filter((b: Bundle) => this.isIiifBundle(b.name)), + mergeMap((bundle: Bundle) => { + return bitstreamDataService.findAllByItemAndBundleName(item, bundle.name, { + currentPage: 1, + elementsPerPage: 5 + }, true, true, ...this.LINKS_TO_FOLLOW).pipe( + getFirstCompletedRemoteData(), + map((bitstreamsRD: RemoteData>) => { + return bitstreamsRD.payload; + }), + map((paginatedList: PaginatedList) => paginatedList.page), + switchMap((bitstreams: Bitstream[]) => bitstreams), + switchMap((bitstream: Bitstream) => bitstream.format.pipe( + getFirstCompletedRemoteData(), + map((formatRD: RemoteData) => { + return formatRD.payload + }), + map((format: BitstreamFormat) => { + if (format.mimetype.includes('image')) { + count++; + } + return count; + }), + ) + ) + ); }), last() - ); - })); + ); } isIiifBundle(bundleName: string): boolean { From 42bb39d097c7a0f4257a304ce7ec9f5c7315f8b8 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Sep 2022 09:18:03 -0700 Subject: [PATCH 46/65] Missing semicolon. --- src/app/item-page/mirador-viewer/mirador-viewer.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts index 06e5aa4ced..c7a0affd53 100644 --- a/src/app/item-page/mirador-viewer/mirador-viewer.service.ts +++ b/src/app/item-page/mirador-viewer/mirador-viewer.service.ts @@ -43,7 +43,7 @@ export class MiradorViewerService { return bundleDataService.findAllByItem(item).pipe( getFirstCompletedRemoteData(), map((bundlesRD: RemoteData>) => { - return bundlesRD.payload + return bundlesRD.payload; }), map((paginatedList: PaginatedList) => paginatedList.page), switchMap((bundles: Bundle[]) => bundles), @@ -62,7 +62,7 @@ export class MiradorViewerService { switchMap((bitstream: Bitstream) => bitstream.format.pipe( getFirstCompletedRemoteData(), map((formatRD: RemoteData) => { - return formatRD.payload + return formatRD.payload; }), map((format: BitstreamFormat) => { if (format.mimetype.includes('image')) { From 30f0622810099bbf46c18213b0bff720ddd798cc Mon Sep 17 00:00:00 2001 From: Danilo Felicio Jr <2768739+danilofjr@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:41:38 -0300 Subject: [PATCH 47/65] pt-BR translation contribution Fully translated til line 538 and other small corrections --- src/assets/i18n/pt-BR.json5 | 239 +++++++++++++----------------------- 1 file changed, 83 insertions(+), 156 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index f1774a06ae..531ffc2ba9 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -1,10 +1,10 @@ { // "401.help": "You're not authorized to access this page. You can use the button below to get back to the home page.", - "401.help": "Você não está autorizado a acessar esta página. Você pode usar o botão abaixo para voltar à página inicial.", + "401.help": "Você não tem autorização para acessar esta página. Clique no botão abaixo para retornar à página inicial.", // "401.link.home-page": "Take me to the home page", - "401.link.home-page": "Leve-me para a página inicial", + "401.link.home-page": "Retornar à página inicial", // "401.unauthorized": "unauthorized", "401.unauthorized": "não autorizado", @@ -12,10 +12,10 @@ // "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", - "403.help": "Você não tem permissão para acessar esta página. Você pode usar o botão abaixo para voltar à página inicial.", + "403.help": "Você não tem permissão para acessar esta página. Clique no botão abaixo para retornar à página inicial.", // "403.link.home-page": "Take me to the home page", - "403.link.home-page": "Leve-me para a página inicial", + "403.link.home-page": "Retornar à página inicial", // "403.forbidden": "forbidden", "403.forbidden": "proibido", @@ -27,14 +27,14 @@ "500.help": "O servidor está temporariamente impossibilitado de atender sua solicitação devido a um período de manutenção ou problemas no servidor. Por favor, tente novamente mais tarde.", // "500.link.home-page": "Take me to the home page", - "500.link.home-page": "Leve-me para a página inicial", + "500.link.home-page": "Retornar à página inicial", // "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", - "404.help": "Não pudemos encontrar a página pela qual procura. A página pode ter sido movida ou apagada. Você pode utilizar o botão abaixo para voltar a página inicial. ", + "404.help": "Não encontramos a página que você procura. A página pode ter sido movida ou apagada. Você pode utilizar o botão abaixo para voltar à página inicial. ", // "404.link.home-page": "Take me to the home page", - "404.link.home-page": "Leve-me a página inicial", + "404.link.home-page": "Retornar à página inicial", // "404.page-not-found": "page not found", "404.page-not-found": "página não encontrada", @@ -49,54 +49,43 @@ "error-page.description.500": "Serviço Indisponível", // "error-page.description.404": "page not found", - "error-page.description.404": "pagína não encontrada", + "error-page.description.404": "página não encontrada", // "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", - // TODO New key - Add a translation - "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", + "error-page.orcid.generic-error": "Um erro ocorreu durante o login via ORCID. Certifique-se de que compartilhou o e-mail da sua conta ORCID com o DSpace. Se o erro persistir, contate o administrador.", // "access-status.embargo.listelement.badge": "Embargo", - // TODO New key - Add a translation "access-status.embargo.listelement.badge": "Embargo", // "access-status.metadata.only.listelement.badge": "Metadata only", - // TODO New key - Add a translation - "access-status.metadata.only.listelement.badge": "Metadata only", + "access-status.metadata.only.listelement.badge": "Somente metadados", // "access-status.open.access.listelement.badge": "Open Access", - // TODO New key - Add a translation - "access-status.open.access.listelement.badge": "Open Access", + "access-status.open.access.listelement.badge": "Acesso aberto", // "access-status.restricted.listelement.badge": "Restricted", - // TODO New key - Add a translation - "access-status.restricted.listelement.badge": "Restricted", + "access-status.restricted.listelement.badge": "Restrito", // "access-status.unknown.listelement.badge": "Unknown", - // TODO New key - Add a translation - "access-status.unknown.listelement.badge": "Unknown", + "access-status.unknown.listelement.badge": "Desconhecido", // "admin.curation-tasks.breadcrumbs": "System curation tasks", - // TODO New key - Add a translation - "admin.curation-tasks.breadcrumbs": "System curation tasks", + "admin.curation-tasks.breadcrumbs": "Tarefas de curadoria do sistema", // "admin.curation-tasks.title": "System curation tasks", - // TODO New key - Add a translation - "admin.curation-tasks.title": "System curation tasks", + "admin.curation-tasks.title": "Tarefas de curadoria do sistema", // "admin.curation-tasks.header": "System curation tasks", - // TODO New key - Add a translation - "admin.curation-tasks.header": "System curation tasks", + "admin.curation-tasks.header": "Tarefas de curadoria do sistema", // "admin.registries.bitstream-formats.breadcrumbs": "Format registry", - // TODO New key - Add a translation - "admin.registries.bitstream-formats.breadcrumbs": "Format registry", + "admin.registries.bitstream-formats.breadcrumbs": "Registro de formato", // "admin.registries.bitstream-formats.create.breadcrumbs": "Bitstream format", - // TODO New key - Add a translation - "admin.registries.bitstream-formats.create.breadcrumbs": "Bitstream format", + "admin.registries.bitstream-formats.create.breadcrumbs": "Formato de bitstream", // "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.", - "admin.registries.bitstream-formats.create.failure.content": "Um erro ocorreu durante a criação do novo formato de bitstream.", + "admin.registries.bitstream-formats.create.failure.content": "Ocorreu um erro durante a criação do novo formato de bitstream.", // "admin.registries.bitstream-formats.create.failure.head": "Failure", "admin.registries.bitstream-formats.create.failure.head": "Falha", @@ -129,8 +118,7 @@ "admin.registries.bitstream-formats.description": "Esta lista de formatos de bitstream provê informações sobre formatos conhecidos e seus níveis de suporte.", // "admin.registries.bitstream-formats.edit.breadcrumbs": "Bitstream format", - // TODO New key - Add a translation - "admin.registries.bitstream-formats.edit.breadcrumbs": "Bitstream format", + "admin.registries.bitstream-formats.edit.breadcrumbs": "Formato de bitstream", // "admin.registries.bitstream-formats.edit.description.hint": "", "admin.registries.bitstream-formats.edit.description.hint": "", @@ -139,7 +127,7 @@ "admin.registries.bitstream-formats.edit.description.label": "Descrição", // "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.", - "admin.registries.bitstream-formats.edit.extensions.hint": "Extensões são extensões de arquivo que são usadas para identificar automaticamente o formato dos arquivo enviados. Você pode informar várias extensões para cada formato.", + "admin.registries.bitstream-formats.edit.extensions.hint": "Extensões são extensões de arquivo usadas para identificar automaticamente o formato dos arquivo enviados. Você pode informar várias extensões para cada formato.", // "admin.registries.bitstream-formats.edit.extensions.label": "File extensions", "admin.registries.bitstream-formats.edit.extensions.label": "Extensões de arquivo", @@ -157,7 +145,7 @@ "admin.registries.bitstream-formats.edit.head": "Formato de bitstream: {{ format }}", // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are hidden from the user, and used for administrative purposes.", - "admin.registries.bitstream-formats.edit.internal.hint": "Formatos marcados como interno são ocultos para o usuário, e utilizados por motivos administrativos.", + "admin.registries.bitstream-formats.edit.internal.hint": "Formatos marcados como interno são ocultos para o usuário e utilizados para propósitos administrativos.", // "admin.registries.bitstream-formats.edit.internal.label": "Internal", "admin.registries.bitstream-formats.edit.internal.label": "Interno", @@ -175,13 +163,13 @@ "admin.registries.bitstream-formats.edit.shortDescription.label": "Nome", // "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.", - "admin.registries.bitstream-formats.edit.success.content": "O formato de bitstream foi editedo com sucesso.", + "admin.registries.bitstream-formats.edit.success.content": "O formato de bitstream foi editado com sucesso.", // "admin.registries.bitstream-formats.edit.success.head": "Success", "admin.registries.bitstream-formats.edit.success.head": "Sucesso", // "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.", - "admin.registries.bitstream-formats.edit.supportLevel.hint": "O nível de suporte que a sua instituição promete para este formato.", + "admin.registries.bitstream-formats.edit.supportLevel.hint": "O nível de suporte que a sua instituição garante para este formato.", // "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level", "admin.registries.bitstream-formats.edit.supportLevel.label": "Nível de suporte", @@ -196,7 +184,7 @@ "admin.registries.bitstream-formats.table.delete": "Apagar selecionado(s)", // "admin.registries.bitstream-formats.table.deselect-all": "Deselect all", - "admin.registries.bitstream-formats.table.deselect-all": "Desselecionar todos", + "admin.registries.bitstream-formats.table.deselect-all": "Desmarcar todos", // "admin.registries.bitstream-formats.table.internal": "internal", "admin.registries.bitstream-formats.table.internal": "Interno", @@ -208,7 +196,6 @@ "admin.registries.bitstream-formats.table.name": "Nome", // "admin.registries.bitstream-formats.table.return": "Back", - // TODO Source message changed - Revise the translation "admin.registries.bitstream-formats.table.return": "Voltar", // "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known", @@ -224,17 +211,15 @@ "admin.registries.bitstream-formats.table.supportLevel.head": "Nível de Suporte", // "admin.registries.bitstream-formats.title": "Bitstream Format Registry", - // TODO Source message changed - Revise the translation "admin.registries.bitstream-formats.title": "DSpace Angular :: Registro de Formato de Bitstream", // "admin.registries.metadata.breadcrumbs": "Metadata registry", - // TODO New key - Add a translation - "admin.registries.metadata.breadcrumbs": "Metadata registry", + "admin.registries.metadata.breadcrumbs": "Registro de metadados", // "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.", - "admin.registries.metadata.description": "O registro de metadados mantém a lista de todos os campos de metadados disponíveis no repositório. Estes campos podêm ser divididos em multiplos esquemas. Entretanto, o DSpace requer esquemas de Dublin Core qualificados.", + "admin.registries.metadata.description": "O registro de metadados mantém a lista de todos os campos de metadados disponíveis no repositório. Estes campos podem ser divididos em múltiplos esquemas. Entretanto, o DSpace requer esquemas de Dublin Core qualificados.", // "admin.registries.metadata.form.create": "Create metadata schema", "admin.registries.metadata.form.create": "Criar esquema de metadados", @@ -252,7 +237,7 @@ "admin.registries.metadata.head": "Registro de Metadados", // "admin.registries.metadata.schemas.no-items": "No metadata schemas to show.", - "admin.registries.metadata.schemas.no-items": "Nenhum esquema de metadados a mostrar.", + "admin.registries.metadata.schemas.no-items": "Nenhum esquema de metadados a exibir.", // "admin.registries.metadata.schemas.table.delete": "Delete selected", "admin.registries.metadata.schemas.table.delete": "Apagar selecionado(s)", @@ -267,14 +252,12 @@ "admin.registries.metadata.schemas.table.namespace": "Namespace", // "admin.registries.metadata.title": "Metadata Registry", - // TODO Source message changed - Revise the translation "admin.registries.metadata.title": "DSpace Angular :: Registro de Metadados", // "admin.registries.schema.breadcrumbs": "Metadata schema", - // TODO New key - Add a translation - "admin.registries.schema.breadcrumbs": "Metadata schema", + "admin.registries.schema.breadcrumbs": "Esquema de metadados", // "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".", "admin.registries.schema.description": "Este é o esquema de metadados para \"{{namespace}}\".", @@ -313,107 +296,92 @@ "admin.registries.schema.head": "Esquema de Metadados", // "admin.registries.schema.notification.created": "Successfully created metadata schema \"{{prefix}}\"", - "admin.registries.schema.notification.created": "Criou o esquema de metadados \"{{prefix}}\" com sucesso", + "admin.registries.schema.notification.created": "Esquema de metadados \"{{prefix}}\" criado com sucesso", // "admin.registries.schema.notification.deleted.failure": "Failed to delete {{amount}} metadata schemas", - "admin.registries.schema.notification.deleted.failure": "Falhou ao apagar {{amount}} esquema(s) de metadados", + "admin.registries.schema.notification.deleted.failure": "Falha ao apagar {{amount}} esquema(s) de metadados", // "admin.registries.schema.notification.deleted.success": "Successfully deleted {{amount}} metadata schemas", "admin.registries.schema.notification.deleted.success": "Apagou {{amount}} esquema(s) de metadados com sucesso", // "admin.registries.schema.notification.edited": "Successfully edited metadata schema \"{{prefix}}\"", - "admin.registries.schema.notification.edited": "Editou o esquema de metadados \"{{prefix}}\" com sucesso", + "admin.registries.schema.notification.edited": "Esquema de metadados \"{{prefix}}\" editado com sucesso", // "admin.registries.schema.notification.failure": "Error", "admin.registries.schema.notification.failure": "Erro", // "admin.registries.schema.notification.field.created": "Successfully created metadata field \"{{field}}\"", - "admin.registries.schema.notification.field.created": "Criou o campo de medado \"{{field}}\" com sucesso", + "admin.registries.schema.notification.field.created": "Campo de metadado \"{{field}}\" criado com sucesso", // "admin.registries.schema.notification.field.deleted.failure": "Failed to delete {{amount}} metadata fields", - "admin.registries.schema.notification.field.deleted.failure": "Falhou ao apagar {{amount}} campo(s) de metadados", + "admin.registries.schema.notification.field.deleted.failure": "Falha ao apagar {{amount}} campo(s) de metadados", // "admin.registries.schema.notification.field.deleted.success": "Successfully deleted {{amount}} metadata fields", "admin.registries.schema.notification.field.deleted.success": "Apagou {{amount}} campo(s) de metadados com sucesso", // "admin.registries.schema.notification.field.edited": "Successfully edited metadata field \"{{field}}\"", - "admin.registries.schema.notification.field.edited": "Editou o campo de metadodo \"{{field}}\" com sucesso", + "admin.registries.schema.notification.field.edited": "Campo de metadado \"{{field}}\" editado com sucesso", // "admin.registries.schema.notification.success": "Success", "admin.registries.schema.notification.success": "Sucesso", // "admin.registries.schema.return": "Back", - // TODO Source message changed - Revise the translation "admin.registries.schema.return": "Voltar", // "admin.registries.schema.title": "Metadata Schema Registry", - // TODO Source message changed - Revise the translation "admin.registries.schema.title": "DSpace Angular :: Registro de Esquema de Metadados", // "admin.access-control.epeople.actions.delete": "Delete EPerson", - // TODO New key - Add a translation - "admin.access-control.epeople.actions.delete": "Delete EPerson", + "admin.access-control.epeople.actions.delete": "Excluir EPerson", // "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", - // TODO New key - Add a translation - "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", + "admin.access-control.epeople.actions.impersonate": "Incorporar EPerson", // "admin.access-control.epeople.actions.reset": "Reset password", - // TODO New key - Add a translation - "admin.access-control.epeople.actions.reset": "Reset password", + "admin.access-control.epeople.actions.reset": "Redefinir senha", // "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", - // TODO New key - Add a translation - "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", + "admin.access-control.epeople.actions.stop-impersonating": "Parar de incorporar EPerson", // "admin.access-control.epeople.breadcrumbs": "EPeople", - // TODO New key - Add a translation "admin.access-control.epeople.breadcrumbs": "EPeople", // "admin.access-control.epeople.title": "EPeople", - // TODO New key - Add a translation "admin.access-control.epeople.title": "EPeople", // "admin.access-control.epeople.head": "EPeople", - // TODO New key - Add a translation "admin.access-control.epeople.head": "EPeople", // "admin.access-control.epeople.search.head": "Search", - // TODO New key - Add a translation - "admin.access-control.epeople.search.head": "Search", + "admin.access-control.epeople.search.head": "Busca", // "admin.access-control.epeople.button.see-all": "Browse All", "admin.access-control.epeople.button.see-all": "Pesquisar Todos", // "admin.access-control.epeople.search.scope.metadata": "Metadata", - // TODO New key - Add a translation - "admin.access-control.epeople.search.scope.metadata": "Metadata", + "admin.access-control.epeople.search.scope.metadata": "Metadado", // "admin.access-control.epeople.search.scope.email": "E-mail (exact)", - "admin.access-control.epeople.search.scope.email": "Email (exato)", + "admin.access-control.epeople.search.scope.email": "E-mail (exato)", // "admin.access-control.epeople.search.button": "Search", "admin.access-control.epeople.search.button": "Procurar", // "admin.access-control.epeople.search.placeholder": "Search people...", - // TODO New key - Add a translation - "admin.access-control.epeople.search.placeholder": "Search people...", + "admin.access-control.epeople.search.placeholder": "Procurar pessoas...", // "admin.access-control.epeople.button.add": "Add EPerson", - // TODO New key - Add a translation - "admin.access-control.epeople.button.add": "Add EPerson", + "admin.access-control.epeople.button.add": "Adicionar EPerson", // "admin.access-control.epeople.table.id": "ID", - // TODO New key - Add a translation "admin.access-control.epeople.table.id": "ID", // "admin.access-control.epeople.table.name": "Name", "admin.access-control.epeople.table.name": "Nome", // "admin.access-control.epeople.table.email": "E-mail (exact)", - // TODO New key - Add a translation "admin.access-control.epeople.table.email": "E-mail (exact)", // "admin.access-control.epeople.table.edit": "Edit", @@ -424,140 +392,108 @@ // "admin.access-control.epeople.table.edit.buttons.edit-disabled": "You are not authorized to edit this group", // TODO New key - Add a translation - "admin.access-control.epeople.table.edit.buttons.edit-disabled": "You are not authorized to edit this group", + "admin.access-control.epeople.table.edit.buttons.edit-disabled": "Você não tem autorização para editar este grupo", // "admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.table.edit.buttons.remove": "Delete \"{{name}}\"", + "admin.access-control.epeople.table.edit.buttons.remove": "Excluir \"{{name}}\"", // "admin.access-control.epeople.no-items": "No EPeople to show.", - // TODO New key - Add a translation - "admin.access-control.epeople.no-items": "No EPeople to show.", + "admin.access-control.epeople.no-items": "Nenhum EPeople a exibir.", // "admin.access-control.epeople.form.create": "Create EPerson", - // TODO New key - Add a translation - "admin.access-control.epeople.form.create": "Create EPerson", + "admin.access-control.epeople.form.create": "Criar EPerson", // "admin.access-control.epeople.form.edit": "Edit EPerson", - // TODO New key - Add a translation - "admin.access-control.epeople.form.edit": "Edit EPerson", + "admin.access-control.epeople.form.edit": "Editar EPerson", // "admin.access-control.epeople.form.firstName": "First name", - // TODO New key - Add a translation - "admin.access-control.epeople.form.firstName": "First name", + "admin.access-control.epeople.form.firstName": "Primeiro nome", // "admin.access-control.epeople.form.lastName": "Last name", - // TODO New key - Add a translation - "admin.access-control.epeople.form.lastName": "Last name", + "admin.access-control.epeople.form.lastName": "Último nome", // "admin.access-control.epeople.form.email": "E-mail", - // TODO New key - Add a translation "admin.access-control.epeople.form.email": "E-mail", // "admin.access-control.epeople.form.emailHint": "Must be valid e-mail address", - // TODO New key - Add a translation - "admin.access-control.epeople.form.emailHint": "Must be valid e-mail address", + "admin.access-control.epeople.form.emailHint": "Deve ser um endereço de e-mail válido", // "admin.access-control.epeople.form.canLogIn": "Can log in", - // TODO New key - Add a translation - "admin.access-control.epeople.form.canLogIn": "Can log in", + "admin.access-control.epeople.form.canLogIn": "Pode fazer login", // "admin.access-control.epeople.form.requireCertificate": "Requires certificate", - // TODO New key - Add a translation - "admin.access-control.epeople.form.requireCertificate": "Requires certificate", + "admin.access-control.epeople.form.requireCertificate": "Requer certificado", // "admin.access-control.epeople.form.return": "Back", - // TODO New key - Add a translation - "admin.access-control.epeople.form.return": "Back", + "admin.access-control.epeople.form.return": "Voltar", // "admin.access-control.epeople.form.notification.created.success": "Successfully created EPerson \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.created.success": "Successfully created EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.created.success": "EPerson \"{{name}}\" criado com sucesso", // "admin.access-control.epeople.form.notification.created.failure": "Failed to create EPerson \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.created.failure": "Failed to create EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.created.failure": "Falha ao criar EPerson \"{{name}}\"", // "admin.access-control.epeople.form.notification.created.failure.emailInUse": "Failed to create EPerson \"{{name}}\", email \"{{email}}\" already in use.", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.created.failure.emailInUse": "Failed to create EPerson \"{{name}}\", email \"{{email}}\" already in use.", + "admin.access-control.epeople.form.notification.created.failure.emailInUse": "Falha ao criar EPerson \"{{name}}\": e-mail \"{{email}}\" já está em uso.", // "admin.access-control.epeople.form.notification.edited.failure.emailInUse": "Failed to edit EPerson \"{{name}}\", email \"{{email}}\" already in use.", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.edited.failure.emailInUse": "Failed to edit EPerson \"{{name}}\", email \"{{email}}\" already in use.", + "admin.access-control.epeople.form.notification.edited.failure.emailInUse": "Falha ao editar EPerson \"{{name}}\": e-mail \"{{email}}\" já está em uso.", // "admin.access-control.epeople.form.notification.edited.success": "Successfully edited EPerson \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.edited.success": "Successfully edited EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.edited.success": "EPerson \"{{name}}\" editado com sucesso", // "admin.access-control.epeople.form.notification.edited.failure": "Failed to edit EPerson \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.edited.failure": "Failed to edit EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.edited.failure": "Falha ao editar EPerson \"{{name}}\"", // "admin.access-control.epeople.form.notification.deleted.success": "Successfully deleted EPerson \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.deleted.success": "Successfully deleted EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.deleted.success": "EPerson \"{{name}}\" excluído com sucesso", // "admin.access-control.epeople.form.notification.deleted.failure": "Failed to delete EPerson \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.form.notification.deleted.failure": "Failed to delete EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.deleted.failure": "Falha ao excluir EPerson \"{{name}}\"", // "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Member of these groups:", - // TODO New key - Add a translation - "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Member of these groups:", + "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Membro destes grupos:", // "admin.access-control.epeople.form.table.id": "ID", - // TODO New key - Add a translation "admin.access-control.epeople.form.table.id": "ID", // "admin.access-control.epeople.form.table.name": "Name", "admin.access-control.epeople.form.table.name": "Nome", // "admin.access-control.epeople.form.table.collectionOrCommunity": "Collection/Community", - // TODO New key - Add a translation - "admin.access-control.epeople.form.table.collectionOrCommunity": "Collection/Community", + "admin.access-control.epeople.form.table.collectionOrCommunity": "Coleção/Comunidade", // "admin.access-control.epeople.form.memberOfNoGroups": "This EPerson is not a member of any groups", - // TODO New key - Add a translation - "admin.access-control.epeople.form.memberOfNoGroups": "This EPerson is not a member of any groups", + "admin.access-control.epeople.form.memberOfNoGroups": "Este EPerson não é membro de nenhum grupo", // "admin.access-control.epeople.form.goToGroups": "Add to groups", - // TODO New key - Add a translation - "admin.access-control.epeople.form.goToGroups": "Add to groups", + "admin.access-control.epeople.form.goToGroups": "Adicionar ao grupos", // "admin.access-control.epeople.notification.deleted.failure": "Failed to delete EPerson: \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.notification.deleted.failure": "Failed to delete EPerson: \"{{name}}\"", + "admin.access-control.epeople.notification.deleted.failure": "Falha ao excluir EPerson: \"{{name}}\"", // "admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"", + "admin.access-control.epeople.notification.deleted.success": "EPerson: \"{{name}}\" excluído com sucesso", // "admin.access-control.groups.title": "Groups", - // TODO New key - Add a translation - "admin.access-control.groups.title": "Groups", + "admin.access-control.groups.title": "Grupos", // "admin.access-control.groups.breadcrumbs": "Groups", - // TODO New key - Add a translation - "admin.access-control.groups.breadcrumbs": "Groups", + "admin.access-control.groups.breadcrumbs": "Grupos", // "admin.access-control.groups.singleGroup.breadcrumbs": "Edit Group", - // TODO New key - Add a translation - "admin.access-control.groups.singleGroup.breadcrumbs": "Edit Group", + "admin.access-control.groups.singleGroup.breadcrumbs": "Editar Grupo", // "admin.access-control.groups.title.singleGroup": "Edit Group", - // TODO New key - Add a translation - "admin.access-control.groups.title.singleGroup": "Edit Group", + "admin.access-control.groups.title.singleGroup": "Editar Grupo", // "admin.access-control.groups.title.addGroup": "New Group", - // TODO New key - Add a translation - "admin.access-control.groups.title.addGroup": "New Group", + "admin.access-control.groups.title.addGroup": "Novo Grupo", // "admin.access-control.groups.addGroup.breadcrumbs": "New Group", - // TODO New key - Add a translation - "admin.access-control.groups.addGroup.breadcrumbs": "New Group", + "admin.access-control.groups.addGroup.breadcrumbs": "Novo Grupo", // "admin.access-control.groups.head": "Groups", "admin.access-control.groups.head": "Grupos", @@ -569,46 +505,37 @@ "admin.access-control.groups.search.head": "Pesquisar grupos", // "admin.access-control.groups.button.see-all": "Browse all", - // TODO New key - Add a translation - "admin.access-control.groups.button.see-all": "Browse all", + "admin.access-control.groups.button.see-all": "Pesquisar todos", // "admin.access-control.groups.search.button": "Search", - // TODO New key - Add a translation - "admin.access-control.groups.search.button": "Search", + "admin.access-control.groups.search.button": "Procurar", // "admin.access-control.groups.search.placeholder": "Search groups...", - // TODO New key - Add a translation - "admin.access-control.groups.search.placeholder": "Search groups...", + "admin.access-control.groups.search.placeholder": "Procurar grupos...", // "admin.access-control.groups.table.id": "ID", - // TODO New key - Add a translation "admin.access-control.groups.table.id": "ID", // "admin.access-control.groups.table.name": "Name", "admin.access-control.groups.table.name": "Nome", // "admin.access-control.groups.table.collectionOrCommunity": "Collection/Community", - // TODO New key - Add a translation - "admin.access-control.groups.table.collectionOrCommunity": "Collection/Community", + "admin.access-control.groups.table.collectionOrCommunity": "Coleção/Comunidade", // "admin.access-control.groups.table.members": "Members", - // TODO New key - Add a translation - "admin.access-control.groups.table.members": "Members", + "admin.access-control.groups.table.members": "Membros", // "admin.access-control.groups.table.edit": "Edit", "admin.access-control.groups.table.edit": "Editar", // "admin.access-control.groups.table.edit.buttons.edit": "Edit \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.groups.table.edit.buttons.edit": "Edit \"{{name}}\"", + "admin.access-control.groups.table.edit.buttons.edit": "Editar \"{{name}}\"", // "admin.access-control.groups.table.edit.buttons.remove": "Delete \"{{name}}\"", - // TODO New key - Add a translation - "admin.access-control.groups.table.edit.buttons.remove": "Delete \"{{name}}\"", + "admin.access-control.groups.table.edit.buttons.remove": "Excluir \"{{name}}\"", // "admin.access-control.groups.no-items": "No groups found with this in their name or this as UUID", - // TODO New key - Add a translation - "admin.access-control.groups.no-items": "No groups found with this in their name or this as UUID", + "admin.access-control.groups.no-items": "Nenhum grupo encontrado com isto no seu nome ou com esse UUID", // "admin.access-control.groups.notification.deleted.success": "Successfully deleted group \"{{name}}\"", // TODO New key - Add a translation From 0235cc2fe83b7087cc34a1d695854f358014b04e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 28 Sep 2022 13:30:42 -0500 Subject: [PATCH 48/65] Update community-roles.component to align with collection-roles --- .../community-roles.component.html | 2 +- .../community-roles.component.spec.ts | 3 +- .../community-roles.component.ts | 39 ++++++++++--------- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/app/community-page/edit-community-page/community-roles/community-roles.component.html b/src/app/community-page/edit-community-page/community-roles/community-roles.component.html index 07d5d96fcd..37a70ff403 100644 --- a/src/app/community-page/edit-community-page/community-roles/community-roles.component.html +++ b/src/app/community-page/edit-community-page/community-roles/community-roles.component.html @@ -1,5 +1,5 @@ diff --git a/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts b/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts index baea5fb9d0..9dddb87f8d 100644 --- a/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts +++ b/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts @@ -78,8 +78,9 @@ describe('CommunityRolesComponent', () => { fixture.detectChanges(); }); - it('should display a community admin role component', () => { + it('should display a community admin role component', (done) => { expect(de.query(By.css('ds-comcol-role .community-admin'))) .toBeTruthy(); + done(); }); }); diff --git a/src/app/community-page/edit-community-page/community-roles/community-roles.component.ts b/src/app/community-page/edit-community-page/community-roles/community-roles.component.ts index 3bb2de9a62..9468aa7048 100644 --- a/src/app/community-page/edit-community-page/community-roles/community-roles.component.ts +++ b/src/app/community-page/edit-community-page/community-roles/community-roles.component.ts @@ -19,28 +19,14 @@ export class CommunityRolesComponent implements OnInit { dsoRD$: Observable>; /** - * The community to manage, as an observable. + * The different roles for the community, as an observable. */ - get community$(): Observable { - return this.dsoRD$.pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - ); - } + comcolRoles$: Observable; /** - * The different roles for the community. + * The community to manage, as an observable. */ - getComcolRoles$(): Observable { - return this.community$.pipe( - map((community) => [ - { - name: 'community-admin', - href: community._links.adminGroup.href, - }, - ]), - ); - } + community$: Observable; constructor( protected route: ActivatedRoute, @@ -52,5 +38,22 @@ export class CommunityRolesComponent implements OnInit { first(), map((data) => data.dso), ); + + this.community$ = this.dsoRD$.pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + ); + + /** + * The different roles for the community. + */ + this.comcolRoles$ = this.community$.pipe( + map((community) => [ + { + name: 'community-admin', + href: community._links.adminGroup.href, + }, + ]), + ); } } From b0d1a74128d2792b608455c8640fde488bf8e04d Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Tue, 5 Jul 2022 19:18:28 +0200 Subject: [PATCH 49/65] Made comcol-page-handle-component themeable --- .../browse-by-metadata-page.component.html | 4 +-- .../collection-page.component.html | 4 +-- .../community-page.component.html | 4 +-- .../themed-comcol-page-handle.component.ts | 36 +++++++++++++++++++ src/app/shared/comcol/comcol.module.ts | 5 +++ .../comcol-page-handle.component.html | 0 .../comcol-page-handle.component.scss | 0 .../comcol-page-handle.component.ts | 18 ++++++++++ src/themes/custom/lazy-theme.module.ts | 3 ++ 9 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 src/app/shared/comcol/comcol-page-handle/themed-comcol-page-handle.component.ts create mode 100644 src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.html create mode 100644 src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.scss create mode 100644 src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.ts diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html index eb15ac9523..227fa8aa78 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html @@ -6,10 +6,10 @@ - - + diff --git a/src/app/collection-page/collection-page.component.html b/src/app/collection-page/collection-page.component.html index c1df38f793..eebfdbd829 100644 --- a/src/app/collection-page/collection-page.component.html +++ b/src/app/collection-page/collection-page.component.html @@ -17,10 +17,10 @@ - - + - - + + diff --git a/src/app/shared/comcol/comcol-page-handle/themed-comcol-page-handle.component.ts b/src/app/shared/comcol/comcol-page-handle/themed-comcol-page-handle.component.ts new file mode 100644 index 0000000000..e7a7caf7be --- /dev/null +++ b/src/app/shared/comcol/comcol-page-handle/themed-comcol-page-handle.component.ts @@ -0,0 +1,36 @@ +import {Component, Input} from '@angular/core'; +import { ThemedComponent } from '../../theme-support/themed.component'; +import { ComcolPageHandleComponent } from './comcol-page-handle.component'; + +/** + * Themed wrapper for BreadcrumbsComponent + */ +@Component({ + selector: 'ds-themed-comcol-page-handle', + styleUrls: [], + templateUrl: '../../theme-support/themed.component.html', +}) + + +export class ThemedComcolPageHandleComponent extends ThemedComponent { + +// Optional title + @Input() title: string; + +// The value of "handle" + @Input() content: string; + + inAndOutputNames: (keyof ComcolPageHandleComponent & keyof this)[] = ['title', 'content']; + + protected getComponentName(): string { + return 'ComcolPageHandleComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../../themes/${themeName}/app/shared/comcol/comcol-page-handle/comcol-page-handle.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./comcol-page-handle.component`); + } +} diff --git a/src/app/shared/comcol/comcol.module.ts b/src/app/shared/comcol/comcol.module.ts index 6e779a24db..094387929a 100644 --- a/src/app/shared/comcol/comcol.module.ts +++ b/src/app/shared/comcol/comcol.module.ts @@ -2,6 +2,8 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ComcolPageContentComponent } from './comcol-page-content/comcol-page-content.component'; import { ComcolPageHandleComponent } from './comcol-page-handle/comcol-page-handle.component'; +import { ThemedComcolPageHandleComponent} from './comcol-page-handle/themed-comcol-page-handle.component'; + import { ComcolPageHeaderComponent } from './comcol-page-header/comcol-page-header.component'; import { ComcolPageLogoComponent } from './comcol-page-logo/comcol-page-logo.component'; import { ComColFormComponent } from './comcol-forms/comcol-form/comcol-form.component'; @@ -26,6 +28,9 @@ const COMPONENTS = [ ComcolPageBrowseByComponent, ThemedComcolPageBrowseByComponent, ComcolRoleComponent, + + ThemedComcolPageHandleComponent + ]; @NgModule({ diff --git a/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.html b/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.scss b/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.ts b/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.ts new file mode 100644 index 0000000000..ef68f1538f --- /dev/null +++ b/src/themes/custom/app/shared/comcol-page-handle/comcol-page-handle.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { ComcolPageHandleComponent as BaseComponent} from '../../../../../app/shared/comcol/comcol-page-handle/comcol-page-handle.component'; + + +/** + * This component builds a URL from the value of "handle" + */ + +@Component({ + selector: 'ds-comcol-page-handle', + // templateUrl: './comcol-page-handle.component.html', + templateUrl: '../../../../../app/shared/comcol/comcol-page-handle/comcol-page-handle.component.html', + // styleUrls: ['./comcol-page-handle.component.scss'], + styleUrls: ['../../../../../app/shared/comcol/comcol-page-handle/comcol-page-handle.component.scss'], +}) + + +export class ComcolPageHandleComponent extends BaseComponent {} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 5a923ebf8e..15637dd2db 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -88,6 +88,8 @@ import { import { BreadcrumbsComponent } from './app/breadcrumbs/breadcrumbs.component'; import { FeedbackComponent } from './app/info/feedback/feedback.component'; import { CommunityListComponent } from './app/community-list-page/community-list/community-list.component'; + +import { ComcolPageHandleComponent } from './app/shared/comcol-page-handle/comcol-page-handle.component'; import { AuthNavMenuComponent } from './app/shared/auth-nav-menu/auth-nav-menu.component'; import { ExpandableNavbarSectionComponent @@ -146,6 +148,7 @@ const DECLARATIONS = [ BreadcrumbsComponent, FeedbackComponent, CommunityListComponent, + ComcolPageHandleComponent, AuthNavMenuComponent, ExpandableNavbarSectionComponent, ItemMetadataComponent, From 24943b9b09b1f8e1c6335596ec581fcaecef725f Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Wed, 6 Jul 2022 15:02:54 +0200 Subject: [PATCH 50/65] Added already themed comcol-page-browse-by.component to custom theme. --- .../comcol-page-browse-by.component.html | 0 .../comcol-page-browse-by.component.scss | 0 .../comcol-page-browse-by.component.ts | 15 +++++++++++++++ src/themes/custom/lazy-theme.module.ts | 2 ++ 4 files changed, 17 insertions(+) create mode 100644 src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html create mode 100644 src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss create mode 100644 src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts diff --git a/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss b/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts new file mode 100644 index 0000000000..c6805175b0 --- /dev/null +++ b/src/themes/custom/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { ComcolPageBrowseByComponent as BaseComponent} from '../../../../../app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component'; + +/** + * A component to display the "Browse By" section of a Community or Collection page + * It expects the ID of the Community or Collection as input to be passed on as a scope + */ +@Component({ + selector: 'ds-comcol-page-browse-by', + // styleUrls: ['./comcol-page-browse-by.component.scss'], + styleUrls: ['../../../../../app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.scss'], + // templateUrl: './comcol-page-browse-by.component.html' + templateUrl: '../../../../../app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html' +}) +export class ComcolPageBrowseByComponent extends BaseComponent {} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 15637dd2db..5a3b898bb0 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -101,6 +101,7 @@ import { import { LoadingComponent } from './app/shared/loading/loading.component'; import { SearchResultsComponent } from './app/shared/search/search-results/search-results.component'; import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component'; +import { ComcolPageBrowseByComponent } from './app/shared/comcol-page-browse-by/comcol-page-browse-by.component'; import { SearchSettingsComponent } from './app/shared/search/search-settings/search-settings.component'; import { CommunityPageSubCommunityListComponent @@ -157,6 +158,7 @@ const DECLARATIONS = [ SearchResultsComponent, AdminSidebarComponent, SearchSettingsComponent + ComcolPageBrowseByComponent, ]; @NgModule({ From 48c3fd24edb27a4c818e52a57001d64a5febebc2 Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Wed, 6 Jul 2022 19:21:18 +0200 Subject: [PATCH 51/65] Themed object-list.component. --- .../object-collection.component.html | 5 +- .../themed-object-list.component.ts | 209 ++++++++++++++++++ src/app/shared/shared.module.ts | 2 + .../object-list/object-list.component.html | 0 .../object-list/object-list.component.scss | 0 .../object-list/object-list.component.ts | 16 ++ src/themes/custom/lazy-theme.module.ts | 2 + 7 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 src/app/shared/object-list/themed-object-list.component.ts create mode 100644 src/themes/custom/app/shared/object-list/object-list.component.html create mode 100644 src/themes/custom/app/shared/object-list/object-list.component.scss create mode 100644 src/themes/custom/app/shared/object-list/object-list.component.ts diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index eb72d07fe5..06d7cedb9e 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -1,4 +1,5 @@ - - + { + /** + * The view mode of the this component + */ + viewMode = ViewMode.ListElement; + + /** + * The current pagination configuration + */ + @Input() config: PaginationComponentOptions; + + /** + * The current sort configuration + */ + @Input() sortConfig: SortOptions; + + /** + * Whether or not the list elements have a border + */ + @Input() hasBorder = false; + + /** + * The whether or not the gear is hidden + */ + @Input() hideGear = false; + + /** + * Whether or not the pager is visible when there is only a single page of results + */ + @Input() hidePagerWhenSinglePage = true; + @Input() selectable = false; + @Input() selectionConfig: { repeatable: boolean, listId: string }; + + /** + * The link type of the listable elements + */ + @Input() linkType: CollectionElementLinkType; + + /** + * The context of the listable elements + */ + @Input() context: Context; + + /** + * Option for hiding the pagination detail + */ + @Input() hidePaginationDetail = false; + + /** + * Whether or not to add an import button to the object + */ + @Input() importable = false; + + /** + * Config used for the import button + */ + @Input() importConfig: { importLabel: string }; + + /** + * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination + */ + @Input() showPaginator = true; + + /** + * Emit when one of the listed object has changed. + */ + @Output() contentChange = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the previous button is clicked + */ + @Output() prev = new EventEmitter(); + + /** + * If showPaginator is set to true, emit when the next button is clicked + */ + @Output() next = new EventEmitter(); + + /** + * The current listable objects + */ + private _objects: RemoteData>; + + /** + * Setter for the objects + * @param objects The new objects + */ + @Input() set objects(objects: RemoteData>) { + this._objects = objects; + } + + /** + * Getter to return the current objects + */ + get objects() { + return this._objects; + } + + /** + * An event fired when the page is changed. + * Event's payload equals to the newly selected page. + */ + @Output() change: EventEmitter<{ + pagination: PaginationComponentOptions, + sort: SortOptions + }> = new EventEmitter<{ + pagination: PaginationComponentOptions, + sort: SortOptions + }>(); + + /** + * An event fired when the page is changed. + * Event's payload equals to the newly selected page. + */ + @Output() pageChange: EventEmitter = new EventEmitter(); + + /** + * An event fired when the page wsize is changed. + * Event's payload equals to the newly selected page size. + */ + @Output() pageSizeChange: EventEmitter = new EventEmitter(); + + /** + * An event fired when the sort direction is changed. + * Event's payload equals to the newly selected sort direction. + */ + @Output() sortDirectionChange: EventEmitter = new EventEmitter(); + + /** + * An event fired when on of the pagination parameters changes + */ + @Output() paginationChange: EventEmitter = new EventEmitter(); + + @Output() deselectObject: EventEmitter = new EventEmitter(); + + @Output() selectObject: EventEmitter = new EventEmitter(); + + /** + * Send an import event to the parent component + */ + @Output() importObject: EventEmitter = new EventEmitter(); + + /** + * An event fired when the sort field is changed. + * Event's payload equals to the newly selected sort field. + */ + @Output() sortFieldChange: EventEmitter = new EventEmitter(); + + inAndOutputNames: (keyof ObjectListComponent & keyof this)[] = [ + 'config', + 'sortConfig', + 'hasBorder', + 'hideGear', + 'hidePagerWhenSinglePage', + 'selectable', + 'selectionConfig', + 'linkType', + 'context', + 'hidePaginationDetail', + 'importable', + 'importConfig', + 'showPaginator', + 'contentChange', + 'prev', + 'next', + 'objects', + 'change', + 'pageChange', + 'pageSizeChange', + 'sortDirectionChange', + 'paginationChange', + 'deselectObject', + 'selectObject', + 'importObject', + 'sortFieldChange', + ]; + + protected getComponentName(): string { + return 'ObjectListComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes//${themeName}/app/shared/object-list/object-list.component`); + } + + protected importUnthemedComponent(): Promise { + return import('./object-list.component'); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index e4231d8f21..a5333d1a7d 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -39,6 +39,7 @@ import { SearchResultListElementComponent } from './object-list/search-result-list-element/search-result-list-element.component'; import { ObjectListComponent } from './object-list/object-list.component'; +import { ThemedObjectListComponent } from './object-list/themed-object-list.component'; import { CollectionGridElementComponent } from './object-grid/collection-grid-element/collection-grid-element.component'; @@ -376,6 +377,7 @@ const COMPONENTS = [ LogOutComponent, NumberPickerComponent, ObjectListComponent, + ThemedObjectListComponent, ObjectDetailComponent, ObjectGridComponent, AbstractListableElementComponent, diff --git a/src/themes/custom/app/shared/object-list/object-list.component.html b/src/themes/custom/app/shared/object-list/object-list.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/object-list/object-list.component.scss b/src/themes/custom/app/shared/object-list/object-list.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/object-list/object-list.component.ts b/src/themes/custom/app/shared/object-list/object-list.component.ts new file mode 100644 index 0000000000..49f464610f --- /dev/null +++ b/src/themes/custom/app/shared/object-list/object-list.component.ts @@ -0,0 +1,16 @@ +import { Component } from '@angular/core'; +import { ObjectListComponent as BaseComponent} from '../../../../../app/shared/object-list/object-list.component'; + +/** + * A component to display the "Browse By" section of a Community or Collection page + * It expects the ID of the Community or Collection as input to be passed on as a scope + */ +@Component({ + selector: 'ds-object-list', + // styleUrls: ['./object-list.component.scss'], + styleUrls: ['../../../../../app/shared/object-list/object-list.component.scss'], + // templateUrl: 'object-list.component.html' + templateUrl: '../../../../../app/shared/object-list/object-list.component.html' +}) + +export class ObjectListComponent extends BaseComponent {} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 5a3b898bb0..acb3b1fa4a 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -109,6 +109,7 @@ import { import { CommunityPageSubCollectionListComponent } from './app/community-page/sub-collection-list/community-page-sub-collection-list.component'; +import { ObjectListComponent } from './app/shared/object-list/object-list.component'; const DECLARATIONS = [ FileSectionComponent, @@ -159,6 +160,7 @@ const DECLARATIONS = [ AdminSidebarComponent, SearchSettingsComponent ComcolPageBrowseByComponent, + ObjectListComponent, ]; @NgModule({ From f5316b5805c36b65abb0756f45c667156e1582c6 Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Fri, 8 Jul 2022 22:06:41 +0200 Subject: [PATCH 52/65] Made community-list-element.component and collection-list-element.component themeable. --- .../collection-list-element.component.html | 0 .../collection-list-element.component.scss | 0 .../collection-list-element.component.ts | 27 +++++++++++++++++++ .../community-list-element.component.html | 0 .../community-list-element.component.scss | 0 .../community-list-element.component.ts | 23 ++++++++++++++++ src/themes/custom/eager-theme.module.ts | 7 +++++ 7 files changed, 57 insertions(+) create mode 100644 src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.html create mode 100644 src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.scss create mode 100644 src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.ts create mode 100644 src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.html create mode 100644 src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.scss create mode 100644 src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.ts diff --git a/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.html b/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.scss b/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.ts b/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.ts new file mode 100644 index 0000000000..78ac59e7b8 --- /dev/null +++ b/src/themes/custom/app/shared/object-list/collection-list-element/collection-list-element.component.ts @@ -0,0 +1,27 @@ +import {Component} from '@angular/core'; + +import {Collection} from '../../../../../../app/core/shared/collection.model'; +import { + CollectionListElementComponent as BaseComponent +} from '../../../../../../app/shared/object-list/collection-list-element/collection-list-element.component'; +import {ViewMode} from '../../../../../../app/core/shared/view-mode.model'; +import { + listableObjectComponent +} from '../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; +import {Context} from '../../../../../../app/core/shared/context.model'; + +@listableObjectComponent(Collection, ViewMode.ListElement, Context.Any, 'custom') + +@Component({ + selector: 'ds-collection-list-element', + // styleUrls: ['./collection-list-element.component.scss'], + styleUrls: ['../../../../../../app/shared/object-list/collection-list-element/collection-list-element.component.scss'], + // templateUrl: './collection-list-element.component.html' + templateUrl: '../../../../../../app/shared/object-list/collection-list-element/collection-list-element.component.html' +}) +/** + * Component representing list element for a collection + */ +export class CollectionListElementComponent extends BaseComponent {} + + diff --git a/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.html b/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.scss b/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.ts b/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.ts new file mode 100644 index 0000000000..24e2cdcfa1 --- /dev/null +++ b/src/themes/custom/app/shared/object-list/community-list-element/community-list-element.component.ts @@ -0,0 +1,23 @@ +import {Component} from '@angular/core'; + +import { Community } from '../../../../../../app/core/shared/community.model'; +import { + CommunityListElementComponent as BaseComponent +} from '../../../../../../app/shared/object-list/community-list-element/community-list-element.component'; +import { ViewMode } from '../../../../../../app/core/shared/view-mode.model'; +import { listableObjectComponent } from '../../../../../../app/shared/object-collection/shared/listable-object/listable-object.decorator'; +import {Context} from '../../../../../../app/core/shared/context.model'; + +@listableObjectComponent(Community, ViewMode.ListElement, Context.Any, 'custom') + +@Component({ + selector: 'ds-community-list-element', + // styleUrls: ['./community-list-element.component.scss'], + styleUrls: ['../../../../../../app/shared/object-list/community-list-element/community-list-element.component.scss'], + // templateUrl: './community-list-element.component.html' + templateUrl: '../../../../../../app/shared/object-list/community-list-element/community-list-element.component.html' +}) +/** + * Component representing a list element for a community + */ +export class CommunityListElementComponent extends BaseComponent {} diff --git a/src/themes/custom/eager-theme.module.ts b/src/themes/custom/eager-theme.module.ts index 4c75f63cc9..3d57f3e951 100644 --- a/src/themes/custom/eager-theme.module.ts +++ b/src/themes/custom/eager-theme.module.ts @@ -40,6 +40,10 @@ import { EditItemSelectorComponent } from './app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; +import { CommunityListElementComponent } from './app/shared/object-list/community-list-element/community-list-element.component'; +import { CollectionListElementComponent} from './app/shared/object-list/collection-list-element/collection-list-element.component'; + + /** * Add components that use a custom decorator to ENTRY_COMPONENTS as well as DECLARATIONS. * This will ensure that decorator gets picked up when the app loads @@ -50,6 +54,9 @@ const ENTRY_COMPONENTS = [ JournalVolumeComponent, PublicationComponent, UntypedItemComponent, + + CommunityListElementComponent, + CollectionListElementComponent, ]; const DECLARATIONS = [ From 3799ed28c1cf35ab91bf355933b1761b788e3df4 Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Sun, 10 Jul 2022 20:54:46 +0200 Subject: [PATCH 53/65] Made browse-by-metadata-page.component, browse-by-title-page.component, browse-by-date-page.component themeable and fixed a forgotten "implements OnDestroy" in browse-by-metadata-page.component.ts --- .../browse-by-date-page.component.ts | 2 -- .../themed-browse-by-date-page.component.ts | 29 +++++++++++++++++++ .../browse-by-metadata-page.component.ts | 8 ++--- ...hemed-browse-by-metadata-page.component.ts | 29 +++++++++++++++++++ .../browse-by-title-page.component.ts | 2 -- .../themed-browse-by-title-page.component.ts | 29 +++++++++++++++++++ src/app/browse-by/browse-by.module.ts | 10 ++++++- .../browse-by-date-page.component.ts | 17 +++++++++++ .../browse-by-metadata-page.component.html | 0 .../browse-by-metadata-page.component.scss | 0 .../browse-by-metadata-page.component.ts | 17 +++++++++++ .../browse-by-title-page.component.ts | 17 +++++++++++ src/themes/custom/lazy-theme.module.ts | 9 ++++++ 13 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts create mode 100644 src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts create mode 100644 src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts create mode 100644 src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts create mode 100644 src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html create mode 100644 src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss create mode 100644 src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts create mode 100644 src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index c9ee669e51..c4a67349a5 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -11,7 +11,6 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { BrowseService } from '../../core/browse/browse.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; -import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @@ -29,7 +28,6 @@ import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; * A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields. * An example would be 'dateissued' for 'dc.date.issued' */ -@rendersBrowseBy(BrowseByDataType.Date) export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { /** diff --git a/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts new file mode 100644 index 0000000000..8eeae0c5de --- /dev/null +++ b/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts @@ -0,0 +1,29 @@ +import {Component} from '@angular/core'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { BrowseByDatePageComponent } from './browse-by-date-page.component'; +import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; + +/** + * Themed wrapper for BrowseByDatePageComponent + * */ +@Component({ + selector: 'ds-themed-browse-by-metadata-page', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) + +@rendersBrowseBy(BrowseByDataType.Date) +export class ThemedBrowseByDatePageComponent + extends ThemedComponent { + protected getComponentName(): string { + return 'BrowseByDatePageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/browse-by/browse-by-date-page/browse-by-date-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./browse-by-date-page.component`); + } +} diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index a95aea7c0a..4cfe332da1 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,5 +1,5 @@ import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; -import { Component, Inject, OnInit } from '@angular/core'; +import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @@ -14,7 +14,6 @@ import { getFirstSucceededRemoteData } from '../../core/shared/operators'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { StartsWithType } from '../../shared/starts-with/starts-with-decorator'; -import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; @@ -32,8 +31,7 @@ export const BBM_PAGINATION_ID = 'bbm'; * or multiple metadata fields. An example would be 'author' for * 'dc.contributor.*' */ -@rendersBrowseBy(BrowseByDataType.Metadata) -export class BrowseByMetadataPageComponent implements OnInit { +export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { /** * The list of browse-entries to display @@ -93,7 +91,7 @@ export class BrowseByMetadataPageComponent implements OnInit { startsWithOptions; /** - * The value we're browing items for + * The value we're browsing items for * - When the value is not empty, we're browsing items * - When the value is empty, we're browsing browse-entries (values for the given metadata definition) */ diff --git a/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts new file mode 100644 index 0000000000..b0679258e9 --- /dev/null +++ b/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts @@ -0,0 +1,29 @@ +import {Component} from '@angular/core'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { BrowseByMetadataPageComponent } from './browse-by-metadata-page.component'; +import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; + +/** + * Themed wrapper for BrowseByMetadataPageComponent + **/ +@Component({ + selector: 'ds-themed-browse-by-metadata-page', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) + +@rendersBrowseBy(BrowseByDataType.Metadata) +export class ThemedBrowseByMetadataPageComponent + extends ThemedComponent { + protected getComponentName(): string { + return 'BrowseByMetadataPageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./browse-by-metadata-page.component`); + } +} diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 85693ccecd..5320d7bb48 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -9,7 +9,6 @@ import { import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { BrowseService } from '../../core/browse/browse.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { PaginationService } from '../../core/pagination/pagination.service'; import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @@ -23,7 +22,6 @@ import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; /** * Component for browsing items by title (dc.title) */ -@rendersBrowseBy(BrowseByDataType.Title) export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { public constructor(protected route: ActivatedRoute, diff --git a/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts new file mode 100644 index 0000000000..4a1bcc0bc1 --- /dev/null +++ b/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts @@ -0,0 +1,29 @@ +import {Component} from '@angular/core'; +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { BrowseByTitlePageComponent } from './browse-by-title-page.component'; +import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; + +/** + * Themed wrapper for BrowseByTitlePageComponent + */ +@Component({ + selector: 'ds-themed-browse-by-title-page', + styleUrls: [], + templateUrl: '../../shared/theme-support/themed.component.html', +}) + +@rendersBrowseBy(BrowseByDataType.Title) +export class ThemedBrowseByTitlePageComponent + extends ThemedComponent { + protected getComponentName(): string { + return 'BrowseByTitlePageComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/browse-by/browse-by-title-page/browse-by-title-page.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./browse-by-title-page.component`); + } +} diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index e1dfaacea5..14e21f8b4c 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -7,12 +7,20 @@ import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date- import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component'; import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; +import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component'; +import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component'; +import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator BrowseByTitlePageComponent, BrowseByMetadataPageComponent, - BrowseByDatePageComponent + BrowseByDatePageComponent, + + ThemedBrowseByMetadataPageComponent, + ThemedBrowseByDatePageComponent, + ThemedBrowseByTitlePageComponent, + ]; @NgModule({ diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts new file mode 100644 index 0000000000..9fcf773350 --- /dev/null +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; + +@Component({ + selector: 'ds-browse-by-date-page', + // styleUrls: ['./browse-by-date-page.component.scss'], + styleUrls: ['../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss'], + // templateUrl: './browse-by-date-page.component.html' + templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' +}) + +/** + * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided + */ + +export class BrowseByDatePageComponent extends BaseComponent { +} diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts new file mode 100644 index 0000000000..9434ca936d --- /dev/null +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; + +@Component({ + selector: 'ds-browse-by-metadata-page', + // styleUrls: ['./browse-by-metadata-page.component.scss'], + styleUrls: ['../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss'], + // templateUrl: './browse-by-metadata-page.component.html' + templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' +}) + +/** + * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided + */ + +export class BrowseByMetadataPageComponent extends BaseComponent { +} diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts new file mode 100644 index 0000000000..503283a384 --- /dev/null +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; + +@Component({ + selector: 'ds-browse-by-title-page', + // styleUrls: ['./browse-by-date-page.component.scss'], + styleUrls: ['../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.scss'], + // templateUrl: './browse-by-date-page.component.html' + templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' +}) + +/** + * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided + */ + +export class BrowseByTitlePageComponent extends BaseComponent { +} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index acb3b1fa4a..dc6190f851 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -111,6 +111,10 @@ import { } from './app/community-page/sub-collection-list/community-page-sub-collection-list.component'; import { ObjectListComponent } from './app/shared/object-list/object-list.component'; +import { BrowseByMetadataPageComponent } from './app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; +import { BrowseByDatePageComponent } from './app/browse-by/browse-by-date-page/browse-by-date-page.component'; +import { BrowseByTitlePageComponent } from './app/browse-by/browse-by-title-page/browse-by-title-page.component'; + const DECLARATIONS = [ FileSectionComponent, HomePageComponent, @@ -161,6 +165,11 @@ const DECLARATIONS = [ SearchSettingsComponent ComcolPageBrowseByComponent, ObjectListComponent, + BrowseByMetadataPageComponent, + BrowseByDatePageComponent, + BrowseByTitlePageComponent, + + ]; @NgModule({ From 7c194ff70052fbb01ba2f3f13ee5f310e7de1263 Mon Sep 17 00:00:00 2001 From: Peter Wolfersberger Date: Fri, 12 Aug 2022 18:16:56 +0200 Subject: [PATCH 54/65] made search-navbar-component themeable --- src/app/header/header.component.html | 2 +- .../themed-search-navbar.component.ts | 24 +++++++++++++++++++ src/app/shared/shared.module.ts | 2 ++ .../search-navbar.component.html | 0 .../search-navbar.component.scss | 0 .../search-navbar/search-navbar.component.ts | 13 ++++++++++ src/themes/custom/eager-theme.module.ts | 2 ++ 7 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 src/app/search-navbar/themed-search-navbar.component.ts create mode 100644 src/themes/custom/app/search-navbar/search-navbar.component.html create mode 100644 src/themes/custom/app/search-navbar/search-navbar.component.scss create mode 100644 src/themes/custom/app/search-navbar/search-navbar.component.ts diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html index e5d5f38971..e2ee33c760 100644 --- a/src/app/header/header.component.html +++ b/src/app/header/header.component.html @@ -6,7 +6,7 @@