diff --git a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.html b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.html
index 2d46cad5329..ac984b06f26 100644
--- a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.html
+++ b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.html
@@ -5,14 +5,18 @@
1">
{{'item.view.box.author.preview.and' | translate}}
- {{ author.name }}
-
+ {{ author.name }}
diff --git a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.scss b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.scss
index 6f28ee1ce79..d0a0a47126e 100644
--- a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.scss
+++ b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.scss
@@ -1,3 +1,17 @@
/**
This is a styling file for the clarin-item-author-preview component.
*/
+
+.orcid-icon-link {
+ margin-left: 0.25rem;
+ text-decoration: none;
+
+ &:hover {
+ opacity: 0.8;
+ }
+}
+
+.orcid-icon {
+ color: #a6ce39;
+}
+
diff --git a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.spec.ts b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.spec.ts
index f75f22ba072..d8fa38e65c8 100644
--- a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.spec.ts
+++ b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.spec.ts
@@ -1,15 +1,15 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ClarinItemAuthorPreviewComponent } from './clarin-item-author-preview.component';
-import {of} from 'rxjs';
-import {ConfigurationDataService} from '../../core/data/configuration-data.service';
+import { ConfigurationDataService } from '../../core/data/configuration-data.service';
+import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
describe('ClarinItemAuthorPreviewComponent', () => {
let component: ClarinItemAuthorPreviewComponent;
let fixture: ComponentFixture;
const configurationServiceSpy = jasmine.createSpyObj('configurationService', {
- findByPropertyName: of(true),
+ findByPropertyName: createSuccessfulRemoteDataObject$({ values: ['https://orcid.org'] }),
});
beforeEach(async () => {
@@ -31,4 +31,11 @@ describe('ClarinItemAuthorPreviewComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ it('assignOrcidDomainUrl should read orcid.domain-url from backend config', async () => {
+ component.orcidDomainUrl = null;
+ await component.assignOrcidDomainUrl();
+ expect(component.orcidDomainUrl).toBe('https://orcid.org');
+ expect(configurationServiceSpy.findByPropertyName).toHaveBeenCalledWith('orcid.domain-url');
+ });
});
diff --git a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.ts b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.ts
index 336b80120a5..9908f456eaf 100644
--- a/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.ts
+++ b/src/app/shared/clarin-item-author-preview/clarin-item-author-preview.component.ts
@@ -1,10 +1,12 @@
import { Component, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
+import { take } from 'rxjs/operators';
import { getBaseUrl, loadItemAuthors } from '../clarin-shared-util';
import { Item } from '../../core/shared/item.model';
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
import { AuthorNameLink } from '../clarin-item-box-view/clarin-author-name-link.model';
+import { getFirstCompletedRemoteData } from '../../core/shared/operators';
@Component({
selector: 'ds-clarin-item-author-preview',
@@ -38,11 +40,18 @@ export class ClarinItemAuthorPreviewComponent implements OnInit {
*/
baseUrl = '';
+ /**
+ * ORCID domain URL loaded from the backend property `orcid.domain-url`.
+ * When unset (backend does not expose the property) bare ORCID iDs are kept as plain text.
+ */
+ orcidDomainUrl: string | null = null;
+
constructor(protected configurationService: ConfigurationDataService) { }
async ngOnInit(): Promise {
await this.assignBaseUrl();
- loadItemAuthors(this.item, this.itemAuthors, this.baseUrl, this.fields);
+ await this.assignOrcidDomainUrl();
+ loadItemAuthors(this.item, this.itemAuthors, this.baseUrl, this.fields, this.orcidDomainUrl);
}
toggleShowEveryAuthor() {
this.showEveryAuthor.next(!this.showEveryAuthor.value);
@@ -57,4 +66,20 @@ export class ClarinItemAuthorPreviewComponent implements OnInit {
return baseUrlResponse?.values?.[0];
});
}
+
+ /**
+ * Load `orcid.domain-url` from the backend configuration so that bare ORCID iDs
+ * can be expanded into a hyperlink to the configured ORCID host.
+ */
+ async assignOrcidDomainUrl(): Promise {
+ this.orcidDomainUrl = await this.configurationService.findByPropertyName('orcid.domain-url').pipe(
+ getFirstCompletedRemoteData(),
+ take(1),
+ ).toPromise().then((rd) => {
+ if (rd?.hasSucceeded && rd.payload?.values?.length > 0) {
+ return rd.payload.values[0];
+ }
+ return null;
+ });
+ }
}
diff --git a/src/app/shared/clarin-item-box-view/clarin-author-name-link.model.ts b/src/app/shared/clarin-item-box-view/clarin-author-name-link.model.ts
index 856655b17fc..74e659c7a84 100644
--- a/src/app/shared/clarin-item-box-view/clarin-author-name-link.model.ts
+++ b/src/app/shared/clarin-item-box-view/clarin-author-name-link.model.ts
@@ -6,4 +6,6 @@ export class AuthorNameLink {
name: string;
url: string;
isAuthority: boolean;
+ isOrcid?: boolean;
+ orcidUrl?: string;
}
diff --git a/src/app/shared/clarin-shared-util.spec.ts b/src/app/shared/clarin-shared-util.spec.ts
new file mode 100644
index 00000000000..b284eab69ba
--- /dev/null
+++ b/src/app/shared/clarin-shared-util.spec.ts
@@ -0,0 +1,105 @@
+import { BehaviorSubject } from 'rxjs';
+
+import { loadItemAuthors, ORCID_ID_PATTERN, ORCID_URL_PATTERN } from './clarin-shared-util';
+import { AuthorNameLink } from './clarin-item-box-view/clarin-author-name-link.model';
+import { MetadataValue } from '../core/shared/metadata.models';
+
+function mv(value: string, authority?: string): MetadataValue {
+ return Object.assign(new MetadataValue(), { value, authority });
+}
+
+function itemWithAuthors(values: MetadataValue[]): any {
+ return {
+ allMetadata: () => values,
+ };
+}
+
+describe('clarin-shared-util ORCID helpers', () => {
+
+ describe('ORCID_ID_PATTERN', () => {
+ it('matches a bare ORCID iD', () => {
+ expect(ORCID_ID_PATTERN.test('0000-0001-2345-6789')).toBeTrue();
+ expect(ORCID_ID_PATTERN.test('0000-0001-2345-678X')).toBeTrue();
+ });
+
+ it('does not match a full URL or arbitrary text', () => {
+ expect(ORCID_ID_PATTERN.test('https://orcid.org/0000-0001-2345-6789')).toBeFalse();
+ expect(ORCID_ID_PATTERN.test('not-an-orcid')).toBeFalse();
+ });
+ });
+
+ describe('ORCID_URL_PATTERN', () => {
+ it('matches the production and sandbox ORCID URLs', () => {
+ expect(ORCID_URL_PATTERN.test('https://orcid.org/0000-0001-2345-6789')).toBeTrue();
+ expect(ORCID_URL_PATTERN.test('https://sandbox.orcid.org/0000-0001-2345-678X')).toBeTrue();
+ });
+
+ it('does not match a bare ORCID iD or non-URL strings', () => {
+ expect(ORCID_URL_PATTERN.test('0000-0001-2345-6789')).toBeFalse();
+ expect(ORCID_URL_PATTERN.test('not-an-orcid')).toBeFalse();
+ });
+ });
+
+ describe('loadItemAuthors', () => {
+ const baseUrl = 'http://localhost:4000';
+ const fields = ['dc.contributor.author'];
+
+ it('builds the search link for every author with the equals operator', () => {
+ const subject = new BehaviorSubject([]);
+ loadItemAuthors(itemWithAuthors([mv('Doe, John')]), subject, baseUrl, fields);
+ expect(subject.value.length).toBe(1);
+ expect(subject.value[0].url)
+ .toBe('http://localhost:4000/search?f.author=Doe%2C%20John,equals');
+ expect(subject.value[0].isOrcid).toBeFalse();
+ });
+
+ it('expands a bare ORCID iD using the orcid.domain-url from the backend', () => {
+ const subject = new BehaviorSubject([]);
+ loadItemAuthors(
+ itemWithAuthors([mv('Doe, John', '0000-0001-2345-6789')]),
+ subject, baseUrl, fields, 'https://sandbox.orcid.org',
+ );
+ expect(subject.value[0].isOrcid).toBeTrue();
+ expect(subject.value[0].orcidUrl).toBe('https://sandbox.orcid.org/0000-0001-2345-6789');
+ });
+
+ it('strips a trailing slash from the orcid.domain-url before composing the URL', () => {
+ const subject = new BehaviorSubject([]);
+ loadItemAuthors(
+ itemWithAuthors([mv('Doe, John', '0000-0001-2345-6789')]),
+ subject, baseUrl, fields, 'https://orcid.org/',
+ );
+ expect(subject.value[0].orcidUrl).toBe('https://orcid.org/0000-0001-2345-6789');
+ });
+
+ it('passes through an authority that already is a full ORCID URL', () => {
+ const subject = new BehaviorSubject([]);
+ loadItemAuthors(
+ itemWithAuthors([mv('Doe, John', 'https://orcid.org/0000-0001-2345-6789')]),
+ subject, baseUrl, fields, null,
+ );
+ expect(subject.value[0].isOrcid).toBeTrue();
+ expect(subject.value[0].orcidUrl).toBe('https://orcid.org/0000-0001-2345-6789');
+ });
+
+ it('does not flag a bare ORCID iD when orcid.domain-url is missing', () => {
+ const subject = new BehaviorSubject([]);
+ loadItemAuthors(
+ itemWithAuthors([mv('Doe, John', '0000-0001-2345-6789')]),
+ subject, baseUrl, fields, null,
+ );
+ expect(subject.value[0].isOrcid).toBeFalse();
+ expect(subject.value[0].orcidUrl).toBeUndefined();
+ });
+
+ it('does not flag a non-ORCID authority', () => {
+ const subject = new BehaviorSubject([]);
+ loadItemAuthors(
+ itemWithAuthors([mv('Smith, Jane', 'some-internal-authority')]),
+ subject, baseUrl, fields, 'https://orcid.org',
+ );
+ expect(subject.value[0].isOrcid).toBeFalse();
+ expect(subject.value[0].isAuthority).toBeTrue();
+ });
+ });
+});
diff --git a/src/app/shared/clarin-shared-util.ts b/src/app/shared/clarin-shared-util.ts
index 44b087a9910..ea52d86bf9e 100644
--- a/src/app/shared/clarin-shared-util.ts
+++ b/src/app/shared/clarin-shared-util.ts
@@ -5,6 +5,20 @@ import { isNull, isUndefined } from './empty.util';
import { MetadataValue } from '../core/shared/metadata.models';
import { AuthorNameLink } from './clarin-item-box-view/clarin-author-name-link.model';
+/**
+ * Pattern that matches a bare ORCID iD (16 digits in groups of 4, last char may be X).
+ * Example: `0000-0001-2345-6789` or `0000-0001-2345-678X`.
+ */
+export const ORCID_ID_PATTERN = /^(\d{4}-){3}\d{3}[\dX]$/i;
+
+/**
+ * Pattern that matches a full ORCID URL authority value. Matches any ORCID-like
+ * host so the backend `orcid.domain-url` (e.g. `https://orcid.org`,
+ * `https://sandbox.orcid.org`) drives canonicalisation, not the regex.
+ * Example: `https://orcid.org/0000-0001-2345-6789` or `https://sandbox.orcid.org/0000-0001-2345-678X`.
+ */
+export const ORCID_URL_PATTERN = /^https?:\/\/[^/]+\/((\d{4}-){3}\d{3}[\dX])$/i;
+
/**
* Convert raw byte array to the image is not secure - this function make it secure
* @param imageByteArray as secure byte array
@@ -44,15 +58,19 @@ export function convertMetadataFieldIntoSearchType(field: string[]) {
}
/**
- * Load Authors of the current item into BehaviourSubject - ItemAuthors. This method also compose
- * search link for every Author.
+ * Load Authors of the current item into BehaviourSubject - ItemAuthors. This method also composes
+ * the search link for every Author and, when the authority value is an ORCID iD / URL, the link
+ * to the ORCID profile.
*
* @param item current Item
* @param itemAuthors BehaviourSubject (async) of Authors with search links
- * @param baseUrl e.g. localhost:8080
+ * @param baseUrl e.g. `localhost:8080`
* @param fields metadata fields where authors are stored
+ * @param orcidDomainUrl ORCID domain URL loaded from the backend property `orcid.domain-url`
+ * (e.g. `https://orcid.org` or `https://sandbox.orcid.org`). When not provided, bare ORCID
+ * iDs cannot be turned into hyperlinks, but full ORCID URLs are still recognised.
*/
-export function loadItemAuthors(item, itemAuthors, baseUrl, fields) {
+export function loadItemAuthors(item, itemAuthors, baseUrl, fields, orcidDomainUrl: string | null = null) {
if (isNull(item) || isNull(itemAuthors) || isNull(baseUrl)) {
return;
}
@@ -61,21 +79,35 @@ export function loadItemAuthors(item, itemAuthors, baseUrl, fields) {
if (isUndefined(authorsMV)) {
return null;
}
+ const domain = orcidDomainUrl?.endsWith('/') ? orcidDomainUrl.slice(0, -1) : orcidDomainUrl;
const itemAuthorsLocal = [];
authorsMV.forEach((authorMV: MetadataValue) => {
- let value: string, operator: string;
+ let isOrcid = false;
+ let orcidUrl: string;
+ let searchValue: string;
+ let searchOperator: string;
if (authorMV.authority) {
- value = encodeURIComponent(authorMV.authority);
- operator = 'authority';
+ const authority = String(authorMV.authority).trim();
+ if (ORCID_URL_PATTERN.test(authority)) {
+ orcidUrl = authority;
+ isOrcid = true;
+ } else if (domain && ORCID_ID_PATTERN.test(authority)) {
+ orcidUrl = `${domain}/${authority}`;
+ isOrcid = true;
+ }
+ searchValue = encodeURIComponent(authorMV.authority);
+ searchOperator = 'authority';
} else {
- value = encodeURIComponent(authorMV.value);
- operator = 'equals';
+ searchValue = encodeURIComponent(authorMV.value);
+ searchOperator = 'equals';
}
- const authorSearchLink = baseUrl + '/search?f.author=' + value + ',' + operator;
+ const authorSearchLink = baseUrl + '/search?f.author=' + searchValue + ',' + searchOperator;
const authorNameLink = Object.assign(new AuthorNameLink(), {
name: authorMV.value,
url: authorSearchLink,
- isAuthority: !!authorMV.authority
+ isAuthority: !!authorMV.authority,
+ isOrcid: isOrcid,
+ orcidUrl: orcidUrl
});
itemAuthorsLocal.push(authorNameLink);
});
diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts
index 7edf1a700e5..a13160cdb79 100644
--- a/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts
+++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts
@@ -10,6 +10,8 @@ import { MetadataRepresentationDirective } from './metadata-representation.direc
import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator';
import { ThemeService } from '../theme-support/theme.service';
import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component';
+import { ConfigurationDataService } from '../../core/data/configuration-data.service';
+import { ConfigurationDataServiceStub } from '../testing/configuration-data.service.stub';
const testType = 'TestType';
const testContext = Context.Search;
@@ -51,6 +53,10 @@ describe('MetadataRepresentationLoaderComponent', () => {
{
provide: ThemeService,
useValue: themeService,
+ },
+ {
+ provide: ConfigurationDataService,
+ useClass: ConfigurationDataServiceStub,
}
]
}).overrideComponent(MetadataRepresentationLoaderComponent, {
diff --git a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html
index 7d416e9f3eb..0f378935b33 100644
--- a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html
+++ b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.html
@@ -7,7 +7,21 @@
target="_blank" [href]="mdRepresentation.getValue()">
{{mdRepresentation.getValue()}}
- {{mdRepresentation.getValue()}}
+
+
+ {{mdRepresentation.getValue()}}
+
+
+
+ {{mdRepresentation.getValue()}}
+
+
{
TestBed.configureTestingModule({
imports: [],
declarations: [PlainTextMetadataListElementComponent],
+ providers: [
+ { provide: ConfigurationDataService, useClass: ConfigurationDataServiceStub },
+ ],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PlainTextMetadataListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
diff --git a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts
index 8a3e1d51a6f..a64655c3054 100644
--- a/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts
+++ b/src/app/shared/object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component.ts
@@ -1,21 +1,50 @@
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+import { take } from 'rxjs/operators';
import { MetadataRepresentationListElementComponent } from '../metadata-representation-list-element.component';
import { metadataRepresentationComponent } from '../../../metadata-representation/metadata-representation.decorator';
import { VALUE_LIST_BROWSE_DEFINITION } from '../../../../core/shared/value-list-browse-definition.resource-type';
+import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
+import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
+import { getFirstCompletedRemoteData } from '../../../../core/shared/operators';
+import { ORCID_ID_PATTERN, ORCID_URL_PATTERN } from '../../../clarin-shared-util';
@metadataRepresentationComponent('Publication', MetadataRepresentationType.PlainText)
// For now, authority controlled fields are rendered the same way as plain text fields
@metadataRepresentationComponent('Publication', MetadataRepresentationType.AuthorityControlled)
@Component({
selector: 'ds-plain-text-metadata-list-element',
- templateUrl: './plain-text-metadata-list-element.component.html'
+ templateUrl: './plain-text-metadata-list-element.component.html',
+ styleUrls: ['./plain-text-metadata-list-element.component.scss']
})
/**
* A component for displaying MetadataRepresentation objects in the form of plain text
* It will simply use the value retrieved from MetadataRepresentation.getValue() to display as plain text
*/
-export class PlainTextMetadataListElementComponent extends MetadataRepresentationListElementComponent {
+export class PlainTextMetadataListElementComponent extends MetadataRepresentationListElementComponent implements OnInit {
+
+ /**
+ * The ORCID domain URL fetched from the backend (`orcid.domain-url`),
+ * e.g. `https://orcid.org` or `https://sandbox.orcid.org`.
+ */
+ orcidDomainUrl$ = new BehaviorSubject(null);
+
+ constructor(protected configurationService: ConfigurationDataService) {
+ super();
+ }
+
+ ngOnInit(): void {
+ this.configurationService.findByPropertyName('orcid.domain-url').pipe(
+ getFirstCompletedRemoteData(),
+ take(1),
+ ).subscribe((rd) => {
+ if (rd?.hasSucceeded && rd.payload?.values?.length > 0) {
+ this.orcidDomainUrl$.next(rd.payload.values[0]);
+ }
+ });
+ }
+
/**
* Get the appropriate query parameters for this browse link, depending on whether the browse definition
* expects 'startsWith' (eg browse by date) or 'value' (eg browse by title)
@@ -27,4 +56,48 @@ export class PlainTextMetadataListElementComponent extends MetadataRepresentatio
}
return queryParams;
}
+
+ /**
+ * Check whether the authority value of this metadata is an ORCID identifier.
+ * Accepts either a bare ORCID iD or a full ORCID URL. A full ORCID URL is recognised
+ * even when the backend does not expose `orcid.domain-url`, mirroring `loadItemAuthors`.
+ * A bare ORCID iD requires `orcid.domain-url` to canonicalise into a full URL.
+ */
+ isOrcidAuthority(orcidDomainUrl: string | null = this.orcidDomainUrl$.value): boolean {
+ if (this.mdRepresentation instanceof MetadatumRepresentation) {
+ const authority = this.mdRepresentation.authority?.trim();
+ if (!authority) {
+ return false;
+ }
+ if (ORCID_URL_PATTERN.test(authority)) {
+ return true;
+ }
+ return !!orcidDomainUrl && ORCID_ID_PATTERN.test(authority);
+ }
+ return false;
+ }
+
+ /**
+ * Build the full ORCID profile URL for the current author.
+ * Returns an empty string when the authority is not an ORCID value
+ * or when the ORCID domain URL is required (bare ORCID iD) but not configured on the backend.
+ * A full ORCID URL authority is returned as-is without requiring `orcid.domain-url`.
+ */
+ getOrcidUrl(orcidDomainUrl: string | null = this.orcidDomainUrl$.value): string {
+ if (!(this.mdRepresentation instanceof MetadatumRepresentation)) {
+ return '';
+ }
+ const authority = this.mdRepresentation.authority?.trim();
+ if (!authority) {
+ return '';
+ }
+ if (ORCID_URL_PATTERN.test(authority)) {
+ return authority;
+ }
+ if (orcidDomainUrl && ORCID_ID_PATTERN.test(authority)) {
+ const domain = orcidDomainUrl.endsWith('/') ? orcidDomainUrl.slice(0, -1) : orcidDomainUrl;
+ return `${domain}/${authority}`;
+ }
+ return '';
+ }
}
diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5
index 7f540553f03..4d0ec104948 100644
--- a/src/assets/i18n/cs.json5
+++ b/src/assets/i18n/cs.json5
@@ -3420,6 +3420,8 @@
"item.view.box.author.preview.and": "a",
// "item.view.box.author.preview.show-everyone": "show everyone:",
"item.view.box.author.preview.show-everyone": "zobraz všechny autory",
+ // "item.view.box.author.preview.orcid-link.title": "View ORCID profile",
+ "item.view.box.author.preview.orcid-link.title": "Zobrazit ORCID profil",
// "item.file.description.not.supported.video": "Your browser does not support the video tag.",
"item.file.description.not.supported.video": "Váš prohlížeč nepodporuje videa.",
// "item.file.description.name": "Name",
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5
index 9a520c64947..1ae3aa647db 100644
--- a/src/assets/i18n/en.json5
+++ b/src/assets/i18n/en.json5
@@ -2869,6 +2869,7 @@
"item.view.box.author.preview.show-everyone": "show everyone",
+ "item.view.box.author.preview.orcid-link.title": "View ORCID profile",
"item.file.description.not.supported.video": "Your browser does not support the video tag.",