Repository: geonetwork/geonetwork-ui
Pull request #1228: [Editor]: Multilingual side panel
Author: login: cmoinier
Created: 2025-04-24T09:05:41Z | Merged: 2025-04-24T12:17:36Z

### Description

This PR introduces a new multilingual side panel in the editor form.
This ticket only includes the creation of the side panel and its opening, not the listing of the languages which will be done in a follow-up ticket. At the moment, the language switch only passes a boolean but does nothing else - it will also be in the next ticket.

### Architectural changes

none

### Screenshots

![image](https://github.com/user-attachments/assets/8caf554b-651d-4206-b560-214f62df593e)

### Quality Assurance Checklist

- [x] Commit history is devoid of any _merge commits_ and readable to facilitate reviews
- [ ] If **new logic** ⚙️ is introduced: unit tests were added
- [x] If **new user stories** 🤏 are introduced: E2E tests were added
- [ ] If **new UI components** 🕹️ are introduced: corresponding stories in Storybook were created
- [ ] If **breaking changes** 🪚 are introduced: add the `breaking change` label
- [ ] If **bugs** 🐞 are fixed: add the `backport <release branch>` label
- [ ] The [documentation website](docs) 📚 has received the love it deserves

<!--
Please only check items relevant to your contribution. Thank you very much for your time and efforts!
-->

### How to test

Open and close the panel in any record form.



Diff:
diff --git a/apps/metadata-editor-e2e/src/e2e/edit/form-display.cy.ts b/apps/metadata-editor-e2e/src/e2e/edit/form-display.cy.ts
index 7caa83a88b..f0baf08161 100644
--- a/apps/metadata-editor-e2e/src/e2e/edit/form-display.cy.ts
+++ b/apps/metadata-editor-e2e/src/e2e/edit/form-display.cy.ts
@@ -69,4 +69,11 @@ describe('editor form', () => {
         .should('contain', 'Cette couche de points reprend les informations')
     })
   })
+  describe('Multilingual panel', () => {
+    it('opens the multilingual panel', () => {
+      cy.get('gn-ui-multilingual-panel').should('not.exist')
+      cy.get('md-editor-top-toolbar').find('gn-ui-button').eq(1).click()
+      cy.get('gn-ui-multilingual-panel').should('be.visible')
+    })
+  })
 })
diff --git a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
index 1c5e499187..c08e4a9665 100644
--- a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
+++ b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
@@ -61,8 +61,16 @@
     [title]="'editor.temporary.disabled' | translate"
   >
     <ng-icon name="matHelpOutlineOutline"></ng-icon>
-  </gn-ui-button>
+  </gn-ui-button> -->
   <gn-ui-button
+    [title]="'editor.record.form.multilingual.open' | translate"
+    [extraClass]="translatePanelOpen ? 'bg-neutral-200 border-0' : ''"
+    (buttonClick)="toggleTranslatePanel()"
+    type="light"
+  >
+    <ng-icon name="iconoirTranslate"></ng-icon>
+  </gn-ui-button>
+  <!-- <gn-ui-button
     type="light"
     disabled="true"
     [title]="'editor.temporary.disabled' | translate"
diff --git a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
index 89f7ec3180..a4659b43a6 100644
--- a/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
+++ b/apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
@@ -1,5 +1,10 @@
 import { CommonModule } from '@angular/common'
-import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
+import {
+  ChangeDetectionStrategy,
+  Component,
+  EventEmitter,
+  Output,
+} from '@angular/core'
 import { MatDialog, MatDialogModule } from '@angular/material/dialog'
 import { MatTooltipModule } from '@angular/material/tooltip'
 import { EditorFacade } from '@geonetwork-ui/feature/editor'
@@ -21,6 +26,7 @@ import {
   iconoirDownload,
   iconoirLightBulb,
   iconoirSidebarCollapse,
+  iconoirTranslate,
   iconoirUndoAction,
 } from '@ng-icons/iconoir'
 import {
@@ -51,6 +57,7 @@ import {
       iconoirUndoAction,
       iconoirBadgeCheck,
       matHelpOutlineOutline,
+      iconoirTranslate,
     }),
     provideNgIconsConfig({
       size: '1.5rem',
@@ -61,6 +68,8 @@ import {
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class TopToolbarComponent {
+  @Output() openTranslatePanel = new EventEmitter(false)
+  translatePanelOpen = false
   protected SaveStatus = [
     'record_not_published', // => when the record is not published yet but saved
     'record_up_to_date', // => when the record was just published (ie saved on the server)
@@ -115,4 +124,9 @@ export class TopToolbarComponent {
       }
     })
   }
+
+  toggleTranslatePanel() {
+    this.translatePanelOpen = !this.translatePanelOpen
+    this.openTranslatePanel.emit(this.translatePanelOpen)
+  }
 }
diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.html b/apps/metadata-editor/src/app/edit/edit-page.component.html
index ea43cddcbe..de7c9c20f4 100644
--- a/apps/metadata-editor/src/app/edit/edit-page.component.html
+++ b/apps/metadata-editor/src/app/edit/edit-page.component.html
@@ -10,53 +10,60 @@
     class="flex flex-col h-full w-full"
   >
     <div class="w-full h-auto shrink-0 relative">
-      <md-editor-top-toolbar></md-editor-top-toolbar>
+      <md-editor-top-toolbar
+        (openTranslatePanel)="translatePanelOpen = $event"
+      ></md-editor-top-toolbar>
       <div class="absolute top-full left-0 w-2/3 z-10 pointer-events-none">
         <gn-ui-notifications-container></gn-ui-notifications-container>
       </div>
     </div>
-    <div class="grow overflow-auto p-[32px]" #scrollContainer>
-      <div class="container-lg mx-auto flex flex-col gap-[32px]">
-        <div *ngIf="hasRecordChanged$ | async as hasChanged">
+    <div class="flex flex-row flex-1 min-h-0">
+      <div class="grow overflow-auto p-[32px]" #scrollContainer>
+        <div class="container-lg mx-auto flex flex-col gap-[32px]">
+          <div *ngIf="hasRecordChanged$ | async as hasChanged">
+            <div
+              *ngIf="hasChanged.user && hasChanged.date"
+              class="p-4 border border-primary bg-primary-lightest rounded-lg text-center"
+              translate
+              [translateParams]="{
+                date: formatDate(hasChanged.date),
+                user: hasChanged.user,
+              }"
+              data-test="draft-alert"
+            >
+              editor.record.form.draft.updateAlert
+            </div>
+          </div>
+          <md-editor-page-selector></md-editor-page-selector>
+          <gn-ui-record-form></gn-ui-record-form>
           <div
-            *ngIf="hasChanged.user && hasChanged.date"
-            class="p-4 border border-primary bg-primary-lightest rounded-lg text-center"
-            translate
-            [translateParams]="{
-              date: formatDate(hasChanged.date),
-              user: hasChanged.user,
-            }"
-            data-test="draft-alert"
+            data-test="previousNextPageButtons"
+            class="mt-auto flex flex-row justify-between"
           >
-            editor.record.form.draft.updateAlert
+            <gn-ui-button
+              type="gray"
+              (buttonClick)="previousPageButtonHandler()"
+              translate
+            >
+              {{
+                (currentPage$ | async) === 0
+                  ? ('editor.record.form.bottomButtons.comeBackLater'
+                    | translate)
+                  : ('editor.record.form.bottomButtons.previous' | translate)
+              }}
+            </gn-ui-button>
+            <gn-ui-button
+              type="primary"
+              *ngIf="(isLastPage$ | async) === false"
+              (buttonClick)="nextPageButtonHandler()"
+            >
+              <span translate>editor.record.form.bottomButtons.next</span>
+            </gn-ui-button>
           </div>
         </div>
-        <md-editor-page-selector></md-editor-page-selector>
-        <gn-ui-record-form></gn-ui-record-form>
-        <div
-          data-test="previousNextPageButtons"
-          class="mt-auto flex flex-row justify-between"
-        >
-          <gn-ui-button
-            type="gray"
-            (buttonClick)="previousPageButtonHandler()"
-            translate
-          >
-            {{
-              (currentPage$ | async) === 0
-                ? ('editor.record.form.bottomButtons.comeBackLater' | translate)
-                : ('editor.record.form.bottomButtons.previous' | translate)
-            }}
-          </gn-ui-button>
-          <gn-ui-button
-            type="primary"
-            *ngIf="(isLastPage$ | async) === false"
-            (buttonClick)="nextPageButtonHandler()"
-          >
-            <span translate>editor.record.form.bottomButtons.next</span>
-          </gn-ui-button>
-        </div>
       </div>
+      <gn-ui-multilingual-panel *ngIf="translatePanelOpen">
+      </gn-ui-multilingual-panel>
     </div>
   </div>
   <div
diff --git a/apps/metadata-editor/src/app/edit/edit-page.component.ts b/apps/metadata-editor/src/app/edit/edit-page.component.ts
index cdad2ca623..04deb72c1d 100644
--- a/apps/metadata-editor/src/app/edit/edit-page.component.ts
+++ b/apps/metadata-editor/src/app/edit/edit-page.component.ts
@@ -12,13 +12,14 @@ import { marker } from '@biesbjerg/ngx-translate-extract-marker'
 import { PublicationVersionError } from '@geonetwork-ui/common/domain/model/error'
 import {
   EditorFacade,
+  MultilingualPanelComponent,
   RecordFormComponent,
 } from '@geonetwork-ui/feature/editor'
 import {
   NotificationsContainerComponent,
   NotificationsService,
 } from '@geonetwork-ui/feature/notifications'
-import { ButtonComponent } from '@geonetwork-ui/ui/inputs'
+import { ButtonComponent, CheckToggleComponent } from '@geonetwork-ui/ui/inputs'
 import { TranslateModule, TranslateService } from '@ngx-translate/core'
 import { combineLatest, filter, firstValueFrom, Subscription, take } from 'rxjs'
 import { map, skip } from 'rxjs/operators'
@@ -52,6 +53,7 @@ marker('editor.record.form.bottomButtons.next')
     SpinningLoaderComponent,
     SearchHeaderComponent,
     PageErrorComponent,
+    MultilingualPanelComponent,
   ],
 })
 export class EditPageComponent implements OnInit, OnDestroy {
@@ -68,6 +70,7 @@ export class EditPageComponent implements OnInit, OnDestroy {
 
   newRecord = false
   isLoading = true
+  translatePanelOpen = false
 
   @ViewChild('scrollContainer') scrollContainer: ElementRef<HTMLElement>
 
diff --git a/libs/feature/editor/src/index.ts b/libs/feature/editor/src/index.ts
index 37bc784d84..8eb8a7cb9e 100644
--- a/libs/feature/editor/src/index.ts
+++ b/libs/feature/editor/src/index.ts
@@ -11,3 +11,4 @@ export * from './lib/components/wizard/wizard.component'
 export * from './lib/components/wizard-field/wizard-field.component'
 export * from './lib/components/wizard-summarize/wizard-summarize.component'
 export * from './lib/components/record-form/form-field'
+export * from './lib/components/multilingual-panel/multilingual-panel.component'
diff --git a/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.css b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.html b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.html
new file mode 100644
index 0000000000..b3bc0e2090
--- /dev/null
+++ b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.html
@@ -0,0 +1,14 @@
+<div
+  class="flex flex-col h-full w-[302px] bg-neutral-100 border-l border-gray-300 py-9 px-3 gap-8"
+>
+  <span class="text-3xl font-title text-black/80 px-2" translate
+    >editor.record.form.multilingual.title</span
+  >
+  <gn-ui-check-toggle
+    class="p-2"
+    [label]="'editor.record.form.multilingual.enable' | translate"
+    [color]="'primary'"
+    [value]="translationsEnabled"
+    (toggled)="translationsEnabled = $event"
+  ></gn-ui-check-toggle>
+</div>
diff --git a/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.spec.ts b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.spec.ts
new file mode 100644
index 0000000000..7b7ae9d491
--- /dev/null
+++ b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing'
+import { MultilingualPanelComponent } from './multilingual-panel.component'
+import { TranslateModule } from '@ngx-translate/core'
+
+describe('MultilingualPanelComponent', () => {
+  let component: MultilingualPanelComponent
+  let fixture: ComponentFixture<MultilingualPanelComponent>
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [MultilingualPanelComponent, TranslateModule.forRoot()],
+    }).compileComponents()
+
+    fixture = TestBed.createComponent(MultilingualPanelComponent)
+    component = fixture.componentInstance
+    fixture.detectChanges()
+  })
+
+  it('should create', () => {
+    expect(component).toBeTruthy()
+  })
+})
diff --git a/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.ts b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.ts
new file mode 100644
index 0000000000..415dc275fe
--- /dev/null
+++ b/libs/feature/editor/src/lib/components/multilingual-panel/multilingual-panel.component.ts
@@ -0,0 +1,15 @@
+import { Component } from '@angular/core'
+import { CommonModule } from '@angular/common'
+import { CheckToggleComponent } from '@geonetwork-ui/ui/inputs'
+import { TranslateModule } from '@ngx-translate/core'
+
+@Component({
+  selector: 'gn-ui-multilingual-panel',
+  standalone: true,
+  imports: [CommonModule, CheckToggleComponent, TranslateModule],
+  templateUrl: './multilingual-panel.component.html',
+  styleUrl: './multilingual-panel.component.css',
+})
+export class MultilingualPanelComponent {
+  translationsEnabled = false
+}
diff --git a/translations/de.json b/translations/de.json
index b7447fc208..514604888c 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "",
   "editor.record.form.license.pddl": "",
   "editor.record.form.license.unknown": "",
+  "editor.record.form.multilingual.enable": "",
+  "editor.record.form.multilingual.open": "",
+  "editor.record.form.multilingual.title": "",
   "editor.record.form.page.accessAndContact": "",
   "editor.record.form.page.description": "",
   "editor.record.form.page.resources": "",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "Layout auswählen",
   "results.records.hits.displayedOn": "{displayed, plural, =0{Kein Datensatz.} one{1 Datensatz} other{{displayed} Datensätze }} {hits, plural, other{angezeigt von {hits} insgesamt.}}",
   "results.records.hits.empty.help.html": "Vorschläge: <ul class='list-disc list-inside'><li>Versuchen Sie es mit anderen Worten</li><li>Beschränken Sie die Suchwörter</li></ul>",
-  "search.filters.recordKind.all": "Alle",
-  "search.filters.recordKind.dataset": "Datensätze",
-  "search.filters.recordKind.reuse": "Wiederverwendungen",
-  "search.filters.recordKind.service": "Dienste",
   "results.records.hits.found": "{hits, plural, =0{Keine Dokumente entsprechen der angegebenen Suche.} one{1 Datensatz gefunden.} other{{hits} Datensätze gefunden.}}",
   "results.records.hits.selected": "{ amount } ausgewählt",
   "results.showMore": "Mehr Ergebnisse anzeigen...",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "",
   "search.filters.publicationYear": "Veröffentlichungsjahr",
   "search.filters.publisherOrg": "",
+  "search.filters.recordKind.all": "Alle",
+  "search.filters.recordKind.dataset": "Datensätze",
+  "search.filters.recordKind.reuse": "Wiederverwendungen",
+  "search.filters.recordKind.service": "Dienste",
   "search.filters.representationType": "Repräsentationstyp",
   "search.filters.resourceType": "Ressourcentyp",
   "search.filters.standard": "Standard",
diff --git a/translations/en.json b/translations/en.json
index 18309ca8ba..cff8b28c0f 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "Open Data Commons ODC-By",
   "editor.record.form.license.pddl": "Open Data Commons PDDL",
   "editor.record.form.license.unknown": "Unknown or absent",
+  "editor.record.form.multilingual.enable": "Multilingual Mode",
+  "editor.record.form.multilingual.open": "Open the multilingual panel",
+  "editor.record.form.multilingual.title": "Translations",
   "editor.record.form.page.accessAndContact": "Information for use",
   "editor.record.form.page.description": "Dataset description",
   "editor.record.form.page.resources": "Resources",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "Results layout",
   "results.records.hits.displayedOn": "{displayed, plural, =0{No dataset.} one{1 } other{{displayed} }} {hits, plural, other{displayed on {hits} total.}}",
   "results.records.hits.empty.help.html": "Suggestions: <ul class='list-disc list-inside'><li>Try other words</li><li>Specify fewer words</li></ul>",
-  "search.filters.recordKind.all": "All",
-  "search.filters.recordKind.dataset": "Datasets",
-  "search.filters.recordKind.reuse": "Reuses",
-  "search.filters.recordKind.service": "Services",
   "results.records.hits.found": "{hits, plural, =0{No datasets match the specified search.} one{1 dataset found.} other{{hits} datasets found.}}",
   "results.records.hits.selected": "{ amount } selected",
   "results.showMore": "Show more results...",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "Producer",
   "search.filters.publicationYear": "Publication year",
   "search.filters.publisherOrg": "Publisher",
+  "search.filters.recordKind.all": "All",
+  "search.filters.recordKind.dataset": "Datasets",
+  "search.filters.recordKind.reuse": "Reuses",
+  "search.filters.recordKind.service": "Services",
   "search.filters.representationType": "Representation type",
   "search.filters.resourceType": "Resource type",
   "search.filters.standard": "Standard",
diff --git a/translations/es.json b/translations/es.json
index def277f79c..39fb17ccfc 100644
--- a/translations/es.json
+++ b/translations/es.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "",
   "editor.record.form.license.pddl": "",
   "editor.record.form.license.unknown": "",
+  "editor.record.form.multilingual.enable": "",
+  "editor.record.form.multilingual.open": "",
+  "editor.record.form.multilingual.title": "",
   "editor.record.form.page.accessAndContact": "",
   "editor.record.form.page.description": "",
   "editor.record.form.page.resources": "",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "",
   "results.records.hits.displayedOn": "",
   "results.records.hits.empty.help.html": "",
-  "search.filters.recordKind.all": "Todos",
-  "search.filters.recordKind.dataset": "",
-  "search.filters.recordKind.reuse": "",
-  "search.filters.recordKind.service": "",
   "results.records.hits.found": "",
   "results.records.hits.selected": "",
   "results.showMore": "",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "",
   "search.filters.publicationYear": "",
   "search.filters.publisherOrg": "",
+  "search.filters.recordKind.all": "Todos",
+  "search.filters.recordKind.dataset": "",
+  "search.filters.recordKind.reuse": "",
+  "search.filters.recordKind.service": "",
   "search.filters.representationType": "",
   "search.filters.resourceType": "",
   "search.filters.standard": "",
diff --git a/translations/fr.json b/translations/fr.json
index 7bdd1238ec..fa78ba1190 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "Open Data Commons ODC-By",
   "editor.record.form.license.pddl": "Open Data Commons PDDL",
   "editor.record.form.license.unknown": "Inconnue ou absente",
+  "editor.record.form.multilingual.enable": "Mode Multilingue",
+  "editor.record.form.multilingual.open": "Ouvrir le menu multilingue",
+  "editor.record.form.multilingual.title": "Traductions",
   "editor.record.form.page.accessAndContact": "Informations pour l'utilisation",
   "editor.record.form.page.description": "Description du jeu de données",
   "editor.record.form.page.resources": "Ressources",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "Affichage des résultats",
   "results.records.hits.displayedOn": "{displayed, plural, =0{Aucun jeu de données} one{1 affiché} other{{displayed} affichés}} {hits, plural, other{sur {hits} au total.}}",
   "results.records.hits.empty.help.html": "Suggestions : <ul class='list-disc list-inside'><li>Essayez d'autres mots-clés</li><li>Cherchez moins de mots</li></ul>",
-  "search.filters.recordKind.all": "Tous",
-  "search.filters.recordKind.dataset": "Jeux de données",
-  "search.filters.recordKind.reuse": "Réutilisations",
-  "search.filters.recordKind.service": "Services",
   "results.records.hits.found": "{hits, plural, =0{Aucune correspondance.} one{1 jeu de données trouvé.} other{{hits} jeux de données.}}",
   "results.records.hits.selected": "{amount, plural, one{1 selectionné} other{{ amount } sélectionnés}}",
   "results.showMore": "Plus de résultats...",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "Producteur",
   "search.filters.publicationYear": "Année de publication",
   "search.filters.publisherOrg": "Distributeur",
+  "search.filters.recordKind.all": "Tous",
+  "search.filters.recordKind.dataset": "Jeux de données",
+  "search.filters.recordKind.reuse": "Réutilisations",
+  "search.filters.recordKind.service": "Services",
   "search.filters.representationType": "Type de représentation",
   "search.filters.resourceType": "Type de ressource",
   "search.filters.standard": "Standard",
diff --git a/translations/it.json b/translations/it.json
index 0c941a1b4c..354b0f2a7e 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "Open Data Commons ODC-By",
   "editor.record.form.license.pddl": "Open Data Commons PDDL",
   "editor.record.form.license.unknown": "Sconosciuto o assente",
+  "editor.record.form.multilingual.enable": "",
+  "editor.record.form.multilingual.open": "",
+  "editor.record.form.multilingual.title": "",
   "editor.record.form.page.accessAndContact": "Informazioni per l'uso",
   "editor.record.form.page.description": "Descrizione del dataset",
   "editor.record.form.page.resources": "Risorse",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "Mostra risultati",
   "results.records.hits.displayedOn": "{displayed, plural, =0{Nessun record} one{1 record visualizzato} other{{displayed} records visualizzati}} {hits, plural, other{su {hits} in totale.}}",
   "results.records.hits.empty.help.html": "Suggerimenti: <ul class='list-disc list-inside'><li>Prova con altre parole chiave</li><li>Cerca con meno parole</li></ul>",
-  "search.filters.recordKind.all": "Tutti",
-  "search.filters.recordKind.dataset": "Dataset",
-  "search.filters.recordKind.reuse": "Riutilizzi",
-  "search.filters.recordKind.service": "Servizi",
   "results.records.hits.found": "{hits, plural, =0{Nessuna corrispondenza.} one{1 record trovato.} other{{hits} risultati.}}",
   "results.records.hits.selected": "{amount, plural, one{1 selezionato} other{{ amount } selezionati}}",
   "results.showMore": "Altri risultati...",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "Produttore",
   "search.filters.publicationYear": "Anno di pubblicazione",
   "search.filters.publisherOrg": "Distributore",
+  "search.filters.recordKind.all": "Tutti",
+  "search.filters.recordKind.dataset": "Dataset",
+  "search.filters.recordKind.reuse": "Riutilizzi",
+  "search.filters.recordKind.service": "Servizi",
   "search.filters.representationType": "Tipo di rappresentazione",
   "search.filters.resourceType": "Tipo di risorsa",
   "search.filters.standard": "Standard",
diff --git a/translations/nl.json b/translations/nl.json
index 29fcdb4095..02391bd6e5 100644
--- a/translations/nl.json
+++ b/translations/nl.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "",
   "editor.record.form.license.pddl": "",
   "editor.record.form.license.unknown": "",
+  "editor.record.form.multilingual.enable": "",
+  "editor.record.form.multilingual.open": "",
+  "editor.record.form.multilingual.title": "",
   "editor.record.form.page.accessAndContact": "",
   "editor.record.form.page.description": "",
   "editor.record.form.page.resources": "",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "",
   "results.records.hits.displayedOn": "",
   "results.records.hits.empty.help.html": "",
-  "search.filters.recordKind.all": "Alles",
-  "search.filters.recordKind.dataset": "",
-  "search.filters.recordKind.reuse": "",
-  "search.filters.recordKind.service": "",
   "results.records.hits.found": "",
   "results.records.hits.selected": "",
   "results.showMore": "",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "",
   "search.filters.publicationYear": "",
   "search.filters.publisherOrg": "",
+  "search.filters.recordKind.all": "Alles",
+  "search.filters.recordKind.dataset": "",
+  "search.filters.recordKind.reuse": "",
+  "search.filters.recordKind.service": "",
   "search.filters.representationType": "",
   "search.filters.resourceType": "",
   "search.filters.standard": "",
diff --git a/translations/pt.json b/translations/pt.json
index 8c5a01ebf0..ef2f48e28e 100644
--- a/translations/pt.json
+++ b/translations/pt.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "",
   "editor.record.form.license.pddl": "",
   "editor.record.form.license.unknown": "",
+  "editor.record.form.multilingual.enable": "",
+  "editor.record.form.multilingual.open": "",
+  "editor.record.form.multilingual.title": "",
   "editor.record.form.page.accessAndContact": "",
   "editor.record.form.page.description": "",
   "editor.record.form.page.resources": "",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "",
   "results.records.hits.displayedOn": "",
   "results.records.hits.empty.help.html": "",
-  "search.filters.recordKind.all": "Todos",
-  "search.filters.recordKind.dataset": "",
-  "search.filters.recordKind.reuse": "",
-  "search.filters.recordKind.service": "",
   "results.records.hits.found": "",
   "results.records.hits.selected": "",
   "results.showMore": "",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "",
   "search.filters.publicationYear": "",
   "search.filters.publisherOrg": "",
+  "search.filters.recordKind.all": "Todos",
+  "search.filters.recordKind.dataset": "",
+  "search.filters.recordKind.reuse": "",
+  "search.filters.recordKind.service": "",
   "search.filters.representationType": "",
   "search.filters.resourceType": "",
   "search.filters.standard": "",
diff --git a/translations/sk.json b/translations/sk.json
index 9ebf5a83ea..de9d607d28 100644
--- a/translations/sk.json
+++ b/translations/sk.json
@@ -204,6 +204,9 @@
   "editor.record.form.license.odc-by": "",
   "editor.record.form.license.pddl": "",
   "editor.record.form.license.unknown": "",
+  "editor.record.form.multilingual.enable": "",
+  "editor.record.form.multilingual.open": "",
+  "editor.record.form.multilingual.title": "",
   "editor.record.form.page.accessAndContact": "",
   "editor.record.form.page.description": "",
   "editor.record.form.page.resources": "",
@@ -467,10 +470,6 @@
   "results.layout.selectOne": "Rozmiestnenie výsledkov",
   "results.records.hits.displayedOn": "{displayed, plural, =0{Žiadny záznam.} one{1 záznam} other{{displayed} záznamov }} {hits, plural, other{zobrazených z {hits} celkovo.}}",
   "results.records.hits.empty.help.html": "Návrhy: <ul class='list-disc list-inside'><li>Vyskúšajte iné slová</li><li>Uveďte menej slov</li></ul>",
-  "search.filters.recordKind.all": "Všetky",
-  "search.filters.recordKind.dataset": "",
-  "search.filters.recordKind.reuse": "",
-  "search.filters.recordKind.service": "",
   "results.records.hits.found": "{hits, plural, =0{Žiadne dokumenty nezodpovedajú zadanému vyhľadávaniu.} one{1 záznam nájdený.} other{{hits} záznamov nájdených.}}",
   "results.records.hits.selected": "{ amount } označených",
   "results.showMore": "Zobraziť viac výsledkov...",
@@ -519,6 +518,10 @@
   "search.filters.producerOrg": "",
   "search.filters.publicationYear": "Rok zverejnenia",
   "search.filters.publisherOrg": "",
+  "search.filters.recordKind.all": "Všetky",
+  "search.filters.recordKind.dataset": "",
+  "search.filters.recordKind.reuse": "",
+  "search.filters.recordKind.service": "",
   "search.filters.representationType": "Typ reprezentácie",
   "search.filters.resourceType": "Typ zdroja",
   "search.filters.standard": "Štandard",


Reviews:
user: login: AlitaBernachot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T09:25:30Z
user: login: AlitaBernachot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T09:27:31Z
user: login: AlitaBernachot
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T09:35:30Z
user: login: AlitaBernachot
body: Thx, working great!

I juste noticed two minor things (i don't know if this is related to your work though):

![image](https://github.com/user-attachments/assets/82b53fc3-89b0-4424-a392-48ba01a9e0e6)

When the panel is open on small screen, there is a minimal width added to the viewport and we need to scroll right to go see the i18n panel. But maybe this one can be ignored, i don't know if we have rules for small screen sizes on the editor.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-24T09:39:36Z
user: login: cmoinier
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-24T09:59:33Z

Review comments:
diff_hunk: @@ -69,4 +69,10 @@ describe('editor form', () => {
         .should('contain', 'Cette couche de points reprend les informations')
     })
   })
+  describe('Multilingual panel', () => {
+    it('opens the multilingual panel', () => {
+      cy.get('md-editor-top-toolbar').find('gn-ui-button').eq(1).click()
path: apps/metadata-editor-e2e/src/e2e/edit/form-display.cy.ts
user: login: AlitaBernachot
body: maybe test if its not visible before clicking?
created_at: 2025-04-24T09:25:30Z
author_association: COLLABORATOR
diff_hunk: @@ -115,4 +124,9 @@ export class TopToolbarComponent {
       }
     })
   }
+
+  handleTranslatePanel() {
path: apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.ts
user: login: AlitaBernachot
body: How about?
```suggestion
  toggleTranslatePanel() {
```
created_at: 2025-04-24T09:27:30Z
author_association: COLLABORATOR
diff_hunk: @@ -61,8 +61,15 @@
     [title]="'editor.temporary.disabled' | translate"
   >
     <ng-icon name="matHelpOutlineOutline"></ng-icon>
-  </gn-ui-button>
+  </gn-ui-button> -->
   <gn-ui-button
+    [extraClass]="translatePanelOpen ? 'bg-neutral-200 border-0' : ''"
+    (buttonClick)="handleTranslatePanel()"
+    type="light"
+  >
+    <ng-icon name="iconoirTranslate"></ng-icon>
path: apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
user: login: AlitaBernachot
body: Not very important: it would be great if the icon button has a label/title/tootltip saying this would open the translation panel (for accessibility)?
created_at: 2025-04-24T09:35:30Z
author_association: COLLABORATOR
diff_hunk: @@ -61,8 +61,15 @@
     [title]="'editor.temporary.disabled' | translate"
   >
     <ng-icon name="matHelpOutlineOutline"></ng-icon>
-  </gn-ui-button>
+  </gn-ui-button> -->
   <gn-ui-button
+    [extraClass]="translatePanelOpen ? 'bg-neutral-200 border-0' : ''"
+    (buttonClick)="handleTranslatePanel()"
+    type="light"
+  >
+    <ng-icon name="iconoirTranslate"></ng-icon>
path: apps/metadata-editor/src/app/edit/components/top-toolbar/top-toolbar.component.html
user: login: cmoinier
body: Yes, will add it :)
created_at: 2025-04-24T09:59:33Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-24T09:06:41Z
body: **Affected libs:**
feature-editor

**Affected apps:**
metadata-editor,demo

- [ ] 🚀 Build and deploy storybook and demo on GitHub Pages
- [ ] 📦 Build and push affected docker images
<!-- thollander/actions-comment-pull-request "build-options" -->
author_association: CONTRIBUTOR
user: login: coveralls
created_at: 2025-04-24T09:15:29Z
body: 
[![Coverage Status](https://coveralls.io/builds/73365861/badge)](https://coveralls.io/builds/73365861)

coverage: 83.017% (+0.8%) from 82.183%
when pulling **a94236fbf73be65210aa6aefd0afbce323af4add on multilingual-side-panel**
into **01f702fed34bc626fc79858418f6a87044f59c55 on main**.

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-24T09:22:31Z
body: 📷 Screenshots are [here](https://github.com/geonetwork/geonetwork-ui/actions/runs/14641095791/artifacts/3001639189)!
<!-- thollander/actions-comment-pull-request "screenshots-url" -->
author_association: CONTRIBUTOR
user: login: cmoinier
created_at: 2025-04-24T10:19:47Z
body: > Thx, working great!
> 
> I juste noticed two minor things (i don't know if this is related to your work though):
> 
> ![image](https://private-user-images.githubusercontent.com/115457104/436950596-82b53fc3-89b0-4424-a392-48ba01a9e0e6.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDU0ODgzMTEsIm5iZiI6MTc0NTQ4ODAxMSwicGF0aCI6Ii8xMTU0NTcxMDQvNDM2OTUwNTk2LTgyYjUzZmMzLTg5YjAtNDQyNC1hMzkyLTQ4YmEwMWE5ZTBlNi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwNDI0JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDQyNFQwOTQ2NTFaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1mYTIwZjI1YTZlNWUwZTEwNGU5N2M1ODg3Yjg5MGY0NTEzNzczZDg1NzljNDQ2MzNmZTQzOTMzNzZjYmU1Y2U5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.fc_71DdZrRp00ab59N9LWUabD2Ldk6407vJAIt-A-vE)
> 
> When the panel is open on small screen, there is a minimal width added to the viewport and we need to scroll right to go see the i18n panel. But maybe this one can be ignored, i don't know if we have rules for small screen sizes on the editor.

I have to agree with you - the right panel going all the way down and the left stopping at screen size is not good looking. However, this is what the mockups show :slightly_frowning_face: The panel will definitely not use the whole height, so I will make it the same size than the left panel (I think the left one as to stay the same height or else the logout button will be at the very bottom and it's not very sweet for the users).

For the screen size, yes, I noticed too, but I think we would need a specific behavior of the translations panel for small screens because reducing the size of the form or the size of the panel is not the solution IMO.
author_association: COLLABORATOR
user: login: cmoinier
created_at: 2025-04-24T12:06:36Z
body: @AlitaBernachot Changes done! Could you give me your opinion on the changes regarding the white space issue you mentionned ? The resolution is a bit different than what the mockups say (but they're not showing how the scroll is supposed to work though), but I actually feel like this is an improvement because IMO it looked weird to scroll all the way down in the form and "loose" the left menu and top toolbar at some point. Now the scrolling is inside of the form making all menus (left, right, and top toolbar)  visible at all times, and I think it makes more sense. Let me know ! :)
author_association: COLLABORATOR
user: login: AlitaBernachot
created_at: 2025-04-24T12:16:33Z
body: > @AlitaBernachot Changes done! Could you give me your opinion on the changes regarding the white space issue you mentionned ? The resolution is a bit different than what the mockups say (but they're not showing how the scroll is supposed to work though), but I actually feel like this is an improvement because IMO it looked weird to scroll all the way down in the form and "loose" the left menu and top toolbar at some point. Now the scrolling is inside of the form making all menus (left, right, and top toolbar) visible at all times, and I think it makes more sense. Let me know ! :)

That is pretty good! thx for the improvement.
author_association: COLLABORATOR