From 50e849dd44a0c92d857eb1325836cbbb560ddf28 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Fri, 10 Jun 2022 20:48:35 +0530 Subject: [PATCH 01/78] [UXP-10] reCAPTCHA angular component --- package.json | 1 + .../eperson-form/eperson-form.component.ts | 2 +- src/app/core/core.module.ts | 5 ++ .../core/data/eperson-registration.service.ts | 5 +- src/app/core/data/google-recaptcha.service.ts | 15 +++++ src/app/core/shared/registration.model.ts | 12 ++++ .../register-email-form.component.ts | 57 +++++++++++++++---- src/assets/i18n/en.json5 | 2 + src/config/build-config.interface.ts | 1 + src/environments/environment.production.ts | 3 +- src/environments/environment.ts | 3 +- yarn.lock | 13 +++++ 12 files changed, 105 insertions(+), 14 deletions(-) create mode 100644 src/app/core/data/google-recaptcha.service.ts diff --git a/package.json b/package.json index 32832460a2..7c81547574 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "moment": "^2.29.4", "morgan": "^1.10.0", "ng-mocks": "^13.1.1", + "ng-recaptcha": "^9.0.0", "ng2-file-upload": "1.4.0", "ng2-nouislider": "^1.8.3", "ngx-infinite-scroll": "^10.0.1", diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 05fc3189d0..0abcc3d3bc 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -491,7 +491,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { */ resetPassword() { if (hasValue(this.epersonInitial.email)) { - this.epersonRegistrationService.registerEmail(this.epersonInitial.email).pipe(getFirstCompletedRemoteData()) + this.epersonRegistrationService.registerEmail(this.epersonInitial.email, null).pipe(getFirstCompletedRemoteData()) .subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationsService.success(this.translateService.get('admin.access-control.epeople.actions.reset'), diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index b16930e819..1010840d76 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -178,6 +178,8 @@ import { OrcidHistoryDataService } from './orcid/orcid-history-data.service'; import { OrcidQueue } from './orcid/model/orcid-queue.model'; import { OrcidHistory } from './orcid/model/orcid-history.model'; import { OrcidAuthService } from './orcid/orcid-auth.service'; +import { GoogleRecaptchaService } from './data/google-recaptcha.service'; +import { RecaptchaV3Module, RECAPTCHA_V3_SITE_KEY } from 'ng-recaptcha'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -193,6 +195,7 @@ export const restServiceFactory = (mocks: ResponseMapMock, http: HttpClient) => const IMPORTS = [ CommonModule, + RecaptchaV3Module, StoreModule.forFeature('core', coreReducers, storeModuleConfig as StoreConfig), EffectsModule.forFeature(coreEffects) ]; @@ -309,6 +312,8 @@ const PROVIDERS = [ OrcidAuthService, OrcidQueueService, OrcidHistoryDataService, + GoogleRecaptchaService, + { provide: RECAPTCHA_V3_SITE_KEY, useValue: environment.recaptchaSiteKey } ]; /** diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 989a401733..3690b63c33 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -54,9 +54,12 @@ export class EpersonRegistrationService { * Register a new email address * @param email */ - registerEmail(email: string): Observable> { + registerEmail(email: string, captchaToken: string): Observable> { const registration = new Registration(); registration.email = email; + if (captchaToken) { + registration.captchaToken = captchaToken; + } const requestId = this.requestService.generateRequestId(); diff --git a/src/app/core/data/google-recaptcha.service.ts b/src/app/core/data/google-recaptcha.service.ts new file mode 100644 index 0000000000..1865b6ffbb --- /dev/null +++ b/src/app/core/data/google-recaptcha.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { ReCaptchaV3Service } from 'ng-recaptcha'; + +@Injectable() +export class GoogleRecaptchaService { + + constructor( + private recaptchaV3Service: ReCaptchaV3Service + ) {} + + public getRecaptchaToken (action) { + return this.recaptchaV3Service.execute(action); + } + +} diff --git a/src/app/core/shared/registration.model.ts b/src/app/core/shared/registration.model.ts index d679eec0ff..8521bb126e 100644 --- a/src/app/core/shared/registration.model.ts +++ b/src/app/core/shared/registration.model.ts @@ -25,5 +25,17 @@ export class Registration implements UnCacheableObject { * The token linked to the registration */ token: string; + /** + * The token linked to the registration + */ + groupNames: string[]; + /** + * The token linked to the registration + */ + groups: string[]; + /** + * The captcha token linked to the registration + */ + captchaToken: string; } diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index d40629f597..4fcc5e6650 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -6,6 +6,12 @@ import { Router } from '@angular/router'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { Registration } from '../core/shared/registration.model'; import { RemoteData } from '../core/data/remote-data'; +import { GoogleRecaptchaService } from '../core/data/google-recaptcha.service'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { ConfigurationProperty } from '../core/shared/configuration-property.model'; +import { isNotEmpty } from '../shared/empty.util'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-register-email-form', @@ -27,12 +33,19 @@ export class RegisterEmailFormComponent implements OnInit { @Input() MESSAGE_PREFIX: string; + /** + * registration verification configuration + */ + registrationVerification = false; + constructor( private epersonRegistrationService: EpersonRegistrationService, private notificationService: NotificationsService, private translateService: TranslateService, private router: Router, - private formBuilder: FormBuilder + private formBuilder: FormBuilder, + private configService: ConfigurationDataService, + private googleRecaptchaService: GoogleRecaptchaService ) { } @@ -45,7 +58,14 @@ export class RegisterEmailFormComponent implements OnInit { ], }) }); - + this.configService.findByPropertyName('registration.verification.enabled').pipe( + getFirstCompletedRemoteData(), + map((res: RemoteData) => { + return res.hasSucceeded && res.payload && isNotEmpty(res.payload.values) && res.payload.values[0].toLowerCase() === 'true'; + }) + ).subscribe((res: boolean) => { + this.registrationVerification = res; + }); } /** @@ -53,20 +73,37 @@ export class RegisterEmailFormComponent implements OnInit { */ register() { if (!this.form.invalid) { - this.epersonRegistrationService.registerEmail(this.email.value).subscribe((response: RemoteData) => { - if (response.hasSucceeded) { - this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); - this.router.navigate(['/home']); + if (this.registrationVerification) { + this.googleRecaptchaService.getRecaptchaToken('register_email').subscribe(res => { + if (isNotEmpty(res)) { + this.registeration(res); } else { this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); + this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`, {email: this.email.value})); } - } - ); + }); + } else { + this.registeration(null); + } } } + /** + * Register an email address + */ + registeration(captchaToken) { + this.epersonRegistrationService.registerEmail(this.email.value, captchaToken).subscribe((response: RemoteData) => { + if (response.hasSucceeded) { + this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); + this.router.navigate(['/home']); + } else { + this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); + } + }); + } + get email() { return this.form.get('email'); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 57863b3a69..7fd5fe1bcd 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3187,6 +3187,8 @@ "register-page.registration.error.content": "An error occured when registering the following email address: {{ email }}", + "register-page.registration.error.recaptcha": "Error when trying to authenticate with recaptcha", + "relationships.add.error.relationship-type.content": "No suitable match could be found for relationship type {{ type }} between the two items", diff --git a/src/config/build-config.interface.ts b/src/config/build-config.interface.ts index beb8097c9e..1bc4d2a917 100644 --- a/src/config/build-config.interface.ts +++ b/src/config/build-config.interface.ts @@ -3,4 +3,5 @@ import { UniversalConfig } from './universal-config.interface'; export interface BuildConfig extends AppConfig { universal: UniversalConfig; + recaptchaSiteKey: string; } diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index 09b5f19ade..baf7cca29b 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -8,5 +8,6 @@ export const environment: Partial = { preboot: true, async: true, time: false - } + }, + recaptchaSiteKey: '6LfmfEsgAAAAACNqQ0aHqJa0HOHcUsvv2OCiEbV4' }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index dc0e808be0..f3017f536a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -13,7 +13,8 @@ export const environment: Partial = { preboot: false, async: true, time: false - } + }, + recaptchaSiteKey: '6LfmfEsgAAAAACNqQ0aHqJa0HOHcUsvv2OCiEbV4' }; /* diff --git a/yarn.lock b/yarn.lock index 1996ace1b1..7780a764b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2247,6 +2247,11 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.5.tgz#9ee342a5d1314bb0928375424a2f162f97c310c7" integrity sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ== +"@types/grecaptcha@^3.0.3": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/grecaptcha/-/grecaptcha-3.0.4.tgz#3de601f3b0cd0298faf052dd5bd62aff64c2be2e" + integrity sha512-7l1Y8DTGXkx/r4pwU1nMVAR+yD/QC+MCHKXAyEX/7JZhwcN1IED09aZ9vCjjkcGdhSQiu/eJqcXInpl6eEEEwg== + "@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" @@ -8858,6 +8863,14 @@ ng-mocks@^13.1.1: resolved "https://registry.yarnpkg.com/ng-mocks/-/ng-mocks-13.1.1.tgz#e967dacc420c2ecec71826c5f24e0120186fad0b" integrity sha512-LYi/1ccDwHKLwi4/hvUsmxBDeQ+n8BTdg5f1ujCDCO7OM9OVnqkQcRlBHHK+5iFtEn/aqa2QG3xU/qwIhnaL4w== +ng-recaptcha@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/ng-recaptcha/-/ng-recaptcha-9.0.0.tgz#11b88f820cfca366d363fffd0f451490f2db2b04" + integrity sha512-39YfJh1+p6gvfsGUhC8cmjhFZ1TtQ1OJES5SUgnanPL2aQuwrSX4WyTFh2liFn1dQqtGUVd5O4EhbcexB7AECQ== + dependencies: + "@types/grecaptcha" "^3.0.3" + tslib "^2.2.0" + ng2-file-upload@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.4.0.tgz#8dea28d573234c52af474ad2a4001b335271e5c4" From 2532e370109178557b070aafeaf09bdc958c7b54 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Mon, 13 Jun 2022 18:43:50 +0530 Subject: [PATCH 02/78] [UXP-10] Test cases --- .../eperson-form.component.spec.ts | 2 +- .../data/eperson-registration.service.spec.ts | 2 +- .../register-email-form.component.spec.ts | 51 +++++++++++++++++-- .../register-email-form.component.ts | 10 ++-- src/environments/environment.test.ts | 2 + 5 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 4957958658..e2bae9a820 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -537,7 +537,7 @@ describe('EPersonFormComponent', () => { }); it('should call epersonRegistrationService.registerEmail', () => { - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail, null); }); }); }); diff --git a/src/app/core/data/eperson-registration.service.spec.ts b/src/app/core/data/eperson-registration.service.spec.ts index dc13fff3a0..48f5d3cfe5 100644 --- a/src/app/core/data/eperson-registration.service.spec.ts +++ b/src/app/core/data/eperson-registration.service.spec.ts @@ -78,7 +78,7 @@ describe('EpersonRegistrationService', () => { describe('registerEmail', () => { it('should send an email registration', () => { - const expected = service.registerEmail('test@mail.org'); + const expected = service.registerEmail('test@mail.org', null); expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration)); expect(expected).toBeObservable(cold('(a|)', { a: rd })); diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index a415ef4808..040af04f94 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -14,6 +14,8 @@ import { RouterStub } from '../shared/testing/router.stub'; import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub'; import { RegisterEmailFormComponent } from './register-email-form.component'; import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { GoogleRecaptchaService } from '../core/data/google-recaptcha.service'; describe('RegisterEmailComponent', () => { @@ -24,6 +26,17 @@ describe('RegisterEmailComponent', () => { let epersonRegistrationService: EpersonRegistrationService; let notificationsService; + const configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: jasmine.createSpy('findByPropertyName') + }); + + const googleRecaptchaService = jasmine.createSpyObj('googleRecaptchaService', { + getRecaptchaToken: jasmine.createSpy('getRecaptchaToken') + }); + + const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['true'] }); + const confResponseDisabled$ = createSuccessfulRemoteDataObject$({ values: ['false'] }); + beforeEach(waitForAsync(() => { router = new RouterStub(); @@ -39,8 +52,10 @@ describe('RegisterEmailComponent', () => { providers: [ {provide: Router, useValue: router}, {provide: EpersonRegistrationService, useValue: epersonRegistrationService}, + {provide: ConfigurationDataService, useValue: configurationDataService}, {provide: FormBuilder, useValue: new FormBuilder()}, {provide: NotificationsService, useValue: notificationsService}, + {provide: GoogleRecaptchaService, useValue: googleRecaptchaService}, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); @@ -48,6 +63,8 @@ describe('RegisterEmailComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(RegisterEmailFormComponent); comp = fixture.componentInstance; + configurationDataService.findByPropertyName.and.returnValues(confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$); + googleRecaptchaService.getRecaptchaToken.and.returnValue(observableOf('googleRecaptchaToken')); fixture.detectChanges(); }); @@ -71,21 +88,47 @@ describe('RegisterEmailComponent', () => { }); }); describe('register', () => { - it('should send a registration to the service and on success display a message and return to home', () => { + it('should send a registration to the service with google recaptcha and on success display a message and return to home', () => { comp.form.patchValue({email: 'valid@email.org'}); comp.register(); - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org'); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); expect(notificationsService.success).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(['/home']); }); - it('should send a registration to the service and on error display a message', () => { + it('should send a registration to the service with google recaptcha and on error display a message', () => { (epersonRegistrationService.registerEmail as jasmine.Spy).and.returnValue(observableOf(new RestResponse(false, 400, 'Bad Request'))); comp.form.patchValue({email: 'valid@email.org'}); comp.register(); - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org'); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); + expect(notificationsService.error).toHaveBeenCalled(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + }); + describe('register', () => { + beforeEach(waitForAsync(() => { + configurationDataService.findByPropertyName.and.returnValues(confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$); + comp.ngOnInit(); + fixture.detectChanges(); + })); + + it('should send a registration to the service without google recaptcha and on success display a message and return to home', () => { + comp.form.patchValue({email: 'valid@email.org'}); + + comp.register(); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', null); + expect(notificationsService.success).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalledWith(['/home']); + }); + it('should send a registration to the service without google recaptcha and on error display a message', () => { + (epersonRegistrationService.registerEmail as jasmine.Spy).and.returnValue(observableOf(new RestResponse(false, 400, 'Bad Request'))); + + comp.form.patchValue({email: 'valid@email.org'}); + + comp.register(); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', null); expect(notificationsService.error).toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled(); }); diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 4fcc5e6650..b8f8a57353 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -74,9 +74,9 @@ export class RegisterEmailFormComponent implements OnInit { register() { if (!this.form.invalid) { if (this.registrationVerification) { - this.googleRecaptchaService.getRecaptchaToken('register_email').subscribe(res => { - if (isNotEmpty(res)) { - this.registeration(res); + this.googleRecaptchaService.getRecaptchaToken('register_email').subscribe(captcha => { + if (isNotEmpty(captcha)) { + this.registeration(captcha); } else { this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`, {email: this.email.value})); @@ -91,7 +91,7 @@ export class RegisterEmailFormComponent implements OnInit { /** * Register an email address */ - registeration(captchaToken) { + registeration(captchaToken) { this.epersonRegistrationService.registerEmail(this.email.value, captchaToken).subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), @@ -102,7 +102,7 @@ export class RegisterEmailFormComponent implements OnInit { this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); } }); - } + } get email() { return this.form.get('email'); diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 4d466bd37b..d605320af9 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -13,6 +13,8 @@ export const environment: BuildConfig = { time: false }, + recaptchaSiteKey: '6LfmfEsgAAAAACNqQ0aHqJa0HOHcUsvv2OCiEbV4', + // Angular Universal server settings. ui: { ssl: false, From a3eb544422a2a39afd625879de87ce6ee11db252 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 14 Jun 2022 13:57:49 +0530 Subject: [PATCH 03/78] [UXP-10] Review fixes --- .../eperson-form.component.spec.ts | 2 +- .../eperson-form/eperson-form.component.ts | 2 +- .../data/eperson-registration.service.spec.ts | 2 +- .../core/data/eperson-registration.service.ts | 2 +- .../register-email-form.component.spec.ts | 22 ++++++------- .../register-email-form.component.ts | 33 +++++++++++++------ 6 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index e2bae9a820..4957958658 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -537,7 +537,7 @@ describe('EPersonFormComponent', () => { }); it('should call epersonRegistrationService.registerEmail', () => { - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail, null); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith(ePersonEmail); }); }); }); diff --git a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts index 0abcc3d3bc..05fc3189d0 100644 --- a/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -491,7 +491,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { */ resetPassword() { if (hasValue(this.epersonInitial.email)) { - this.epersonRegistrationService.registerEmail(this.epersonInitial.email, null).pipe(getFirstCompletedRemoteData()) + this.epersonRegistrationService.registerEmail(this.epersonInitial.email).pipe(getFirstCompletedRemoteData()) .subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationsService.success(this.translateService.get('admin.access-control.epeople.actions.reset'), diff --git a/src/app/core/data/eperson-registration.service.spec.ts b/src/app/core/data/eperson-registration.service.spec.ts index 48f5d3cfe5..dc13fff3a0 100644 --- a/src/app/core/data/eperson-registration.service.spec.ts +++ b/src/app/core/data/eperson-registration.service.spec.ts @@ -78,7 +78,7 @@ describe('EpersonRegistrationService', () => { describe('registerEmail', () => { it('should send an email registration', () => { - const expected = service.registerEmail('test@mail.org', null); + const expected = service.registerEmail('test@mail.org'); expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration)); expect(expected).toBeObservable(cold('(a|)', { a: rd })); diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 3690b63c33..b667d87e04 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -54,7 +54,7 @@ export class EpersonRegistrationService { * Register a new email address * @param email */ - registerEmail(email: string, captchaToken: string): Observable> { + registerEmail(email: string, captchaToken: string = null): Observable> { const registration = new Registration(); registration.email = email; if (captchaToken) { diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index 040af04f94..2d80b6d594 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -63,7 +63,7 @@ describe('RegisterEmailComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(RegisterEmailFormComponent); comp = fixture.componentInstance; - configurationDataService.findByPropertyName.and.returnValues(confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$); + configurationDataService.findByPropertyName.and.returnValues(confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$); googleRecaptchaService.getRecaptchaToken.and.returnValue(observableOf('googleRecaptchaToken')); fixture.detectChanges(); @@ -88,47 +88,47 @@ describe('RegisterEmailComponent', () => { }); }); describe('register', () => { - it('should send a registration to the service with google recaptcha and on success display a message and return to home', () => { + it('should send a registration to the service and on success display a message and return to home', () => { comp.form.patchValue({email: 'valid@email.org'}); comp.register(); - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org'); expect(notificationsService.success).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(['/home']); }); - it('should send a registration to the service with google recaptcha and on error display a message', () => { + it('should send a registration to the service and on error display a message', () => { (epersonRegistrationService.registerEmail as jasmine.Spy).and.returnValue(observableOf(new RestResponse(false, 400, 'Bad Request'))); comp.form.patchValue({email: 'valid@email.org'}); comp.register(); - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org'); expect(notificationsService.error).toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled(); }); }); - describe('register', () => { + describe('register with google recaptcha', () => { beforeEach(waitForAsync(() => { - configurationDataService.findByPropertyName.and.returnValues(confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$); + configurationDataService.findByPropertyName.and.returnValues(confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$); comp.ngOnInit(); fixture.detectChanges(); })); - it('should send a registration to the service without google recaptcha and on success display a message and return to home', () => { + it('should send a registration to the service and on success display a message and return to home', () => { comp.form.patchValue({email: 'valid@email.org'}); comp.register(); - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', null); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); expect(notificationsService.success).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(['/home']); }); - it('should send a registration to the service without google recaptcha and on error display a message', () => { + it('should send a registration to the service and on error display a message', () => { (epersonRegistrationService.registerEmail as jasmine.Spy).and.returnValue(observableOf(new RestResponse(false, 400, 'Bad Request'))); comp.form.patchValue({email: 'valid@email.org'}); comp.register(); - expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', null); + expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); expect(notificationsService.error).toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled(); }); diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index b8f8a57353..b124ed2a8c 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -92,16 +92,29 @@ export class RegisterEmailFormComponent implements OnInit { * Register an email address */ registeration(captchaToken) { - this.epersonRegistrationService.registerEmail(this.email.value, captchaToken).subscribe((response: RemoteData) => { - if (response.hasSucceeded) { - this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); - this.router.navigate(['/home']); - } else { - this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); - } - }); + if (captchaToken) { + this.epersonRegistrationService.registerEmail(this.email.value, captchaToken).subscribe((response: RemoteData) => { + if (response.hasSucceeded) { + this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); + this.router.navigate(['/home']); + } else { + this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); + } + }); + } else { + this.epersonRegistrationService.registerEmail(this.email.value).subscribe((response: RemoteData) => { + if (response.hasSucceeded) { + this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); + this.router.navigate(['/home']); + } else { + this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); + } + }); + } } get email() { From 0783cd5cb67b214cc2824d1aebe4176eab9bdc4e Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 14 Jun 2022 14:36:06 +0530 Subject: [PATCH 04/78] [UXP-10] compacting code --- .../register-email-form.component.ts | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index b124ed2a8c..1752865248 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -92,29 +92,20 @@ export class RegisterEmailFormComponent implements OnInit { * Register an email address */ registeration(captchaToken) { + let a = this.epersonRegistrationService.registerEmail(this.email.value); if (captchaToken) { - this.epersonRegistrationService.registerEmail(this.email.value, captchaToken).subscribe((response: RemoteData) => { - if (response.hasSucceeded) { - this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); - this.router.navigate(['/home']); - } else { - this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); - } - }); - } else { - this.epersonRegistrationService.registerEmail(this.email.value).subscribe((response: RemoteData) => { - if (response.hasSucceeded) { - this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); - this.router.navigate(['/home']); - } else { - this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); - } - }); + a = this.epersonRegistrationService.registerEmail(this.email.value, captchaToken); } + a.subscribe((response: RemoteData) => { + if (response.hasSucceeded) { + this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); + this.router.navigate(['/home']); + } else { + this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.error.content`, {email: this.email.value})); + } + }); } get email() { From 4906516359dadb24b7309021c6bb5a6d64414ddf Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 14 Jun 2022 20:53:20 +0530 Subject: [PATCH 05/78] [UXP-10] typdocs and tests --- .../data/google-recaptcha.service.spec.ts | 32 +++++++++++++++++++ src/app/core/data/google-recaptcha.service.ts | 7 ++++ 2 files changed, 39 insertions(+) create mode 100644 src/app/core/data/google-recaptcha.service.spec.ts diff --git a/src/app/core/data/google-recaptcha.service.spec.ts b/src/app/core/data/google-recaptcha.service.spec.ts new file mode 100644 index 0000000000..bc13e3e494 --- /dev/null +++ b/src/app/core/data/google-recaptcha.service.spec.ts @@ -0,0 +1,32 @@ +import { GoogleRecaptchaService } from './google-recaptcha.service'; +import { of as observableOf } from 'rxjs'; + +describe('GoogleRecaptchaService', () => { + let service: GoogleRecaptchaService; + + let reCaptchaV3Service; + + function init() { + reCaptchaV3Service = jasmine.createSpyObj('reCaptchaV3Service', { + execute: observableOf('googleRecaptchaToken') + }); + service = new GoogleRecaptchaService(reCaptchaV3Service); + } + + beforeEach(() => { + init(); + }); + + describe('getRecaptchaToken', () => { + let result; + + beforeEach(() => { + result = service.getRecaptchaToken('test'); + }); + + it('should send a Request with action', () => { + expect(reCaptchaV3Service.execute).toHaveBeenCalledWith('test'); + }); + + }); +}); diff --git a/src/app/core/data/google-recaptcha.service.ts b/src/app/core/data/google-recaptcha.service.ts index 1865b6ffbb..edf559d501 100644 --- a/src/app/core/data/google-recaptcha.service.ts +++ b/src/app/core/data/google-recaptcha.service.ts @@ -1,6 +1,9 @@ import { Injectable } from '@angular/core'; import { ReCaptchaV3Service } from 'ng-recaptcha'; +/** + * A GoogleRecaptchaService used to send action and get a token from REST + */ @Injectable() export class GoogleRecaptchaService { @@ -8,6 +11,10 @@ export class GoogleRecaptchaService { private recaptchaV3Service: ReCaptchaV3Service ) {} + /** + * Returns an observable of string + * @param action action is the process type in which used to protect multiple spam REST calls + */ public getRecaptchaToken (action) { return this.recaptchaV3Service.execute(action); } From 095380686597710eb13262a1705842376346db8e Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 16 Jun 2022 18:43:41 +0530 Subject: [PATCH 06/78] script insert and test cases with dynamic site key --- package.json | 2 +- src/app/core/core.module.ts | 5 -- .../data/google-recaptcha.service.spec.ts | 32 --------- src/app/core/data/google-recaptcha.service.ts | 22 ------ .../google-recaptcha.module.ts | 20 ++++++ .../google-recaptcha.service.spec.ts | 47 +++++++++++++ .../google-recaptcha.service.ts | 70 +++++++++++++++++++ .../register-email-form.component.spec.ts | 20 +++--- .../register-email-form.component.ts | 31 ++++---- .../register-email-form.module.ts | 2 + tsconfig.app.json | 2 +- tsconfig.server.json | 3 +- tsconfig.spec.json | 3 +- yarn.lock | 10 +-- 14 files changed, 172 insertions(+), 97 deletions(-) delete mode 100644 src/app/core/data/google-recaptcha.service.spec.ts delete mode 100644 src/app/core/data/google-recaptcha.service.ts create mode 100644 src/app/core/google-recaptcha/google-recaptcha.module.ts create mode 100644 src/app/core/google-recaptcha/google-recaptcha.service.spec.ts create mode 100644 src/app/core/google-recaptcha/google-recaptcha.service.ts diff --git a/package.json b/package.json index 7c81547574..31cc8d7b23 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@nguniversal/express-engine": "^13.0.2", "@ngx-translate/core": "^13.0.0", "@nicky-lenaers/ngx-scroll-to": "^9.0.0", + "@types/grecaptcha": "^3.0.4", "angular-idle-preload": "3.0.0", "angulartics2": "^12.0.0", "axios": "^0.27.2", @@ -110,7 +111,6 @@ "moment": "^2.29.4", "morgan": "^1.10.0", "ng-mocks": "^13.1.1", - "ng-recaptcha": "^9.0.0", "ng2-file-upload": "1.4.0", "ng2-nouislider": "^1.8.3", "ngx-infinite-scroll": "^10.0.1", diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 1010840d76..b16930e819 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -178,8 +178,6 @@ import { OrcidHistoryDataService } from './orcid/orcid-history-data.service'; import { OrcidQueue } from './orcid/model/orcid-queue.model'; import { OrcidHistory } from './orcid/model/orcid-history.model'; import { OrcidAuthService } from './orcid/orcid-auth.service'; -import { GoogleRecaptchaService } from './data/google-recaptcha.service'; -import { RecaptchaV3Module, RECAPTCHA_V3_SITE_KEY } from 'ng-recaptcha'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -195,7 +193,6 @@ export const restServiceFactory = (mocks: ResponseMapMock, http: HttpClient) => const IMPORTS = [ CommonModule, - RecaptchaV3Module, StoreModule.forFeature('core', coreReducers, storeModuleConfig as StoreConfig), EffectsModule.forFeature(coreEffects) ]; @@ -312,8 +309,6 @@ const PROVIDERS = [ OrcidAuthService, OrcidQueueService, OrcidHistoryDataService, - GoogleRecaptchaService, - { provide: RECAPTCHA_V3_SITE_KEY, useValue: environment.recaptchaSiteKey } ]; /** diff --git a/src/app/core/data/google-recaptcha.service.spec.ts b/src/app/core/data/google-recaptcha.service.spec.ts deleted file mode 100644 index bc13e3e494..0000000000 --- a/src/app/core/data/google-recaptcha.service.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { GoogleRecaptchaService } from './google-recaptcha.service'; -import { of as observableOf } from 'rxjs'; - -describe('GoogleRecaptchaService', () => { - let service: GoogleRecaptchaService; - - let reCaptchaV3Service; - - function init() { - reCaptchaV3Service = jasmine.createSpyObj('reCaptchaV3Service', { - execute: observableOf('googleRecaptchaToken') - }); - service = new GoogleRecaptchaService(reCaptchaV3Service); - } - - beforeEach(() => { - init(); - }); - - describe('getRecaptchaToken', () => { - let result; - - beforeEach(() => { - result = service.getRecaptchaToken('test'); - }); - - it('should send a Request with action', () => { - expect(reCaptchaV3Service.execute).toHaveBeenCalledWith('test'); - }); - - }); -}); diff --git a/src/app/core/data/google-recaptcha.service.ts b/src/app/core/data/google-recaptcha.service.ts deleted file mode 100644 index edf559d501..0000000000 --- a/src/app/core/data/google-recaptcha.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Injectable } from '@angular/core'; -import { ReCaptchaV3Service } from 'ng-recaptcha'; - -/** - * A GoogleRecaptchaService used to send action and get a token from REST - */ -@Injectable() -export class GoogleRecaptchaService { - - constructor( - private recaptchaV3Service: ReCaptchaV3Service - ) {} - - /** - * Returns an observable of string - * @param action action is the process type in which used to protect multiple spam REST calls - */ - public getRecaptchaToken (action) { - return this.recaptchaV3Service.execute(action); - } - -} diff --git a/src/app/core/google-recaptcha/google-recaptcha.module.ts b/src/app/core/google-recaptcha/google-recaptcha.module.ts new file mode 100644 index 0000000000..e2acba0e93 --- /dev/null +++ b/src/app/core/google-recaptcha/google-recaptcha.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '../../shared/shared.module'; + +import { GoogleRecaptchaService } from './google-recaptcha.service'; + +const PROVIDERS = [ + GoogleRecaptchaService +]; + +@NgModule({ + imports: [ SharedModule ], + providers: [ + ...PROVIDERS + ] +}) + +/** + * This module handles google recaptcha functionalities + */ +export class GoogleRecaptchaModule {} diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts b/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts new file mode 100644 index 0000000000..3698306763 --- /dev/null +++ b/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts @@ -0,0 +1,47 @@ +import { GoogleRecaptchaService } from './google-recaptcha.service'; +import { of as observableOf } from 'rxjs'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; + +describe('GoogleRecaptchaService', () => { + let service: GoogleRecaptchaService; + + let rendererFactory2; + let configurationDataService; + let spy: jasmine.Spy; + let scriptElementMock: any; + const innerHTMLTestValue = 'mock-script-inner-html'; + const document = { documentElement: { lang: 'en' } } as Document; + scriptElementMock = { + set innerHTML(newVal) { /* noop */ }, + get innerHTML() { return innerHTMLTestValue; } + }; + + function init() { + rendererFactory2 = jasmine.createSpyObj('rendererFactory2', { + createRenderer: observableOf('googleRecaptchaToken'), + createElement: scriptElementMock + }); + configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$({ values: ['googleRecaptchaToken'] }) + }); + service = new GoogleRecaptchaService(document, rendererFactory2, configurationDataService); + } + + beforeEach(() => { + init(); + }); + + describe('getRecaptchaToken', () => { + let result; + + beforeEach(() => { + spy = spyOn(service, 'getRecaptchaToken').and.stub(); + }); + + it('should send a Request with action', () => { + service.getRecaptchaToken('test'); + expect(spy).toHaveBeenCalledWith('test'); + }); + + }); +}); diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts new file mode 100644 index 0000000000..168e385597 --- /dev/null +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -0,0 +1,70 @@ +import { Inject, Injectable, Renderer2, RendererFactory2 } from '@angular/core'; +import { getFirstCompletedRemoteData } from '../shared/operators'; +import { ConfigurationProperty } from '../shared/configuration-property.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { DOCUMENT } from '@angular/common'; +import { ConfigurationDataService } from '../data/configuration-data.service'; +import { RemoteData } from '../data/remote-data'; + +/** + * A GoogleRecaptchaService used to send action and get a token from REST + */ +@Injectable() +export class GoogleRecaptchaService { + + private renderer: Renderer2; + captchaSiteKey: string; + + constructor( + @Inject(DOCUMENT) private _document: Document, + rendererFactory: RendererFactory2, + private configService: ConfigurationDataService, + ) { + this.renderer = rendererFactory.createRenderer(null, null); + this.configService.findByPropertyName('google.recaptcha.key').pipe( + getFirstCompletedRemoteData(), + ).subscribe((res: RemoteData) => { + if (res.hasSucceeded && isNotEmpty(res?.payload?.values[0])) { + this.captchaSiteKey = res?.payload?.values[0]; + this.loadScript(this.buildCaptchaUrl(res?.payload?.values[0])); + } + }); + } + + /** + * Returns an observable of string + * @param action action is the process type in which used to protect multiple spam REST calls + */ + public async getRecaptchaToken (action) { + return await grecaptcha.execute(this.captchaSiteKey, {action: action}); + } + + /** + * Return the google captcha ur with google captchas api key + * + * @param key contains a secret key of a google captchas + * @returns string which has google captcha url with google captchas key + */ + buildCaptchaUrl(key: string) { + return `https://www.google.com/recaptcha/api.js?render=${key}`; + } + + /** + * Append the google captchas script to the document + * + * @param url contains a script url which will be loaded into page + * @returns A promise + */ + private loadScript(url) { + return new Promise((resolve, reject) => { + const script = this.renderer.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.text = ``; + script.onload = resolve; + script.onerror = reject; + this.renderer.appendChild(this._document.head, script); + }); + } + +} diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index 2d80b6d594..330933426a 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -1,4 +1,4 @@ -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { waitForAsync, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; import { of as observableOf } from 'rxjs'; import { RestResponse } from '../core/cache/response.models'; import { CommonModule } from '@angular/common'; @@ -15,7 +15,7 @@ import { NotificationsServiceStub } from '../shared/testing/notifications-servic import { RegisterEmailFormComponent } from './register-email-form.component'; import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; -import { GoogleRecaptchaService } from '../core/data/google-recaptcha.service'; +import { GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; describe('RegisterEmailComponent', () => { @@ -31,7 +31,7 @@ describe('RegisterEmailComponent', () => { }); const googleRecaptchaService = jasmine.createSpyObj('googleRecaptchaService', { - getRecaptchaToken: jasmine.createSpy('getRecaptchaToken') + getRecaptchaToken: Promise.resolve('googleRecaptchaToken') }); const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['true'] }); @@ -64,7 +64,6 @@ describe('RegisterEmailComponent', () => { fixture = TestBed.createComponent(RegisterEmailFormComponent); comp = fixture.componentInstance; configurationDataService.findByPropertyName.and.returnValues(confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$); - googleRecaptchaService.getRecaptchaToken.and.returnValue(observableOf('googleRecaptchaToken')); fixture.detectChanges(); }); @@ -108,29 +107,30 @@ describe('RegisterEmailComponent', () => { }); }); describe('register with google recaptcha', () => { - beforeEach(waitForAsync(() => { + beforeEach(fakeAsync(() => { configurationDataService.findByPropertyName.and.returnValues(confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$); comp.ngOnInit(); fixture.detectChanges(); })); - it('should send a registration to the service and on success display a message and return to home', () => { + it('should send a registration to the service and on success display a message and return to home', fakeAsync(() => { comp.form.patchValue({email: 'valid@email.org'}); - comp.register(); + tick(); expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); expect(notificationsService.success).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(['/home']); - }); - it('should send a registration to the service and on error display a message', () => { + })); + it('should send a registration to the service and on error display a message', fakeAsync(() => { (epersonRegistrationService.registerEmail as jasmine.Spy).and.returnValue(observableOf(new RestResponse(false, 400, 'Bad Request'))); comp.form.patchValue({email: 'valid@email.org'}); comp.register(); + tick(); expect(epersonRegistrationService.registerEmail).toHaveBeenCalledWith('valid@email.org', 'googleRecaptchaToken'); expect(notificationsService.error).toHaveBeenCalled(); expect(router.navigate).not.toHaveBeenCalled(); - }); + })); }); }); diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 1752865248..c53fbd993d 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -6,12 +6,12 @@ import { Router } from '@angular/router'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { Registration } from '../core/shared/registration.model'; import { RemoteData } from '../core/data/remote-data'; -import { GoogleRecaptchaService } from '../core/data/google-recaptcha.service'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { getFirstCompletedRemoteData } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; import { map } from 'rxjs/operators'; +import { GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; @Component({ selector: 'ds-register-email-form', @@ -71,19 +71,18 @@ export class RegisterEmailFormComponent implements OnInit { /** * Register an email address */ - register() { + async register() { if (!this.form.invalid) { if (this.registrationVerification) { - this.googleRecaptchaService.getRecaptchaToken('register_email').subscribe(captcha => { - if (isNotEmpty(captcha)) { - this.registeration(captcha); - } else { - this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`, {email: this.email.value})); - } - }); + const token = await this.googleRecaptchaService.getRecaptchaToken('register_email'); + if (isNotEmpty(token)) { + this.registeration(token); + } else { + this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`, {email: this.email.value})); + } } else { - this.registeration(null); + this.registeration(); } } } @@ -91,12 +90,14 @@ export class RegisterEmailFormComponent implements OnInit { /** * Register an email address */ - registeration(captchaToken) { - let a = this.epersonRegistrationService.registerEmail(this.email.value); + registeration(captchaToken = null) { + let registerEmail$; if (captchaToken) { - a = this.epersonRegistrationService.registerEmail(this.email.value, captchaToken); + registerEmail$ = this.epersonRegistrationService.registerEmail(this.email.value, captchaToken); + } else { + registerEmail$ = this.epersonRegistrationService.registerEmail(this.email.value); } - a.subscribe((response: RemoteData) => { + registerEmail$.subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), this.translateService.get(`${this.MESSAGE_PREFIX}.success.content`, {email: this.email.value})); diff --git a/src/app/register-email-form/register-email-form.module.ts b/src/app/register-email-form/register-email-form.module.ts index f19e869beb..18c2830ff4 100644 --- a/src/app/register-email-form/register-email-form.module.ts +++ b/src/app/register-email-form/register-email-form.module.ts @@ -2,11 +2,13 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '../shared/shared.module'; import { RegisterEmailFormComponent } from './register-email-form.component'; +import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module'; @NgModule({ imports: [ CommonModule, SharedModule, + GoogleRecaptchaModule ], declarations: [ RegisterEmailFormComponent, diff --git a/tsconfig.app.json b/tsconfig.app.json index f67e8b25d8..c89834db4a 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": ["grecaptcha"] }, "files": [ "src/main.browser.ts", diff --git a/tsconfig.server.json b/tsconfig.server.json index 76be339fca..d2fb2c9d40 100644 --- a/tsconfig.server.json +++ b/tsconfig.server.json @@ -4,7 +4,8 @@ "outDir": "./out-tsc/app-server", "target": "es2016", "types": [ - "node" + "node", + "grecaptcha" ] }, "files": [ diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 05d55a31f8..0d92677545 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -5,7 +5,8 @@ "outDir": "./out-tsc/spec", "types": [ "jasmine", - "node" + "node", + "grecaptcha" ] }, "files": [ diff --git a/yarn.lock b/yarn.lock index 7780a764b6..1c262f83d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2247,7 +2247,7 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.5.tgz#9ee342a5d1314bb0928375424a2f162f97c310c7" integrity sha512-zv9kNf3keYegP5oThGLaPk8E081DFDuwfqjtiTzm6PoxChdJ1raSuADf2YGCVIyrSynLrgc8JWv296s7Q7pQSQ== -"@types/grecaptcha@^3.0.3": +"@types/grecaptcha@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/grecaptcha/-/grecaptcha-3.0.4.tgz#3de601f3b0cd0298faf052dd5bd62aff64c2be2e" integrity sha512-7l1Y8DTGXkx/r4pwU1nMVAR+yD/QC+MCHKXAyEX/7JZhwcN1IED09aZ9vCjjkcGdhSQiu/eJqcXInpl6eEEEwg== @@ -8863,14 +8863,6 @@ ng-mocks@^13.1.1: resolved "https://registry.yarnpkg.com/ng-mocks/-/ng-mocks-13.1.1.tgz#e967dacc420c2ecec71826c5f24e0120186fad0b" integrity sha512-LYi/1ccDwHKLwi4/hvUsmxBDeQ+n8BTdg5f1ujCDCO7OM9OVnqkQcRlBHHK+5iFtEn/aqa2QG3xU/qwIhnaL4w== -ng-recaptcha@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/ng-recaptcha/-/ng-recaptcha-9.0.0.tgz#11b88f820cfca366d363fffd0f451490f2db2b04" - integrity sha512-39YfJh1+p6gvfsGUhC8cmjhFZ1TtQ1OJES5SUgnanPL2aQuwrSX4WyTFh2liFn1dQqtGUVd5O4EhbcexB7AECQ== - dependencies: - "@types/grecaptcha" "^3.0.3" - tslib "^2.2.0" - ng2-file-upload@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.4.0.tgz#8dea28d573234c52af474ad2a4001b335271e5c4" From 2ef701a231f37edad67c696a39b725f87c63b231 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 16 Jun 2022 18:46:41 +0530 Subject: [PATCH 07/78] remove site key from environment --- src/config/build-config.interface.ts | 1 - src/environments/environment.production.ts | 3 +-- src/environments/environment.test.ts | 2 -- src/environments/environment.ts | 3 +-- 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/config/build-config.interface.ts b/src/config/build-config.interface.ts index 1bc4d2a917..beb8097c9e 100644 --- a/src/config/build-config.interface.ts +++ b/src/config/build-config.interface.ts @@ -3,5 +3,4 @@ import { UniversalConfig } from './universal-config.interface'; export interface BuildConfig extends AppConfig { universal: UniversalConfig; - recaptchaSiteKey: string; } diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts index baf7cca29b..09b5f19ade 100644 --- a/src/environments/environment.production.ts +++ b/src/environments/environment.production.ts @@ -8,6 +8,5 @@ export const environment: Partial = { preboot: true, async: true, time: false - }, - recaptchaSiteKey: '6LfmfEsgAAAAACNqQ0aHqJa0HOHcUsvv2OCiEbV4' + } }; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index d605320af9..4d466bd37b 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -13,8 +13,6 @@ export const environment: BuildConfig = { time: false }, - recaptchaSiteKey: '6LfmfEsgAAAAACNqQ0aHqJa0HOHcUsvv2OCiEbV4', - // Angular Universal server settings. ui: { ssl: false, diff --git a/src/environments/environment.ts b/src/environments/environment.ts index f3017f536a..dc0e808be0 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -13,8 +13,7 @@ export const environment: Partial = { preboot: false, async: true, time: false - }, - recaptchaSiteKey: '6LfmfEsgAAAAACNqQ0aHqJa0HOHcUsvv2OCiEbV4' + } }; /* From fcad492a25a5f31492a3d36e54d7da180deae95a Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 7 Jul 2022 14:38:10 +0530 Subject: [PATCH 08/78] [UXP-10] token passing in header --- .../data/eperson-registration.service.spec.ts | 19 ++++++++++++- .../core/data/eperson-registration.service.ts | 14 +++++++--- .../google-recaptcha.service.ts | 28 ++++++++++++++----- src/app/core/shared/registration.model.ts | 5 ---- .../register-email-form.component.ts | 8 +++--- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/app/core/data/eperson-registration.service.spec.ts b/src/app/core/data/eperson-registration.service.spec.ts index dc13fff3a0..c7785302ef 100644 --- a/src/app/core/data/eperson-registration.service.spec.ts +++ b/src/app/core/data/eperson-registration.service.spec.ts @@ -9,6 +9,8 @@ import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils import { of as observableOf } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; import { RequestEntry } from './request-entry.model'; +import { HttpHeaders } from '@angular/common/http'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; describe('EpersonRegistrationService', () => { let testScheduler; @@ -79,8 +81,23 @@ describe('EpersonRegistrationService', () => { it('should send an email registration', () => { const expected = service.registerEmail('test@mail.org'); + let headers = new HttpHeaders(); + const options: HttpOptions = Object.create({}); + options.headers = headers; - expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration)); + expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration, options)); + expect(expected).toBeObservable(cold('(a|)', { a: rd })); + }); + + it('should send an email registration with captcha', () => { + + const expected = service.registerEmail('test@mail.org', 'afreshcaptchatoken'); + let headers = new HttpHeaders(); + const options: HttpOptions = Object.create({}); + headers = headers.append('X-Recaptcha-Token', 'afreshcaptchatoken'); + options.headers = headers; + + expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration, options)); expect(expected).toBeObservable(cold('(a|)', { a: rd })); }); }); diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index b667d87e04..6f64879e0b 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -12,6 +12,8 @@ import { GenericConstructor } from '../shared/generic-constructor'; import { RegistrationResponseParsingService } from './registration-response-parsing.service'; import { RemoteData } from './remote-data'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { HttpHeaders } from '@angular/common/http'; @Injectable( { @@ -57,18 +59,22 @@ export class EpersonRegistrationService { registerEmail(email: string, captchaToken: string = null): Observable> { const registration = new Registration(); registration.email = email; - if (captchaToken) { - registration.captchaToken = captchaToken; - } const requestId = this.requestService.generateRequestId(); const href$ = this.getRegistrationEndpoint(); + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + if (captchaToken) { + headers = headers.append('X-Recaptcha-Token', captchaToken); + } + options.headers = headers; + href$.pipe( find((href: string) => hasValue(href)), map((href: string) => { - const request = new PostRequest(requestId, href, registration); + const request = new PostRequest(requestId, href, registration, options); this.requestService.send(request); }) ).subscribe(); diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts index 168e385597..46611e5ebb 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -5,6 +5,8 @@ import { isNotEmpty } from '../../shared/empty.util'; import { DOCUMENT } from '@angular/common'; import { ConfigurationDataService } from '../data/configuration-data.service'; import { RemoteData } from '../data/remote-data'; +import { map } from 'rxjs/operators'; +import { combineLatest } from 'rxjs'; /** * A GoogleRecaptchaService used to send action and get a token from REST @@ -13,6 +15,9 @@ import { RemoteData } from '../data/remote-data'; export class GoogleRecaptchaService { private renderer: Renderer2; + /** + * A Google Recaptcha site key + */ captchaSiteKey: string; constructor( @@ -21,12 +26,21 @@ export class GoogleRecaptchaService { private configService: ConfigurationDataService, ) { this.renderer = rendererFactory.createRenderer(null, null); - this.configService.findByPropertyName('google.recaptcha.key').pipe( + const registrationVerification$ = this.configService.findByPropertyName('registration.verification.enabled').pipe( getFirstCompletedRemoteData(), - ).subscribe((res: RemoteData) => { - if (res.hasSucceeded && isNotEmpty(res?.payload?.values[0])) { - this.captchaSiteKey = res?.payload?.values[0]; - this.loadScript(this.buildCaptchaUrl(res?.payload?.values[0])); + map((res: RemoteData) => { + return res.hasSucceeded && res.payload && isNotEmpty(res.payload.values) && res.payload.values[0].toLowerCase() === 'true'; + }) + ); + const recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( + getFirstCompletedRemoteData(), + ); + combineLatest(registrationVerification$, recaptchaKey$).subscribe(([registrationVerification, recaptchaKey]) => { + if (registrationVerification) { + if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { + this.captchaSiteKey = recaptchaKey?.payload?.values[0]; + this.loadScript(this.buildCaptchaUrl(recaptchaKey?.payload?.values[0])); + } } }); } @@ -45,7 +59,7 @@ export class GoogleRecaptchaService { * @param key contains a secret key of a google captchas * @returns string which has google captcha url with google captchas key */ - buildCaptchaUrl(key: string) { + buildCaptchaUrl(key: string) { return `https://www.google.com/recaptcha/api.js?render=${key}`; } @@ -55,7 +69,7 @@ export class GoogleRecaptchaService { * @param url contains a script url which will be loaded into page * @returns A promise */ - private loadScript(url) { + private loadScript(url) { return new Promise((resolve, reject) => { const script = this.renderer.createElement('script'); script.type = 'text/javascript'; diff --git a/src/app/core/shared/registration.model.ts b/src/app/core/shared/registration.model.ts index 8521bb126e..bc4488964f 100644 --- a/src/app/core/shared/registration.model.ts +++ b/src/app/core/shared/registration.model.ts @@ -33,9 +33,4 @@ export class Registration implements UnCacheableObject { * The token linked to the registration */ groups: string[]; - - /** - * The captcha token linked to the registration - */ - captchaToken: string; } diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index c53fbd993d..04081dc7ff 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -76,21 +76,21 @@ export class RegisterEmailFormComponent implements OnInit { if (this.registrationVerification) { const token = await this.googleRecaptchaService.getRecaptchaToken('register_email'); if (isNotEmpty(token)) { - this.registeration(token); + this.registration(token); } else { this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`, {email: this.email.value})); } } else { - this.registeration(); + this.registration(); } } } /** - * Register an email address + * Registration of an email address */ - registeration(captchaToken = null) { + registration(captchaToken = null) { let registerEmail$; if (captchaToken) { registerEmail$ = this.epersonRegistrationService.registerEmail(this.email.value, captchaToken); From e295dccc8a884083030d34d613c832da40598595 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 16 Aug 2022 20:38:48 +0530 Subject: [PATCH 09/78] [UXP-10] dynamic recaptcha versions and modes --- src/app/app.component.ts | 5 +- .../google-recaptcha.module.ts | 16 +++-- .../google-recaptcha.service.ts | 70 +++++++++++++++++-- .../register-email-form.component.html | 6 +- .../register-email-form.component.spec.ts | 12 +++- .../register-email-form.component.ts | 44 ++++++++++-- .../register-email-form.module.ts | 4 +- src/app/shared/cookies/klaro-configuration.ts | 10 +++ .../google-recaptcha.component.html | 4 ++ .../google-recaptcha.component.scss | 0 .../google-recaptcha.component.spec.ts | 50 +++++++++++++ .../google-recaptcha.component.ts | 45 ++++++++++++ src/app/shared/shared.module.ts | 4 +- src/assets/i18n/en.json5 | 17 +++++ 14 files changed, 262 insertions(+), 25 deletions(-) create mode 100644 src/app/shared/google-recaptcha/google-recaptcha.component.html create mode 100644 src/app/shared/google-recaptcha/google-recaptcha.component.scss create mode 100644 src/app/shared/google-recaptcha/google-recaptcha.component.spec.ts create mode 100644 src/app/shared/google-recaptcha/google-recaptcha.component.ts diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ee8c4d685f..68c1bce6f3 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -48,8 +48,8 @@ import { BASE_THEME_NAME } from './shared/theme-support/theme.constants'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; -import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; -import { ModalBeforeDismiss } from './shared/interfaces/modal-before-dismiss.interface'; +import { AppConfig, APP_CONFIG } from '../config/app-config.interface'; +import { GoogleRecaptchaService } from './core/google-recaptcha/google-recaptcha.service'; @Component({ selector: 'ds-app', @@ -110,6 +110,7 @@ export class AppComponent implements OnInit, AfterViewInit { private modalConfig: NgbModalConfig, @Optional() private cookiesService: KlaroService, @Optional() private googleAnalyticsService: GoogleAnalyticsService, + @Optional() private googleRecaptchaService: GoogleRecaptchaService, ) { if (!isEqual(environment, this.appConfig)) { diff --git a/src/app/core/google-recaptcha/google-recaptcha.module.ts b/src/app/core/google-recaptcha/google-recaptcha.module.ts index e2acba0e93..8af9adb641 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.module.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.module.ts @@ -1,17 +1,23 @@ +import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { GoogleRecaptchaComponent } from '../../shared/google-recaptcha/google-recaptcha.component'; import { SharedModule } from '../../shared/shared.module'; import { GoogleRecaptchaService } from './google-recaptcha.service'; const PROVIDERS = [ - GoogleRecaptchaService + GoogleRecaptchaService +]; + +const COMPONENTS = [ + GoogleRecaptchaComponent ]; @NgModule({ - imports: [ SharedModule ], - providers: [ - ...PROVIDERS - ] + imports: [ CommonModule ], + providers: [...PROVIDERS], + declarations: [...COMPONENTS], + exports: [...COMPONENTS] }) /** diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts index 46611e5ebb..99311247f6 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -5,8 +5,10 @@ import { isNotEmpty } from '../../shared/empty.util'; import { DOCUMENT } from '@angular/common'; import { ConfigurationDataService } from '../data/configuration-data.service'; import { RemoteData } from '../data/remote-data'; -import { map } from 'rxjs/operators'; -import { combineLatest } from 'rxjs'; +import { map, take } from 'rxjs/operators'; +import { combineLatest, Observable, of } from 'rxjs'; + +export const CAPTCHA_COOKIE = '_GRECAPTCHA'; /** * A GoogleRecaptchaService used to send action and get a token from REST @@ -18,7 +20,22 @@ export class GoogleRecaptchaService { /** * A Google Recaptcha site key */ - captchaSiteKey: string; + captchaSiteKeyStr: string; + + /** + * A Google Recaptcha site key + */ + captchaSiteKey$: Observable; + + /** + * A Google Recaptcha mode + */ + captchaMode$: Observable = of('invisible'); + + /** + * A Google Recaptcha version + */ + captchaVersion$: Observable; constructor( @Inject(DOCUMENT) private _document: Document, @@ -27,19 +44,43 @@ export class GoogleRecaptchaService { ) { this.renderer = rendererFactory.createRenderer(null, null); const registrationVerification$ = this.configService.findByPropertyName('registration.verification.enabled').pipe( + take(1), getFirstCompletedRemoteData(), map((res: RemoteData) => { return res.hasSucceeded && res.payload && isNotEmpty(res.payload.values) && res.payload.values[0].toLowerCase() === 'true'; }) ); const recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( + take(1), getFirstCompletedRemoteData(), ); - combineLatest(registrationVerification$, recaptchaKey$).subscribe(([registrationVerification, recaptchaKey]) => { + const recaptchaVersion$ = this.configService.findByPropertyName('google.recaptcha.version').pipe( + take(1), + getFirstCompletedRemoteData(), + ); + const recaptchaMode$ = this.configService.findByPropertyName('google.recaptcha.mode').pipe( + take(1), + getFirstCompletedRemoteData(), + ); + combineLatest(registrationVerification$, recaptchaVersion$, recaptchaMode$, recaptchaKey$).subscribe(([registrationVerification, recaptchaVersion, recaptchaMode, recaptchaKey]) => { if (registrationVerification) { if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { - this.captchaSiteKey = recaptchaKey?.payload?.values[0]; - this.loadScript(this.buildCaptchaUrl(recaptchaKey?.payload?.values[0])); + this.captchaSiteKeyStr = recaptchaKey?.payload?.values[0]; + this.captchaSiteKey$ = of(recaptchaKey?.payload?.values[0]); + } + + if (recaptchaVersion.hasSucceeded && isNotEmpty(recaptchaVersion?.payload?.values[0]) && recaptchaVersion?.payload?.values[0] === 'v3') { + this.captchaVersion$ = of('v3'); + if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { + this.loadScript(this.buildCaptchaUrl(recaptchaKey?.payload?.values[0])); + } + } else { + this.captchaVersion$ = of('v2'); + const captchaUrl = 'https://www.google.com/recaptcha/api.js'; + if (recaptchaMode.hasSucceeded && isNotEmpty(recaptchaMode?.payload?.values[0])) { + this.captchaMode$ = of(recaptchaMode?.payload?.values[0]); + this.loadScript(captchaUrl); + } } } }); @@ -50,7 +91,22 @@ export class GoogleRecaptchaService { * @param action action is the process type in which used to protect multiple spam REST calls */ public async getRecaptchaToken (action) { - return await grecaptcha.execute(this.captchaSiteKey, {action: action}); + return await grecaptcha.execute(this.captchaSiteKeyStr, {action: action}); + } + + /** + * Returns an observable of string + */ + public async executeRecaptcha () { + return await grecaptcha.execute(); + } + + /** + * Returns an observable of string + * @param action action is the process type in which used to protect multiple spam REST calls + */ + public async getRecaptchaTokenResponse () { + return await grecaptcha.getResponse(); } /** diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index e47eedb6ae..5e87d2bd42 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -24,6 +24,9 @@
{{MESSAGE_PREFIX + '.email.hint' |translate}}
+
+ +
@@ -32,5 +35,6 @@ + (click)="(captchaVersion === 'v2' && captchaMode === 'invisible') ? executeRecaptcha() : register()"> + {{MESSAGE_PREFIX + '.submit'| translate}} diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index 330933426a..55004c044b 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -1,5 +1,5 @@ import { waitForAsync, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; -import { of as observableOf } from 'rxjs'; +import { of as observableOf, of } from 'rxjs'; import { RestResponse } from '../core/cache/response.models'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; @@ -31,9 +31,13 @@ describe('RegisterEmailComponent', () => { }); const googleRecaptchaService = jasmine.createSpyObj('googleRecaptchaService', { - getRecaptchaToken: Promise.resolve('googleRecaptchaToken') + getRecaptchaToken: Promise.resolve('googleRecaptchaToken'), + executeRecaptcha: Promise.resolve('googleRecaptchaToken'), + getRecaptchaTokenResponse: Promise.resolve('googleRecaptchaToken') }); + const captchaVersion$ = of('v3'); + const captchaMode$ = of('invisible'); const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['true'] }); const confResponseDisabled$ = createSuccessfulRemoteDataObject$({ values: ['false'] }); @@ -63,6 +67,8 @@ describe('RegisterEmailComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(RegisterEmailFormComponent); comp = fixture.componentInstance; + googleRecaptchaService.captchaVersion$ = captchaVersion$; + googleRecaptchaService.captchaMode$ = captchaMode$; configurationDataService.findByPropertyName.and.returnValues(confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$, confResponseDisabled$); fixture.detectChanges(); @@ -109,6 +115,8 @@ describe('RegisterEmailComponent', () => { describe('register with google recaptcha', () => { beforeEach(fakeAsync(() => { configurationDataService.findByPropertyName.and.returnValues(confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$, confResponse$); + googleRecaptchaService.captchaVersion$ = captchaVersion$; + googleRecaptchaService.captchaMode$ = captchaMode$; comp.ngOnInit(); fixture.detectChanges(); })); diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 04081dc7ff..79303c8a04 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -7,11 +7,12 @@ import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms' import { Registration } from '../core/shared/registration.model'; import { RemoteData } from '../core/data/remote-data'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; -import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; import { map } from 'rxjs/operators'; import { GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; +import { Observable } from 'rxjs'; @Component({ selector: 'ds-register-email-form', @@ -38,6 +39,18 @@ export class RegisterEmailFormComponent implements OnInit { */ registrationVerification = false; + /** + * captcha version + */ + captchaVersion = 'v2'; + + /** + * captcha mode + */ + captchaMode = 'checkbox'; + + recaptchaKey$: Observable; + constructor( private epersonRegistrationService: EpersonRegistrationService, private notificationService: NotificationsService, @@ -45,7 +58,7 @@ export class RegisterEmailFormComponent implements OnInit { private router: Router, private formBuilder: FormBuilder, private configService: ConfigurationDataService, - private googleRecaptchaService: GoogleRecaptchaService + public googleRecaptchaService: GoogleRecaptchaService ) { } @@ -58,6 +71,15 @@ export class RegisterEmailFormComponent implements OnInit { ], }) }); + this.recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( + getFirstSucceededRemoteDataPayload(), + ); + this.googleRecaptchaService.captchaVersion$.subscribe(res => { + this.captchaVersion = res; + }); + this.googleRecaptchaService.captchaMode$.subscribe(res => { + this.captchaMode = res; + }); this.configService.findByPropertyName('registration.verification.enabled').pipe( getFirstCompletedRemoteData(), map((res: RemoteData) => { @@ -68,13 +90,27 @@ export class RegisterEmailFormComponent implements OnInit { }); } + /** + * execute the captcha function for v2 invisible + */ + async executeRecaptcha() { + await this.googleRecaptchaService.executeRecaptcha(); + } + /** * Register an email address */ - async register() { + async register(tokenV2 = null) { if (!this.form.invalid) { if (this.registrationVerification) { - const token = await this.googleRecaptchaService.getRecaptchaToken('register_email'); + let token; + if (this.captchaVersion === 'v3') { + token = await this.googleRecaptchaService.getRecaptchaToken('register_email'); + } else if (this.captchaMode === 'checkbox') { + token = await this.googleRecaptchaService.getRecaptchaTokenResponse(); + } else { + token = tokenV2; + } if (isNotEmpty(token)) { this.registration(token); } else { diff --git a/src/app/register-email-form/register-email-form.module.ts b/src/app/register-email-form/register-email-form.module.ts index 18c2830ff4..a765759413 100644 --- a/src/app/register-email-form/register-email-form.module.ts +++ b/src/app/register-email-form/register-email-form.module.ts @@ -2,13 +2,11 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '../shared/shared.module'; import { RegisterEmailFormComponent } from './register-email-form.component'; -import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module'; @NgModule({ imports: [ CommonModule, - SharedModule, - GoogleRecaptchaModule + SharedModule ], declarations: [ RegisterEmailFormComponent, diff --git a/src/app/shared/cookies/klaro-configuration.ts b/src/app/shared/cookies/klaro-configuration.ts index 659583ad87..1f780198f4 100644 --- a/src/app/shared/cookies/klaro-configuration.ts +++ b/src/app/shared/cookies/klaro-configuration.ts @@ -1,6 +1,7 @@ import { TOKENITEM } from '../../core/auth/models/auth-token-info.model'; import { IMPERSONATING_COOKIE, REDIRECT_COOKIE } from '../../core/auth/auth.service'; import { LANG_COOKIE } from '../../core/locale/locale.service'; +import { CAPTCHA_COOKIE } from 'src/app/core/google-recaptcha/google-recaptcha.service'; /** * Cookie for has_agreed_end_user @@ -155,5 +156,14 @@ export const klaroConfiguration: any = { */ onlyOnce: true, }, + { + name: 'google-recaptcha', + purposes: ['registration-password-recovery'], + required: true, + cookies: [ + CAPTCHA_COOKIE + ], + onlyOnce: true, + } ], }; diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.html b/src/app/shared/google-recaptcha/google-recaptcha.component.html new file mode 100644 index 0000000000..50bc51c808 --- /dev/null +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.html @@ -0,0 +1,4 @@ +
\ No newline at end of file diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.scss b/src/app/shared/google-recaptcha/google-recaptcha.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.spec.ts b/src/app/shared/google-recaptcha/google-recaptcha.component.spec.ts new file mode 100644 index 0000000000..67f66c9757 --- /dev/null +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.spec.ts @@ -0,0 +1,50 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { NativeWindowService } from '../../core/services/window.service'; + +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { NativeWindowMockFactory } from '../mocks/mock-native-window-ref'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { GoogleRecaptchaComponent } from './google-recaptcha.component'; + +describe('GoogleRecaptchaComponent', () => { + + let component: GoogleRecaptchaComponent; + + let fixture: ComponentFixture; + + + const configurationDataService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: jasmine.createSpy('findByPropertyName') + }); + + const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['valid-google-recaptcha-key'] }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GoogleRecaptchaComponent ], + providers: [ + { provide: ConfigurationDataService, useValue: configurationDataService }, + { provide: NativeWindowService, useFactory: NativeWindowMockFactory }, + ] + }) + .compileComponents(); + }); + + + beforeEach(() => { + fixture = TestBed.createComponent(GoogleRecaptchaComponent); + component = fixture.componentInstance; + configurationDataService.findByPropertyName.and.returnValues(confResponse$); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should rendered google recaptcha.',() => { + const container = fixture.debugElement.query(By.css('.g-recaptcha')); + expect(container).toBeTruthy(); + }); +}); diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.ts b/src/app/shared/google-recaptcha/google-recaptcha.component.ts new file mode 100644 index 0000000000..f9c9b599f6 --- /dev/null +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.ts @@ -0,0 +1,45 @@ +import { Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; + +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { Observable } from 'rxjs'; +import { NativeWindowRef, NativeWindowService } from 'src/app/core/services/window.service'; + +@Component({ + selector: 'ds-google-recaptcha', + templateUrl: './google-recaptcha.component.html', + styleUrls: ['./google-recaptcha.component.scss'], +}) +export class GoogleRecaptchaComponent implements OnInit { + + @Input() captchaMode: string; + /** + * An EventEmitter that's fired whenever the form is being submitted + */ + @Output() executeRecaptcha: EventEmitter = new EventEmitter(); + + recaptchaKey$: Observable; + + constructor( + @Inject(NativeWindowService) private _window: NativeWindowRef, + private configService: ConfigurationDataService, + ) { + } + + /** + * Retrieve the google recaptcha site key + */ + ngOnInit() { + this.recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( + getFirstSucceededRemoteDataPayload(), + ); + if (this.captchaMode === 'invisible') { + this._window.nativeWindow.executeRecaptcha = this.execute; + } + } + + execute = (event) => { + this.executeRecaptcha.emit(event); + }; + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index f40ddd5b90..9ca2f22fa4 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -293,6 +293,7 @@ import { BrowserOnlyPipe } from './utils/browser-only.pipe'; import { ThemedLoadingComponent } from './loading/themed-loading.component'; import { PersonPageClaimButtonComponent } from './dso-page/person-page-claim-button/person-page-claim-button.component'; import { SearchExportCsvComponent } from './search/search-export-csv/search-export-csv.component'; +import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module'; const MODULES = [ CommonModule, @@ -313,7 +314,8 @@ const MODULES = [ NouisliderModule, MomentModule, DragDropModule, - CdkTreeModule + CdkTreeModule, + GoogleRecaptchaModule ]; const ROOT_MODULES = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 7fd5fe1bcd..7aa884866f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1262,10 +1262,27 @@ + "cookies.consent.app.title.google-recaptcha": "Google reCaptcha", + + "cookies.consent.app.description.google-recaptcha": "Allows us to track registration and password recovery data", + + + "cookies.consent.purpose.functional": "Functional", "cookies.consent.purpose.statistical": "Statistical", + "cookies.consent.purpose.registration-password-recovery": "Registration and Password recovery", + + "cookies.consent.purpose.sharing": "Sharing", + + "cris-layout.toggle.open": "Open section", + + "cris-layout.toggle.close": "Close section", + + "cris-layout.toggle.aria.open": "Expand {{sectionHeader}} section", + + "cris-layout.toggle.aria.close": "Collapse {{sectionHeader}} section", "curation-task.task.checklinks.label": "Check Links in Metadata", From bcc747dc3eecd16ed93a9b6b0831715dfa57f1ee Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Mon, 22 Aug 2022 15:03:35 +0530 Subject: [PATCH 10/78] [UXP-10] klaro cookies permission and test cases --- .../google-recaptcha.service.spec.ts | 12 +++++++- .../google-recaptcha.service.ts | 27 +++++++++++++++--- .../register-email-form.component.html | 14 +++++++--- .../register-email-form.component.ts | 28 +++++++------------ src/app/shared/cookies/klaro-configuration.ts | 9 ++++-- src/assets/i18n/en.json5 | 2 +- 6 files changed, 61 insertions(+), 31 deletions(-) diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts b/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts index 3698306763..545e3b9873 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.spec.ts @@ -1,6 +1,7 @@ import { GoogleRecaptchaService } from './google-recaptcha.service'; import { of as observableOf } from 'rxjs'; import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { NativeWindowRef } from '../services/window.service'; describe('GoogleRecaptchaService', () => { let service: GoogleRecaptchaService; @@ -9,6 +10,8 @@ describe('GoogleRecaptchaService', () => { let configurationDataService; let spy: jasmine.Spy; let scriptElementMock: any; + let cookieService; + let window; const innerHTMLTestValue = 'mock-script-inner-html'; const document = { documentElement: { lang: 'en' } } as Document; scriptElementMock = { @@ -17,6 +20,7 @@ describe('GoogleRecaptchaService', () => { }; function init() { + window = new NativeWindowRef(); rendererFactory2 = jasmine.createSpyObj('rendererFactory2', { createRenderer: observableOf('googleRecaptchaToken'), createElement: scriptElementMock @@ -24,7 +28,13 @@ describe('GoogleRecaptchaService', () => { configurationDataService = jasmine.createSpyObj('configurationDataService', { findByPropertyName: createSuccessfulRemoteDataObject$({ values: ['googleRecaptchaToken'] }) }); - service = new GoogleRecaptchaService(document, rendererFactory2, configurationDataService); + cookieService = jasmine.createSpyObj('cookieService', { + get: '{%22token_item%22:true%2C%22impersonation%22:true%2C%22redirect%22:true%2C%22language%22:true%2C%22klaro%22:true%2C%22has_agreed_end_user%22:true%2C%22google-analytics%22:true}', + set: () => { + /* empty */ + } + }); + service = new GoogleRecaptchaService(cookieService, document, window, rendererFactory2, configurationDataService); } beforeEach(() => { diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts index 99311247f6..4f602ef575 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -7,8 +7,11 @@ import { ConfigurationDataService } from '../data/configuration-data.service'; import { RemoteData } from '../data/remote-data'; import { map, take } from 'rxjs/operators'; import { combineLatest, Observable, of } from 'rxjs'; +import { CookieService } from '../services/cookie.service'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; export const CAPTCHA_COOKIE = '_GRECAPTCHA'; +export const CAPTCHA_NAME = 'google-recaptcha'; /** * A GoogleRecaptchaService used to send action and get a token from REST @@ -35,13 +38,18 @@ export class GoogleRecaptchaService { /** * A Google Recaptcha version */ - captchaVersion$: Observable; + captchaVersion$: Observable = of(''); constructor( + private cookieService: CookieService, @Inject(DOCUMENT) private _document: Document, + @Inject(NativeWindowService) private _window: NativeWindowRef, rendererFactory: RendererFactory2, private configService: ConfigurationDataService, ) { + if (this._window.nativeWindow) { + this._window.nativeWindow.refreshCaptchaScript = this.refreshCaptchaScript; + } this.renderer = rendererFactory.createRenderer(null, null); const registrationVerification$ = this.configService.findByPropertyName('registration.verification.enabled').pipe( take(1), @@ -50,6 +58,14 @@ export class GoogleRecaptchaService { return res.hasSucceeded && res.payload && isNotEmpty(res.payload.values) && res.payload.values[0].toLowerCase() === 'true'; }) ); + registrationVerification$.subscribe(registrationVerification => { + if (registrationVerification) { + this.loadRecaptchaProperties(); + } + }); + } + + loadRecaptchaProperties() { const recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( take(1), getFirstCompletedRemoteData(), @@ -62,13 +78,12 @@ export class GoogleRecaptchaService { take(1), getFirstCompletedRemoteData(), ); - combineLatest(registrationVerification$, recaptchaVersion$, recaptchaMode$, recaptchaKey$).subscribe(([registrationVerification, recaptchaVersion, recaptchaMode, recaptchaKey]) => { - if (registrationVerification) { + combineLatest(recaptchaVersion$, recaptchaMode$, recaptchaKey$).subscribe(([recaptchaVersion, recaptchaMode, recaptchaKey]) => { + if (this.cookieService.get('klaro-anonymous') && this.cookieService.get('klaro-anonymous')[CAPTCHA_NAME]) { if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { this.captchaSiteKeyStr = recaptchaKey?.payload?.values[0]; this.captchaSiteKey$ = of(recaptchaKey?.payload?.values[0]); } - if (recaptchaVersion.hasSucceeded && isNotEmpty(recaptchaVersion?.payload?.values[0]) && recaptchaVersion?.payload?.values[0] === 'v3') { this.captchaVersion$ = of('v3'); if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { @@ -137,4 +152,8 @@ export class GoogleRecaptchaService { }); } + refreshCaptchaScript = () => { + this.loadRecaptchaProperties(); + }; + } diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 5e87d2bd42..b10833c097 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -24,8 +24,8 @@
{{MESSAGE_PREFIX + '.email.hint' |translate}}
-
- +
+
@@ -33,8 +33,14 @@ - + diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 79303c8a04..0079ea2da8 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -39,16 +39,6 @@ export class RegisterEmailFormComponent implements OnInit { */ registrationVerification = false; - /** - * captcha version - */ - captchaVersion = 'v2'; - - /** - * captcha mode - */ - captchaMode = 'checkbox'; - recaptchaKey$: Observable; constructor( @@ -74,12 +64,6 @@ export class RegisterEmailFormComponent implements OnInit { this.recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( getFirstSucceededRemoteDataPayload(), ); - this.googleRecaptchaService.captchaVersion$.subscribe(res => { - this.captchaVersion = res; - }); - this.googleRecaptchaService.captchaMode$.subscribe(res => { - this.captchaMode = res; - }); this.configService.findByPropertyName('registration.verification.enabled').pipe( getFirstCompletedRemoteData(), map((res: RemoteData) => { @@ -104,9 +88,17 @@ export class RegisterEmailFormComponent implements OnInit { if (!this.form.invalid) { if (this.registrationVerification) { let token; - if (this.captchaVersion === 'v3') { + let captchaVersion; + let captchaMode; + this.googleRecaptchaService.captchaVersion$.subscribe(res => { + captchaVersion = res; + }); + this.googleRecaptchaService.captchaMode$.subscribe(res => { + captchaMode = res; + }); + if (captchaVersion === 'v3') { token = await this.googleRecaptchaService.getRecaptchaToken('register_email'); - } else if (this.captchaMode === 'checkbox') { + } else if (captchaMode === 'checkbox') { token = await this.googleRecaptchaService.getRecaptchaTokenResponse(); } else { token = tokenV2; diff --git a/src/app/shared/cookies/klaro-configuration.ts b/src/app/shared/cookies/klaro-configuration.ts index 1f780198f4..4075f7f050 100644 --- a/src/app/shared/cookies/klaro-configuration.ts +++ b/src/app/shared/cookies/klaro-configuration.ts @@ -1,7 +1,7 @@ import { TOKENITEM } from '../../core/auth/models/auth-token-info.model'; import { IMPERSONATING_COOKIE, REDIRECT_COOKIE } from '../../core/auth/auth.service'; import { LANG_COOKIE } from '../../core/locale/locale.service'; -import { CAPTCHA_COOKIE } from 'src/app/core/google-recaptcha/google-recaptcha.service'; +import { CAPTCHA_COOKIE, CAPTCHA_NAME } from '../../core/google-recaptcha/google-recaptcha.service'; /** * Cookie for has_agreed_end_user @@ -157,12 +157,15 @@ export const klaroConfiguration: any = { onlyOnce: true, }, { - name: 'google-recaptcha', + name: CAPTCHA_NAME, purposes: ['registration-password-recovery'], - required: true, + required: false, cookies: [ CAPTCHA_COOKIE ], + onAccept: `window.refreshCaptchaScript()`, + onDecline: `window.refreshCaptchaScript()`, + onInit: `window.refreshCaptchaScript()`, onlyOnce: true, } ], diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 7aa884866f..024a3ca54b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1264,7 +1264,7 @@ "cookies.consent.app.title.google-recaptcha": "Google reCaptcha", - "cookies.consent.app.description.google-recaptcha": "Allows us to track registration and password recovery data", + "cookies.consent.app.description.google-recaptcha": "We use google reCAPTCHA service during registration and password recovery", From 6d1d7c36110c6f7e26aa9462f0d53feb658864a0 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Mon, 22 Aug 2022 15:30:18 +0530 Subject: [PATCH 11/78] [UXP-10] property check fixed --- .../cookies/browser-klaro.service.spec.ts | 18 +++++++++++++ .../shared/cookies/browser-klaro.service.ts | 25 +++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.spec.ts b/src/app/shared/cookies/browser-klaro.service.spec.ts index 2155fb1bad..7ed00013ba 100644 --- a/src/app/shared/cookies/browser-klaro.service.spec.ts +++ b/src/app/shared/cookies/browser-klaro.service.spec.ts @@ -11,8 +11,13 @@ import { CookieService } from '../../core/services/cookie.service'; import { getTestScheduler } from 'jasmine-marbles'; import { MetadataValue } from '../../core/shared/metadata.models'; import { cloneDeep } from 'lodash'; +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { ConfigurationProperty } from '../../core/shared/configuration-property.model'; describe('BrowserKlaroService', () => { + const recaptchaProp = 'registration.verification.enabled'; + const recaptchaValue = 'true'; let translateService; let ePersonService; let authService; @@ -20,6 +25,14 @@ describe('BrowserKlaroService', () => { let user; let service: BrowserKlaroService; + let configurationDataService: ConfigurationDataService; + const createConfigSuccessSpy = (...values: string[]) => jasmine.createSpyObj('configurationDataService', { + findByPropertyName: createSuccessfulRemoteDataObject$({ + ... new ConfigurationProperty(), + name: recaptchaProp, + values: values, + }), + }); let mockConfig; let appName; @@ -38,6 +51,7 @@ describe('BrowserKlaroService', () => { isAuthenticated: observableOf(true), getAuthenticatedUserFromStore: observableOf(user) }); + configurationDataService = createConfigSuccessSpy(recaptchaValue); cookieService = jasmine.createSpyObj('cookieService', { get: '{%22token_item%22:true%2C%22impersonation%22:true%2C%22redirect%22:true%2C%22language%22:true%2C%22klaro%22:true%2C%22has_agreed_end_user%22:true%2C%22google-analytics%22:true}', set: () => { @@ -63,6 +77,10 @@ describe('BrowserKlaroService', () => { { provide: CookieService, useValue: cookieService + }, + { + provide: ConfigurationDataService, + useValue: configurationDataService } ] }); diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 638d465864..517a945eef 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -4,15 +4,18 @@ import { combineLatest as observableCombineLatest, Observable, of as observableO import { AuthService } from '../../core/auth/auth.service'; import { TranslateService } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; -import { switchMap, take } from 'rxjs/operators'; +import { catchError, switchMap, take } from 'rxjs/operators'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { KlaroService } from './klaro.service'; -import { hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../empty.util'; import { CookieService } from '../../core/services/cookie.service'; 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 { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { CAPTCHA_NAME } from '../../core/google-recaptcha/google-recaptcha.service'; /** * Metadata field to store a user's cookie consent preferences in @@ -52,6 +55,7 @@ export class BrowserKlaroService extends KlaroService { private translateService: TranslateService, private authService: AuthService, private ePersonService: EPersonDataService, + private configService: ConfigurationDataService, private cookieService: CookieService) { super(); } @@ -68,6 +72,15 @@ export class BrowserKlaroService extends KlaroService { this.klaroConfig.translations.en.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy'; } + this.configService.findByPropertyName('registration.verification.enabled').pipe( + getFirstCompletedRemoteData(), + catchError(this.removeGoogleRecaptcha()) + ).subscribe((remoteData) => { + // make sure we got a success response from the backend + if (!remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values) || remoteData.payload.values[0].toLowerCase() === 'false' ) { + this.removeGoogleRecaptcha(); + } + }); this.translateService.setDefaultLang(environment.defaultLanguage); const user$: Observable = this.getUser$(); @@ -257,4 +270,12 @@ export class BrowserKlaroService extends KlaroService { getStorageName(identifier: string) { return 'klaro-' + identifier; } + + /** + * remove the google recaptcha from the services + */ + removeGoogleRecaptcha() { + this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); + return this.klaroConfig.services; + } } From b72b37a64706baba7f3ea28ec4909acf7befda56 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Mon, 22 Aug 2022 17:30:19 +0530 Subject: [PATCH 12/78] [UXP-10] remove configuration fixed --- .../register-email-form.component.ts | 2 +- src/app/shared/cookies/browser-klaro.service.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 0079ea2da8..08e172d5f1 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -107,7 +107,7 @@ export class RegisterEmailFormComponent implements OnInit { this.registration(token); } else { this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`, {email: this.email.value})); + this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`)); } } else { this.registration(); diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 517a945eef..b763335b04 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -74,10 +74,10 @@ export class BrowserKlaroService extends KlaroService { this.configService.findByPropertyName('registration.verification.enabled').pipe( getFirstCompletedRemoteData(), - catchError(this.removeGoogleRecaptcha()) ).subscribe((remoteData) => { + this.klaroConfig = klaroConfiguration; // make sure we got a success response from the backend - if (!remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values) || remoteData.payload.values[0].toLowerCase() === 'false' ) { + if (!remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { this.removeGoogleRecaptcha(); } }); @@ -275,7 +275,8 @@ export class BrowserKlaroService extends KlaroService { * remove the google recaptcha from the services */ removeGoogleRecaptcha() { - this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); - return this.klaroConfig.services; - } + this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); + return this.klaroConfig.services; + } + } From b6d6091c87e814c0320b27f315edba1b925732e8 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 7 Sep 2022 18:02:23 +0200 Subject: [PATCH 13/78] [UXP-10] Code refactoring --- src/app/app.component.ts | 7 +- .../google-recaptcha.service.ts | 116 ++++++++++-------- .../register-email-form.component.html | 14 +-- .../register-email-form.component.ts | 81 ++++++------ 4 files changed, 115 insertions(+), 103 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 68c1bce6f3..e7033f51ba 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,7 +15,8 @@ import { ActivationEnd, NavigationCancel, NavigationEnd, - NavigationStart, ResolveEnd, + NavigationStart, + ResolveEnd, Router, } from '@angular/router'; @@ -48,8 +49,7 @@ import { BASE_THEME_NAME } from './shared/theme-support/theme.constants'; import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; -import { AppConfig, APP_CONFIG } from '../config/app-config.interface'; -import { GoogleRecaptchaService } from './core/google-recaptcha/google-recaptcha.service'; +import { APP_CONFIG, AppConfig } from '../config/app-config.interface'; @Component({ selector: 'ds-app', @@ -110,7 +110,6 @@ export class AppComponent implements OnInit, AfterViewInit { private modalConfig: NgbModalConfig, @Optional() private cookiesService: KlaroService, @Optional() private googleAnalyticsService: GoogleAnalyticsService, - @Optional() private googleRecaptchaService: GoogleRecaptchaService, ) { if (!isEqual(environment, this.appConfig)) { diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts index 4f602ef575..080ddfc19f 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -5,8 +5,8 @@ import { isNotEmpty } from '../../shared/empty.util'; import { DOCUMENT } from '@angular/common'; import { ConfigurationDataService } from '../data/configuration-data.service'; import { RemoteData } from '../data/remote-data'; -import { map, take } from 'rxjs/operators'; -import { combineLatest, Observable, of } from 'rxjs'; +import { map, switchMap, take } from 'rxjs/operators'; +import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { CookieService } from '../services/cookie.service'; import { NativeWindowRef, NativeWindowService } from '../services/window.service'; @@ -20,25 +20,33 @@ export const CAPTCHA_NAME = 'google-recaptcha'; export class GoogleRecaptchaService { private renderer: Renderer2; - /** - * A Google Recaptcha site key - */ - captchaSiteKeyStr: string; - - /** - * A Google Recaptcha site key - */ - captchaSiteKey$: Observable; - - /** - * A Google Recaptcha mode - */ - captchaMode$: Observable = of('invisible'); /** * A Google Recaptcha version */ - captchaVersion$: Observable = of(''); + private captchaVersionSubject$ = new BehaviorSubject(null); + + /** + * The Google Recaptcha Key + */ + private captchaKeySubject$ = new BehaviorSubject(null); + + /** + * The Google Recaptcha mode + */ + private captchaModeSubject$ = new BehaviorSubject(null); + + captchaKey(): Observable { + return this.captchaKeySubject$.asObservable(); + } + + captchaMode(): Observable { + return this.captchaModeSubject$.asObservable(); + } + + captchaVersion(): Observable { + return this.captchaVersionSubject$.asObservable(); + } constructor( private cookieService: CookieService, @@ -66,36 +74,46 @@ export class GoogleRecaptchaService { } loadRecaptchaProperties() { - const recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( - take(1), + const recaptchaKeyRD$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( getFirstCompletedRemoteData(), ); - const recaptchaVersion$ = this.configService.findByPropertyName('google.recaptcha.version').pipe( - take(1), + const recaptchaVersionRD$ = this.configService.findByPropertyName('google.recaptcha.version').pipe( getFirstCompletedRemoteData(), ); - const recaptchaMode$ = this.configService.findByPropertyName('google.recaptcha.mode').pipe( - take(1), + const recaptchaModeRD$ = this.configService.findByPropertyName('google.recaptcha.mode').pipe( getFirstCompletedRemoteData(), ); - combineLatest(recaptchaVersion$, recaptchaMode$, recaptchaKey$).subscribe(([recaptchaVersion, recaptchaMode, recaptchaKey]) => { - if (this.cookieService.get('klaro-anonymous') && this.cookieService.get('klaro-anonymous')[CAPTCHA_NAME]) { - if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { - this.captchaSiteKeyStr = recaptchaKey?.payload?.values[0]; - this.captchaSiteKey$ = of(recaptchaKey?.payload?.values[0]); + combineLatest([recaptchaVersionRD$, recaptchaModeRD$, recaptchaKeyRD$]).subscribe(([recaptchaVersionRD, recaptchaModeRD, recaptchaKeyRD]) => { + + if ( + this.cookieService.get('klaro-anonymous') && this.cookieService.get('klaro-anonymous')[CAPTCHA_NAME] && + recaptchaKeyRD.hasSucceeded && recaptchaVersionRD.hasSucceeded && + isNotEmpty(recaptchaVersionRD.payload?.values) && isNotEmpty(recaptchaKeyRD.payload?.values) + ) { + const key = recaptchaKeyRD.payload?.values[0]; + const version = recaptchaVersionRD.payload?.values[0]; + this.captchaKeySubject$.next(key); + this.captchaVersionSubject$.next(version); + + let captchaUrl; + switch (version) { + case 'v3': + if (recaptchaKeyRD.hasSucceeded && isNotEmpty(recaptchaKeyRD.payload?.values)) { + captchaUrl = this.buildCaptchaUrl(key); + this.captchaModeSubject$.next('invisible'); + } + break; + case 'v2': + if (recaptchaModeRD.hasSucceeded && isNotEmpty(recaptchaModeRD.payload?.values)) { + captchaUrl = 'https://www.google.com/recaptcha/api.js'; + this.captchaModeSubject$.next(recaptchaModeRD.payload?.values[0]); + } + break; + default: + // TODO handle error } - if (recaptchaVersion.hasSucceeded && isNotEmpty(recaptchaVersion?.payload?.values[0]) && recaptchaVersion?.payload?.values[0] === 'v3') { - this.captchaVersion$ = of('v3'); - if (recaptchaKey.hasSucceeded && isNotEmpty(recaptchaKey?.payload?.values[0])) { - this.loadScript(this.buildCaptchaUrl(recaptchaKey?.payload?.values[0])); - } - } else { - this.captchaVersion$ = of('v2'); - const captchaUrl = 'https://www.google.com/recaptcha/api.js'; - if (recaptchaMode.hasSucceeded && isNotEmpty(recaptchaMode?.payload?.values[0])) { - this.captchaMode$ = of(recaptchaMode?.payload?.values[0]); - this.loadScript(captchaUrl); - } + if (captchaUrl) { + this.loadScript(captchaUrl); } } }); @@ -105,23 +123,21 @@ export class GoogleRecaptchaService { * Returns an observable of string * @param action action is the process type in which used to protect multiple spam REST calls */ - public async getRecaptchaToken (action) { - return await grecaptcha.execute(this.captchaSiteKeyStr, {action: action}); + public getRecaptchaToken(action) { + return this.captchaKey().pipe( + switchMap((key) => grecaptcha.execute(key, {action: action})) + ); } /** * Returns an observable of string */ - public async executeRecaptcha () { - return await grecaptcha.execute(); + public executeRecaptcha() { + return of(grecaptcha.execute()); } - /** - * Returns an observable of string - * @param action action is the process type in which used to protect multiple spam REST calls - */ - public async getRecaptchaTokenResponse () { - return await grecaptcha.getResponse(); + public getRecaptchaTokenResponse () { + return grecaptcha.getResponse(); } /** diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index b10833c097..19912ec0a9 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -24,23 +24,23 @@
{{MESSAGE_PREFIX + '.email.hint' |translate}}
-
- +
+
- - - + {{ MESSAGE_PREFIX + '.submit' | translate }} diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 08e172d5f1..084446eced 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -7,12 +7,12 @@ import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms' import { Registration } from '../core/shared/registration.model'; import { RemoteData } from '../core/data/remote-data'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; +import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; +import { combineLatest, Observable, of, switchMap } from 'rxjs'; import { map } from 'rxjs/operators'; import { GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; -import { Observable } from 'rxjs'; @Component({ selector: 'ds-register-email-form', @@ -39,9 +39,16 @@ export class RegisterEmailFormComponent implements OnInit { */ registrationVerification = false; - recaptchaKey$: Observable; + captchaVersion(): Observable { + return this.googleRecaptchaService.captchaVersion(); + } - constructor( + captchaMode(): Observable { + return this.googleRecaptchaService.captchaMode(); + } + + +constructor( private epersonRegistrationService: EpersonRegistrationService, private notificationService: NotificationsService, private translateService: TranslateService, @@ -61,14 +68,9 @@ export class RegisterEmailFormComponent implements OnInit { ], }) }); - this.recaptchaKey$ = this.configService.findByPropertyName('google.recaptcha.key.site').pipe( - getFirstSucceededRemoteDataPayload(), - ); this.configService.findByPropertyName('registration.verification.enabled').pipe( - getFirstCompletedRemoteData(), - map((res: RemoteData) => { - return res.hasSucceeded && res.payload && isNotEmpty(res.payload.values) && res.payload.values[0].toLowerCase() === 'true'; - }) + getFirstSucceededRemoteDataPayload(), + map((res: ConfigurationProperty) => res?.values[0].toLowerCase() === 'true') ).subscribe((res: boolean) => { this.registrationVerification = res; }); @@ -77,38 +79,36 @@ export class RegisterEmailFormComponent implements OnInit { /** * execute the captcha function for v2 invisible */ - async executeRecaptcha() { - await this.googleRecaptchaService.executeRecaptcha(); + executeRecaptcha() { + console.log('executeRecaptcha'); + this.googleRecaptchaService.executeRecaptcha(); } /** * Register an email address */ - async register(tokenV2 = null) { + register(tokenV2 = null) { if (!this.form.invalid) { if (this.registrationVerification) { - let token; - let captchaVersion; - let captchaMode; - this.googleRecaptchaService.captchaVersion$.subscribe(res => { - captchaVersion = res; - }); - this.googleRecaptchaService.captchaMode$.subscribe(res => { - captchaMode = res; - }); - if (captchaVersion === 'v3') { - token = await this.googleRecaptchaService.getRecaptchaToken('register_email'); - } else if (captchaMode === 'checkbox') { - token = await this.googleRecaptchaService.getRecaptchaTokenResponse(); - } else { - token = tokenV2; - } - if (isNotEmpty(token)) { - this.registration(token); - } else { - this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`)); - } + combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( + switchMap(([captchaVersion, captchaMode]) => { + if (captchaVersion === 'v3') { + return this.googleRecaptchaService.getRecaptchaToken('register_email'); + } else if (captchaMode === 'checkbox') { + return this.googleRecaptchaService.getRecaptchaTokenResponse(); + } else { + return of(tokenV2); + } + }), + ).subscribe((token) => { + if (isNotEmpty(token)) { + this.registration(token); + } else { + this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), + this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`)); + } + } + ); } else { this.registration(); } @@ -119,12 +119,9 @@ export class RegisterEmailFormComponent implements OnInit { * Registration of an email address */ registration(captchaToken = null) { - let registerEmail$; - if (captchaToken) { - registerEmail$ = this.epersonRegistrationService.registerEmail(this.email.value, captchaToken); - } else { - registerEmail$ = this.epersonRegistrationService.registerEmail(this.email.value); - } + let registerEmail$ = captchaToken ? + this.epersonRegistrationService.registerEmail(this.email.value, captchaToken) : + this.epersonRegistrationService.registerEmail(this.email.value); registerEmail$.subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationService.success(this.translateService.get(`${this.MESSAGE_PREFIX}.success.head`), From db6c8f00a87728dabfee4e55f7e977b1e4e3e0f2 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 7 Sep 2022 19:01:54 +0200 Subject: [PATCH 14/78] [UXP-10] Fix headers --- src/app/core/data/eperson-registration.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 6f64879e0b..43a5ae8168 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -67,7 +67,7 @@ export class EpersonRegistrationService { const options: HttpOptions = Object.create({}); let headers = new HttpHeaders(); if (captchaToken) { - headers = headers.append('X-Recaptcha-Token', captchaToken); + headers = headers.append('x-recaptcha-token', captchaToken); } options.headers = headers; From 0b7cf23e3fcc64c978c11a4dbd0f42be54d0cd05 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 8 Sep 2022 16:52:43 +0200 Subject: [PATCH 15/78] [UXP-10] Fix onInit script --- src/app/shared/cookies/browser-klaro.service.ts | 7 +++---- src/app/shared/cookies/klaro-configuration.ts | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index b763335b04..e6ae29e44c 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -4,10 +4,10 @@ import { combineLatest as observableCombineLatest, Observable, of as observableO import { AuthService } from '../../core/auth/auth.service'; import { TranslateService } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; -import { catchError, switchMap, take } from 'rxjs/operators'; +import { switchMap, take } from 'rxjs/operators'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { KlaroService } from './klaro.service'; -import { hasValue, isEmpty, isNotEmpty } from '../empty.util'; +import { hasValue, isNotEmpty } from '../empty.util'; import { CookieService } from '../../core/services/cookie.service'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { cloneDeep, debounce } from 'lodash'; @@ -274,9 +274,8 @@ export class BrowserKlaroService extends KlaroService { /** * remove the google recaptcha from the services */ - removeGoogleRecaptcha() { + removeGoogleRecaptcha(): void { this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); - return this.klaroConfig.services; } } diff --git a/src/app/shared/cookies/klaro-configuration.ts b/src/app/shared/cookies/klaro-configuration.ts index 4075f7f050..b4ed3d91ba 100644 --- a/src/app/shared/cookies/klaro-configuration.ts +++ b/src/app/shared/cookies/klaro-configuration.ts @@ -163,9 +163,8 @@ export const klaroConfiguration: any = { cookies: [ CAPTCHA_COOKIE ], - onAccept: `window.refreshCaptchaScript()`, - onDecline: `window.refreshCaptchaScript()`, - onInit: `window.refreshCaptchaScript()`, + onAccept: `window.refreshCaptchaScript?.call()`, + onDecline: `window.refreshCaptchaScript?.call()`, onlyOnce: true, } ], From 05784bfec642a9e8e440d33c6e57905a6dbe3949 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 8 Sep 2022 17:14:14 +0200 Subject: [PATCH 16/78] [UXP-10] Fix remove Recaptcha from klaro config --- src/app/shared/cookies/browser-klaro.service.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index e6ae29e44c..1481f7c0d3 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -7,7 +7,7 @@ import { environment } from '../../../environments/environment'; import { switchMap, take } from 'rxjs/operators'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { KlaroService } from './klaro.service'; -import { hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../empty.util'; import { CookieService } from '../../core/services/cookie.service'; import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { cloneDeep, debounce } from 'lodash'; @@ -75,10 +75,8 @@ export class BrowserKlaroService extends KlaroService { this.configService.findByPropertyName('registration.verification.enabled').pipe( getFirstCompletedRemoteData(), ).subscribe((remoteData) => { - this.klaroConfig = klaroConfiguration; - // make sure we got a success response from the backend - if (!remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { - this.removeGoogleRecaptcha(); + if (!remoteData.hasSucceeded || isEmpty(remoteData.payload?.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { + this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); } }); this.translateService.setDefaultLang(environment.defaultLanguage); @@ -271,11 +269,4 @@ export class BrowserKlaroService extends KlaroService { return 'klaro-' + identifier; } - /** - * remove the google recaptcha from the services - */ - removeGoogleRecaptcha(): void { - this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); - } - } From 97a12cae47667ad876c1e30205469fa13c147d43 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 8 Sep 2022 20:06:36 +0200 Subject: [PATCH 17/78] [UXP-10] Notify unaccepted cookies - Fixes --- .../register-email-form.component.html | 36 +++++++++------- .../register-email-form.component.ts | 42 +++++++++++++++---- .../shared/cookies/browser-klaro.service.ts | 2 +- 3 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 19912ec0a9..4def4d94a8 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -2,7 +2,7 @@

{{MESSAGE_PREFIX + '.header'|translate}}

{{MESSAGE_PREFIX + '.info' | translate}}

-
+
@@ -24,23 +24,31 @@
{{MESSAGE_PREFIX + '.email.hint' |translate}}
-
- -
- - + +

{{ MESSAGE_PREFIX + '.google-recaptcha.must-accept-cookies' | translate }}

+

{{ MESSAGE_PREFIX + '.google-recaptcha.open-cookie-settings' | translate }}

+
+ +
+ +
+ + + + + + + + + diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 084446eced..14c22d7650 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, Optional } from '@angular/core'; import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; @@ -11,8 +11,11 @@ import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; import { combineLatest, Observable, of, switchMap } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; +import { map, take, tap } from 'rxjs/operators'; +import { CAPTCHA_NAME, GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; +import { AlertType } from '../shared/alert/aletr-type'; +import { KlaroService } from '../shared/cookies/klaro.service'; +import { CookieService } from '../core/services/cookie.service'; @Component({ selector: 'ds-register-email-form', @@ -34,6 +37,8 @@ export class RegisterEmailFormComponent implements OnInit { @Input() MESSAGE_PREFIX: string; + public AlertTypeEnum = AlertType; + /** * registration verification configuration */ @@ -47,15 +52,16 @@ export class RegisterEmailFormComponent implements OnInit { return this.googleRecaptchaService.captchaMode(); } - -constructor( + constructor( private epersonRegistrationService: EpersonRegistrationService, private notificationService: NotificationsService, private translateService: TranslateService, private router: Router, private formBuilder: FormBuilder, private configService: ConfigurationDataService, - public googleRecaptchaService: GoogleRecaptchaService + public googleRecaptchaService: GoogleRecaptchaService, + public cookieService: CookieService, + @Optional() public klaroService: KlaroService, ) { } @@ -80,14 +86,13 @@ constructor( * execute the captcha function for v2 invisible */ executeRecaptcha() { - console.log('executeRecaptcha'); this.googleRecaptchaService.executeRecaptcha(); } /** * Register an email address */ - register(tokenV2 = null) { + register(tokenV2?) { if (!this.form.invalid) { if (this.registrationVerification) { combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( @@ -100,6 +105,7 @@ constructor( return of(tokenV2); } }), + take(1), ).subscribe((token) => { if (isNotEmpty(token)) { this.registration(token); @@ -134,6 +140,26 @@ constructor( }); } + isRecaptchaCookieAccepted(): boolean { + const klaroAnonymousCookie = this.cookieService.get('klaro-anonymous'); + return isNotEmpty(klaroAnonymousCookie) ? klaroAnonymousCookie[CAPTCHA_NAME] : false; + } + + disableRegisterButton(): Observable { + return combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( + switchMap(([captchaVersion, captchaMode]) => { + if (captchaVersion === 'v2' && captchaMode === 'checkbox') { +// this.googleRecaptchaService.getRecaptchaTokenResponse() + return of(false); + // TODO disable if captcha unchecked + } else { + return of(false); + } + }), + tap(console.log) + ); + } + get email() { return this.form.get('email'); } diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index 1481f7c0d3..a1ed0ff77d 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -75,7 +75,7 @@ export class BrowserKlaroService extends KlaroService { this.configService.findByPropertyName('registration.verification.enabled').pipe( getFirstCompletedRemoteData(), ).subscribe((remoteData) => { - if (!remoteData.hasSucceeded || isEmpty(remoteData.payload?.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { + if (remoteData.statusCode === 404 || isEmpty(remoteData.payload?.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); } }); From 895e44a25fa7ed40e66fc32d37a2af07aeb69d81 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 9 Sep 2022 13:19:07 +0200 Subject: [PATCH 18/78] [UXP-10] Disable button - Error handling - Notifications --- .../register-email-form.component.html | 8 ++- .../register-email-form.component.ts | 64 +++++++++++++++---- .../google-recaptcha.component.html | 6 +- .../google-recaptcha.component.ts | 24 ++++++- src/assets/i18n/en.json5 | 10 ++- 5 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 4def4d94a8..8ade052708 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -31,16 +31,18 @@ -

{{ MESSAGE_PREFIX + '.google-recaptcha.must-accept-cookies' | translate }}

+

{{ MESSAGE_PREFIX + '.google-recaptcha.open-cookie-settings' | translate }}

- +
- diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 14c22d7650..3338200eb9 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, Optional } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnInit, Optional } from '@angular/core'; import { EpersonRegistrationService } from '../core/data/eperson-registration.service'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; @@ -10,7 +10,7 @@ import { ConfigurationDataService } from '../core/data/configuration-data.servic import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; -import { combineLatest, Observable, of, switchMap } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, of, switchMap } from 'rxjs'; import { map, take, tap } from 'rxjs/operators'; import { CAPTCHA_NAME, GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; import { AlertType } from '../shared/alert/aletr-type'; @@ -44,6 +44,11 @@ export class RegisterEmailFormComponent implements OnInit { */ registrationVerification = false; + /** + * Return true if the user completed the reCaptcha verification (checkbox mode) + */ + checkboxCheckedSubject$ = new BehaviorSubject(false); + captchaVersion(): Observable { return this.googleRecaptchaService.captchaVersion(); } @@ -62,6 +67,8 @@ export class RegisterEmailFormComponent implements OnInit { public googleRecaptchaService: GoogleRecaptchaService, public cookieService: CookieService, @Optional() public klaroService: KlaroService, + private changeDetectorRef: ChangeDetectorRef, + private notificationsService: NotificationsService, ) { } @@ -99,10 +106,13 @@ export class RegisterEmailFormComponent implements OnInit { switchMap(([captchaVersion, captchaMode]) => { if (captchaVersion === 'v3') { return this.googleRecaptchaService.getRecaptchaToken('register_email'); - } else if (captchaMode === 'checkbox') { + } else if (captchaVersion === 'v2' && captchaMode === 'checkbox') { return this.googleRecaptchaService.getRecaptchaTokenResponse(); - } else { + } else if (captchaVersion === 'v2' && captchaMode === 'invisible') { return of(tokenV2); + } else { + console.error(`Invalid reCaptcha configuration: version = ${captchaVersion}, mode = ${captchaMode}`); + this.showNotification('error'); } }), take(1), @@ -110,8 +120,8 @@ export class RegisterEmailFormComponent implements OnInit { if (isNotEmpty(token)) { this.registration(token); } else { - this.notificationService.error(this.translateService.get(`${this.MESSAGE_PREFIX}.error.head`), - this.translateService.get(`${this.MESSAGE_PREFIX}.error.recaptcha`)); + console.error('reCaptcha error'); + this.showNotification('error'); } } ); @@ -140,23 +150,28 @@ export class RegisterEmailFormComponent implements OnInit { }); } + /** + * Return true if the user has accepted the required cookies for reCaptcha + */ isRecaptchaCookieAccepted(): boolean { const klaroAnonymousCookie = this.cookieService.get('klaro-anonymous'); return isNotEmpty(klaroAnonymousCookie) ? klaroAnonymousCookie[CAPTCHA_NAME] : false; } - disableRegisterButton(): Observable { + /** + * Return true if the user completed the reCaptcha verification (checkbox mode) + */ + isCheckboxChecked(): Observable { return combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( switchMap(([captchaVersion, captchaMode]) => { if (captchaVersion === 'v2' && captchaMode === 'checkbox') { -// this.googleRecaptchaService.getRecaptchaTokenResponse() - return of(false); - // TODO disable if captcha unchecked + return this.checkboxCheckedSubject$.asObservable(); } else { - return of(false); + return of(true); } }), - tap(console.log) + tap(console.log), + tap(() => { this.changeDetectorRef.markForCheck(); }) ); } @@ -164,4 +179,29 @@ export class RegisterEmailFormComponent implements OnInit { return this.form.get('email'); } + onCheckboxChecked($event) { + if (isNotEmpty($event)) { + this.checkboxCheckedSubject$.next(true); + } + } + + /** + * Show a notification to the user + * @param key + */ + showNotification(key) { + const notificationTitle = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.title'); + const notificationErrorMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.error'); + const notificationExpiredMsg = this.translateService.get(this.MESSAGE_PREFIX + '.google-recaptcha.notification.message.expired'); + switch (key) { + case 'expired': + this.notificationsService.warning(notificationTitle, notificationExpiredMsg); + break; + case 'error': + this.notificationsService.error(notificationTitle, notificationErrorMsg); + break; + default: + } + } + } diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.html b/src/app/shared/google-recaptcha/google-recaptcha.component.html index 50bc51c808..315514d696 100644 --- a/src/app/shared/google-recaptcha/google-recaptcha.component.html +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.html @@ -1,4 +1,6 @@
\ No newline at end of file + [attr.data-size]="captchaMode === 'invisible' ? 'invisible' : null"> diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.ts b/src/app/shared/google-recaptcha/google-recaptcha.component.ts index f9c9b599f6..5b08e770db 100644 --- a/src/app/shared/google-recaptcha/google-recaptcha.component.ts +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.ts @@ -13,11 +13,16 @@ import { NativeWindowRef, NativeWindowService } from 'src/app/core/services/wind export class GoogleRecaptchaComponent implements OnInit { @Input() captchaMode: string; + /** * An EventEmitter that's fired whenever the form is being submitted */ @Output() executeRecaptcha: EventEmitter = new EventEmitter(); + @Output() checkboxChecked: EventEmitter = new EventEmitter(); + + @Output() showNotification: EventEmitter = new EventEmitter(); + recaptchaKey$: Observable; constructor( @@ -34,12 +39,27 @@ export class GoogleRecaptchaComponent implements OnInit { getFirstSucceededRemoteDataPayload(), ); if (this.captchaMode === 'invisible') { - this._window.nativeWindow.executeRecaptcha = this.execute; + this._window.nativeWindow.executeRecaptchaCallback = this.executeRecaptchaFcn; } + if (this.captchaMode === 'checkbox') { + this._window.nativeWindow.checkboxCheckedCallback = this.checkboxCheckedFcn; + } + this._window.nativeWindow.expiredCallback = this.notificationFcn('expired'); + this._window.nativeWindow.errorCallback = this.notificationFcn('error'); } - execute = (event) => { + executeRecaptchaFcn = (event) => { this.executeRecaptcha.emit(event); }; + checkboxCheckedFcn = (event) => { + this.checkboxChecked.emit(event); // todo fix con boolean + }; + + notificationFcn(key) { + return () => { + this.showNotification.emit(key); + }; + } + } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 024a3ca54b..ce0e4e0fa0 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: ", @@ -3206,7 +3206,15 @@ "register-page.registration.error.recaptcha": "Error when trying to authenticate with recaptcha", + "register-page.registration.google-recaptcha.must-accept-cookies": "In order to register you must accept the Registration and Password recovery (Google reCaptcha) cookies.", + "register-page.registration.google-recaptcha.open-cookie-settings": "Open cookie settings", + + "register-page.registration.google-recaptcha.notification.title": "Google reCaptcha", + + "register-page.registration.google-recaptcha.notification.message.error": "An error occurred during reCaptcha verification", + + "register-page.registration.google-recaptcha.notification.message.expired": "Verification expired. Please verify again.", "relationships.add.error.relationship-type.content": "No suitable match could be found for relationship type {{ type }} between the two items", From 8af725e76f8f66cf58023f63fe0f5b3033e892c9 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 9 Sep 2022 16:32:23 +0200 Subject: [PATCH 19/78] [UXP-10] Refactoring WIP --- .../register-email-form.component.html | 5 +-- .../register-email-form.component.ts | 32 +++++++++---------- .../google-recaptcha.component.html | 2 +- .../google-recaptcha.component.ts | 27 +++++++++------- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 8ade052708..63320db9d3 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -35,16 +35,17 @@

{{ MESSAGE_PREFIX + '.google-recaptcha.open-cookie-settings' | translate }}

-
+
- + invalid = {{form.invalid}}, disableUntilChecked = {{disableUntilChecked() | async}} diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 3338200eb9..dc82cec86a 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -11,7 +11,7 @@ import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; import { BehaviorSubject, combineLatest, Observable, of, switchMap } from 'rxjs'; -import { map, take, tap } from 'rxjs/operators'; +import { map, startWith, take, tap } from 'rxjs/operators'; import { CAPTCHA_NAME, GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; import { AlertType } from '../shared/alert/aletr-type'; import { KlaroService } from '../shared/cookies/klaro.service'; @@ -159,19 +159,18 @@ export class RegisterEmailFormComponent implements OnInit { } /** - * Return true if the user completed the reCaptcha verification (checkbox mode) + * Return true if the user has not completed the reCaptcha verification (checkbox mode) */ - isCheckboxChecked(): Observable { - return combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( - switchMap(([captchaVersion, captchaMode]) => { - if (captchaVersion === 'v2' && captchaMode === 'checkbox') { - return this.checkboxCheckedSubject$.asObservable(); - } else { - return of(true); - } - }), - tap(console.log), - tap(() => { this.changeDetectorRef.markForCheck(); }) + disableUntilChecked(): Observable { + const checked$ = this.checkboxCheckedSubject$.asObservable(); + return combineLatest([this.captchaVersion(), this.captchaMode(), checked$]).pipe( + // disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode + switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)), + startWith(true), + tap((res) => { + console.log('DISABLED = ' + res); + }), // TODO remove + // tap(() => { this.changeDetectorRef.markForCheck(); }), ); } @@ -179,10 +178,8 @@ export class RegisterEmailFormComponent implements OnInit { return this.form.get('email'); } - onCheckboxChecked($event) { - if (isNotEmpty($event)) { - this.checkboxCheckedSubject$.next(true); - } + onCheckboxChecked(checked: boolean) { + this.checkboxCheckedSubject$.next(checked); } /** @@ -201,6 +198,7 @@ export class RegisterEmailFormComponent implements OnInit { this.notificationsService.error(notificationTitle, notificationErrorMsg); break; default: + console.warn(`Unimplemented notification '${key}' from reCaptcha service`); } } diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.html b/src/app/shared/google-recaptcha/google-recaptcha.component.html index 315514d696..64c05cb739 100644 --- a/src/app/shared/google-recaptcha/google-recaptcha.component.html +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.html @@ -1,5 +1,5 @@
{ - this.executeRecaptcha.emit(event); - }; - - checkboxCheckedFcn = (event) => { - this.checkboxChecked.emit(event); // todo fix con boolean + dataCallbackFcn = ($event) => { + switch (this.captchaMode) { + case 'invisible': + this.executeRecaptcha.emit($event); + break; + case 'checkbox': + console.log('CB ' + isNotEmpty($event)); + this.checkboxChecked.emit(isNotEmpty($event)); // todo fix con boolean + break; + default: + console.error(`Invalid reCaptcha mode '${this.captchaMode}`); + this.showNotification.emit('error'); + } }; notificationFcn(key) { From 6dfc5ef2f5e95c7965ec25fb1772be0b2d6ea850 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 9 Sep 2022 17:04:01 +0200 Subject: [PATCH 20/78] [UXP-10] Handle captcha expiration --- .../google-recaptcha.component.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.ts b/src/app/shared/google-recaptcha/google-recaptcha.component.ts index 265ee96e02..6c3bb85808 100644 --- a/src/app/shared/google-recaptcha/google-recaptcha.component.ts +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.ts @@ -40,8 +40,8 @@ export class GoogleRecaptchaComponent implements OnInit { getFirstSucceededRemoteDataPayload(), ); this._window.nativeWindow.dataCallback = this.dataCallbackFcn; - this._window.nativeWindow.expiredCallback = this.notificationFcn('expired'); - this._window.nativeWindow.errorCallback = this.notificationFcn('error'); + this._window.nativeWindow.expiredCallback = this.expiredCallbackFcn; + this._window.nativeWindow.errorCallback = this.errorCallbackFcn; } dataCallbackFcn = ($event) => { @@ -59,10 +59,13 @@ export class GoogleRecaptchaComponent implements OnInit { } }; - notificationFcn(key) { - return () => { - this.showNotification.emit(key); - }; - } + expiredCallbackFcn = () => { + this.checkboxChecked.emit(false); + this.showNotification.emit('expired'); + }; + + errorCallbackFcn = () => { + this.showNotification.emit('error'); + }; } From 89a73208232bfa87bdfc06d73f8fbaea0ff19f56 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 9 Sep 2022 17:45:46 +0200 Subject: [PATCH 21/78] [UXP-10] Refactoring - Disable button fix --- .../register-email-form.component.html | 6 +++--- .../register-email-form.component.ts | 16 ++++++++++------ .../google-recaptcha.component.ts | 1 - 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 63320db9d3..323fdb4151 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -28,7 +28,6 @@
-

@@ -42,10 +41,9 @@ - - invalid = {{form.invalid}}, disableUntilChecked = {{disableUntilChecked() | async}} @@ -53,5 +51,7 @@ {{ MESSAGE_PREFIX + '.submit' | translate }} + + diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index dc82cec86a..023e2c7d98 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -11,7 +11,7 @@ import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { ConfigurationProperty } from '../core/shared/configuration-property.model'; import { isNotEmpty } from '../shared/empty.util'; import { BehaviorSubject, combineLatest, Observable, of, switchMap } from 'rxjs'; -import { map, startWith, take, tap } from 'rxjs/operators'; +import { map, startWith, take } from 'rxjs/operators'; import { CAPTCHA_NAME, GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; import { AlertType } from '../shared/alert/aletr-type'; import { KlaroService } from '../shared/cookies/klaro.service'; @@ -49,6 +49,8 @@ export class RegisterEmailFormComponent implements OnInit { */ checkboxCheckedSubject$ = new BehaviorSubject(false); + disableUntilChecked = true; + captchaVersion(): Observable { return this.googleRecaptchaService.captchaVersion(); } @@ -87,6 +89,12 @@ export class RegisterEmailFormComponent implements OnInit { ).subscribe((res: boolean) => { this.registrationVerification = res; }); + + this.disableUntilCheckedFcn().subscribe((res) => { + this.disableUntilChecked = res; + this.changeDetectorRef.detectChanges(); + }); + } /** @@ -161,16 +169,12 @@ export class RegisterEmailFormComponent implements OnInit { /** * Return true if the user has not completed the reCaptcha verification (checkbox mode) */ - disableUntilChecked(): Observable { + disableUntilCheckedFcn(): Observable { const checked$ = this.checkboxCheckedSubject$.asObservable(); return combineLatest([this.captchaVersion(), this.captchaMode(), checked$]).pipe( // disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)), startWith(true), - tap((res) => { - console.log('DISABLED = ' + res); - }), // TODO remove - // tap(() => { this.changeDetectorRef.markForCheck(); }), ); } diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.ts b/src/app/shared/google-recaptcha/google-recaptcha.component.ts index 6c3bb85808..980699046b 100644 --- a/src/app/shared/google-recaptcha/google-recaptcha.component.ts +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.ts @@ -50,7 +50,6 @@ export class GoogleRecaptchaComponent implements OnInit { this.executeRecaptcha.emit($event); break; case 'checkbox': - console.log('CB ' + isNotEmpty($event)); this.checkboxChecked.emit(isNotEmpty($event)); // todo fix con boolean break; default: From e57970349d146378b9b0a68980299ef14e72323d Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 9 Sep 2022 18:22:54 +0200 Subject: [PATCH 22/78] [UXP-10] Test fixed --- .../data/eperson-registration.service.spec.ts | 2 +- .../register-email-form.component.spec.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/core/data/eperson-registration.service.spec.ts b/src/app/core/data/eperson-registration.service.spec.ts index c7785302ef..afd4927103 100644 --- a/src/app/core/data/eperson-registration.service.spec.ts +++ b/src/app/core/data/eperson-registration.service.spec.ts @@ -94,7 +94,7 @@ describe('EpersonRegistrationService', () => { const expected = service.registerEmail('test@mail.org', 'afreshcaptchatoken'); let headers = new HttpHeaders(); const options: HttpOptions = Object.create({}); - headers = headers.append('X-Recaptcha-Token', 'afreshcaptchatoken'); + headers = headers.append('x-recaptcha-token', 'afreshcaptchatoken'); options.headers = headers; expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration, options)); diff --git a/src/app/register-email-form/register-email-form.component.spec.ts b/src/app/register-email-form/register-email-form.component.spec.ts index 55004c044b..bac922c73b 100644 --- a/src/app/register-email-form/register-email-form.component.spec.ts +++ b/src/app/register-email-form/register-email-form.component.spec.ts @@ -16,6 +16,8 @@ import { RegisterEmailFormComponent } from './register-email-form.component'; import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { ConfigurationDataService } from '../core/data/configuration-data.service'; import { GoogleRecaptchaService } from '../core/google-recaptcha/google-recaptcha.service'; +import { CookieService } from '../core/services/cookie.service'; +import { CookieServiceMock } from '../shared/mocks/cookie.service.mock'; describe('RegisterEmailComponent', () => { @@ -30,17 +32,18 @@ describe('RegisterEmailComponent', () => { findByPropertyName: jasmine.createSpy('findByPropertyName') }); - const googleRecaptchaService = jasmine.createSpyObj('googleRecaptchaService', { - getRecaptchaToken: Promise.resolve('googleRecaptchaToken'), - executeRecaptcha: Promise.resolve('googleRecaptchaToken'), - getRecaptchaTokenResponse: Promise.resolve('googleRecaptchaToken') - }); - const captchaVersion$ = of('v3'); const captchaMode$ = of('invisible'); const confResponse$ = createSuccessfulRemoteDataObject$({ values: ['true'] }); const confResponseDisabled$ = createSuccessfulRemoteDataObject$({ values: ['false'] }); + const googleRecaptchaService = jasmine.createSpyObj('googleRecaptchaService', { + getRecaptchaToken: Promise.resolve('googleRecaptchaToken'), + executeRecaptcha: Promise.resolve('googleRecaptchaToken'), + getRecaptchaTokenResponse: Promise.resolve('googleRecaptchaToken'), + captchaVersion: captchaVersion$, + captchaMode: captchaMode$, + }); beforeEach(waitForAsync(() => { router = new RouterStub(); @@ -59,6 +62,7 @@ describe('RegisterEmailComponent', () => { {provide: ConfigurationDataService, useValue: configurationDataService}, {provide: FormBuilder, useValue: new FormBuilder()}, {provide: NotificationsService, useValue: notificationsService}, + {provide: CookieService, useValue: new CookieServiceMock()}, {provide: GoogleRecaptchaService, useValue: googleRecaptchaService}, ], schemas: [CUSTOM_ELEMENTS_SCHEMA] From b02826d24d4c4e5b8ee168a475d620be086e63c5 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 9 Sep 2022 18:51:58 +0200 Subject: [PATCH 23/78] [UXP-10] Removed unused import --- src/app/core/google-recaptcha/google-recaptcha.module.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/google-recaptcha/google-recaptcha.module.ts b/src/app/core/google-recaptcha/google-recaptcha.module.ts index 8af9adb641..64620a48f4 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.module.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.module.ts @@ -1,7 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { GoogleRecaptchaComponent } from '../../shared/google-recaptcha/google-recaptcha.component'; -import { SharedModule } from '../../shared/shared.module'; import { GoogleRecaptchaService } from './google-recaptcha.service'; From f70fc7b87ac04cd4c591e9f6816d84582194385e Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 12 Sep 2022 17:42:37 +0200 Subject: [PATCH 24/78] 94233: use actual CSS variables in theme --- .../admin-sidebar-section.component.spec.ts | 2 +- .../admin-sidebar.component.spec.ts | 2 +- .../admin-sidebar/admin-sidebar.component.ts | 4 +- ...le-admin-sidebar-section.component.spec.ts | 2 +- ...andable-admin-sidebar-section.component.ts | 4 +- src/app/app.component.spec.ts | 2 +- src/app/app.component.ts | 21 ++--- src/app/app.reducer.ts | 2 +- src/app/core/core.module.ts | 2 +- src/app/root/root.component.spec.ts | 2 +- src/app/root/root.component.ts | 6 +- src/app/shared/host-window.service.ts | 10 +-- src/app/shared/key-value-pair.model.ts | 4 + ...per.actions.ts => css-variable.actions.ts} | 0 ...per.reducer.ts => css-variable.reducer.ts} | 2 +- .../sass-helper/css-variable.service.spec.ts | 78 +++++++++++++++++++ .../sass-helper/css-variable.service.ts | 71 +++++++++++++++++ .../shared/sass-helper/css-variable.utils.ts | 46 +++++++++++ .../shared/sass-helper/sass-helper.service.ts | 30 ------- 19 files changed, 226 insertions(+), 64 deletions(-) create mode 100644 src/app/shared/key-value-pair.model.ts rename src/app/shared/sass-helper/{sass-helper.actions.ts => css-variable.actions.ts} (100%) rename src/app/shared/sass-helper/{sass-helper.reducer.ts => css-variable.reducer.ts} (85%) create mode 100644 src/app/shared/sass-helper/css-variable.service.spec.ts create mode 100644 src/app/shared/sass-helper/css-variable.service.ts create mode 100644 src/app/shared/sass-helper/css-variable.utils.ts delete mode 100644 src/app/shared/sass-helper/sass-helper.service.ts diff --git a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts index 14d5d38199..1e28b62626 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MenuService } from '../../../shared/menu/menu.service'; import { MenuServiceStub } from '../../../shared/testing/menu-service.stub'; -import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service'; import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub'; import { Component } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts index 65026c1504..bcc40aae60 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -6,7 +6,7 @@ import { ScriptDataService } from '../../core/data/processes/script-data.service import { AdminSidebarComponent } from './admin-sidebar.component'; import { MenuService } from '../../shared/menu/menu.service'; import { MenuServiceStub } from '../../shared/testing/menu-service.stub'; -import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../../shared/sass-helper/css-variable.service'; import { CSSVariableServiceStub } from '../../shared/testing/css-variable-service.stub'; import { AuthServiceStub } from '../../shared/testing/auth-service.stub'; import { AuthService } from '../../core/auth/auth.service'; diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index c81b2e6e93..777fc4a250 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -18,7 +18,7 @@ import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model'; import { MenuComponent } from '../../shared/menu/menu.component'; import { MenuService } from '../../shared/menu/menu.service'; -import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../../shared/sass-helper/css-variable.service'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { Router, ActivatedRoute } from '@angular/router'; @@ -82,7 +82,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { ngOnInit(): void { this.createMenu(); super.ngOnInit(); - this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth'); + this.sidebarWidth = this.variableService.getVariable('--ds-sidebar-items-width'); this.authService.isAuthenticated() .subscribe((loggedIn: boolean) => { if (loggedIn) { diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts index b1f3a63c06..0f0181c3d5 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ExpandableAdminSidebarSectionComponent } from './expandable-admin-sidebar-section.component'; import { MenuService } from '../../../shared/menu/menu.service'; import { MenuServiceStub } from '../../../shared/testing/menu-service.stub'; -import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service'; import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub'; import { of as observableOf } from 'rxjs'; import { Component } from '@angular/core'; diff --git a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts index aaa6a85c51..59f07979bb 100644 --- a/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts +++ b/src/app/admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts @@ -2,7 +2,7 @@ import { Component, Inject, Injector, OnInit } from '@angular/core'; import { rotate } from '../../../shared/animations/rotate'; import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sidebar-section.component'; import { slide } from '../../../shared/animations/slide'; -import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service'; import { bgColor } from '../../../shared/animations/bgColor'; import { MenuID } from '../../../shared/menu/initial-menus-state'; import { MenuService } from '../../../shared/menu/menu.service'; @@ -65,7 +65,7 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC */ ngOnInit(): void { super.ngOnInit(); - this.sidebarActiveBg = this.variableService.getVariable('adminSidebarActiveBg'); + this.sidebarActiveBg = this.variableService.getVariable('--ds-admin-sidebar-active-bg'); this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID); this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID); this.expanded = combineLatestObservable(this.active, this.sidebarCollapsed, this.sidebarPreviewCollapsed) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index a892e34a5a..dcbf11525e 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -19,7 +19,7 @@ import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.ser import { AuthServiceMock } from './shared/mocks/auth.service.mock'; import { AuthService } from './core/auth/auth.service'; import { MenuService } from './shared/menu/menu.service'; -import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from './shared/sass-helper/css-variable.service'; import { CSSVariableServiceStub } from './shared/testing/css-variable-service.stub'; import { MenuServiceStub } from './shared/testing/menu-service.stub'; import { HostWindowService } from './shared/host-window.service'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 669411d9aa..0c38d2bd64 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -31,7 +31,7 @@ import { HostWindowState } from './shared/search/host-window.reducer'; import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { isAuthenticationBlocking } from './core/auth/selectors'; import { AuthService } from './core/auth/auth.service'; -import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from './shared/sass-helper/css-variable.service'; import { MenuService } from './shared/menu/menu.service'; import { HostWindowService } from './shared/host-window.service'; import { HeadTagConfig, ThemeConfig } from '../config/theme.model'; @@ -48,6 +48,7 @@ import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; +import { getCSSCustomPropIndex } from './shared/sass-helper/css-variable.utils'; @Component({ selector: 'ds-app', @@ -161,7 +162,6 @@ export class AppComponent implements OnInit, AfterViewInit { if (environment.debug) { console.info(environment); } - this.storeCSSVariables(); } ngOnInit() { @@ -181,18 +181,9 @@ export class AppComponent implements OnInit, AfterViewInit { } private storeCSSVariables() { - this.cssService.addCSSVariable('xlMin', '1200px'); - this.cssService.addCSSVariable('mdMin', '768px'); - this.cssService.addCSSVariable('lgMin', '576px'); - this.cssService.addCSSVariable('smMin', '0'); - this.cssService.addCSSVariable('adminSidebarActiveBg', '#0f1b28'); - this.cssService.addCSSVariable('sidebarItemsWidth', '250px'); - this.cssService.addCSSVariable('collapsedSidebarWidth', '53.234px'); - this.cssService.addCSSVariable('totalSidebarWidth', '303.234px'); - // const vars = variables.locals || {}; - // Object.keys(vars).forEach((name: string) => { - // this.cssService.addCSSVariable(name, vars[name]); - // }) + getCSSCustomPropIndex(this.document).forEach(([prop, val]) => { + this.cssService.addCSSVariable(prop, val); + }); } ngAfterViewInit() { @@ -282,6 +273,8 @@ export class AppComponent implements OnInit, AfterViewInit { } // the fact that this callback is used, proves we're on the browser. this.isThemeCSSLoading$.next(false); + + this.storeCSSVariables(); }; head.appendChild(link); } diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index 5bd4f745d9..23d38bae30 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -35,7 +35,7 @@ import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer'; -import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer'; +import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/css-variable.reducer'; import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer'; import { diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 8d8a614a89..2a4c75545e 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -23,7 +23,7 @@ import { NotificationsService } from '../shared/notifications/notifications.serv import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service'; import { ObjectSelectService } from '../shared/object-select/object-select.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { CSSVariableService } from '../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../shared/sass-helper/css-variable.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { SectionFormOperationsService } from '../submission/sections/form/section-form-operations.service'; diff --git a/src/app/root/root.component.spec.ts b/src/app/root/root.component.spec.ts index 81b22592d6..2d41b8b610 100644 --- a/src/app/root/root.component.spec.ts +++ b/src/app/root/root.component.spec.ts @@ -18,7 +18,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { RouterMock } from '../shared/mocks/router.mock'; import { MockActivatedRoute } from '../shared/mocks/active-router.mock'; import { MenuService } from '../shared/menu/menu.service'; -import { CSSVariableService } from '../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../shared/sass-helper/css-variable.service'; import { CSSVariableServiceStub } from '../shared/testing/css-variable-service.stub'; import { HostWindowService } from '../shared/host-window.service'; import { HostWindowServiceStub } from '../shared/testing/host-window-service.stub'; diff --git a/src/app/root/root.component.ts b/src/app/root/root.component.ts index dc44095573..8584af357e 100644 --- a/src/app/root/root.component.ts +++ b/src/app/root/root.component.ts @@ -11,7 +11,7 @@ import { MetadataService } from '../core/metadata/metadata.service'; import { HostWindowState } from '../shared/search/host-window.reducer'; import { NativeWindowRef, NativeWindowService } from '../core/services/window.service'; import { AuthService } from '../core/auth/auth.service'; -import { CSSVariableService } from '../shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../shared/sass-helper/css-variable.service'; import { MenuService } from '../shared/menu/menu.service'; import { MenuID } from '../shared/menu/initial-menus-state'; import { HostWindowService } from '../shared/host-window.service'; @@ -65,8 +65,8 @@ export class RootComponent implements OnInit { ngOnInit() { this.sidebarVisible = this.menuService.isMenuVisible(MenuID.ADMIN); - this.collapsedSidebarWidth = this.cssService.getVariable('collapsedSidebarWidth'); - this.totalSidebarWidth = this.cssService.getVariable('totalSidebarWidth'); + this.collapsedSidebarWidth = this.cssService.getVariable('--ds-collapsed-sidebar-width'); + this.totalSidebarWidth = this.cssService.getVariable('--ds-total-sidebar-width'); const sidebarCollapsed = this.menuService.isMenuCollapsed(MenuID.ADMIN); this.slideSidebarOver = combineLatestObservable([sidebarCollapsed, this.windowService.isXsOrSm()]) diff --git a/src/app/shared/host-window.service.ts b/src/app/shared/host-window.service.ts index 443dd40b14..6d13d921e0 100644 --- a/src/app/shared/host-window.service.ts +++ b/src/app/shared/host-window.service.ts @@ -7,7 +7,7 @@ import { createSelector, select, Store } from '@ngrx/store'; import { hasValue } from './empty.util'; import { AppState } from '../app.reducer'; -import { CSSVariableService } from './sass-helper/sass-helper.service'; +import { CSSVariableService } from './sass-helper/css-variable.service'; export enum WidthCategory { XS, @@ -31,10 +31,10 @@ export class HostWindowService { /* See _exposed_variables.scss */ variableService.getAllVariables() .subscribe((variables) => { - this.breakPoints.XL_MIN = parseInt(variables.xlMin, 10); - this.breakPoints.LG_MIN = parseInt(variables.lgMin, 10); - this.breakPoints.MD_MIN = parseInt(variables.mdMin, 10); - this.breakPoints.SM_MIN = parseInt(variables.smMin, 10); + this.breakPoints.XL_MIN = parseInt(variables['--bs-xl-min'], 10); + this.breakPoints.LG_MIN = parseInt(variables['--bs-lg-min'], 10); + this.breakPoints.MD_MIN = parseInt(variables['--bs-md-min'], 10); + this.breakPoints.SM_MIN = parseInt(variables['--bs-sm-min'], 10); }); } diff --git a/src/app/shared/key-value-pair.model.ts b/src/app/shared/key-value-pair.model.ts new file mode 100644 index 0000000000..6fb63070ad --- /dev/null +++ b/src/app/shared/key-value-pair.model.ts @@ -0,0 +1,4 @@ +export interface KeyValuePair { + key: K; + value: V; +} diff --git a/src/app/shared/sass-helper/sass-helper.actions.ts b/src/app/shared/sass-helper/css-variable.actions.ts similarity index 100% rename from src/app/shared/sass-helper/sass-helper.actions.ts rename to src/app/shared/sass-helper/css-variable.actions.ts diff --git a/src/app/shared/sass-helper/sass-helper.reducer.ts b/src/app/shared/sass-helper/css-variable.reducer.ts similarity index 85% rename from src/app/shared/sass-helper/sass-helper.reducer.ts rename to src/app/shared/sass-helper/css-variable.reducer.ts index 6f080619fa..a196304c92 100644 --- a/src/app/shared/sass-helper/sass-helper.reducer.ts +++ b/src/app/shared/sass-helper/css-variable.reducer.ts @@ -1,4 +1,4 @@ -import { CSSVariableAction, CSSVariableActionTypes } from './sass-helper.actions'; +import { CSSVariableAction, CSSVariableActionTypes } from './css-variable.actions'; export interface CSSVariablesState { [name: string]: string; diff --git a/src/app/shared/sass-helper/css-variable.service.spec.ts b/src/app/shared/sass-helper/css-variable.service.spec.ts new file mode 100644 index 0000000000..4fd33591f5 --- /dev/null +++ b/src/app/shared/sass-helper/css-variable.service.spec.ts @@ -0,0 +1,78 @@ +import { TestBed } from '@angular/core/testing'; +import { CSSVariableService } from './css-variable.service'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { getTestScheduler } from 'jasmine-marbles'; +import { buildPaginatedList } from '../../core/data/paginated-list.model'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { KeyValuePair } from '../key-value-pair.model'; + +describe('CSSVariableService', () => { + let store: MockStore; + + let service: CSSVariableService; + let initialState; + const varKey1 = '--test-1'; + const varValue1 = 'test-value-1'; + const varKey2 = '--test-2'; + const varValue2 = 'test-value-2'; + const varKey3 = '--test-3'; + const varValue3 = 'test-value-3'; + const queryInAll = 'test'; + const queryFor3 = '3'; + + function init() { + initialState = { + ['cssVariables']: { + [varKey1]: varValue1, + [varKey2]: varValue2, + [varKey3]: varValue3, + } + }; + } + + beforeEach(() => { + init(); + TestBed.configureTestingModule({ + providers: [ + CSSVariableService, + provideMockStore({ initialState }), + ], + }); + service = TestBed.inject(CSSVariableService as any); + store = TestBed.inject(MockStore as any); + }); + + it('should create', () => { + expect(service).toBeTruthy(); + }); + + fdescribe('searchVariable', () => { + it('should return the right keys and variables in a paginated list for query that returns all 3 results', () => { + const currentPage = 1; + const pageSize = 5; + const pageInfo = new PageInfo({ currentPage, elementsPerPage: pageSize, totalPages: 1, totalElements: 3 }); + const page: KeyValuePair[] = [{ key: varKey1, value: varValue1 }, { key: varKey2, value: varValue2 }, { key: varKey3, value: varValue3 }]; + const result = buildPaginatedList(pageInfo, page); + getTestScheduler().expectObservable(service.searchVariable(queryInAll, { currentPage, pageSize } as any)).toBe('a', { a: result }); + }); + + it('should return the right keys and variables in a paginated list for query that returns only the 3rd results', () => { + const currentPage = 1; + const pageSize = 5; + const pageInfo = new PageInfo({ currentPage, elementsPerPage: pageSize, totalPages: 1, totalElements: 1 }); + const page: KeyValuePair[] = [{ key: varKey3, value: varValue3 }]; + const result = buildPaginatedList(pageInfo, page); + getTestScheduler().expectObservable(service.searchVariable(queryFor3, { currentPage, pageSize } as any)).toBe('a', { a: result }); + }); + + it('should return the right keys and variables in a paginated list that\'s not longer than the page size', () => { + const currentPage = 1; + const pageSize = 2; + const pageInfo = new PageInfo({ currentPage, elementsPerPage: pageSize, totalPages: 2, totalElements: 3 }); + const page: KeyValuePair[] = [{ key: varKey1, value: varValue1 }, { key: varKey2, value: varValue2 }]; + const result = buildPaginatedList(pageInfo, page); + getTestScheduler().expectObservable(service.searchVariable(queryInAll, { currentPage, pageSize } as any)).toBe('a', { a: result }); + }); + }); + +}); diff --git a/src/app/shared/sass-helper/css-variable.service.ts b/src/app/shared/sass-helper/css-variable.service.ts new file mode 100644 index 0000000000..3e0ed7f214 --- /dev/null +++ b/src/app/shared/sass-helper/css-variable.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@angular/core'; +import { AppState, keySelector } from '../../app.reducer'; +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; +import { AddCSSVariableAction } from './css-variable.actions'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; +import { Observable } from 'rxjs'; +import { hasValue } from '../empty.util'; +import { KeyValuePair } from '../key-value-pair.model'; +import { PageInfo } from '../../core/shared/page-info.model'; + +@Injectable() +export class CSSVariableService { + constructor( + protected store: Store) { + } + + addCSSVariable(name: string, value: string) { + this.store.dispatch(new AddCSSVariableAction(name, value)); + } + + getVariable(name: string) { + return this.store.pipe(select(themeVariableByNameSelector(name))); + } + + getAllVariables() { + return this.store.pipe(select(themeVariablesSelector)); + } + + searchVariable(query: string, paginationOptions: PaginationComponentOptions): Observable>> { + return this.store.pipe(select(themePaginatedVariablesByQuery(query, paginationOptions))); + } +} + +const themeVariablesSelector = (state: AppState) => state.cssVariables; + +const themeVariableByNameSelector = (name: string): MemoizedSelector => { + return keySelector(name, themeVariablesSelector); +}; + +// Split this up into two memoized selectors so the query search gets cached separately from the pagination, +// since the entire list has to be retrieved every time anyway +const themePaginatedVariablesByQuery = (query: string, pagination: PaginationComponentOptions): MemoizedSelector>> => { + return createSelector(themeVariablesByQuery(query), (pairs) => { + if (hasValue(pairs)) { + const { currentPage, pageSize } = pagination; + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + const pairsPage = pairs.slice(startIndex, endIndex); + const totalPages = Math.ceil(pairs.length / pageSize); + const pageInfo = new PageInfo({ currentPage, elementsPerPage: pageSize, totalElements: pairs.length, totalPages }); + return buildPaginatedList(pageInfo, pairsPage); + } else { + return undefined; + } + }); +}; + +const themeVariablesByQuery = (query: string): MemoizedSelector[]> => { + return createSelector(themeVariablesSelector, (state) => { + if (hasValue(state)) { + return Object.keys(state) + .filter((key: string) => key.includes(query)) + .map((key: string) => { + return { key, value: state[key] }; + }); + } else { + return undefined; + } + }); +}; diff --git a/src/app/shared/sass-helper/css-variable.utils.ts b/src/app/shared/sass-helper/css-variable.utils.ts new file mode 100644 index 0000000000..63a93b8bc4 --- /dev/null +++ b/src/app/shared/sass-helper/css-variable.utils.ts @@ -0,0 +1,46 @@ +// Uses code from https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/ + +const isSameDomain = (styleSheet) => { + // Internal style blocks won't have an href value + if (!styleSheet.href) { + return true; + } + + return styleSheet.href.indexOf(window.location.origin) === 0; +}; + +/* + Determine if the given rule is a CSSStyleRule + See: https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants +*/ +const isStyleRule = (rule) => rule.type === 1; + +/** + * Get all custom properties on a page + * @return array + * ex; [["--color-accent", "#b9f500"], ["--color-text", "#252525"], ...] + */ +export const getCSSCustomPropIndex = (document: Document) => + // styleSheets is array-like, so we convert it to an array. + // Filter out any stylesheets not on this domain + [...document.styleSheets] + .filter(isSameDomain) + .reduce( + (finalArr, sheet) => + finalArr.concat( + // cssRules is array-like, so we convert it to an array + [...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule: any) => { + const props = [...rule.style] + .map((propName) => [ + propName.trim(), + rule.style.getPropertyValue(propName).trim() + ]) + // Discard any props that don't start with "--". Custom props are required to. + .filter(([propName]) => propName.indexOf('--') === 0); + + return [...propValArr, ...props]; + }, []) + ), + [] + ); + diff --git a/src/app/shared/sass-helper/sass-helper.service.ts b/src/app/shared/sass-helper/sass-helper.service.ts deleted file mode 100644 index 7cc83dab2d..0000000000 --- a/src/app/shared/sass-helper/sass-helper.service.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Injectable } from '@angular/core'; -import { AppState, keySelector } from '../../app.reducer'; -import { MemoizedSelector, select, Store } from '@ngrx/store'; -import { AddCSSVariableAction } from './sass-helper.actions'; - -@Injectable() -export class CSSVariableService { - constructor( - protected store: Store) { - } - - addCSSVariable(name: string, value: string) { - this.store.dispatch(new AddCSSVariableAction(name, value)); - } - - getVariable(name: string) { - return this.store.pipe(select(themeVariableByNameSelector(name))); - } - - getAllVariables() { - return this.store.pipe(select(themeVariablesSelector)); - } - -} - -const themeVariablesSelector = (state: AppState) => state.cssVariables; - -const themeVariableByNameSelector = (name: string): MemoizedSelector => { - return keySelector(name, themeVariablesSelector); -}; From ac094aa623bfb1d83e540f82ff4a6487287cffd4 Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 13 Sep 2022 12:22:55 +0200 Subject: [PATCH 25/78] 94233: added typedoc --- .../sass-helper/css-variable.reducer.ts | 8 ++++++-- .../sass-helper/css-variable.service.ts | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/app/shared/sass-helper/css-variable.reducer.ts b/src/app/shared/sass-helper/css-variable.reducer.ts index a196304c92..a2d150dabf 100644 --- a/src/app/shared/sass-helper/css-variable.reducer.ts +++ b/src/app/shared/sass-helper/css-variable.reducer.ts @@ -6,12 +6,16 @@ export interface CSSVariablesState { const initialState: CSSVariablesState = Object.create({}); +/** + * Reducer that handles the state of CSS variables in the store + * @param state The current state of the store + * @param action The action to apply onto the current state of the store + */ export function cssVariablesReducer(state = initialState, action: CSSVariableAction): CSSVariablesState { switch (action.type) { case CSSVariableActionTypes.ADD: { const variable = action.payload; - const t = Object.assign({}, state, { [variable.name]: variable.value }); - return t; + return Object.assign({}, state, { [variable.name]: variable.value }); } default: { return state; diff --git a/src/app/shared/sass-helper/css-variable.service.ts b/src/app/shared/sass-helper/css-variable.service.ts index 3e0ed7f214..bdcba8503e 100644 --- a/src/app/shared/sass-helper/css-variable.service.ts +++ b/src/app/shared/sass-helper/css-variable.service.ts @@ -9,24 +9,44 @@ import { hasValue } from '../empty.util'; import { KeyValuePair } from '../key-value-pair.model'; import { PageInfo } from '../../core/shared/page-info.model'; +/** + * This service deals with adding and retrieving CSS variables to and from the store + */ @Injectable() export class CSSVariableService { constructor( protected store: Store) { } + /** + * Adds a CSS variable to the store + * @param name The name/key of the CSS variable + * @param value The value of the CSS variable + */ addCSSVariable(name: string, value: string) { this.store.dispatch(new AddCSSVariableAction(name, value)); } + /** + * Returns the value of a specific CSS key + * @param name The name/key of the CSS value + */ getVariable(name: string) { return this.store.pipe(select(themeVariableByNameSelector(name))); } + /** + * Returns the CSSVariablesState of the store containing all variables + */ getAllVariables() { return this.store.pipe(select(themeVariablesSelector)); } + /** + * Method to find CSS variables by their partially supplying their key. Case sensitive. Returns a paginated list of KeyValuePairs with CSS variables that match the query. + * @param query The query to look for in the keys + * @param paginationOptions The pagination options for the requested page + */ searchVariable(query: string, paginationOptions: PaginationComponentOptions): Observable>> { return this.store.pipe(select(themePaginatedVariablesByQuery(query, paginationOptions))); } From 183653112e41fd07251f862a889f82d228a11642 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 15 Sep 2022 10:40:59 +0200 Subject: [PATCH 26/78] [UXP-10] e2e test fixed --- cypress/support/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 024b46cdde..2d4f6d8fd3 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -24,7 +24,7 @@ import 'cypress-axe'; beforeEach(() => { // Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie // This just ensures it doesn't get in the way of matching other objects in the page. - cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true}'); + cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22registration-password-recovery%22:true}'); }); // For better stability between tests, we visit "about:blank" (i.e. blank page) after each test. From 70f2625d15dd43ac6868eee02516a414b5fbf6b2 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 15 Sep 2022 13:57:06 +0200 Subject: [PATCH 27/78] [UXP-10] e2e test fix --- cypress/support/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 2d4f6d8fd3..70da23f044 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -24,7 +24,7 @@ import 'cypress-axe'; beforeEach(() => { // Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie // This just ensures it doesn't get in the way of matching other objects in the page. - cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22registration-password-recovery%22:true}'); + cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}'); }); // For better stability between tests, we visit "about:blank" (i.e. blank page) after each test. From 89ba24fc1c1df13cfecdf12ebe59883901980d28 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 16 Sep 2022 12:40:04 +0200 Subject: [PATCH 28/78] 94233: applied feedback for css variables --- src/app/app.component.ts | 6 +- .../sass-helper/css-variable.actions.ts | 20 +++++- .../sass-helper/css-variable.reducer.ts | 6 ++ .../sass-helper/css-variable.service.ts | 71 ++++++++++++++++++- .../shared/sass-helper/css-variable.utils.ts | 44 +----------- 5 files changed, 96 insertions(+), 51 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0c38d2bd64..32753ce74c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -48,7 +48,6 @@ import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service'; import { IdleModalComponent } from './shared/idle-modal/idle-modal.component'; import { getDefaultThemeConfig } from '../config/config.util'; import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface'; -import { getCSSCustomPropIndex } from './shared/sass-helper/css-variable.utils'; @Component({ selector: 'ds-app', @@ -181,9 +180,8 @@ export class AppComponent implements OnInit, AfterViewInit { } private storeCSSVariables() { - getCSSCustomPropIndex(this.document).forEach(([prop, val]) => { - this.cssService.addCSSVariable(prop, val); - }); + this.cssService.clearCSSVariables(); + this.cssService.addCSSVariables(this.cssService.getCSSVariablesFromStylesheets(this.document)); } ngAfterViewInit() { diff --git a/src/app/shared/sass-helper/css-variable.actions.ts b/src/app/shared/sass-helper/css-variable.actions.ts index 144904646e..93225f9426 100644 --- a/src/app/shared/sass-helper/css-variable.actions.ts +++ b/src/app/shared/sass-helper/css-variable.actions.ts @@ -1,5 +1,6 @@ import { Action } from '@ngrx/store'; import { type } from '../ngrx/type'; +import { KeyValuePair } from '../key-value-pair.model'; /** * For each action type in an action group, make a simple @@ -11,6 +12,8 @@ import { type } from '../ngrx/type'; */ export const CSSVariableActionTypes = { ADD: type('dspace/css-variables/ADD'), + ADD_ALL: type('dspace/css-variables/ADD_ALL'), + CLEAR: type('dspace/css-variables/CLEAR'), }; export class AddCSSVariableAction implements Action { @@ -24,5 +27,20 @@ export class AddCSSVariableAction implements Action { this.payload = {name, value}; } } +export class AddAllCSSVariablesAction implements Action { + type = CSSVariableActionTypes.ADD_ALL; + payload: KeyValuePair[]; -export type CSSVariableAction = AddCSSVariableAction; + constructor(variables: KeyValuePair[]) { + this.payload = variables; + } +} + +export class ClearCSSVariablesAction implements Action { + type = CSSVariableActionTypes.CLEAR; + + constructor() { + } +} + +export type CSSVariableAction = AddCSSVariableAction | AddAllCSSVariablesAction | ClearCSSVariablesAction; diff --git a/src/app/shared/sass-helper/css-variable.reducer.ts b/src/app/shared/sass-helper/css-variable.reducer.ts index a2d150dabf..405cbf5df4 100644 --- a/src/app/shared/sass-helper/css-variable.reducer.ts +++ b/src/app/shared/sass-helper/css-variable.reducer.ts @@ -1,4 +1,5 @@ import { CSSVariableAction, CSSVariableActionTypes } from './css-variable.actions'; +import { KeyValuePair } from '../key-value-pair.model'; export interface CSSVariablesState { [name: string]: string; @@ -16,6 +17,11 @@ export function cssVariablesReducer(state = initialState, action: CSSVariableAct case CSSVariableActionTypes.ADD: { const variable = action.payload; return Object.assign({}, state, { [variable.name]: variable.value }); + } case CSSVariableActionTypes.ADD_ALL: { + const variables = action.payload; + return Object.assign({}, state, ...variables.map(({ key, value }: KeyValuePair) => {return {[key]: value}})); + } case CSSVariableActionTypes.CLEAR: { + return initialState; } default: { return state; diff --git a/src/app/shared/sass-helper/css-variable.service.ts b/src/app/shared/sass-helper/css-variable.service.ts index bdcba8503e..9ba9dfca3c 100644 --- a/src/app/shared/sass-helper/css-variable.service.ts +++ b/src/app/shared/sass-helper/css-variable.service.ts @@ -1,19 +1,35 @@ import { Injectable } from '@angular/core'; import { AppState, keySelector } from '../../app.reducer'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; -import { AddCSSVariableAction } from './css-variable.actions'; +import { AddAllCSSVariablesAction, AddCSSVariableAction, ClearCSSVariablesAction } from './css-variable.actions'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; import { Observable } from 'rxjs'; import { hasValue } from '../empty.util'; import { KeyValuePair } from '../key-value-pair.model'; import { PageInfo } from '../../core/shared/page-info.model'; +import { CSSVariablesState } from './css-variable.reducer'; /** * This service deals with adding and retrieving CSS variables to and from the store */ @Injectable() export class CSSVariableService { + isSameDomain = (styleSheet) => { + // Internal style blocks won't have an href value + if (!styleSheet.href) { + return true; + } + + return styleSheet.href.indexOf(window.location.origin) === 0; + }; + + /* + Determine if the given rule is a CSSStyleRule + See: https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants + */ + isStyleRule = (rule) => rule.type === 1; + constructor( protected store: Store) { } @@ -27,18 +43,33 @@ export class CSSVariableService { this.store.dispatch(new AddCSSVariableAction(name, value)); } + /** + * Adds multiples CSS variables to the store + * @param variables The key-value pairs with the CSS variables to be added + */ + addCSSVariables(variables: KeyValuePair[]) { + this.store.dispatch(new AddAllCSSVariablesAction(variables)); + } + + /** + * Clears all CSS variables ƒrom the store + */ + clearCSSVariables() { + this.store.dispatch(new ClearCSSVariablesAction()); + } + /** * Returns the value of a specific CSS key * @param name The name/key of the CSS value */ - getVariable(name: string) { + getVariable(name: string): Observable { return this.store.pipe(select(themeVariableByNameSelector(name))); } /** * Returns the CSSVariablesState of the store containing all variables */ - getAllVariables() { + getAllVariables(): Observable { return this.store.pipe(select(themeVariablesSelector)); } @@ -50,6 +81,40 @@ export class CSSVariableService { searchVariable(query: string, paginationOptions: PaginationComponentOptions): Observable>> { return this.store.pipe(select(themePaginatedVariablesByQuery(query, paginationOptions))); } + + /** + * Get all custom properties on a page + * @return array> + * ex; [{key: "--color-accent", value: "#b9f500"}, {key: "--color-text", value: "#252525"}, ...] + */ + getCSSVariablesFromStylesheets(document: Document): KeyValuePair[] + { + // styleSheets is array-like, so we convert it to an array. + // Filter out any stylesheets not on this domain + return [...document.styleSheets] + .filter(this.isSameDomain) + .reduce( + (finalArr, sheet) => + finalArr.concat( + // cssRules is array-like, so we convert it to an array + [...sheet.cssRules].filter(this.isStyleRule).reduce((propValArr, rule: any) => { + const props = [...rule.style] + .map((propName) => { + return { + key: propName.trim(), + value: rule.style.getPropertyValue(propName).trim() + } as KeyValuePair; + } + ) + // Discard any props that don't start with "--". Custom props are required to. + .filter(({ key }: KeyValuePair) => key.indexOf('--') === 0); + + return [...propValArr, ...props]; + }, []) + ), + [] + ); + } } const themeVariablesSelector = (state: AppState) => state.cssVariables; diff --git a/src/app/shared/sass-helper/css-variable.utils.ts b/src/app/shared/sass-helper/css-variable.utils.ts index 63a93b8bc4..05e3074f98 100644 --- a/src/app/shared/sass-helper/css-variable.utils.ts +++ b/src/app/shared/sass-helper/css-variable.utils.ts @@ -1,46 +1,4 @@ // Uses code from https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/ -const isSameDomain = (styleSheet) => { - // Internal style blocks won't have an href value - if (!styleSheet.href) { - return true; - } - - return styleSheet.href.indexOf(window.location.origin) === 0; -}; - -/* - Determine if the given rule is a CSSStyleRule - See: https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants -*/ -const isStyleRule = (rule) => rule.type === 1; - -/** - * Get all custom properties on a page - * @return array - * ex; [["--color-accent", "#b9f500"], ["--color-text", "#252525"], ...] - */ -export const getCSSCustomPropIndex = (document: Document) => - // styleSheets is array-like, so we convert it to an array. - // Filter out any stylesheets not on this domain - [...document.styleSheets] - .filter(isSameDomain) - .reduce( - (finalArr, sheet) => - finalArr.concat( - // cssRules is array-like, so we convert it to an array - [...sheet.cssRules].filter(isStyleRule).reduce((propValArr, rule: any) => { - const props = [...rule.style] - .map((propName) => [ - propName.trim(), - rule.style.getPropertyValue(propName).trim() - ]) - // Discard any props that don't start with "--". Custom props are required to. - .filter(([propName]) => propName.indexOf('--') === 0); - - return [...propValArr, ...props]; - }, []) - ), - [] - ); +import { KeyValuePair } from '../key-value-pair.model'; From ce67003f26478f2ef225c82e0466642454a0cd6d Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 16 Sep 2022 16:23:11 +0200 Subject: [PATCH 29/78] 94233: fixed tests after CSSVariableService changes --- src/app/shared/sass-helper/css-variable.service.spec.ts | 2 +- src/app/shared/testing/css-variable-service.stub.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/shared/sass-helper/css-variable.service.spec.ts b/src/app/shared/sass-helper/css-variable.service.spec.ts index 4fd33591f5..559384a5d7 100644 --- a/src/app/shared/sass-helper/css-variable.service.spec.ts +++ b/src/app/shared/sass-helper/css-variable.service.spec.ts @@ -46,7 +46,7 @@ describe('CSSVariableService', () => { expect(service).toBeTruthy(); }); - fdescribe('searchVariable', () => { + describe('searchVariable', () => { it('should return the right keys and variables in a paginated list for query that returns all 3 results', () => { const currentPage = 1; const pageSize = 5; diff --git a/src/app/shared/testing/css-variable-service.stub.ts b/src/app/shared/testing/css-variable-service.stub.ts index 6159d89655..f72e338455 100644 --- a/src/app/shared/testing/css-variable-service.stub.ts +++ b/src/app/shared/testing/css-variable-service.stub.ts @@ -1,10 +1,10 @@ import { Observable, of as observableOf } from 'rxjs'; const variables = { - smMin: '576px,', - mdMin: '768px,', - lgMin: '992px', - xlMin: '1200px', + '--bs-sm-min': '576px,', + '--bs-md-min': '768px,', + '--bs-lg-min': '992px', + '--bs-xl-min': '1200px', } as any; export class CSSVariableServiceStub { From 4484c3ebbc9139abc2fe2458947803c58166785b Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Mon, 19 Sep 2022 16:26:31 +0200 Subject: [PATCH 30/78] [CST-6782] Fixed failing tests --- .../cookies/browser-klaro.service.spec.ts | 63 +++++++++++++++---- .../shared/cookies/browser-klaro.service.ts | 4 +- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.spec.ts b/src/app/shared/cookies/browser-klaro.service.spec.ts index df4ac7ebd2..d65b1477d7 100644 --- a/src/app/shared/cookies/browser-klaro.service.spec.ts +++ b/src/app/shared/cookies/browser-klaro.service.spec.ts @@ -262,8 +262,10 @@ describe('BrowserKlaroService', () => { describe('initialize google analytics configuration', () => { let GOOGLE_ANALYTICS_KEY; + let REGISTRATION_VERIFICATION_ENABLED_KEY; beforeEach(() => { GOOGLE_ANALYTICS_KEY = clone((service as any).GOOGLE_ANALYTICS_KEY); + REGISTRATION_VERIFICATION_ENABLED_KEY = clone((service as any).REGISTRATION_VERIFICATION_ENABLED_KEY); spyOn((service as any), 'getUser$').and.returnValue(observableOf(user)); translateService.get.and.returnValue(observableOf('loading...')); spyOn(service, 'addAppMessages'); @@ -292,27 +294,64 @@ describe('BrowserKlaroService', () => { 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(), - name: googleAnalytics, - values: [], - })); + configurationDataService.findByPropertyName = + jasmine.createSpy() + .withArgs(GOOGLE_ANALYTICS_KEY) + .and + .returnValue( + createSuccessfulRemoteDataObject$({ + ... new ConfigurationProperty(), + name: googleAnalytics, + values: [], + } + ) + ) + .withArgs(REGISTRATION_VERIFICATION_ENABLED_KEY) + .and + .returnValue( + createSuccessfulRemoteDataObject$({ + ... new ConfigurationProperty(), + name: trackingIdTestValue, + values: ['false'], + }) + ); service.initialize(); 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') - ); + configurationDataService.findByPropertyName = + jasmine.createSpy() + .withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue( + createFailedRemoteDataObject$('Error while loading GA') + ) + .withArgs(REGISTRATION_VERIFICATION_ENABLED_KEY) + .and + .returnValue( + createSuccessfulRemoteDataObject$({ + ... new ConfigurationProperty(), + name: trackingIdTestValue, + values: ['false'], + }) + ); service.initialize(); 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) - ); + configurationDataService.findByPropertyName = + jasmine.createSpy() + .withArgs(GOOGLE_ANALYTICS_KEY).and.returnValue( + createSuccessfulRemoteDataObject$(null) + ) + .withArgs(REGISTRATION_VERIFICATION_ENABLED_KEY) + .and + .returnValue( + createSuccessfulRemoteDataObject$({ + ... new ConfigurationProperty(), + name: trackingIdTestValue, + values: ['false'], + }) + ); service.initialize(); 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 32a8258ab3..8932b8be0e 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -50,6 +50,8 @@ export class BrowserKlaroService extends KlaroService { private readonly GOOGLE_ANALYTICS_KEY = 'google.analytics.key'; + private readonly REGISTRATION_VERIFICATION_ENABLED_KEY = 'registration.verification.enabled'; + private readonly GOOGLE_ANALYTICS_SERVICE_NAME = 'google-analytics'; /** @@ -90,7 +92,7 @@ export class BrowserKlaroService extends KlaroService { }), ); - this.configService.findByPropertyName('registration.verification.enabled').pipe( + this.configService.findByPropertyName(this.REGISTRATION_VERIFICATION_ENABLED_KEY).pipe( getFirstCompletedRemoteData(), ).subscribe((remoteData) => { if (remoteData.statusCode === 404 || isEmpty(remoteData.payload?.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { From 6d3f3cad2f3bc5ce8b5f9ae3113ab9f1084bcf7e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 20 Sep 2022 18:04:48 +0200 Subject: [PATCH 31/78] [CST-6876] Retrieve filter list in parent search.component --- .../search-filters.component.spec.ts | 23 ------------- .../search-filters.component.ts | 20 ++---------- .../search-sidebar.component.html | 1 + .../search-sidebar.component.ts | 9 +++++- src/app/shared/search/search.component.html | 2 ++ .../shared/search/search.component.spec.ts | 32 ++++++++++++++++++- src/app/shared/search/search.component.ts | 30 +++++++++++++++-- 7 files changed, 73 insertions(+), 44 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filters.component.spec.ts b/src/app/shared/search/search-filters/search-filters.component.spec.ts index ec1a51a1c4..522459b603 100644 --- a/src/app/shared/search/search-filters/search-filters.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filters.component.spec.ts @@ -7,7 +7,6 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; import { SearchFiltersComponent } from './search-filters.component'; import { SearchService } from '../../../core/shared/search/search.service'; -import { of as observableOf, Subject } from 'rxjs'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub'; @@ -64,26 +63,4 @@ describe('SearchFiltersComponent', () => { }); }); - describe('when refreshSearch observable is present and emit events', () => { - - let refreshFiltersEmitter: Subject; - - beforeEach(() => { - spyOn(comp, 'initFilters').and.callFake(() => { /****/}); - - refreshFiltersEmitter = new Subject(); - comp.refreshFilters = refreshFiltersEmitter.asObservable(); - comp.ngOnInit(); - }); - - it('should reinitialize search filters', () => { - - expect(comp.initFilters).toHaveBeenCalledTimes(1); - - refreshFiltersEmitter.next(null); - - expect(comp.initFilters).toHaveBeenCalledTimes(2); - }); - }); - }); diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index 548103a320..a7d1f7bd8b 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { SearchService } from '../../../core/shared/search/search.service'; @@ -8,10 +9,8 @@ import { RemoteData } from '../../../core/data/remote-data'; import { SearchFilterConfig } from '../models/search-filter-config.model'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; -import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { currentPath } from '../../utils/route.utils'; -import { Router } from '@angular/router'; import { hasValue } from '../../empty.util'; @Component({ @@ -28,7 +27,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { /** * An observable containing configuration about which filters are shown and how they are shown */ - filters: Observable>; + @Input() filters: Observable>; /** * List of all filters that are currently active with their value set to null. @@ -78,13 +77,6 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { } ngOnInit(): void { - - this.initFilters(); - - if (this.refreshFilters) { - this.subs.push(this.refreshFilters.subscribe(() => this.initFilters())); - } - this.clearParams = this.searchConfigService.getCurrentFrontendFilters().pipe(map((filters) => { Object.keys(filters).forEach((f) => filters[f] = null); return filters; @@ -92,12 +84,6 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { this.searchLink = this.getSearchLink(); } - initFilters() { - this.filters = this.searchConfigService.getConfig(this.currentScope, this.currentConfiguration).pipe( - getFirstSucceededRemoteData() - ); - } - /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ 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..abe17688ea 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.html +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.html @@ -19,6 +19,7 @@ (changeConfiguration)="changeConfiguration.emit($event)"> diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.ts b/src/app/shared/search/search-sidebar/search-sidebar.component.ts index 415cc19d3b..0212dc2497 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.ts +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.ts @@ -1,10 +1,12 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { SearchConfigurationOption } from '../search-switch-configuration/search-configuration-option.model'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { PaginatedSearchOptions } from '../models/paginated-search-options.model'; import { SortOptions } from '../../../core/cache/models/sort-options.model'; import { ViewMode } from '../../../core/shared/view-mode.model'; +import { RemoteData } from '../../../core/data/remote-data'; +import { SearchFilterConfig } from '../models/search-filter-config.model'; /** * This component renders a simple item page. @@ -43,6 +45,11 @@ export class SearchSidebarComponent { */ @Input() currentSortOption: SortOptions; + /** + * An observable containing configuration about which filters are shown and how they are shown + */ + @Input() filters: Observable>; + /** * The total amount of results */ diff --git a/src/app/shared/search/search.component.html b/src/app/shared/search/search.component.html index 95f051778f..d7dab8c126 100644 --- a/src/app/shared/search/search.component.html +++ b/src/app/shared/search/search.component.html @@ -48,6 +48,7 @@ [configurationList]="configurationList" [configuration]="(currentConfiguration$ | async)" [currentScope]="(currentScope$ | async)" + [filters]="filtersRD$.asObservable()" [resultCount]="(resultsRD$ | async)?.payload?.totalElements" [searchOptions]="(searchOptions$ | async)" [sortOptionsList]="(sortOptionsList$ | async)" @@ -61,6 +62,7 @@ [configurationList]="configurationList" [configuration]="(currentConfiguration$ | async)" [currentScope]="(currentScope$ | async)" + [filters]="filtersRD$.asObservable()" [resultCount]="(resultsRD$ | async)?.payload.totalElements" [searchOptions]="(searchOptions$ | async)" [sortOptionsList]="(sortOptionsList$ | async)" diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index bb8f70ffd6..3f00cf354f 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -5,7 +5,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Store } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import { cold } from 'jasmine-marbles'; -import { Observable, BehaviorSubject, of as observableOf } from 'rxjs'; +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { HostWindowService } from '../host-window.service'; @@ -29,6 +29,8 @@ import { Item } from '../../core/shared/item.model'; import { RemoteData } from '../../core/data/remote-data'; import { SearchObjects } from './models/search-objects.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { SearchFilterConfig } from './models/search-filter-config.model'; +import { FilterType } from './models/filter-type.model'; let comp: SearchComponent; let fixture: ComponentFixture; @@ -131,6 +133,24 @@ const activatedRouteStub = { }) }; +const mockFilterConfig: SearchFilterConfig = Object.assign(new SearchFilterConfig(), { + name: 'test1', + filterType: FilterType.text, + hasFacets: false, + isOpenByDefault: false, + pageSize: 2 +}); +const mockFilterConfig2: SearchFilterConfig = Object.assign(new SearchFilterConfig(), { + name: 'test2', + filterType: FilterType.text, + hasFacets: false, + isOpenByDefault: false, + pageSize: 1 +}); + +const filtersConfigRD = createSuccessfulRemoteDataObject([mockFilterConfig, mockFilterConfig2]); +const filtersConfigRD$ = observableOf(filtersConfigRD); + const routeServiceStub = { getRouteParameterValue: () => { return observableOf(''); @@ -151,6 +171,7 @@ let searchConfigurationServiceStub; export function configureSearchComponentTestingModule(compType, additionalDeclarations: any[] = []) { searchConfigurationServiceStub = jasmine.createSpyObj('SearchConfigurationService', { getConfigurationSortOptions: sortOptionsList, + getConfig: filtersConfigRD$, getConfigurationSearchConfig: observableOf(searchConfig), getCurrentConfiguration: observableOf('default'), getCurrentScope: observableOf('test-id'), @@ -273,6 +294,15 @@ describe('SearchComponent', () => { })); })); + it('should retrieve Search Filters', fakeAsync(() => { + fixture.detectChanges(); + tick(100); + const expectedResults = filtersConfigRD; + expect(comp.filtersRD$).toBeObservable(cold('b', { + b: expectedResults + })); + })); + it('should emit resultFound event', fakeAsync(() => { spyOn(comp.resultFound, 'emit'); const expectedResults = mockSearchResults; diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index f40947ad19..03e76c2f0b 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -31,7 +31,8 @@ import { ViewMode } from '../../core/shared/view-mode.model'; import { SelectionConfig } from './search-results/search-results.component'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { CollectionElementLinkType } from '../object-collection/collection-element-link.type'; -import { environment } from 'src/environments/environment'; +import { environment } from '../../../environments/environment'; +import { SearchFilterConfig } from './models/search-filter-config.model'; @Component({ selector: 'ds-search', @@ -160,6 +161,16 @@ export class SearchComponent implements OnInit { */ currentSortOptions$: BehaviorSubject = new BehaviorSubject(null); + /** + * An observable containing configuration about which filters are shown and how they are shown + */ + filtersRD$: BehaviorSubject> = new BehaviorSubject>(null); + + /** + * Maintains the last search options, so it can be used in refresh + */ + lastSearchOptions: PaginatedSearchOptions; + /** * The current search results */ @@ -294,6 +305,7 @@ export class SearchComponent implements OnInit { this.initialized$.next(true); // retrieve results this.retrieveSearchResults(newSearchOptions); + this.retrieveFilters(searchOptions); } }); } @@ -344,6 +356,20 @@ export class SearchComponent implements OnInit { return this.searchConfigService.paginatedSearchOptions; } + /** + * Retrieve search filters by the given search options + * @param searchOptions + * @private + */ + private retrieveFilters(searchOptions: PaginatedSearchOptions) { + this.filtersRD$.next(null); + this.searchConfigService.getConfig(searchOptions.scope, searchOptions.configuration).pipe( + getFirstCompletedRemoteData(), + ).subscribe((filtersRD: RemoteData) => { + this.filtersRD$.next(filtersRD); + }); + } + /** * Retrieve search result by the given search options * @param searchOptions @@ -351,6 +377,7 @@ export class SearchComponent implements OnInit { */ private retrieveSearchResults(searchOptions: PaginatedSearchOptions) { this.resultsRD$.next(null); + this.lastSearchOptions = searchOptions; this.service.search( searchOptions, undefined, @@ -385,5 +412,4 @@ export class SearchComponent implements OnInit { return this.service.getSearchLink(); } - } From e072cdf75b1bf46b5427ecf947706fc137e6cdfa Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 21 Sep 2022 09:54:10 +0200 Subject: [PATCH 32/78] [CST-6494] Update filter values when a workflow action is dispatched --- .../shared/search/search-filter.service.ts | 3 +- src/app/core/shared/search/search.service.ts | 6 +- .../object-collection.component.html | 3 +- .../object-collection.component.ts | 5 + ...-object-component-loader.component.spec.ts | 8 +- ...table-object-component-loader.component.ts | 13 +- .../object-detail.component.html | 8 +- .../object-detail/object-detail.component.ts | 14 +- .../object-list/object-list.component.html | 6 +- .../object-list/object-list.component.ts | 3 +- .../search-facet-filter-wrapper.component.ts | 15 +- .../search-facet-filter.component.spec.ts | 4 +- .../search-facet-filter.component.ts | 132 ++++++++++-------- .../search-filter.component.html | 3 +- .../search-filter/search-filter.component.ts | 7 +- .../search-hierarchy-filter.component.spec.ts | 16 ++- .../search-range-filter.component.spec.ts | 4 +- .../search-range-filter.component.ts | 6 +- .../search-filters.component.html | 2 +- .../search-filters.component.ts | 2 +- .../search-results.component.html | 13 +- .../search-results.component.ts | 5 + .../themed-search-results.component.ts | 4 +- .../search-sidebar.component.ts | 2 +- src/app/shared/search/search.component.html | 3 + src/app/shared/search/search.component.ts | 14 ++ 26 files changed, 187 insertions(+), 114 deletions(-) diff --git a/src/app/core/shared/search/search-filter.service.ts b/src/app/core/shared/search/search-filter.service.ts index 00125e31f5..80ba200d38 100644 --- a/src/app/core/shared/search/search-filter.service.ts +++ b/src/app/core/shared/search/search-filter.service.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { Injectable, InjectionToken } from '@angular/core'; import { @@ -26,6 +26,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; export const FILTER_CONFIG: InjectionToken = new InjectionToken('filterConfig'); export const IN_PLACE_SEARCH: InjectionToken = new InjectionToken('inPlaceSearch'); +export const REFRESH_FILTER: InjectionToken> = new InjectionToken('refreshFilters'); /** * Service that performs all actions that have to do with search filters and facets diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 7911db026d..ffbe9fdfec 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -262,9 +262,11 @@ export class SearchService implements OnDestroy { * @param {number} valuePage The page number of the filter values * @param {SearchOptions} searchOptions The search configuration for the current search * @param {string} filterQuery The optional query used to filter out filter values + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true * @returns {Observable>>} Emits the given page of facet values */ - getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string): Observable> { + getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string, useCachedVersionIfAvailable = true): Observable> { let href; const args: string[] = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`]; if (hasValue(filterQuery)) { @@ -282,7 +284,7 @@ export class SearchService implements OnDestroy { return FacetValueResponseParsingService; } }); - this.requestService.send(request, true); + this.requestService.send(request, useCachedVersionIfAvailable); return this.rdb.buildFromHref(href); } diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index 999ae9a120..fc24defbc5 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -19,7 +19,7 @@ [importable]="importable" [importConfig]="importConfig" (importObject)="importObject.emit($event)" - (contentChange)="contentChange.emit()" + (contentChange)="contentChange.emit($event)" (prev)="goPrev()" (next)="goNext()" *ngIf="(currentMode$ | async) === viewModeEnum.ListElement"> @@ -49,6 +49,7 @@ [context]="context" [hidePaginationDetail]="hidePaginationDetail" [showPaginator]="showPaginator" + (contentChange)="contentChange.emit($event)" *ngIf="(currentMode$ | async) === viewModeEnum.DetailedListElement"> diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index f2706f1a4c..42e8b3f19a 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -50,6 +50,11 @@ export class ObjectCollectionComponent implements OnInit { @Input() hideGear = false; @Input() selectable = false; @Input() selectionConfig: {repeatable: boolean, listId: string}; + + /** + * Emit custom event for listable object custom actions. + */ + @Output() customEvent = new EventEmitter(); @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index edf0b3ea7c..ff88256f6a 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -5,7 +5,9 @@ import { ListableObject } from '../listable-object.model'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { Context } from '../../../../core/shared/context.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; +import { + ItemListElementComponent +} from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; import { ListableObjectDirective } from './listable-object.directive'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; @@ -146,7 +148,7 @@ describe('ListableObjectComponentLoaderComponent', () => { expect((comp as any).instantiateComponent).not.toHaveBeenCalled(); (listableComponent as any).reloadedObject.emit(reloadedObject); - tick(); + tick(200); expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject); })); @@ -155,7 +157,7 @@ describe('ListableObjectComponentLoaderComponent', () => { expect((comp as any).contentChange.emit).not.toHaveBeenCalled(); (listableComponent as any).reloadedObject.emit(reloadedObject); - tick(); + tick(200); expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject); })); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 9c7ad5f659..366a78b891 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,16 +1,16 @@ import { Component, ComponentFactoryResolver, + ComponentRef, ElementRef, + EventEmitter, Input, + OnChanges, OnDestroy, OnInit, Output, - ViewChild, - EventEmitter, SimpleChanges, - OnChanges, - ComponentRef + ViewChild } from '@angular/core'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; @@ -187,7 +187,10 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges this.compRef.destroy(); this.object = reloadedObject; this.instantiateComponent(reloadedObject); - this.contentChange.emit(reloadedObject); + // Add delay before emitting event to allow the new object is instantiated + setTimeout(() => { + this.contentChange.emit(reloadedObject); + }, 100); } }); } diff --git a/src/app/shared/object-detail/object-detail.component.html b/src/app/shared/object-detail/object-detail.component.html index 824e7f3dcc..05b8342ca3 100644 --- a/src/app/shared/object-detail/object-detail.component.html +++ b/src/app/shared/object-detail/object-detail.component.html @@ -14,12 +14,14 @@ (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)" (prev)="goPrev()" - (next)="goNext()" - > + (next)="goNext()">
- +
diff --git a/src/app/shared/object-detail/object-detail.component.ts b/src/app/shared/object-detail/object-detail.component.ts index 05d2ca4b5b..15bd5b7bca 100644 --- a/src/app/shared/object-detail/object-detail.component.ts +++ b/src/app/shared/object-detail/object-detail.component.ts @@ -1,11 +1,4 @@ -import { - ChangeDetectionStrategy, - Component, - EventEmitter, - Input, - Output, - ViewEncapsulation -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginatedList } from '../../core/data/paginated-list.model'; @@ -71,6 +64,11 @@ export class ObjectDetailComponent { */ @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 */ diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 927f2b9d2a..863d328a69 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -14,8 +14,7 @@ (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)" (prev)="goPrev()" - (next)="goNext()" - > + (next)="goNext()">
  • + (contentChange)="contentChange.emit($event)">
diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 166eb3f1ab..65e2b508da 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -74,7 +74,7 @@ export class ObjectListComponent { /** * Config used for the import button */ - @Input() importConfig: { importLabel: string }; + @Input() importConfig: { buttonLabel: string }; /** * Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination @@ -221,4 +221,5 @@ export class ObjectListComponent { goNext() { this.next.emit(true); } + } diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts index 3f83c766fe..0aa131d428 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts @@ -2,9 +2,14 @@ import { Component, Injector, Input, OnInit } from '@angular/core'; import { renderFilterType } from '../search-filter-type-decorator'; import { FilterType } from '../../../models/filter-type.model'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; -import { FILTER_CONFIG, IN_PLACE_SEARCH } from '../../../../../core/shared/search/search-filter.service'; +import { + FILTER_CONFIG, + IN_PLACE_SEARCH, + REFRESH_FILTER +} from '../../../../../core/shared/search/search-filter.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { SearchFacetFilterComponent } from '../search-facet-filter/search-facet-filter.component'; +import { BehaviorSubject } from 'rxjs'; @Component({ selector: 'ds-search-facet-filter-wrapper', @@ -25,6 +30,11 @@ export class SearchFacetFilterWrapperComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: BehaviorSubject; + /** * The constructor of the search facet filter that should be rendered, based on the filter config's type */ @@ -45,7 +55,8 @@ export class SearchFacetFilterWrapperComponent implements OnInit { this.objectInjector = Injector.create({ providers: [ { provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] }, - { provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] } + { provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] }, + { provide: REFRESH_FILTER, useFactory: () => (this.refreshFilters), deps: [] } ], parent: this.injector }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts index ad8df3e3ac..92d2e5265b 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts @@ -5,13 +5,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FilterType } from '../../../models/filter-type.model'; import { FacetValue } from '../../../models/facet-value.model'; import { FormsModule } from '@angular/forms'; -import { of as observableOf } from 'rxjs'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { SearchServiceStub } from '../../../../testing/search-service.stub'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; @@ -97,6 +98,7 @@ describe('SearchFacetFilterComponent', () => { { provide: RemoteDataBuildService, useValue: { aggregate: () => observableOf({}) } }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) }, { provide: SearchFilterService, useValue: { getSelectedValuesForFilter: () => observableOf(selectedValues), diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index ada1bccd63..c3c50f2922 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -6,7 +6,7 @@ import { Subject, Subscription } from 'rxjs'; -import { distinctUntilChanged, map, switchMap, take, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; @@ -21,6 +21,7 @@ import { SearchService } from '../../../../../core/shared/search/search.service' import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service'; @@ -98,7 +99,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { protected router: Router, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, - @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig) { + @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject) { } /** @@ -110,66 +112,15 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged()); this.searchOptions$ = this.searchConfigService.searchOptions; - this.subs.push(this.searchOptions$.subscribe(() => this.updateFilterValueList())); - const facetValues$ = observableCombineLatest(this.searchOptions$, this.currentPage).pipe( - map(([options, page]) => { - return { options, page }; - }), - switchMap(({ options, page }) => { - return this.searchService.getFacetValuesFor(this.filterConfig, page, options) - .pipe( - getFirstSucceededRemoteData(), - map((results) => { - return { - values: observableOf(results), - page: page - }; - } - ) - ); + this.subs.push( + this.searchOptions$.subscribe(() => this.updateFilterValueList()), + this.refreshFilters.asObservable().pipe( + filter((toRefresh: boolean) => toRefresh), + ).subscribe(() => { + this.retrieveFilterValues(false); }) ); - - let filterValues = []; - this.subs.push(facetValues$.subscribe((facetOutcome) => { - const newValues$ = facetOutcome.values; - - if (this.collapseNextUpdate) { - this.showFirstPageOnly(); - facetOutcome.page = 1; - this.collapseNextUpdate = false; - } - if (facetOutcome.page === 1) { - filterValues = []; - } - - filterValues = [...filterValues, newValues$]; - - this.subs.push(this.rdbs.aggregate(filterValues).pipe( - tap((rd: RemoteData[]>) => { - this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( - map((selectedValues) => { - return selectedValues.map((value: string) => { - const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); - if (hasValue(fValue)) { - return fValue; - } - const filterValue = stripOperatorFromFilterValue(value); - return Object.assign(new FacetValue(), { label: filterValue, value: filterValue }); - }); - }) - ); - }) - ).subscribe((rd: RemoteData[]>) => { - this.animationState = 'ready'; - this.filterValues$.next(rd); - - })); - this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => { - this.isLastPage$.next(hasNoValue(rd.payload.next)); - })); - })); - + this.retrieveFilterValues(); } /** @@ -324,6 +275,67 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return getFacetValueForType(facet, this.filterConfig); } + protected retrieveFilterValues(useCachedVersionIfAvailable = true) { + const facetValues$ = observableCombineLatest([this.searchOptions$, this.currentPage]).pipe( + map(([options, page]) => { + return { options, page }; + }), + switchMap(({ options, page }) => { + return this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable) + .pipe( + getFirstSucceededRemoteData(), + map((results) => { + return { + values: observableOf(results), + page: page + }; + } + ) + ); + }) + ); + + let filterValues = []; + this.subs.push(facetValues$.subscribe((facetOutcome) => { + const newValues$ = facetOutcome.values; + + if (this.collapseNextUpdate) { + this.showFirstPageOnly(); + facetOutcome.page = 1; + this.collapseNextUpdate = false; + } + if (facetOutcome.page === 1) { + filterValues = []; + } + + filterValues = [...filterValues, newValues$]; + + this.subs.push(this.rdbs.aggregate(filterValues).pipe( + tap((rd: RemoteData[]>) => { + this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( + map((selectedValues) => { + return selectedValues.map((value: string) => { + const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); + if (hasValue(fValue)) { + return fValue; + } + const filterValue = stripOperatorFromFilterValue(value); + return Object.assign(new FacetValue(), { label: filterValue, value: filterValue }); + }); + }) + ); + }) + ).subscribe((rd: RemoteData[]>) => { + this.animationState = 'ready'; + this.filterValues$.next(rd); + + })); + this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => { + this.isLastPage$.next(hasNoValue(rd.payload.next)); + })); + })); + } + /** * Transforms the facet value string, so if the query matches part of the value, it's emphasized in the value * @param {FacetValue} facet The value of the facet as returned by the server diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-filter.component.html index 452433e165..13457cc008 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.html @@ -19,7 +19,8 @@ class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }"> + [inPlaceSearch]="inPlaceSearch" + [refreshFilters]="refreshFilters" > diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index 1897169a2e..d1d3bd729d 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -1,6 +1,6 @@ import { Component, Inject, Input, OnInit } from '@angular/core'; -import { Observable, of as observableOf } from 'rxjs'; +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { filter, map, startWith, switchMap, take } from 'rxjs/operators'; import { SearchFilterConfig } from '../../models/search-filter-config.model'; @@ -33,6 +33,11 @@ export class SearchFilterComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + @Input() refreshFilters: BehaviorSubject; + /** * True when the filter is 100% collapsed in the UI */ diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts index c59ae7a906..9302e66d98 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts @@ -1,17 +1,18 @@ -import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SearchHierarchyFilterComponent } from './search-hierarchy-filter.component'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { - SearchFilterService, FILTER_CONFIG, - IN_PLACE_SEARCH + IN_PLACE_SEARCH, + REFRESH_FILTER, + SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { SearchFiltersComponent } from '../../search-filters.component'; import { Router } from '@angular/router'; import { RouterStub } from '../../../../testing/router.stub'; import { SearchServiceStub } from '../../../../testing/search-service.stub'; -import { of as observableOf, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../../../testing/search-configuration-service.stub'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; @@ -21,7 +22,7 @@ import { } from '../../../../input-suggestions/filter-suggestions/filter-input-suggestions.component'; import { FormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { NO_ERRORS_SCHEMA, ChangeDetectionStrategy } from '@angular/core'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils'; import { FacetValue } from '../../../models/facet-value.model'; import { FilterType } from '../../../models/filter-type.model'; @@ -112,7 +113,8 @@ describe('SearchHierarchyFilterComponent', () => { { provide: Router, useValue: new RouterStub() }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, - { provide: FILTER_CONFIG, useValue: new SearchFilterConfig() } + { provide: FILTER_CONFIG, useValue: new SearchFilterConfig() }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(SearchHierarchyFilterComponent, { @@ -140,7 +142,7 @@ describe('SearchHierarchyFilterComponent', () => { }); it('should navigate to the correct filter with the query operator', () => { - expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}); + expect((comp as any).searchService.getFacetValuesFor).toHaveBeenCalledWith(comp.filterConfig, 0, {}, null, true); const searchQuery = 'MARVEL'; comp.onSubmit(searchQuery); diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index bc2f60b357..44dda40d15 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -5,13 +5,14 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FilterType } from '../../../models/filter-type.model'; import { FacetValue } from '../../../models/facet-value.model'; import { FormsModule } from '@angular/forms'; -import { of as observableOf } from 'rxjs'; +import { BehaviorSubject, of as observableOf } from 'rxjs'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { SearchServiceStub } from '../../../../testing/search-service.stub'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; @@ -104,6 +105,7 @@ describe('SearchRangeFilterComponent', () => { { provide: RouteService, useValue: { getQueryParameterValue: () => observableOf({}) } }, { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) }, { provide: SearchFilterService, useValue: { getSelectedValuesForFilter: () => selectedValues, diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index a6b33ddf88..fbd767284f 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Subscription } from 'rxjs'; +import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs'; import { map, startWith } from 'rxjs/operators'; import { isPlatformBrowser } from '@angular/common'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; @@ -10,6 +10,7 @@ import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FILTER_CONFIG, IN_PLACE_SEARCH, + REFRESH_FILTER, SearchFilterService } from '../../../../../core/shared/search/search-filter.service'; import { SearchService } from '../../../../../core/shared/search/search.service'; @@ -86,8 +87,9 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple @Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean, @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, @Inject(PLATFORM_ID) private platformId: any, + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject, private route: RouteService) { - super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig); + super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters); } diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html index 488ae7b517..e392cd2663 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,7 +1,7 @@

{{"search.filters.head" | translate}}

- +
{{"search.filters.reset" | translate}} diff --git a/src/app/shared/search/search-filters/search-filters.component.ts b/src/app/shared/search/search-filters/search-filters.component.ts index a7d1f7bd8b..766939226d 100644 --- a/src/app/shared/search/search-filters/search-filters.component.ts +++ b/src/app/shared/search/search-filters/search-filters.component.ts @@ -53,7 +53,7 @@ export class SearchFiltersComponent implements OnInit, OnDestroy { /** * Emits when the search filters values may be stale, and so they must be refreshed. */ - @Input() refreshFilters: Observable; + @Input() refreshFilters: BehaviorSubject; /** * Link to the search page diff --git a/src/app/shared/search/search-results/search-results.component.html b/src/app/shared/search/search-results/search-results.component.html index d557af2451..44498c3cab 100644 --- a/src/app/shared/search/search-results/search-results.component.html +++ b/src/app/shared/search/search-results/search-results.component.html @@ -1,6 +1,6 @@
-

{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}

- +

{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}

+
+ (selectObject)="selectObject.emit($event)">
- +
{{ 'search.results.no-results' | translate }} (); + @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/search/search-results/themed-search-results.component.ts b/src/app/shared/search/search-results/themed-search-results.component.ts index 19a8fc55e8..deb64bf840 100644 --- a/src/app/shared/search/search-results/themed-search-results.component.ts +++ b/src/app/shared/search/search-results/themed-search-results.component.ts @@ -21,7 +21,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m templateUrl: '../../theme-support/themed.component.html', }) export class ThemedSearchResultsComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'deselectObject', 'selectObject']; + protected inAndOutputNames: (keyof SearchResultsComponent & keyof this)[] = ['linkType', 'searchResults', 'searchConfig', 'sortConfig', 'viewMode', 'configuration', 'disableHeader', 'selectable', 'context', 'hidePaginationDetail', 'selectionConfig', 'contentChange', 'deselectObject', 'selectObject']; @Input() linkType: CollectionElementLinkType; @@ -45,6 +45,8 @@ export class ThemedSearchResultsComponent extends ThemedComponent = new EventEmitter(); + @Output() deselectObject: EventEmitter = new EventEmitter(); @Output() selectObject: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/search/search-sidebar/search-sidebar.component.ts b/src/app/shared/search/search-sidebar/search-sidebar.component.ts index 0212dc2497..929f819ca3 100644 --- a/src/app/shared/search/search-sidebar/search-sidebar.component.ts +++ b/src/app/shared/search/search-sidebar/search-sidebar.component.ts @@ -83,7 +83,7 @@ export class SearchSidebarComponent { /** * Emits when the search filters values may be stale, and so they must be refreshed. */ - @Input() refreshFilters: Observable; + @Input() refreshFilters: BehaviorSubject; /** * Emits event when the user clicks a button to open or close the sidebar diff --git a/src/app/shared/search/search.component.html b/src/app/shared/search/search.component.html index d7dab8c126..cdad62dcbd 100644 --- a/src/app/shared/search/search.component.html +++ b/src/app/shared/search/search.component.html @@ -37,6 +37,7 @@ [context]="(currentContext$ | async)" [selectable]="selectable" [selectionConfig]="selectionConfig" + (contentChange)="onContentChange($event)" (deselectObject)="deselectObject.emit($event)" (selectObject)="selectObject.emit($event)">
@@ -49,6 +50,7 @@ [configuration]="(currentConfiguration$ | async)" [currentScope]="(currentScope$ | async)" [filters]="filtersRD$.asObservable()" + [refreshFilters]="refreshFilters" [resultCount]="(resultsRD$ | async)?.payload?.totalElements" [searchOptions]="(searchOptions$ | async)" [sortOptionsList]="(sortOptionsList$ | async)" @@ -63,6 +65,7 @@ [configuration]="(currentConfiguration$ | async)" [currentScope]="(currentScope$ | async)" [filters]="filtersRD$.asObservable()" + [refreshFilters]="refreshFilters" [resultCount]="(resultsRD$ | async)?.payload.totalElements" [searchOptions]="(searchOptions$ | async)" [sortOptionsList]="(sortOptionsList$ | async)" diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 03e76c2f0b..d035c5fd73 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -201,6 +201,11 @@ export class SearchComponent implements OnInit { */ isXsOrSm$: Observable; + /** + * Emits when the search filters values may be stale, and so they must be refreshed. + */ + refreshFilters: BehaviorSubject = new BehaviorSubject(false); + /** * Link to the search page */ @@ -339,6 +344,15 @@ export class SearchComponent implements OnInit { this.sidebarService.expand(); } + /** + * Emit event to refresh filter content + * @param $event + */ + public onContentChange($event: any) { + this.retrieveFilters(this.lastSearchOptions); + this.refreshFilters.next(true); + } + /** * Unsubscribe from the subscription */ From f2a6b8208fb2af72b5da6361a0110813647327ab Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 21 Sep 2022 09:54:49 +0200 Subject: [PATCH 33/78] remove console.log --- .../end-user-agreement-current-user.guard.spec.ts | 1 - src/app/core/orcid/orcid-auth.service.ts | 1 - .../item-page/edit-item-page/edit-item-page.component.spec.ts | 1 - .../form/footer/submission-form-footer.component.spec.ts | 2 -- 4 files changed, 5 deletions(-) diff --git a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts index 40728ab601..21d7c3908d 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts @@ -50,7 +50,6 @@ describe('EndUserAgreementGuard', () => { it('should return true', (done) => { environment.info.enableEndUserAgreement = false; guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { - console.log(result); expect(result).toEqual(true); done(); }); diff --git a/src/app/core/orcid/orcid-auth.service.ts b/src/app/core/orcid/orcid-auth.service.ts index 572ab79b4a..a930a8f614 100644 --- a/src/app/core/orcid/orcid-auth.service.ts +++ b/src/app/core/orcid/orcid-auth.service.ts @@ -111,7 +111,6 @@ export class OrcidAuthService { ).pipe( map(([authorizeUrl, clientId, scopes]) => { const redirectUri = new URLCombiner(this._window.nativeWindow.origin, encodeURIComponent(this.router.url.split('?')[0])); - console.log(redirectUri.toString()); return authorizeUrl.values[0] + '?client_id=' + clientId.values[0] + '&redirect_uri=' + redirectUri + '&response_type=code&scope=' + scopes.values.join(' '); })); diff --git a/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts b/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts index f153e9beb9..2521f7e67d 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.component.spec.ts @@ -22,7 +22,6 @@ describe('ItemPageComponent', () => { class AcceptNoneGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { - console.log('BLA'); return observableOf(false); } } diff --git a/src/app/submission/form/footer/submission-form-footer.component.spec.ts b/src/app/submission/form/footer/submission-form-footer.component.spec.ts index a47085f1e0..dd28f9a10a 100644 --- a/src/app/submission/form/footer/submission-form-footer.component.spec.ts +++ b/src/app/submission/form/footer/submission-form-footer.component.spec.ts @@ -196,8 +196,6 @@ describe('SubmissionFormFooterComponent', () => { const confirmBtn: any = ((document as any).querySelector('.btn-danger:nth-child(2)')); - console.log(confirmBtn); - confirmBtn.click(); fixture.detectChanges(); From ab77e66be7c99f57358adfe8b744bd5ee99328dc Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 21 Sep 2022 17:16:10 +0200 Subject: [PATCH 34/78] [CST-6494] Fix issue for which sometimes claimed and pool task weren't shown when a filter is applied --- ...med-search-result-list-element.component.spec.ts | 7 ++++++- .../claimed-search-result-list-element.component.ts | 13 ++++++++++--- ...ool-search-result-list-element.component.spec.ts | 8 +++++++- .../pool-search-result-list-element.component.ts | 12 +++++++++--- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts index 7896061a73..87cc70b67c 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts @@ -18,6 +18,7 @@ import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; let component: ClaimedSearchResultListElementComponent; let fixture: ComponentFixture; @@ -59,6 +60,9 @@ const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdIt const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowitem: observableOf(rdWorkflowitem) }); const linkService = getMockLinkService(); +const objectCacheServiceMock = jasmine.createSpyObj('ObjectCacheService', { + remove: jasmine.createSpy('remove') +}); describe('ClaimedSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { @@ -68,7 +72,8 @@ describe('ClaimedSearchResultListElementComponent', () => { providers: [ { provide: TruncatableService, useValue: {} }, { provide: LinkService, useValue: linkService }, - { provide: DSONameService, useClass: DSONameServiceMock } + { provide: DSONameService, useClass: DSONameServiceMock }, + { provide: ObjectCacheService, useValue: objectCacheServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ClaimedSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index 2cf8f9a231..9de27ff7e6 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; @@ -13,6 +13,7 @@ import { followLink } from '../../../utils/follow-link-config.model'; import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -20,7 +21,7 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; templateUrl: './claimed-search-result-list-element.component.html' }) @listableObjectComponent(ClaimedTaskSearchResult, ViewMode.ListElement) -export class ClaimedSearchResultListElementComponent extends SearchResultListElementComponent { +export class ClaimedSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit, OnDestroy { /** * A boolean representing if to show submitter information @@ -40,7 +41,8 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle public constructor( protected linkService: LinkService, protected truncatableService: TruncatableService, - protected dsoNameService: DSONameService + protected dsoNameService: DSONameService, + protected objectCache: ObjectCacheService ) { super(truncatableService, dsoNameService); } @@ -56,4 +58,9 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle this.workflowitemRD$ = this.dso.workflowitem as Observable>; } + ngOnDestroy() { + // This ensures the object is removed from cache, when action is performed on task + this.objectCache.remove(this.dso._links.workflowitem.href); + } + } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts index 18db9abd67..85f056a129 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts @@ -18,6 +18,7 @@ import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; let component: PoolSearchResultListElementComponent; let fixture: ComponentFixture; @@ -59,6 +60,10 @@ const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdIt const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); mockResultObject.indexableObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkflowitem) }); const linkService = getMockLinkService(); +const objectCacheServiceMock = jasmine.createSpyObj('ObjectCacheService', { + remove: jasmine.createSpy('remove') +}); + describe('PoolSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { @@ -68,7 +73,8 @@ describe('PoolSearchResultListElementComponent', () => { providers: [ { provide: TruncatableService, useValue: {} }, { provide: LinkService, useValue: linkService }, - { provide: DSONameService, useClass: DSONameServiceMock } + { provide: DSONameService, useClass: DSONameServiceMock }, + { provide: ObjectCacheService, useValue: objectCacheServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolSearchResultListElementComponent, { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index e9d64db572..c5c9721003 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; @@ -14,6 +14,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service'; import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; /** * This component renders pool task object for the search result in the list view. @@ -25,7 +26,7 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; }) @listableObjectComponent(PoolTaskSearchResult, ViewMode.ListElement) -export class PoolSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit { +export class PoolSearchResultListElementComponent extends SearchResultListElementComponent implements OnInit, OnDestroy { /** * A boolean representing if to show submitter information @@ -50,7 +51,8 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen constructor( protected linkService: LinkService, protected truncatableService: TruncatableService, - protected dsoNameService: DSONameService + protected dsoNameService: DSONameService, + protected objectCache: ObjectCacheService ) { super(truncatableService, dsoNameService); } @@ -66,4 +68,8 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen this.workflowitemRD$ = this.dso.workflowitem as Observable>; } + ngOnDestroy() { + // This ensures the object is removed from cache, when action is performed on task + this.objectCache.remove(this.dso._links.workflowitem.href); + } } From d68f38e848ec06c02f4d9445fe1c9fdbea764fa8 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 22 Sep 2022 17:07:09 +0200 Subject: [PATCH 35/78] 94233: Fixed tests --- src/app/shared/sass-helper/css-variable.actions.ts | 4 +--- src/app/shared/sass-helper/css-variable.reducer.ts | 2 +- src/app/shared/sass-helper/css-variable.service.ts | 3 +-- src/app/shared/testing/css-variable-service.stub.ts | 13 +++++++++++++ src/modules/app/server-init.service.ts | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/app/shared/sass-helper/css-variable.actions.ts b/src/app/shared/sass-helper/css-variable.actions.ts index 93225f9426..2d58a2978b 100644 --- a/src/app/shared/sass-helper/css-variable.actions.ts +++ b/src/app/shared/sass-helper/css-variable.actions.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { Action } from '@ngrx/store'; import { type } from '../ngrx/type'; import { KeyValuePair } from '../key-value-pair.model'; @@ -38,9 +39,6 @@ export class AddAllCSSVariablesAction implements Action { export class ClearCSSVariablesAction implements Action { type = CSSVariableActionTypes.CLEAR; - - constructor() { - } } export type CSSVariableAction = AddCSSVariableAction | AddAllCSSVariablesAction | ClearCSSVariablesAction; diff --git a/src/app/shared/sass-helper/css-variable.reducer.ts b/src/app/shared/sass-helper/css-variable.reducer.ts index 405cbf5df4..449a936b4e 100644 --- a/src/app/shared/sass-helper/css-variable.reducer.ts +++ b/src/app/shared/sass-helper/css-variable.reducer.ts @@ -19,7 +19,7 @@ export function cssVariablesReducer(state = initialState, action: CSSVariableAct return Object.assign({}, state, { [variable.name]: variable.value }); } case CSSVariableActionTypes.ADD_ALL: { const variables = action.payload; - return Object.assign({}, state, ...variables.map(({ key, value }: KeyValuePair) => {return {[key]: value}})); + return Object.assign({}, state, ...variables.map(({ key, value }: KeyValuePair) => {return {[key]: value};})); } case CSSVariableActionTypes.CLEAR: { return initialState; } diff --git a/src/app/shared/sass-helper/css-variable.service.ts b/src/app/shared/sass-helper/css-variable.service.ts index 9ba9dfca3c..af40c634fd 100644 --- a/src/app/shared/sass-helper/css-variable.service.ts +++ b/src/app/shared/sass-helper/css-variable.service.ts @@ -87,8 +87,7 @@ export class CSSVariableService { * @return array> * ex; [{key: "--color-accent", value: "#b9f500"}, {key: "--color-text", value: "#252525"}, ...] */ - getCSSVariablesFromStylesheets(document: Document): KeyValuePair[] - { + getCSSVariablesFromStylesheets(document: Document): KeyValuePair[] { // styleSheets is array-like, so we convert it to an array. // Filter out any stylesheets not on this domain return [...document.styleSheets] diff --git a/src/app/shared/testing/css-variable-service.stub.ts b/src/app/shared/testing/css-variable-service.stub.ts index f72e338455..47be5c0ff4 100644 --- a/src/app/shared/testing/css-variable-service.stub.ts +++ b/src/app/shared/testing/css-variable-service.stub.ts @@ -1,4 +1,5 @@ import { Observable, of as observableOf } from 'rxjs'; +import { KeyValuePair } from '../key-value-pair.model'; const variables = { '--bs-sm-min': '576px,', @@ -19,4 +20,16 @@ export class CSSVariableServiceStub { addCSSVariable(name: string, value: string): void { /**/ } + + addCSSVariables(variables: KeyValuePair[]): void { + /**/ + } + + clearCSSVariables(): void { + /**/ + } + + getCSSVariablesFromStylesheets(document: Document): void { + /**/ + } } diff --git a/src/modules/app/server-init.service.ts b/src/modules/app/server-init.service.ts index e93c692cd7..9f6aa65921 100644 --- a/src/modules/app/server-init.service.ts +++ b/src/modules/app/server-init.service.ts @@ -18,7 +18,7 @@ import { LocaleService } from '../../app/core/locale/locale.service'; import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; import { MetadataService } from '../../app/core/metadata/metadata.service'; import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service'; -import { CSSVariableService } from '../../app/shared/sass-helper/sass-helper.service'; +import { CSSVariableService } from '../../sass-helper/css-variable.service'; import { ThemeService } from '../../app/shared/theme-support/theme.service'; import { take } from 'rxjs/operators'; From 97bceffb0272d095f1c1826b2daabca26034c4a4 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 22 Sep 2022 17:14:55 +0200 Subject: [PATCH 36/78] 94233: Fixed lint issue --- src/app/shared/testing/css-variable-service.stub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/testing/css-variable-service.stub.ts b/src/app/shared/testing/css-variable-service.stub.ts index 47be5c0ff4..2f5c647945 100644 --- a/src/app/shared/testing/css-variable-service.stub.ts +++ b/src/app/shared/testing/css-variable-service.stub.ts @@ -21,7 +21,7 @@ export class CSSVariableServiceStub { /**/ } - addCSSVariables(variables: KeyValuePair[]): void { + addCSSVariables(variablesToAdd: KeyValuePair[]): void { /**/ } From f7619249630f9df9bf5731eb7793ecbc27551d19 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 22 Sep 2022 17:16:35 +0200 Subject: [PATCH 37/78] 94233: removed empty file --- src/app/shared/sass-helper/css-variable.utils.ts | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/app/shared/sass-helper/css-variable.utils.ts diff --git a/src/app/shared/sass-helper/css-variable.utils.ts b/src/app/shared/sass-helper/css-variable.utils.ts deleted file mode 100644 index 05e3074f98..0000000000 --- a/src/app/shared/sass-helper/css-variable.utils.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Uses code from https://css-tricks.com/how-to-get-all-custom-properties-on-a-page-in-javascript/ - -import { KeyValuePair } from '../key-value-pair.model'; - From af982471953836051dd51fb391929f5b3f8c38a3 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 22 Sep 2022 18:18:42 +0200 Subject: [PATCH 38/78] add check to ensure document.styleSheets is defined --- src/app/core/core.module.ts | 2 - .../sass-helper/css-variable.service.ts | 58 ++++++++++--------- src/modules/app/server-init.service.ts | 2 - 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f80a2a020e..90cefd54c7 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -23,7 +23,6 @@ import { NotificationsService } from '../shared/notifications/notifications.serv import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service'; import { ObjectSelectService } from '../shared/object-select/object-select.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { CSSVariableService } from '../shared/sass-helper/css-variable.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { SectionFormOperationsService } from '../submission/sections/form/section-form-operations.service'; @@ -257,7 +256,6 @@ const PROVIDERS = [ DefaultChangeAnalyzer, ArrayMoveChangeAnalyzer, ObjectSelectService, - CSSVariableService, MenuService, ObjectUpdatesService, SearchService, diff --git a/src/app/shared/sass-helper/css-variable.service.ts b/src/app/shared/sass-helper/css-variable.service.ts index af40c634fd..0190a05036 100644 --- a/src/app/shared/sass-helper/css-variable.service.ts +++ b/src/app/shared/sass-helper/css-variable.service.ts @@ -5,7 +5,7 @@ import { AddAllCSSVariablesAction, AddCSSVariableAction, ClearCSSVariablesAction import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model'; import { Observable } from 'rxjs'; -import { hasValue } from '../empty.util'; +import { hasValue, isNotEmpty } from '../empty.util'; import { KeyValuePair } from '../key-value-pair.model'; import { PageInfo } from '../../core/shared/page-info.model'; import { CSSVariablesState } from './css-variable.reducer'; @@ -13,7 +13,9 @@ import { CSSVariablesState } from './css-variable.reducer'; /** * This service deals with adding and retrieving CSS variables to and from the store */ -@Injectable() +@Injectable({ + providedIn: 'root' +}) export class CSSVariableService { isSameDomain = (styleSheet) => { // Internal style blocks won't have an href value @@ -88,31 +90,35 @@ export class CSSVariableService { * ex; [{key: "--color-accent", value: "#b9f500"}, {key: "--color-text", value: "#252525"}, ...] */ getCSSVariablesFromStylesheets(document: Document): KeyValuePair[] { - // styleSheets is array-like, so we convert it to an array. - // Filter out any stylesheets not on this domain - return [...document.styleSheets] - .filter(this.isSameDomain) - .reduce( - (finalArr, sheet) => - finalArr.concat( - // cssRules is array-like, so we convert it to an array - [...sheet.cssRules].filter(this.isStyleRule).reduce((propValArr, rule: any) => { - const props = [...rule.style] - .map((propName) => { - return { - key: propName.trim(), - value: rule.style.getPropertyValue(propName).trim() - } as KeyValuePair; - } - ) - // Discard any props that don't start with "--". Custom props are required to. - .filter(({ key }: KeyValuePair) => key.indexOf('--') === 0); + if (isNotEmpty(document.styleSheets)) { + // styleSheets is array-like, so we convert it to an array. + // Filter out any stylesheets not on this domain + return [...document.styleSheets] + .filter(this.isSameDomain) + .reduce( + (finalArr, sheet) => + finalArr.concat( + // cssRules is array-like, so we convert it to an array + [...sheet.cssRules].filter(this.isStyleRule).reduce((propValArr, rule: any) => { + const props = [...rule.style] + .map((propName) => { + return { + key: propName.trim(), + value: rule.style.getPropertyValue(propName).trim() + } as KeyValuePair; + } + ) + // Discard any props that don't start with "--". Custom props are required to. + .filter(({ key }: KeyValuePair) => key.indexOf('--') === 0); - return [...propValArr, ...props]; - }, []) - ), - [] - ); + return [...propValArr, ...props]; + }, []) + ), + [] + ); + } else { + return []; + } } } diff --git a/src/modules/app/server-init.service.ts b/src/modules/app/server-init.service.ts index 9f6aa65921..903bd91b7c 100644 --- a/src/modules/app/server-init.service.ts +++ b/src/modules/app/server-init.service.ts @@ -18,7 +18,6 @@ import { LocaleService } from '../../app/core/locale/locale.service'; import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; import { MetadataService } from '../../app/core/metadata/metadata.service'; import { BreadcrumbsService } from '../../app/breadcrumbs/breadcrumbs.service'; -import { CSSVariableService } from '../../sass-helper/css-variable.service'; import { ThemeService } from '../../app/shared/theme-support/theme.service'; import { take } from 'rxjs/operators'; @@ -37,7 +36,6 @@ export class ServerInitService extends InitService { protected angulartics2DSpace: Angulartics2DSpace, protected metadata: MetadataService, protected breadcrumbsService: BreadcrumbsService, - protected cssService: CSSVariableService, protected themeService: ThemeService, ) { super( From 711982d423ca3623f77dd6767fdaa5da9b55d19e Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 23 Sep 2022 10:32:50 +0200 Subject: [PATCH 39/78] [CST-6782] fixes after merge --- src/assets/i18n/en.json5 | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b9b0a178e6..8755cf3592 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1276,14 +1276,6 @@ "cookies.consent.purpose.sharing": "Sharing", - "cris-layout.toggle.open": "Open section", - - "cris-layout.toggle.close": "Close section", - - "cris-layout.toggle.aria.open": "Expand {{sectionHeader}} section", - - "cris-layout.toggle.aria.close": "Collapse {{sectionHeader}} section", - "curation-task.task.citationpage.label": "Generate Citation Page", "curation-task.task.checklinks.label": "Check Links in Metadata", From 11eacc10f740fbeaebdf3c32610c6b045d050453 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 23 Sep 2022 12:46:16 +0200 Subject: [PATCH 40/78] [CST-6782] Fix klaro configuration --- src/app/shared/cookies/klaro-configuration.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/cookies/klaro-configuration.ts b/src/app/shared/cookies/klaro-configuration.ts index b4ed3d91ba..44869ee470 100644 --- a/src/app/shared/cookies/klaro-configuration.ts +++ b/src/app/shared/cookies/klaro-configuration.ts @@ -161,6 +161,7 @@ export const klaroConfiguration: any = { purposes: ['registration-password-recovery'], required: false, cookies: [ + [/^klaro-.+$/], CAPTCHA_COOKIE ], onAccept: `window.refreshCaptchaScript?.call()`, From 7ad68530ea4e251a1558b3e8903ddb1302d665af Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 26 Sep 2022 15:40:24 +0200 Subject: [PATCH 41/78] [CST-6782] JSdoc fixed; buildCaptchaUrl refactored; import cleanup --- src/app/core/data/eperson-registration.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index 26ebc2d017..e3af6e2821 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -3,10 +3,10 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { GetRequest, PostRequest } from './request.models'; import { Observable } from 'rxjs'; -import { filter, find, map, skipWhile } from 'rxjs/operators'; +import { filter, find, map } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { Registration } from '../shared/registration.model'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../shared/operators'; +import { getFirstCompletedRemoteData } from '../shared/operators'; import { ResponseParsingService } from './parsing.service'; import { GenericConstructor } from '../shared/generic-constructor'; import { RegistrationResponseParsingService } from './registration-response-parsing.service'; @@ -53,6 +53,7 @@ export class EpersonRegistrationService { /** * Register a new email address * @param email + * @param captchaToken */ registerEmail(email: string, captchaToken: string = null): Observable> { const registration = new Registration(); From ab3b05b950a0ad8ea7ca2c31232d6053d77aabab Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 26 Sep 2022 15:43:47 +0200 Subject: [PATCH 42/78] [CST-6782] JSdoc fixed; buildCaptchaUrl refactored; import cleanup --- src/app/core/google-recaptcha/google-recaptcha.service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts index 080ddfc19f..c2e3fdc86f 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -105,7 +105,7 @@ export class GoogleRecaptchaService { break; case 'v2': if (recaptchaModeRD.hasSucceeded && isNotEmpty(recaptchaModeRD.payload?.values)) { - captchaUrl = 'https://www.google.com/recaptcha/api.js'; + captchaUrl = this.buildCaptchaUrl(); this.captchaModeSubject$.next(recaptchaModeRD.payload?.values[0]); } break; @@ -146,8 +146,9 @@ export class GoogleRecaptchaService { * @param key contains a secret key of a google captchas * @returns string which has google captcha url with google captchas key */ - buildCaptchaUrl(key: string) { - return `https://www.google.com/recaptcha/api.js?render=${key}`; + buildCaptchaUrl(key?: string) { + const apiUrl = 'https://www.google.com/recaptcha/api.js'; + return key ? `${apiUrl}?render=${key}` : apiUrl; } /** From 2f13beac7c94f49924f085d1f5a6da9bb0e3bfbe Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 26 Sep 2022 15:44:38 +0200 Subject: [PATCH 43/78] [CST-6782] JSdoc fixed --- src/app/core/data/eperson-registration.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/data/eperson-registration.service.ts b/src/app/core/data/eperson-registration.service.ts index e3af6e2821..bfbecdaecb 100644 --- a/src/app/core/data/eperson-registration.service.ts +++ b/src/app/core/data/eperson-registration.service.ts @@ -53,7 +53,7 @@ export class EpersonRegistrationService { /** * Register a new email address * @param email - * @param captchaToken + * @param captchaToken the value of x-recaptcha-token header */ registerEmail(email: string, captchaToken: string = null): Observable> { const registration = new Registration(); From 750c6032bd9aa604f32cb022d334ae577213a9dc Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Sep 2022 11:49:21 +0200 Subject: [PATCH 44/78] [CST-6876] fix error when submitter is empty --- .../item-submitter.component.html | 2 +- .../item-submitter.component.ts | 30 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html index 1e8524e5ec..0f7ae433fa 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html +++ b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html @@ -1,3 +1,3 @@ -
+
{{'submission.workflow.tasks.generic.submitter' | translate}} : {{(submitter$ | async)?.name}}
diff --git a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts index 61087b3851..75927f6629 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts @@ -1,12 +1,13 @@ import { Component, Input, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { filter, find, map, mergeMap } from 'rxjs/operators'; +import { EMPTY, Observable } from 'rxjs'; +import { map, mergeMap } from 'rxjs/operators'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { RemoteData } from '../../../../core/data/remote-data'; -import { isNotEmpty, isNotUndefined } from '../../../empty.util'; +import { isNotEmpty } from '../../../empty.util'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; /** * This component represents a badge with submitter information. @@ -24,7 +25,7 @@ export class ItemSubmitterComponent implements OnInit { @Input() object: any; /** - * The Eperson object + * The submitter object */ submitter$: Observable; @@ -33,9 +34,22 @@ export class ItemSubmitterComponent implements OnInit { */ ngOnInit() { this.submitter$ = (this.object.workflowitem as Observable>).pipe( - filter((rd: RemoteData) => (rd.hasSucceeded && isNotUndefined(rd.payload))), - mergeMap((rd: RemoteData) => rd.payload.submitter as Observable>), - find((rd: RemoteData) => rd.hasSucceeded && isNotEmpty(rd.payload)), - map((rd: RemoteData) => rd.payload)); + getFirstCompletedRemoteData(), + mergeMap((rd: RemoteData) => { + if (rd.hasSucceeded && isNotEmpty(rd.payload)) { + return (rd.payload.submitter as Observable>).pipe( + getFirstCompletedRemoteData(), + map((rds: RemoteData) => { + if (rds.hasSucceeded && isNotEmpty(rds.payload)) { + return rds.payload; + } else { + return null; + } + }) + ); + } else { + return EMPTY; + } + })); } } From 7f30e92782d481c5ba16cb8e3d06e745aacd9db0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 29 Sep 2022 15:05:18 +0200 Subject: [PATCH 45/78] [CST-6876] Fix issue with invalid request done when the claimed/pool task changed its status due to a performed action --- ...-search-result-list-element.component.html | 25 +++++++++++------ ...arch-result-list-element.component.spec.ts | 26 +++++++++--------- ...ed-search-result-list-element.component.ts | 27 +++++++++++++++---- ...-search-result-list-element.component.html | 23 ++++++++-------- ...arch-result-list-element.component.spec.ts | 27 +++++++++---------- ...ol-search-result-list-element.component.ts | 22 +++++++++++---- 6 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html index 747fb9bc36..2cee94ce20 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html @@ -1,9 +1,18 @@ - - - - + + +
+
+ +
+
+ + + + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts index 3e9f64e718..c5383a2e31 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -7,7 +7,9 @@ import { of as observableOf } from 'rxjs'; import { Item } from '../../../../core/shared/item.model'; import { ClaimedSearchResultListElementComponent } from './claimed-search-result-list-element.component'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; @@ -94,17 +96,15 @@ describe('ClaimedSearchResultListElementComponent', () => { fixture.detectChanges(); }); - it('should init workflowitem properly', (done) => { - component.workflowitemRD$.subscribe((workflowitemRD) => { - expect(linkService.resolveLinks).toHaveBeenCalledWith( - component.dso, - jasmine.objectContaining({ name: 'workflowitem' }), - jasmine.objectContaining({ name: 'action' }) - ); - expect(workflowitemRD.payload).toEqual(workflowitem); - done(); - }); - }); + it('should init workflowitem properly', fakeAsync(() => { + flush(); + expect(linkService.resolveLinks).toHaveBeenCalledWith( + component.dso, + jasmine.objectContaining({ name: 'workflowitem' }), + jasmine.objectContaining({ name: 'action' }) + ); + expect(component.workflowitem$.value).toEqual(workflowitem); + })); it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.VALIDATION); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index 2d02687a43..c1458043a7 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -5,16 +5,21 @@ import { listableObjectComponent } from '../../../object-collection/shared/lista import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { TruncatableService } from '../../../truncatable/truncatable.service'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; -import { Observable } from 'rxjs'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { BehaviorSubject, Observable } from 'rxjs'; import { RemoteData } from '../../../../core/data/remote-data'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { followLink } from '../../../utils/follow-link-config.model'; -import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; +import { + SearchResultListElementComponent +} from '../../search-result-list-element/search-result-list-element.component'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -37,7 +42,12 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle /** * The workflowitem object that belonging to the result object */ - public workflowitemRD$: Observable>; + public workflowitem$: BehaviorSubject = new BehaviorSubject(null); + + /** + * Display thumbnails if required by configuration + */ + showThumbnails: boolean; public constructor( protected linkService: LinkService, @@ -57,7 +67,14 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle this.linkService.resolveLinks(this.dso, followLink('workflowitem', {}, followLink('item'), followLink('submitter') ), followLink('action')); - this.workflowitemRD$ = this.dso.workflowitem as Observable>; + (this.dso.workflowitem as Observable>).pipe( + getFirstCompletedRemoteData() + ).subscribe((wfiRD: RemoteData) => { + if (wfiRD.hasSucceeded) { + this.workflowitem$.next(wfiRD.payload); + } + }); + this.showThumbnails = this.appConfig.browseBy.showThumbnails; } ngOnDestroy() { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html index 0fd27eb073..6a6e729dea 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html @@ -1,13 +1,12 @@ - - -
-
- -
+ +
+
+
- +
diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts index 9f40ebb1f4..ab5652138e 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { waitForAsync, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -7,7 +7,9 @@ import { of as observableOf } from 'rxjs'; import { Item } from '../../../../core/shared/item.model'; import { PoolSearchResultListElementComponent } from './pool-search-result-list-element.component'; import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { PoolTaskSearchResult } from '../../../object-collection/shared/pool-task-search-result.model'; @@ -72,7 +74,6 @@ const objectCacheServiceMock = jasmine.createSpyObj('ObjectCacheService', { remove: jasmine.createSpy('remove') }); - describe('PoolSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -101,17 +102,15 @@ describe('PoolSearchResultListElementComponent', () => { fixture.detectChanges(); }); - it('should init workflowitem properly', (done) => { - component.workflowitemRD$.subscribe((workflowitemRD) => { - expect(linkService.resolveLinks).toHaveBeenCalledWith( - component.dso, - jasmine.objectContaining({ name: 'workflowitem' }), - jasmine.objectContaining({ name: 'action' }) - ); - expect(workflowitemRD.payload).toEqual(workflowitem); - done(); - }); - }); + it('should init workflowitem properly', fakeAsync(() => { + flush(); + expect(linkService.resolveLinks).toHaveBeenCalledWith( + component.dso, + jasmine.objectContaining({ name: 'workflowitem' }), + jasmine.objectContaining({ name: 'action' }) + ); + expect(component.workflowitem$.value).toEqual(workflowitem); + })); it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.WAITING_CONTROLLER); diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index 60ba846347..a42b3d04d6 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -1,21 +1,26 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { RemoteData } from '../../../../core/data/remote-data'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { PoolTaskSearchResult } from '../../../object-collection/shared/pool-task-search-result.model'; -import { SearchResultListElementComponent } from '../../search-result-list-element/search-result-list-element.component'; +import { + SearchResultListElementComponent +} from '../../search-result-list-element/search-result-list-element.component'; import { TruncatableService } from '../../../truncatable/truncatable.service'; import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; /** * This component renders pool task object for the search result in the list view. @@ -42,7 +47,7 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen /** * The workflowitem object that belonging to the result object */ - public workflowitemRD$: Observable>; + public workflowitem$: BehaviorSubject = new BehaviorSubject(null); /** * The index of this list element @@ -72,12 +77,19 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen this.linkService.resolveLinks(this.dso, followLink('workflowitem', {}, followLink('item'), followLink('submitter') ), followLink('action')); - this.workflowitemRD$ = this.dso.workflowitem as Observable>; + (this.dso.workflowitem as Observable>).pipe( + getFirstCompletedRemoteData() + ).subscribe((wfiRD: RemoteData) => { + if (wfiRD.hasSucceeded) { + this.workflowitem$.next(wfiRD.payload); + } + }); this.showThumbnails = this.appConfig.browseBy.showThumbnails; } ngOnDestroy() { // This ensures the object is removed from cache, when action is performed on task + // this.wfiService.invalidateByHref(this.dso._links.workflowitem.href); this.objectCache.remove(this.dso._links.workflowitem.href); } } From a6d29f5a236af9f05b2c494de5a5056813212f43 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 29 Sep 2022 15:06:30 +0200 Subject: [PATCH 46/78] [CST-6876] Replace deprecated createComponent method and improve code --- ...table-object-component-loader.component.ts | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 366a78b891..1750ce067f 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,6 +1,6 @@ import { + ChangeDetectorRef, Component, - ComponentFactoryResolver, ComponentRef, ElementRef, EventEmitter, @@ -12,6 +12,10 @@ import { SimpleChanges, ViewChild } from '@angular/core'; + +import { Subscription } from 'rxjs'; +import { debounceTime, take } from 'rxjs/operators'; + import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { Context } from '../../../../core/shared/context.model'; @@ -20,9 +24,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor' import { ListableObjectDirective } from './listable-object.directive'; import { CollectionElementLinkType } from '../../collection-element-link.type'; import { hasValue, isNotEmpty } from '../../../empty.util'; -import { Subscription } from 'rxjs'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; -import { take } from 'rxjs/operators'; import { ThemeService } from '../../../theme-support/theme.service'; @Component({ @@ -82,7 +84,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges /** * Directive hook used to place the dynamic child component */ - @ViewChild(ListableObjectDirective, {static: true}) listableObjectDirective: ListableObjectDirective; + @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; /** * View on the badges template, to be passed on to the loaded component (which will place the badges in the desired @@ -120,22 +122,19 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges * The list of input and output names for the dynamic component */ protected inAndOutputNames: string[] = [ - 'object', - 'index', - 'linkType', - 'listID', - 'showLabel', - 'context', - 'viewMode', - 'value', - 'hideBadges', - 'contentChange', - ]; + 'object', + 'index', + 'linkType', + 'listID', + 'showLabel', + 'context', + 'viewMode', + 'value', + 'hideBadges', + 'contentChange', + ]; - constructor( - private componentFactoryResolver: ComponentFactoryResolver, - private themeService: ThemeService - ) { + constructor(private cdr: ChangeDetectorRef, private themeService: ThemeService) { } /** @@ -166,31 +165,33 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context); - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); this.compRef = viewContainerRef.createComponent( - componentFactory, - 0, - undefined, - [ - [this.badges.nativeElement], - ]); + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + ] + } + ); this.connectInputsAndOutputs(); if ((this.compRef.instance as any).reloadedObject) { - (this.compRef.instance as any).reloadedObject.pipe(take(1)).subscribe((reloadedObject: DSpaceObject) => { + (this.compRef.instance as any).reloadedObject.pipe( + // Add delay before emitting event to allow the new object is elaborated on REST side + debounceTime((100)), + take(1) + ).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { this.compRef.destroy(); this.object = reloadedObject; this.instantiateComponent(reloadedObject); - // Add delay before emitting event to allow the new object is instantiated - setTimeout(() => { - this.contentChange.emit(reloadedObject); - }, 100); + this.cdr.detectChanges(); + this.contentChange.emit(reloadedObject); } }); } From 262fac3dd03dc18e403602d0b2d659d3d7ddcf81 Mon Sep 17 00:00:00 2001 From: lucaszc Date: Thu, 29 Sep 2022 14:50:59 -0300 Subject: [PATCH 47/78] Some translations for pt-BR Some translations for pt-BR language --- src/assets/i18n/pt-BR.json5 | 1674 +++++++++++------------------------ 1 file changed, 525 insertions(+), 1149 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index f1774a06ae..f14c18a6f4 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -9,8 +9,6 @@ // "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.", "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.", @@ -49,51 +47,40 @@ "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": "Ocorreu um erro durante o login via ORCID. Certifique-se de ter compartilhado o endereço de e-mail da sua conta ORCID com o DSpace. Se o erro persistir, entre em contato com 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 Metadadados", // "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.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": "Formatar registro", // "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": "Formatar 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.", @@ -129,8 +116,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": "Formatar Bitstream", // "admin.registries.bitstream-formats.edit.description.hint": "", "admin.registries.bitstream-formats.edit.description.hint": "", @@ -208,7 +194,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,14 +209,11 @@ "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 Metadadados", // "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.", @@ -267,14 +249,10 @@ "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.metadata.title": "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}}\".", @@ -343,53 +321,40 @@ "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.registries.schema.title": "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": "Apagar 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": "Personificar 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 personificar 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": "Procurar", // "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": "Metadados", // "admin.access-control.epeople.search.scope.email": "E-mail (exact)", "admin.access-control.epeople.search.scope.email": "Email (exato)", @@ -398,23 +363,19 @@ "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 pessoa ...", // "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.email": "E-mail (exato)", // "admin.access-control.epeople.table.edit": "Edit", "admin.access-control.epeople.table.edit": "Editar", @@ -423,141 +384,106 @@ "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-disabled": "You are not authorized to edit this group", + "admin.access-control.epeople.table.edit.buttons.edit-disabled": "Você não está autorizado a 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": "Apagar \"{{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": "Nenhuma EPeople para mostrar.", // "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 ter um 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 logar", // "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}}\" criada 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": "Failha 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}}\", email \"{{email}}\" já 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": "Failha ao editar EPerson \"{{name}}\", email \"{{email}}\" já 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}}\" editada 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}}\" apagado 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 deletar 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": "Esta EPerson não é mebro 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 aos 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 apagar 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}}\" apagado 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,68 +495,52 @@ "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": "Procurar tudo", // "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": "Apagar \"{{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 este nome ou com esta 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.success": "Grupo \"{{name}}\" apagado com sucesso", // "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.title": "Falha ao deletar o grupo \"{{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.notification.deleted.failure.content": "Causa: \"{{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.permanent": "Este grupo é permanente, portanto, não pode ser editado ou excluído. Você ainda pode adicionar e remover membros do grupo usando esta página.", // "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.alert.workflowGroup": "Este grupo não pode ser modificado ou excluído porque corresponde a uma função no processo de submissão e workflow \"{{name}}\" {{comcol}}. Você não pode deletar ele da função atribuída: 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", @@ -648,111 +558,85 @@ "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.success": "Grupo \"{{name}}\" criado com sucesso", // "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": "Falha ao criar o Grupo \"{{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.created.failure.groupNameInUse": "Falha ao criar o Grupo com o nome: \"{{name}}\", tenha certeza que o nome não está em uso.", // "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": "Falha ao editar o Grupo \"{{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.failure.groupNameInUse": "Nome \"{{name}}\" já foi utilizado!", // "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.notification.edited.success": "Grupo \"{{name}}\" editado com sucesso", // "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.actions.delete": "Apagar Grupo", // "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.header": "Apagar Grupo \"{{ 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.info": "Você tem certeza que quer deletar o Grupo \"{{ 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.cancel": "Cancelar", // "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.delete-group.modal.confirm": "Apagar", // "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.success": "Grupo \"{{ name }}\" apagado com sucesso", // "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.title": "Falha ao apagar o grupo \"{{ 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.notification.deleted.failure.content": "Causa: \"{{ 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.search.head": "Adicionar 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.button.see-all": "Procurar Todos", // "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.headMembers": "Membros Atuais", // "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.metadata": "Metadados", // "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.scope.email": "E-mail (exato)", // "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.search.button": "Procurar", // "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.identity": "Identitade", // "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": "Remover / Adicionar", // "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}}\"", @@ -761,122 +645,94 @@ "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.failure.addMember": "Falha ao adicionar membro: \"{{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.success.deleteMember": "Membro apagado com sucesso: \"{{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.notification.failure.deleteMember": "Falha ao apagar membro: \"{{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.table.edit.buttons.add": "Adicionar membro com o nome \"{{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.notification.failure.noActiveGroup": "Nenhum grupo ativo atualmente, envie um nome primeiro.", // "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-members-yet": "Nenhum membro no grupo ainda, procurar e adicionar.", // "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.members-list.no-items": "Nenhuma EPeople encontrada nesta pesquisa", // "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.notification.failure": "Alguma coisa deu errado: \"{{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.search.head": "Adicionar Subgrupo", // "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.button.see-all": "Procurar Todos", // "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.headSubgroups": "Subgrupos atuais", // "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.search.button": "Procurar", // "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.collectionOrCommunity": "Coleção/Comunidade", // "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.remove": "Remover subgrupo com nome \"{{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.buttons.add": "Adicionar subgrupo com nome \"{{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.success.addSubgroup": "Subgrupo adicionado com sucesso: \"{{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.failure.addSubgroup": "Falha ao adicionar subgrupo: \"{{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.success.deleteSubgroup": "Subgrupo: \"{{name}}\" apagado com sucesso", // "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.deleteSubgroup": "Falha ao apagar subgrupo: \"{{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.noActiveGroup": "Nenhum grupo ativo atual, envie um nome primeiro.", // "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.notification.failure.subgroupToAddIsActiveGroup": "Este é o grupo atual, não pode ser adicionado.", // "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-items": "Nenhum grupo encontrado com este nome ou com este 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.subgroups-list.no-subgroups-yet": "Ainda não há subgrupos no grupo.", // "admin.access-control.groups.form.return": "Back", - // TODO Source message changed - Revise the translation - "admin.access-control.groups.form.return": "Retornar aos grupos", - - + "admin.access-control.groups.form.return": "Voltar", // "admin.search.breadcrumbs": "Administrative Search", - // TODO New key - Add a translation - "admin.search.breadcrumbs": "Administrative Search", + "admin.search.breadcrumbs": "Pesquisa Administrativa", // "admin.search.collection.edit": "Edit", "admin.search.collection.edit": "Editar", @@ -885,8 +741,7 @@ "admin.search.community.edit": "Editar", // "admin.search.item.delete": "Delete", - // TODO New key - Add a translation - "admin.search.item.delete": "Delete", + "admin.search.item.delete": "Apagar", // "admin.search.item.edit": "Edit", "admin.search.item.edit": "Editar", @@ -903,91 +758,64 @@ "admin.search.item.move": "Mover", // "admin.search.item.reinstate": "Reinstate", - // TODO New key - Add a translation - "admin.search.item.reinstate": "Reinstate", + "admin.search.item.reinstate": "Reintegrar", // "admin.search.item.withdraw": "Withdraw", - // TODO New key - Add a translation - "admin.search.item.withdraw": "Withdraw", + "admin.search.item.withdraw": "Retirar", // "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", - - - + "administrativeView.search.results.head": "Pesquisa Administrativa", // "admin.workflow.breadcrumbs": "Administer Workflow", - // TODO New key - Add a translation - "admin.workflow.breadcrumbs": "Administer Workflow", + "admin.workflow.breadcrumbs": "Administrar Workflow", // "admin.workflow.title": "Administer Workflow", - // TODO New key - Add a translation - "admin.workflow.title": "Administer Workflow", + "admin.workflow.title": "Administrar 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.delete": "Apagar", // "admin.workflow.item.send-back": "Send back", - // TODO New key - Add a translation - "admin.workflow.item.send-back": "Send back", - - + "admin.workflow.item.send-back": "Devolver", // "admin.metadata-import.breadcrumbs": "Import Metadata", - // TODO New key - Add a translation - "admin.metadata-import.breadcrumbs": "Import Metadata", + "admin.metadata-import.breadcrumbs": "Importar Metadados", // "admin.metadata-import.title": "Import Metadata", - // TODO New key - Add a translation - "admin.metadata-import.title": "Import Metadata", + "admin.metadata-import.title": "Importar Metadados", // "admin.metadata-import.page.header": "Import Metadata", - // TODO New key - Add a translation - "admin.metadata-import.page.header": "Import Metadata", + "admin.metadata-import.page.header": "Importar Metadados", // "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.help": "Você arrastar soltar ou procurar arquivos CSV que contêm operações de metadados em lote em arquivos aqui", // "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.dropMsg": "Arraste um CSV de metadados para importar", // "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.dropMsgReplace": "Solte para substituir o CSV de metadados a ser importado", // "admin.metadata-import.page.button.return": "Back", - // TODO New key - Add a translation - "admin.metadata-import.page.button.return": "Back", + "admin.metadata-import.page.button.return": "Voltar", // "admin.metadata-import.page.button.proceed": "Proceed", - // TODO New key - Add a translation - "admin.metadata-import.page.button.proceed": "Proceed", + "admin.metadata-import.page.button.proceed": "Continuar", // "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.error.addFile": "Selecione o arquivo primeiro!", // "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": "Validar Somente", // "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.", - - - + "admin.metadata-import.page.validateOnly.hint": "Quando selecionado, o CSV carregado será validado. Você receberá um relatório das alterações detectadas, mas nenhuma alteração será salva.", // "auth.errors.invalid-user": "Invalid email address or password.", "auth.errors.invalid-user": "Endereço de email ou senha inválidos.", @@ -996,54 +824,42 @@ "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.", - - + "auth.messages.token-refresh-failed": "Falha ao atualizar seu token de sessão. Por favor faça login novamente.", // "bitstream.download.page": "Now downloading {{bitstream}}..." , - // TODO New key - Add a translation - "bitstream.download.page": "Now downloading {{bitstream}}..." , + "bitstream.download.page": "Agora downloading {{bitstream}}..." , // "bitstream.download.page.back": "Back" , - // TODO New key - Add a translation - "bitstream.download.page.back": "Back" , + "bitstream.download.page.back": "Voltar" , // "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.link": "Editar as políticas do bitstream's", // "bitstream.edit.authorizations.title": "Edit bitstream's Policies", - // TODO New key - Add a translation - "bitstream.edit.authorizations.title": "Edit bitstream's Policies", + "bitstream.edit.authorizations.title": "Editar as políticas do bitstream's", // "bitstream.edit.return": "Back", - // TODO New key - Add a translation - "bitstream.edit.return": "Back", + "bitstream.edit.return": "Voltar", // "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.hint": "Opcionalmente, forneça uma breve descrição do arquivo, por exemplo \"Main article\" ou \"Experimento leitura de dados\".", // "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.", + // "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 Autorizações..., 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.embargo.label": "Embargo até data específica", // "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.hint": "Altere o nome do arquivo para o bitstream. Observe que isso alterará o URL do fluxo de bits de exibição, mas os links antigos ainda serão resolvidos, desde que a ID da sequência não seja alterada.", // "bitstream.edit.form.fileName.label": "Filename", "bitstream.edit.form.fileName.label": "Nome do arquivo", @@ -1052,153 +868,117 @@ "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.newFormat.hint": "O aplicativo que você usou para criar o arquivo e o número da versão (por exemplo, \"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.primaryBitstream.label": "Bitstream primário", // "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.hint": "Se o formato não estiver na lista acima, selecione \"formato fora da lista\" acima e descreva-o em \"Descrever novo formato\".", // "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.form.selectedFormat.unknown": "Formato não está na lista", // "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.notifications.error.format.title": "Um erro ocorreu salvando o formato do bitstream's", // "bitstream.edit.form.iiifLabel.label": "IIIF Label", - // TODO New key - Add a translation - "bitstream.edit.form.iiifLabel.label": "IIIF Label", + "bitstream.edit.form.iiifLabel.label": "Etiqueta IIIF", // "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.iiifLabel.hint": "Etiqueta de tela para esta imagem. Se não for fornecido, o rótulo padrão será usado.", // "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.label": "Tabela de Conteúdos IIIF", // "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.iiifToc.hint": "Adicionar texto aqui faz com que este seja o início de um novo intervalo de índice.", // "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.label": "Largura da tela IIIF", // "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.iiifWidth.hint": "A largura da tela geralmente deve corresponder à largura da imagem.", // "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.label": "Altura da tela IIIF", // "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.form.iiifHeight.hint": "A altura da tela geralmente deve corresponder à altura da imagem.", // "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.content": "Suas alterações neste fluxo de bits foram salvas.", // "bitstream.edit.notifications.saved.title": "Bitstream saved", - // TODO New key - Add a translation - "bitstream.edit.notifications.saved.title": "Bitstream saved", + "bitstream.edit.notifications.saved.title": "Bitstream salvo", // "bitstream.edit.title": "Edit bitstream", - // TODO New key - Add a translation - "bitstream.edit.title": "Edit bitstream", + "bitstream.edit.title": "Editar 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.canDownload1": "Você já tem acesso a este arquivo. Se você deseja baixar o arquivo, clique ", // "bitstream-request-a-copy.alert.canDownload2": "here", - // TODO New key - Add a translation - "bitstream-request-a-copy.alert.canDownload2": "here", + "bitstream-request-a-copy.alert.canDownload2": "aqui", // "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.header": "Solicite uma cópia do arquivo", // "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": "Insira as seguintes informações para solicitar uma cópia do seguinte 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.one": "Solicitando o seguinte arquivo: ", // "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.intro.bitstream.all": "Solicitando todos os arquivos. ", // "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.label": "Nome *", // "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.name.error": "O nome é requerido", // "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.label": "Seu endereço de e-mail *", // "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.hint": "Este endereço de e-mail é usado para enviar o arquivo.", // "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.email.error": "Por favor entre um endereço de email válido.", // "bitstream-request-a-copy.allfiles.label": "Files", - // TODO New key - Add a translation - "bitstream-request-a-copy.allfiles.label": "Files", + "bitstream-request-a-copy.allfiles.label": "Arquivos", // "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-false.label": "Apenas o arquivo solicitado", // "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.files-all-true.label": "Todos os arquivos (deste item) em acesso restrito", // "bitstream-request-a-copy.message.label": "Message", - // TODO New key - Add a translation - "bitstream-request-a-copy.message.label": "Message", + "bitstream-request-a-copy.message.label": "Mensagem", // "bitstream-request-a-copy.return": "Back", - // TODO New key - Add a translation - "bitstream-request-a-copy.return": "Back", + "bitstream-request-a-copy.return": "Voltar", // "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": "Solicitar cópia", // "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.success": "A solicitação de item foi enviada com sucesso.", // "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.", + "bitstream-request-a-copy.submit.error": "Ocorreu um erro ao enviar a solicitação de item.", // "browse.back.all-results": "All browse results", - // TODO New key - Add a translation - "browse.back.all-results": "All browse results", + "browse.back.all-results": "Todos os resultados", // "browse.comcol.by.author": "By Author", "browse.comcol.by.author": "Por Autor", @@ -1243,16 +1023,13 @@ "browse.metadata.title.breadcrumbs": "Pesquisar por Título", // "pagination.next.button": "Next", - // TODO New key - Add a translation - "pagination.next.button": "Next", + "pagination.next.button": "Próximo", // "pagination.previous.button": "Previous", - // TODO New key - Add a translation - "pagination.previous.button": "Previous", + "pagination.previous.button": "Anterior", // "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", + "pagination.next.button.disabled.tooltip": "Não há mais páginas de resultados", // "browse.startsWith": ", starting with {{ startsWith }}", // TODO New key - Add a translation @@ -1265,12 +1042,10 @@ "browse.startsWith.choose_year": "(Escolha o ano)", // "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.choose_year.label": "Escolha o ano de emissão", // "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.jump": "Filtrar resultados por ano ou mês", // "browse.startsWith.months.april": "April", "browse.startsWith.months.april": "Abril", @@ -1303,8 +1078,7 @@ "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.none.label": "Escolha o mês de emissão", // "browse.startsWith.months.november": "November", "browse.startsWith.months.november": "Novembro", @@ -1320,8 +1094,7 @@ "browse.startsWith.submit": "Ir", // "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_date": "Filtrar bresultados pela data", // "browse.startsWith.type_date.label": "Or type in a date (year-month) and click on the Browse button", // TODO New key - Add a translation @@ -1366,8 +1139,7 @@ "collection.delete.confirm": "Confirmar", // "collection.delete.processing": "Deleting", - // TODO New key - Add a translation - "collection.delete.processing": "Deleting", + "collection.delete.processing": "Apagando", // "collection.delete.head": "Delete Collection", "collection.delete.head": "Apagar Coleção", @@ -1390,8 +1162,7 @@ "collection.edit.head": "Editar Coleção", // "collection.edit.breadcrumbs": "Edit Collection", - // TODO New key - Add a translation - "collection.edit.breadcrumbs": "Edit Collection", + "collection.edit.breadcrumbs": "Editar Coleção", @@ -1624,20 +1395,17 @@ // "collection.edit.template.add-button": "Add", - // TODO New key - Add a translation - "collection.edit.template.add-button": "Add", + "collection.edit.template.add-button": "Adicionar", // "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.cancel": "Cancelar", // "collection.edit.template.delete-button": "Delete", - // TODO New key - Add a translation - "collection.edit.template.delete-button": "Delete", + "collection.edit.template.delete-button": "Apagar", // "collection.edit.template.edit-button": "Edit", "collection.edit.template.edit-button": "Editar", @@ -1714,8 +1482,7 @@ "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.edit": "Editar está coleção", // "collection.page.handle": "Permanent URI for this collection", "collection.page.handle": "URI Permanente para esta coleção", @@ -1737,40 +1504,28 @@ // "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.head": "Controles de Harvest", // "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.submit.error": "Alguma coisa errada durante a inicialização e teste das configurações", // "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.failed": "O script de teste das configurações falhou", // "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.completed": "O script de teste das configurações terminou com sucesso", // "collection.source.controls.test.submit": "Test configuration", - // TODO New key - Add a translation - "collection.source.controls.test.submit": "Test configuration", + "collection.source.controls.test.submit": "Testar a configuração", // "collection.source.controls.test.running": "Testing configuration...", - // TODO New key - Add a translation - "collection.source.controls.test.running": "Testing configuration...", + "collection.source.controls.test.running": "Testando a configuração...", // "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.success": "A importação foi iniciada com sucesso", // "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.error": "Alguma coisa errada durante a inicialização da importação", // "collection.source.controls.import.submit": "Import now", - // TODO New key - Add a translation - "collection.source.controls.import.submit": "Import now", + "collection.source.controls.import.submit": "Importar agora", // "collection.source.controls.import.running": "Importing...", - // TODO New key - Add a translation - "collection.source.controls.import.running": "Importing...", + "collection.source.controls.import.running": "Importando...", // "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.failed": "Um erro ocorreu durante a importação", // "collection.source.controls.import.completed": "The import completed", // TODO New key - Add a translation "collection.source.controls.import.completed": "The import completed", @@ -1778,36 +1533,25 @@ // 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.submit.error": "Alguma coisa errada durante a inicialização da redefinição e reimportação", // "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.failed": "Um erro ocorreru durante o reset e reimportação", // "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.completed": "Completou o reset e a reimportação", // "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.submit": "Resetar e reimportar", // "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.reset.running": "Resetando e importando...", // "collection.source.controls.harvest.status": "Harvest status:", - // TODO New key - Add a translation - "collection.source.controls.harvest.status": "Harvest status:", + "collection.source.controls.harvest.status": "Status do Harvest:", // "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.start": "Hora de ínicio Harvest:", // "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.last": "Última hora de 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.message": "Informação Harvest:", // "collection.source.controls.harvest.no-information": "N/A", - // TODO New key - Add a translation - "collection.source.controls.harvest.no-information": "N/A", - + "collection.source.controls.harvest.no-information": "N/D", // "collection.source.update.notifications.error.content": "The provided settings have been tested and didn't work.", // TODO New key - Add a translation @@ -1816,8 +1560,6 @@ // "collection.source.update.notifications.error.title": "Server Error", "collection.source.update.notifications.error.title": "Erro no Servidor", - - // "communityList.breadcrumbs": "Community List", "communityList.breadcrumbs": "Lista da Comunidade", @@ -1830,21 +1572,17 @@ // "communityList.showMore": "Show More", "communityList.showMore": "Mostrar Mais", - - // "community.create.head": "Create a Community", "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.notifications.success": "Comunidade criada com sucesso", // "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.curate.header": "Curadoria da Comunidade: {{community}}", // "community.delete.cancel": "Cancel", "community.delete.cancel": "Cancelar", @@ -1853,8 +1591,7 @@ "community.delete.confirm": "Confirmar", // "community.delete.processing": "Deleting...", - // TODO New key - Add a translation - "community.delete.processing": "Deleting...", + "community.delete.processing": "Apagando...", // "community.delete.head": "Delete Community", "community.delete.head": "Apagar Comunidade", @@ -1877,133 +1614,96 @@ // "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.title": "Apagar 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.delete-undo.title": "Desfazer apagar", // "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.error": "Erro no Upload do Logo da Communidade. Por favor verifique o conteúdo antes de tentar novamente.", // "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.add.success": "Sucesso no Upload do logo da Comunidade.", // "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.title": "Logo apagado", // "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.success.content": "Logo da comunidade apagado com sucesso", // "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.notifications.delete.error.title": "Erro apagando o 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.logo.upload": "Arraste um Logo de Comunidade para fazer 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.success": "Comunidade Editada com sucesso", // "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.unauthorized": "Você não tem privilégio de fazer esta mudança", // "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.notifications.error": "Um erro ocorreu enquanto editava a Comunidade", // "community.edit.return": "Back", - // TODO New key - Add a translation - "community.edit.return": "Back", - - + "community.edit.return": "Voltar", // "community.edit.tabs.curate.head": "Curate", - // TODO New key - Add a translation - "community.edit.tabs.curate.head": "Curate", + "community.edit.tabs.curate.head": "Curador", // "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.curate.title": "Editar Comunidade - Curador", // "community.edit.tabs.metadata.head": "Edit Metadata", - // TODO New key - Add a translation - "community.edit.tabs.metadata.head": "Edit Metadata", + "community.edit.tabs.metadata.head": "Editar Metadado", // "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.metadata.title": "Editar Communidade - Metadados", // "community.edit.tabs.roles.head": "Assign Roles", - // TODO New key - Add a translation - "community.edit.tabs.roles.head": "Assign Roles", + "community.edit.tabs.roles.head": "Atribuir Funções", // "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.roles.title": "Editar Comunidade - Funções", // "community.edit.tabs.authorizations.head": "Authorizations", - // TODO New key - Add a translation - "community.edit.tabs.authorizations.head": "Authorizations", + "community.edit.tabs.authorizations.head": "Authorizações", // "community.edit.tabs.authorizations.title": "Community Edit - Authorizations", - // TODO New key - Add a translation - "community.edit.tabs.authorizations.title": "Community Edit - Authorizations", - - + "community.edit.tabs.authorizations.title": "Editar Communidades - Authorizações", // "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.no-group": "Nenhum", // "comcol-role.edit.create": "Create", - // TODO New key - Add a translation - "comcol-role.edit.create": "Create", + "comcol-role.edit.create": "Criar", // "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.create.error.title": "Falha ao criar o grupo para a '{{ role }}' role", // "comcol-role.edit.restrict": "Restrict", - // TODO New key - Add a translation - "comcol-role.edit.restrict": "Restrict", + "comcol-role.edit.restrict": "Restrito", // "comcol-role.edit.delete": "Delete", - // TODO New key - Add a translation - "comcol-role.edit.delete": "Delete", + "comcol-role.edit.delete": "Apagar", // "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.delete.error.title": "Falha ao deletar a '{{ role }}' role's para o grupo", // "comcol-role.edit.community-admin.name": "Administrators", - // TODO New key - Add a translation - "comcol-role.edit.community-admin.name": "Administrators", + "comcol-role.edit.community-admin.name": "Administradores", // "comcol-role.edit.collection-admin.name": "Administrators", - // TODO New key - Add a translation - "comcol-role.edit.collection-admin.name": "Administrators", + "comcol-role.edit.collection-admin.name": "Administradores", // "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).", @@ -2016,8 +1716,7 @@ // "comcol-role.edit.submitters.name": "Submitters", - // TODO New key - Add a translation - "comcol-role.edit.submitters.name": "Submitters", + "comcol-role.edit.submitters.name": "Remetentes", // "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 @@ -2025,8 +1724,7 @@ // "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.name": "Default item acesso leitura", // "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 @@ -2038,8 +1736,7 @@ // "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.name": "Default bitstream acesso leitura", // "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 @@ -2051,31 +1748,24 @@ // "comcol-role.edit.editor.name": "Editors", - // TODO New key - Add a translation - "comcol-role.edit.editor.name": "Editors", + "comcol-role.edit.editor.name": "Editores", // "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.editor.description": "Editores podem editar metadados de submissões de entrada, e aceitar ou rejeitar elas.", // "comcol-role.edit.finaleditor.name": "Final editors", - // TODO New key - Add a translation - "comcol-role.edit.finaleditor.name": "Final editors", + "comcol-role.edit.finaleditor.name": "Editores Finais", // "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.finaleditor.description": "Editores Finais podem editar os metadadods de submissões de entrada, mas não podem rejeitar elas.", // "comcol-role.edit.reviewer.name": "Reviewers", - // TODO New key - Add a translation - "comcol-role.edit.reviewer.name": "Reviewers", + "comcol-role.edit.reviewer.name": "Revisores", // "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.", - + "comcol-role.edit.reviewer.description": "Revisores podem aceitar ou rejeitar submissões de entrada.. Entretanto, eles não podem editar os metadados da submissão.", // "community.form.abstract": "Short Description", @@ -2097,8 +1787,7 @@ "community.form.title": "Nome", // "community.page.edit": "Edit this community", - // TODO New key - Add a translation - "community.page.edit": "Edit this community", + "community.page.edit": "Editar esta comunidade", // "community.page.handle": "Permanent URI for this community", "community.page.handle": "URI Permanente desta comunidade", @@ -2121,106 +1810,82 @@ // "cookies.consent.accept-all": "Accept all", - // TODO New key - Add a translation - "cookies.consent.accept-all": "Accept all", + "cookies.consent.accept-all": "Aceitar tudo", // "cookies.consent.accept-selected": "Accept selected", - // TODO New key - Add a translation - "cookies.consent.accept-selected": "Accept selected", + "cookies.consent.accept-selected": "Aceitar selecionados", // "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.description": "Este aplicativo é carregado por padrão (mas você pode desativar)", // "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.opt-out.title": "(desativar)", // "cookies.consent.app.purpose": "purpose", - // TODO New key - Add a translation - "cookies.consent.app.purpose": "purpose", + "cookies.consent.app.purpose": "propósito", // "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.description": "Esta aplicação é sempre necessária", // "cookies.consent.app.required.title": "(always required)", - // TODO New key - Add a translation - "cookies.consent.app.required.title": "(always required)", + "cookies.consent.app.required.title": "(sempre requerido)", // "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.update": "Houve alterações desde sua última visita, atualize seu consentimento.", // "cookies.consent.close": "Close", - // TODO New key - Add a translation - "cookies.consent.close": "Close", + "cookies.consent.close": "Fechar", // "cookies.consent.decline": "Decline", - // TODO New key - Add a translation - "cookies.consent.decline": "Decline", + "cookies.consent.decline": "Recusar", // "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": "Coletamos e processamos suas informações pessoais para os seguintes propósitos: Autenticação, Preferências, Reconhecimento e Estatísticas.
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.description.no-privacy": "Coletamos e processamos suas informações pessoais para os seguintes propósitos: Autenticação, Preferências, Reconhecimento e Estatísticas.", // "cookies.consent.content-notice.learnMore": "Customize", - // TODO New key - Add a translation - "cookies.consent.content-notice.learnMore": "Customize", + "cookies.consent.content-notice.learnMore": "Customizar", // "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.description": "Aqui você pode ver e personalizar as informações que coletamos sobre você.", // "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.name": "política de privacidade", // "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.privacy-policy.text": "Para saber mais, leia nosso {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.content-modal.title": "Informações que coletamos", // "cookies.consent.app.title.authentication": "Authentication", - // TODO New key - Add a translation - "cookies.consent.app.title.authentication": "Authentication", + "cookies.consent.app.title.authentication": "Authenticação", // "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.description.authentication": "Obrigatório para fazer login", // "cookies.consent.app.title.preferences": "Preferences", - // TODO New key - Add a translation - "cookies.consent.app.title.preferences": "Preferences", + "cookies.consent.app.title.preferences": "Preferências", // "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.description.preferences": "Necessário para salvar suas preferências", // "cookies.consent.app.title.acknowledgement": "Acknowledgement", - // TODO New key - Add a translation - "cookies.consent.app.title.acknowledgement": "Acknowledgement", + "cookies.consent.app.title.acknowledgement": "Reconhecimento", // "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.description.acknowledgement": "Necessário para salvar suas confirmações e consentimentos", // "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", @@ -2230,12 +1895,10 @@ // "cookies.consent.purpose.functional": "Functional", - // TODO New key - Add a translation - "cookies.consent.purpose.functional": "Functional", + "cookies.consent.purpose.functional": "Funcional", // "cookies.consent.purpose.statistical": "Statistical", - // TODO New key - Add a translation - "cookies.consent.purpose.statistical": "Statistical", + "cookies.consent.purpose.statistical": "Estatística", // "curation-task.task.citationpage.label": "Generate Citation Page", // TODO New key - Add a translation @@ -2258,12 +1921,10 @@ "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-task.task.vscan.label": "Escanear Virus", @@ -5105,16 +4766,14 @@ // "process.detail.arguments" : "Arguments", - // TODO New key - Add a translation - "process.detail.arguments" : "Arguments", + "process.detail.arguments" : "Argumentos", // "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.back" : "Voltar", // "process.detail.output" : "Process Output", // TODO New key - Add a translation @@ -5181,8 +4840,7 @@ "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.cancel": "Cancelar", // "process.detail.delete.confirm": "Delete process", // TODO New key - Add a translation @@ -5219,8 +4877,7 @@ "process.overview.table.status" : "Status", // "process.overview.table.user" : "User", - // TODO New key - Add a translation - "process.overview.table.user" : "User", + "process.overview.table.user" : "Usuário", // "process.overview.title": "Processes Overview", // TODO New key - Add a translation @@ -5231,12 +4888,10 @@ "process.overview.breadcrumbs": "Processes Overview", // "process.overview.new": "New", - // TODO New key - Add a translation - "process.overview.new": "New", + "process.overview.new": "Novo", // "process.overview.table.actions": "Actions", - // TODO New key - Add a translation - "process.overview.table.actions": "Actions", + "process.overview.table.actions": "Ações", // "process.overview.delete": "Delete {{count}} processes", // TODO New key - Add a translation @@ -5280,12 +4935,10 @@ "profile.card.identify": "Identify", // "profile.card.security": "Security", - // TODO New key - Add a translation - "profile.card.security": "Security", + "profile.card.security": "Segurança", // "profile.form.submit": "Save", - // TODO New key - Add a translation - "profile.form.submit": "Save", + "profile.form.submit": "Salvar", // "profile.groups.head": "Authorization groups you belong to", // TODO New key - Add a translation @@ -5450,12 +5103,10 @@ // "media-viewer.next": "Next", - // TODO New key - Add a translation - "media-viewer.next": "Next", + "media-viewer.next": "Próximo", // "media-viewer.previous": "Previous", - // TODO New key - Add a translation - "media-viewer.previous": "Previous", + "media-viewer.previous": "Anterior", // "media-viewer.playlist": "Playlist", // TODO New key - Add a translation @@ -5463,108 +5114,83 @@ // "register-email.title": "New user registration", - // TODO New key - Add a translation - "register-email.title": "New user registration", + "register-email.title": "Novo registro de usuário", // "register-page.create-profile.header": "Create Profile", - // TODO New key - Add a translation - "register-page.create-profile.header": "Create Profile", + "register-page.create-profile.header": "Criar 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.header": "Identificar", // "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": "Primeiro Nome *", // "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.first-name.error": "Por favor preencha o Primeiro Nome", // "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": "Último Nome *", // "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.last-name.error": "Por favor preencha o Último Nome", // "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.contact": "Telefone de Contato", // "register-page.create-profile.identification.language": "Language", - // TODO New key - Add a translation - "register-page.create-profile.identification.language": "Language", + "register-page.create-profile.identification.language": "Linguagem", // "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.header": "Segurança", // "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.", + "register-page.create-profile.security.info": "Por favor entre a senha na caixa abaixo, e confirme digitando na segunda caixa abaixo.", // "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.password": "Senha *", // "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.label.passwordrepeat": "Redigite para confirmar *", // "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.empty-password": "Por favor entre a senha na caixa abaixo.", // "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.matching-passwords": "As senhas não coincidem.", // "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": "Registro Completo", // "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.content": "Algo deu errado ao registrar um novo usuário.", // "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.error.head": "Registro falhou", // "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.content": "O registro foi realizado com sucesso. Você está logado como o usuário criado", // "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.create-profile.submit.success.head": "Registo completo", // "register-page.registration.header": "New user registration", - // TODO New key - Add a translation - "register-page.registration.header": "New user registration", + "register-page.registration.header": "Novo registro de usuário", // "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.info": "Registre uma conta para assinar coleções para atualizações por e-mail e enviar novos itens para o DSpace.", // "register-page.registration.email": "Email Address *", - // TODO New key - Add a translation - "register-page.registration.email": "Email Address *", + "register-page.registration.email": "Endereço de Email *", // "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.required": "Por favor preencha o endereço de email", // "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.error.pattern": "Por favor preencha com um endereço válido de email", // "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.", @@ -5573,45 +5199,36 @@ "register-page.registration.submit": "Cadastrar", // "register-page.registration.success.head": "Verification email sent", - // TODO New key - Add a translation "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.success.content": "Um email foi enviado para {{ email }} contendo uma URL especial e mais instruções.", // "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.head": "Erro tentando registrar o 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 }}", + "register-page.registration.error.content": "Um erro ocorreu enquanto registrava o seguinte endereço de email: {{ 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.relationship-type.content": "Nenhuma correspondência adequada foi encontrada para o tipo de relacionamento {{ type }} entre os dois itens", // "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.server.content": "O servidor retornou um erro", // "relationships.add.error.title": "Unable to add relationship", - // TODO New key - Add a translation - "relationships.add.error.title": "Unable to add relationship", + "relationships.add.error.title": "Não foi possível adicionar relacionamento", // "relationships.isAuthorOf": "Authors", "relationships.isAuthorOf": "Autores", // "relationships.isAuthorOf.Person": "Authors (persons)", - // TODO New key - Add a translation - "relationships.isAuthorOf.Person": "Authors (persons)", + "relationships.isAuthorOf.Person": "Autores (pessoas)", // "relationships.isAuthorOf.OrgUnit": "Authors (organizational units)", - // TODO New key - Add a translation - "relationships.isAuthorOf.OrgUnit": "Authors (organizational units)", + "relationships.isAuthorOf.OrgUnit": "Autores (unidades organizacionais)", // "relationships.isIssueOf": "Journal Issues", "relationships.isIssueOf": "Fascículo", @@ -5647,62 +5264,49 @@ "relationships.isVolumeOf": "Volumes do Periódico", // "relationships.isContributorOf": "Contributors", - // TODO New key - Add a translation - "relationships.isContributorOf": "Contributors", + "relationships.isContributorOf": "Contribuidores", // "relationships.isContributorOf.OrgUnit": "Contributor (Organizational Unit)", - // TODO New key - Add a translation - "relationships.isContributorOf.OrgUnit": "Contributor (Organizational Unit)", + "relationships.isContributorOf.OrgUnit": "Contribuidor (Unidade Organizacional)", // "relationships.isContributorOf.Person": "Contributor", - // TODO New key - Add a translation - "relationships.isContributorOf.Person": "Contributor", + "relationships.isContributorOf.Person": "Contribuidor", // "relationships.isFundingAgencyOf.OrgUnit": "Funder", - // TODO New key - Add a translation - "relationships.isFundingAgencyOf.OrgUnit": "Funder", + "relationships.isFundingAgencyOf.OrgUnit": "Financiador", // "repository.image.logo": "Repository logo", - // TODO New key - Add a translation - "repository.image.logo": "Repository logo", + "repository.image.logo": "Logo do Repositório", // "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.button": "Adicionar", // "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.": "Adicionar uma nova política", // "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.bitstream": "Adicionar uma nova política para o Bitstream", // "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.item": "Adicionar uma nova política para Item", // "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.community": "Adicionar uma nova política para Comunidade", // "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.add.for.collection": "Adicionar uma nova política para Coleção", // "resource-policies.create.page.heading": "Create new resource policy for ", // TODO New key - Add a translation @@ -5713,16 +5317,14 @@ "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.success.content": "Operação com sucesso", // "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": "Apagar selecionado", // "resource-policies.delete.btn.title": "Delete selected resource policies", // TODO New key - Add a translation @@ -5733,8 +5335,7 @@ "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.delete.success.content": "Operação com sucesso", // "resource-policies.edit.page.heading": "Edit resource policy ", // TODO New key - Add a translation @@ -5753,8 +5354,7 @@ "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.success.content": "Operação com sucesso", // "resource-policies.edit.page.title": "Edit resource policy", // TODO New key - Add a translation @@ -5773,32 +5373,26 @@ "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.select.btn": "Selecione", // "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.tab.group": "Procurar por um grupo", // "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.action": "Ação", // "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.table.headers.name": "Nome", // "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.header": "Não posso alterar o tipo", // "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 @@ -5813,95 +5407,75 @@ "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.end.label": "Data Fim", // "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.date.start.label": "Data Início", // "resource-policies.form.description.label": "Description", - // TODO New key - Add a translation - "resource-policies.form.description.label": "Description", + "resource-policies.form.description.label": "Descrição", // "resource-policies.form.name.label": "Name", - // TODO New key - Add a translation - "resource-policies.form.name.label": "Name", + "resource-policies.form.name.label": "Nome", // "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.label": "Selecione o tipo política", // "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.action": "Ação", // "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.end": "Data Fim", // "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.date.start": "Data Início", // "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.group": "Editar grupo", // "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.edit.policy": "Editar política", // "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.group": "Grupo", // "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.name": "Nome", // "resource-policies.table.headers.policyType": "type", - // TODO New key - Add a translation - "resource-policies.table.headers.policyType": "type", + "resource-policies.table.headers.policyType": "tipo", // "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.bitstream": "Políticas para 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.item": "Políticas para 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.community": "Políticas para Comunidade", // "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", + "resource-policies.table.headers.title.for.collection": "Políticas para Coleção", @@ -6014,8 +5588,7 @@ "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.creativeWorkEditor.label": "Pesquisar editor", // "search.filters.filter.creativeWorkKeywords.head": "Subject", "search.filters.filter.creativeWorkKeywords.head": "Assunto", @@ -6024,8 +5597,7 @@ "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.creativeWorkKeywords.label": "Pesquisar assunto", // "search.filters.filter.creativeWorkPublisher.head": "Publisher", "search.filters.filter.creativeWorkPublisher.head": "Editora", @@ -6034,8 +5606,7 @@ "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.creativeWorkPublisher.label": "Search editora", // "search.filters.filter.dateIssued.head": "Date", "search.filters.filter.dateIssued.head": "Data", @@ -6050,8 +5621,7 @@ "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.dateIssued.min.label": "Início", // "search.filters.filter.dateSubmitted.head": "Date submitted", "search.filters.filter.dateSubmitted.head": "Data de submissão", @@ -6060,8 +5630,7 @@ "search.filters.filter.dateSubmitted.placeholder": "Data de submissão", // "search.filters.filter.dateSubmitted.label": "Search date submitted", - // TODO New key - Add a translation - "search.filters.filter.dateSubmitted.label": "Search date submitted", + "search.filters.filter.dateSubmitted.label": "Procurar data de submissão", // "search.filters.filter.discoverable.head": "Non-discoverable", // TODO New key - Add a translation @@ -6078,12 +5647,10 @@ "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.entityType.label": "Procurar tipo de item", // "search.filters.filter.expand": "Expand filter", - // TODO New key - Add a translation - "search.filters.filter.expand": "Expand filter", + "search.filters.filter.expand": "Expandir filtro", // "search.filters.filter.has_content_in_original_bundle.head": "Has files", "search.filters.filter.has_content_in_original_bundle.head": "Tem arquivos", @@ -6095,8 +5662,7 @@ "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.itemtype.label": "Procurar tipo", // "search.filters.filter.jobTitle.head": "Job Title", "search.filters.filter.jobTitle.head": "Cargo", @@ -6105,8 +5671,7 @@ "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.jobTitle.label": "Procurar cargo", // "search.filters.filter.knowsLanguage.head": "Known language", "search.filters.filter.knowsLanguage.head": "Idioma conhecido", @@ -6115,8 +5680,7 @@ "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.knowsLanguage.label": "Procurar Idioma conhecido", // "search.filters.filter.namedresourcetype.head": "Status", "search.filters.filter.namedresourcetype.head": "Estado", @@ -6125,8 +5689,7 @@ "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.namedresourcetype.label": "Procurar por estado", // "search.filters.filter.objectpeople.head": "People", "search.filters.filter.objectpeople.head": "Pessoas", @@ -6135,8 +5698,7 @@ "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.objectpeople.label": "Procurar pessoas", // "search.filters.filter.organizationAddressCountry.head": "Country", "search.filters.filter.organizationAddressCountry.head": "País", @@ -6145,8 +5707,7 @@ "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.organizationAddressCountry.label": "Procurar país", // "search.filters.filter.organizationAddressLocality.head": "City", "search.filters.filter.organizationAddressLocality.head": "Cidade", @@ -6155,8 +5716,7 @@ "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.organizationAddressLocality.label": "Procurar cidade", // "search.filters.filter.organizationFoundingDate.head": "Date Founded", "search.filters.filter.organizationFoundingDate.head": "Data de Fundação", @@ -6165,8 +5725,7 @@ "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.organizationFoundingDate.label": "Procurar Data de Fundação", // "search.filters.filter.scope.head": "Scope", "search.filters.filter.scope.head": "Escopo", @@ -6175,8 +5734,7 @@ "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.scope.label": "Procurar filtro de escopo", // "search.filters.filter.show-less": "Collapse", "search.filters.filter.show-less": "Mostrar menos", @@ -6191,8 +5749,7 @@ "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.subject.label": "Procurar assunto", // "search.filters.filter.submitter.head": "Submitter", "search.filters.filter.submitter.head": "Submetedor", @@ -6201,8 +5758,7 @@ "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.filter.submitter.label": "Procurar submetedor", @@ -6243,8 +5799,7 @@ "search.filters.reset": "Limpar filtros", // "search.filters.search.submit": "Submit", - // TODO New key - Add a translation - "search.filters.search.submit": "Submit", + "search.filters.search.submit": "Submeter", @@ -6272,20 +5827,16 @@ "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.view-result": "Visão", // "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", + "search.results.response.500": "Ocorreu um erro durante a execução da consulta. Tente novamente mais tarde", // "default.search.results.head": "Search Results", - // TODO New key - Add a translation - "default.search.results.head": "Search Results", + "default.search.results.head": "Resultados de Busca", // "default-relationships.search.results.head": "Search Results", - // TODO New key - Add a translation - "default-relationships.search.results.head": "Search Results", + "default-relationships.search.results.head": "Resultados de Busca", // "search.sidebar.close": "Back to results", @@ -6323,12 +5874,10 @@ // "sorting.ASC": "Ascending", - // TODO New key - Add a translation - "sorting.ASC": "Ascending", + "sorting.ASC": "Ascendente", // "sorting.DESC": "Descending", - // TODO New key - Add a translation - "sorting.DESC": "Descending", + "sorting.DESC": "Descendente", // "sorting.dc.title.ASC": "Title Ascending", "sorting.dc.title.ASC": "Título Ascendente", @@ -6337,19 +5886,16 @@ "sorting.dc.title.DESC": "Título Descendente", // "sorting.score.ASC": "Least Relevant", - // TODO New key - Add a translation - "sorting.score.ASC": "Least Relevant", + "sorting.score.ASC": "Menos relevante", // "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.ASC": "Data de emissão ascendente", // "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.issued.DESC": "Data de emissão descendente", // "sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending", // TODO New key - Add a translation @@ -6369,61 +5915,48 @@ // "statistics.title": "Statistics", - // TODO New key - Add a translation - "statistics.title": "Statistics", + "statistics.title": "Estatisticas", // "statistics.header": "Statistics for {{ scope }}", - // TODO New key - Add a translation - "statistics.header": "Statistics for {{ scope }}", + "statistics.header": "Estatisticas para {{ scope }}", // "statistics.breadcrumbs": "Statistics", - // TODO New key - Add a translation - "statistics.breadcrumbs": "Statistics", + "statistics.breadcrumbs": "Estatisticas", // "statistics.page.no-data": "No data available", - // TODO New key - Add a translation - "statistics.page.no-data": "No data available", + "statistics.page.no-data": "Nenhum dado disponível", // "statistics.table.no-data": "No data available", - // TODO New key - Add a translation - "statistics.table.no-data": "No data available", + "statistics.table.no-data": "Nenhum dado disponível", // "statistics.table.title.TotalVisits": "Total visits", - // TODO New key - Add a translation - "statistics.table.title.TotalVisits": "Total visits", + "statistics.table.title.TotalVisits": "Total visitas", // "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.TotalVisitsPerMonth": "Total visitas por mês", // "statistics.table.title.TotalDownloads": "File Visits", - // TODO New key - Add a translation - "statistics.table.title.TotalDownloads": "File Visits", + "statistics.table.title.TotalDownloads": "Visitas Arquivos", // "statistics.table.title.TopCountries": "Top country views", - // TODO New key - Add a translation - "statistics.table.title.TopCountries": "Top country views", + "statistics.table.title.TopCountries": "Maiores visualizações por país", // "statistics.table.title.TopCities": "Top city views", - // TODO New key - Add a translation - "statistics.table.title.TopCities": "Top city views", + "statistics.table.title.TopCities": "Maiores visualizações por cidade", // "statistics.table.header.views": "Views", - // TODO New key - Add a translation - "statistics.table.header.views": "Views", + "statistics.table.header.views": "Visão", // "submission.edit.breadcrumbs": "Edit Submission", - // TODO New key - Add a translation - "submission.edit.breadcrumbs": "Edit Submission", + "submission.edit.breadcrumbs": "Editar Submissão", // "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.cancel": "Cancelar", // "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.", @@ -6447,12 +5980,10 @@ "submission.general.discard.submit": "Descartar", // "submission.general.info.saved": "Saved", - // TODO New key - Add a translation - "submission.general.info.saved": "Saved", + "submission.general.info.saved": "Salvo", // "submission.general.info.pending-changes": "Unsaved changes", - // TODO New key - Add a translation - "submission.general.info.pending-changes": "Unsaved changes", + "submission.general.info.pending-changes": "Modificações não salvas", // "submission.general.save": "Save", "submission.general.save": "Salvar", @@ -6462,12 +5993,10 @@ // "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.page.title": "Importar metadados de fonte externa", // "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": "Importar metadados de fonte externa", // "submission.import-external.title.Journal": "Import a journal from an external source", // TODO New key - Add a translation @@ -6486,175 +6015,137 @@ "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.Person": "Importar uma pessoa de uma fonte externa", // "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.Project": "Importar um projeto de uma fonte externa", // "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.Publication": "Importar uma publicação de uma fonte externa", // "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.title.none": "Importar metadados de uma fonte externa", // "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.back-to-my-dspace": "Voltar para o 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.placeholder": "Procurar de uma fonte externa", // "submission.import-external.search.button": "Search", - // TODO New key - Add a translation - "submission.import-external.search.button": "Search", + "submission.import-external.search.button": "Procurar", // "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.search.source.hint": "Escolha uma fonte externa", // "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.loading": "Carregando ...", // "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": "Item Previsão", // "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.Publication": "Publicação Previsão", // "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.none": "Item Previsão", // "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.OrgUnit": "Unidade Organizacional Previsão", // "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.Person": "Pessoa Previsão", // "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.title.Project": "Projeto Previsão", // "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.button.import": "Iniciar submissão", // "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.title": "Erro submissão", // "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.import-external.preview.error.import.body": "Ocorreu um erro durante o processo de importação da entrada de origem externa.", // "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.added": "Entrada local adicionada com sucesso à seleção", // "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.isAuthorOfPublication": "Importar autor remoto", // "submission.sections.describe.relationship-lookup.external-source.import-button-title.Journal": "Import remote journal", // TODO New key - Add a translation @@ -6669,8 +6160,7 @@ "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.isProjectOfPublication": "Projeto", // "submission.sections.describe.relationship-lookup.external-source.import-button-title.none": "Import remote item", // TODO New key - Add a translation @@ -6717,8 +6207,7 @@ "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.isProjectOfPublication.title": "Projeto", // "submission.sections.describe.relationship-lookup.external-source.import-modal.head.openAIREFunding": "Funding OpenAIRE API", // TODO New key - Add a translation @@ -6745,8 +6234,7 @@ "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.cancel": "Cancelar", // "submission.sections.describe.relationship-lookup.external-source.import-modal.collection": "Select a collection to import new entries to", // TODO New key - Add a translation @@ -6761,8 +6249,7 @@ "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.lcname": "Importando do 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", @@ -6771,20 +6258,16 @@ "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.sherpaPublisher": "Importando do 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.pubmed": "Importando do 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.head.arxiv": "Importando do 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.import": "Importar", // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.title": "Import Remote Journal", // TODO New key - Add a translation @@ -6827,12 +6310,10 @@ "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-all": "Desmarcar todos", // "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.deselect-page": "Desmarcar página", // "submission.sections.describe.relationship-lookup.search-tab.loading": "Loading...", "submission.sections.describe.relationship-lookup.search-tab.loading": "Carregando...", @@ -6842,20 +6323,16 @@ "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": "Ir", // "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.search-form.placeholder": "Procurar...", // "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-all": "Selecionar todos", // "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.search-tab.select-page": "Selecionar página", // "submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items", // TODO New key - Add a translation @@ -6911,27 +6388,21 @@ "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", @@ -6951,8 +6422,7 @@ "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.isProjectOfPublication": "Projetos", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfProject": "Funder of the Project", // TODO New key - Add a translation @@ -6963,12 +6433,10 @@ "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.selection-tab.title.isProjectOfPublication": "Projeto", // "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.isProjectOfPublication": "Projetos", // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfProject": "Funder of the Project", // TODO New key - Add a translation @@ -6978,12 +6446,10 @@ // "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.search-form.placeholder": "Procurar...", // "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.selection-tab.tab-title": "Seleção Atual ({{ count }})", // "submission.sections.describe.relationship-lookup.title.isJournalIssueOfPublication": "Journal Issues", // TODO New key - Add a translation @@ -7004,8 +6470,7 @@ "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.isAuthorOfPublication": "Autores", // "submission.sections.describe.relationship-lookup.title.isFundingAgencyOfPublication": "Funding Agency", // TODO New key - Add a translation @@ -7015,20 +6480,16 @@ "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.Publication": "Publicações", // "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.Person": "Autores", // "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.OrgUnit": "Unidade Organizacional", // "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.DataPackage": "Pacotes de Dados", // "submission.sections.describe.relationship-lookup.title.DataFile": "Data Files", // TODO New key - Add a translation @@ -7051,16 +6512,13 @@ "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.settings": "Configurações", // "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.no-selection": "Sua seleção atual está vazia.", // "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.isAuthorOfPublication": "Autores Selecionados", // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalOfPublication": "Selected Journals", // TODO New key - Add a translation @@ -7070,16 +6528,14 @@ // 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.Project": "Projetos Selecionados", // "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.Person": "Autores Selecionados", // "submission.sections.describe.relationship-lookup.selection-tab.title.OrgUnit": "Selected Organizational Units", // TODO New key - Add a translation @@ -7120,56 +6576,43 @@ "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.sherpaJournal": "Resultados da pesquisa", // "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.sherpaPublisher": "Resultados da pesquisa", // "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.orcid": "Resultados da pesquisa", // "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.orcidv2": "Resultados da pesquisa", // "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.lcname": "Resultados da pesquisa", // "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.pubmed": "Resultados da pesquisa", // "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.arxiv": "Resultados da pesquisa", // "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.crossref": "Resultados da pesquisa", // "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.epo": "Resultados da pesquisa", // "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.scopus": "Resultados da pesquisa", // "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.scielo": "Resultados da pesquisa", // "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.wos": "Resultados da pesquisa", // "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.selection-tab.title": "Resultados da pesquisa", // "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 @@ -7564,11 +7007,9 @@ "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.version": "Versão", // "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", @@ -7576,47 +7017,37 @@ "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.nolocation": "Nenhum", // "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.license": "Licenças", // "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.prerequisites": "Prerequisitos", // "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.location": "Localização", // "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.conditions": "Condições", // "submission.sections.sherpa.publisher.policy.refresh": "Refresh", - // TODO New key - Add a translation - "submission.sections.sherpa.publisher.policy.refresh": "Refresh", + "submission.sections.sherpa.publisher.policy.refresh": "Atualizar", // "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.created": "Data de Criação", // "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.date.modified": "Última modificação", // "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", @@ -7632,7 +7063,6 @@ "submission.submit.title": "Novo envio", - // "submission.workflow.generic.delete": "Delete", "submission.workflow.generic.delete": "Apagar", @@ -7652,7 +7082,6 @@ "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", @@ -7690,7 +7119,6 @@ "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...", @@ -7703,8 +7131,6 @@ // "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", @@ -7719,8 +7145,7 @@ // "submission.workspace.generic.view": "View", - // TODO New key - Add a translation - "submission.workspace.generic.view": "View", + "submission.workspace.generic.view": "Visualizar", // "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", // TODO New key - Add a translation @@ -7728,16 +7153,13 @@ // "thumbnail.default.alt": "Thumbnail Image", - // TODO New key - Add a translation - "thumbnail.default.alt": "Thumbnail Image", + "thumbnail.default.alt": "Imagem de Thumbnail Image", // "thumbnail.default.placeholder": "No Thumbnail Available", - // TODO New key - Add a translation - "thumbnail.default.placeholder": "No Thumbnail Available", + "thumbnail.default.placeholder": "Nenhuma Miniatura disponível", // "thumbnail.project.alt": "Project Logo", - // TODO New key - Add a translation - "thumbnail.project.alt": "Project Logo", + "thumbnail.project.alt": "Logo do Projetpo", // "thumbnail.project.placeholder": "Project Placeholder Image", // TODO New key - Add a translation @@ -7803,8 +7225,7 @@ "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.delete.btn-title": "Apagar", // "uploader.or": ", or ", "uploader.or": ", ou ", @@ -7876,13 +7297,10 @@ "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.cancel": "Cancelar", // "workflow-item.delete.button.confirm": "Delete", - // TODO New key - Add a translation - "workflow-item.delete.button.confirm": "Delete", - + "workflow-item.delete.button.confirm": "Apagar", // "workflow-item.send-back.notification.success.title": "Sent back to submitter", // TODO New key - Add a translation @@ -7909,12 +7327,10 @@ "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.cancel": "Cancelar", // "workflow-item.send-back.button.confirm": "Send back", - // TODO New key - Add a translation - "workflow-item.send-back.button.confirm": "Send back", + "workflow-item.send-back.button.confirm": "Devolver", // "workflow-item.view.breadcrumbs": "Workflow View", // TODO New key - Add a translation @@ -7925,28 +7341,23 @@ "workspace-item.view.breadcrumbs": "Workspace View", // "workspace-item.view.title": "Workspace View", - // TODO New key - Add a translation - "workspace-item.view.title": "Workspace View", + "workspace-item.view.title": "Visão do Workspace", // "idle-modal.header": "Session will expire soon", - // TODO New key - Add a translation - "idle-modal.header": "Session will expire soon", + "idle-modal.header": "Sessão vai exoirar em breve", // "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.log-out": "Deslogar", // "idle-modal.extend-session": "Extend session", - // TODO New key - Add a translation - "idle-modal.extend-session": "Extend session", + "idle-modal.extend-session": "Extender sessão", // "researcher.profile.action.processing" : "Processing...", - // TODO New key - Add a translation - "researcher.profile.action.processing" : "Processing...", + "researcher.profile.action.processing" : "Processando...", // "researcher.profile.associated": "Researcher profile associated", // TODO New key - Add a translation @@ -7957,8 +7368,7 @@ "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.new": "Criar novo", // "researcher.profile.create.success": "Researcher profile created successfully", // TODO New key - Add a translation @@ -7969,35 +7379,28 @@ "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.delete": "Apagar", // "researcher.profile.expose": "Expose", - // TODO New key - Add a translation - "researcher.profile.expose": "Expose", + "researcher.profile.expose": "Expor", // "researcher.profile.hide": "Hide", - // TODO New key - Add a translation - "researcher.profile.hide": "Hide", + "researcher.profile.hide": "Esconder", // "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.view": "Ver", // "researcher.profile.private.visibility" : "PRIVATE", - // TODO New key - Add a translation - "researcher.profile.private.visibility" : "PRIVATE", + "researcher.profile.private.visibility" : "PRIVADO", // "researcher.profile.public.visibility" : "PUBLIC", - // TODO New key - Add a translation - "researcher.profile.public.visibility" : "PUBLIC", + "researcher.profile.public.visibility" : "PUBLICO", // "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).", @@ -8009,24 +7412,20 @@ "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.error.claim.title" : "Erro", // "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", + "researcherprofile.success.claim.title" : "Successo", // "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.create": "Criar um 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.granted-authorizations": "Autorizações concedidas", // "person.page.orcid.grant-authorizations" : "Grant authorizations", // TODO New key - Add a translation @@ -8345,96 +7744,73 @@ "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.disambiguated-organization.value-required": "Os identificadores da organização requerem um valor", // "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.sync-queue.send.validation-error.disambiguation-source.invalid": "A origem de um dos identificadores da organização é inválida. As fontes suportadas são RINGGOLD, GRID, LEI e 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": "Modo de sincronização", // "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.batch": "Lote", // "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.label": "Modo de sincronização", // "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-message": "Selecione como deseja que ocorra a sincronização com ORCID. As opções incluem \"Manual\" (você deve enviar seus dados para o ORCID manualmente), ou \"Lote\" (o sistema enviará seus dados para o ORCID por meio de um script programado).", // "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-funding-message": "Selecione se deseja enviar suas entidades de projeto vinculadas à lista de informações de financiamento do seu registro ORCID.", // "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-publication-message": "Selecione se deseja enviar suas entidades de publicação vinculadas à lista de trabalhos do seu registro ORCID.", // "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-mode-profile-message": "Selecione se deseja enviar seus dados biográficos ou identificadores pessoais para seu registro ORCID.", // "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.success": "As configurações de sincronização foram atualizadas com sucesso", // "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-settings-update.error": "A atualização das configurações de sincronização falhou", // "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.authenticate": "COnseguir seu 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.read-limited": "Leia suas informações com visibilidade definida como Parceiros Confiáveis", // "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.activities-update": "Adicione/atualize suas atividades de pesquisa", // "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.scope.person-update": "Adicionar/atualizar outras informações sobre você", // "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.success": "A desconexão entre o perfil e o registro ORCID foi bem-sucedida", // "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.page.orcid.unlink.error": "Um erro ocorreu enquanto dA desconexão entre o perfil e o registro ORCID. Tente novamente", // "person.orcid.sync.setting": "ORCID Synchronization settings", - // TODO New key - Add a translation - "person.orcid.sync.setting": "ORCID Synchronization settings", + "person.orcid.sync.setting": "ORCID Configuração de sincronização", // "person.orcid.registry.queue": "ORCID Registry Queue", - // TODO New key - Add a translation - "person.orcid.registry.queue": "ORCID Registry Queue", + "person.orcid.registry.queue": "ORCID Registro na Fila", - // "person.orcid.registry.auth": "ORCID Authorizations", - // TODO New key - Add a translation - "person.orcid.registry.auth": "ORCID Authorizations", + // "person.orcid.registry.auth": "ORCID Autorizações", + "person.orcid.registry.auth": "ORCID Autorizações", + // "home.recent-submissions.head": "Recent Submissions", - // TODO New key - Add a translation - "home.recent-submissions.head": "Recent Submissions", - - + "home.recent-submissions.head": "Submissões Recentes", } From c69934aab6519ecda4aa184a03bd7ebce1e5a6b1 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 29 Sep 2022 22:07:15 +0200 Subject: [PATCH 48/78] [CST-6782] Fix --- src/app/core/google-recaptcha/google-recaptcha.service.ts | 2 +- src/app/register-email-form/register-email-form.component.ts | 2 +- src/app/shared/google-recaptcha/google-recaptcha.component.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/core/google-recaptcha/google-recaptcha.service.ts b/src/app/core/google-recaptcha/google-recaptcha.service.ts index c2e3fdc86f..72de1bb26c 100644 --- a/src/app/core/google-recaptcha/google-recaptcha.service.ts +++ b/src/app/core/google-recaptcha/google-recaptcha.service.ts @@ -136,7 +136,7 @@ export class GoogleRecaptchaService { return of(grecaptcha.execute()); } - public getRecaptchaTokenResponse () { + public getRecaptchaTokenResponse() { return grecaptcha.getResponse(); } diff --git a/src/app/register-email-form/register-email-form.component.ts b/src/app/register-email-form/register-email-form.component.ts index 023e2c7d98..ced87b9e75 100644 --- a/src/app/register-email-form/register-email-form.component.ts +++ b/src/app/register-email-form/register-email-form.component.ts @@ -115,7 +115,7 @@ export class RegisterEmailFormComponent implements OnInit { if (captchaVersion === 'v3') { return this.googleRecaptchaService.getRecaptchaToken('register_email'); } else if (captchaVersion === 'v2' && captchaMode === 'checkbox') { - return this.googleRecaptchaService.getRecaptchaTokenResponse(); + return of(this.googleRecaptchaService.getRecaptchaTokenResponse()); } else if (captchaVersion === 'v2' && captchaMode === 'invisible') { return of(tokenV2); } else { diff --git a/src/app/shared/google-recaptcha/google-recaptcha.component.ts b/src/app/shared/google-recaptcha/google-recaptcha.component.ts index 980699046b..16c49ba45b 100644 --- a/src/app/shared/google-recaptcha/google-recaptcha.component.ts +++ b/src/app/shared/google-recaptcha/google-recaptcha.component.ts @@ -20,7 +20,7 @@ export class GoogleRecaptchaComponent implements OnInit { */ @Output() executeRecaptcha: EventEmitter = new EventEmitter(); - @Output() checkboxChecked: EventEmitter = new EventEmitter(); + @Output() checkboxChecked: EventEmitter = new EventEmitter(); @Output() showNotification: EventEmitter = new EventEmitter(); @@ -50,7 +50,7 @@ export class GoogleRecaptchaComponent implements OnInit { this.executeRecaptcha.emit($event); break; case 'checkbox': - this.checkboxChecked.emit(isNotEmpty($event)); // todo fix con boolean + this.checkboxChecked.emit(isNotEmpty($event)); break; default: console.error(`Invalid reCaptcha mode '${this.captchaMode}`); From a384a462eb570c4254c39b8af5161ceba12a58fd Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Sep 2022 12:57:55 +0200 Subject: [PATCH 49/78] [CST-6876] Fix issue with filter values request for which pagination was sent twice --- src/app/core/shared/search/search.service.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 3c91bdff73..9cb62263ea 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -268,15 +268,21 @@ export class SearchService implements OnDestroy { * no valid cached version. Defaults to true * @returns {Observable>>} Emits the given page of facet values */ - getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: SearchOptions, filterQuery?: string, useCachedVersionIfAvailable = true): Observable> { + getFacetValuesFor(filterConfig: SearchFilterConfig, valuePage: number, searchOptions?: PaginatedSearchOptions, filterQuery?: string, useCachedVersionIfAvailable = true): Observable> { let href; - const args: string[] = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`]; + let args: string[] = []; if (hasValue(filterQuery)) { args.push(`prefix=${filterQuery}`); } if (hasValue(searchOptions)) { + searchOptions = Object.assign(new PaginatedSearchOptions({}), searchOptions, { + pagination: Object.assign({}, searchOptions.pagination, { + currentPage: valuePage + }) + }); href = searchOptions.toRestUrl(filterConfig._links.self.href, args); } else { + args = [`page=${valuePage - 1}`, `size=${filterConfig.pageSize}`, ...args]; href = new URLCombiner(filterConfig._links.self.href, `?${args.join('&')}`).toString(); } From c352ac440cf8042eaa80efce8a66c0bba8aa7a3b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Sep 2022 14:35:47 +0200 Subject: [PATCH 50/78] [CST-6876] Fix LGTM alert --- src/app/core/shared/search/search.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index 9cb62263ea..d67fff997d 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -13,7 +13,6 @@ import { GenericConstructor } from '../generic-constructor'; import { HALEndpointService } from '../hal-endpoint.service'; import { URLCombiner } from '../../url-combiner/url-combiner'; import { hasValue, hasValueOperator, isNotEmpty } from '../../../shared/empty.util'; -import { SearchOptions } from '../../../shared/search/models/search-options.model'; import { SearchFilterConfig } from '../../../shared/search/models/search-filter-config.model'; import { SearchResponseParsingService } from '../../data/search-response-parsing.service'; import { SearchObjects } from '../../../shared/search/models/search-objects.model'; From bbdaa253aae2eeeab6bc0957dfb4158d412c4fcb Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 3 Oct 2022 10:48:28 +0200 Subject: [PATCH 51/78] [CST-6876] Fix missing page size --- src/app/core/shared/search/search.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts index d67fff997d..c8ce4b0348 100644 --- a/src/app/core/shared/search/search.service.ts +++ b/src/app/core/shared/search/search.service.ts @@ -276,7 +276,8 @@ export class SearchService implements OnDestroy { if (hasValue(searchOptions)) { searchOptions = Object.assign(new PaginatedSearchOptions({}), searchOptions, { pagination: Object.assign({}, searchOptions.pagination, { - currentPage: valuePage + currentPage: valuePage, + pageSize: filterConfig.pageSize }) }); href = searchOptions.toRestUrl(filterConfig._links.self.href, args); From ac36cc20dcfd194a249e91382e979836a3e12a88 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 3 Oct 2022 10:58:51 +0200 Subject: [PATCH 52/78] [CST-6876] Refactoring in order to remove nested subscriptions --- .../search-facet-filter.component.ts | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index c3c50f2922..32c9ebefa7 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -1,3 +1,7 @@ +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + import { BehaviorSubject, combineLatest as observableCombineLatest, @@ -6,10 +10,8 @@ import { Subject, Subscription } from 'rxjs'; -import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators'; -import { animate, state, style, transition, trigger } from '@angular/animations'; -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; +import { distinctUntilChanged, filter, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; + import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { RemoteData } from '../../../../../core/data/remote-data'; @@ -32,6 +34,7 @@ import { SEARCH_CONFIG_SERVICE } from '../../../../../my-dspace-page/my-dspace-p import { currentPath } from '../../../../utils/route.utils'; import { getFacetValueForType, stripOperatorFromFilterValue } from '../../../search.utils'; import { createPendingRemoteDataObject } from '../../../../remote-data.utils'; +import { FacetValues } from '../../../models/facet-values.model'; @Component({ selector: 'ds-search-facet-filter', @@ -76,6 +79,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { * Emits the active values for this filter */ selectedValues$: Observable; + protected collapseNextUpdate = true; /** @@ -284,38 +288,43 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return this.searchService.getFacetValuesFor(this.filterConfig, page, options, null, useCachedVersionIfAvailable) .pipe( getFirstSucceededRemoteData(), - map((results) => { - return { - values: observableOf(results), - page: page - }; - } + tap((rd: RemoteData) => { + this.isLastPage$.next(hasNoValue(rd?.payload?.next)); + }), + map((rd: RemoteData) => ({ + values: observableOf(rd), + page: page + }) ) ); }) ); let filterValues = []; - this.subs.push(facetValues$.subscribe((facetOutcome) => { - const newValues$ = facetOutcome.values; + this.subs.push( + facetValues$.pipe( + mergeMap((facetOutcome) => { + const newValues$ = facetOutcome.values; - if (this.collapseNextUpdate) { - this.showFirstPageOnly(); - facetOutcome.page = 1; - this.collapseNextUpdate = false; - } - if (facetOutcome.page === 1) { - filterValues = []; - } + if (this.collapseNextUpdate) { + this.showFirstPageOnly(); + facetOutcome.page = 1; + this.collapseNextUpdate = false; + } + if (facetOutcome.page === 1) { + filterValues = []; + } - filterValues = [...filterValues, newValues$]; + filterValues = [...filterValues, newValues$]; - this.subs.push(this.rdbs.aggregate(filterValues).pipe( + return this.rdbs.aggregate(filterValues); + }), tap((rd: RemoteData[]>) => { this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( map((selectedValues) => { return selectedValues.map((value: string) => { - const fValue = [].concat(...rd.payload.map((page) => page.page)).find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); + const fValue = [].concat(...rd.payload.map((page) => page.page)) + .find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); if (hasValue(fValue)) { return fValue; } @@ -328,12 +337,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { ).subscribe((rd: RemoteData[]>) => { this.animationState = 'ready'; this.filterValues$.next(rd); - - })); - this.subs.push(newValues$.pipe(take(1)).subscribe((rd) => { - this.isLastPage$.next(hasNoValue(rd.payload.next)); - })); - })); + }) + ); } /** From f16dcc794278b6d251451b562befb0a5c7719e28 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 3 Oct 2022 14:07:59 +0200 Subject: [PATCH 53/78] [CST-6876] Refactoring in order to remove nested subscriptions --- .../search-facet-filter.component.ts | 70 +++++++++---------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 32c9ebefa7..6c3142ffd0 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -120,11 +120,10 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.searchOptions$.subscribe(() => this.updateFilterValueList()), this.refreshFilters.asObservable().pipe( filter((toRefresh: boolean) => toRefresh), - ).subscribe(() => { - this.retrieveFilterValues(false); - }) + mergeMap(() => this.retrieveFilterValues(false)) + ).subscribe() ); - this.retrieveFilterValues(); + this.retrieveFilterValues().subscribe(); } /** @@ -279,7 +278,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return getFacetValueForType(facet, this.filterConfig); } - protected retrieveFilterValues(useCachedVersionIfAvailable = true) { + protected retrieveFilterValues(useCachedVersionIfAvailable = true): Observable[]>> { const facetValues$ = observableCombineLatest([this.searchOptions$, this.currentPage]).pipe( map(([options, page]) => { return { options, page }; @@ -301,40 +300,39 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { ); let filterValues = []; - this.subs.push( - facetValues$.pipe( - mergeMap((facetOutcome) => { - const newValues$ = facetOutcome.values; + return facetValues$.pipe( + mergeMap((facetOutcome) => { + const newValues$ = facetOutcome.values; - if (this.collapseNextUpdate) { - this.showFirstPageOnly(); - facetOutcome.page = 1; - this.collapseNextUpdate = false; - } - if (facetOutcome.page === 1) { - filterValues = []; - } + if (this.collapseNextUpdate) { + this.showFirstPageOnly(); + facetOutcome.page = 1; + this.collapseNextUpdate = false; + } + if (facetOutcome.page === 1) { + filterValues = []; + } - filterValues = [...filterValues, newValues$]; + filterValues = [...filterValues, newValues$]; - return this.rdbs.aggregate(filterValues); - }), - tap((rd: RemoteData[]>) => { - this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( - map((selectedValues) => { - return selectedValues.map((value: string) => { - const fValue = [].concat(...rd.payload.map((page) => page.page)) - .find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); - if (hasValue(fValue)) { - return fValue; - } - const filterValue = stripOperatorFromFilterValue(value); - return Object.assign(new FacetValue(), { label: filterValue, value: filterValue }); - }); - }) - ); - }) - ).subscribe((rd: RemoteData[]>) => { + return this.rdbs.aggregate(filterValues); + }), + tap((rd: RemoteData[]>) => { + this.selectedValues$ = this.filterService.getSelectedValuesForFilter(this.filterConfig).pipe( + map((selectedValues) => { + return selectedValues.map((value: string) => { + const fValue = [].concat(...rd.payload.map((page) => page.page)) + .find((facetValue: FacetValue) => this.getFacetValue(facetValue) === value); + if (hasValue(fValue)) { + return fValue; + } + const filterValue = stripOperatorFromFilterValue(value); + return Object.assign(new FacetValue(), { label: filterValue, value: filterValue }); + }); + }) + ); + }), + tap((rd: RemoteData[]>) => { this.animationState = 'ready'; this.filterValues$.next(rd); }) From 89cdad42d2efd016083740851bf8bfbdd04267bf Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 3 Oct 2022 17:34:00 +0200 Subject: [PATCH 54/78] [CST-6782] Hide missing cookie alert when verification is not enabled --- src/app/register-email-form/register-email-form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 323fdb4151..6ae893d81c 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -29,7 +29,7 @@
- +

{{ MESSAGE_PREFIX + '.google-recaptcha.open-cookie-settings' | translate }}

From ec375fb910694210ac3f64331f82c2f02447b32a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 3 Oct 2022 17:37:54 +0200 Subject: [PATCH 55/78] [CST-6876] move delay in the search-facet-filter.component --- .../listable-object-component-loader.component.ts | 4 +--- .../search-facet-filter/search-facet-filter.component.ts | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 1750ce067f..6b75c59181 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -14,7 +14,7 @@ import { } from '@angular/core'; import { Subscription } from 'rxjs'; -import { debounceTime, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; @@ -182,8 +182,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges if ((this.compRef.instance as any).reloadedObject) { (this.compRef.instance as any).reloadedObject.pipe( - // Add delay before emitting event to allow the new object is elaborated on REST side - debounceTime((100)), take(1) ).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 6c3142ffd0..2b2eb9b11a 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -10,7 +10,7 @@ import { Subject, Subscription } from 'rxjs'; -import { distinctUntilChanged, filter, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, filter, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service'; import { PaginatedList } from '../../../../../core/data/paginated-list.model'; @@ -120,6 +120,8 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { this.searchOptions$.subscribe(() => this.updateFilterValueList()), this.refreshFilters.asObservable().pipe( filter((toRefresh: boolean) => toRefresh), + // NOTE This is a workaround, otherwise retrieving filter values returns tha old cached response + debounceTime((100)), mergeMap(() => this.retrieveFilterValues(false)) ).subscribe() ); From 2e4b96b2dd00f8ca43f6b72ac1a6607c03e4b958 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 3 Oct 2022 17:41:38 +0200 Subject: [PATCH 56/78] [CST-6782] Disable registration button if cookies haven't been accepted --- src/app/register-email-form/register-email-form.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/register-email-form/register-email-form.component.html b/src/app/register-email-form/register-email-form.component.html index 6ae893d81c..cc0ce4c782 100644 --- a/src/app/register-email-form/register-email-form.component.html +++ b/src/app/register-email-form/register-email-form.component.html @@ -41,7 +41,7 @@
- From c2376b6fc7a49d310bd85fc0fb78715f7f06ef31 Mon Sep 17 00:00:00 2001 From: lucaszc Date: Mon, 3 Oct 2022 14:58:42 -0300 Subject: [PATCH 57/78] Update pt-BR.json5 Merge changes from #1866 and add more translations to pt-BR --- src/assets/i18n/pt-BR.json5 | 2362 ++++++++++++----------------------- 1 file changed, 794 insertions(+), 1568 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index f14c18a6f4..1cb0c54ac7 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -209,7 +209,7 @@ "admin.registries.bitstream-formats.table.supportLevel.head": "Nível de Suporte", // "admin.registries.bitstream-formats.title": "Bitstream Format Registry", - "admin.registries.bitstream-formats.title": "DSpace Angular :: Registro de Formato de Bitstream", + "admin.registries.bitstream-formats.title": "Registro de Formato de Bitstream", // "admin.registries.metadata.breadcrumbs": "Metadata registry", @@ -330,13 +330,13 @@ "admin.access-control.epeople.actions.delete": "Apagar EPerson", // "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", - "admin.access-control.epeople.actions.impersonate": "Personificar EPerson", + "admin.access-control.epeople.actions.impersonate": "Assumir o papel EPerson", // "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", - "admin.access-control.epeople.actions.stop-impersonating": "Parar de personificar EPerson", + "admin.access-control.epeople.actions.stop-impersonating": "Parar de assumir o papel EPerson", // "admin.access-control.epeople.breadcrumbs": "EPeople", "admin.access-control.epeople.breadcrumbs": "EPeople", @@ -747,11 +747,9 @@ "admin.search.item.edit": "Editar", // "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 discoverable", - // TODO Source message changed - Revise the translation "admin.search.item.make-public": "Tornar Público", // "admin.search.item.move": "Move", @@ -852,8 +850,7 @@ "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 Autorizações..., 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.hint": "O primeiro dia a partir do qual o acesso é permitido. Esta data não pode ser modificada neste formulário. Para definir uma data de embargo para este bitstream, vá para a guia Status do Item, clique em Autorizações..., crie ou edite a política READ do bitstream e defina a Data de Início conforme desejado.", // "bitstream.edit.form.embargo.label": "Embargo until specific date", "bitstream.edit.form.embargo.label": "Embargo até data específica", @@ -1032,8 +1029,7 @@ "pagination.next.button.disabled.tooltip": "Não há mais páginas de resultados", // "browse.startsWith": ", starting with {{ startsWith }}", - // TODO New key - Add a translation - "browse.startsWith": ", starting with {{ startsWith }}", + "browse.startsWith": ", começando com {{ startsWith }}", // "browse.startsWith.choose_start": "(Choose start)", "browse.startsWith.choose_start": "(Escolha o início)", @@ -1090,27 +1086,22 @@ "browse.startsWith.months.september": "Setembro", // "browse.startsWith.submit": "Browse", - // TODO Source message changed - Revise the translation "browse.startsWith.submit": "Ir", // "browse.startsWith.type_date": "Filter results by date", "browse.startsWith.type_date": "Filtrar bresultados pela data", // "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_date.label": "Ou digite na data (year-month) e clique no botão de Ir", // "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.startsWith.type_text": "Filtrar resultados informando as primeiras letras:", // "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 }}", + "browse.title.page": "Navegando {{ collection }} por {{ field }} {{ value }}", // "chips.remove": "Remove chip", @@ -1122,15 +1113,13 @@ "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.notifications.success": "Coleção criada com sucesso", // "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.curate.header": "Curadoria da Coleção: {{collection}}", // "collection.delete.cancel": "Cancel", "collection.delete.cancel": "Cancelar", @@ -1167,12 +1156,10 @@ // "collection.edit.tabs.mapper.head": "Item Mapper", - // TODO New key - Add a translation - "collection.edit.tabs.mapper.head": "Item Mapper", + "collection.edit.tabs.mapper.head": "Mapeamentos", // "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.tabs.item-mapper.title": "Editar Coleção - Mapeamentos", // "collection.edit.item-mapper.cancel": "Cancel", "collection.edit.item-mapper.cancel": "Cancelar", @@ -1219,9 +1206,8 @@ // "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.search-form.placeholder": "Search Item to Map...", + "collection.edit.item-mapper.search-form.placeholder": "Pesquisar Item a Mapear...", // "collection.edit.item-mapper.tabs.browse": "Browse mapped items", "collection.edit.item-mapper.tabs.browse": "Navegar por itens mapeados", @@ -1231,122 +1217,95 @@ // "collection.edit.logo.delete.title": "Delete logo", - // TODO New key - Add a translation - "collection.edit.logo.delete.title": "Delete logo", + "collection.edit.logo.delete.title": "Apagar 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.delete-undo.title": "Desfazer apagar", // "collection.edit.logo.label": "Collection logo", - // TODO New key - Add a translation - "collection.edit.logo.label": "Collection logo", + "collection.edit.logo.label": "Logotipo de Coleção", // "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.error": "Falha ao carregar o logotipo da coleção. Verifique o ficheiro antes de tentar de novo.", // "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.add.success": "O logotipo da coleção foi carregado com sucesso.", // "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.success.content": "O logotipo da coleção foi apagado com sucesso", // "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.notifications.delete.error.title": "Erro ao apagar logotipo", // "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.logo.upload": "Arraste um logotipo da coleção para 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.notifications.success": "Coleção editada com sucesso", - // "collection.edit.return": "Back", - // TODO New key - Add a translation - "collection.edit.return": "Back", + // "collection.edit.return": "Return", + "collection.edit.return": "Voltar", // "collection.edit.tabs.curate.head": "Curate", - // TODO New key - Add a translation - "collection.edit.tabs.curate.head": "Curate", + "collection.edit.tabs.curate.head": "Curadoria", // "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.curate.title": "Editar Coleção - Curadoria", // "collection.edit.tabs.authorizations.head": "Authorizations", - // TODO New key - Add a translation - "collection.edit.tabs.authorizations.head": "Authorizations", + "collection.edit.tabs.authorizations.head": "Autorizações", // "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", - // TODO New key - Add a translation - "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", + "collection.edit.tabs.authorizations.title": "Editar Coleção - Autorizações", // "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-bundle-button": "Carregar mais pacotes", // "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.load-more-button": "Carregar mais", // "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.item.authorizations.show-bitstreams-button": "Mostrar políticas de bitstream policies para pacotes", // "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.metadata.title": "Coleção Editar - Metadados", // "collection.edit.tabs.roles.head": "Assign Roles", - // TODO New key - Add a translation - "collection.edit.tabs.roles.head": "Assign Roles", + "collection.edit.tabs.roles.head": "Atribuir Papéis", // "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.roles.title": "Editar Coleção - Papéis", // "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.external": "Esta coleção agrega conteúdo de uma fonte externa", // "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.errors.oaiSource.required": "Você deve indicar o id da coleção a ser agregada", // "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.harvestType": "Conteúdo a ser agregado", // "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.head": "Configurar uma fonte externa", // "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.metadataConfigId": "Formato de Metadados", // "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.oaiSetId": "ID específico do OAI", // "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.oaiSource": "Provedor OAI", // "collection.edit.tabs.source.form.options.harvestType.METADATA_AND_BITSTREAMS": "Harvest metadata and bitstreams (requires ORE support)", // TODO New key - Add a translation @@ -1361,36 +1320,28 @@ "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.head": "Conteúdo Fonte", // "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.content": "As alterações foram ignoradas. Para regravar as alterações clique no botão ´Voltar", // "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.content": "As suas alterações não foram guardadas. Confirme se todos os campos estão válidos antes de salvar.", // "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.invalid.title": "Metadados inválidos", // "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.content": "As alterações neta coleção foram salvas.", // "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.notifications.saved.title": "Conteúdo Fonte salvo", // "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.tabs.source.title": "Editar Coleção - Conteúdo fonte", @@ -1398,8 +1349,7 @@ "collection.edit.template.add-button": "Adicionar", // "collection.edit.template.breadcrumbs": "Item template", - // TODO New key - Add a translation - "collection.edit.template.breadcrumbs": "Item template", + "collection.edit.template.breadcrumbs": "Template do Item", // "collection.edit.template.cancel": "Cancel", "collection.edit.template.cancel": "Cancelar", @@ -1411,32 +1361,25 @@ "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.error": "An error occurred retrieving the template item", + "collection.edit.template.error": "Um erro ocorreu retornando o Template do 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.head": "Editar o Template de Item para a Coleção \"{{ collection }}\"", // "collection.edit.template.label": "Template item", - // TODO New key - Add a translation - "collection.edit.template.label": "Template item", + "collection.edit.template.label": "Template de item", // "collection.edit.template.loading": "Loading template item...", - // TODO New key - Add a translation - "collection.edit.template.loading": "Loading template item...", + "collection.edit.template.loading": "Carregando o template de 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.error": "Falha ao deletar o item do 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.notifications.delete.success": "Item do template apagado com sucesso", // "collection.edit.template.title": "Edit Template Item", - // TODO New key - Add a translation - "collection.edit.template.title": "Edit Template Item", + "collection.edit.template.title": "Editar Template de Item", @@ -1465,8 +1408,7 @@ "collection.form.title": "Nome", // "collection.form.entityType": "Entity Type", - // TODO New key - Add a translation - "collection.form.entityType": "Entity Type", + "collection.form.entityType": "Tipo de Entidade", @@ -1505,7 +1447,7 @@ "collection.select.table.title": "Título", // "collection.source.controls.head": "Harvest Controls", - "collection.source.controls.head": "Controles de Harvest", + "collection.source.controls.head": "Controles de Colheita(Harvest)", // "collection.source.controls.test.submit.error": "Something went wrong with initiating the testing of the settings", "collection.source.controls.test.submit.error": "Alguma coisa errada durante a inicialização e teste das configurações", // "collection.source.controls.test.failed": "The script to test the settings has failed", @@ -1527,15 +1469,13 @@ // "collection.source.controls.import.failed": "An error occurred during the import", "collection.source.controls.import.failed": "Um erro ocorreu durante a importação", // "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.import.completed": "A importação concluiu", // "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.success": "A redefinição e reimportação foram iniciadas com sucesso", // "collection.source.controls.reset.submit.error": "Something went wrong with initiating the reset and reimport", "collection.source.controls.reset.submit.error": "Alguma coisa errada durante a inicialização da redefinição e reimportação", // "collection.source.controls.reset.failed": "An error occurred during the reset and reimport", - "collection.source.controls.reset.failed": "Um erro ocorreru durante o reset e reimportação", + "collection.source.controls.reset.failed": "Um erro ocorreru durante a redifinição e reimportação", // "collection.source.controls.reset.completed": "The reset and reimport completed", "collection.source.controls.reset.completed": "Completou o reset e a reimportação", // "collection.source.controls.reset.submit": "Reset and reimport", @@ -1543,19 +1483,18 @@ // "collection.source.controls.reset.running": "Resetting and reimporting...", "collection.source.controls.reset.running": "Resetando e importando...", // "collection.source.controls.harvest.status": "Harvest status:", - "collection.source.controls.harvest.status": "Status do Harvest:", + "collection.source.controls.harvest.status": "Status da Colheita (Harvest):", // "collection.source.controls.harvest.start": "Harvest start time:", - "collection.source.controls.harvest.start": "Hora de ínicio Harvest:", + "collection.source.controls.harvest.start": "Hora de ínicio da Colheita(Harvest):", // "collection.source.controls.harvest.last": "Last time harvested:", - "collection.source.controls.harvest.last": "Última hora de harvested:", + "collection.source.controls.harvest.last": "Última hora de colhida:", // "collection.source.controls.harvest.message": "Harvest info:", - "collection.source.controls.harvest.message": "Informação Harvest:", + "collection.source.controls.harvest.message": "Informação sobre Colheita (Harvest):", // "collection.source.controls.harvest.no-information": "N/A", "collection.source.controls.harvest.no-information": "N/D", // "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.content": "As configurações fornecidas foram testadas e não funcionaram.", // "collection.source.update.notifications.error.title": "Server Error", "collection.source.update.notifications.error.title": "Erro no Servidor", @@ -1719,32 +1658,27 @@ "comcol-role.edit.submitters.name": "Remetentes", // "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.submitters.description": "As E-People e Grupos que têm permissão para enviar novos itens para esta coleção.", // "comcol-role.edit.item_read.name": "Default item read access", - "comcol-role.edit.item_read.name": "Default item acesso leitura", + "comcol-role.edit.item_read.name": "Acesso de leitura de item padrão", // "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.description": "E-People e Grupos que podem ler novos itens enviados para esta coleção. As alterações nesta função não são retroativas. Os itens existentes no sistema ainda poderão ser visualizados por aqueles que tinham acesso de leitura no momento de sua adição.", // "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.item_read.anonymous-group": "A leitura padrão para itens de entrada está atualmente definida como Anônima.", // "comcol-role.edit.bitstream_read.name": "Default bitstream read access", - "comcol-role.edit.bitstream_read.name": "Default bitstream acesso leitura", + "comcol-role.edit.bitstream_read.name": "Acesso de leitura de bitstream padrão", // "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.description": "Os administradores da comunidade podem criar subcomunidades ou coleções e gerenciar ou atribuir gerenciamento para essas subcomunidades ou coleções. Além disso, eles decidem quem pode enviar itens para quaisquer subcoleções, editar metadados de itens (após o envio) e adicionar (mapear) itens existentes de outras coleções (sujeito a autorização).", // "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.bitstream_read.anonymous-group": "A leitura padrão para bitstreams de entrada está atualmente definida como Anônima.", // "comcol-role.edit.editor.name": "Editors", @@ -1840,7 +1774,7 @@ "cookies.consent.decline": "Recusar", // "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": "Coletamos e processamos suas informações pessoais para os seguintes propósitos: Autenticação, Preferências, Reconhecimento e Estatísticas.
To learn more, please read our {privacyPolicy}.", + "cookies.consent.content-notice.description": "Coletamos e processamos suas informações pessoais para os seguintes propósitos: Autenticação, Preferências, Reconhecimento e Estatísticas.
Para aprender mais, por favor leia nossa {privacyPolicy}.", // "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.description.no-privacy": "Coletamos e processamos suas informações pessoais para os seguintes propósitos: Autenticação, Preferências, Reconhecimento e Estatísticas.", @@ -1889,8 +1823,7 @@ "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.app.description.google-analytics": "Nos permite rastrear dados estatísticos", @@ -1901,24 +1834,19 @@ "cookies.consent.purpose.statistical": "Estatística", // "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.citationpage.label": "Gerar página de citação", // "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.checklinks.label": "Verificar links em metadados", // "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.profileformats.label": "Perfil dos formatos de Bitstream", // "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.requiredmetadata.label": "Checar por Metadados Requeridos", // "curation-task.task.translate.label": "Microsoft Translator", "curation-task.task.translate.label": "Microsoft Translator", @@ -1929,40 +1857,31 @@ // "curation.form.task-select.label": "Task:", - // TODO New key - Add a translation - "curation.form.task-select.label": "Task:", + "curation.form.task-select.label": "Tarefa:", // "curation.form.submit": "Start", - // TODO New key - Add a translation - "curation.form.submit": "Start", + "curation.form.submit": "Iniciar", // "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.head": "A tarefa de curadoria iniciou com sucesso", // "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.success.content": "Será redirecionado para a página correspondente ao processo.", // "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.head": "Falha ao executar a tarefa de curadoria", // "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.content": "Ocorreu um erro ao tentar iniciar a tarefa de curadoria.", // "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.submit.error.invalid-handle": "Não posso determinar o handle para este objeto", // "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)", + "curation.form.handle.hint": "Dica: Introduza [o-seu-prefixo-handle]/0 para executar a tarefa em todo o repositório (nem todas as tarefas são compatíveis com esta opção)", @@ -1971,30 +1890,24 @@ "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.email.subject": "Solicitar cópia do documento", // "deny-request-copy.error": "An error occurred", - // TODO New key - Add a translation - "deny-request-copy.error": "An error occurred", + "deny-request-copy.error": "Um erro ocorreu", // "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.header": "Negar solicitação de cópia de documento", // "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.intro": "Esta mensagem será enviada ao requerente do pedido", // "deny-request-copy.success": "Successfully denied item request", - // TODO New key - Add a translation - "deny-request-copy.success": "Successfully denied item request", + "deny-request-copy.success": "Pedido de item negado com sucesso", // "dso.name.untitled": "Untitled", - // TODO New key - Add a translation - "dso.name.untitled": "Untitled", + "dso.name.untitled": "Sem título", @@ -2002,8 +1915,7 @@ "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.collection.sub-level": "Crie uma nova coleção em", // "dso-selector.create.community.head": "New community", "dso-selector.create.community.head": "Nova comunidade", @@ -2021,8 +1933,7 @@ "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.create.submission.head": "Nova submissão", // "dso-selector.edit.collection.head": "Edit collection", "dso-selector.edit.collection.head": "Editar coleção", @@ -2034,12 +1945,10 @@ "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.error.title": "Um erro ocorreu procurando por um {{ 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.export-metadata.dspaceobject.head": "Exportar metadados de", // "dso-selector.no-results": "No {{ type }} found", "dso-selector.no-results": "Nenhum(a) {{ type }} encontrado(a)", @@ -2063,69 +1972,53 @@ "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.body": "Esses são perfis existentes que podem estar relacionados a você. Se você se reconhece em um desses perfis, selecione-o e na página de detalhes, entre as opções, escolha reivindicá-lo. Por outro lado ", // "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.not-mine-label": "Nenhum desses é meu", // "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", + "dso-selector.claim.item.create-from-scratch": "Criar um novo", // "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.header": "Exportar metadados para {{ 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.info": "Você tem certeza que quer exportar os metadados para {{ dsoName }}", // "confirmation-modal.export-metadata.cancel": "Cancel", - // TODO New key - Add a translation - "confirmation-modal.export-metadata.cancel": "Cancel", + "confirmation-modal.export-metadata.cancel": "Cancelar", // "confirmation-modal.export-metadata.confirm": "Export", - // TODO New key - Add a translation - "confirmation-modal.export-metadata.confirm": "Export", + "confirmation-modal.export-metadata.confirm": "Exportar", // "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.header": "Apagar 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.info": "Você tem certeza que quer apagar EPerson \"{{ dsoName }}\"", // "confirmation-modal.delete-eperson.cancel": "Cancel", - // TODO New key - Add a translation - "confirmation-modal.delete-eperson.cancel": "Cancel", + "confirmation-modal.delete-eperson.cancel": "Cancelar", // "confirmation-modal.delete-eperson.confirm": "Delete", - // TODO New key - Add a translation - "confirmation-modal.delete-eperson.confirm": "Delete", + "confirmation-modal.delete-eperson.confirm": "Apagar", // "confirmation-modal.delete-profile.header": "Delete Profile", - // TODO New key - Add a translation - "confirmation-modal.delete-profile.header": "Delete Profile", + "confirmation-modal.delete-profile.header": "Apagar Perfil", // "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.info": "Você tem certeza que quer apagar o seu perfil", // "confirmation-modal.delete-profile.cancel": "Cancel", - // TODO New key - Add a translation - "confirmation-modal.delete-profile.cancel": "Cancel", + "confirmation-modal.delete-profile.cancel": "Cancelar", // "confirmation-modal.delete-profile.confirm": "Delete", - // TODO New key - Add a translation - "confirmation-modal.delete-profile.confirm": "Delete", + "confirmation-modal.delete-profile.confirm": "Apagar", // "error.bitstream": "Error fetching bitstream", - // TODO New key - Add a translation - "error.bitstream": "Error fetching bitstream", + "error.bitstream": "Erro ao carregar bitstream", // "error.browse-by": "Error fetching items", "error.browse-by": "Erro ao carregar itens", @@ -2161,8 +2054,7 @@ "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.invalid-search-query": "A consulta de pesquisa não é válida. Por favor verifique Solr query syntax para melhores práticas e mais informações sobre este erro.", // "error.sub-collections": "Error fetching sub-collections", "error.sub-collections": "Erro ao carregar sub-coleções", @@ -2183,34 +2075,27 @@ "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.filerequired": "O arquivo para upload é obrigatório", // "error.validation.required": "This field is required", - // TODO New key - Add a translation - "error.validation.required": "This field is required", + "error.validation.required": "Este campo é requerido", // "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.NotValidEmail": "Este E-mail não é válido", // "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.emailTaken": "Este e-mail já está registrado", // "error.validation.groupExists": "This group already exists", - // TODO New key - Add a translation - "error.validation.groupExists": "This group already exists", + "error.validation.groupExists": "Este Grupo já existe", // "feed.description": "Syndication feed", - // TODO New key - Add a translation - "feed.description": "Syndication feed", + "feed.description": "Feed de distribuição", // "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", + "file-section.error.header": "Erro obtendo arquivos para este item", @@ -2238,115 +2123,88 @@ // "forgot-email.form.header": "Forgot Password", - // TODO New key - Add a translation - "forgot-email.form.header": "Forgot Password", + "forgot-email.form.header": "Esqueci a Senha", // "forgot-email.form.info": "Enter the email address associated with the account.", - // TODO New key - Add a translation - "forgot-email.form.info": "Enter the email address associated with the account.", + "forgot-email.form.info": "Entre o email associado com esta conta.", // "forgot-email.form.email": "Email Address *", - // TODO New key - Add a translation - "forgot-email.form.email": "Email Address *", + "forgot-email.form.email": "Endereço de 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.required": "Por favor preencha o endereço de email", // "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.error.pattern": "Por favor preencha com um e-mail válido", // "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": "An email will be sent to this address with a further instructions.", + "forgot-email.form.email.hint": "Um e-mail será enviado para este endereço com mais instruções.", // "forgot-email.form.submit": "Reset password", - // TODO New key - Add a translation - "forgot-email.form.submit": "Reset password", + "forgot-email.form.submit": "Redefinir senha", // "forgot-email.form.success.head": "Password reset email sent", - // TODO New key - Add a translation - "forgot-email.form.success.head": "Password reset email sent", + "forgot-email.form.success.head": "Senha redefinida email enviado", // "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.success.content": "Um email foi enviado para {{ email }} contendo uma URL especial e mais instruções.", // "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 reset password", + "forgot-email.form.error.head": "Erro tentando redefinir a senha", // "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 attempting to reset the password for the account associated with the following email address: {{ email }}", + "forgot-email.form.error.content": "Um erro ocorreu tentando redefinir a senha associada com esta conta para o seguinte endereço de email: {{ email }}", // "forgot-password.title": "Forgot Password", - // TODO New key - Add a translation - "forgot-password.title": "Forgot Password", + "forgot-password.title": "Esqueceu a Senha", // "forgot-password.form.head": "Forgot Password", - // TODO New key - Add a translation - "forgot-password.form.head": "Forgot Password", + "forgot-password.form.head": "Esqueceu a Senha", // "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.", + "forgot-password.form.info": "Digite uma nova senha na caixa abaixo e confirme digitando-a novamente na segunda caixa.", // "forgot-password.form.card.security": "Security", - // TODO New key - Add a translation - "forgot-password.form.card.security": "Security", + "forgot-password.form.card.security": "Segurança", // "forgot-password.form.identification.header": "Identify", - // TODO New key - Add a translation - "forgot-password.form.identification.header": "Identify", + "forgot-password.form.identification.header": "Identificar", // "forgot-password.form.identification.email": "Email address: ", - // TODO New key - Add a translation - "forgot-password.form.identification.email": "Email address: ", + "forgot-password.form.identification.email": "Endereço de Email: ", // "forgot-password.form.label.password": "Password", - // TODO New key - Add a translation - "forgot-password.form.label.password": "Password", + "forgot-password.form.label.password": "Senha", // "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.label.passwordrepeat": "Redigite para confirmar", // "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.empty-password": "Por favor digite a senha na caixa abaixo.", // "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.matching-passwords": "As senhas não coincidem.", // "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.error.title": "Erro quando tentou submeter a nova senha", // "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.content": "A redefinição de senha foi bem-sucedida. Você está logado como o usuário criado.", // "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.notification.success.title": "Redefinição de Senha completa", // "forgot-password.form.submit": "Submit password", - // TODO New key - Add a translation - "forgot-password.form.submit": "Submit password", + "forgot-password.form.submit": "Envie a Senha", // "form.add": "Add more", - // TODO New key - Add a translation - "form.add": "Add more", + "form.add": "Adicionar mais", // "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.add-help": "Clique aqui para adicionar a entrada atual e para adicionar outra", // "form.cancel": "Cancel", "form.cancel": "Cancelar", @@ -2358,12 +2216,10 @@ "form.clear-help": "Clique aqui para apagar o valor selecionado", // "form.discard": "Discard", - // TODO New key - Add a translation - "form.discard": "Discard", + "form.discard": "Descartar", // "form.drag": "Drag", - // TODO New key - Add a translation - "form.drag": "Drag", + "form.drag": "Solte", // "form.edit": "Edit", "form.edit": "Editar", @@ -2393,12 +2249,10 @@ "form.loading": "Carregando...", // "form.lookup": "Lookup", - // TODO New key - Add a translation - "form.lookup": "Lookup", + "form.lookup": "Procurar", // "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.lookup-help": "Clique aqui para procurar uma relação existente", // "form.no-results": "No results found", "form.no-results": "Nenhum resultado encontrado", @@ -2422,78 +2276,60 @@ "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": "Save", - // TODO Source message changed - Revise the translation - "form.submit": "Submeter", + "form.submit": "Salvar", // "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", + "form.repeatable.sort.tip": "Solte o item na nova posição", // "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.deny": "Não envie uma cópia", // "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.back": "Voltar", // "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": "Menssagem", // "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.message.empty": "Por favor coloque uma mensagem", // "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.info": "Você pode aproveitar esta ocasião para reconsiderar as restrições de acesso ao documento, para evitar ter que responder a essas solicitações. Se você quiser pedir aos administradores do repositório para remover essas restrições, marque a caixa abaixo.", // "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.permissions.label": "Trocar para acesso aberto", // "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.send": "Enviar", // "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": "Assunto", // "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.email.subject.empty": "Por favor entre um assunto", // "grant-deny-request-copy.grant": "Send copy", - // TODO New key - Add a translation - "grant-deny-request-copy.grant": "Send copy", + "grant-deny-request-copy.grant": "Enviar cópia", // "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.header": "Solicitação de cópia de documento", // "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.home-page": "Leve-me para a página inicial", // "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.intro1": "Se você é um dos autores do documento {{ name }}, em seguida, use uma das opções abaixo para responder à solicitação do usuário.", // "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.intro2": "Depois de escolher uma opção, você receberá uma resposta de e-mail sugerida que você pode editar.", // "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-deny-request-copy.processed": "Esta solicitação já foi processada. Você pode usar o botão abaixo para voltar para a página inicial", @@ -2502,121 +2338,93 @@ "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.email.subject": "Solicitar cópia do documento", // "grant-request-copy.error": "An error occurred", - // TODO New key - Add a translation - "grant-request-copy.error": "An error occurred", + "grant-request-copy.error": "Um erro ocorreu", // "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.header": "Conceder solicitação de cópia do documento", // "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.intro": "Esta mensagem será enviada ao requerente do pedido. O(s) documento(s) solicitado(s) será(ão) anexado(s).", // "grant-request-copy.success": "Successfully granted item request", - // TODO New key - Add a translation - "grant-request-copy.success": "Successfully granted item request", + "grant-request-copy.success": "Solicitação de item concedida com sucesso", // "health.breadcrumbs": "Health", - // TODO New key - Add a translation - "health.breadcrumbs": "Health", + "health.breadcrumbs": "Saúde", // "health-page.heading" : "Health", - // TODO New key - Add a translation - "health-page.heading" : "Health", + "health-page.heading" : "Saúde", // "health-page.info-tab" : "Info", - // TODO New key - Add a translation - "health-page.info-tab" : "Info", + "health-page.info-tab" : "Informação", // "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.error.msg": "O serviço de verificação de integridade está temporariamente indisponível", // "health-page.property.status": "Status code", - // TODO New key - Add a translation - "health-page.property.status": "Status code", + "health-page.property.status": "Código de Status", // "health-page.section.db.title": "Database", - // TODO New key - Add a translation - "health-page.section.db.title": "Database", + "health-page.section.db.title": "Banco de Dados", // "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.solrAuthorityCore.title": "Solr: 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.solrOaiCore.title": "Solr: 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.solrSearchCore.title": "Solr: core pesquisa", // "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.solrStatisticsCore.title": "Soilr: estatisticas 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.app.title": "Aplicação de 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.ok.info": "Operacional", // "health-page.status.error.info": "Problems detected", - // TODO New key - Add a translation - "health-page.status.error.info": "Problems detected", + "health-page.status.error.info": "Problemas detectados", // "health-page.status.warning.info": "Possible issues detected", - // TODO New key - Add a translation - "health-page.status.warning.info": "Possible issues detected", + "health-page.status.warning.info": "Possíveis problemas detectados", // "health-page.title": "Health", - // TODO New key - Add a translation - "health-page.title": "Health", + "health-page.title": "Saúde", // "health-page.section.no-issues": "No issues detected", - // TODO New key - Add a translation - "health-page.section.no-issues": "No issues detected", + "health-page.section.no-issues": "Nennhum problema detectado", // "home.description": "", - // TODO New key - Add a translation "home.description": "", // "home.breadcrumbs": "Home", - // TODO New key - Add a translation - "home.breadcrumbs": "Home", + "home.breadcrumbs": "Início", // "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.title": "Início", // "home.top-level-communities.head": "Communities in DSpace", "home.top-level-communities.head": "Comunidades no DSpace", @@ -2627,296 +2435,226 @@ // "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": "Li e concordo com o Contrato do Usuário Final", // "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.error": "Ocorreu um erro ao aceitar o Contrato de usuário final", // "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.accept.success": "O Contrato do Usuário Final foi atualizado com sucesso", // "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.breadcrumbs": "Contrato de Usuário Final", // "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.cancel": "Cancelar", // "info.end-user-agreement.buttons.save": "Save", - // TODO New key - Add a translation - "info.end-user-agreement.buttons.save": "Save", + "info.end-user-agreement.buttons.save": "Salvar", // "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.head": "Contrato de Usuário Final", // "info.end-user-agreement.title": "End User Agreement", - // TODO New key - Add a translation - "info.end-user-agreement.title": "End User Agreement", + "info.end-user-agreement.title": "Contrato de Usuário Final", // "info.privacy.breadcrumbs": "Privacy Statement", - // TODO New key - Add a translation - "info.privacy.breadcrumbs": "Privacy Statement", + "info.privacy.breadcrumbs": "Política de privacidade", // "info.privacy.head": "Privacy Statement", - // TODO New key - Add a translation - "info.privacy.head": "Privacy Statement", + "info.privacy.head": "Política de privacidade", // "info.privacy.title": "Privacy Statement", - // TODO New key - Add a translation - "info.privacy.title": "Privacy Statement", + "info.privacy.title": "Política de privacidade", // "info.feedback.breadcrumbs": "Feedback", - // TODO New key - Add a translation "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.info": "Obrigado por compartilhar seus comentários sobre o sistema DSpace. Seus comentários são apreciados!", // "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.email_help": "Este endereço será usado para acompanhar seus comentário.", // "info.feedback.send": "Send Feedback", - // TODO New key - Add a translation - "info.feedback.send": "Send Feedback", + "info.feedback.send": "Enviar Feedback", // "info.feedback.comments": "Comments", - // TODO New key - Add a translation - "info.feedback.comments": "Comments", + "info.feedback.comments": "Comentários", // "info.feedback.email-label": "Your Email", - // TODO New key - Add a translation - "info.feedback.email-label": "Your Email", + "info.feedback.email-label": "Seu Email", // "info.feedback.create.success" : "Feedback Sent Successfully!", - // TODO New key - Add a translation - "info.feedback.create.success" : "Feedback Sent Successfully!", + "info.feedback.create.success" : "Feedback Enviado com Sucesso!", // "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.email.required" : "Um endereço de email válido é requerido", // "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.error.message.required" : "Um comentário é requerido", // "info.feedback.page-label" : "Page", - // TODO New key - Add a translation - "info.feedback.page-label" : "Page", + "info.feedback.page-label" : "Página", // "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", + "info.feedback.page_help" : "A página relacionada ao seu 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.private": "Este item é privado", // "item.alerts.withdrawn": "This item has been withdrawn", - // TODO New key - Add a translation - "item.alerts.withdrawn": "This item has been withdrawn", + "item.alerts.withdrawn": "Este item foi recebido", // "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.heading": "Com este editor pode ver e alterar as políticas de um item, assim como alterar políticas de componentes individuais do item: pacotes e ficheiros. Em resumo, um item contém pacotes, e os pacotes contêm de ficheiros. Os pacotes possuem usualmente políticas de ADICIONAR/REMOVER/LER/ESCREVER enquanto que os ficheiros apenas políticas de LER/ESCREVER.", // "item.edit.authorizations.title": "Edit item's Policies", - // TODO New key - Add a translation - "item.edit.authorizations.title": "Edit item's Policies", + "item.edit.authorizations.title": "Editar Política de item", - // "item.badge.private": "Non-discoverable", - // TODO New key - Add a translation - "item.badge.private": "Non-discoverable", + // "item.badge.private": "Private", + "item.badge.private": "Privado", // "item.badge.withdrawn": "Withdrawn", - // TODO New key - Add a translation - "item.badge.withdrawn": "Withdrawn", + "item.badge.withdrawn": "Retirado", // "item.bitstreams.upload.bundle": "Bundle", - // TODO New key - Add a translation - "item.bitstreams.upload.bundle": "Bundle", + "item.bitstreams.upload.bundle": "Pacote", // "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 or input new bundle name", + "item.bitstreams.upload.bundle.placeholder": "Selecione um pacote ou insira um novo nome de pacote", // "item.bitstreams.upload.bundle.new": "Create bundle", - // TODO New key - Add a translation - "item.bitstreams.upload.bundle.new": "Create bundle", + "item.bitstreams.upload.bundle.new": "Criar pacote", // "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.bundles.empty": "Este item não possui um pacote para poder receber o bitstream.", // "item.bitstreams.upload.cancel": "Cancel", - // TODO New key - Add a translation - "item.bitstreams.upload.cancel": "Cancel", + "item.bitstreams.upload.cancel": "Cancelar", // "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.drop-message": "Solte um arquivo para 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.content": "Novo pacote criado com sucesso.", // "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.bundle.created.title": "Criar pacote", // "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.notifications.upload.failed": "Falha no Upload. Por favor verifique o conteúdo antes de tentar novamente.", // "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.displaying": "Mostrando atualmente {{ amount }} bitstreams de {{ 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.all": "Carregar tudo ({{ 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.load.more": "Carregar mais", // "item.edit.bitstreams.bundle.name": "BUNDLE: {{ name }}", - // TODO New key - Add a translation - "item.edit.bitstreams.bundle.name": "BUNDLE: {{ name }}", + "item.edit.bitstreams.bundle.name": "PACOTE: {{ name }}", // "item.edit.bitstreams.discard-button": "Discard", - // TODO New key - Add a translation - "item.edit.bitstreams.discard-button": "Discard", + "item.edit.bitstreams.discard-button": "Descartar", // "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.drag": "Arraste", // "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.remove": "Remover", // "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.edit.buttons.undo": "Desfazer mudanças", // "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.empty": "Este item não possui nenhum bitstream. Clique no botão de upload e envie um novo.", // "item.edit.bitstreams.headers.actions": "Actions", - // TODO New key - Add a translation - "item.edit.bitstreams.headers.actions": "Actions", + "item.edit.bitstreams.headers.actions": "Ações", // "item.edit.bitstreams.headers.bundle": "Bundle", - // TODO New key - Add a translation - "item.edit.bitstreams.headers.bundle": "Bundle", + "item.edit.bitstreams.headers.bundle": "Pacote", // "item.edit.bitstreams.headers.description": "Description", - // TODO New key - Add a translation - "item.edit.bitstreams.headers.description": "Description", + "item.edit.bitstreams.headers.description": "Descrição", // "item.edit.bitstreams.headers.format": "Format", - // TODO New key - Add a translation - "item.edit.bitstreams.headers.format": "Format", + "item.edit.bitstreams.headers.format": "Formato", // "item.edit.bitstreams.headers.name": "Name", - // TODO New key - Add a translation - "item.edit.bitstreams.headers.name": "Name", + "item.edit.bitstreams.headers.name": "Nome", // "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.content": "Suas alterações foram descartadas. Para restabelecer suas alterações, clique no botão 'Desfazer'", // "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.discarded.title": "Mudanças descartadas", // "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.failed.title": "Erro movendo 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.content": "Suas alterações de movimentação nos bitstreams e nos pacotes deste item foram salvas.", // "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.move.saved.title": "Mover alterações salvas", // "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.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.bitstreams.notifications.outdated.title": "Changes outdated", - // TODO New key - Add a translation - "item.edit.bitstreams.notifications.outdated.title": "Changes outdated", + "item.edit.bitstreams.notifications.outdated.title": "Alterações desatualizadas", // "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.failed.title": "Erro apagando 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.content": "Suas alterações de remoção nos bitstream deste item foram salvas.", // "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.notifications.remove.saved.title": "Alterações de remoção salvas", // "item.edit.bitstreams.reinstate-button": "Undo", - // TODO New key - Add a translation - "item.edit.bitstreams.reinstate-button": "Undo", + "item.edit.bitstreams.reinstate-button": "Desfazer", // "item.edit.bitstreams.save-button": "Save", - // TODO New key - Add a translation - "item.edit.bitstreams.save-button": "Save", + "item.edit.bitstreams.save-button": "Salvar", // "item.edit.bitstreams.upload-button": "Upload", - // TODO New key - Add a translation "item.edit.bitstreams.upload-button": "Upload", @@ -2943,21 +2681,17 @@ "item.edit.head": "Editar Item", // "item.edit.breadcrumbs": "Edit Item", - // TODO New key - Add a translation - "item.edit.breadcrumbs": "Edit Item", + "item.edit.breadcrumbs": "Editar 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.disabled.tooltip": "Você não está autorizado a acessar esta guia", // "item.edit.tabs.mapper.head": "Collection Mapper", - // TODO New key - Add a translation - "item.edit.tabs.mapper.head": "Collection Mapper", + "item.edit.tabs.mapper.head": "Mapeamento de Coleção", // "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.tabs.item-mapper.title": "Editar Item - Mapeamento de Coleção", // "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)", @@ -2990,7 +2724,7 @@ "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.add.success.head": "Mapeamento completo", // "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.", @@ -2999,14 +2733,13 @@ "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.content": "Mapeamento removido com sucesso do item para {{amount}} coleções.", // "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.search-form.placeholder": "Procurar coleções ...", // "item.edit.item-mapper.tabs.browse": "Browse mapped collections", "item.edit.item-mapper.tabs.browse": "Navegar nas coleções mapeadas", @@ -3035,8 +2768,7 @@ "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.empty": "O item atualmente não contém metadados. Clique em Adicionar para começar a adicionar um valor de metadados.", // "item.edit.metadata.headers.edit": "Edit", "item.edit.metadata.headers.edit": "Editar", @@ -3057,12 +2789,10 @@ "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": "Changes discarded", - // TODO Source message changed - Revise the translation - "item.edit.metadata.notifications.discarded.title": "Mudança descartada", + "item.edit.metadata.notifications.discarded.title": "Mudanças descartadas", // "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.error.title": "Um erro ocorreu", // "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.", @@ -3074,8 +2804,7 @@ "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": "Changes outdated", - // TODO Source message changed - Revise the translation - "item.edit.metadata.notifications.outdated.title": "Alteração desatualizada", + "item.edit.metadata.notifications.outdated.title": "Alterações desatualizadas", // "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.", @@ -3103,16 +2832,13 @@ // "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.save-button": "Salvar", // "item.edit.move.discard-button": "Discard", - // TODO New key - Add a translation - "item.edit.move.discard-button": "Discard", + "item.edit.move.discard-button": "Descartar", // "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.", @@ -3149,24 +2875,19 @@ // "item.edit.private.cancel": "Cancel", "item.edit.private.cancel": "Cancelar", - // "item.edit.private.confirm": "Make it non-discoverable", - // TODO Source message changed - Revise the translation + // "item.edit.private.confirm": "Make it Private", "item.edit.private.confirm": "Tornar Privado", - // "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": "Are you sure this item should be made private in the archive?", "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 non-discoverable", - // TODO Source message changed - Revise the translation + // "item.edit.private.error": "An error occurred while making the item private", "item.edit.private.error": "Ocorreu um erro ao tornar o item privado", - // "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.header": "Make item private: {{ id }}", + "item.edit.private.header": "Tornar o item privado: {{ id }}", - // "item.edit.private.success": "The item is now non-discoverable", - // TODO Source message changed - Revise the translation + // "item.edit.private.success": "The item is now private", "item.edit.private.success": "O item agora é privado", @@ -3174,24 +2895,19 @@ // "item.edit.public.cancel": "Cancel", "item.edit.public.cancel": "Cancelar", - // "item.edit.public.confirm": "Make it discoverable", - // TODO Source message changed - Revise the translation + // "item.edit.public.confirm": "Make it public", "item.edit.public.confirm": "Tornar público", - // "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": "Are you sure this item should be made public in the archive?", "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 discoverable", - // TODO Source message changed - Revise the translation + // "item.edit.public.error": "An error occurred while making the item public", "item.edit.public.error": "Ocorreu um erro ao tornar o item público", - // "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.header": "Make item public: {{ id }}", + "item.edit.public.header": "Tornar o item público: {{ id }}", - // "item.edit.public.success": "The item is now discoverable", - // TODO Source message changed - Revise the translation + // "item.edit.public.success": "The item is now public", "item.edit.public.success": "O item agora é público", @@ -3229,8 +2945,7 @@ "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.no-relationships": "Sem relações", // "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'", @@ -3239,8 +2954,7 @@ "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.failed.title": "Erro ao apagar relação", // "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", @@ -3261,8 +2975,7 @@ "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.relationships.no-entity-type": "Adicione o metadado 'dspace.entity.type' para ativar relações neste item", // "item.edit.return": "Back", @@ -3336,8 +3049,7 @@ "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.unauthorized": "Você não está autorizado a realizar esta ação", // "item.edit.tabs.status.buttons.withdraw.button": "Withdraw...", "item.edit.tabs.status.buttons.withdraw.button": "Retirar...", @@ -3349,7 +3061,7 @@ "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", - "item.edit.tabs.status.head": "Situação", + "item.edit.tabs.status.head": "Status", // "item.edit.tabs.status.labels.handle": "Handle", "item.edit.tabs.status.labels.handle": "Handle", @@ -3364,19 +3076,16 @@ "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.status.title": "Editar Item - Status", // "item.edit.tabs.versionhistory.head": "Version History", - // TODO New key - Add a translation - "item.edit.tabs.versionhistory.head": "Version History", + "item.edit.tabs.versionhistory.head": "Histórico de versões", // "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.title": "Editar Item - Histórico de versões", // "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.versionhistory.under-construction": "Editar ou adicionar novas versões ainda não é possível nesta interface do usuário.", // "item.edit.tabs.view.head": "View Item", "item.edit.tabs.view.head": "Visualizar Item", @@ -3405,17 +3114,14 @@ "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.orcid.return": "Voltar", // "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.description": "Descrição", // "item.page.journal-issn": "Journal ISSN", // TODO New key - Add a translation @@ -3426,32 +3132,25 @@ "item.page.journal-title": "Journal Title", // "item.page.publisher": "Publisher", - // TODO New key - Add a translation - "item.page.publisher": "Publisher", + "item.page.publisher": "Editor", // "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.page.volume-title": "Título de Volume", // "item.search.results.head": "Item Search Results", - // TODO New key - Add a translation - "item.search.results.head": "Item Search Results", + "item.search.results.head": "Resultados da pesquisa de itens", // "item.search.title": "Item Search", - // TODO New key - Add a translation - "item.search.title": "Item Search", + "item.search.title": "Pesquisa de itens", // "item.truncatable-part.show-more": "Show more", - // TODO New key - Add a translation - "item.truncatable-part.show-more": "Show more", + "item.truncatable-part.show-more": "Mostrar mais", // "item.truncatable-part.show-less": "Collapse", - // TODO New key - Add a translation - "item.truncatable-part.show-less": "Collapse", + "item.truncatable-part.show-less": "Fechar", @@ -3468,19 +3167,16 @@ "item.page.collections": "Coleções", // "item.page.collections.loading": "Loading...", - // TODO New key - Add a translation - "item.page.collections.loading": "Loading...", + "item.page.collections.loading": "Carregando...", // "item.page.collections.load-more": "Load more", - // TODO New key - Add a translation - "item.page.collections.load-more": "Load more", + "item.page.collections.load-more": "Carregar mais", // "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.edit": "Editar este item", // "item.page.files": "Files", "item.page.files": "Arquivos", @@ -3489,7 +3185,7 @@ "item.page.filesection.description": "Descrição:", // "item.page.filesection.download": "Download", - "item.page.filesection.download": "Baixar", + "item.page.filesection.download": "Download", // "item.page.filesection.format": "Format:", "item.page.filesection.format": "Formato:", @@ -3510,12 +3206,10 @@ "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.orcid.tooltip": "Abrir página de configuração do ORCID", // "item.page.person.search.title": "Articles by this author", "item.page.person.search.title": "Artigos deste autor", @@ -3527,20 +3221,16 @@ "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.isAuthorOfPublication": "Publicações", // "item.page.relationships.isJournalOfPublication": "Publications", - // TODO New key - Add a translation - "item.page.relationships.isJournalOfPublication": "Publications", + "item.page.relationships.isJournalOfPublication": "Publicações", // "item.page.relationships.isOrgUnitOfPerson": "Authors", - // TODO New key - Add a translation - "item.page.relationships.isOrgUnitOfPerson": "Authors", + "item.page.relationships.isOrgUnitOfPerson": "Authores", // "item.page.relationships.isOrgUnitOfProject": "Research Projects", - // TODO New key - Add a translation - "item.page.relationships.isOrgUnitOfProject": "Research Projects", + "item.page.relationships.isOrgUnitOfProject": "Pesquisar em Projetos", // "item.page.subject": "Keywords", "item.page.subject": "Palavras-chave", @@ -3549,140 +3239,108 @@ "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.view-more": "Mostrar mais", // "item.page.bitstreams.collapse": "Collapse", - // TODO New key - Add a translation - "item.page.bitstreams.collapse": "Collapse", + "item.page.bitstreams.collapse": "Fechar", // "item.page.filesection.original.bundle" : "Original bundle", - // TODO New key - Add a translation - "item.page.filesection.original.bundle" : "Original bundle", + "item.page.filesection.original.bundle" : "Pacote Original", // "item.page.filesection.license.bundle" : "License bundle", - // TODO New key - Add a translation - "item.page.filesection.license.bundle" : "License bundle", + "item.page.filesection.license.bundle" : "Licença do Pacote", // "item.page.return": "Back", - // TODO New key - Add a translation - "item.page.return": "Back", + "item.page.return": "Voltar", // "item.page.version.create": "Create new version", - // TODO New key - Add a translation - "item.page.version.create": "Create new version", + "item.page.version.create": "Criar noca versão", // "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.version.hasDraft": "Não é possível criar uma nova versão porque há uma submissão em andamento no histórico de versões", // "item.page.claim.button": "Claim", - // TODO New key - Add a translation - "item.page.claim.button": "Claim", + "item.page.claim.button": "Reinvidicar", // "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.page.claim.tooltip": "Reivindicar este item como perfil", // "item.preview.dc.identifier.uri": "Identifier:", - // TODO New key - Add a translation - "item.preview.dc.identifier.uri": "Identifier:", + "item.preview.dc.identifier.uri": "Identificador:", // "item.preview.dc.contributor.author": "Authors:", - // TODO New key - Add a translation - "item.preview.dc.contributor.author": "Authors:", + "item.preview.dc.contributor.author": "Autores:", // "item.preview.dc.date.issued": "Published date:", - // TODO New key - Add a translation - "item.preview.dc.date.issued": "Published date:", + "item.preview.dc.date.issued": "Data de publicação:", // "item.preview.dc.description.abstract": "Abstract:", - // TODO New key - Add a translation - "item.preview.dc.description.abstract": "Abstract:", + "item.preview.dc.description.abstract": "Resumo:", // "item.preview.dc.identifier.other": "Other identifier:", - // TODO New key - Add a translation - "item.preview.dc.identifier.other": "Other identifier:", + "item.preview.dc.identifier.other": "Outro identificador:", // "item.preview.dc.language.iso": "Language:", - // TODO New key - Add a translation - "item.preview.dc.language.iso": "Language:", + "item.preview.dc.language.iso": "Linguagem:", // "item.preview.dc.subject": "Subjects:", - // TODO New key - Add a translation - "item.preview.dc.subject": "Subjects:", + "item.preview.dc.subject": "Assuntos:", // "item.preview.dc.title": "Title:", - // TODO New key - Add a translation - "item.preview.dc.title": "Title:", + "item.preview.dc.title": "Titúlo:", // "item.preview.dc.type": "Type:", - // TODO New key - Add a translation - "item.preview.dc.type": "Type:", + "item.preview.dc.type": "Tipo:", // "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.identifier": "Identificador:", // "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.familyName": "Sobrenome:", // "item.preview.person.givenName": "Name:", - // TODO New key - Add a translation - "item.preview.person.givenName": "Name:", + "item.preview.person.givenName": "Nome:", // "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.name": "Financiador:", // "item.preview.project.funder.identifier": "Funder Identifier:", - // TODO New key - Add a translation - "item.preview.project.funder.identifier": "Funder Identifier:", + "item.preview.project.funder.identifier": "Identificador do Financiamento:", // "item.preview.oaire.awardNumber": "Funding ID:", - // TODO New key - Add a translation - "item.preview.oaire.awardNumber": "Funding ID:", + "item.preview.oaire.awardNumber": "ID de Financiamento:", // "item.preview.dc.title.alternative": "Acronym:", - // TODO New key - Add a translation - "item.preview.dc.title.alternative": "Acronym:", + "item.preview.dc.title.alternative": "Acrônimo:", // "item.preview.dc.coverage.spatial": "Jurisdiction:", - // TODO New key - Add a translation - "item.preview.dc.coverage.spatial": "Jurisdiction:", + "item.preview.dc.coverage.spatial": "Jurisdição:", // "item.preview.oaire.fundingStream": "Funding Stream:", - // TODO New key - Add a translation - "item.preview.oaire.fundingStream": "Funding Stream:", + "item.preview.oaire.fundingStream": "Linha de Financiamento:", @@ -3703,44 +3361,34 @@ // "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.empty": "Não existem ainda outras versões deste item.", // "item.version.history.head": "Version History", - // TODO New key - Add a translation - "item.version.history.head": "Version History", + "item.version.history.head": "Histórico de Versões", // "item.version.history.return": "Back", - // TODO New key - Add a translation - "item.version.history.return": "Back", + "item.version.history.return": "Voltar", // "item.version.history.selected": "Selected version", - // TODO New key - Add a translation - "item.version.history.selected": "Selected version", + "item.version.history.selected": "Versão selecionada", // "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.selected.alert": "Você está vendo atualmente a versão {{version}} para o item.", // "item.version.history.table.version": "Version", - // TODO New key - Add a translation - "item.version.history.table.version": "Version", + "item.version.history.table.version": "Versão", // "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.date": "Data", // "item.version.history.table.summary": "Summary", - // TODO New key - Add a translation - "item.version.history.table.summary": "Summary", + "item.version.history.table.summary": "Sumário", // "item.version.history.table.workspaceItem": "Workspace item", // TODO New key - Add a translation @@ -3751,36 +3399,29 @@ "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.actions": "Ação", // "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.editSummary": "Editar o sumário", // "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.saveSummary": "Salvar edições de resumo", // "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.discardSummary": "Descartar edições de resumo", // "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.newVersion": "Criar nova versão a partir desta", // "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.deleteVersion": "Deletar versão", // "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.history.table.action.hasDraft": "Não é possível criar uma nova versão porque há um envio em andamento no histórico de versões", // "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", @@ -3789,102 +3430,78 @@ // "item.version.create.modal.header": "New version", - // TODO New key - Add a translation - "item.version.create.modal.header": "New version", + "item.version.create.modal.header": "Nova versão", // "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": "Criar uma nova versão deste 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.text.startingFrom": "iniciando da versão {{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": "Criar", // "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.confirm.tooltip": "Criar uma nova versão", // "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": "Cancelar", // "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.button.cancel.tooltip": "Não criar uma nova versão", // "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.label": "Resumo", // "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.form.summary.placeholder": "Insira o resumo para a nova versão", // "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.header": "Criando nova versão...", // "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.modal.submitted.text": "A nova versão está sendo criada. Isso pode levar algum tempo se o item tiver muitos relacionamentos.", // "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.success" : "Uma nova versão foi criada com o número de versão {{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.failure" : "A nova versão não foi criada", // "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.create.notification.inProgress" : "Não é possível criar uma nova versão porque há uma submissão em andamento no histórico de versões", // "item.version.delete.modal.header": "Delete version", - // TODO New key - Add a translation - "item.version.delete.modal.header": "Delete version", + "item.version.delete.modal.header": "Apagar versão", // "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.text": "Você quer apagar a versão {{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": "Apagar", // "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.confirm.tooltip": "Apagar esta versão", // "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": "Cancelar", // "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.modal.button.cancel.tooltip": "Não apague esta versão", // "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.success" : "Versão número {{version}} foi apagada", // "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.delete.notification.failure" : "Versão número {{version}} não foi apagada", // "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.success" : "O resumo da versão número {{version}} foi alterado", // "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", + "item.version.edit.notification.failure" : "O resumo da versão número {{version}} não foi alterado", @@ -3929,8 +3546,7 @@ "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.edit": "Editar este item", // "journalissue.page.issuedate": "Issue Date", "journalissue.page.issuedate": "Data de Publicação", @@ -3959,8 +3575,7 @@ "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.edit": "Editar este item", // "journalvolume.page.issuedate": "Issue Date", "journalvolume.page.issuedate": "Data de Publicação", @@ -3973,57 +3588,46 @@ // "iiifsearchable.listelement.badge": "Document Media", - // TODO New key - Add a translation - "iiifsearchable.listelement.badge": "Document Media", + "iiifsearchable.listelement.badge": "Mídia do Documento", // "iiifsearchable.page.titleprefix": "Document: ", - // TODO New key - Add a translation - "iiifsearchable.page.titleprefix": "Document: ", + "iiifsearchable.page.titleprefix": "Documento: ", // "iiifsearchable.page.doi": "Permanent Link: ", - // TODO New key - Add a translation - "iiifsearchable.page.doi": "Permanent Link: ", + "iiifsearchable.page.doi": "Link Permanente: ", // "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: ", + "iiifsearchable.page.description": "Descrição: ", // "iiifviewer.fullscreen.notice": "Use full screen for better viewing.", - // TODO New key - Add a translation - "iiifviewer.fullscreen.notice": "Use full screen for better viewing.", + "iiifviewer.fullscreen.notice": "Use a tela inteira para melhor visualização.", // "iiif.listelement.badge": "Image Media", - // TODO New key - Add a translation - "iiif.listelement.badge": "Image Media", + "iiif.listelement.badge": "Mídia de imagem", // "iiif.page.titleprefix": "Image: ", - // TODO New key - Add a translation - "iiif.page.titleprefix": "Image: ", + "iiif.page.titleprefix": "Imagem: ", // "iiif.page.doi": "Permanent Link: ", - // TODO New key - Add a translation - "iiif.page.doi": "Permanent Link: ", + "iiif.page.doi": "Link Permanente: ", // "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: ", + "iiif.page.description": "Descrição: ", // "loading.bitstream": "Loading bitstream...", - // TODO New key - Add a translation - "loading.bitstream": "Loading bitstream...", + "loading.bitstream": "Carregando bitstream...", // "loading.bitstreams": "Loading bitstreams...", - // TODO New key - Add a translation - "loading.bitstreams": "Loading bitstreams...", + "loading.bitstreams": "Carregando bitstreams...", // "loading.browse-by": "Loading items...", "loading.browse-by": "Carregando itens...", @@ -4038,8 +3642,7 @@ "loading.collections": "Carregando coleções...", // "loading.content-source": "Loading content source...", - // TODO New key - Add a translation - "loading.content-source": "Loading content source...", + "loading.content-source": "Carregando fonte de conteúdo...", // "loading.community": "Loading community...", "loading.community": "Carregando comunidade...", @@ -4089,23 +3692,19 @@ "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.or-divider": "ou", // "login.form.oidc": "Log in with OIDC", - // TODO New key - Add a translation - "login.form.oidc": "Log in with OIDC", + "login.form.oidc": "Entrar com o OIDC", // "login.form.orcid": "Log in with ORCID", - // TODO New key - Add a translation - "login.form.orcid": "Log in with ORCID", + "login.form.orcid": "Entrar com o 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.shibboleth": "Entrar com Shibboleth", // "login.form.submit": "Log in", "login.form.submit": "Entrar", @@ -4114,8 +3713,7 @@ "login.title": "Entrar", // "login.breadcrumbs": "Login", - // TODO New key - Add a translation - "login.breadcrumbs": "Login", + "login.breadcrumbs": "Entrar", @@ -4137,8 +3735,7 @@ "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.header.admin.description": "Menu de gerenciamento", @@ -4157,8 +3754,7 @@ // "menu.section.admin_search": "Admin Search", - // TODO New key - Add a translation - "menu.section.admin_search": "Admin Search", + "menu.section.admin_search": "Pesquisa Administrativa", @@ -4244,8 +3840,7 @@ "menu.section.icon.control_panel": "Seção do menu Painel de Controle", // "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.curation_tasks": "Seção do menu Tarefa de curadoria", // "menu.section.icon.edit": "Edit menu section", "menu.section.icon.edit": "Seção do menu Editar", @@ -4257,8 +3852,7 @@ "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.health": "Seção do menu de verificação de integridade", // "menu.section.icon.import": "Import menu section", "menu.section.icon.import": "Seção do menu Importar", @@ -4270,8 +3864,7 @@ "menu.section.icon.pin": "Fixar barra lateral", // "menu.section.icon.processes": "Processes Health", - // TODO New key - Add a translation - "menu.section.icon.processes": "Processes Health", + "menu.section.icon.processes": "Saúde de Processos", // "menu.section.icon.registries": "Registries menu section", "menu.section.icon.registries": "Seção do menu Registros", @@ -4280,8 +3873,7 @@ "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.workflow": "Administrar workflow menu de seção ", // "menu.section.icon.unpin": "Unpin sidebar", "menu.section.icon.unpin": "Soltar barra lateral", @@ -4328,12 +3920,10 @@ // "menu.section.processes": "Processes", - // TODO New key - Add a translation - "menu.section.processes": "Processes", + "menu.section.processes": "Processos", // "menu.section.health": "Health", - // TODO New key - Add a translation - "menu.section.health": "Health", + "menu.section.health": "Saúde", @@ -4393,19 +3983,15 @@ // "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.tooltip": "Exportar resultados de pesquisa como 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.success": "A exportação foi iniciada com sucesso", // "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", + "metadata-export-search.submit.error": "Falha ao iniciar a exportação", // "mydspace.breadcrumbs": "MyDSpace", - // TODO New key - Add a translation - "mydspace.breadcrumbs": "MyDSpace", + "mydspace.breadcrumbs": "MeuDSpace", // "mydspace.description": "", "mydspace.description": "", @@ -4456,12 +4042,10 @@ "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": "Importar metadados de fonte externa", // "mydspace.new-submission-external-short": "Import metadata", - // TODO New key - Add a translation - "mydspace.new-submission-external-short": "Import metadata", + "mydspace.new-submission-external-short": "Importar metadados", // "mydspace.results.head": "Your submissions", "mydspace.results.head": "Minhas submissões", @@ -4491,8 +4075,7 @@ "mydspace.results.no-uri": "Sem Uri", // "mydspace.search-form.placeholder": "Search in mydspace...", - // TODO New key - Add a translation - "mydspace.search-form.placeholder": "Search in mydspace...", + "mydspace.search-form.placeholder": "Procurar no meudspace...", // "mydspace.show.workflow": "Workflow tasks", // TODO Source message changed - Revise the translation @@ -4523,12 +4106,10 @@ "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-manyentries": "Arquivo não processável. Detectou muitas entradas, mas permitiu apenas uma para o arquivo.", // "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-failed-moreonefile": "Pedido não processado. Apenas um arquivo é permitido.", // "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.", @@ -4560,7 +4141,7 @@ "nav.main.description": "Barra de navegação principal", // "nav.mydspace": "MyDSpace", - "nav.mydspace": "MyDSpace", + "nav.mydspace": "MeuDSpace", // "nav.profile": "Profile", "nav.profile": "Perfil", @@ -4572,19 +4153,15 @@ "nav.statistics.header": "Estatísticas", // "nav.stop-impersonating": "Stop impersonating EPerson", - // TODO New key - Add a translation - "nav.stop-impersonating": "Stop impersonating EPerson", + "nav.stop-impersonating": "Deixar de assumir o papel de EPerson", // "nav.toggle" : "Toggle navigation", - // TODO New key - Add a translation - "nav.toggle" : "Toggle navigation", + "nav.toggle" : "Alternar navegação", // "nav.user.description" : "User profile bar", - // TODO New key - Add a translation - "nav.user.description" : "User profile bar", + "nav.user.description" : "Barra de perfil do usuário", // "none.listelement.badge": "Item", - // TODO New key - Add a translation "none.listelement.badge": "Item", @@ -4604,8 +4181,7 @@ "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.edit": "Editar este item", // "orgunit.page.id": "ID", "orgunit.page.id": "ID", @@ -4616,8 +4192,7 @@ // "pagination.options.description": "Pagination options", - // TODO New key - Add a translation - "pagination.options.description": "Pagination options", + "pagination.options.description": "Opções de Paginação", // "pagination.results-per-page": "Results Per Page", "pagination.results-per-page": "Resultados por página", @@ -4637,15 +4212,13 @@ "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.listelement.no-title": "Nenhum nome encontrado", // "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.edit": "Editar este item", // "person.page.email": "Email Address", "person.page.email": "Endereço de Email", @@ -4660,8 +4233,7 @@ "person.page.lastname": "Último Nome", // "person.page.name": "Name", - // TODO New key - Add a translation - "person.page.name": "Name", + "person.page.name": "Nome", // "person.page.link.full": "Show all metadata", "person.page.link.full": "Mostrar todos os metadados", @@ -4679,8 +4251,7 @@ "person.search.results.head": "Resultado da Busca de Pessoa", // "person-relationships.search.results.head": "Person Search Results", - // TODO New key - Add a translation - "person-relationships.search.results.head": "Person Search Results", + "person-relationships.search.results.head": "Resultados da pesquisa de pessoas", // "person.search.title": "Person Search", "person.search.title": "Buscar Pessoa", @@ -4688,80 +4259,61 @@ // "process.new.select-parameters": "Parameters", - // TODO New key - Add a translation - "process.new.select-parameters": "Parameters", + "process.new.select-parameters": "Parâmetros", // "process.new.cancel": "Cancel", - // TODO New key - Add a translation - "process.new.cancel": "Cancel", + "process.new.cancel": "Cancelar", // "process.new.submit": "Save", - // TODO New key - Add a translation - "process.new.submit": "Save", + "process.new.submit": "Salvar", // "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.placeholder": "Selecione um 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.select-script.required": "Script é requerido", // "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.upload-button": "Selecione um arquivo...", // "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.file.required": "Por favor selecione um arquivo", // "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.string.required": "Valor do parâmetro é obrigatório", // "process.new.parameter.type.value": "value", - // TODO New key - Add a translation - "process.new.parameter.type.value": "value", + "process.new.parameter.type.value": "valor", // "process.new.parameter.type.file": "file", - // TODO New key - Add a translation - "process.new.parameter.type.file": "file", + "process.new.parameter.type.file": "arquivo", // "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.parameter.required.missing": "Os seguintes parâmetros são obrigatórios, mas ainda estão ausentes:", // "process.new.notification.success.title": "Success", - // TODO New key - Add a translation - "process.new.notification.success.title": "Success", + "process.new.notification.success.title": "Successo", // "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.success.content": "O processo foi criado com sucesso", // "process.new.notification.error.title": "Error", - // TODO New key - Add a translation - "process.new.notification.error.title": "Error", + "process.new.notification.error.title": "Erro", // "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.notification.error.content": "Um erro ocorreu enquanto criava o processo", // "process.new.header": "Create a new process", - // TODO New key - Add a translation - "process.new.header": "Create a new process", + "process.new.header": "Criar novo processo", // "process.new.title": "Create a new process", - // TODO New key - Add a translation - "process.new.title": "Create a new process", + "process.new.title": "Criar novo processo", // "process.new.breadcrumbs": "Create a new process", - // TODO New key - Add a translation - "process.new.breadcrumbs": "Create a new process", + "process.new.breadcrumbs": "Criar novo processo", @@ -4769,123 +4321,96 @@ "process.detail.arguments" : "Argumentos", // "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.arguments.empty" : "Este processo não contêm nenhum argumento", // "process.detail.back" : "Back", "process.detail.back" : "Voltar", // "process.detail.output" : "Process Output", - // TODO New key - Add a translation - "process.detail.output" : "Process Output", + "process.detail.output" : "Saída do Processo", // "process.detail.logs.button": "Retrieve process output", - // TODO New key - Add a translation - "process.detail.logs.button": "Retrieve process output", + "process.detail.logs.button": "Recuperar a saída do processo", // "process.detail.logs.loading": "Retrieving", - // TODO New key - Add a translation - "process.detail.logs.loading": "Retrieving", + "process.detail.logs.loading": "Recuperando", // "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.logs.none": "Este processo não tem saída", // "process.detail.output-files" : "Output Files", - // TODO New key - Add a translation - "process.detail.output-files" : "Output Files", + "process.detail.output-files" : "Arquivos de saída", // "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.output-files.empty" : "Este processo não contém nenhum arquivo de saída", // "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.title" : "Processo: {{ id }} - {{ name }}", // "process.detail.start-time" : "Start time", - // TODO New key - Add a translation - "process.detail.start-time" : "Start time", + "process.detail.start-time" : "Hora de Início", // "process.detail.end-time" : "Finish time", - // TODO New key - Add a translation - "process.detail.end-time" : "Finish time", + "process.detail.end-time" : "Hora de Fim", // "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.detail.create" : "Criar processo similar", // "process.detail.actions": "Actions", - // TODO New key - Add a translation - "process.detail.actions": "Actions", + "process.detail.actions": "Ações", // "process.detail.delete.button": "Delete process", - // TODO New key - Add a translation - "process.detail.delete.button": "Delete process", + "process.detail.delete.button": "Apagar processo", // "process.detail.delete.header": "Delete process", - // TODO New key - Add a translation - "process.detail.delete.header": "Delete process", + "process.detail.delete.header": "Apagar processo", // "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.body": "Tem certeza de que deseja excluir o processo atual?", // "process.detail.delete.cancel": "Cancel", "process.detail.delete.cancel": "Cancelar", // "process.detail.delete.confirm": "Delete process", - // TODO New key - Add a translation - "process.detail.delete.confirm": "Delete process", + "process.detail.delete.confirm": "Apagar processo", // "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.success": "O processo foi excluído com sucesso.", // "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.detail.delete.error": "Algo deu errado ao excluir o processo", // "process.overview.table.finish" : "Finish time (UTC)", - // TODO New key - Add a translation - "process.overview.table.finish" : "Finish time (UTC)", + "process.overview.table.finish" : "Hora de Fim (UTC)", // "process.overview.table.id" : "Process ID", - // TODO New key - Add a translation - "process.overview.table.id" : "Process ID", + "process.overview.table.id" : "ID do Processo", // "process.overview.table.name" : "Name", - // TODO New key - Add a translation - "process.overview.table.name" : "Name", + "process.overview.table.name" : "Nome", // "process.overview.table.start" : "Start time (UTC)", - // TODO New key - Add a translation - "process.overview.table.start" : "Start time (UTC)", + "process.overview.table.start" : "Hora de Início (UTC)", // "process.overview.table.status" : "Status", - // TODO New key - Add a translation "process.overview.table.status" : "Status", // "process.overview.table.user" : "User", "process.overview.table.user" : "Usuário", // "process.overview.title": "Processes Overview", - // TODO New key - Add a translation - "process.overview.title": "Processes Overview", + "process.overview.title": "Visão geral dos processos", // "process.overview.breadcrumbs": "Processes Overview", - // TODO New key - Add a translation - "process.overview.breadcrumbs": "Processes Overview", + "process.overview.breadcrumbs": "Visão geral dos processos", // "process.overview.new": "New", "process.overview.new": "Novo", @@ -4894,36 +4419,29 @@ "process.overview.table.actions": "Ações", // "process.overview.delete": "Delete {{count}} processes", - // TODO New key - Add a translation - "process.overview.delete": "Delete {{count}} processes", + "process.overview.delete": "Apagar {{count}} processos", // "process.overview.delete.clear": "Clear delete selection", - // TODO New key - Add a translation - "process.overview.delete.clear": "Clear delete selection", + "process.overview.delete.clear": "Limpar seleção de exclusão", // "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.body": "Tem certeza de que deseja excluir {{count}} processo(s)?", // "process.overview.delete.header": "Delete processes", - // TODO New key - Add a translation - "process.overview.delete.header": "Delete processes", + "process.overview.delete.header": "Apagar processos", // "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.head": "Erro ao excluir processo", // "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.error.body": "O processo com ID {{processId}} não pôde ser excluído. Os demais processos continuarão sendo excluídos. ", // "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", + "process.bulk.delete.success": "{{count}} processo(s) foram excluídos com sucesso", @@ -4931,8 +4449,7 @@ "profile.breadcrumbs": "Atualizar Perfil", // "profile.card.identify": "Identify", - // TODO New key - Add a translation - "profile.card.identify": "Identify", + "profile.card.identify": "Identificação", // "profile.card.security": "Security", "profile.card.security": "Segurança", @@ -4941,12 +4458,10 @@ "profile.form.submit": "Salvar", // "profile.groups.head": "Authorization groups you belong to", - // TODO New key - Add a translation - "profile.groups.head": "Authorization groups you belong to", + "profile.groups.head": "Grupos de autorização aos quais você pertence", // "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.special.groups.head": "Grupos especiais de autorização aos quais você pertence", // "profile.head": "Update Profile", "profile.head": "Atualizar Perfil", @@ -4954,8 +4469,8 @@ // "profile.metadata.form.error.firstname.required": "First Name is required", "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.error.lastname.required": "Last Name is required", + "profile.metadata.form.error.lastname.required": "Sobrenome é obrigatório", // "profile.metadata.form.label.email": "Email Address", "profile.metadata.form.label.email": "Endereço de Email", @@ -4985,45 +4500,37 @@ "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.matching-passwords": "As senhas não coincidem.", // "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.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.info": "Opcionalmente, você pode inserir uma nova senha na caixa abaixo e confirmá-la digitando-a novamente na segunda caixa.", // "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.label.passwordrepeat": "Redigite para confirmar", // "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.content": "Suas alterações na senha foram salvas.", // "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.title": "Erro trocando a senha", // "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.security.form.notifications.error.not-same": "As senhas fornecidas não são as mesmas.", // "profile.security.form.notifications.error.general": "Please fill required fields of security form.", - // TODO New key - Add a translation - "profile.security.form.notifications.error.general": "Please fill required fields of security form.", + "profile.security.form.notifications.error.general": "Por favor, preencha os campos obrigatórios do formulário de segurança.", // "profile.title": "Update Profile", "profile.title": "Atualizar Perfil", // "profile.card.researcher": "Researcher Profile", - // TODO New key - Add a translation - "profile.card.researcher": "Researcher Profile", + "profile.card.researcher": "Perfil do Pesquisador", // "project.listelement.badge": "Research Project", "project.listelement.badge": "Projeto de Pesquisa", @@ -5035,8 +4542,7 @@ "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.edit": "Editar este item", // "project.page.expectedcompletion": "Expected Completion", "project.page.expectedcompletion": "Conclusão esperada", @@ -5057,12 +4563,10 @@ "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.search.results.head": "Resultado da Pesquisa de Projetos", // "project-relationships.search.results.head": "Project Search Results", - // TODO New key - Add a translation - "project-relationships.search.results.head": "Project Search Results", + "project-relationships.search.results.head": "Resultado da Pesquisa de Projetos", @@ -5073,8 +4577,7 @@ "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.edit": "Editar este item", // "publication.page.journal-issn": "Journal ISSN", "publication.page.journal-issn": "ISSN do Periódico", @@ -5095,8 +4598,7 @@ "publication.search.results.head": "Resultados da Busca de Publicação", // "publication-relationships.search.results.head": "Publication Search Results", - // TODO New key - Add a translation - "publication-relationships.search.results.head": "Publication Search Results", + "publication-relationships.search.results.head": "Resultados da pesquisa de publicação", // "publication.search.title": "Publication Search", "publication.search.title": "Buscar Publicação", @@ -5109,8 +4611,7 @@ "media-viewer.previous": "Anterior", // "media-viewer.playlist": "Playlist", - // TODO New key - Add a translation - "media-viewer.playlist": "Playlist", + "media-viewer.playlist": "Lista de reprodução", // "register-email.title": "New user registration", @@ -5174,7 +4675,7 @@ "register-page.create-profile.submit.success.content": "O registro foi realizado com sucesso. Você está logado como o usuário criado", // "register-page.create-profile.submit.success.head": "Registration completed", - "register-page.create-profile.submit.success.head": "Registo completo", + "register-page.create-profile.submit.success.head": "Registro completo", // "register-page.registration.header": "New user registration", @@ -5296,8 +4797,7 @@ "resource-policies.add.for.bitstream": "Adicionar uma nova política para o Bitstream", // "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.bundle": "Adicionar uma nova Política de Pacote", // "resource-policies.add.for.item": "Add a new Item policy", "resource-policies.add.for.item": "Adicionar uma nova política para Item", @@ -5309,75 +4809,61 @@ "resource-policies.add.for.collection": "Adicionar uma nova política para Coleção", // "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.heading": "Criar nova política de recursos para ", // "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.failure.content": "Ocorreu um erro ao criar a política de recursos.", // "resource-policies.create.page.success.content": "Operation successful", "resource-policies.create.page.success.content": "Operação com sucesso", // "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.create.page.title": "Criar nova política de recurso", // "resource-policies.delete.btn": "Delete selected", "resource-policies.delete.btn": "Apagar selecionado", // "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.btn.title": "Apagar políticas de recurso selecionadas", // "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.failure.content": "Ocorreu um erro ao excluir as políticas de recursos selecionadas.", // "resource-policies.delete.success.content": "Operation successful", "resource-policies.delete.success.content": "Operação com sucesso", // "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.heading": "Editar política de recurso ", // "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.failure.content": "Ocorreu um erro ao editar a política de recursos.", // "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.target-failure.content": "Ocorreu um erro ao editar o destino (ePerson ou grupo) da política de recursos.", // "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.other-failure.content": "Ocorreu um erro ao editar a política de recursos. O destino (ePerson ou grupo) foi atualizado com sucesso.", // "resource-policies.edit.page.success.content": "Operation successful", "resource-policies.edit.page.success.content": "Operação com sucesso", // "resource-policies.edit.page.title": "Edit resource policy", - // TODO New key - Add a translation - "resource-policies.edit.page.title": "Edit resource policy", + "resource-policies.edit.page.title": "Editar política de recursos", // "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.label": "Selecione um tipo de ação", // "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.action-type.required": "Você deve selecionar a ação de política de recursos.", // "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.label": "A eperson ou grupo que receberá a permissão", // "resource-policies.form.eperson-group-list.select.btn": "Select", "resource-policies.form.eperson-group-list.select.btn": "Selecione", // "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.eperson": "Procurar por uma ePerson", // "resource-policies.form.eperson-group-list.tab.group": "Search for a group", "resource-policies.form.eperson-group-list.tab.group": "Procurar por um grupo", @@ -5395,25 +4881,22 @@ "resource-policies.form.eperson-group-list.modal.header": "Não posso alterar o tipo", // "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.toGroup": "Não é possível substituir uma ePerson por um grupo.", // "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.text1.toEPerson": "Não é possível substituir um grupo por uma 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.text2": "Exclua a política de recursos atual e crie uma nova com o tipo desejado.", // "resource-policies.form.eperson-group-list.modal.close": "Ok", "resource-policies.form.eperson-group-list.modal.close": "Ok", // "resource-policies.form.date.end.label": "End Date", - "resource-policies.form.date.end.label": "Data Fim", + "resource-policies.form.date.end.label": "Data de Fim", // "resource-policies.form.date.start.label": "Start Date", - "resource-policies.form.date.start.label": "Data Início", + "resource-policies.form.date.start.label": "Data de Início", // "resource-policies.form.description.label": "Description", "resource-policies.form.description.label": "Descrição", @@ -5425,14 +4908,13 @@ "resource-policies.form.policy-type.label": "Selecione o tipo política", // "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.form.policy-type.required": "Você deve selecionar o tipo de política de recurso.", // "resource-policies.table.headers.action": "Action", "resource-policies.table.headers.action": "Ação", // "resource-policies.table.headers.date.end": "End Date", - "resource-policies.table.headers.date.end": "Data Fim", + "resource-policies.table.headers.date.end": "Data de Fim", // "resource-policies.table.headers.date.start": "Start Date", "resource-policies.table.headers.date.start": "Data Início", @@ -5465,8 +4947,7 @@ "resource-policies.table.headers.title.for.bitstream": "Políticas para 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.bundle": "Políticas para Pacote", // "resource-policies.table.headers.title.for.item": "Policies for Item", "resource-policies.table.headers.title.for.item": "Políticas para Item", @@ -5508,8 +4989,7 @@ "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": "Non-discoverable", + "search.filters.applied.f.discoverable": "Não detectável", // "search.filters.applied.f.entityType": "Item Type", "search.filters.applied.f.entityType": "Tipo de Item", @@ -5530,20 +5010,16 @@ "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.jobTitle": "Título do trabalho", // "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.max": "Fim data de nascimento", // "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.birthDate.min": "Início data de nascimento", // "search.filters.applied.f.withdrawn": "Withdrawn", - // TODO New key - Add a translation - "search.filters.applied.f.withdrawn": "Withdrawn", + "search.filters.applied.f.withdrawn": "Retirado", @@ -5554,8 +5030,7 @@ "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.author.label": "Procurar por nome do autor", // "search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Data de nascimento", @@ -5564,12 +5039,10 @@ "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.birthDate.label": "Procurar por data de nascimento", // "search.filters.filter.collapse": "Collapse filter", - // TODO New key - Add a translation - "search.filters.filter.collapse": "Collapse filter", + "search.filters.filter.collapse": "Fechar filtro", // "search.filters.filter.creativeDatePublished.head": "Date Published", "search.filters.filter.creativeDatePublished.head": "Data de publicação", @@ -5578,8 +5051,7 @@ "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.creativeDatePublished.label": "Procurar por data de publicação", // "search.filters.filter.creativeWorkEditor.head": "Editor", "search.filters.filter.creativeWorkEditor.head": "Editor", @@ -5632,13 +5104,11 @@ // "search.filters.filter.dateSubmitted.label": "Search date submitted", "search.filters.filter.dateSubmitted.label": "Procurar data de submissão", - // "search.filters.filter.discoverable.head": "Non-discoverable", - // TODO New key - Add a translation - "search.filters.filter.discoverable.head": "Non-discoverable", + // "search.filters.filter.discoverable.head": "Private", + "search.filters.filter.discoverable.head": "Privado", // "search.filters.filter.withdrawn.head": "Withdrawn", - // TODO New key - Add a translation - "search.filters.filter.withdrawn.head": "Withdrawn", + "search.filters.filter.withdrawn.head": "Retirado", // "search.filters.filter.entityType.head": "Item Type", "search.filters.filter.entityType.head": "Tipo de Item", @@ -5898,30 +5368,26 @@ "sorting.dc.date.issued.DESC": "Data de emissão descendente", // "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.ASC": "Data de Disponibilização Ascendente", // "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", - // TODO New key - Add a translation - "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", + "sorting.dc.date.accessioned.DESC": "Data de Disponibilização Descendente", // "sorting.lastModified.ASC": "Last modified Ascending", - // TODO New key - Add a translation - "sorting.lastModified.ASC": "Last modified Ascending", + "sorting.lastModified.ASC": "Última modificação Ascendente", // "sorting.lastModified.DESC": "Last modified Descending", - // TODO New key - Add a translation - "sorting.lastModified.DESC": "Last modified Descending", + "sorting.lastModified.DESC": "Última modificação Descendente", // "statistics.title": "Statistics", - "statistics.title": "Estatisticas", + "statistics.title": "Estatísticas", // "statistics.header": "Statistics for {{ scope }}", - "statistics.header": "Estatisticas para {{ scope }}", + "statistics.header": "Estatísticas para {{ scope }}", // "statistics.breadcrumbs": "Statistics", - "statistics.breadcrumbs": "Estatisticas", + "statistics.breadcrumbs": "Estatísticas", // "statistics.page.no-data": "No data available", "statistics.page.no-data": "Nenhum dado disponível", @@ -5945,7 +5411,7 @@ "statistics.table.title.TopCities": "Maiores visualizações por cidade", // "statistics.table.header.views": "Views", - "statistics.table.header.views": "Visão", + "statistics.table.header.views": "Visualizações", @@ -5980,10 +5446,10 @@ "submission.general.discard.submit": "Descartar", // "submission.general.info.saved": "Saved", - "submission.general.info.saved": "Salvo", + "submission.general.info.saved": "Armazenado", // "submission.general.info.pending-changes": "Unsaved changes", - "submission.general.info.pending-changes": "Modificações não salvas", + "submission.general.info.pending-changes": "Alterações não armazenadas", // "submission.general.save": "Save", "submission.general.save": "Salvar", @@ -6011,8 +5477,7 @@ "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.OrgUnit": "Importar um editor de uma fonte externa", // "submission.import-external.title.Person": "Import a person from an external source", "submission.import-external.title.Person": "Importar uma pessoa de uma fonte externa", @@ -6027,11 +5492,10 @@ "submission.import-external.title.none": "Importar metadados de uma fonte externa", // "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.page.hint": "Insira uma consulta acima para encontrar itens da web para importar para o DSpace.", // "submission.import-external.back-to-my-dspace": "Back to MyDSpace", - "submission.import-external.back-to-my-dspace": "Voltar para o MyDSpace", + "submission.import-external.back-to-my-dspace": "Voltar para o MeuDSpace", // "submission.import-external.search.placeholder": "Search the external source", "submission.import-external.search.placeholder": "Procurar de uma fonte externa", @@ -6040,8 +5504,7 @@ "submission.import-external.search.button": "Procurar", // "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.button.hint": "Escreva algumas palavras para pesquisar", // "submission.import-external.search.source.hint": "Pick an external source", "submission.import-external.search.source.hint": "Escolha uma fonte externa", @@ -6083,7 +5546,7 @@ "submission.import-external.source.sherpaJournal": "SHERPA Journals", // "submission.import-external.source.sherpaJournalIssn": "SHERPA Journals by ISSN", - "submission.import-external.source.sherpaJournalIssn": "SHERPA Journals by ISSN", + "submission.import-external.source.sherpaJournalIssn": "SHERPA Journals por ISSN", // "submission.import-external.source.sherpaPublisher": "SHERPA Publishers", "submission.import-external.source.sherpaPublisher": "SHERPA Publishers", @@ -6104,39 +5567,38 @@ "submission.import-external.source.lcname": "Library of Congress Names", // "submission.import-external.preview.title": "Item Preview", - "submission.import-external.preview.title": "Item Previsão", + "submission.import-external.preview.title": "Pré-visualização de Item", // "submission.import-external.preview.title.Publication": "Publication Preview", - "submission.import-external.preview.title.Publication": "Publicação Previsão", + "submission.import-external.preview.title.Publication": "Pré-visualização de Publicação", // "submission.import-external.preview.title.none": "Item Preview", - "submission.import-external.preview.title.none": "Item Previsão", + "submission.import-external.preview.title.none": "Pré-visualização de Item", // "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", - "submission.import-external.preview.title.OrgUnit": "Unidade Organizacional Previsão", + "submission.import-external.preview.title.OrgUnit": "Pré-visualização de Unidade Organizacional", // "submission.import-external.preview.title.Person": "Person Preview", - "submission.import-external.preview.title.Person": "Pessoa Previsão", + "submission.import-external.preview.title.Person": "Pré-visualização de Pessoa", // "submission.import-external.preview.title.Project": "Project Preview", - "submission.import-external.preview.title.Project": "Projeto Previsão", + "submission.import-external.preview.title.Project": "Pré-visualização de Projeto", // "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.subtitle": "Os metadados abaixo foram importados de uma fonte externa. Ele será pré-preenchido quando você iniciar a submissão.", // "submission.import-external.preview.button.import": "Start submission", "submission.import-external.preview.button.import": "Iniciar submissão", // "submission.import-external.preview.error.import.title": "Submission error", - "submission.import-external.preview.error.import.title": "Erro submissão", + "submission.import-external.preview.error.import.title": "Erro na submissão", // "submission.import-external.preview.error.import.body": "An error occurs during the external source entry import process.", - "submission.import-external.preview.error.import.body": "Ocorreu um erro durante o processo de importação da entrada de origem externa.", + "submission.import-external.preview.error.import.body": "Ocorreu um erro durante o processo de importação com uma entrada de origem externa.", // "submission.sections.describe.relationship-lookup.close": "Close", "submission.sections.describe.relationship-lookup.close": "Fechar", @@ -6163,48 +5625,37 @@ "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Projeto", // "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.none": "Importar item remoto", // "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.Event": "Importar evento remoto", // "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.Product": "Importar produto remoto", // "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.Equipment": "Importar equipamento remoto", // "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.OrgUnit": "Importar unidade organizacional remota", // "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.Funding": "Importar fundo remoto", // "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.Person": "Importar pessoa remota", // "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.Patent": "Importar patente remota", // "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.Project": "Importar projeto remoto", // "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-button-title.Publication": "Importar publicação remota", // "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.added.new-entity": "Nova Entidade Adicionada!", // "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.title": "Project", "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.title": "Projeto", @@ -6222,31 +5673,25 @@ "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.isAuthorOfPublication.added.new-entity": "Autor externo importado e adicionado com sucesso à seleção", // "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": "Authoridade", // "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.authority.new": "Importar como uma nova entrada de autoridade local", // "submission.sections.describe.relationship-lookup.external-source.import-modal.cancel": "Cancel", "submission.sections.describe.relationship-lookup.external-source.import-modal.cancel": "Cancelar", // "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.collection": "Selecione uma coleção para importar as novas entradas", // "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": "Entidades", // "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.entities.new": "Importar como uma nova entidade local", // "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.lcname": "Importando do LC Name", @@ -6306,8 +5751,7 @@ "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.external-source.import-modal.select": "Selecione uma correspondência local:", // "submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all", "submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Desmarcar todos", @@ -6319,8 +5763,7 @@ "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.placeholder": "Consulta de pesquisa", // "submission.sections.describe.relationship-lookup.search-tab.search": "Go", "submission.sections.describe.relationship-lookup.search-tab.search": "Ir", @@ -6335,39 +5778,31 @@ "submission.sections.describe.relationship-lookup.search-tab.select-page": "Selecionar página", // "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.selected": "Items selecionados {{ size }}", // "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.isAuthorOfPublication": "Autores Locais ({{ 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.Project": "Projetos Locais ({{ 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.Publication": "Publicações Locais ({{ 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.Person": "Autores Locais ({{ 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.OrgUnit": "Unidades Oranizacionais Locais ({{ 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.DataPackage": "Pacotes de Dados Locais ({{ 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.DataFile": "Arquivos de Dado Locais ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Local Journals ({{ count }})", // TODO New key - Add a translation @@ -6406,31 +5841,25 @@ "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.isFundingAgencyOfPublication": "Procurar por agências de financiamento", // "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.isFundingOfPublication": "Procurar por financiamento", // "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.isChildOrgUnitOf": "Procure por Unidade Organizacional", // "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.openAIREFunding": "Financiamento da API OpenAIRE", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.isProjectOfPublication": "Projects", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isProjectOfPublication": "Projetos", // "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.search-tab.tab-title.isFundingAgencyOfProject": "Financiador do Projeto", // "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.openAIREFunding": "Financiamento da API OpenAIRE", // "submission.sections.describe.relationship-lookup.selection-tab.title.isProjectOfPublication": "Project", "submission.sections.describe.relationship-lookup.selection-tab.title.isProjectOfPublication": "Projeto", @@ -6439,8 +5868,7 @@ "submission.sections.describe.relationship-lookup.title.isProjectOfPublication": "Projetos", // "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.title.isFundingAgencyOfProject": "Financiador do Projeto", @@ -6473,11 +5901,9 @@ "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Autores", // "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.isFundingAgencyOfPublication": "Agências de Financiamento", // "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.Project": "Projetos", // "submission.sections.describe.relationship-lookup.title.Publication": "Publications", "submission.sections.describe.relationship-lookup.title.Publication": "Publicações", @@ -6492,24 +5918,19 @@ "submission.sections.describe.relationship-lookup.title.DataPackage": "Pacotes de Dados", // "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.DataFile": "Arquivos de dados", // "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.Funding Agency": "Agência de financiamento", // "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.isFundingOfPublication": "Financiamento", // "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.title.isChildOrgUnitOf": "Unidade organizacional principal", // "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.search-tab.toggle-dropdown": "Alternar menu suspenso", // "submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings", "submission.sections.describe.relationship-lookup.selection-tab.settings": "Configurações", @@ -6531,49 +5952,40 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Projetos Selecionados", // "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.Publication": "Publicações selecionadas", // "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Selected Authors", "submission.sections.describe.relationship-lookup.selection-tab.title.Person": "Autores Selecionados", // "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.OrgUnit": "Unidades Organizacionais Selecionadas", // "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.DataPackage": "Pacotes de dados selecionados", // "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.DataFile": "Pacotes de arquivos de dados", // "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.isJournalIssueOfPublication": "Problema selecionado", // "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.isFundingAgencyOfPublication": "Agência de financiamento selecionada", // "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.isFundingOfPublication": "Financiamento selecionado", // "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.JournalIssue": "Problema selecionado", // "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.isChildOrgUnitOf": "Selecione a Unidade Organizacional", // "submission.sections.describe.relationship-lookup.selection-tab.title.sherpaJournal": "Search Results", "submission.sections.describe.relationship-lookup.selection-tab.title.sherpaJournal": "Resultados da pesquisa", @@ -6615,51 +6027,40 @@ "submission.sections.describe.relationship-lookup.selection-tab.title": "Resultados da pesquisa", // "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.content": "Você gostaria de salvar \"{{ value }}\" como uma variante de nome para esta pessoa para que você e outros possam reutilizá-lo para futuras submissões? Se você não fizer isso, ainda poderá usá-lo para esta submissão.", // "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.confirm": "Salvar uma nova variante de nome", // "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.describe.relationship-lookup.name-variant.notification.decline": "Usar apenas para esta submissão", // "submission.sections.ccLicense.type": "License Type", - // TODO New key - Add a translation - "submission.sections.ccLicense.type": "License Type", + "submission.sections.ccLicense.type": "Tipo de Licença", // "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.select": "Selecione um tipo de licença…", // "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.change": "Troque o tipo de sua licença …", // "submission.sections.ccLicense.none": "No licenses available", - // TODO New key - Add a translation - "submission.sections.ccLicense.none": "No licenses available", + "submission.sections.ccLicense.none": "Nenhuma licença disponível", // "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.option.select": "Selecione uma opção ...", // "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.link": "Você selecionou a seguinte licença:", // "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.ccLicense.confirmation": "Eu concedo a licença acima", // "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.cannot_deposit": "O depósito não pode ser concluído devido a erros no formulário.
Preencha todos os campos obrigatórios para concluir o depósito.", // "submission.sections.general.collection": "Collection", "submission.sections.general.collection": "Coleção", @@ -6703,12 +6104,10 @@ // "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.accessCondition": "Condições de acesso ao Item", // "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.CClicense": "Licença Creative commons", // "submission.sections.submit.progressbar.describe.recycle": "Recycle", "submission.sections.submit.progressbar.describe.recycle": "Reciclar", @@ -6729,68 +6128,53 @@ "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.sherpapolicy": "Políticas Sherpa", // "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.submit.progressbar.sherpaPolicies": "Informações da política de acesso aberto do editor", // "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.sherpa-policy.title-empty": "Nenhuma informação de política do editor disponível. Se o seu trabalho tiver um ISSN associado, insira-o acima para ver as políticas de acesso aberto do editor relacionadas.", // "submission.sections.status.errors.title": "Errors", - // TODO New key - Add a translation - "submission.sections.status.errors.title": "Errors", + "submission.sections.status.errors.title": "Erros", // "submission.sections.status.valid.title": "Valid", - // TODO New key - Add a translation - "submission.sections.status.valid.title": "Valid", + "submission.sections.status.valid.title": "Valido", // "submission.sections.status.warnings.title": "Warnings", - // TODO New key - Add a translation - "submission.sections.status.warnings.title": "Warnings", + "submission.sections.status.warnings.title": "Avisos", // "submission.sections.status.errors.aria": "has errors", - // TODO New key - Add a translation - "submission.sections.status.errors.aria": "has errors", + "submission.sections.status.errors.aria": "possui erros", // "submission.sections.status.valid.aria": "is valid", - // TODO New key - Add a translation - "submission.sections.status.valid.aria": "is valid", + "submission.sections.status.valid.aria": "é válido", // "submission.sections.status.warnings.aria": "has warnings", - // TODO New key - Add a translation - "submission.sections.status.warnings.aria": "has warnings", + "submission.sections.status.warnings.aria": "possui avisos", // "submission.sections.status.info.title": "Additional Information", - // TODO New key - Add a translation - "submission.sections.status.info.title": "Additional Information", + "submission.sections.status.info.title": "Informações Adicionais", // "submission.sections.status.info.aria": "Additional Information", - // TODO New key - Add a translation - "submission.sections.status.info.aria": "Additional Information", + "submission.sections.status.info.aria": "Informações Adicionais", // "submission.sections.toggle.open": "Open section", - // TODO New key - Add a translation - "submission.sections.toggle.open": "Open section", + "submission.sections.toggle.open": "Abrir seção", // "submission.sections.toggle.close": "Close section", - // TODO New key - Add a translation - "submission.sections.toggle.close": "Close section", + "submission.sections.toggle.close": "Fechar seção", // "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.open": "Expandir seção {{sectionHeader}}", // "submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section", - // TODO New key - Add a translation - "submission.sections.toggle.aria.close": "Collapse {{sectionHeader}} section", + "submission.sections.toggle.aria.close": "Fechar seção {{sectionHeader}}", // "submission.sections.upload.delete.confirm.cancel": "Cancel", "submission.sections.upload.delete.confirm.cancel": "Cancelar", @@ -6805,43 +6189,37 @@ "submission.sections.upload.delete.confirm.title": "Remover bitstream", // "submission.sections.upload.delete.submit": "Delete", - "submission.sections.upload.delete.submit": "Remover", + "submission.sections.upload.delete.submit": "Apagar", // "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.edit.title": "Editar 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.access-condition-hint": "Selecione uma condição de acesso para aplicar no bitstream assim que o item for depositado", // "submission.sections.upload.form.date-required": "Date is required.", - "submission.sections.upload.form.date-required": "Data necessária.", + "submission.sections.upload.form.date-required": "Data é obrigatório.", // "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-from": "É obrigatório conceder acesso a partir da data..", // "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.date-required-until": "É obrigatório conceder acesso até a data..", // "submission.sections.upload.form.from-label": "Grant access from", "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-hint": "Selecione a data a partir da qual a condição de acesso relacionada é aplicada", // "submission.sections.upload.form.from-placeholder": "From", "submission.sections.upload.form.from-placeholder": "De", @@ -6856,8 +6234,7 @@ "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-hint": "Selecione até que data a condição de acesso relacionada é aplicada", // "submission.sections.upload.form.until-placeholder": "Until", "submission.sections.upload.form.until-placeholder": "Até", @@ -6869,7 +6246,7 @@ "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.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", @@ -6890,121 +6267,93 @@ "submission.sections.upload.upload-successful": "Enviado com sucesso", // "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-description": "Quando marcado, este item poderá ser descoberto na pesquisa/navegação. Quando desmarcado, o item estará disponível apenas por meio de um link direto e nunca aparecerá na pesquisa/navegação.", // "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-label": "Tipo de condição de acesso", // "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.access-condition-hint": "Selecione uma condição de acesso para aplicar no item após o depósito", // "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": "Data é obrigatório.", // "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-from": "Conceder acesso a partir de uma data é obrigatório.", // "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.date-required-until": "Conceder acesso até uma data é obrigatório.", // "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-label": "Conceder acesso de", // "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-hint": "Selecione a data a partir da qual a condição de acesso relacionada é aplicada", // "submission.sections.accesses.form.from-placeholder": "From", - // TODO New key - Add a translation - "submission.sections.accesses.form.from-placeholder": "From", + "submission.sections.accesses.form.from-placeholder": "De", // "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-label": "Grupo", // "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.group-required": "Grupo é obrigatório.", // "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-label": "Conceder acesso até", // "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-hint": "Selecione a data até a qual a condição de acesso relacionada é aplicada", // "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.accesses.form.until-placeholder": "Até", + + // "submission.sections.license.granted-label": "Confirmo a licença acima", "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.required": "Você deve aceitar a licença", // "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.license.notgranted": "Você deve aceitar a licença", // "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": "Informações de publicação", // "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.title": "Título", // "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.publishers": "Editor", // "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": "Política do Editor", // "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.description": "As informações abaixo foram encontradas via Sherpa Romeo. Com base nas políticas de seu editor, ele fornece conselhos sobre se um embargo pode ser necessário e/ou quais arquivos você tem permissão para enviar. Se você tiver dúvidas, entre em contato com o administrador do site por meio do formulário de comentários no rodapé.", // "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.openaccess": "Os caminhos de Open Access permitidos pela política desta revista estão listados abaixo por versão do artigo. Clique em um caminho para uma visualização mais detalhada", // "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.more.information": "Para mais informações, consulte os seguintes links:", // "submission.sections.sherpa.publisher.policy.version": "Version", "submission.sections.sherpa.publisher.policy.version": "Versão", @@ -7013,8 +6362,7 @@ "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.noembargo": "Sem Embargo", // "submission.sections.sherpa.publisher.policy.nolocation": "None", "submission.sections.sherpa.publisher.policy.nolocation": "Nenhum", @@ -7035,8 +6383,7 @@ "submission.sections.sherpa.publisher.policy.refresh": "Atualizar", // "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": "Registrar informação", // "submission.sections.sherpa.record.information.id": "ID", "submission.sections.sherpa.record.information.id": "ID", @@ -7051,8 +6398,7 @@ "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.sections.sherpa.error.message": "Ocorreu um erro ao recuperar as informações sherpa", @@ -7148,38 +6494,34 @@ "submission.workspace.generic.view": "Visualizar", // "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.", + "submission.workspace.generic.view-help": "Selecione esta opção para visualizar os metadados do item.", // "thumbnail.default.alt": "Thumbnail Image", - "thumbnail.default.alt": "Imagem de Thumbnail Image", + "thumbnail.default.alt": "Imagem de Miniatura", // "thumbnail.default.placeholder": "No Thumbnail Available", "thumbnail.default.placeholder": "Nenhuma Miniatura disponível", // "thumbnail.project.alt": "Project Logo", - "thumbnail.project.alt": "Logo do Projetpo", + "thumbnail.project.alt": "Logo do Projeto", // "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.alt": "Logo UnitOrg", // "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.alt": "Foto do Perfil", // "thumbnail.person.placeholder": "No Profile Picture Available", - // TODO New key - Add a translation - "thumbnail.person.placeholder": "No Profile Picture Available", + "thumbnail.person.placeholder": "Nenhuma Foto de Perfil disponível", @@ -7189,32 +6531,26 @@ // "vocabulary-treeview.header": "Hierarchical tree view", - // TODO New key - Add a translation - "vocabulary-treeview.header": "Hierarchical tree view", + "vocabulary-treeview.header": "Visualização de árvore hierárquica", // "vocabulary-treeview.load-more": "Load more", - // TODO New key - Add a translation - "vocabulary-treeview.load-more": "Load more", + "vocabulary-treeview.load-more": "Carregar mais", // "vocabulary-treeview.search.form.reset": "Reset", - // TODO New key - Add a translation - "vocabulary-treeview.search.form.reset": "Reset", + "vocabulary-treeview.search.form.reset": "Redefinir", // "vocabulary-treeview.search.form.search": "Search", - // TODO New key - Add a translation - "vocabulary-treeview.search.form.search": "Search", + "vocabulary-treeview.search.form.search": "Procurar", // "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.search.no-result": "Não há items para mostrar", // "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", + "vocabulary-treeview.tree.description.srsc": "Categorias de Assuntos de Pesquisa", @@ -7222,7 +6558,7 @@ "uploader.browse": "Navegar", // "uploader.drag-message": "Drag & Drop your files here", - "uploader.drag-message": "Clique e arraste seus arquivos aqui", + "uploader.drag-message": "Arraste e solte seus arquivos aqui", // "uploader.delete.btn-title": "Delete", "uploader.delete.btn-title": "Apagar", @@ -7237,64 +6573,50 @@ "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.info": "Selecione os tipos para os quais você deseja salvar os metadados virtuais como metadados reais", // "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-item.modal-head": "Os metadados virtuais desta relação", // "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", + "virtual-metadata.delete-relationship.modal-head": "Selecione os itens para os quais você deseja salvar os metadados virtuais como metadados reais", // "workspace.search.results.head": "Your submissions", - // TODO New key - Add a translation - "workspace.search.results.head": "Your submissions", + "workspace.search.results.head": "Suas submissões", // "workflowAdmin.search.results.head": "Administer Workflow", - // TODO New key - Add a translation - "workflowAdmin.search.results.head": "Administer Workflow", + "workflowAdmin.search.results.head": "Administrar Workflow", // "workflow.search.results.head": "Workflow tasks", - // TODO New key - Add a translation - "workflow.search.results.head": "Workflow tasks", + "workflow.search.results.head": "Tarefas do Workflow", // "workflow-item.edit.breadcrumbs": "Edit workflowitem", - // TODO New key - Add a translation - "workflow-item.edit.breadcrumbs": "Edit workflowitem", + "workflow-item.edit.breadcrumbs": "Editar item do workflow", // "workflow-item.edit.title": "Edit workflowitem", - // TODO New key - Add a translation - "workflow-item.edit.title": "Edit workflowitem", + "workflow-item.edit.title": "Editar item do workflow", // "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.title": "Apagado", // "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.success.content": "O item do workflow foi apagado com sucesso", // "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.title": "Algo deu errado", // "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.notification.error.content": "O item do workflow não pode ser apagado", // "workflow-item.delete.title": "Delete workflow item", - // TODO New key - Add a translation - "workflow-item.delete.title": "Delete workflow item", + "workflow-item.delete.title": "Apagar item do workflow", // "workflow-item.delete.header": "Delete workflow item", - // TODO New key - Add a translation - "workflow-item.delete.header": "Delete workflow item", + "workflow-item.delete.header": "Apagar item do workflow", // "workflow-item.delete.button.cancel": "Cancel", "workflow-item.delete.button.cancel": "Cancelar", @@ -7303,28 +6625,22 @@ "workflow-item.delete.button.confirm": "Apagar", // "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.title": "Enviado de volta ao remetente", // "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.success.content": "Este item do workflow foi enviado de volta ao remetente", // "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.title": "Algo deu errado", // "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.notification.error.content": "O item do worflow não pôde ser enviado de volta ao envio", // "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.title": "Enviar este item do workflow de volta ao remetente", // "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.header": "Enviar este item do workflow de volta ao remetente", // "workflow-item.send-back.button.cancel": "Cancel", "workflow-item.send-back.button.cancel": "Cancelar", @@ -7333,8 +6649,7 @@ "workflow-item.send-back.button.confirm": "Devolver", // "workflow-item.view.breadcrumbs": "Workflow View", - // TODO New key - Add a translation - "workflow-item.view.breadcrumbs": "Workflow View", + "workflow-item.view.breadcrumbs": "Visualização do Workflow", // "workspace-item.view.breadcrumbs": "Workspace View", // TODO New key - Add a translation @@ -7344,39 +6659,34 @@ "workspace-item.view.title": "Visão do Workspace", // "idle-modal.header": "Session will expire soon", - "idle-modal.header": "Sessão vai exoirar em breve", + "idle-modal.header": "Sessão vai expirar em breve", // "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.info": "Por motivos de segurança, as sessões do usuário expiram após {{ timeToExpire }} minutos de inatividade. Sua sessão expirará em breve. Deseja estendê-lo ou terminar?", // "idle-modal.log-out": "Log out", - "idle-modal.log-out": "Deslogar", + "idle-modal.log-out": "Terminar sessão", // "idle-modal.extend-session": "Extend session", - "idle-modal.extend-session": "Extender sessão", + "idle-modal.extend-session": "Estender sessão", // "researcher.profile.action.processing" : "Processing...", "researcher.profile.action.processing" : "Processando...", // "researcher.profile.associated": "Researcher profile associated", - // TODO New key - Add a translation - "researcher.profile.associated": "Researcher profile associated", + "researcher.profile.associated": "Perfil do pesquisador associado", // "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.change-visibility.fail": "Ocorreu um erro inesperado ao alterar a visibilidade do perfi", // "researcher.profile.create.new": "Create new", "researcher.profile.create.new": "Criar novo", // "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.success": "Perfil do pesquisador criado com sucesso", // "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.create.fail": "Ocorreu um erro durante a criação do perfil do pesquisador", // "researcher.profile.delete": "Delete", "researcher.profile.delete": "Apagar", @@ -7388,8 +6698,7 @@ "researcher.profile.hide": "Esconder", // "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.not.associated": "Perfil do pesquisador ainda não associado", // "researcher.profile.view": "View", "researcher.profile.view": "Ver", @@ -7398,25 +6707,22 @@ "researcher.profile.private.visibility" : "PRIVADO", // "researcher.profile.public.visibility" : "PUBLIC", - "researcher.profile.public.visibility" : "PUBLICO", + "researcher.profile.public.visibility" : "PÚBLICO", // "researcher.profile.status": "Status:", "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.claim.not-authorized": "Você não está autorizado a reivindicar este item. Para mais detalhes, entre em contato com o(s) administrador(es)", // "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.body" : "Ocorreu um erro ao reivindicar o perfil, tente novamente mais tarde", // "researcherprofile.error.claim.title" : "Error", "researcherprofile.error.claim.title" : "Erro", // "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.body" : "Perfil reivindicado com sucesso", // "researcherprofile.success.claim.title" : "Success", "researcherprofile.success.claim.title" : "Successo", @@ -7428,327 +6734,247 @@ "person.page.orcid.granted-authorizations": "Autorizações concedidas", // "person.page.orcid.grant-authorizations" : "Grant authorizations", - // TODO New key - Add a translation - "person.page.orcid.grant-authorizations" : "Grant authorizations", + "person.page.orcid.grant-authorizations" : "Conceder autorizações", // "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": "Conectar ao 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.processing": "Vinculando perfil ao 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.link.error.message": "Algo deu errado ao conectar o perfil com ORCID. Se o problema persistir, entre em contato com o administrador.", // "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.orcid-not-linked-message": "O ORCID iD deste perfil ({{ orcid }}) ainda não foi conectado a uma conta no registro ORCID ou a conexão expirou.", // "person.page.orcid.unlink": "Disconnect from ORCID", - // TODO New key - Add a translation - "person.page.orcid.unlink": "Disconnect from ORCID", + "person.page.orcid.unlink": "Desconectar do ORCID", // "person.page.orcid.unlink.processing": "Processing...", - // TODO New key - Add a translation - "person.page.orcid.unlink.processing": "Processing...", + "person.page.orcid.unlink.processing": "Processando...", // "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": "Faltando Autorizações", // "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.missing-authorizations-message": "Faltam as seguintes autorizações:", // "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-missing-authorizations-message": "Ótimo! Esta caixa está vazia, então você concedeu todos os direitos de acesso para usar todas as funções oferecidas por sua instituição.", // "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.no-orcid-message": "Nenhum ORCID iD associado ainda. Ao clicar no botão abaixo é possível vincular este perfil a uma conta ORCID.", // "person.page.orcid.profile-preferences": "Profile preferences", - // TODO New key - Add a translation - "person.page.orcid.profile-preferences": "Profile preferences", + "person.page.orcid.profile-preferences": "Preferências de perfil", // "person.page.orcid.funding-preferences": "Funding preferences", - // TODO New key - Add a translation - "person.page.orcid.funding-preferences": "Funding preferences", + "person.page.orcid.funding-preferences": "Preferências de financiamento", // "person.page.orcid.publications-preferences": "Publication preferences", - // TODO New key - Add a translation - "person.page.orcid.publications-preferences": "Publication preferences", + "person.page.orcid.publications-preferences": "Preferências de publicação", // "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.remove-orcid-message": "Se você precisar remover seu ORCID, entre em contato com o administrador do repositório", // "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.save.preference.changes": "Atualizar configurações", // "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.affiliation" : "Afiliação", // "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.biographical" : "Dados biográficos", // "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.education" : "Educação", // "person.page.orcid.sync-profile.identifiers" : "Identifiers", - // TODO New key - Add a translation - "person.page.orcid.sync-profile.identifiers" : "Identifiers", + "person.page.orcid.sync-profile.identifiers" : "Identificadores", // "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.all" : "Todos financiamentos", // "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.mine" : "Meus financiamentos", // "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.my_selected" : "Financiamentos selecionados", // "person.page.orcid.sync-fundings.disabled" : "Disabled", - // TODO New key - Add a translation - "person.page.orcid.sync-fundings.disabled" : "Disabled", + "person.page.orcid.sync-fundings.disabled" : "Desabilitado", // "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.all" : "Todas publicações", // "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.mine" : "Minhas publicações", // "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.my_selected" : "Publicações selecionadas", // "person.page.orcid.sync-publications.disabled" : "Disabled", - // TODO New key - Add a translation - "person.page.orcid.sync-publications.disabled" : "Disabled", + "person.page.orcid.sync-publications.disabled" : "Desabilitado", // "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" : "Descarte a alteração e não sincronize com o registro ORCID", // "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.error": "Falha ao descartar o registro da fila ORCID", // "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.discard.success": "O registro da fila ORCID foi descartado com sucesso", // "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.empty-message": "O registro da fila ORCID está vazio", // "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.type" : "Tipo", // "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.description" : "Descrição", // "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.table.header.action" : "Ação", // "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.affiliation": "Afiliação", // "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.country": "País", // "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.education": "Educação", // "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.external_ids": "IDs Externos", // "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.other_names": "Outros nomes", // "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.qualification": "Qualificações", // "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.researcher_urls": "URL do pesquisador", // "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.description.keywords": "Palavra-chave", // "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.insert": "Adicionar uma nova entrada no registro ORCID", // "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.update": "Atualizar esta entrada no registro ORCID", // "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.delete": "Remover esta entrada do registro ORCID", // "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.publication": "Publicação", // "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.project": "Projeto", // "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.affiliation": "Afiliação", // "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.education": "Educação", // "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.qualification": "Qualificações", // "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.other_names": "Outro nome", // "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.country": "País", // "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.keywords": "Palavra-chave", // "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.external_ids": "Identificador externo", // "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.tooltip.researcher_urls": "URL do pesquisador", // "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" : "Sincronize com o registro ORCID", // "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.title": "A submissão ao ORCID falhou por falta de autorizações.", // "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.unauthorized-error.content": "Clique aqui para conceder novamente as permissões necessárias. Se o problema persistir, entre em contato com o administrador", // "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.bad-request-error": "Falha no envio ao ORCID porque o recurso enviado ao registro ORCID não é válido", // "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.error": "Falha no envio ao ORCID", // "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.conflict-error": "Falha no envio ao ORCID porque o recurso já está presente no registro ORCID", // "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.not-found-warning": "O recurso não existe mais no registro ORCID.", // "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.success": "A submissão ao ORCID foi concluída com sucesso", // "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": "Os dados que você deseja sincronizar com ORCID não são válidos", // "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.amount-currency.required": "A moeda do valor é obrigatória", // "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.external-id.required": "O recurso a ser enviado requer pelo menos um identificador", // "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.title.required": "O título é obrigatório", // "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.type.required": "O dc.type é obrigatório", // "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.start-date.required": "Data de Início é obrigatória", // "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.funder.required": "O financiador é necessário", // "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.country.invalid": "Os 2 dígitos de país são inválidos (ISO 3166)", // "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.required": "A organização é obrigatória", // "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.organization.name-required": "O nome da organização é obrigatório", // "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.publication.date-invalid" : "A data de publicação deve ser um ano após 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.address-required": "O endereço da organização a ser enviado exige uma endereço", // "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.city-required": "O endereço da organização a ser enviado exige uma cidade", // "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.organization.country-required": "O endereço da organização a ser enviado exige uma identificação de 2 dígitos válida para país ISO 3166", // "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.required": "É necessário um identificador para evitar ambiguidade das organizações. Os IDs suportados são GRID, Ringgold, identificadores de entidade legal (LEIs) e identificadores do Crossref Funder Registry", // "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.disambiguated-organization.value-required": "Os identificadores da organização requerem um valor", + "person.page.orcid.sync-queue.send.validation-error.disambiguated-organization.value-required": "Os identificadores da organização exigem um valor", // "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.required": "Os identificadores da organização exigem uma fonte", // "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.sync-queue.send.validation-error.disambiguation-source.invalid": "A origem de um dos identificadores da organização é inválida. As fontes suportadas são RINGGOLD, GRID, LEI e FUNDREF", @@ -7766,10 +6992,10 @@ "person.page.orcid.synchronization-mode-message": "Selecione como deseja que ocorra a sincronização com ORCID. As opções incluem \"Manual\" (você deve enviar seus dados para o ORCID manualmente), ou \"Lote\" (o sistema enviará seus dados para o ORCID por meio de um script programado).", // "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-funding-message": "Selecione se deseja enviar suas entidades de projeto vinculadas à lista de informações de financiamento do seu registro ORCID.", + "person.page.orcid.synchronization-mode-funding-message": "Selecione se deseja enviar suas entidades projeto para a lista de informações de financiamento no seu registro ORCID.", // "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-publication-message": "Selecione se deseja enviar suas entidades de publicação vinculadas à lista de trabalhos do seu registro ORCID.", + "person.page.orcid.synchronization-mode-publication-message": "Selecione se deseja enviar suas entidades publicação para a sua lista de trabalhos do seu registro ORCID.", // "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-mode-profile-message": "Selecione se deseja enviar seus dados biográficos ou identificadores pessoais para seu registro ORCID.", @@ -7784,7 +7010,7 @@ "person.page.orcid.synchronization-mode.manual": "Manual", // "person.page.orcid.scope.authenticate": "Get your ORCID iD", - "person.page.orcid.scope.authenticate": "COnseguir seu ORCID iD", + "person.page.orcid.scope.authenticate": "Obter seu ORCID iD", // "person.page.orcid.scope.read-limited": "Read your information with visibility set to Trusted Parties", "person.page.orcid.scope.read-limited": "Leia suas informações com visibilidade definida como Parceiros Confiáveis", @@ -7799,7 +7025,7 @@ "person.page.orcid.unlink.success": "A desconexão entre o perfil e o registro ORCID foi bem-sucedida", // "person.page.orcid.unlink.error": "An error occurred while disconnecting between the profile and the ORCID registry. Try again", - "person.page.orcid.unlink.error": "Um erro ocorreu enquanto dA desconexão entre o perfil e o registro ORCID. Tente novamente", + "person.page.orcid.unlink.error": "Ocorreu um erro ao desconectar o perfil e o registro ORCID. Tente novamente", // "person.orcid.sync.setting": "ORCID Synchronization settings", "person.orcid.sync.setting": "ORCID Configuração de sincronização", From a94877ad5a6f685974fcdd0378ecd621ef7039c4 Mon Sep 17 00:00:00 2001 From: lucaszc Date: Mon, 3 Oct 2022 19:45:27 -0300 Subject: [PATCH 58/78] Correcting, missing comma in line 62 Correct missing comma in line 62 --- src/assets/i18n/pt-BR.json5 | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index 27a5ff3172..89b345eeec 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -59,7 +59,7 @@ "access-status.metadata.only.listelement.badge": "Somente Metadadados", // "access-status.open.access.listelement.badge": "Open Access", - "access-status.open.access.listelement.badge": "Acesso aberto (Open Access)" + "access-status.open.access.listelement.badge": "Acesso aberto (Open Access)", // "access-status.restricted.listelement.badge": "Restricted", "access-status.restricted.listelement.badge": "Restrito", @@ -389,7 +389,6 @@ // "admin.access-control.epeople.table.email": "E-mail (exact)", "admin.access-control.epeople.table.email": "E-mail (exato)", - // "admin.access-control.epeople.table.edit": "Edit", "admin.access-control.epeople.table.edit": "Editar", @@ -482,7 +481,6 @@ // "admin.access-control.epeople.notification.deleted.success": "Successfully deleted EPerson: \"{{name}}\"", "admin.access-control.epeople.notification.deleted.success": "EPerson: \"{{name}}\" apagado com sucesso", - // "admin.access-control.groups.title": "Groups", "admin.access-control.groups.title": "Grupos", @@ -988,8 +986,6 @@ // "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.", "bitstream-request-a-copy.submit.error": "Ocorreu um erro ao enviar a solicitação de item.", - - // "browse.back.all-results": "All browse results", "browse.back.all-results": "Todos os resultados", @@ -1119,12 +1115,9 @@ // "browse.title.page": "Browsing {{ collection }} by {{ field }} {{ value }}", "browse.title.page": "Navegando {{ collection }} por {{ field }} {{ value }}", - // "chips.remove": "Remove chip", "chips.remove": "Remover chip", - - // "collection.create.head": "Create a Collection", "collection.create.head": "Criar uma coleção", @@ -1158,8 +1151,6 @@ // "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", @@ -1169,8 +1160,6 @@ // "collection.edit.breadcrumbs": "Edit Collection", "collection.edit.breadcrumbs": "Editar Coleção", - - // "collection.edit.tabs.mapper.head": "Item Mapper", "collection.edit.tabs.mapper.head": "Mapeamentos", @@ -1231,7 +1220,6 @@ // "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", "collection.edit.logo.delete.title": "Apagar logo", @@ -1259,16 +1247,12 @@ // "collection.edit.logo.upload": "Drop a Collection Logo to upload", "collection.edit.logo.upload": "Arraste um logotipo da coleção para upload", - - // "collection.edit.notifications.success": "Successfully edited the Collection", "collection.edit.notifications.success": "Coleção editada com sucesso", // "collection.edit.return": "Return", "collection.edit.return": "Voltar", - - // "collection.edit.tabs.curate.head": "Curate", "collection.edit.tabs.curate.head": "Curadoria", @@ -1359,8 +1343,6 @@ // "collection.edit.tabs.source.title": "Collection Edit - Content Source", "collection.edit.tabs.source.title": "Editar Coleção - Conteúdo fonte", - - // "collection.edit.template.add-button": "Add", "collection.edit.template.add-button": "Adicionar", @@ -1398,7 +1380,6 @@ "collection.edit.template.title": "Editar Template de Item", - // "collection.form.abstract": "Short Description", "collection.form.abstract": "Descrição curta", @@ -6858,4 +6839,4 @@ // "home.recent-submissions.head": "Recent Submissions", "home.recent-submissions.head": "Submissões Recentes", -} \ No newline at end of file +} From f72f6a3166f20b998fe908c1661c0bad8a94544e Mon Sep 17 00:00:00 2001 From: lucaszc Date: Tue, 4 Oct 2022 06:51:52 -0300 Subject: [PATCH 59/78] Update pt-BR.json5 Add missing translations .. --- src/assets/i18n/pt-BR.json5 | 189 +++++++++++++----------------------- 1 file changed, 66 insertions(+), 123 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index 89b345eeec..a345d081dd 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -1308,16 +1308,13 @@ "collection.edit.tabs.source.form.oaiSource": "Provedor OAI", // "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_BITSTREAMS": "Colheita (Harvest) metadado e bitstreams (requer suporte ORE)", // "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_AND_REF": "Colheita (Harvest) metadado e referências do bitstreams (requer suporte ORE)", // "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.form.options.harvestType.METADATA_ONLY": "Colheita (Harvest) somente do metadado", // "collection.edit.tabs.source.head": "Content Source", "collection.edit.tabs.source.head": "Conteúdo Fonte", @@ -1635,12 +1632,10 @@ "comcol-role.edit.collection-admin.name": "Administradores", // "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.community-admin.description": "Os administradores da comunidade podem criar subcomunidades ou coleções e gerenciar ou atribuir gerenciamento para essas subcomunidades ou coleções. Além disso, eles decidem quem pode enviar itens para quaisquer subcoleções, editar metadados de itens (após o envio) e adicionar (mapear) itens existentes de outras coleções (sujeito a autorização).", // "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.collection-admin.description": "Os administradores da coleção decidem quem pode enviar itens para a coleção, editar metadados do item (após o envio) e adicionar (mapear) itens existentes de outras coleções a esta coleção (sujeito à autorização para essa coleção).", // "comcol-role.edit.submitters.name": "Submitters", "comcol-role.edit.submitters.name": "Remetentes", @@ -1858,8 +1853,7 @@ "curation.form.handle.hint": "Dica: Introduza [o-seu-prefixo-handle]/0 para executar a tarefa em todo o repositório (nem todas as tarefas são compatíveis com esta opção)", // "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.message": "Caro {{ receiverName }},\nEm resposta ao seu pedido, lamento informar que não é possível enviar uma cópia do(s) arquivo(s) que você solicitou, referente ao documento: \"{{ itemUrl }}\" ({{ itemName }}), do qual sou autor.\n\nAtenciosamente,\n{{ authorName }} <{{ authorEmail }}>", // "deny-request-copy.email.subject": "Request copy of document", "deny-request-copy.email.subject": "Solicitar cópia do documento", @@ -2289,8 +2283,7 @@ // "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.message": "Prezado {{ receiverName }},\nEm resposta à sua solicitação, tenho o prazer de lhe enviar em anexo uma cópia do(s) arquivo(s) referente(s) ao documento: \"{{ itemUrl }}\" ({{ itemName }}) , do qual sou o(a) autor(a).\n\nAtenciosamente,\n{{ authorName }} <{{ authorEmail }}>", // "grant-request-copy.email.subject": "Request copy of document", "grant-request-copy.email.subject": "Solicitar cópia do documento", @@ -2419,13 +2412,13 @@ "info.privacy.title": "Política de privacidade", // "info.feedback.breadcrumbs": "Feedback", - "info.feedback.breadcrumbs": "Feedback", + "info.feedback.breadcrumbs": "Sugestão", // "info.feedback.head": "Feedback", - "info.feedback.head": "Feedback", + "info.feedback.head": "Sugestão", // "info.feedback.title": "Feedback", - "info.feedback.title": "Feedback", + "info.feedback.title": "Sugestão", // "info.feedback.info": "Thanks for sharing your feedback about the DSpace system. Your comments are appreciated!", "info.feedback.info": "Obrigado por compartilhar seus comentários sobre o sistema DSpace. Seus comentários são apreciados!", @@ -2443,7 +2436,7 @@ "info.feedback.email-label": "Seu Email", // "info.feedback.create.success" : "Feedback Sent Successfully!", - "info.feedback.create.success" : "Feedback Enviado com Sucesso!", + "info.feedback.create.success" : "Sugestão Enviada com Sucesso!", // "info.feedback.error.email.required" : "A valid email address is required", "info.feedback.error.email.required" : "Um endereço de email válido é requerido", @@ -2455,7 +2448,7 @@ "info.feedback.page-label" : "Página", // "info.feedback.page_help" : "Tha page related to your feedback", - "info.feedback.page_help" : "A página relacionada ao seu feedback", + "info.feedback.page_help" : "A página relacionada a sua sugestão", // "item.alerts.private": "This item is non-discoverable", "item.alerts.private": "Este item é privado", @@ -3044,12 +3037,10 @@ "item.page.description": "Descrição", // "item.page.journal-issn": "Journal ISSN", - // TODO New key - Add a translation - "item.page.journal-issn": "Journal ISSN", + "item.page.journal-issn": "ISSN da Revista", // "item.page.journal-title": "Journal Title", - // TODO New key - Add a translation - "item.page.journal-title": "Journal Title", + "item.page.journal-title": "Título da Revista", // "item.page.publisher": "Publisher", "item.page.publisher": "Editor", @@ -3211,8 +3202,7 @@ "item.preview.dc.type": "Tipo:", // "item.preview.oaire.citation.issue" : "Issue", - // TODO New key - Add a translation - "item.preview.oaire.citation.issue" : "Issue", + "item.preview.oaire.citation.issue" : "Questão", // "item.preview.oaire.citation.volume" : "Volume", "item.preview.oaire.citation.volume" : "Volume", @@ -3227,8 +3217,7 @@ "item.preview.dc.identifier": "Identificador:", // "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.relation.ispartof" : "Revista ou Série", // "item.preview.dc.identifier.doi" : "DOI", "item.preview.dc.identifier.doi" : "DOI", @@ -3306,19 +3295,16 @@ "item.version.history.table.summary": "Sumário", // "item.version.history.table.workspaceItem": "Workspace item", - // TODO New key - Add a translation - "item.version.history.table.workspaceItem": "Workspace item", + "item.version.history.table.workspaceItem": "Item do Workspace (Área de trabalho)", // "item.version.history.table.workflowItem": "Workflow item", - // TODO New key - Add a translation - "item.version.history.table.workflowItem": "Workflow item", + "item.version.history.table.workflowItem": "Item do Workflow (Fluxo de Trabalho)", // "item.version.history.table.actions": "Action", "item.version.history.table.actions": "Ação", // "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.editWorkspaceItem": "Editar item do Workspace (Área de Trabalho)", // "item.version.history.table.action.editSummary": "Edit summary", "item.version.history.table.action.editSummary": "Editar o sumário", @@ -3339,8 +3325,7 @@ "item.version.history.table.action.hasDraft": "Não é possível criar uma nova versão porque há um envio em andamento no histórico de versões", // "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.notice": "Esta não é a versão mais recente deste item. A versão mais recente pode ser encontrada aqui.", // "item.version.create.modal.header": "New version", "item.version.create.modal.header": "Nova versão", @@ -3421,8 +3406,7 @@ "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.edit": "Editar este item", // "journal.page.editor": "Editor-in-Chief", "journal.page.editor": "Editor Chefe", @@ -3440,8 +3424,7 @@ "journal.search.results.head": "Resultado da 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-relationships.search.results.head": "Resultados da Busca de Periódicos", // "journal.search.title": "Journal Search", "journal.search.title": "Busca de Periódicos", @@ -3716,8 +3699,7 @@ "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.admin_search": "Seção do menu de busca administrativa", // "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.control_panel": "Seção do menu Painel de Controle", @@ -3846,8 +3828,7 @@ "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", + "menu.section.workflow": "Administrar Workflow", // "metadata-export-search.tooltip": "Export search results as CSV", "metadata-export-search.tooltip": "Exportar resultados de pesquisa como CSV", @@ -3943,8 +3924,7 @@ // "mydspace.search-form.placeholder": "Search in mydspace...", "mydspace.search-form.placeholder": "Procurar no meudspace...", - // "mydspace.show.workflow": "Workflow tasks", - // TODO Source message changed - Revise the translation + // "mydspace.show.workflow": "All tasks", "mydspace.show.workflow": "Todas as tarefas", // "mydspace.show.workspace": "Your Submissions", @@ -3966,7 +3946,7 @@ "mydspace.status.workspace": "Espaço de trabalho", // "mydspace.title": "MyDSpace", - "mydspace.title": "MyDSpace", + "mydspace.title": "MeuDSpace", // "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.", @@ -4279,8 +4259,7 @@ "process.overview.delete.clear": "Limpar seleção de exclusão", // "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.processing": "{{count}} processos estão sendo excluídos. Aguarde até que a exclusão seja totalmente concluída. Observe que isso pode demorar um pouco.", // "process.overview.delete.body": "Are you sure you want to delete {{count}} process(es)?", "process.overview.delete.body": "Tem certeza de que deseja excluir {{count}} processo(s)?", @@ -5069,12 +5048,10 @@ "search.filters.filter.submitter.label": "Procurar submetedor", // "search.filters.entityType.JournalIssue": "Journal Issue", - // TODO New key - Add a translation - "search.filters.entityType.JournalIssue": "Journal Issue", + "search.filters.entityType.JournalIssue": "Número de Revista", // "search.filters.entityType.JournalVolume": "Journal Volume", - // TODO New key - Add a translation - "search.filters.entityType.JournalVolume": "Journal Volume", + "search.filters.entityType.JournalVolume": "Volume da Revista", // "search.filters.entityType.OrgUnit": "Organizational Unit", "search.filters.entityType.OrgUnit": "Unidade Organizacional", @@ -5287,16 +5264,14 @@ "submission.import-external.title": "Importar metadados de fonte externa", // "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.Journal": "Importar uma revista de uma fonte externa", // "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.JournalVolume": "Importar um volume de revista de uma fonte externa", // "submission.import-external.title.OrgUnit": "Import a publisher from an external source", "submission.import-external.title.OrgUnit": "Importar um editor de uma fonte externa", @@ -5398,8 +5373,7 @@ "submission.import-external.preview.title.none": "Pré-visualização de Item", // "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.Journal": "Pré-visualização de Revista", // "submission.import-external.preview.title.OrgUnit": "Organizational Unit Preview", "submission.import-external.preview.title.OrgUnit": "Pré-visualização de Unidade Organizacional", @@ -5432,16 +5406,14 @@ "submission.sections.describe.relationship-lookup.external-source.import-button-title.isAuthorOfPublication": "Importar autor remoto", // "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": "Importar revista remota", // "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.Journal Volume": "Importar volume de revista remoto", // "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Project", "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Projeto", @@ -5483,16 +5455,13 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.title": "Projeto", // "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.head.openAIREFunding": "Financiamento API 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.title": "Importar Autor Remoto", // "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.local-entity": "Autor local adicionado com sucesso à seleção", // "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.isAuthorOfPublication.added.new-entity": "Autor externo importado e adicionado com sucesso à seleção", @@ -5537,16 +5506,13 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.import": "Importar", // "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.title": "Importar Revista Remota", // "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.local-entity": "Revista Local adicionada com sucesso à seleção", // "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.added.new-entity": "Revista Externa importada e adicionada com sucesso à seleção", // "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal Issue.title": "Import Remote Journal Issue", // TODO New key - Add a translation @@ -5561,16 +5527,13 @@ "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.title": "Importar Volume de Revista Remoto", // "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.local-entity": "Volume de Revista Local adicionado com sucesso à seleção", // "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.Journal Volume.added.new-entity": "Volume de Revista Externo importado e adicionado com sucesso à seleção", // "submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Select a local match:", "submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Selecione uma correspondência local:", @@ -5606,8 +5569,7 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.isAuthorOfPublication": "Autores Locais ({{ 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.isJournalOfPublication": "Revistas Locais ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Local Projects ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Project": "Projetos Locais ({{ count }})", @@ -5627,25 +5589,20 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.DataFile": "Arquivos de Dado Locais ({{ 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.Journal": "Revistas Locais ({{ 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.isJournalIssueOfPublication": "Números de Revista Local ({{ 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.JournalIssue": "Números de Revista Local ({{ 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.isJournalVolumeOfPublication": "Volumes de Revista Local ({{ 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.JournalVolume": "Volumes de Revista Local ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})", - "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Sherpa Journals ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaJournal": "Revistas Sherpa ({{ count }})", // "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Sherpa Publishers ({{ count }})", "submission.sections.describe.relationship-lookup.search-tab.tab-title.sherpaPublisher": "Sherpa Publishers ({{ count }})", @@ -5699,22 +5656,17 @@ "submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Seleção Atual ({{ 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.isJournalIssueOfPublication": "Números de Revista", // "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.JournalIssue": "Números de Revista", // "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.isJournalVolumeOfPublication": "Volumes de Revista", // "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.JournalVolume": "Volumes de Revista", // "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.isJournalOfPublication": "Revistas", // "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Authors", "submission.sections.describe.relationship-lookup.title.isAuthorOfPublication": "Autores", @@ -5761,12 +5713,10 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.isAuthorOfPublication": "Autores Selecionados", // "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.isJournalOfPublication": "Revistas Selecionadas", // "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.isJournalVolumeOfPublication": "Volume de Revista selecionados", // "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Selected Projects", "submission.sections.describe.relationship-lookup.selection-tab.title.Project": "Projetos Selecionados", @@ -5786,14 +5736,12 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.DataFile": "Pacotes de arquivos de dados", // "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.Journal": "Revistas Selecionadas", // "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Selected Issue", "submission.sections.describe.relationship-lookup.selection-tab.title.isJournalIssueOfPublication": "Problema selecionado", // "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.JournalVolume": "Volume de Revista selecionado", // "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingAgencyOfPublication": "Selected Funding Agency", "submission.sections.describe.relationship-lookup.selection-tab.title.isFundingAgencyOfPublication": "Agência de financiamento selecionada", @@ -6163,7 +6111,7 @@ "submission.sections.sherpa.publisher.policy": "Política do Editor", // "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.description": "As informações abaixo foram encontradas via Sherpa Romeo. Com base nas políticas de seu editor, ele fornece conselhos sobre se um embargo pode ser necessário e/ou quais arquivos você tem permissão para enviar. Se você tiver dúvidas, entre em contato com o administrador do site por meio do formulário de comentários no rodapé.", + "submission.sections.sherpa.publisher.policy.description": "As informações abaixo foram encontradas via Sherpa Romeo. Com base nas políticas de seu editor, ele fornece conselhos sobre se um embargo pode ser necessário e/ou quais arquivos você tem permissão para enviar. Se você tiver dúvidas, entre em contato com o administrador do site por meio do formulário de sugestões no rodapé.", // "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.openaccess": "Os caminhos de Open Access permitidos pela política desta revista estão listados abaixo por versão do artigo. Clique em um caminho para uma visualização mais detalhada", @@ -6316,15 +6264,13 @@ "thumbnail.project.alt": "Logo do Projeto", // "thumbnail.project.placeholder": "Project Placeholder Image", - // TODO New key - Add a translation - "thumbnail.project.placeholder": "Project Placeholder Image", + "thumbnail.project.placeholder": "Imagem do Projeto", // "thumbnail.orgunit.alt": "OrgUnit Logo", "thumbnail.orgunit.alt": "Logo UnitOrg", // "thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image", - // TODO New key - Add a translation - "thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image", + "thumbnail.orgunit.placeholder": "Imagem da OrgUnit", // "thumbnail.person.alt": "Profile Picture", "thumbnail.person.alt": "Foto do Perfil", @@ -6351,7 +6297,6 @@ "vocabulary-treeview.search.no-result": "Não há items para mostrar", // "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", @@ -6369,8 +6314,7 @@ // "uploader.or": ", or ", "uploader.or": ", ou ", - // "uploader.processing": "Processing uploaded file(s)... (it's now safe to close this page)", - // TODO Source message changed - Revise the translation + // "uploader.processing": "Processing", "uploader.processing": "Processando", // "uploader.queue-length": "Queue length", @@ -6449,14 +6393,13 @@ "workflow-item.send-back.button.confirm": "Devolver", // "workflow-item.view.breadcrumbs": "Workflow View", - "workflow-item.view.breadcrumbs": "Visualização do Workflow", + "workflow-item.view.breadcrumbs": "Visualização do FLuxo de Trabalho (Workflow)", // "workspace-item.view.breadcrumbs": "Workspace View", - // TODO New key - Add a translation - "workspace-item.view.breadcrumbs": "Workspace View", + "workspace-item.view.breadcrumbs": "Visualização da Área de Trabalho (Workspace)", // "workspace-item.view.title": "Workspace View", - "workspace-item.view.title": "Visão do Workspace", + "workspace-item.view.title": "Visualização da Área de Trabalho (Workspace)", // "idle-modal.header": "Session will expire soon", "idle-modal.header": "Sessão vai expirar em breve", From d8c8a43da477e1fa2a8fe353515fe00cf88a4f6c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 4 Oct 2022 14:47:07 +0200 Subject: [PATCH 60/78] [CST-6876] Refactoring workflow actions components in order to pass item and workflowitem objects down from paren search element component to children --- ...claimed-task-actions-abstract.component.ts | 24 +++++---- .../claimed-task-actions.component.html | 17 +++++-- .../claimed-task-actions.component.spec.ts | 9 ++-- .../claimed-task-actions.component.ts | 15 +++--- ...imed-task-actions-loader.component.spec.ts | 37 +++++++++++++- .../claimed-task-actions-loader.component.ts | 14 +++++ .../mydspace-reloadable-actions.spec.ts | 7 ++- .../pool-task-actions.component.html | 2 +- .../pool-task-actions.component.spec.ts | 9 ++-- .../pool-task/pool-task-actions.component.ts | 23 +++------ ...earch-result-detail-element.component.html | 10 ++-- ...ch-result-detail-element.component.spec.ts | 35 +++++++------ ...-search-result-detail-element.component.ts | 49 +++++++++++++++--- ...earch-result-detail-element.component.html | 11 ++-- ...ch-result-detail-element.component.spec.ts | 35 +++++++------ ...-search-result-detail-element.component.ts | 51 ++++++++++++++++--- ...-search-result-list-element.component.html | 29 +++++------ ...arch-result-list-element.component.spec.ts | 1 + ...ed-search-result-list-element.component.ts | 38 +++++++++++--- ...-search-result-list-element.component.html | 25 +++++---- ...arch-result-list-element.component.spec.ts | 1 + ...ol-search-result-list-element.component.ts | 38 +++++++++++--- 22 files changed, 332 insertions(+), 148 deletions(-) diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts index f1b486a075..1ff55cefd6 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/claimed-task-actions-abstract.component.ts @@ -10,11 +10,11 @@ import { RequestService } from '../../../../core/data/request.service'; import { Observable } from 'rxjs'; import { RemoteData } from '../../../../core/data/remote-data'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; -import { switchMap, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { CLAIMED_TASK } from '../../../../core/tasks/models/claimed-task-object.resource-type'; -import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; import { Item } from '../../../../core/shared/item.model'; import { MyDSpaceReloadableActionsComponent } from '../../mydspace-reloadable-actions'; +import { isEmpty } from '../../../empty.util'; /** * Abstract component for rendering a claimed task's action @@ -36,6 +36,11 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload object: ClaimedTask; + /** + * The item object that belonging to the ClaimedTask object + */ + item: Item; + /** * Anchor used to reload the pool task. */ @@ -43,6 +48,11 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload subs = []; + /** + * The workflowitem object that belonging to the ClaimedTask object + */ + workflowitem: WorkflowItem; + protected constructor(protected injector: Injector, protected router: Router, protected notificationsService: NotificationsService, @@ -85,16 +95,10 @@ export abstract class ClaimedTaskActionsAbstractComponent extends MyDSpaceReload * Retrieve the itemUuid. */ initReloadAnchor() { - if (!(this.object as any).workflowitem) { + if (isEmpty(this.item)) { return; } - this.subs.push(this.object.workflowitem.pipe( - getFirstSucceededRemoteDataPayload(), - switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) - )) - .subscribe((item: Item) => { - this.itemUuid = item.uuid; - })); + this.itemUuid = this.item.uuid; } ngOnDestroy() { diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html index 47ae8cdb8e..34932f47bf 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html @@ -1,16 +1,23 @@
- + - +
diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts index 6f7970d71f..a2e0b14134 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts @@ -3,7 +3,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; -import { cold } from 'jasmine-marbles'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; @@ -123,7 +122,9 @@ describe('ClaimedTaskActionsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ClaimedTaskActionsComponent); component = fixture.componentInstance; + component.item = item; component.object = mockObject; + component.workflowitem = workflowitem; notificationsServiceStub = TestBed.inject(NotificationsService as any); router = TestBed.inject(Router as any); fixture.detectChanges(); @@ -133,11 +134,11 @@ describe('ClaimedTaskActionsComponent', () => { component.object = null; component.initObjects(mockObject); + expect(component.item).toEqual(item); + expect(component.object).toEqual(mockObject); - expect(component.workflowitem$).toBeObservable(cold('(b|)', { - b: rdWorkflowitem.payload - })); + expect(component.workflowitem).toEqual(workflowitem); }); it('should reload page on process completed', waitForAsync(() => { diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index f9bf22fa86..dc57105a4e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -2,12 +2,10 @@ import { Component, Injector, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { filter, map, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTask } from '../../../core/tasks/models/claimed-task-object.model'; -import { isNotUndefined } from '../../empty.util'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; import { RemoteData } from '../../../core/data/remote-data'; import { MyDSpaceActionsComponent } from '../mydspace-actions'; @@ -18,6 +16,7 @@ import { WorkflowAction } from '../../../core/tasks/models/workflow-action-objec import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { WORKFLOW_TASK_OPTION_RETURN_TO_POOL } from './return-to-pool/claimed-task-actions-return-to-pool.component'; import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; +import { Item } from '../../../core/shared/item.model'; /** * This component represents actions related to ClaimedTask object. @@ -34,10 +33,15 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent; + @Input() workflowitem: WorkflowItem; /** * The workflow action available for this task @@ -87,11 +91,6 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent>).pipe( - filter((rd: RemoteData) => ((!rd.isRequestPending) && isNotUndefined(rd.payload))), - map((rd: RemoteData) => rd.payload), - take(1)); } /** diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts index 6de2056fe8..95e31f5523 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts @@ -1,5 +1,5 @@ import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component'; -import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; @@ -15,6 +15,8 @@ import { RequestService } from '../../../../core/data/request.service'; import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; import { getMockSearchService } from '../../../mocks/search-service.mock'; import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { Item } from '../../../../core/shared/item.model'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; const searchService = getMockSearchService(); @@ -27,6 +29,37 @@ describe('ClaimedTaskActionsLoaderComponent', () => { const option = 'test_option'; const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); + const item = Object.assign(new Item(), { + metadata: { + 'dc.title': [ + { + language: 'en_US', + value: 'This is just another title' + } + ], + 'dc.type': [ + { + language: null, + value: 'Article' + } + ], + 'dc.contributor.author': [ + { + language: 'en_US', + value: 'Smith, Donald' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '2015-06-26' + } + ] + } + }); + + const workflowitem = Object.assign(new WorkflowItem(), { id: '333' }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], @@ -52,8 +85,10 @@ describe('ClaimedTaskActionsLoaderComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(ClaimedTaskActionsLoaderComponent); comp = fixture.componentInstance; + comp.item = item; comp.object = object; comp.option = option; + comp.workflowitem = workflowitem; spyOn(comp, 'getComponentByWorkflowTaskOption').and.returnValue(ClaimedTaskActionsEditMetadataComponent); fixture.detectChanges(); diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index 68c597a41c..6a14aeb5bc 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -15,6 +15,8 @@ import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-ac import { hasValue } from '../../../empty.util'; import { Subscription } from 'rxjs'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; +import { Item } from '../../../../core/shared/item.model'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; @Component({ selector: 'ds-claimed-task-actions-loader', @@ -25,6 +27,11 @@ import { MyDSpaceActionsResult } from '../../mydspace-actions'; * Passes on the ClaimedTask to the component and subscribes to the processCompleted output */ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { + /** + * The item object that belonging to the ClaimedTask object + */ + @Input() item: Item; + /** * The ClaimedTask object */ @@ -36,6 +43,11 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { */ @Input() option: string; + /** + * The workflowitem object that belonging to the ClaimedTask object + */ + @Input() workflowitem: WorkflowItem; + /** * Emits the success or failure of a processed action */ @@ -69,7 +81,9 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { const componentRef = viewContainerRef.createComponent(componentFactory); const componentInstance = (componentRef.instance as ClaimedTaskActionsAbstractComponent); + componentInstance.item = this.item; componentInstance.object = this.object; + componentInstance.workflowitem = this.workflowitem; if (hasValue(componentInstance.processCompleted)) { this.subs.push(componentInstance.processCompleted.subscribe((result) => this.processCompleted.emit(result))); } diff --git a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts index 30c7db3c4b..fe9d955287 100644 --- a/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts +++ b/src/app/shared/mydspace-actions/mydspace-reloadable-actions.spec.ts @@ -12,10 +12,7 @@ import { RouterStub } from '../testing/router.stub'; import { getMockSearchService } from '../mocks/search-service.mock'; import { getMockRequestService } from '../mocks/request.service.mock'; import { Item } from '../../core/shared/item.model'; -import { - createFailedRemoteDataObject, - createSuccessfulRemoteDataObject -} from '../remote-data.utils'; +import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from '../remote-data.utils'; import { WorkflowItem } from '../../core/submission/models/workflowitem.model'; import { TranslateLoaderMock } from '../mocks/translate-loader.mock'; import { NotificationsService } from '../notifications/notifications.service'; @@ -103,7 +100,9 @@ describe('MyDSpaceReloadableActionsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PoolTaskActionsComponent); component = fixture.componentInstance; + component.item = item; component.object = mockObject; + component.workflowitem = workflowitem; notificationsServiceStub = TestBed.get(NotificationsService); router = TestBed.get(Router); fixture.detectChanges(); diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html index 57617d8151..9d63a42d03 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.html @@ -8,6 +8,6 @@ diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts index 8c0de55138..385dea73f3 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.spec.ts @@ -4,7 +4,6 @@ import { Router } from '@angular/router'; import { By } from '@angular/platform-browser'; import { of as observableOf } from 'rxjs'; -import { cold } from 'jasmine-marbles'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; @@ -105,7 +104,9 @@ describe('PoolTaskActionsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PoolTaskActionsComponent); component = fixture.componentInstance; + component.item = item; component.object = mockObject; + component.workflowitem = workflowitem; notificationsServiceStub = TestBed.inject(NotificationsService as any); router = TestBed.inject(Router as any); fixture.detectChanges(); @@ -120,11 +121,11 @@ describe('PoolTaskActionsComponent', () => { component.object = null; component.initObjects(mockObject); + expect(component.item).toEqual(item); + expect(component.object).toEqual(mockObject); - expect(component.workflowitem$).toBeObservable(cold('(b|)', { - b: rdWorkflowitem.payload - })); + expect(component.workflowitem).toEqual(workflowitem); }); it('should display claim task button', () => { diff --git a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts index 193739ec0d..c8dcd87a33 100644 --- a/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/pool-task/pool-task-actions.component.ts @@ -2,19 +2,17 @@ import { Component, Injector, Input, OnDestroy } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { filter, map, switchMap, take } from 'rxjs/operators'; +import { switchMap, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; import { RemoteData } from '../../../core/data/remote-data'; import { PoolTask } from '../../../core/tasks/models/pool-task-object.model'; import { PoolTaskDataService } from '../../../core/tasks/pool-task-data.service'; -import { isNotUndefined } from '../../empty.util'; import { NotificationsService } from '../../notifications/notifications.service'; import { RequestService } from '../../../core/data/request.service'; import { SearchService } from '../../../core/shared/search/search.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; -import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { Item } from '../../../core/shared/item.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { MyDSpaceReloadableActionsComponent } from '../mydspace-reloadable-actions'; @@ -36,10 +34,15 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent */ @Input() object: PoolTask; + /** + * The item object that belonging to the PoolTask object + */ + @Input() item: Item; + /** * The workflowitem object that belonging to the PoolTask object */ - public workflowitem$: Observable; + @Input() workflowitem: WorkflowItem; /** * Anchor used to reload the pool task. @@ -83,10 +86,6 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent */ initObjects(object: PoolTask) { this.object = object; - this.workflowitem$ = (this.object.workflowitem as Observable>).pipe( - filter((rd: RemoteData) => ((!rd.isRequestPending) && isNotUndefined(rd.payload))), - map((rd: RemoteData) => rd.payload), - take(1)); } actionExecution(): Observable { @@ -104,13 +103,7 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent * Retrieve the itemUuid. */ initReloadAnchor() { - (this.object as any).workflowitem.pipe( - getFirstSucceededRemoteDataPayload(), - switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) - )) - .subscribe((item: Item) => { - this.itemUuid = item.uuid; - }); + this.itemUuid = this.item.uuid; } ngOnDestroy() { diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html index 1d8f599e65..3695f4714d 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.html @@ -1,10 +1,12 @@ - - + - + diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts index a6a3e2020b..b2bbb69ef4 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.spec.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed, tick, waitForAsync, fakeAsync} from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; @@ -7,7 +7,9 @@ import { of as observableOf } from 'rxjs'; import { Item } from '../../../../core/shared/item.model'; import { ClaimedTaskSearchResultDetailElementComponent } from './claimed-task-search-result-detail-element.component'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; @@ -15,6 +17,7 @@ import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; let component: ClaimedTaskSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -58,6 +61,9 @@ const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdIt const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); mockResultObject.indexableObject = Object.assign(new ClaimedTask(), { workflowitem: observableOf(rdWorkflowitem) }); const linkService = getMockLinkService(); +const objectCacheServiceMock = jasmine.createSpyObj('ObjectCacheService', { + remove: jasmine.createSpy('remove') +}); describe('ClaimedTaskSearchResultDetailElementComponent', () => { beforeEach(waitForAsync(() => { @@ -65,7 +71,8 @@ describe('ClaimedTaskSearchResultDetailElementComponent', () => { imports: [NoopAnimationsModule], declarations: [ClaimedTaskSearchResultDetailElementComponent, VarDirective], providers: [ - { provide: LinkService, useValue: linkService } + { provide: LinkService, useValue: linkService }, + { provide: ObjectCacheService, useValue: objectCacheServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ClaimedTaskSearchResultDetailElementComponent, { @@ -83,18 +90,16 @@ describe('ClaimedTaskSearchResultDetailElementComponent', () => { fixture.detectChanges(); }); - it('should init workflowitem properly', (done) => { - component.workflowitemRD$.subscribe((workflowitemRD) => { - // Make sure the necessary links are being resolved - expect(linkService.resolveLinks).toHaveBeenCalledWith( - component.dso, - jasmine.objectContaining({ name: 'workflowitem' }), - jasmine.objectContaining({ name: 'action' }) - ); - expect(workflowitemRD.payload).toEqual(workflowitem); - done(); - }); - }); + it('should init workflowitem properly', fakeAsync(() => { + flush(); + expect(linkService.resolveLinks).toHaveBeenCalledWith( + component.dso, + jasmine.objectContaining({ name: 'workflowitem' }), + jasmine.objectContaining({ name: 'action' }) + ); + expect(component.workflowitem$.value).toEqual(workflowitem); + expect(component.item$.value).toEqual(item); + })); it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.VALIDATION); diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts index 74411d2341..2ee661ef38 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component.ts @@ -1,17 +1,24 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; +import { mergeMap, tap } from 'rxjs/operators'; import { RemoteData } from '../../../../core/data/remote-data'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { SearchResultDetailElementComponent } from '../search-result-detail-element.component'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { ClaimedTaskSearchResult } from '../../../object-collection/shared/claimed-task-search-result.model'; import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { Item } from '../../../../core/shared/item.model'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { isNotEmpty } from '../../../empty.util'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; /** * This component renders claimed task object for the search result in the detail view. @@ -23,7 +30,12 @@ import { LinkService } from '../../../../core/cache/builders/link.service'; }) @listableObjectComponent(ClaimedTaskSearchResult, ViewMode.DetailedListElement) -export class ClaimedTaskSearchResultDetailElementComponent extends SearchResultDetailElementComponent { +export class ClaimedTaskSearchResultDetailElementComponent extends SearchResultDetailElementComponent implements OnInit, OnDestroy { + + /** + * The item object that belonging to the result object + */ + public item$: BehaviorSubject = new BehaviorSubject(null); /** * A boolean representing if to show submitter information @@ -38,9 +50,9 @@ export class ClaimedTaskSearchResultDetailElementComponent extends SearchResultD /** * The workflowitem object that belonging to the result object */ - public workflowitemRD$: Observable>; + public workflowitem$: BehaviorSubject = new BehaviorSubject(null); - constructor(protected linkService: LinkService) { + constructor(protected linkService: LinkService, protected objectCache: ObjectCacheService) { super(); } @@ -53,7 +65,30 @@ export class ClaimedTaskSearchResultDetailElementComponent extends SearchResultD followLink('item', {}, followLink('bundles')), followLink('submitter') ), followLink('action')); - this.workflowitemRD$ = this.dso.workflowitem as Observable>; + + (this.dso.workflowitem as Observable>).pipe( + getFirstCompletedRemoteData(), + mergeMap((wfiRD: RemoteData) => { + if (wfiRD.hasSucceeded) { + this.workflowitem$.next(wfiRD.payload); + return (wfiRD.payload.item as Observable>).pipe( + getFirstCompletedRemoteData() + ); + } else { + return EMPTY; + } + }), + tap((itemRD: RemoteData) => { + if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { + this.item$.next(itemRD.payload); + } + }) + ).subscribe(); + } + + ngOnDestroy() { + // This ensures the object is removed from cache, when action is performed on task + this.objectCache.remove(this.dso._links.workflowitem.href); } } diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html index 232b54d4d9..c9165b416a 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.html @@ -1,9 +1,12 @@ - - + - + + diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts index 8e0458d49f..59725233ba 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.spec.ts @@ -1,12 +1,14 @@ import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, flush, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { of as observableOf } from 'rxjs'; import { Item } from '../../../../core/shared/item.model'; import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; import { PoolSearchResultDetailElementComponent } from './pool-search-result-detail-element.component'; @@ -15,8 +17,7 @@ import { VarDirective } from '../../../utils/var.directive'; import { LinkService } from '../../../../core/cache/builders/link.service'; import { getMockLinkService } from '../../../mocks/link-service.mock'; import { By } from '@angular/platform-browser'; -import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; -import { DSONameServiceMock } from '../../../mocks/dso-name.service.mock'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; let component: PoolSearchResultDetailElementComponent; let fixture: ComponentFixture; @@ -60,6 +61,9 @@ const workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdIt const rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); mockResultObject.indexableObject = Object.assign(new PoolTask(), { workflowitem: observableOf(rdWorkflowitem) }); const linkService = getMockLinkService(); +const objectCacheServiceMock = jasmine.createSpyObj('ObjectCacheService', { + remove: jasmine.createSpy('remove') +}); describe('PoolSearchResultDetailElementComponent', () => { beforeEach(waitForAsync(() => { @@ -70,7 +74,7 @@ describe('PoolSearchResultDetailElementComponent', () => { { provide: 'objectElementProvider', useValue: (mockResultObject) }, { provide: 'indexElementProvider', useValue: (compIndex) }, { provide: LinkService, useValue: linkService }, - { provide: DSONameService, useClass: DSONameServiceMock }, + { provide: ObjectCacheService, useValue: objectCacheServiceMock } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(PoolSearchResultDetailElementComponent, { @@ -88,17 +92,16 @@ describe('PoolSearchResultDetailElementComponent', () => { fixture.detectChanges(); }); - it('should init workflowitem properly', (done) => { - component.workflowitemRD$.subscribe((workflowitemRD) => { - expect(linkService.resolveLinks).toHaveBeenCalledWith( - component.dso, - jasmine.objectContaining({ name: 'workflowitem' }), - jasmine.objectContaining({ name: 'action' }) - ); - expect(workflowitemRD.payload).toEqual(workflowitem); - done(); - }); - }); + it('should init workflowitem properly', fakeAsync(() => { + flush(); + expect(linkService.resolveLinks).toHaveBeenCalledWith( + component.dso, + jasmine.objectContaining({ name: 'workflowitem' }), + jasmine.objectContaining({ name: 'action' }) + ); + expect(component.workflowitem$.value).toEqual(workflowitem); + expect(component.item$.value).toEqual(item); + })); it('should have properly status', () => { expect(component.status).toEqual(MyDspaceItemStatusType.WAITING_CONTROLLER); diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts index df27abd42e..6dec14f9cb 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component.ts @@ -1,16 +1,24 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; +import { mergeMap, tap } from 'rxjs/operators'; -import { Observable } from 'rxjs'; import { RemoteData } from '../../../../core/data/remote-data'; import { PoolTask } from '../../../../core/tasks/models/pool-task-object.model'; import { SearchResultDetailElementComponent } from '../search-result-detail-element.component'; -import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; +import { + MyDspaceItemStatusType +} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { PoolTaskSearchResult } from '../../../object-collection/shared/pool-task-search-result.model'; import { followLink } from '../../../utils/follow-link-config.model'; import { LinkService } from '../../../../core/cache/builders/link.service'; +import { Item } from '../../../../core/shared/item.model'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { isNotEmpty } from '../../../empty.util'; +import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; /** * This component renders pool task object for the search result in the detail view. @@ -22,7 +30,12 @@ import { LinkService } from '../../../../core/cache/builders/link.service'; }) @listableObjectComponent(PoolTaskSearchResult, ViewMode.DetailedListElement) -export class PoolSearchResultDetailElementComponent extends SearchResultDetailElementComponent { +export class PoolSearchResultDetailElementComponent extends SearchResultDetailElementComponent implements OnInit, OnDestroy { + + /** + * The item object that belonging to the result object + */ + public item$: BehaviorSubject = new BehaviorSubject(null); /** * A boolean representing if to show submitter information @@ -37,9 +50,9 @@ export class PoolSearchResultDetailElementComponent extends SearchResultDetailEl /** * The workflowitem object that belonging to the result object */ - public workflowitemRD$: Observable>; + public workflowitem$: BehaviorSubject = new BehaviorSubject(null); - constructor(protected linkService: LinkService) { + constructor(protected linkService: LinkService, protected objectCache: ObjectCacheService) { super(); } @@ -52,7 +65,31 @@ export class PoolSearchResultDetailElementComponent extends SearchResultDetailEl followLink('item', {}, followLink('bundles')), followLink('submitter') ), followLink('action')); - this.workflowitemRD$ = this.dso.workflowitem as Observable>; + + (this.dso.workflowitem as Observable>).pipe( + getFirstCompletedRemoteData(), + mergeMap((wfiRD: RemoteData) => { + if (wfiRD.hasSucceeded) { + this.workflowitem$.next(wfiRD.payload); + return (wfiRD.payload.item as Observable>).pipe( + getFirstCompletedRemoteData() + ); + } else { + return EMPTY; + } + }), + tap((itemRD: RemoteData) => { + if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { + this.item$.next(itemRD.payload); + } + }) + ).subscribe(); + + } + + ngOnDestroy() { + // This ensures the object is removed from cache, when action is performed on task + this.objectCache.remove(this.dso._links.workflowitem.href); } } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html index 2cee94ce20..5e98b00926 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html @@ -1,18 +1,15 @@ - + + -
-
- +
+
+ +
-
- - - - - + diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts index c5383a2e31..4ea716cc2a 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.spec.ts @@ -104,6 +104,7 @@ describe('ClaimedSearchResultListElementComponent', () => { jasmine.objectContaining({ name: 'action' }) ); expect(component.workflowitem$.value).toEqual(workflowitem); + expect(component.item$.value).toEqual(item); })); it('should have properly status', () => { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts index c1458043a7..237a5f516e 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.ts @@ -8,7 +8,7 @@ import { TruncatableService } from '../../../truncatable/truncatable.service'; import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; import { RemoteData } from '../../../../core/data/remote-data'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { followLink } from '../../../utils/follow-link-config.model'; @@ -20,6 +20,9 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { Item } from '../../../../core/shared/item.model'; +import { mergeMap, tap } from 'rxjs/operators'; +import { isNotEmpty } from '../../../empty.util'; @Component({ selector: 'ds-claimed-search-result-list-element', @@ -39,6 +42,11 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle */ public status = MyDspaceItemStatusType.VALIDATION; + /** + * The item object that belonging to the result object + */ + public item$: BehaviorSubject = new BehaviorSubject(null); + /** * The workflowitem object that belonging to the result object */ @@ -65,15 +73,29 @@ export class ClaimedSearchResultListElementComponent extends SearchResultListEle ngOnInit() { super.ngOnInit(); this.linkService.resolveLinks(this.dso, followLink('workflowitem', {}, - followLink('item'), followLink('submitter') + followLink('item', {}, followLink('bundles')), + followLink('submitter') ), followLink('action')); + (this.dso.workflowitem as Observable>).pipe( - getFirstCompletedRemoteData() - ).subscribe((wfiRD: RemoteData) => { - if (wfiRD.hasSucceeded) { - this.workflowitem$.next(wfiRD.payload); - } - }); + getFirstCompletedRemoteData(), + mergeMap((wfiRD: RemoteData) => { + if (wfiRD.hasSucceeded) { + this.workflowitem$.next(wfiRD.payload); + return (wfiRD.payload.item as Observable>).pipe( + getFirstCompletedRemoteData() + ); + } else { + return EMPTY; + } + }), + tap((itemRD: RemoteData) => { + if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { + this.item$.next(itemRD.payload); + } + }) + ).subscribe(); + this.showThumbnails = this.appConfig.browseBy.showThumbnails; } diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html index 6a6e729dea..4f0d6f774a 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html @@ -1,12 +1,15 @@ - -
-
- + + +
+
+ +
-
+ diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts index ab5652138e..0e5b2eaa6f 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.spec.ts @@ -110,6 +110,7 @@ describe('PoolSearchResultListElementComponent', () => { jasmine.objectContaining({ name: 'action' }) ); expect(component.workflowitem$.value).toEqual(workflowitem); + expect(component.item$.value).toEqual(item); })); it('should have properly status', () => { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index a42b3d04d6..046763eb98 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -1,6 +1,7 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; +import { mergeMap, tap } from 'rxjs/operators'; import { ViewMode } from '../../../../core/shared/view-mode.model'; import { RemoteData } from '../../../../core/data/remote-data'; @@ -21,6 +22,8 @@ import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { Item } from '../../../../core/shared/item.model'; +import { isNotEmpty } from '../../../empty.util'; /** * This component renders pool task object for the search result in the list view. @@ -44,6 +47,11 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen */ public status = MyDspaceItemStatusType.WAITING_CONTROLLER; + /** + * The item object that belonging to the result object + */ + public item$: BehaviorSubject = new BehaviorSubject(null); + /** * The workflowitem object that belonging to the result object */ @@ -75,15 +83,29 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen ngOnInit() { super.ngOnInit(); this.linkService.resolveLinks(this.dso, followLink('workflowitem', {}, - followLink('item'), followLink('submitter') + followLink('item', {}, followLink('bundles')), + followLink('submitter') ), followLink('action')); + (this.dso.workflowitem as Observable>).pipe( - getFirstCompletedRemoteData() - ).subscribe((wfiRD: RemoteData) => { - if (wfiRD.hasSucceeded) { - this.workflowitem$.next(wfiRD.payload); - } - }); + getFirstCompletedRemoteData(), + mergeMap((wfiRD: RemoteData) => { + if (wfiRD.hasSucceeded) { + this.workflowitem$.next(wfiRD.payload); + return (wfiRD.payload.item as Observable>).pipe( + getFirstCompletedRemoteData() + ); + } else { + return EMPTY; + } + }), + tap((itemRD: RemoteData) => { + if (isNotEmpty(itemRD) && itemRD.hasSucceeded) { + this.item$.next(itemRD.payload); + } + }) + ).subscribe(); + this.showThumbnails = this.appConfig.browseBy.showThumbnails; } From 3b7a830ffea68993aaf168f60dc6d78f3621ef69 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Tue, 4 Oct 2022 15:37:04 +0200 Subject: [PATCH 61/78] [CST-6782] Hide cookie settings when verification is disabled --- .../shared/cookies/browser-klaro.service.ts | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/app/shared/cookies/browser-klaro.service.ts b/src/app/shared/cookies/browser-klaro.service.ts index cc0f26ff84..c6819012d9 100644 --- a/src/app/shared/cookies/browser-klaro.service.ts +++ b/src/app/shared/cookies/browser-klaro.service.ts @@ -81,24 +81,31 @@ export class BrowserKlaroService extends KlaroService { this.klaroConfig.translations.en.consentNotice.description = 'cookies.consent.content-notice.description.no-privacy'; } - const servicesToHide$: Observable = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe( + const hideGoogleAnalytics$ = this.configService.findByPropertyName(this.GOOGLE_ANALYTICS_KEY).pipe( getFirstCompletedRemoteData(), - map(remoteData => { - if (!remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values)) { - return [this.GOOGLE_ANALYTICS_SERVICE_NAME]; - } else { - return []; - } - }), + map(remoteData => !remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values)), ); - this.configService.findByPropertyName(this.REGISTRATION_VERIFICATION_ENABLED_KEY).pipe( + const hideRegistrationVerification$ = this.configService.findByPropertyName(this.REGISTRATION_VERIFICATION_ENABLED_KEY).pipe( getFirstCompletedRemoteData(), - ).subscribe((remoteData) => { - if (remoteData.statusCode === 404 || isEmpty(remoteData.payload?.values) || remoteData.payload.values[0].toLowerCase() !== 'true') { - this.klaroConfig.services = klaroConfiguration.services.filter(config => config.name !== CAPTCHA_NAME); - } - }); + map((remoteData) => + !remoteData.hasSucceeded || !remoteData.payload || isEmpty(remoteData.payload.values) || remoteData.payload.values[0].toLowerCase() !== 'true' + ), + ); + + const servicesToHide$: Observable = observableCombineLatest([hideGoogleAnalytics$, hideRegistrationVerification$]).pipe( + map(([hideGoogleAnalytics, hideRegistrationVerification]) => { + let servicesToHideArray: string[] = []; + if (hideGoogleAnalytics) { + servicesToHideArray.push(this.GOOGLE_ANALYTICS_SERVICE_NAME); + } + if (hideRegistrationVerification) { + servicesToHideArray.push(CAPTCHA_NAME); + } + console.log(servicesToHideArray); + return servicesToHideArray; + }) + ); this.translateService.setDefaultLang(environment.defaultLanguage); From c47405cfe12178d294380a229943119b2073bb06 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 4 Oct 2022 20:35:14 +0200 Subject: [PATCH 62/78] [CST-6876] Fix issue for which submitter disappear when workflow state were changed --- .../item-submitter.component.spec.ts | 8 +++++--- .../mydspace-item-submitter/item-submitter.component.ts | 9 +++++++++ .../pool-search-result-list-element.component.ts | 1 - 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.spec.ts b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.spec.ts index 261759dfe6..227db9ec82 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.spec.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.spec.ts @@ -11,12 +11,11 @@ import { EPersonMock } from '../../../testing/eperson.mock'; import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; import { By } from '@angular/platform-browser'; import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; +import { LinkService } from '../../../../core/cache/builders/link.service'; +import { getMockLinkService } from '../../../mocks/link-service.mock'; let component: ItemSubmitterComponent; let fixture: ComponentFixture; - -const compIndex = 1; - let mockResultObject: PoolTask; const rdSumbitter = createSuccessfulRemoteDataObject(EPersonMock); @@ -36,6 +35,9 @@ describe('ItemSubmitterComponent', () => { }) ], declarations: [ItemSubmitterComponent], + providers: [ + { provide: LinkService, useValue: getMockLinkService() }, + ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(ItemSubmitterComponent, { set: { changeDetection: ChangeDetectionStrategy.Default } diff --git a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts index 75927f6629..fb644017db 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.ts @@ -8,6 +8,8 @@ import { RemoteData } from '../../../../core/data/remote-data'; import { isNotEmpty } from '../../../empty.util'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { LinkService } from '../../../../core/cache/builders/link.service'; +import { followLink } from '../../../utils/follow-link-config.model'; /** * This component represents a badge with submitter information. @@ -29,10 +31,17 @@ export class ItemSubmitterComponent implements OnInit { */ submitter$: Observable; + public constructor(protected linkService: LinkService) { + + } + /** * Initialize submitter object */ ngOnInit() { + this.linkService.resolveLinks(this.object, followLink('workflowitem', {}, + followLink('submitter',{}) + )); this.submitter$ = (this.object.workflowitem as Observable>).pipe( getFirstCompletedRemoteData(), mergeMap((rd: RemoteData) => { diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts index 046763eb98..cb924af40f 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.ts @@ -111,7 +111,6 @@ export class PoolSearchResultListElementComponent extends SearchResultListElemen ngOnDestroy() { // This ensures the object is removed from cache, when action is performed on task - // this.wfiService.invalidateByHref(this.dso._links.workflowitem.href); this.objectCache.remove(this.dso._links.workflowitem.href); } } From c55c15b6f60a84a3758f58fed538eaaf8ce1d5f7 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 5 Oct 2022 18:27:09 +0200 Subject: [PATCH 63/78] fix issue where deleting a member from a group would cause an infinite re-request loop --- .../group-form/members-list/members-list.component.ts | 2 -- src/app/core/eperson/group-data.service.spec.ts | 8 ++------ src/app/core/eperson/group-data.service.ts | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index 5c02e02d78..169d009d63 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -209,7 +209,6 @@ export class MembersListComponent implements OnInit, OnDestroy { if (activeGroup != null) { const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson); this.showNotifications('deleteMember', response, ePerson.eperson.name, activeGroup); - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); } else { this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup')); } @@ -315,7 +314,6 @@ export class MembersListComponent implements OnInit, OnDestroy { response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject })); - this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href); } else { this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject })); } diff --git a/src/app/core/eperson/group-data.service.spec.ts b/src/app/core/eperson/group-data.service.spec.ts index 6fdc302cff..b424aed1aa 100644 --- a/src/app/core/eperson/group-data.service.spec.ts +++ b/src/app/core/eperson/group-data.service.spec.ts @@ -191,9 +191,7 @@ describe('GroupDataService', () => { callback(); expect(objectCache.getByHref).toHaveBeenCalledWith(EPersonMock2._links.self.href); - expect(objectCache.getByHref).toHaveBeenCalledWith(GroupMock._links.self.href); - expect(requestService.setStaleByUUID).toHaveBeenCalledTimes(4); - expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request1'); + expect(requestService.setStaleByUUID).toHaveBeenCalledTimes(2); expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request2'); }); }); @@ -218,9 +216,7 @@ describe('GroupDataService', () => { callback(); expect(objectCache.getByHref).toHaveBeenCalledWith(EPersonMock._links.self.href); - expect(objectCache.getByHref).toHaveBeenCalledWith(GroupMock._links.self.href); - expect(requestService.setStaleByUUID).toHaveBeenCalledTimes(4); - expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request1'); + expect(requestService.setStaleByUUID).toHaveBeenCalledTimes(2); expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request2'); }); }); diff --git a/src/app/core/eperson/group-data.service.ts b/src/app/core/eperson/group-data.service.ts index 77a4add159..bb38e46758 100644 --- a/src/app/core/eperson/group-data.service.ts +++ b/src/app/core/eperson/group-data.service.ts @@ -179,7 +179,6 @@ export class GroupDataService extends IdentifiableDataService implements return this.rdbService.buildFromRequestUUIDAndAwait(requestId, () => observableZip( this.invalidateByHref(ePerson._links.self.href), - this.invalidateByHref(activeGroup._links.self.href), this.requestService.setStaleByHrefSubstring(ePerson._links.groups.href).pipe(take(1)), this.requestService.setStaleByHrefSubstring(activeGroup._links.epersons.href).pipe(take(1)), )); @@ -198,7 +197,6 @@ export class GroupDataService extends IdentifiableDataService implements return this.rdbService.buildFromRequestUUIDAndAwait(requestId, () => observableZip( this.invalidateByHref(ePerson._links.self.href), - this.invalidateByHref(activeGroup._links.self.href), this.requestService.setStaleByHrefSubstring(ePerson._links.groups.href).pipe(take(1)), this.requestService.setStaleByHrefSubstring(activeGroup._links.epersons.href).pipe(take(1)), )); From 8cc144cb732b7939d91e63e6ab54ab1bf916fb16 Mon Sep 17 00:00:00 2001 From: lucaszc Date: Wed, 5 Oct 2022 14:54:51 -0300 Subject: [PATCH 64/78] Misspellings corrections in pt-BR Misspellings corrections in portuguese brazil --- src/assets/i18n/pt-BR.json5 | 110 ++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index a345d081dd..dae60803f5 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -341,7 +341,7 @@ "admin.access-control.epeople.actions.delete": "Apagar EPerson", // "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", - "admin.access-control.epeople.actions.impersonate": "Assumir o papel EPerson", + "admin.access-control.epeople.actions.impersonate": "Assumir o papel do EPerson", // "admin.access-control.epeople.actions.reset": "Reset password", "admin.access-control.epeople.actions.reset": "Redefinir senha", @@ -423,7 +423,7 @@ "admin.access-control.epeople.form.emailHint": "Deve ter um e-mail válido", // "admin.access-control.epeople.form.canLogIn": "Can log in", - "admin.access-control.epeople.form.canLogIn": "Pode logar", + "admin.access-control.epeople.form.canLogIn": "Pode entrar", // "admin.access-control.epeople.form.requireCertificate": "Requires certificate", @@ -454,7 +454,7 @@ "admin.access-control.epeople.form.notification.deleted.success": "EPerson \"{{name}}\" apagado com sucesso", // "admin.access-control.epeople.form.notification.deleted.failure": "Failed to delete EPerson \"{{name}}\"", - "admin.access-control.epeople.form.notification.deleted.failure": "Falha ao deletar EPerson \"{{name}}\"", + "admin.access-control.epeople.form.notification.deleted.failure": "Falha ao apagar EPerson \"{{name}}\"", // "admin.access-control.epeople.form.groupsEPersonIsMemberOf": "Member of these groups:", @@ -545,7 +545,7 @@ "admin.access-control.groups.notification.deleted.success": "Grupo \"{{name}}\" apagado com sucesso", // "admin.access-control.groups.notification.deleted.failure.title": "Failed to delete group \"{{name}}\"", - "admin.access-control.groups.notification.deleted.failure.title": "Falha ao deletar o grupo \"{{name}}\"", + "admin.access-control.groups.notification.deleted.failure.title": "Falha ao apagar o grupo \"{{name}}\"", // "admin.access-control.groups.notification.deleted.failure.content": "Cause: \"{{cause}}\"", "admin.access-control.groups.notification.deleted.failure.content": "Causa: \"{{cause}}\"", @@ -554,7 +554,7 @@ "admin.access-control.groups.form.alert.permanent": "Este grupo é permanente, portanto, não pode ser editado ou excluído. Você ainda pode adicionar e remover membros do grupo usando esta página.", // "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.alert.workflowGroup": "Este grupo não pode ser modificado ou excluído porque corresponde a uma função no processo de submissão e workflow \"{{name}}\" {{comcol}}. Você não pode deletar ele da função atribuída: tab on the edit {{comcol}} page. You can still add and remove group members using this page.", + "admin.access-control.groups.form.alert.workflowGroup": "Este grupo não pode ser modificado ou excluído porque corresponde a uma função no processo de submissão e workflow \"{{name}}\" {{comcol}}. Você não pode apagar ele da função atribuída: 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", @@ -596,7 +596,7 @@ "admin.access-control.groups.form.delete-group.modal.header": "Apagar Grupo \"{{ dsoName }}\"", // "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.info": "Você tem certeza que quer deletar o Grupo \"{{ dsoName }}\"", + "admin.access-control.groups.form.delete-group.modal.info": "Você tem certeza que quer apagar o Grupo \"{{ dsoName }}\"", // "admin.access-control.groups.form.delete-group.modal.cancel": "Cancel", "admin.access-control.groups.form.delete-group.modal.cancel": "Cancelar", @@ -839,7 +839,7 @@ "auth.messages.token-refresh-failed": "Falha ao atualizar seu token de sessão. Por favor faça login novamente.", // "bitstream.download.page": "Now downloading {{bitstream}}..." , - "bitstream.download.page": "Agora downloading {{bitstream}}..." , + "bitstream.download.page": "Baixando {{bitstream}} agora ..." , // "bitstream.download.page.back": "Back" , "bitstream.download.page.back": "Voltar" , @@ -922,7 +922,7 @@ // "bitstream.edit.notifications.saved.content": "Your changes to this bitstream were saved.", - "bitstream.edit.notifications.saved.content": "Suas alterações neste fluxo de bits foram salvas.", + "bitstream.edit.notifications.saved.content": "Suas alterações neste bitstream foram salvas.", // "bitstream.edit.notifications.saved.title": "Bitstream saved", "bitstream.edit.notifications.saved.title": "Bitstream salvo", @@ -981,10 +981,10 @@ "bitstream-request-a-copy.submit": "Solicitar cópia", // "bitstream-request-a-copy.submit.success": "The item request was submitted successfully.", - "bitstream-request-a-copy.submit.success": "A solicitação de item foi enviada com sucesso.", + "bitstream-request-a-copy.submit.success": "A solicitação do item foi enviada com sucesso.", // "bitstream-request-a-copy.submit.error": "Something went wrong with submitting the item request.", - "bitstream-request-a-copy.submit.error": "Ocorreu um erro ao enviar a solicitação de item.", + "bitstream-request-a-copy.submit.error": "Ocorreu um erro ao enviar a solicitação do item.", // "browse.back.all-results": "All browse results", "browse.back.all-results": "Todos os resultados", @@ -1098,7 +1098,7 @@ "browse.startsWith.months.september": "Setembro", // "browse.startsWith.submit": "Browse", - "browse.startsWith.submit": "Ir", + "browse.startsWith.submit": "Pesquisar", // "browse.startsWith.type_date": "Filter results by date", "browse.startsWith.type_date": "Filtrar bresultados pela data", @@ -1146,7 +1146,7 @@ "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.notification.success": "Coleção apagada 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 }}?\"", @@ -1203,7 +1203,7 @@ "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.content": "Removeu os mapeamentos 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", @@ -1368,7 +1368,7 @@ "collection.edit.template.loading": "Carregando o template de item...", // "collection.edit.template.notifications.delete.error": "Failed to delete the item template", - "collection.edit.template.notifications.delete.error": "Falha ao deletar o item do template", + "collection.edit.template.notifications.delete.error": "Falha ao apagar o item do template", // "collection.edit.template.notifications.delete.success": "Successfully deleted the item template", "collection.edit.template.notifications.delete.success": "Item do template apagado com sucesso", @@ -1463,13 +1463,13 @@ // "collection.source.controls.reset.submit.error": "Something went wrong with initiating the reset and reimport", "collection.source.controls.reset.submit.error": "Alguma coisa errada durante a inicialização da redefinição e reimportação", // "collection.source.controls.reset.failed": "An error occurred during the reset and reimport", - "collection.source.controls.reset.failed": "Um erro ocorreru durante a redifinição e reimportação", + "collection.source.controls.reset.failed": "Um erro ocorreu durante a redifinição e reimportação", // "collection.source.controls.reset.completed": "The reset and reimport completed", - "collection.source.controls.reset.completed": "Completou o reset e a reimportação", + "collection.source.controls.reset.completed": "Completou a redefinição e a reimportação", // "collection.source.controls.reset.submit": "Reset and reimport", - "collection.source.controls.reset.submit": "Resetar e reimportar", + "collection.source.controls.reset.submit": "Redefinir e reimportar", // "collection.source.controls.reset.running": "Resetting and reimporting...", - "collection.source.controls.reset.running": "Resetando e importando...", + "collection.source.controls.reset.running": "Redefinindo e importando...", // "collection.source.controls.harvest.status": "Harvest status:", "collection.source.controls.harvest.status": "Status da Colheita (Harvest):", // "collection.source.controls.harvest.start": "Harvest start time:", @@ -1551,10 +1551,10 @@ "community.edit.logo.label": "Logo da Comunidade", // "community.edit.logo.notifications.add.error": "Uploading Community logo failed. Please verify the content before retrying.", - "community.edit.logo.notifications.add.error": "Erro no Upload do Logo da Communidade. Por favor verifique o conteúdo antes de tentar novamente.", + "community.edit.logo.notifications.add.error": "Erro no envio do Logo da Communidade. Por favor verifique o conteúdo antes de tentar novamente.", // "community.edit.logo.notifications.add.success": "Upload Community logo successful.", - "community.edit.logo.notifications.add.success": "Sucesso no Upload do logo da Comunidade.", + "community.edit.logo.notifications.add.success": "Sucesso no envio do logo da Comunidade.", // "community.edit.logo.notifications.delete.success.title": "Logo deleted", "community.edit.logo.notifications.delete.success.title": "Logo apagado", @@ -1614,7 +1614,7 @@ "comcol-role.edit.create": "Criar", // "comcol-role.edit.create.error.title": "Failed to create a group for the '{{ role }}' role", - "comcol-role.edit.create.error.title": "Falha ao criar o grupo para a '{{ role }}' role", + "comcol-role.edit.create.error.title": "Falha ao criar o grupo '{{ role }}' para a função", // "comcol-role.edit.restrict": "Restrict", "comcol-role.edit.restrict": "Restrito", @@ -1623,7 +1623,7 @@ "comcol-role.edit.delete": "Apagar", // "comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group", - "comcol-role.edit.delete.error.title": "Falha ao deletar a '{{ role }}' role's para o grupo", + "comcol-role.edit.delete.error.title": "Falha ao apagar a(s) funções '{{ role }}' para o grupo", // "comcol-role.edit.community-admin.name": "Administrators", "comcol-role.edit.community-admin.name": "Administradores", @@ -2045,7 +2045,7 @@ "error.validation.NotValidEmail": "Este E-mail não é válido", // "error.validation.emailTaken": "This E-mail is already taken", - "error.validation.emailTaken": "Este e-mail já está registrado", + "error.validation.emailTaken": "Este E-mail já está registrado", // "error.validation.groupExists": "This group already exists", "error.validation.groupExists": "Este Grupo já existe", @@ -2243,7 +2243,7 @@ "grant-deny-request-copy.email.back": "Voltar", // "grant-deny-request-copy.email.message": "Message", - "grant-deny-request-copy.email.message": "Menssagem", + "grant-deny-request-copy.email.message": "Mensagem", // "grant-deny-request-copy.email.message.empty": "Please enter a message", "grant-deny-request-copy.email.message.empty": "Por favor coloque uma mensagem", @@ -2252,7 +2252,7 @@ "grant-deny-request-copy.email.permissions.info": "Você pode aproveitar esta ocasião para reconsiderar as restrições de acesso ao documento, para evitar ter que responder a essas solicitações. Se você quiser pedir aos administradores do repositório para remover essas restrições, marque a caixa abaixo.", // "grant-deny-request-copy.email.permissions.label": "Change to open access", - "grant-deny-request-copy.email.permissions.label": "Trocar para acesso aberto", + "grant-deny-request-copy.email.permissions.label": "Trocar para acesso aberto (open access)", // "grant-deny-request-copy.email.send": "Send", "grant-deny-request-copy.email.send": "Enviar", @@ -2499,10 +2499,10 @@ "item.bitstreams.upload.notifications.upload.failed": "Falha no Upload. Por favor verifique o conteúdo antes de tentar novamente.", // "item.bitstreams.upload.title": "Upload bitstream", - "item.bitstreams.upload.title": "Upload bitstream", + "item.bitstreams.upload.title": "Enviar bitstream", // "item.edit.bitstreams.bundle.edit.buttons.upload": "Upload", - "item.edit.bitstreams.bundle.edit.buttons.upload": "Upload", + "item.edit.bitstreams.bundle.edit.buttons.upload": "Enviar", // "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", "item.edit.bitstreams.bundle.displaying": "Mostrando atualmente {{ amount }} bitstreams de {{ total }}.", @@ -2520,7 +2520,7 @@ "item.edit.bitstreams.discard-button": "Descartar", // "item.edit.bitstreams.edit.buttons.download": "Download", - "item.edit.bitstreams.edit.buttons.download": "Download", + "item.edit.bitstreams.edit.buttons.download": "Baixar", // "item.edit.bitstreams.edit.buttons.drag": "Drag", "item.edit.bitstreams.edit.buttons.drag": "Arraste", @@ -2535,7 +2535,7 @@ "item.edit.bitstreams.edit.buttons.undo": "Desfazer mudanças", // "item.edit.bitstreams.empty": "This item doesn't contain any bitstreams. Click the upload button to create one.", - "item.edit.bitstreams.empty": "Este item não possui nenhum bitstream. Clique no botão de upload e envie um novo.", + "item.edit.bitstreams.empty": "Este item não possui nenhum bitstream. Clique no botão de envio e envie um novo.", // "item.edit.bitstreams.headers.actions": "Actions", "item.edit.bitstreams.headers.actions": "Ações", @@ -2589,7 +2589,7 @@ "item.edit.bitstreams.save-button": "Salvar", // "item.edit.bitstreams.upload-button": "Upload", - "item.edit.bitstreams.upload-button": "Upload", + "item.edit.bitstreams.upload-button": "Enviar", // "item.edit.delete.cancel": "Cancel", "item.edit.delete.cancel": "Cancelar", @@ -3094,7 +3094,7 @@ "item.page.filesection.description": "Descrição:", // "item.page.filesection.download": "Download", - "item.page.filesection.download": "Download", + "item.page.filesection.download": "Baixar", // "item.page.filesection.format": "Format:", "item.page.filesection.format": "Formato:", @@ -3106,7 +3106,7 @@ "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.journal.search.title": "Artigos nesta revista", // "item.page.link.full": "Full item page", "item.page.link.full": "Página do item completo", @@ -3163,7 +3163,7 @@ "item.page.return": "Voltar", // "item.page.version.create": "Create new version", - "item.page.version.create": "Criar noca versão", + "item.page.version.create": "Criar nova versão", // "item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", "item.page.version.hasDraft": "Não é possível criar uma nova versão porque há uma submissão em andamento no histórico de versões", @@ -3190,7 +3190,7 @@ "item.preview.dc.identifier.other": "Outro identificador:", // "item.preview.dc.language.iso": "Language:", - "item.preview.dc.language.iso": "Linguagem:", + "item.preview.dc.language.iso": "Idioma:", // "item.preview.dc.subject": "Subjects:", "item.preview.dc.subject": "Assuntos:", @@ -3253,7 +3253,7 @@ "item.select.confirm": "Confirmar seleção", // "item.select.empty": "No items to show", - "item.select.empty": "Nenhum itme a mostrar", + "item.select.empty": "Nenhum item a mostrar", // "item.select.table.author": "Author", "item.select.table.author": "Autor", @@ -3319,10 +3319,10 @@ "item.version.history.table.action.newVersion": "Criar nova versão a partir desta", // "item.version.history.table.action.deleteVersion": "Delete version", - "item.version.history.table.action.deleteVersion": "Deletar versão", + "item.version.history.table.action.deleteVersion": "Apagar versão", // "item.version.history.table.action.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", - "item.version.history.table.action.hasDraft": "Não é possível criar uma nova versão porque há um envio em andamento no histórico de versões", + "item.version.history.table.action.hasDraft": "Não é possível criar uma nova versão porque há uma submissão em andamento no histórico de versões", // "item.version.notice": "This is not the latest version of this item. The latest version can be found here.", "item.version.notice": "Esta não é a versão mais recente deste item. A versão mais recente pode ser encontrada aqui.", @@ -3400,7 +3400,7 @@ "item.version.edit.notification.failure" : "O resumo da versão número {{version}} não foi alterado", // "journal.listelement.badge": "Journal", - "journal.listelement.badge": "Periódico", + "journal.listelement.badge": "Revista", // "journal.page.description": "Description", "journal.page.description": "Descrição", @@ -3418,16 +3418,16 @@ "journal.page.publisher": "Editora", // "journal.page.titleprefix": "Journal: ", - "journal.page.titleprefix": "Periódico: ", + "journal.page.titleprefix": "Revista: ", // "journal.search.results.head": "Journal Search Results", - "journal.search.results.head": "Resultado da Busca de Periódicos", + "journal.search.results.head": "Resultado da Busca de Revistas", // "journal-relationships.search.results.head": "Journal Search Results", - "journal-relationships.search.results.head": "Resultados da Busca de Periódicos", + "journal-relationships.search.results.head": "Resultados da Busca de Revistas", // "journal.search.title": "Journal Search", - "journal.search.title": "Busca de Periódicos", + "journal.search.title": "Busca de Revistas", // "journalissue.listelement.badge": "Journal Issue", "journalissue.listelement.badge": "Fascículo", @@ -3442,10 +3442,10 @@ "journalissue.page.issuedate": "Data de Publicação", // "journalissue.page.journal-issn": "Journal ISSN", - "journalissue.page.journal-issn": "ISSN do Periódico", + "journalissue.page.journal-issn": "ISSN da Revista", // "journalissue.page.journal-title": "Journal Title", - "journalissue.page.journal-title": "Título do Periódico", + "journalissue.page.journal-title": "Título da Revista", // "journalissue.page.keyword": "Keywords", "journalissue.page.keyword": "Palavras-chave", @@ -3457,7 +3457,7 @@ "journalissue.page.titleprefix": "Fascículo: ", // "journalvolume.listelement.badge": "Journal Volume", - "journalvolume.listelement.badge": "Volume do Periódico", + "journalvolume.listelement.badge": "Volume da Revista", // "journalvolume.page.description": "Description", "journalvolume.page.description": "Descrição", @@ -3469,7 +3469,7 @@ "journalvolume.page.issuedate": "Data de Publicação", // "journalvolume.page.titleprefix": "Journal Volume: ", - "journalvolume.page.titleprefix": "Volume do Periódico: ", + "journalvolume.page.titleprefix": "Volume da Revista: ", // "journalvolume.page.volume": "Volume", "journalvolume.page.volume": "Volume", @@ -3997,7 +3997,7 @@ "nav.statistics.header": "Estatísticas", // "nav.stop-impersonating": "Stop impersonating EPerson", - "nav.stop-impersonating": "Deixar de assumir o papel de EPerson", + "nav.stop-impersonating": "Deixar de assumir o papel do EPerson", // "nav.toggle" : "Toggle navigation", "nav.toggle" : "Alternar navegação", @@ -4409,10 +4409,10 @@ "publication.page.edit": "Editar este item", // "publication.page.journal-issn": "Journal ISSN", - "publication.page.journal-issn": "ISSN do Periódico", + "publication.page.journal-issn": "ISSN da Revista", // "publication.page.journal-title": "Journal Title", - "publication.page.journal-title": "Título do Periódico", + "publication.page.journal-title": "Título da Revista", // "publication.page.publisher": "Publisher", "publication.page.publisher": "Editora", @@ -4469,7 +4469,7 @@ "register-page.create-profile.identification.contact": "Telefone de Contato", // "register-page.create-profile.identification.language": "Language", - "register-page.create-profile.identification.language": "Linguagem", + "register-page.create-profile.identification.language": "Idioma", // "register-page.create-profile.security.header": "Security", "register-page.create-profile.security.header": "Segurança", @@ -4562,7 +4562,7 @@ "relationships.isJournalIssueOf": "Fascículo", // "relationships.isJournalOf": "Journals", - "relationships.isJournalOf": "Periódicos", + "relationships.isJournalOf": "Revistas", // "relationships.isOrgUnitOf": "Organizational Units", "relationships.isOrgUnitOf": "Unidades Organizacionais", @@ -4580,13 +4580,13 @@ "relationships.isPublicationOfJournalIssue": "Artigos", // "relationships.isSingleJournalOf": "Journal", - "relationships.isSingleJournalOf": "Periódico", + "relationships.isSingleJournalOf": "Revista", // "relationships.isSingleVolumeOf": "Journal Volume", - "relationships.isSingleVolumeOf": "Volume do Periódico", + "relationships.isSingleVolumeOf": "Volume da Revista", // "relationships.isVolumeOf": "Journal Volumes", - "relationships.isVolumeOf": "Volumes do Periódico", + "relationships.isVolumeOf": "Volumes da Revista", // "relationships.isContributorOf": "Contributors", "relationships.isContributorOf": "Contribuidores", @@ -5198,7 +5198,7 @@ "statistics.table.no-data": "Nenhum dado disponível", // "statistics.table.title.TotalVisits": "Total visits", - "statistics.table.title.TotalVisits": "Total visitas", + "statistics.table.title.TotalVisits": "Total de visitas", // "statistics.table.title.TotalVisitsPerMonth": "Total visits per month", "statistics.table.title.TotalVisitsPerMonth": "Total visitas por mês", From acc32bf77dc4f9ca0c91be443af6c4cb553a7437 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 5 Oct 2022 14:50:25 -0700 Subject: [PATCH 65/78] Hiding thumbnail in modal. --- .../virtual-metadata.component.html | 2 +- .../virtual-metadata.component.ts | 10 +++++- src/styles/_global-styles.scss | 36 +++++++++++++------ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html b/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html index c103d83c71..b83b93d8f1 100644 --- a/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html +++ b/src/app/item-page/edit-item-page/virtual-metadata/virtual-metadata.component.html @@ -1,4 +1,4 @@ -
+
- +
From 603c60f36865ecf258e654e01dc2590a1f61c6e4 Mon Sep 17 00:00:00 2001 From: Lucas Zinato Carraro Date: Wed, 12 Oct 2022 11:38:18 -0300 Subject: [PATCH 74/78] Add missing translations in version 7.4 pt-BR Add missing keywords, and correct translations in pt-BR --- src/assets/i18n/pt-BR.json5 | 53 ++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index dae60803f5..7d4c49fa2d 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -745,6 +745,33 @@ // "admin.access-control.groups.form.return": "Back", "admin.access-control.groups.form.return": "Voltar", + //"admin.batch-import.breadcrumbs": "Import Batch", + "admin.batch-import.breadcrumbs": "Importar um Lote", + + //"admin.batch-import.title": "Import Batch", + "admin.batch-import.title": "Importar um Lote", + + //"admin.batch-import.page.header": "Import Batch", + "admin.batch-import.page.header": "Importar um Lote", + + //"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.batch-import.page.help": "Selecione a Coleção para o qual deseja importar. Arraste ou selecione um arquivo zip no formato Simple Archive Format (SAF) que inclua os Items para importar", + + //"admin.batch-import.page.dropMsg": "Drop a batch ZIP to import", + "admin.batch-import.page.dropMsg": "Arraste e solte um lote ZIP para importar", + + //"admin.batch-import.page.dropMsgReplace": "Drop to replace the batch ZIP to import", + "admin.batch-import.page.dropMsgReplace": "Arraste e solte um lote ZIP para substituir o lote para importar", + + //"admin.batch-import.page.error.addFile": "Select Zip file first!", + "admin.batch-import.page.error.addFile": "Selecione um arquivo ZIP primeiro!", + + //"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.validateOnly.hint": "Quando selecionado , o ZIP enviado sera validado. Você receberá um relatório das alterações detectadas, mas nenhuma alteração será salva.", + + //"admin.batch-import.page.remove": "remove", + "admin.batch-import.page.remove": "remover", + // "admin.search.breadcrumbs": "Administrative Search", "admin.search.breadcrumbs": "Pesquisa Administrativa", @@ -820,6 +847,9 @@ // "admin.metadata-import.page.button.proceed": "Proceed", "admin.metadata-import.page.button.proceed": "Continuar", + //"admin.metadata-import.page.button.select-collection": "Select Collection", + "admin.metadata-import.page.button.select-collection": "Selecione a Coleção", + // "admin.metadata-import.page.error.addFile": "Select file first!", "admin.metadata-import.page.error.addFile": "Selecione o arquivo primeiro!", @@ -1176,7 +1206,7 @@ "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.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 buscar em outras coleções para mapear, 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", @@ -1912,6 +1942,12 @@ // "dso-selector.export-metadata.dspaceobject.head": "Export metadata from", "dso-selector.export-metadata.dspaceobject.head": "Exportar metadados de", + //"dso-selector.export-batch.dspaceobject.head": "Export Batch (ZIP) from", + "dso-selector.export-batch.dspaceobject.head": "Exportar Lote (ZIP) de", + + //"dso-selector.import-batch.dspaceobject.head": "Import batch from", + "dso-selector.import-batch.dspaceobject.head": "Importar lote de", + // "dso-selector.no-results": "No {{ type }} found", "dso-selector.no-results": "Nenhum(a) {{ type }} encontrado(a)", @@ -1954,6 +1990,18 @@ // "confirmation-modal.export-metadata.confirm": "Export", "confirmation-modal.export-metadata.confirm": "Exportar", + //"confirmation-modal.export-batch.header": "Export batch (ZIP) for {{ dsoName }}", + "confirmation-modal.export-batch.header": "Exportar lote (ZIP) para {{ dsoName }}", + + //"confirmation-modal.export-batch.info": "Are you sure you want to export batch (ZIP) for {{ dsoName }}", + "confirmation-modal.export-batch.info": "Você tem certeza que deseja exportar o lote (ZIP) para {{ dsoName }}", + + //"confirmation-modal.export-batch.cancel": "Cancel", + "confirmation-modal.export-batch.cancel": "Cancelar", + + //"confirmation-modal.export-batch.confirm": "Export", + "confirmation-modal.export-batch.confirm": "Exportar", + // "confirmation-modal.delete-eperson.header": "Delete EPerson \"{{ dsoName }}\"", "confirmation-modal.delete-eperson.header": "Apagar EPerson \"{{ dsoName }}\"", @@ -3748,6 +3796,9 @@ // "menu.section.import_batch": "Batch Import (ZIP)", "menu.section.import_batch": "Importação em Lote (ZIP)", + + // "menu.section.export_batch": "Batch Export (ZIP)", + "menu.section.export_batch": "Exportação em Lote (ZIP)", // "menu.section.import_metadata": "Metadata", "menu.section.import_metadata": "Metadados", From 519e031e6a77124edec2ba6f838df504328b831b Mon Sep 17 00:00:00 2001 From: Lucas Zinato Carraro Date: Wed, 12 Oct 2022 12:06:03 -0300 Subject: [PATCH 75/78] Fix missing { --- .../home-page/recent-item-list/recent-item-list.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7f9ca2646b..cd14891b3f 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 @@ -6,7 +6,7 @@
- +
From d031942d1e4a134fa1417691387c9edfd6f2f9eb Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Mon, 17 Oct 2022 18:53:38 -0500 Subject: [PATCH 76/78] chore(README): fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 837cb48004..0ede4d4d19 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ Documentation Official DSpace documentation is available in the DSpace wiki at https://wiki.lyrasis.org/display/DSDOC7x/ -Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of htis codebase. +Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of this codebase. ### Building code documentation From edbf1f847bea36d637f55c9e31b033d0c31dbf4c Mon Sep 17 00:00:00 2001 From: "David P. Steelman" Date: Thu, 20 Oct 2022 11:49:02 -0400 Subject: [PATCH 77/78] Issue 1920 - "data-service.decorator.spec.ts" tests fails intermittently Replaced the timestamp (intended to produce unique "testType" instances) with UUID, because intermittent failures are occurring when tests are running quickly enough that the "new Data().getTime()" function is returning the same timestamp for multiple tests. --- src/app/core/data/base/data-service.decorator.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/core/data/base/data-service.decorator.spec.ts b/src/app/core/data/base/data-service.decorator.spec.ts index ec4c1c8f6f..e09c531a56 100644 --- a/src/app/core/data/base/data-service.decorator.spec.ts +++ b/src/app/core/data/base/data-service.decorator.spec.ts @@ -10,6 +10,7 @@ import { ResourceType } from '../../shared/resource-type'; import { BaseDataService } from './base-data.service'; import { HALDataService } from './hal-data-service.interface'; import { dataService, getDataServiceFor } from './data-service.decorator'; +import { v4 as uuidv4 } from 'uuid'; class TestService extends BaseDataService { } @@ -28,7 +29,7 @@ let testType; describe('@dataService/getDataServiceFor', () => { beforeEach(() => { - testType = new ResourceType('testType-' + new Date().getTime()); + testType = new ResourceType(`testType-${uuidv4()}`); }); it('should register a resourcetype for a dataservice', () => { From 5b69bb1cc417247067c34b6624212e19d797e102 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 21 Oct 2022 16:49:17 -0500 Subject: [PATCH 78/78] Update action to add issues to new triage board --- .github/workflows/issue_opened.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/issue_opened.yml b/.github/workflows/issue_opened.yml index 6b9a273ab6..631bb4836d 100644 --- a/.github/workflows/issue_opened.yml +++ b/.github/workflows/issue_opened.yml @@ -10,20 +10,16 @@ jobs: runs-on: ubuntu-latest steps: # Add the new issue to a project board, if it needs triage - # See https://github.com/marketplace/actions/create-project-card-action - - name: Add issue to project board + # See https://github.com/actions/add-to-project + - name: Add issue to triage board # Only add to project board if issue is flagged as "needs triage" or has no labels # NOTE: By default we flag new issues as "needs triage" in our issue template if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '') - uses: technote-space/create-project-card-action@v1 + uses: actions/add-to-project@v0.3.0 # Note, the authentication token below is an ORG level Secret. - # It must be created/recreated manually via a personal access token with "public_repo" and "admin:org" permissions + # It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token # This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific) with: - GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }} - PROJECT: DSpace Backlog - COLUMN: Triage - CHECK_ORG_PROJECT: true - # Ignore errors - continue-on-error: true + github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }} + project-url: https://github.com/orgs/DSpace/projects/24