diff --git a/geonode_mapstore_client/client/js/actions/gnresource.js b/geonode_mapstore_client/client/js/actions/gnresource.js
index da52044747..b0a544dfc0 100644
--- a/geonode_mapstore_client/client/js/actions/gnresource.js
+++ b/geonode_mapstore_client/client/js/actions/gnresource.js
@@ -332,7 +332,7 @@ export function setResourceExtent(coords) {
export function updateResourceExtent() {
return {
- type: UPDATE_RESOURCE_EXTENT,
+ type: UPDATE_RESOURCE_EXTENT
};
}
diff --git a/geonode_mapstore_client/client/js/api/geonode/v2/index.js b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
index ef2ba78ddb..4967caaa00 100644
--- a/geonode_mapstore_client/client/js/api/geonode/v2/index.js
+++ b/geonode_mapstore_client/client/js/api/geonode/v2/index.js
@@ -742,7 +742,7 @@ export const getMetadataDownloadLinkByPk = (pk) => {
export const updateResourceExtent = (pk) => {
return axios.put(getEndpointUrl(DATASETS, `/${pk}/recalc-bbox`))
.then(({ data }) => data);
-}
+};
export default {
getEndpoints,
diff --git a/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx b/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx
index 607f4590ee..ff1e27bc3b 100644
--- a/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx
+++ b/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_fields/SchemaField.jsx
@@ -15,6 +15,7 @@ import template from 'lodash/template';
import Autocomplete from '../Autocomplete';
import DefaultSchemaField from '@rjsf/core/lib/components/fields/SchemaField';
import useSchemaReference from './useSchemaReference';
+import TextWidgetMultiLang from '../_widgets/TextWidgetMultiLang';
function findProperty(name, properties) {
return Object.keys(properties || {}).some((key) => {
@@ -42,6 +43,7 @@ function shouldHideLabel({
* - Fallback to the default `SchemaField` from `@rjsf` when no custom rendering is needed.
*/
const SchemaField = (props) => {
+
const {
onChange,
schema,
@@ -53,6 +55,8 @@ const SchemaField = (props) => {
required,
formContext
} = props;
+
+ const uiWidget = uiSchema?.['ui:widget']?.toLowerCase();
const uiOptions = uiSchema?.['ui:options'];
const autocomplete = uiOptions?.['geonode-ui:autocomplete'];
const isSchemaItemString = schema?.items?.type === 'string';
@@ -179,6 +183,11 @@ const SchemaField = (props) => {
return ;
}
+ // this override ObjectFieldTemplate
+ if (uiWidget === 'textwidgetmultilang') {
+ return ;
+ }
+
const hideLabel = shouldHideLabel(props);
return (
;
diff --git a/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_widgets/TextWidgetMultiLang.jsx b/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_widgets/TextWidgetMultiLang.jsx
new file mode 100644
index 0000000000..4ce55be764
--- /dev/null
+++ b/geonode_mapstore_client/client/js/plugins/MetadataEditor/components/_widgets/TextWidgetMultiLang.jsx
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2026, GeoSolutions Sas.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React, { useState } from "react";
+import isEmpty from 'lodash/isEmpty';
+import isString from 'lodash/isString';
+
+import DefaultTextareaWidget from '@rjsf/core/lib/components/widgets/TextareaWidget';
+import IconWithTooltip from '../IconWithTooltip';
+import { getMessageById, getSupportedLocales } from '@mapstore/framework/utils/LocaleUtils';
+
+const TextWidgetMultiLang = (props) => {
+
+ const { formData, onChange, schema, required, formContext } = props;
+
+ const id = props.idSchema?.$id;
+ const { title, description } = schema;
+
+ const languages = Object.keys(schema?.properties);
+ const languageLabels = languages.reduce((acc, lang) => {
+ const label = schema?.properties?.[lang]?.['geonode:multilang-lang-label'];
+ acc[lang] = label;
+ return acc;
+ }, {});
+
+ const getLanguageName = (langCode) => {
+ const languagesNames = getSupportedLocales();
+ const langName = languageLabels[langCode] || languagesNames[langCode]?.description
+ return `${langName} (${langCode})`
+ };
+
+ const isTextarea = schema?.['ui:options']?.widget === 'textarea';
+
+ const [currentLang, setCurrentLang] = useState(languages[0]);
+ const values = formData || {};
+
+ const handleInputChange = (ev) => {
+ const value = isString(ev) ? ev : ev?.target?.value;
+ const newValue = {
+ ...values,
+ [currentLang]: value
+ };
+
+ onChange(newValue);
+ };
+
+ const placeholder = getMessageById(formContext.messages, "gnviewer.typeText").replace("{lang}", getLanguageName(currentLang));
+
+ return (
+
@@ -117,14 +232,15 @@ function MetadataEditor({
readonly={readOnly}
ref={initialize.current}
formContext={{
- title: metadata?.title,
- metadata,
- capitalizeTitle: capitalizeFieldTitle
+ title: metadata.title || metadataMultiLang.title.en || getMessageById(messages, 'gnviewer.metadataEditorTitle'),
+ metadata: metadataMultiLang,
+ capitalizeTitle: capitalizeFieldTitle,
+ messages
}}
- schema={schema}
+ schema={schemaMultiLang}
+ uiSchema={uiSchemaMultiLang}
+ formData={metadataMultiLang}
widgets={widgets}
- uiSchema={uiSchema}
- formData={metadata}
validator={validator}
templates={templates}
fields={fields}
@@ -169,3 +285,5 @@ MetadataEditor.defaultProps = {
};
export default MetadataEditor;
+
+
diff --git a/geonode_mapstore_client/client/js/plugins/Operation/components/UploadPanel.jsx b/geonode_mapstore_client/client/js/plugins/Operation/components/UploadPanel.jsx
index ea8b37d106..e622710f22 100644
--- a/geonode_mapstore_client/client/js/plugins/Operation/components/UploadPanel.jsx
+++ b/geonode_mapstore_client/client/js/plugins/Operation/components/UploadPanel.jsx
@@ -57,7 +57,7 @@ function UploadPanel({
remoteTypeErrorMessageId,
remoteTypeFromUrl,
isRemoteTypesDisabled,
- uploadActions=[{ labelId: 'gnviewer.upload' }],
+ uploadActions = [{ labelId: 'gnviewer.upload' }]
}) {
const inputFile = useRef();
@@ -113,7 +113,6 @@ function UploadPanel({
return handleAdd([getDefaultRemoteResource({ id: uuidv1(), type: 'remote', url: '' })]);
};
-
const supportedLabels = uniq(supportedFiles.map(supportedFile => supportedFile.label)).join(', ');
const uploadsList = uploads.filter(upload => upload.type === 'file' ? upload.supported : true);
@@ -224,20 +223,20 @@ function UploadPanel({
:
!loading ? (
- <>
- {uploadActions?.map(({ labelId, variant, action, showConfirm: shouldConfirm }, id) => (
-
- ))}
+ <>
+ {uploadActions?.map(({ labelId, variant, action, showConfirm: shouldConfirm }, id) => (
+
+ ))}
>
- ) :