geonetwork-ui 2.10.0-dev.4caccddec → 2.10.0-dev.5f1c6f718

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1031 -911
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +53 -17
  4. package/index.d.ts.map +1 -1
  5. package/package.json +2 -2
  6. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +14 -2
  7. package/src/libs/api/repository/src/lib/gn4/gn4.provider.ts +6 -1
  8. package/src/libs/feature/editor/src/lib/+state/editor.actions.ts +6 -0
  9. package/src/libs/feature/editor/src/lib/+state/editor.effects.ts +0 -1
  10. package/src/libs/feature/editor/src/lib/+state/editor.facade.ts +10 -1
  11. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.html +18 -3
  12. package/src/libs/feature/editor/src/lib/components/metadata-quality-panel/metadata-quality-panel.component.ts +33 -40
  13. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-constraints-shortcuts/form-field-constraints-shortcuts.component.ts +34 -34
  14. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.html +8 -15
  15. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts/form-field-contacts.component.ts +6 -4
  16. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.html +8 -7
  17. package/src/libs/feature/editor/src/lib/components/record-form/form-field/form-field-contacts-for-resource/form-field-contacts-for-resource.component.ts +6 -6
  18. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.css +3 -0
  19. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.html +1 -0
  20. package/src/libs/feature/editor/src/lib/components/record-form/record-form.component.ts +57 -3
  21. package/src/libs/feature/notify-reuse/src/index.ts +1 -0
  22. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.css +0 -0
  23. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.html +1 -0
  24. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +21 -0
  25. package/src/libs/ui/elements/src/index.ts +1 -0
  26. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.html +16 -0
  27. package/src/libs/ui/elements/src/lib/contact-pill/contact-pill.component.ts +26 -0
  28. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.html +1 -1
  29. package/src/libs/ui/elements/src/lib/internal-link-card/internal-link-card.component.ts +0 -1
  30. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.css +0 -4
  31. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.html +27 -66
  32. package/src/libs/ui/elements/src/lib/metadata-info/metadata-info.component.ts +30 -10
  33. package/src/libs/ui/inputs/src/lib/button/button.component.ts +4 -0
  34. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -1
  35. package/src/libs/ui/map/src/lib/components/spatial-extent/spatial-extent.component.ts +11 -10
  36. package/src/libs/ui/search/src/lib/record-preview-row/record-preview-row.component.html +0 -1
  37. package/src/libs/util/shared/src/lib/record/quality-score.util.ts +33 -18
  38. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  39. package/src/libs/util/shared/src/lib/utils/user-display.ts +23 -0
  40. package/tailwind.base.css +11 -2
  41. package/translations/de.json +1 -0
  42. package/translations/en.json +1 -0
  43. package/translations/es.json +1 -0
  44. package/translations/fr.json +1 -0
  45. package/translations/it.json +1 -0
  46. package/translations/nl.json +1 -0
  47. package/translations/pt.json +1 -0
  48. package/translations/sk.json +1 -0
@@ -28,11 +28,12 @@
28
28
  <gn-ui-max-lines [maxLines]="7">
29
29
  <div class="metadata-info-keywords sm:pb-4 flex flex-wrap gap-2">
30
30
  @for (keyword of metadata.keywords; track keyword) {
31
- <gn-ui-badge
32
- class="inline-block lowercase"
33
- (click)="onKeywordClick(keyword)"
34
- [clickable]="true"
35
- >{{ keyword.label }}</gn-ui-badge
31
+ <gn-ui-button
32
+ type="primary-light"
33
+ class="inline-block opacity-70"
34
+ extraClass="lowercase text-sm py-1 px-2"
35
+ (buttonClick)="onKeywordClick(keyword)"
36
+ >{{ keyword.label }}</gn-ui-button
36
37
  >
37
38
  }
38
39
  </div>
@@ -105,9 +106,29 @@
105
106
  </div>
106
107
  </gn-ui-expandable-panel>
107
108
  }
109
+ @if (metadata.contactsForResource?.length) {
110
+ <gn-ui-expandable-panel
111
+ [title]="'record.metadata.resource.contacts' | translate"
112
+ data-test="contacts-panel"
113
+ >
114
+ <div class="flex flex-col gap-1 pt-3 pb-4">
115
+ @for (group of contactGroups; track group.role) {
116
+ <div class="flex flex-col gap-1 rounded bg-gray-50 py-4 px-2">
117
+ <p class="text-xs font-normal text-black">
118
+ {{ group.roleLabel | translate }}
119
+ </p>
120
+ <div class="grid gap-0.5 grid-cols-1 md:grid-cols-2">
121
+ @for (contact of group.contacts; track contact.email) {
122
+ <gn-ui-contact-pill [contact]="contact"></gn-ui-contact-pill>
123
+ }
124
+ </div>
125
+ </div>
126
+ }
127
+ </div>
128
+ </gn-ui-expandable-panel>
129
+ }
108
130
  @if (
109
131
  (metadata.kind === 'dataset' && metadata.lineage) ||
110
- resourceContact ||
111
132
  metadata.resourceCreated ||
112
133
  metadata.resourcePublished ||
113
134
  metadata.resourceUpdated ||
@@ -129,66 +150,6 @@
129
150
  </p>
130
151
  </div>
131
152
  }
132
- @if (resourceContact) {
133
- <div
134
- class="flex flex-row gap-6 mt-5 mb-8 resource-contact"
135
- data-test="details-panel-resource-contact"
136
- >
137
- @if (resourceContact.organization?.logoUrl?.href) {
138
- <div
139
- class="flex items-center justify-center border-solid border border-gray-300 rounded-md bg-white h-32 overflow-hidden"
140
- >
141
- <gn-ui-thumbnail
142
- class="relative h-full w-full"
143
- [thumbnailUrl]="resourceContact.organization.logoUrl.href"
144
- fit="contain"
145
- ></gn-ui-thumbnail>
146
- </div>
147
- }
148
- <div class="flex flex-col gap-1">
149
- <p class="text-sm font-medium" translate>record.metadata.producer</p>
150
- <div
151
- class="text-primary font-title text-21 mr-2 cursor-pointer hover:underline"
152
- data-cy="organization-name"
153
- >
154
- {{ resourceContact.organization?.name }}
155
- </div>
156
- @if (resourceContact.organization?.website) {
157
- <div>
158
- <a
159
- [href]="resourceContact.organization.website"
160
- target="_blank"
161
- class="contact-website text-primary text-sm cursor-pointer hover:underline transition-all"
162
- >{{ resourceContact.organization.website }}
163
- <ng-icon
164
- class="!w-[12px] !h-[12px] !text-[12px] opacity-75 shrink-0"
165
- name="matOpenInNew"
166
- ></ng-icon>
167
- </a>
168
- </div>
169
- }
170
- @if (resourceContact.email) {
171
- <div class="mt-4">
172
- <div class="flex">
173
- <ng-icon
174
- class="!w-5 !h-5 !text-[20px] opacity-75 shrink-0"
175
- name="matMailOutline"
176
- ></ng-icon>
177
- @if (resourceContact.email) {
178
- <a
179
- [href]="'mailto:' + resourceContact.email"
180
- class="text-sm hover:underline ml-2"
181
- target="_blank"
182
- data-cy="contact-email"
183
- >{{ resourceContact?.email }}</a
184
- >
185
- }
186
- </div>
187
- </div>
188
- }
189
- </div>
190
- </div>
191
- }
192
153
  <div
193
154
  class="py-6 px-6 rounded bg-gray-100 grid grid-cols-2 gap-y-6 gap-x-[20px] text-gray-700"
194
155
  >
@@ -8,7 +8,10 @@ import {
8
8
  } from '@angular/core'
9
9
  import {
10
10
  CatalogRecord,
11
+ Individual,
11
12
  Keyword,
13
+ Role,
14
+ RoleLabels,
12
15
  } from '../../../../../../libs/common/domain/src/lib/model/record'
13
16
  import { DateService, getTemporalRangeUnion } from '../../../../../../libs/util/shared/src'
14
17
  import { MarkdownParserComponent } from '../markdown-parser/markdown-parser.component'
@@ -19,17 +22,18 @@ import {
19
22
  import { TranslateDirective, TranslatePipe } from '@ngx-translate/core'
20
23
  import {
21
24
  BadgeComponent,
25
+ ButtonComponent,
22
26
  CopyTextButtonComponent,
23
27
  } from '../../../../../../libs/ui/inputs/src'
24
28
  import { ContentGhostComponent } from '../content-ghost/content-ghost.component'
25
29
  import { NgIcon, provideIcons } from '@ng-icons/core'
26
30
  import { matOpenInNew } from '@ng-icons/material-icons/baseline'
27
31
  import { matMailOutline } from '@ng-icons/material-icons/outline'
28
- import { ThumbnailComponent } from '../thumbnail/thumbnail.component'
29
32
  import { GnUiLinkifyDirective } from './linkify.directive'
30
33
  import { GnUiHumanizeDateDirective } from '../../../../../../libs/util/shared/src'
31
34
 
32
35
  import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
36
+ import { ContactPillComponent } from '../contact-pill/contact-pill.component'
33
37
 
34
38
  @Component({
35
39
  selector: 'gn-ui-metadata-info',
@@ -42,15 +46,16 @@ import { SpatialExtentComponent } from '../../../../../../libs/ui/map/src'
42
46
  TranslatePipe,
43
47
  MarkdownParserComponent,
44
48
  ExpandablePanelComponent,
49
+ ButtonComponent,
45
50
  BadgeComponent,
46
51
  ContentGhostComponent,
47
- ThumbnailComponent,
48
52
  MaxLinesComponent,
49
53
  CopyTextButtonComponent,
50
54
  NgIcon,
51
55
  GnUiLinkifyDirective,
52
56
  GnUiHumanizeDateDirective,
53
57
  SpatialExtentComponent,
58
+ ContactPillComponent,
54
59
  ],
55
60
  viewProviders: [
56
61
  provideIcons({
@@ -132,18 +137,33 @@ export class MetadataInfoComponent {
132
137
  return getTemporalRangeUnion(temporalExtents, this.dateService)
133
138
  }
134
139
 
135
- get shownOrganization() {
136
- return this.metadata.ownerOrganization
137
- }
138
-
139
- get resourceContact() {
140
- return this.metadata.contactsForResource?.[0]
141
- }
142
-
143
140
  fieldReady(propName: string) {
144
141
  return !this.incomplete || propName in this.metadata
145
142
  }
146
143
 
144
+ get contactGroups(): {
145
+ role: Role
146
+ roleLabel: string
147
+ contacts: Individual[]
148
+ }[] {
149
+ const groups: { role: Role; roleLabel: string; contacts: Individual[] }[] =
150
+ []
151
+ const indexByRole = new Map<Role, number>()
152
+ for (const contact of this.metadata.contactsForResource ?? []) {
153
+ if (indexByRole.has(contact.role)) {
154
+ groups[indexByRole.get(contact.role)].contacts.push(contact)
155
+ } else {
156
+ indexByRole.set(contact.role, groups.length)
157
+ groups.push({
158
+ role: contact.role,
159
+ roleLabel: RoleLabels.get(contact.role),
160
+ contacts: [contact],
161
+ })
162
+ }
163
+ }
164
+ return groups
165
+ }
166
+
147
167
  onKeywordClick(keyword: Keyword) {
148
168
  this.keyword.emit(keyword)
149
169
  }
@@ -26,6 +26,7 @@ export class ButtonComponent {
26
26
  | 'light'
27
27
  | 'gray'
28
28
  | 'black'
29
+ | 'primary-light'
29
30
  ) {
30
31
  // btn-classes are written in full to be picked up by tailwind
31
32
  switch (value) {
@@ -47,6 +48,9 @@ export class ButtonComponent {
47
48
  case 'black':
48
49
  this.btnClass = 'gn-ui-btn-black'
49
50
  break
51
+ case 'primary-light':
52
+ this.btnClass = 'gn-ui-btn-primary-light'
53
+ break
50
54
  case 'default':
51
55
  default:
52
56
  this.btnClass = 'gn-ui-btn-default'
@@ -204,11 +204,12 @@ export class MapContainerComponent implements AfterViewInit, OnChanges {
204
204
 
205
205
  async ngOnChanges(changes: SimpleChanges) {
206
206
  if ('context' in changes && !changes['context'].isFirstChange()) {
207
+ const olMap = await this.openlayersMap
207
208
  const diff = computeMapContextDiff(
208
209
  this.processContext(changes['context'].currentValue),
209
210
  this.processContext(changes['context'].previousValue)
210
211
  )
211
- await applyContextDiffToMap(this.olMap, diff)
212
+ await applyContextDiffToMap(olMap, diff)
212
213
 
213
214
  if (this._resolvedExtentChange && diff.viewChanges) {
214
215
  this._resolvedExtentChange.emit(this.calculateCurrentMapExtent())
@@ -1,4 +1,4 @@
1
- import { Component, Input } from '@angular/core'
1
+ import { ChangeDetectorRef, Component, inject, Input } from '@angular/core'
2
2
  import { CommonModule } from '@angular/common'
3
3
  import { Geometry } from 'geojson'
4
4
  import { GeoJSONFeatureCollection } from 'ol/format/GeoJSON.js'
@@ -10,8 +10,8 @@ import {
10
10
  MapContextLayer,
11
11
  } from '@geospatial-sdk/core'
12
12
  import { MapContainerComponent } from '../map-container/map-container.component'
13
- import { BehaviorSubject, Observable } from 'rxjs'
14
- import { switchMap } from 'rxjs/operators'
13
+ import { BehaviorSubject, from, Observable, of } from 'rxjs'
14
+ import { map, switchMap, tap } from 'rxjs/operators'
15
15
  import { DatasetSpatialExtent } from '../../../../../../../libs/common/domain/src/lib/model/record'
16
16
 
17
17
  @Component({
@@ -22,14 +22,16 @@ import { DatasetSpatialExtent } from '../../../../../../../libs/common/domain/sr
22
22
  styleUrl: './spatial-extent.component.css',
23
23
  })
24
24
  export class SpatialExtentComponent {
25
+ private _cdr = inject(ChangeDetectorRef)
26
+
25
27
  @Input() set spatialExtents(value: DatasetSpatialExtent[]) {
26
28
  this.spatialExtents$.next(value)
27
29
  }
28
30
  spatialExtents$ = new BehaviorSubject<DatasetSpatialExtent[]>([])
29
31
  mapContext$: Observable<MapContext> = this.spatialExtents$.pipe(
30
- switchMap(async (extents) => {
32
+ switchMap((extents) => {
31
33
  if (extents.length === 0) {
32
- return null // null extent means default view
34
+ return of(null)
33
35
  }
34
36
  const featureCollection: GeoJSONFeatureCollection = {
35
37
  type: 'FeatureCollection',
@@ -61,11 +63,10 @@ export class SpatialExtentComponent {
61
63
  'fill-color': 'rgba(153, 153, 153, 0.3)',
62
64
  },
63
65
  }
64
- const view = await createViewFromLayer(layer)
65
- return {
66
- view,
67
- layers: [layer],
68
- }
66
+ return from(createViewFromLayer(layer)).pipe(
67
+ map((view) => ({ view, layers: [layer] }) as MapContext),
68
+ tap(() => this._cdr.markForCheck())
69
+ )
69
70
  })
70
71
  )
71
72
 
@@ -1,6 +1,5 @@
1
1
  <gn-ui-internal-link-card
2
2
  [linkHref]="linkHref"
3
- [linkTarget]="linkTarget"
4
3
  [record]="record"
5
4
  [favoriteTemplate]="favoriteTemplate"
6
5
  [metadataQualityDisplay]="metadataQualityDisplay"
@@ -3,27 +3,41 @@ import {
3
3
  RecordKind,
4
4
  } from '../../../../../../libs/common/domain/src/lib/model/record'
5
5
 
6
+ type TValidatorEntry = {
7
+ validator: (metadata: Partial<CatalogRecord>) => boolean
8
+ alias?: string
9
+ }
10
+
6
11
  type TValidatorMapper = {
7
- [key: string]: (metadata: Partial<CatalogRecord>) => boolean
12
+ [key: string]: TValidatorEntry
8
13
  }
9
14
 
10
15
  const ValidatorMapper: TValidatorMapper = {
11
- title: (record) => !!record?.title,
12
- abstract: (record) => !!record?.abstract,
13
- keywords: (record) => (record?.keywords?.length ?? 0) > 0,
14
- legalConstraints: (record) =>
15
- !!(
16
- record?.legalConstraints?.length &&
17
- record.legalConstraints.some((c) => c?.text?.trim().length > 0)
18
- ),
19
- contacts: (record) =>
20
- !!record?.contacts?.[0]?.email &&
21
- record.contacts[0].email !== 'missing@missing.com',
22
- updateFrequency: (record) =>
23
- !!record?.updateFrequency && record.updateFrequency !== 'unknown',
24
- topics: (record) => (record?.topics?.length ?? 0) > 0,
25
- organisation: (record) => !!record?.contacts?.[0]?.organization?.name,
26
- source: (record) => !!record?.extras?.sourcesIdentifiers,
16
+ title: { validator: (record) => !!record?.title },
17
+ abstract: { validator: (record) => !!record?.abstract },
18
+ keywords: { validator: (record) => (record?.keywords?.length ?? 0) > 0 },
19
+ legalConstraints: {
20
+ validator: (record) =>
21
+ !!(
22
+ record?.legalConstraints?.length &&
23
+ record.legalConstraints.some((c) => c?.text?.trim().length > 0)
24
+ ),
25
+ },
26
+ contacts: {
27
+ validator: (record) =>
28
+ !!record?.contacts?.[0]?.email &&
29
+ record.contacts[0].email !== 'missing@missing.com',
30
+ },
31
+ updateFrequency: {
32
+ validator: (record) =>
33
+ !!record?.updateFrequency && record.updateFrequency !== 'unknown',
34
+ },
35
+ topics: { validator: (record) => (record?.topics?.length ?? 0) > 0 },
36
+ organisation: {
37
+ validator: (record) => !!record?.contacts?.[0]?.organization?.name,
38
+ alias: 'contacts',
39
+ },
40
+ source: { validator: (record) => !!record?.extras?.sourcesIdentifiers },
27
41
  } as const
28
42
 
29
43
  export type ValidatorMapperKeys = keyof typeof ValidatorMapper & string
@@ -67,6 +81,7 @@ export function getQualityValidators(
67
81
 
68
82
  return filteredProps.map((name) => ({
69
83
  name,
70
- validator: () => ValidatorMapper[name](record),
84
+ validator: () => ValidatorMapper[name].validator(record),
85
+ alias: ValidatorMapper[name].alias,
71
86
  }))
72
87
  }
@@ -13,3 +13,4 @@ export * from './sort-by'
13
13
  export * from './strip-html'
14
14
  export * from './temporal-extent-union'
15
15
  export * from './url'
16
+ export * from './user-display'
@@ -0,0 +1,23 @@
1
+ import { UserModel } from '../../../../../../libs/common/domain/src/lib/model/user'
2
+ import { Individual } from '../../../../../../libs/common/domain/src/lib/model/record'
3
+
4
+ export function getIndividualDisplayName(individual: Individual): string {
5
+ const nameParts = [individual.firstName, individual.lastName]
6
+ .filter(Boolean)
7
+ .join(' ')
8
+ const orgPart = individual.organization?.name
9
+ ? ` (${individual.organization.name})`
10
+ : ''
11
+ if (nameParts) return `${nameParts}${orgPart}`
12
+ return individual.organization?.name ?? individual.email ?? ''
13
+ }
14
+
15
+ export function toIndividual(user: UserModel): Individual {
16
+ return {
17
+ firstName: user.name,
18
+ lastName: user.surname,
19
+ email: user.email,
20
+ role: 'unspecified',
21
+ organization: user.organisation ? { name: user.organisation } : undefined,
22
+ }
23
+ }
package/tailwind.base.css CHANGED
@@ -74,7 +74,10 @@
74
74
  --bg-hover: var(--gn-ui-button-bg-hover, var(--color-background));
75
75
  --font-size: var(--gn-ui-button-font-size, 1em);
76
76
  --color: var(--gn-ui-button-color, var(--color-main));
77
- @apply flex flex-row items-center justify-center
77
+ --disabled-opacity: var(--gn-ui-button-disabled-opacity, 0.5);
78
+ --justify: var(--gn-ui-button-justify, center);
79
+ justify-content: var(--justify);
80
+ @apply flex flex-row items-center
78
81
  text-[length:--font-size] leading-none
79
82
  text-[color:--color]
80
83
  p-[--padding] rounded-[--rounded] w-[--width] h-[--height] overflow-hidden
@@ -82,7 +85,7 @@
82
85
  hover:bg-[--bg-hover] focus:bg-[--bg-hover] active:bg-[--background]
83
86
  outline-none
84
87
  relative
85
- disabled:opacity-50 disabled:pointer-events-none
88
+ disabled:opacity-[--disabled-opacity] disabled:pointer-events-none
86
89
  focus:z-10 active:z-10 hover:z-10;
87
90
  }
88
91
  /* makes sure icons will not make the buttons grow vertically */
@@ -134,6 +137,12 @@
134
137
  bg-white border-white focus:ring-4 focus:ring-gray-300;
135
138
  }
136
139
 
140
+ .gn-ui-btn-primary-light {
141
+ @apply gn-ui-btn text-primary-darkest
142
+ bg-primary-white hover:bg-primary focus:bg-primary hover:text-white focus:text-white active:bg-primary-darker
143
+ border-transparent;
144
+ }
145
+
137
146
  .gn-ui-card-s {
138
147
  @apply min-h-[64px] w-full xs:min-w-[260px] xs:max-w-[280px] sm:min-w-[300px] sm:max-w-[500px] md:min-w-[230px];
139
148
  }
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "Aktualisierungsfrequenz nicht angegeben",
521
521
  "record.metadata.quality.updateFrequency.success": "Aktualisierungsfrequenz angegeben",
522
522
  "record.metadata.related": "Entdecken Sie den Katalog",
523
+ "record.metadata.resource.contacts": "Kontakte zur Ressource",
523
524
  "record.metadata.resourceCreated": "Erstellt",
524
525
  "record.metadata.resourcePublished": "Veröffentlicht",
525
526
  "record.metadata.resourceUpdated": "Zuletzt aktualisiert",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "Update frequency is not specified",
521
521
  "record.metadata.quality.updateFrequency.success": "Update frequency is specified",
522
522
  "record.metadata.related": "Explore the catalog",
523
+ "record.metadata.resource.contacts": "Contacts for the resource",
523
524
  "record.metadata.resourceCreated": "Created",
524
525
  "record.metadata.resourcePublished": "Published",
525
526
  "record.metadata.resourceUpdated": "Last updated",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "",
521
521
  "record.metadata.quality.updateFrequency.success": "",
522
522
  "record.metadata.related": "",
523
+ "record.metadata.resource.contacts": "",
523
524
  "record.metadata.resourceCreated": "",
524
525
  "record.metadata.resourcePublished": "",
525
526
  "record.metadata.resourceUpdated": "",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "La fréquence de mise à jour n'est pas renseignée",
521
521
  "record.metadata.quality.updateFrequency.success": "La fréquence de mise à jour est renseignée",
522
522
  "record.metadata.related": "Explorez le catalogue",
523
+ "record.metadata.resource.contacts": "Contacts liés à la donnée",
523
524
  "record.metadata.resourceCreated": "Créé",
524
525
  "record.metadata.resourcePublished": "Publié",
525
526
  "record.metadata.resourceUpdated": "Mis à jour",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "La frequenza di aggiornamento non è specificata",
521
521
  "record.metadata.quality.updateFrequency.success": "La frequenza di aggiornamento è specificata",
522
522
  "record.metadata.related": "Vedi anche",
523
+ "record.metadata.resource.contacts": "Contatti per la risorsa",
523
524
  "record.metadata.resourceCreated": "Creato",
524
525
  "record.metadata.resourcePublished": "Pubblicato",
525
526
  "record.metadata.resourceUpdated": "Ultimo aggiornamento",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "",
521
521
  "record.metadata.quality.updateFrequency.success": "",
522
522
  "record.metadata.related": "",
523
+ "record.metadata.resource.contacts": "",
523
524
  "record.metadata.resourceCreated": "",
524
525
  "record.metadata.resourcePublished": "",
525
526
  "record.metadata.resourceUpdated": "",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "",
521
521
  "record.metadata.quality.updateFrequency.success": "",
522
522
  "record.metadata.related": "",
523
+ "record.metadata.resource.contacts": "",
523
524
  "record.metadata.resourceCreated": "",
524
525
  "record.metadata.resourcePublished": "",
525
526
  "record.metadata.resourceUpdated": "",
@@ -520,6 +520,7 @@
520
520
  "record.metadata.quality.updateFrequency.failed": "Frekvencia aktualizácie nie je určená",
521
521
  "record.metadata.quality.updateFrequency.success": "Frekvencia aktualizácie je určená",
522
522
  "record.metadata.related": "Súvisiace záznamy",
523
+ "record.metadata.resource.contacts": "Kontakty k zdroju",
523
524
  "record.metadata.resourceCreated": "",
524
525
  "record.metadata.resourcePublished": "",
525
526
  "record.metadata.resourceUpdated": "",