From 24e5e8a6792ca872f6ff7c218e6391275158a963 Mon Sep 17 00:00:00 2001 From: Ian Mayo Date: Fri, 4 Apr 2025 19:53:30 +0100 Subject: [PATCH 01/14] Translations on welcome page. --- package.json | 3 ++ src/components/LanguageSelector/index.tsx | 33 ++++++++++++++ src/components/WelcomePage/index.tsx | 22 +++++++--- src/i18n/i18n.ts | 52 +++++++++++++++++++++++ src/i18n/locales/de.json | 14 ++++++ src/i18n/locales/en-US.json | 14 ++++++ src/i18n/locales/fr.json | 14 ++++++ src/i18n/locales/it.json | 14 ++++++ src/i18n/locales/nl.json | 14 ++++++ src/main.tsx | 2 + yarn.lock | 41 ++++++++++++++++++ 11 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 src/components/LanguageSelector/index.tsx create mode 100644 src/i18n/i18n.ts create mode 100644 src/i18n/locales/de.json create mode 100644 src/i18n/locales/en-US.json create mode 100644 src/i18n/locales/fr.json create mode 100644 src/i18n/locales/it.json create mode 100644 src/i18n/locales/nl.json diff --git a/package.json b/package.json index b8848943..18013d36 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,8 @@ "dom-to-image": "^2.6.0", "electron-window-state": "^5.0.3", "font-gis": "^1.0.6", + "i18next": "^24.2.3", + "i18next-browser-languagedetector": "^8.0.4", "leaflet": "^1.9.4", "leaflet.nauticscale": "^1.1.0", "leaflet.polylinemeasure": "^3.0.0", @@ -49,6 +51,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-error-boundary": "^5.0.0", + "react-i18next": "^15.4.1", "react-leaflet": "^5.0.0", "react-leaflet-custom-control": "^1.4.0", "react-leaflet-geoman-v2": "^1.0.1", diff --git a/src/components/LanguageSelector/index.tsx b/src/components/LanguageSelector/index.tsx new file mode 100644 index 00000000..232b922d --- /dev/null +++ b/src/components/LanguageSelector/index.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { Select } from 'antd' +import { useTranslation } from 'react-i18next' + +const { Option } = Select + +const LanguageSelector: React.FC = () => { + const { i18n, t } = useTranslation() + + const handleLanguageChange = (value: string) => { + i18n.changeLanguage(value) + } + + return ( +
+ {t('common.language')}: + +
+ ) +} + +export default LanguageSelector diff --git a/src/components/WelcomePage/index.tsx b/src/components/WelcomePage/index.tsx index 87e4f0a9..64e23e5d 100644 --- a/src/components/WelcomePage/index.tsx +++ b/src/components/WelcomePage/index.tsx @@ -1,4 +1,6 @@ import { Button, Col, Image, Row, Typography } from 'antd' +import { useTranslation } from 'react-i18next' +import LanguageSelector from '../LanguageSelector' import './styles.css' interface WelcomePageProps { @@ -21,10 +23,11 @@ const WelcomePage: React.FC = ({ const buildDate = import.meta.env.VITE_BUILD_DATE ? new Date(import.meta.env.VITE_BUILD_DATE).toLocaleString() : 'unknown' + const { t } = useTranslation() return (
- Welcome to Albatross + {t('welcome.title')}   @@ -36,25 +39,32 @@ const WelcomePage: React.FC = ({   - Open an existing document or create a new one + {t('welcome.subtitle')} - - + + - + + {/* Language selector */} + + + + + + {/* Debug info box */}
- Branch: {gitBranch} | Build: {buildDate} + {t('common.branch')}: {gitBranch} | {t('common.build')}: {buildDate}
) diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts new file mode 100644 index 00000000..0bc91ab0 --- /dev/null +++ b/src/i18n/i18n.ts @@ -0,0 +1,52 @@ +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' +import LanguageDetector from 'i18next-browser-languagedetector' + +import enUS from './locales/en-US.json' +import it from './locales/it.json' +import nl from './locales/nl.json' +import fr from './locales/fr.json' +import de from './locales/de.json' + +// Initialize i18next +i18n + // Detect user language + .use(LanguageDetector) + // Pass the i18n instance to react-i18next + .use(initReactI18next) + // Initialize i18next + .init({ + // Default language + fallbackLng: 'en-US', + // Debug mode + debug: false, + // Resources containing translations + resources: { + 'en-US': { + translation: enUS + }, + it: { + translation: it + }, + nl: { + translation: nl + }, + fr: { + translation: fr + }, + de: { + translation: de + } + }, + // Language detection options + detection: { + order: ['localStorage', 'navigator'], + caches: ['localStorage'] + }, + // Interpolation configuration + interpolation: { + escapeValue: false // React already escapes values + } + }) + +export default i18n diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json new file mode 100644 index 00000000..95088ea4 --- /dev/null +++ b/src/i18n/locales/de.json @@ -0,0 +1,14 @@ +{ + "welcome": { + "title": "Willkommen bei Albatross", + "subtitle": "Öffnen Sie ein vorhandenes Dokument oder erstellen Sie ein neues", + "new": "Neu", + "samplePlot": "Beispiel", + "open": "Öffnen" + }, + "common": { + "branch": "Branch", + "build": "Build", + "language": "Sprache" + } +} diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json new file mode 100644 index 00000000..8c4caa32 --- /dev/null +++ b/src/i18n/locales/en-US.json @@ -0,0 +1,14 @@ +{ + "welcome": { + "title": "Welcome to Albatross", + "subtitle": "Open an existing document or create a new one", + "new": "New", + "samplePlot": "Sample plot", + "open": "Open" + }, + "common": { + "branch": "Branch", + "build": "Build", + "language": "Language" + } +} diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json new file mode 100644 index 00000000..eb2ee728 --- /dev/null +++ b/src/i18n/locales/fr.json @@ -0,0 +1,14 @@ +{ + "welcome": { + "title": "Bienvenue sur Albatross", + "subtitle": "Ouvrez un document existant ou créez-en un nouveau", + "new": "Nouveau", + "samplePlot": "Exemple", + "open": "Ouvrir" + }, + "common": { + "branch": "Branche", + "build": "Compilation", + "language": "Langue" + } +} diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json new file mode 100644 index 00000000..fdb83e2f --- /dev/null +++ b/src/i18n/locales/it.json @@ -0,0 +1,14 @@ +{ + "welcome": { + "title": "Benvenuto in Albatross", + "subtitle": "Apri un documento esistente o creane uno nuovo", + "new": "Nuovo", + "samplePlot": "Esempio", + "open": "Apri" + }, + "common": { + "branch": "Ramo", + "build": "Compilazione", + "language": "Lingua" + } +} diff --git a/src/i18n/locales/nl.json b/src/i18n/locales/nl.json new file mode 100644 index 00000000..83ee1ed6 --- /dev/null +++ b/src/i18n/locales/nl.json @@ -0,0 +1,14 @@ +{ + "welcome": { + "title": "Welkom bij Albatross", + "subtitle": "Open een bestaand document of maak een nieuw document", + "new": "Nieuw", + "samplePlot": "Voorbeeldplot", + "open": "Openen" + }, + "common": { + "branch": "Branch", + "build": "Build", + "language": "Taal" + } +} diff --git a/src/main.tsx b/src/main.tsx index 444cae67..71a8a472 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -7,6 +7,8 @@ import { AppContextProvider } from './state/AppContextProvider' import { ErrorBoundary } from 'react-error-boundary' import '@ant-design/v5-patch-for-react-19' // shims to allow ant5 work on react 19 import { ConfigProvider, theme } from 'antd' +// Import i18n configuration +import './i18n/i18n' import { AppContext } from './state/AppContext' const fallbackRender: React.FC<{ error: Error, resetErrorBoundary: () => void }> = ({ error, resetErrorBoundary }) => { diff --git a/yarn.lock b/yarn.lock index d71824e5..3178f24a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -960,6 +960,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.25.0", "@babel/runtime@^7.26.10": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.0.tgz#fbee7cf97c709518ecc1f590984481d5460d4762" + integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.25.9", "@babel/template@^7.3.3": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" @@ -7346,6 +7353,13 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -7409,6 +7423,20 @@ husky@^9.1.7: resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" integrity sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== +i18next-browser-languagedetector@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.4.tgz#9b16f6440b6aad3521f2ab1a2ffbb7d917397df2" + integrity sha512-f3frU3pIxD50/Tz20zx9TD9HobKYg47fmAETb117GKGPrhwcSSPJDoCposXlVycVebQ9GQohC3Efbpq7/nnJ5w== + dependencies: + "@babel/runtime" "^7.23.2" + +i18next@^24.2.3: + version "24.2.3" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-24.2.3.tgz#3a05f72615cbd7c00d7e348667e2aabef1df753b" + integrity sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A== + dependencies: + "@babel/runtime" "^7.26.10" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -10054,6 +10082,14 @@ react-fast-compare@^3.2.0: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== +react-i18next@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.4.1.tgz#33f3e89c2f6c68e2bfcbf9aa59986ad42fe78758" + integrity sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw== + dependencies: + "@babel/runtime" "^7.25.0" + html-parse-stringify "^3.0.1" + react-icons@^4.2.0: version "4.12.0" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.12.0.tgz#54806159a966961bfd5cdb26e492f4dafd6a8d78" @@ -11946,6 +11982,11 @@ vite@^5.4.10: optionalDependencies: fsevents "~2.3.3" +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== + walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" From e629fe543a523da2f107d6a12ce62156ec22c48d Mon Sep 17 00:00:00 2001 From: Ian Mayo Date: Fri, 4 Apr 2025 19:54:35 +0100 Subject: [PATCH 02/14] better symbol --- src/components/LanguageSelector/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/LanguageSelector/index.tsx b/src/components/LanguageSelector/index.tsx index 232b922d..b730b14d 100644 --- a/src/components/LanguageSelector/index.tsx +++ b/src/components/LanguageSelector/index.tsx @@ -1,11 +1,12 @@ import React from 'react' import { Select } from 'antd' import { useTranslation } from 'react-i18next' +import { GlobalOutlined } from '@ant-design/icons' const { Option } = Select const LanguageSelector: React.FC = () => { - const { i18n, t } = useTranslation() + const { i18n } = useTranslation() const handleLanguageChange = (value: string) => { i18n.changeLanguage(value) @@ -13,7 +14,7 @@ const LanguageSelector: React.FC = () => { return (
- {t('common.language')}: + - label='Visible' + label={t('forms.common.visible')} name={'visible'} style={itemStyle} valuePropName="checked" > @@ -64,24 +66,24 @@ export const BackdropForm: React.FC = ({backdrop, onChange, c { create && <> - label='URL' + label={t('forms.common.url')} name='url' style={itemStyle} - rules={[{ required: true, message: 'Please enter backdrop URL!' }]}> + rules={[{ required: true, message: t('forms.common.urlRequired') }]}> - label='Max Native Zoom' + label={t('forms.common.maxNativeZoom')} name= 'maxNativeZoom' style={itemStyle} - rules={[{ required: true, message: 'Please enter backdrop max native zoom!' }]}> + rules={[{ required: true, message: t('forms.common.maxNativeZoomRequired') }]}> - label='Max Zoom' + label={t('forms.common.maxZoom')} name='maxZoom' style={itemStyle} - rules={[{ required: true, message: 'Please enter backdrop max zoom!' }]}> + rules={[{ required: true, message: t('forms.common.maxZoomRequired') }]}> } diff --git a/src/components/BuoyFieldForm/index.tsx b/src/components/BuoyFieldForm/index.tsx index 04edc63e..c2f986a8 100644 --- a/src/components/BuoyFieldForm/index.tsx +++ b/src/components/BuoyFieldForm/index.tsx @@ -8,6 +8,7 @@ import { } from 'antd' import { Color } from 'antd/es/color-picker' import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import { BuoyFieldProps } from '../../types' import { presetColors } from '../../helpers/standardShades' import './index.css' @@ -29,6 +30,7 @@ export const BuoyFieldForm: React.FC = ({ field, onChange, }) => { + const { t } = useTranslation() const [state, setState] = useState(null) useEffect(() => { @@ -96,23 +98,23 @@ export const BuoyFieldForm: React.FC = ({ size='small' > - label='Name' + label={t('forms.common.name')} name='name' style={itemStyle} - rules={[{ required: true, message: 'Please enter track name!' }]} + rules={[{ required: true, message: t('forms.common.nameRequired') }]} > - label='Short name' + label={t('forms.common.shortName')} name='shortName' style={itemStyle} - rules={[{ required: true, message: 'Please enter short name!' }]} + rules={[{ required: true, message: t('forms.common.shortNameRequired') }]} > - label='Visible' + label={t('forms.common.visible')} name={'visible'} style={itemStyle} valuePropName='checked' @@ -120,10 +122,10 @@ export const BuoyFieldForm: React.FC = ({ - label='Color' + label={t('forms.common.color')} name='marker-color' style={itemStyle} - rules={[{ required: true, message: 'color is required!' }]} + rules={[{ required: true, message: t('forms.common.colorRequired') }]} > = ({ presets={presetColors} /> - label='Time' style={itemStyle} name='dTime'> + label={t('forms.common.time')} style={itemStyle} name='dTime'> - label='Time end' + label={t('forms.common.timeEnd')} style={itemStyle} // validate that dTimeEnd is after dTime rules={[ @@ -145,7 +147,7 @@ export const BuoyFieldForm: React.FC = ({ // if there is a value, check if it's after the dTime return !value || getFieldValue('dTime') < value ? Promise.resolve() - : Promise.reject(new Error('Time-end must be after time!')) + : Promise.reject(new Error(t('forms.common.timeEndAfterTime'))) }, }), ]} diff --git a/src/components/MultiFeatureForm/index.tsx b/src/components/MultiFeatureForm/index.tsx index 26c33978..cc3dc020 100644 --- a/src/components/MultiFeatureForm/index.tsx +++ b/src/components/MultiFeatureForm/index.tsx @@ -5,6 +5,7 @@ import { ExportOutlined } from '@ant-design/icons' import { Feature, GeoJsonProperties, Geometry } from 'geojson' +import { useTranslation } from 'react-i18next' import { useAppDispatch } from '../../state/hooks' import { Color } from 'antd/es/color-picker' import { presetColors } from '../../helpers/standardShades' @@ -26,6 +27,7 @@ const MultiFeatureForm: React.FC = ({ onDelete, onExport }) => { + const { t } = useTranslation() const dispatch = useAppDispatch() // Check if all features have the same visibility @@ -71,20 +73,20 @@ const MultiFeatureForm: React.FC = ({ return (
- {features.length} items selected + {features.length} {t('forms.multiFeature.itemsSelected')}
- - + + - - + + = ({ /> - - + + + - label='Start' + label={t('forms.common.start')} name='dTime' style={itemStyle}> - label='End' + label={t('forms.common.end')} name='dTimeEnd' style={itemStyle}> diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index 23f2d6ca..6492855d 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -90,5 +90,56 @@ "addZone": "Neue Zone hinzufügen", "addPoint": "Neuen Punkt hinzufügen", "addBackdrop": "Neuen Hintergrund hinzufügen" + }, + "forms": { + "core": { + "delete": "Löschen", + "deleteFeature": "Element löschen", + "cancel": "Abbrechen", + "cancelCreation": "Erstellung eines neuen Elements abbrechen", + "reset": "Zurücksetzen", + "resetChanges": "Änderungen für dieses Element zurücksetzen", + "save": "Speichern", + "create": "Erstellen", + "saveEdits": "Änderungen speichern" + }, + "common": { + "name": "Name", + "shortName": "Kurzname", + "visible": "Sichtbar", + "color": "Farbe", + "fill": "Füllung", + "colorRequired": "Farbe ist erforderlich!", + "nameRequired": "Bitte geben Sie einen Namen ein!", + "shortNameRequired": "Bitte geben Sie einen Kurznamen ein!", + "position": "Position", + "time": "Zeit", + "timeEnd": "Endzeit", + "timeEndAfterTime": "Die Endzeit muss nach der Startzeit liegen!", + "start": "Start", + "end": "Ende", + "url": "URL", + "urlRequired": "Bitte geben Sie eine URL ein!", + "maxNativeZoom": "Maximaler nativer Zoom", + "maxNativeZoomRequired": "Bitte geben Sie den maximalen nativen Zoom ein!", + "maxZoom": "Maximaler Zoom", + "maxZoomRequired": "Bitte geben Sie den maximalen Zoom ein!", + "environment": "Umgebung", + "environmentRequired": "Bitte geben Sie die Umgebung/Symbol an", + "markers": "Markierungen", + "labels": "Beschriftungen", + "symbols": "Symbole", + "shape": "Form", + "edit": "Bearbeiten" + }, + "multiFeature": { + "itemsSelected": "Elemente ausgewählt", + "visibility": "Sichtbarkeit", + "hideAll": "Alle ausblenden", + "showAll": "Alle anzeigen", + "mixedVisibility": "Gemischte Sichtbarkeit, klicken Sie, um alle auszublenden", + "currentColor": "Aktuelle Farbe:", + "mixedColors": "Gemischte Farben" + } } } diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 337eb694..b9fd6536 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -90,5 +90,56 @@ "addZone": "Add new zone", "addPoint": "Add new point", "addBackdrop": "Add new backdrop" + }, + "forms": { + "core": { + "delete": "Delete", + "deleteFeature": "Delete feature", + "cancel": "Cancel", + "cancelCreation": "Cancel new feature creation", + "reset": "Reset", + "resetChanges": "Reset changes for this feature", + "save": "Save", + "create": "Create", + "saveEdits": "Save edits" + }, + "common": { + "name": "Name", + "shortName": "Short name", + "visible": "Visible", + "color": "Color", + "fill": "Fill", + "colorRequired": "color is required!", + "nameRequired": "Please enter name!", + "shortNameRequired": "Please enter short name!", + "position": "Position", + "time": "Time", + "timeEnd": "Time end", + "timeEndAfterTime": "Time-end must be after time!", + "start": "Start", + "end": "End", + "url": "URL", + "urlRequired": "Please enter URL!", + "maxNativeZoom": "Max Native Zoom", + "maxNativeZoomRequired": "Please enter max native zoom!", + "maxZoom": "Max Zoom", + "maxZoomRequired": "Please enter max zoom!", + "environment": "Environment", + "environmentRequired": "Please specify the environment/symbol", + "markers": "Markers", + "labels": "Labels", + "symbols": "Symbols", + "shape": "Shape", + "edit": "Edit" + }, + "multiFeature": { + "itemsSelected": "items selected", + "visibility": "Visibility", + "hideAll": "Hide All", + "showAll": "Show All", + "mixedVisibility": "Mixed Visibility, click to hide all", + "currentColor": "Current color:", + "mixedColors": "Mixed colors" + } } } diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 8d5a80a5..52d3f50c 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -90,5 +90,56 @@ "addZone": "Ajouter une nouvelle zone", "addPoint": "Ajouter un nouveau point", "addBackdrop": "Ajouter un nouvel arrière-plan" + }, + "forms": { + "core": { + "delete": "Supprimer", + "deleteFeature": "Supprimer l'élément", + "cancel": "Annuler", + "cancelCreation": "Annuler la création d'un nouvel élément", + "reset": "Réinitialiser", + "resetChanges": "Réinitialiser les modifications pour cet élément", + "save": "Enregistrer", + "create": "Créer", + "saveEdits": "Enregistrer les modifications" + }, + "common": { + "name": "Nom", + "shortName": "Nom court", + "visible": "Visible", + "color": "Couleur", + "fill": "Remplissage", + "colorRequired": "la couleur est requise !", + "nameRequired": "Veuillez entrer un nom !", + "shortNameRequired": "Veuillez entrer un nom court !", + "position": "Position", + "time": "Temps", + "timeEnd": "Fin du temps", + "timeEndAfterTime": "La fin du temps doit être après le début !", + "start": "Début", + "end": "Fin", + "url": "URL", + "urlRequired": "Veuillez entrer une URL !", + "maxNativeZoom": "Zoom natif maximal", + "maxNativeZoomRequired": "Veuillez entrer le zoom natif maximal !", + "maxZoom": "Zoom maximal", + "maxZoomRequired": "Veuillez entrer le zoom maximal !", + "environment": "Environnement", + "environmentRequired": "Veuillez spécifier l'environnement/symbole", + "markers": "Marqueurs", + "labels": "Étiquettes", + "symbols": "Symboles", + "shape": "Forme", + "edit": "Éditer" + }, + "multiFeature": { + "itemsSelected": "éléments sélectionnés", + "visibility": "Visibilité", + "hideAll": "Tout masquer", + "showAll": "Tout afficher", + "mixedVisibility": "Visibilité mixte, cliquez pour tout masquer", + "currentColor": "Couleur actuelle :", + "mixedColors": "Couleurs mixtes" + } } } diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json index 88feaf3f..b846d754 100644 --- a/src/i18n/locales/it.json +++ b/src/i18n/locales/it.json @@ -90,5 +90,56 @@ "addZone": "Aggiungi nuova zona", "addPoint": "Aggiungi nuovo punto", "addBackdrop": "Aggiungi nuovo sfondo" + }, + "forms": { + "core": { + "delete": "Elimina", + "deleteFeature": "Elimina elemento", + "cancel": "Annulla", + "cancelCreation": "Annulla creazione nuovo elemento", + "reset": "Ripristina", + "resetChanges": "Ripristina modifiche per questo elemento", + "save": "Salva", + "create": "Crea", + "saveEdits": "Salva modifiche" + }, + "common": { + "name": "Nome", + "shortName": "Nome breve", + "visible": "Visibile", + "color": "Colore", + "fill": "Riempimento", + "colorRequired": "il colore è obbligatorio!", + "nameRequired": "Inserisci un nome!", + "shortNameRequired": "Inserisci un nome breve!", + "position": "Posizione", + "time": "Tempo", + "timeEnd": "Fine tempo", + "timeEndAfterTime": "La fine del tempo deve essere dopo l'inizio!", + "start": "Inizio", + "end": "Fine", + "url": "URL", + "urlRequired": "Inserisci un URL!", + "maxNativeZoom": "Zoom nativo massimo", + "maxNativeZoomRequired": "Inserisci lo zoom nativo massimo!", + "maxZoom": "Zoom massimo", + "maxZoomRequired": "Inserisci lo zoom massimo!", + "environment": "Ambiente", + "environmentRequired": "Specifica l'ambiente/simbolo", + "markers": "Marcatori", + "labels": "Etichette", + "symbols": "Simboli", + "shape": "Forma", + "edit": "Modifica" + }, + "multiFeature": { + "itemsSelected": "elementi selezionati", + "visibility": "Visibilità", + "hideAll": "Nascondi tutto", + "showAll": "Mostra tutto", + "mixedVisibility": "Visibilità mista, clicca per nascondere tutto", + "currentColor": "Colore attuale:", + "mixedColors": "Colori misti" + } } } diff --git a/src/i18n/locales/nl.json b/src/i18n/locales/nl.json index 3930f5f1..a99ec477 100644 --- a/src/i18n/locales/nl.json +++ b/src/i18n/locales/nl.json @@ -90,5 +90,56 @@ "addZone": "Nieuwe zone toevoegen", "addPoint": "Nieuw punt toevoegen", "addBackdrop": "Nieuwe achtergrond toevoegen" + }, + "forms": { + "core": { + "delete": "Verwijderen", + "deleteFeature": "Object verwijderen", + "cancel": "Annuleren", + "cancelCreation": "Aanmaken van nieuw object annuleren", + "reset": "Resetten", + "resetChanges": "Wijzigingen voor dit object resetten", + "save": "Opslaan", + "create": "Aanmaken", + "saveEdits": "Wijzigingen opslaan" + }, + "common": { + "name": "Naam", + "shortName": "Korte naam", + "visible": "Zichtbaar", + "color": "Kleur", + "fill": "Vulling", + "colorRequired": "kleur is vereist!", + "nameRequired": "Voer een naam in!", + "shortNameRequired": "Voer een korte naam in!", + "position": "Positie", + "time": "Tijd", + "timeEnd": "Eindtijd", + "timeEndAfterTime": "Eindtijd moet na begintijd zijn!", + "start": "Start", + "end": "Einde", + "url": "URL", + "urlRequired": "Voer een URL in!", + "maxNativeZoom": "Max Eigen Zoom", + "maxNativeZoomRequired": "Voer max eigen zoom in!", + "maxZoom": "Max Zoom", + "maxZoomRequired": "Voer max zoom in!", + "environment": "Omgeving", + "environmentRequired": "Specificeer de omgeving/symbool", + "markers": "Markers", + "labels": "Labels", + "symbols": "Symbolen", + "shape": "Vorm", + "edit": "Bewerken" + }, + "multiFeature": { + "itemsSelected": "items geselecteerd", + "visibility": "Zichtbaarheid", + "hideAll": "Alles verbergen", + "showAll": "Alles tonen", + "mixedVisibility": "Gemengde zichtbaarheid, klik om alles te verbergen", + "currentColor": "Huidige kleur:", + "mixedColors": "Gemengde kleuren" + } } } From 496f0ab44791834252ea10cb7d059ee2d2400d53 Mon Sep 17 00:00:00 2001 From: Ian Mayo Date: Fri, 4 Apr 2025 20:33:36 +0100 Subject: [PATCH 09/14] translate modals. --- .../GraphsPanel/FeatureSelectorModal.tsx | 4 +- src/components/LoadTrackModal/index.tsx | 53 ++++++++++--------- src/components/UndoModal/index.tsx | 24 +++++---- .../ZoneForm/ZoneSpecificsModal.tsx | 28 +++++----- 4 files changed, 58 insertions(+), 51 deletions(-) diff --git a/src/components/GraphsPanel/FeatureSelectorModal.tsx b/src/components/GraphsPanel/FeatureSelectorModal.tsx index 3a5f0b70..7028309d 100644 --- a/src/components/GraphsPanel/FeatureSelectorModal.tsx +++ b/src/components/GraphsPanel/FeatureSelectorModal.tsx @@ -1,5 +1,6 @@ import { Modal, Transfer, Space } from 'antd' import { useMemo, useState } from 'react' +import { useTranslation } from 'react-i18next' import { Feature, GeoJsonProperties, Geometry } from 'geojson' import { featureAsOption } from './featureUtils' @@ -13,6 +14,7 @@ interface FeatureSelectorModalProps { } export const FeatureSelectorModal: React.FC = ({isOpen, title, onSave, onClose, features, defaults}) => { + const { t } = useTranslation() const [selectedTracks, setSelectedTracks] = useState(defaults) const options = useMemo(() => features.map(featureAsOption), [features]) @@ -52,7 +54,7 @@ export const FeatureSelectorModal: React.FC = ({isOpe > setSelectedTracks(nextTargetKeys as string[])} render={renderItem} diff --git a/src/components/LoadTrackModal/index.tsx b/src/components/LoadTrackModal/index.tsx index bad0c2fd..a8ad41e8 100644 --- a/src/components/LoadTrackModal/index.tsx +++ b/src/components/LoadTrackModal/index.tsx @@ -14,6 +14,7 @@ import { Typography, } from 'antd' import { Color } from 'antd/es/color-picker' +import { useTranslation } from 'react-i18next' import { presetColors } from '../../helpers/standardShades' import { useAppSelector } from '../../state/hooks' import { selectFeatures } from '../../state/geoFeaturesSlice' @@ -42,6 +43,7 @@ export const LoadTrackModel: React.FC = ({ environment, createTrackOnly = false, }) => { + const { t } = useTranslation() const features = useAppSelector(selectFeatures) const trackOptions = features .filter((feature) => feature.properties?.dataType === 'track') @@ -114,7 +116,7 @@ export const LoadTrackModel: React.FC = ({ const tabs: TabsProps['items'] = [ { key: 'add', - label: 'Add to existing track', + label: t('layers.addToExistingTrack'), children: (
= ({ disabled={trackOptions.length === 0} > { trackOptions.length ? - Select a track to add data to, from the list below: + {t('layers.selectTrackToAddData')} : - No tracks available to add to. Please load as a new track. + {t('layers.noTracksAvailable')} } - label='Track' + label={t('layers.tracks')} name='trackId' initialValue={defaultTrackId} style={itemStyle} rules={[ { required: true, - message: 'Please indicate which track to add data to', + message: t('layers.pleaseSelectTrack'), }, ]} > @@ -146,10 +148,10 @@ export const LoadTrackModel: React.FC = ({ @@ -157,7 +159,7 @@ export const LoadTrackModel: React.FC = ({ }, { key: 'create', - label: 'Create new track', + label: t('layers.addTrack'), children: (
= ({ autoComplete='off' > - Extra details are required for a new track. Please complete the - following: + {t('layers.extraDetailsForNewTrack')} - label='Name' + label={t('forms.common.name')} name='name' style={itemStyle} - rules={[{ required: true, message: 'Please enter track name!' }]} + rules={[{ required: true, message: t('forms.common.nameRequired') }]} > - label='Short Name' + label={t('forms.common.shortName')} name='shortName' style={itemStyle} rules={[ { required: true, - message: 'Please enter abbreviated track name!', + message: t('forms.common.shortNameRequired'), }, ]} > @@ -198,31 +199,31 @@ export const LoadTrackModel: React.FC = ({ - label='Year' + label={t('layers.year')} name='initialYear' style={itemStyle} - rules={[{ required: true, message: 'Please enter Year for data' }]} + rules={[{ required: true, message: t('layers.yearRequired') }]} > - label='Month' + label={t('layers.month')} name='initialMonth' style={itemStyle} - rules={[{ required: true, message: 'Please enter Month for data' }]} + rules={[{ required: true, message: t('layers.monthRequired') }]} > - label='Environment' + label={t('forms.common.environment')} name='env' style={itemStyle} rules={[ { required: true, - message: 'Please specify the environment for the track', + message: t('forms.common.environmentRequired'), }, ]} > @@ -230,10 +231,10 @@ export const LoadTrackModel: React.FC = ({ - label='Colour' + label={t('forms.common.color')} name='stroke' style={itemStyle} - rules={[{ required: true, message: 'Please enter track color' }]} + rules={[{ required: true, message: t('forms.common.colorRequired') }]} > = ({ style={itemStyle} - label='Markers'> + label={t('forms.common.markers')}> - label='Labels' + label={t('forms.common.labels')} className="labelInterval" name='labelInterval' style={itemStyle}> + label={t('forms.common.shortName')} @@ -194,7 +194,7 @@ export const LoadTrackModel: React.FC = ({ ]} > - + @@ -204,7 +204,7 @@ export const LoadTrackModel: React.FC = ({ style={itemStyle} rules={[{ required: true, message: t('layers.yearRequired') }]} > - + @@ -213,7 +213,7 @@ export const LoadTrackModel: React.FC = ({ style={itemStyle} rules={[{ required: true, message: t('layers.monthRequired') }]} > - + @@ -268,10 +268,10 @@ export const LoadTrackModel: React.FC = ({ - - diff --git a/tests/details/edit-simple-details.spec.ts b/tests/details/edit-simple-details.spec.ts index 0d9ad923..30c830b0 100644 --- a/tests/details/edit-simple-details.spec.ts +++ b/tests/details/edit-simple-details.spec.ts @@ -25,13 +25,14 @@ test('test adding feature', async ({ page }) => { // introduce pause await page.waitForTimeout(100) await page.waitForSelector('.create-track') - await page.getByRole('tabpanel', { name: 'Create new track' }).getByLabel('Name').click() - await page.getByRole('tabpanel', { name: 'Create new track' }).getByLabel('Name').fill(newName) - await page.getByRole('tabpanel', { name: 'Create new track' }).getByLabel('Name').press('Tab') - await page.locator('#createTrack_shortName').getByRole('textbox').fill('DAIR') - await page.getByRole('spinbutton', { name: '* Year :' }).click() - await page.getByRole('button', { name: 'Increase Value' }).first().click() - await page.getByRole('button', { name: 'Create' }).click() + await page.locator('.create-track').locator('.create-track-name').click() + await page.locator('.create-track').locator('.create-track-name').fill(newName) + await page.locator('.create-track').locator('.create-track-name').press('Tab') + await page.locator('.create-track').locator('.create-track-shortName').fill('DAIR') + await page.locator('.create-track').locator('#createTrack_initialYear').click() + await page.locator('.create-track').locator('.create-track-year').locator('.ant-input-number-handler-up').click() + await page.locator('.create-track').locator('.create-track-year').locator('.ant-input-number-handler-up').click() + await page.locator('.create-track-button').click() await page.waitForTimeout(100) await page.getByText('AIR').click() await expect(page.getByRole('tree').getByText(newName)).toBeVisible()