From 364970a2fac47f14c0caa97a2e4624ec06bc2b83 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 6 Aug 2026 12:51:12 +0100 Subject: [PATCH 1/9] Add Welsh language translations to the clientside maps --- src/client/javascripts/geospatial-map.js | 246 +++++++++++++++++++---- test/client/javascripts/map.test.js | 17 +- 2 files changed, 217 insertions(+), 46 deletions(-) diff --git a/src/client/javascripts/geospatial-map.js b/src/client/javascripts/geospatial-map.js index 83f5033fb..1ada547fd 100644 --- a/src/client/javascripts/geospatial-map.js +++ b/src/client/javascripts/geospatial-map.js @@ -16,10 +16,113 @@ import { import sssiDataset from '~/src/client/javascripts/sssi-dataset.js' import { formatDelimtedList } from '~/src/client/javascripts/utils.js' +/** @type {LanguageCode} */ +export const ENGLISH_LANG = 'en-GB' +/** @type {LanguageCode} */ +export const WELSH_LANG = 'cy' +/** @type {LanguageCode} */ +export const DEFAULT_LANG = ENGLISH_LANG + +/** + * @type {Record} + */ +export const texts = { + [ENGLISH_LANG]: { + typeDescriptions: { + Point: 'Point', + LineString: 'Line', + Polygon: 'Shape' + }, + panel: { + label: 'How to use this map', + ledeIntro: 'You can add', + ledeOutro: 'to the map.', + lineText: 'a line', + shapeText: 'a shape', + lineOrShapeText: 'a line or shape', + typesPhrasePoint: 'points', + typesPhraseLine: 'lines', + typesPhraseShape: 'shapes', + typesPhraseOr: 'or', + point1: 'Search for a county, place or postcode', + point2: 'Use the + and - icons to zoom in and out', + point3: "Double‑click, or select 'Done', when you have finished drawing", + point4: 'Give the location a name' + }, + buttons: { + Point: 'Add point', + LineString: 'Add line', + Polygon: 'Add shape' + }, + itemLabel1: 'Location', + itemLabel2: 'description', + itemUpdateLink: 'Update', + itemDeleteLink: 'Delete', + itemFocusLink: 'Show', + itemVisuallyHidden: 'location', + details: { + summary: 'Coordinates', + type: 'Type', + centroidGridReference: 'Centre grid reference', + firstPointGridReference: 'First point grid reference', + detailedCoordinates: 'Detailed coordinates' + } + }, + [WELSH_LANG]: { + typeDescriptions: { + Point: 'Pwynt', + LineString: 'Llinell', + Polygon: 'Siâp' + }, + panel: { + label: "Sut i ddefnyddio'r map hwn", + ledeIntro: 'Gallwch ychwanegu', + ledeOutro: "i'r map.", + lineText: 'llinell', + shapeText: 'siâp', + lineOrShapeText: 'llinell neu siâp', + typesPhrasePoint: 'pwyntiau', + typesPhraseLine: 'lliniau', + typesPhraseShape: 'siâpiau', + typesPhraseOr: 'neu', + point1: 'Chwilio am sir, lle neu god post', + point2: 'Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan', + point3: + "Cliciwch ddwywaith, neu dewiswch 'Wedi gorffen', pan fyddwch wedi", + point4: "Rhowch enw i'r lleoliad" + }, + buttons: { + Point: 'Ychwanegu pwynt', + LineString: 'Ychwanegu llinell', + Polygon: 'Ychwanegu siâp' + }, + itemLabel1: 'Lleoliad', + itemLabel2: 'disgrifiad', + itemUpdateLink: 'Diweddaru', + itemDeleteLink: 'Dileu', + itemFocusLink: 'Dangos', + itemVisuallyHidden: 'lleoliad', + details: { + summary: 'Cyfesurynnau', + type: 'Math', + centroidGridReference: 'Canol cyfeirnod grid', + firstPointGridReference: 'Cyfeirnod grid y pwynt cyntaf', + detailedCoordinates: 'Cyfesurynnau manwl' + } + } +} + +/** + * Get the map texts for the given language code + * @param {LanguageCode} lang - the lanugae code + */ +export function getTexts(lang = DEFAULT_LANG) { + return texts[lang] +} + const helpPanelConfig = { focus: false, showLabel: true, - label: 'How to use this map', mobile: { slot: 'drawer', open: true, @@ -41,59 +144,63 @@ const helpPanelConfig = { } /** + * @param {PanelTexts} texts * @param {boolean} allowLine * @param {boolean} allowShape */ -function getLineOrShapeText(allowLine, allowShape) { +function getLineOrShapeText(texts, allowLine, allowShape) { if (allowLine && allowShape) { - return 'a line or shape' + return texts.lineOrShapeText } if (allowLine) { - return 'a line' + return texts.lineText } if (allowShape) { - return 'a shape' + return texts.shapeText } return '' } /** + * @param {PanelTexts} texts * @param {boolean} allowPoint * @param {boolean} allowLine * @param {boolean} allowShape */ -function getAllowedTypesPhrase(allowPoint, allowLine, allowShape) { +function getAllowedTypesPhrase(texts, allowPoint, allowLine, allowShape) { const items = [] if (allowPoint) { - items.push('points') + items.push(texts.typesPhrasePoint) } if (allowLine) { - items.push('lines') + items.push(texts.typesPhraseLine) } if (allowShape) { - items.push('shapes') + items.push(texts.typesPhraseShape) } - return formatDelimtedList(items, ',', 'or') + return formatDelimtedList(items, ',', texts.typesPhraseOr) } /** + * @param {PanelTexts} texts * @param {boolean} allowPoint * @param {boolean} allowLine * @param {boolean} allowShape */ -export function getHelpPanelHtml(allowPoint, allowLine, allowShape) { - const lineOrShapeText = getLineOrShapeText(allowLine, allowShape) - const doneExtra = lineOrShapeText - ? `
  • Double‑click, or select 'Done', when you have finished drawing ${lineOrShapeText}
  • ` +export function getHelpPanelHtml(texts, allowPoint, allowLine, allowShape) { + const lineOrShapeText = getLineOrShapeText(texts, allowLine, allowShape) + const point3 = lineOrShapeText + ? `
  • ${texts.point3} ${lineOrShapeText}
  • ` : '' const allowedTypesText = getAllowedTypesPhrase( + texts, allowPoint, allowLine, allowShape ) - return `

    You can add ${allowedTypesText} to the map.

    ` + return `

    ${texts.ledeIntro} ${allowedTypesText} ${texts.ledeOutro}

    ` } const lineFeatureProperties = { @@ -108,15 +215,6 @@ const polygonFeatureProperties = { strokeWidth: 2 } -/** - * @type {Record<'Point' | 'LineString' | 'Polygon', string>} - */ -const typeDescriptions = { - Point: 'Point', - LineString: 'Line', - Polygon: 'Shape' -} - const POINT_SVG = '' const POLYGON_SVG = @@ -208,10 +306,19 @@ export function processGeospatial(config, geospatial, index) { options ) + const lang = /** @type {LanguageCode} */ ( + geospatial.dataset.lang ?? DEFAULT_LANG + ) + const languageManager = { + lang, + texts: getTexts(lang) + } + /** * @type {Context} */ const context = { + languageManager, map, featuresManager, activeFeatureManager, @@ -279,14 +386,17 @@ export function focusFeature(feature, mapProvider) { * @param {string} mapId - the ID of the map * @param {boolean} [disabled] - render the list with disabled links * @param {boolean} [readonly] - render the list item in readonly mode + * @param {LanguageCode} [lang] - the language for the feature details */ export function createFeatureHTML( feature, index, mapId, disabled = false, - readonly = false + readonly = false, + lang = DEFAULT_LANG ) { + const texts = getTexts(lang) const flattened = feature.geometry.coordinates.flat(2) const points = [] @@ -302,18 +412,18 @@ export function createFeatureHTML( // Change action link const changeAction = () => `
  • Update location + data-type="${feature.geometry.type}">${texts.itemUpdateLink} ${texts.itemVisuallyHidden}
  • ` // Delete action link const deleteAction = () => `
  • Delete location + data-type="${feature.geometry.type}">${texts.itemDeleteLink} ${texts.itemVisuallyHidden}
  • ` // Focus action link const focusAction = () => `
  • - Show location + ${texts.itemFocusLink} ${texts.itemVisuallyHidden}
  • ` const links = readonly ? focusAction() : `${changeAction()}${deleteAction()}` @@ -323,7 +433,7 @@ export function createFeatureHTML( return `
    - + ${description}
    @@ -334,24 +444,24 @@ export function createFeatureHTML(
    - Coordinates + ${texts.details.summary}
    -
    Type
    -
    ${typeDescriptions[feature.geometry.type]}
    +
    ${texts.details.type}
    +
    ${texts.typeDescriptions[feature.geometry.type]}
    -
    Centre grid reference
    +
    ${texts.details.centroidGridReference}
    ${feature.properties.centroidGridReference}
    -
    First point grid reference
    +
    ${texts.details.firstPointGridReference}
    ${feature.properties.coordinateGridReference}
    -
    Detailed coordinates
    +
    ${texts.details.detailedCoordinates}
      ${coordinates}
    @@ -661,17 +771,19 @@ function onMapReadyFactory(context) { const allowPoint = types.includes('point') const allowLine = types.includes('line') const allowShape = types.includes('shape') + const { texts } = context.languageManager // Add info panel map.addPanel('info', { ...helpPanelConfig, - html: getHelpPanelHtml(allowPoint, allowLine, allowShape) + label: texts.panel.label, + html: getHelpPanelHtml(texts.panel, allowPoint, allowLine, allowShape) }) if (allowPoint) { map.addButton('btnAddPoint', { variant: 'tertiary', - label: 'Add point', + label: texts.buttons.Point, iconSvgContent: POINT_SVG, onClick: () => { resetActiveFeature() @@ -688,7 +800,7 @@ function onMapReadyFactory(context) { if (allowShape) { map.addButton('btnAddPolygon', { variant: 'tertiary', - label: 'Add shape', + label: texts.buttons.Polygon, iconSvgContent: POLYGON_SVG, onClick: () => { resetActiveFeature() @@ -705,7 +817,7 @@ function onMapReadyFactory(context) { if (allowLine) { map.addButton('btnAddLine', { variant: 'tertiary', - label: 'Add line', + label: texts.buttons.LineString, iconSvgContent: LINE_SVG, onClick: () => { resetActiveFeature() @@ -1183,8 +1295,64 @@ function onListElKeydownFactory() { * @property {GetAllowableGeometryTypes} getAllowableGeometryTypes - function that returns the array of geometry types a user can create */ +/** + * Supported language codes + * @typedef {('en-GB'|'cy')} LanguageCode + */ + +/** + * @typedef {object} GeometryTexts + * @property {string} Point - the label for a point feature in the list + * @property {string} LineString - the label for a line string feature in the list + * @property {string} Polygon - the label for a polygon feature in the list + */ + +/** + * @typedef {object} PanelTexts + * @property {string} label - the label for the info panel + * @property {string} ledeIntro - the lede intro text for the info panel + * @property {string} ledeOutro - the lede outro text for the info panel + * @property {string} lineText - the label for the info panel line text + * @property {string} shapeText - the label for the info panel shape text + * @property {string} point1 - the text for bullet point 1 in the info panel + * @property {string} point2 - the text for bullet point 2 in the info panel + * @property {string} point3 - the text for bullet point 3 in the info panel + * @property {string} point4 - the text for bullet point 4 in the info panel + * @property {string} lineOrShapeText - the label for the info panel line or shape text + * @property {string} typesPhrasePoint - the label for the info panel types phrase point + * @property {string} typesPhraseLine - the label for the info panel types phrase line + * @property {string} typesPhraseShape - the label for the info panel types phrase shape + * @property {string} typesPhraseOr - the label for the info panel types phrase "or" + */ + +/** + * @typedef {object} LanguageTexts + * @property {GeometryTexts} typeDescriptions - the descriptions of the geometry types + * @property {PanelTexts} panel - texts for the info panel + * @property {GeometryTexts} buttons - the labels for the action buttons + * @property {string} itemLabel1 - the first part of the label for a feature in the list + * @property {string} itemLabel2 - the second part of the label for a feature in the list + * @property {string} itemUpdateLink - the label for the "update" link for a feature in the list + * @property {string} itemDeleteLink - the label for the "delete" link for a feature in the list + * @property {string} itemFocusLink - the label for the "focus" link for a feature in the list + * @property {string} itemVisuallyHidden - visually hidden text to add context to action links + * @property {object} details - labels for the details section of a feature in the list + * @property {string} details.summary - label for the summary of a feature in the list + * @property {string} details.type - label for the type of a feature in the list + * @property {string} details.centroidGridReference - label for the centroid grid reference of a feature in the list + * @property {string} details.firstPointGridReference - label for the first point grid reference of a feature in the list + * @property {string} details.detailedCoordinates - label for the detailed coordinates of a feature in the list + */ + +/** + * @typedef {object} LanguageManager + * @property {LanguageCode} lang - the language for the interactive map + * @property {LanguageTexts} texts - the descriptions of the geometry types + */ + /** * @typedef {object} Context + * @property {LanguageManager} languageManager - the language manager for the interactive map * @property {InteractiveMap} map - the interactive map * @property {MapLibreMap} [mapProvider] - the interactive map provider * @property {FeaturesManager} featuresManager - the features manager diff --git a/test/client/javascripts/map.test.js b/test/client/javascripts/map.test.js index 5e5ba0673..a7f17e570 100644 --- a/test/client/javascripts/map.test.js +++ b/test/client/javascripts/map.test.js @@ -17,9 +17,11 @@ import createSearchPlugin from '@defra/interactive-map/plugins/search' import maplibreProvider from '@defra/interactive-map/providers/maplibre' import { + ENGLISH_LANG, createFeatureHTML, createFeaturesHTML, getHelpPanelHtml, + getTexts, getUIManager } from '~/src/client/javascripts/geospatial-map.js' import { @@ -310,38 +312,39 @@ describe('Maps Client JS', () => { }) describe('getHelpPanelHtml', () => { + const texts = getTexts(ENGLISH_LANG) it('should handle only point', () => { - expect(getHelpPanelHtml(true, false, false)).toBe( + expect(getHelpPanelHtml(texts.panel, true, false, false)).toBe( '

    You can add points to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Give the location a name
    ' ) }) it('should handle only line', () => { - expect(getHelpPanelHtml(false, true, false)).toBe( + expect(getHelpPanelHtml(texts.panel, false, true, false)).toBe( '

    You can add lines to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line
    • Give the location a name
    ' ) }) it('should handle only shape', () => { - expect(getHelpPanelHtml(false, false, true)).toBe( + expect(getHelpPanelHtml(texts.panel, false, false, true)).toBe( '

    You can add shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a shape
    • Give the location a name
    ' ) }) it('should handle point and line', () => { - expect(getHelpPanelHtml(true, true, false)).toBe( + expect(getHelpPanelHtml(texts.panel, true, true, false)).toBe( '

    You can add points or lines to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line
    • Give the location a name
    ' ) }) it('should handle point and shape', () => { - expect(getHelpPanelHtml(true, false, true)).toBe( + expect(getHelpPanelHtml(texts.panel, true, false, true)).toBe( '

    You can add points or shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a shape
    • Give the location a name
    ' ) }) it('should handle line and shape', () => { - expect(getHelpPanelHtml(false, true, true)).toBe( + expect(getHelpPanelHtml(texts.panel, false, true, true)).toBe( '

    You can add lines or shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line or shape
    • Give the location a name
    ' ) }) it('should handle point, line and shape', () => { - expect(getHelpPanelHtml(true, true, true)).toBe( + expect(getHelpPanelHtml(texts.panel, true, true, true)).toBe( '

    You can add points, lines or shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line or shape
    • Give the location a name
    ' ) }) From de0a612663faa9088cac16bfcbd1c1c0fc31aa34 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 6 Aug 2026 12:55:09 +0100 Subject: [PATCH 2/9] Send language in the geospatial component --- src/server/plugins/engine/components/GeospatialField.ts | 1 + src/server/plugins/engine/views/components/geospatialfield.html | 1 + 2 files changed, 2 insertions(+) diff --git a/src/server/plugins/engine/components/GeospatialField.ts b/src/server/plugins/engine/components/GeospatialField.ts index c60a2180f..d1bf362cd 100644 --- a/src/server/plugins/engine/components/GeospatialField.ts +++ b/src/server/plugins/engine/components/GeospatialField.ts @@ -101,6 +101,7 @@ export class GeospatialField extends FormComponent { return { ...viewModel, + lang: context.translator.language, country: this.options.countries?.at(0), geometryTypes: this.options.geometryTypes, mapLayers: getMapLayers(this), diff --git a/src/server/plugins/engine/views/components/geospatialfield.html b/src/server/plugins/engine/views/components/geospatialfield.html index 5a3e52033..4cd1864a7 100644 --- a/src/server/plugins/engine/views/components/geospatialfield.html +++ b/src/server/plugins/engine/views/components/geospatialfield.html @@ -2,6 +2,7 @@ {% macro GeospatialField(component) %}
    From 68e909821916f2303cdd882d48ee634847f564de Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 6 Aug 2026 13:30:00 +0100 Subject: [PATCH 3/9] Sonar fixes (variable scope) --- src/client/javascripts/geospatial-map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/javascripts/geospatial-map.js b/src/client/javascripts/geospatial-map.js index 1ada547fd..e438417b6 100644 --- a/src/client/javascripts/geospatial-map.js +++ b/src/client/javascripts/geospatial-map.js @@ -26,7 +26,7 @@ export const DEFAULT_LANG = ENGLISH_LANG /** * @type {Record} */ -export const texts = { +export const languageTexts = { [ENGLISH_LANG]: { typeDescriptions: { Point: 'Point', @@ -117,7 +117,7 @@ export const texts = { * @param {LanguageCode} lang - the lanugae code */ export function getTexts(lang = DEFAULT_LANG) { - return texts[lang] + return languageTexts[lang] } const helpPanelConfig = { From e0b17e3a59bbcccc71f9284b208c793b52bb43fd Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 6 Aug 2026 14:51:51 +0100 Subject: [PATCH 4/9] Use language code in renderer --- src/client/javascripts/geospatial-map.js | 39 ++++++++++++++++++------ 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/client/javascripts/geospatial-map.js b/src/client/javascripts/geospatial-map.js index e438417b6..2307fe0cb 100644 --- a/src/client/javascripts/geospatial-map.js +++ b/src/client/javascripts/geospatial-map.js @@ -297,18 +297,19 @@ export function processGeospatial(config, geospatial, index) { const options = { geometryTypes } + const lang = /** @type {LanguageCode} */ ( + geospatial.dataset.lang ?? DEFAULT_LANG + ) const uiManager = getUIManager( geojson, map, mapId, listEl, geospatialInput, - options + options, + lang ) - const lang = /** @type {LanguageCode} */ ( - geospatial.dataset.lang ?? DEFAULT_LANG - ) const languageManager = { lang, texts: getTexts(lang) @@ -358,15 +359,17 @@ export function addFeatureToMap(feature, drawPlugin, map) { * @param {string} mapId - the ID of the map * @param {boolean} [disabled] - render the list with disabled links * @param {boolean} [readonly] - render the list in readonly mode + * @param {LanguageCode} [lang] - the language for the feature details */ export function createFeaturesHTML( features, mapId, disabled = false, - readonly = false + readonly = false, + lang = DEFAULT_LANG ) { return `
    - ${features.map((feature, index) => createFeatureHTML(feature, index, mapId, disabled, readonly)).join('\n')} + ${features.map((feature, index) => createFeatureHTML(feature, index, mapId, disabled, readonly, lang)).join('\n')}
    ` } @@ -620,11 +623,24 @@ function getFeaturesManager(geojson) { * @param {string} mapId - the ID of the map * @param {HTMLDivElement} listEl - where to render the feature list * @param {Function} renderValue - function that renders the features JSON into the hidden textarea + * @param {LanguageCode} [lang] - the language for the feature details * @returns {RenderList} */ -function getListRenderer(geojson, mapId, listEl, renderValue) { +function getListRenderer( + geojson, + mapId, + listEl, + renderValue, + lang = DEFAULT_LANG +) { return function renderList(disabled = false) { - const html = createFeaturesHTML(geojson.features, mapId, disabled) + const html = createFeaturesHTML( + geojson.features, + mapId, + disabled, + false, + lang + ) listEl.innerHTML = html @@ -652,6 +668,7 @@ function getValueRenderer(geojson, geospatialInput) { * @param {HTMLDivElement} listEl - where to render the feature list * @param {HTMLTextAreaElement} geospatialInput - the geospatial textarea * @param { UIManagerOptions | undefined } options - extra options such as allowable geometry types + * @param {LanguageCode} [lang] - the language for the feature details */ export function getUIManager( geojson, @@ -659,7 +676,8 @@ export function getUIManager( mapId, listEl, geospatialInput, - options + options, + lang = DEFAULT_LANG ) { /** * Get a CSV list of geometry types the user can create @@ -704,7 +722,7 @@ export function getUIManager( } const renderValue = getValueRenderer(geojson, geospatialInput) - const renderList = getListRenderer(geojson, mapId, listEl, renderValue) + const renderList = getListRenderer(geojson, mapId, listEl, renderValue, lang) /** @type {UIManager} */ return { @@ -1241,6 +1259,7 @@ function onListElKeydownFactory() { * Renders the features into the list * @callback RenderList * @param {boolean} [disabled] - whether to render the list with disabled links + * @param {LanguageCode} [lang] - the language for the feature details * @returns {void} */ From 84e0e2cb8db3b93615ac55824a3fe9dc38291130 Mon Sep 17 00:00:00 2001 From: David Stone Date: Thu, 6 Aug 2026 17:14:42 +0100 Subject: [PATCH 5/9] Add location field help panel translations --- src/client/javascripts/geospatial-map.js | 37 +++++------ src/client/javascripts/location-map.js | 64 ++++++++++++++++++- src/client/javascripts/map-constants.js | 3 + src/client/javascripts/map.js | 5 ++ .../engine/components/LocationFieldBase.ts | 4 ++ .../engine/components/LocationFieldHelpers.ts | 3 +- .../components/_location-field-base.html | 1 + .../views/components/osgridreffield.html | 4 +- test/client/javascripts/map.test.js | 2 +- 9 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 src/client/javascripts/map-constants.js diff --git a/src/client/javascripts/geospatial-map.js b/src/client/javascripts/geospatial-map.js index 2307fe0cb..4fa8a3092 100644 --- a/src/client/javascripts/geospatial-map.js +++ b/src/client/javascripts/geospatial-map.js @@ -4,6 +4,11 @@ import createDatasetsPlugin from '@defra/interactive-map/plugins/datasets' import createDrawPlugin from '@defra/interactive-map/plugins/draw-ml' import { bbox } from '@turf/bbox' +import { + DEFAULT_LANG, + ENGLISH_LANG, + WELSH_LANG +} from '~/src/client/javascripts/map-constants.js' import { EVENTS, createMap, @@ -16,15 +21,8 @@ import { import sssiDataset from '~/src/client/javascripts/sssi-dataset.js' import { formatDelimtedList } from '~/src/client/javascripts/utils.js' -/** @type {LanguageCode} */ -export const ENGLISH_LANG = 'en-GB' -/** @type {LanguageCode} */ -export const WELSH_LANG = 'cy' -/** @type {LanguageCode} */ -export const DEFAULT_LANG = ENGLISH_LANG - /** - * @type {Record} + * @type {Record} */ export const languageTexts = { [ENGLISH_LANG]: { @@ -144,7 +142,7 @@ const helpPanelConfig = { } /** - * @param {PanelTexts} texts + * @param {GeospatialPanelTexts} texts * @param {boolean} allowLine * @param {boolean} allowShape */ @@ -162,7 +160,7 @@ function getLineOrShapeText(texts, allowLine, allowShape) { } /** - * @param {PanelTexts} texts + * @param {GeospatialPanelTexts} texts * @param {boolean} allowPoint * @param {boolean} allowLine * @param {boolean} allowShape @@ -184,7 +182,7 @@ function getAllowedTypesPhrase(texts, allowPoint, allowLine, allowShape) { } /** - * @param {PanelTexts} texts + * @param {GeospatialPanelTexts} texts * @param {boolean} allowPoint * @param {boolean} allowLine * @param {boolean} allowShape @@ -298,7 +296,8 @@ export function processGeospatial(config, geospatial, index) { geometryTypes } const lang = /** @type {LanguageCode} */ ( - geospatial.dataset.lang ?? DEFAULT_LANG + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + geospatial.dataset.lang || DEFAULT_LANG ) const uiManager = getUIManager( geojson, @@ -1314,11 +1313,6 @@ function onListElKeydownFactory() { * @property {GetAllowableGeometryTypes} getAllowableGeometryTypes - function that returns the array of geometry types a user can create */ -/** - * Supported language codes - * @typedef {('en-GB'|'cy')} LanguageCode - */ - /** * @typedef {object} GeometryTexts * @property {string} Point - the label for a point feature in the list @@ -1327,7 +1321,7 @@ function onListElKeydownFactory() { */ /** - * @typedef {object} PanelTexts + * @typedef {object} GeospatialPanelTexts * @property {string} label - the label for the info panel * @property {string} ledeIntro - the lede intro text for the info panel * @property {string} ledeOutro - the lede outro text for the info panel @@ -1345,9 +1339,9 @@ function onListElKeydownFactory() { */ /** - * @typedef {object} LanguageTexts + * @typedef {object} GeospatialLanguageTexts * @property {GeometryTexts} typeDescriptions - the descriptions of the geometry types - * @property {PanelTexts} panel - texts for the info panel + * @property {GeospatialPanelTexts} panel - texts for the info panel * @property {GeometryTexts} buttons - the labels for the action buttons * @property {string} itemLabel1 - the first part of the label for a feature in the list * @property {string} itemLabel2 - the second part of the label for a feature in the list @@ -1366,7 +1360,7 @@ function onListElKeydownFactory() { /** * @typedef {object} LanguageManager * @property {LanguageCode} lang - the language for the interactive map - * @property {LanguageTexts} texts - the descriptions of the geometry types + * @property {GeospatialLanguageTexts} texts - the descriptions of the geometry types */ /** @@ -1382,5 +1376,6 @@ function onListElKeydownFactory() { */ /** + * @import { LanguageCode } from '~/src/client/javascripts/map.js' * @import { MapLibreMap, UIManagerOptions } from '~/src/client/javascripts/map.js' */ diff --git a/src/client/javascripts/location-map.js b/src/client/javascripts/location-map.js index 9a8798477..1c0acb845 100644 --- a/src/client/javascripts/location-map.js +++ b/src/client/javascripts/location-map.js @@ -1,6 +1,11 @@ // @ts-expect-error - no types import createDatasetsPlugin from '@defra/interactive-map/plugins/datasets' +import { + DEFAULT_LANG, + ENGLISH_LANG, + WELSH_LANG +} from '~/src/client/javascripts/map-constants.js' import { EVENTS, centerMap, @@ -17,6 +22,39 @@ import sssiDataset from '~/src/client/javascripts/sssi-dataset.js' const LOCATION_FIELD_SELECTOR = 'input.govuk-input' +/** + * @type {Record} + */ +export const languageTexts = { + [ENGLISH_LANG]: { + panel: { + label: 'How to use this map', + point1: 'Search for a place or postcode', + point2: 'Use the + and - icons to zoom in and out', + point3: 'Use a mouse or keyboard to centre the point at the location', + point4: 'Click to add the location to the map' + } + }, + [WELSH_LANG]: { + panel: { + label: "Sut i ddefnyddio'r map hwn", + point1: 'Chwilio am sir, lle neu god post', + point2: 'Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan', + point3: + "Defnyddiwch lyfliwr neu allweddlon i ganolbwyntio'r pwynt yn y lleoliad", + point4: "Cliciwch i ychwanegu'r lleoliad i'r map" + } + } +} + +/** + * Get the map texts for the given language code + * @param {LanguageCode} lang - the lanugae code + */ +export function getTexts(lang = DEFAULT_LANG) { + return languageTexts[lang] +} + /** * Gets initial map config for a location field * @param {HTMLDivElement} locationField - the location field element @@ -437,6 +475,11 @@ export function processLocation(config, location, index) { initConfig.plugins = [createDatasetsPlugin({ datasets })] } + const lang = /** @type {LanguageCode} */ ( + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + location.dataset.lang || DEFAULT_LANG + ) + const { map, interactPlugin } = createMap(mapId, initConfig, config) map.on( @@ -461,11 +504,13 @@ export function processLocation(config, location, index) { throw new Error('Not implemented') } + const texts = getTexts(lang) + // Add info panel map.addPanel('info', { focus: false, showLabel: true, - label: 'How to use the map', + label: texts.panel.label, mobile: { slot: 'drawer', open: true, @@ -484,7 +529,7 @@ export function processLocation(config, location, index) { dismissible: true, modal: false }, - html: '
    • Search for a place or postcode
    • Use the + and - icons to zoom in and out
    • Use a mouse or keyboard to centre the point at the location
    • Click to add the location to the map
    ' + html: `
    • ${texts.panel.point1}
    • ${texts.panel.point2}
    • ${texts.panel.point3}
    • ${texts.panel.point4}
    ` }) // Enable the interact plugin @@ -494,5 +539,20 @@ export function processLocation(config, location, index) { } /** + * @typedef {object} LocationPanelTexts + * @property {string} label - the label for the info panel + * @property {string} point1 - the text for bullet point 1 in the info panel + * @property {string} point2 - the text for bullet point 2 in the info panel + * @property {string} point3 - the text for bullet point 3 in the info panel + * @property {string} point4 - the text for bullet point 4 in the info panel + */ + +/** + * @typedef {object} LocationLanguageTexts + * @property {LocationPanelTexts} panel - texts for the info panel + */ + +/** + * @import { LanguageCode } from '~/src/client/javascripts/map.js' * @import { InteractiveMap, InteractiveMapInitConfig, MapCenter, MapLibreMap, MapsEnvironmentConfig } from '~/src/client/javascripts/map.js' */ diff --git a/src/client/javascripts/map-constants.js b/src/client/javascripts/map-constants.js new file mode 100644 index 000000000..f0687f2fa --- /dev/null +++ b/src/client/javascripts/map-constants.js @@ -0,0 +1,3 @@ +export const ENGLISH_LANG = 'en-GB' +export const WELSH_LANG = 'cy' +export const DEFAULT_LANG = ENGLISH_LANG diff --git a/src/client/javascripts/map.js b/src/client/javascripts/map.js index 1cc1e5e22..4cb469d09 100644 --- a/src/client/javascripts/map.js +++ b/src/client/javascripts/map.js @@ -460,3 +460,8 @@ export function getMapCountryLayers(apiPath, country) { /** * @import { Feature } from '~/src/server/plugins/engine/types.js' */ + +/** + * Supported language codes + * @typedef {('en-GB'|'cy')} LanguageCode + */ diff --git a/src/server/plugins/engine/components/LocationFieldBase.ts b/src/server/plugins/engine/components/LocationFieldBase.ts index 34a9905a4..2a9a52bff 100644 --- a/src/server/plugins/engine/components/LocationFieldBase.ts +++ b/src/server/plugins/engine/components/LocationFieldBase.ts @@ -124,6 +124,10 @@ export abstract class LocationFieldBase extends FormComponent { getViewModel(context: RenderContext) { const viewModel = super.getViewModel(context) + Object.assign(viewModel, { + lang: context.translator.language + }) + if (this.instructionText) { return { ...viewModel, diff --git a/src/server/plugins/engine/components/LocationFieldHelpers.ts b/src/server/plugins/engine/components/LocationFieldHelpers.ts index 92b1d8de6..fb0bf28c7 100644 --- a/src/server/plugins/engine/components/LocationFieldHelpers.ts +++ b/src/server/plugins/engine/components/LocationFieldHelpers.ts @@ -194,7 +194,8 @@ export function getLocationFieldViewModel( country: component instanceof EastingNorthingField ? component.options.countries?.at(0) - : undefined + : undefined, + lang: context.translator.language } if (component.options.instructionText) { diff --git a/src/server/plugins/engine/views/components/_location-field-base.html b/src/server/plugins/engine/views/components/_location-field-base.html index 59c2f040d..0a3813f2d 100644 --- a/src/server/plugins/engine/views/components/_location-field-base.html +++ b/src/server/plugins/engine/views/components/_location-field-base.html @@ -73,6 +73,7 @@
    {{ govukFieldset({ legend: { diff --git a/src/server/plugins/engine/views/components/osgridreffield.html b/src/server/plugins/engine/views/components/osgridreffield.html index 6c15957e6..70a84f7ee 100644 --- a/src/server/plugins/engine/views/components/osgridreffield.html +++ b/src/server/plugins/engine/views/components/osgridreffield.html @@ -3,7 +3,9 @@ {% macro OsGridRefField(component) %} {% set hasErrors = component.model.errorMessage %} -
    +
    {{ TextField(component) }}
    diff --git a/test/client/javascripts/map.test.js b/test/client/javascripts/map.test.js index a7f17e570..0138ec26e 100644 --- a/test/client/javascripts/map.test.js +++ b/test/client/javascripts/map.test.js @@ -17,13 +17,13 @@ import createSearchPlugin from '@defra/interactive-map/plugins/search' import maplibreProvider from '@defra/interactive-map/providers/maplibre' import { - ENGLISH_LANG, createFeatureHTML, createFeaturesHTML, getHelpPanelHtml, getTexts, getUIManager } from '~/src/client/javascripts/geospatial-map.js' +import { ENGLISH_LANG } from '~/src/client/javascripts/map-constants.js' import { formSubmitFactory, getCentroidGridRef, From 710eec7e8f51648c973b80144ec95545e24befe2 Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 7 Aug 2026 12:05:38 +0100 Subject: [PATCH 6/9] Import map translations from the JSON translations files --- src/client/javascripts/geospatial-map.js | 127 +++++------------- .../plugins/engine/i18n/translations/cy.json | 41 ++++++ .../engine/i18n/translations/en-GB.json | 41 ++++++ .../javascripts/geospatial-map-i18n.test.js | 31 +++++ 4 files changed, 146 insertions(+), 94 deletions(-) create mode 100644 test/client/javascripts/geospatial-map-i18n.test.js diff --git a/src/client/javascripts/geospatial-map.js b/src/client/javascripts/geospatial-map.js index 4fa8a3092..268bcaa50 100644 --- a/src/client/javascripts/geospatial-map.js +++ b/src/client/javascripts/geospatial-map.js @@ -20,94 +20,18 @@ import { } from '~/src/client/javascripts/map.js' import sssiDataset from '~/src/client/javascripts/sssi-dataset.js' import { formatDelimtedList } from '~/src/client/javascripts/utils.js' +import cy from '~/src/server/plugins/engine/i18n/translations/cy.json' with { type: 'json' } +import enGB from '~/src/server/plugins/engine/i18n/translations/en-GB.json' with { type: 'json' } + +const englishTranslations = enGB.components.geospatialField.map +const welshTranslations = cy.components.geospatialField.map /** * @type {Record} */ export const languageTexts = { - [ENGLISH_LANG]: { - typeDescriptions: { - Point: 'Point', - LineString: 'Line', - Polygon: 'Shape' - }, - panel: { - label: 'How to use this map', - ledeIntro: 'You can add', - ledeOutro: 'to the map.', - lineText: 'a line', - shapeText: 'a shape', - lineOrShapeText: 'a line or shape', - typesPhrasePoint: 'points', - typesPhraseLine: 'lines', - typesPhraseShape: 'shapes', - typesPhraseOr: 'or', - point1: 'Search for a county, place or postcode', - point2: 'Use the + and - icons to zoom in and out', - point3: "Double‑click, or select 'Done', when you have finished drawing", - point4: 'Give the location a name' - }, - buttons: { - Point: 'Add point', - LineString: 'Add line', - Polygon: 'Add shape' - }, - itemLabel1: 'Location', - itemLabel2: 'description', - itemUpdateLink: 'Update', - itemDeleteLink: 'Delete', - itemFocusLink: 'Show', - itemVisuallyHidden: 'location', - details: { - summary: 'Coordinates', - type: 'Type', - centroidGridReference: 'Centre grid reference', - firstPointGridReference: 'First point grid reference', - detailedCoordinates: 'Detailed coordinates' - } - }, - [WELSH_LANG]: { - typeDescriptions: { - Point: 'Pwynt', - LineString: 'Llinell', - Polygon: 'Siâp' - }, - panel: { - label: "Sut i ddefnyddio'r map hwn", - ledeIntro: 'Gallwch ychwanegu', - ledeOutro: "i'r map.", - lineText: 'llinell', - shapeText: 'siâp', - lineOrShapeText: 'llinell neu siâp', - typesPhrasePoint: 'pwyntiau', - typesPhraseLine: 'lliniau', - typesPhraseShape: 'siâpiau', - typesPhraseOr: 'neu', - point1: 'Chwilio am sir, lle neu god post', - point2: 'Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan', - point3: - "Cliciwch ddwywaith, neu dewiswch 'Wedi gorffen', pan fyddwch wedi", - point4: "Rhowch enw i'r lleoliad" - }, - buttons: { - Point: 'Ychwanegu pwynt', - LineString: 'Ychwanegu llinell', - Polygon: 'Ychwanegu siâp' - }, - itemLabel1: 'Lleoliad', - itemLabel2: 'disgrifiad', - itemUpdateLink: 'Diweddaru', - itemDeleteLink: 'Dileu', - itemFocusLink: 'Dangos', - itemVisuallyHidden: 'lleoliad', - details: { - summary: 'Cyfesurynnau', - type: 'Math', - centroidGridReference: 'Canol cyfeirnod grid', - firstPointGridReference: 'Cyfeirnod grid y pwynt cyntaf', - detailedCoordinates: 'Cyfesurynnau manwl' - } - } + [ENGLISH_LANG]: englishTranslations, + [WELSH_LANG]: welshTranslations } /** @@ -181,6 +105,21 @@ function getAllowedTypesPhrase(texts, allowPoint, allowLine, allowShape) { return formatDelimtedList(items, ',', texts.typesPhraseOr) } +/** + * Gets the type description for a given feature + * @param {Feature} feature - the geojson feature + * @param {GeometryTexts} typeDescriptions + */ +function getTypeDescription(feature, typeDescriptions) { + switch (feature.geometry.type) { + case 'Point': + return typeDescriptions.point + case 'LineString': + return typeDescriptions.line + case 'Polygon': + return typeDescriptions.polygon + } +} /** * @param {GeospatialPanelTexts} texts * @param {boolean} allowPoint @@ -429,8 +368,8 @@ export function createFeatureHTML( ` const links = readonly ? focusAction() : `${changeAction()}${deleteAction()}` - const actions = `
      ${links}
    ` + const typeDescription = getTypeDescription(feature, texts.typeDescriptions) return `
    @@ -452,7 +391,7 @@ export function createFeatureHTML(
    ${texts.details.type}
    -
    ${texts.typeDescriptions[feature.geometry.type]}
    +
    ${typeDescription}
    ${texts.details.centroidGridReference}
    @@ -793,14 +732,14 @@ function onMapReadyFactory(context) { // Add info panel map.addPanel('info', { ...helpPanelConfig, - label: texts.panel.label, - html: getHelpPanelHtml(texts.panel, allowPoint, allowLine, allowShape) + label: texts.helpPanel.label, + html: getHelpPanelHtml(texts.helpPanel, allowPoint, allowLine, allowShape) }) if (allowPoint) { map.addButton('btnAddPoint', { variant: 'tertiary', - label: texts.buttons.Point, + label: texts.buttons.point, iconSvgContent: POINT_SVG, onClick: () => { resetActiveFeature() @@ -817,7 +756,7 @@ function onMapReadyFactory(context) { if (allowShape) { map.addButton('btnAddPolygon', { variant: 'tertiary', - label: texts.buttons.Polygon, + label: texts.buttons.polygon, iconSvgContent: POLYGON_SVG, onClick: () => { resetActiveFeature() @@ -834,7 +773,7 @@ function onMapReadyFactory(context) { if (allowLine) { map.addButton('btnAddLine', { variant: 'tertiary', - label: texts.buttons.LineString, + label: texts.buttons.line, iconSvgContent: LINE_SVG, onClick: () => { resetActiveFeature() @@ -1315,9 +1254,9 @@ function onListElKeydownFactory() { /** * @typedef {object} GeometryTexts - * @property {string} Point - the label for a point feature in the list - * @property {string} LineString - the label for a line string feature in the list - * @property {string} Polygon - the label for a polygon feature in the list + * @property {string} point - the label for a point feature in the list + * @property {string} line - the label for a line string feature in the list + * @property {string} polygon - the label for a polygon feature in the list */ /** @@ -1341,7 +1280,7 @@ function onListElKeydownFactory() { /** * @typedef {object} GeospatialLanguageTexts * @property {GeometryTexts} typeDescriptions - the descriptions of the geometry types - * @property {GeospatialPanelTexts} panel - texts for the info panel + * @property {GeospatialPanelTexts} helpPanel - texts for the info panel * @property {GeometryTexts} buttons - the labels for the action buttons * @property {string} itemLabel1 - the first part of the label for a feature in the list * @property {string} itemLabel2 - the second part of the label for a feature in the list diff --git a/src/server/plugins/engine/i18n/translations/cy.json b/src/server/plugins/engine/i18n/translations/cy.json index 7496f4545..f3d380e18 100644 --- a/src/server/plugins/engine/i18n/translations/cy.json +++ b/src/server/plugins/engine/i18n/translations/cy.json @@ -294,6 +294,47 @@ "added_few": "Ychwanegwyd [[count]] lleoliad", "added_many": "Ychwanegwyd [[count]] o leoliadau", "added_other": "Ychwanegwyd [[count]] lleoliad", + "map": { + "helpPanel": { + "label": "Sut i ddefnyddio'r map hwn", + "ledeIntro": "Gallwch ychwanegu", + "ledeOutro": "i'r map.", + "lineText": "llinell", + "shapeText": "siâp", + "lineOrShapeText": "llinell neu siâp", + "typesPhrasePoint": "pwyntiau", + "typesPhraseLine": "lliniau", + "typesPhraseShape": "siâpiau", + "typesPhraseOr": "neu", + "point1": "Chwilio am sir, lle neu god post", + "point2": "Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan", + "point3": "Cliciwch ddwywaith, neu dewiswch 'Wedi gorffen', pan fyddwch wedi", + "point4": "Rhowch enw i'r lleoliad" + }, + "typeDescriptions": { + "point": "Pwynt", + "line": "Llinell", + "polygon": "Siâp" + }, + "buttons": { + "point": "Ychwanegu pwynt", + "line": "Ychwanegu llinell", + "polygon": "Ychwanegu siâp" + }, + "itemLabel1": "Lleoliad", + "itemLabel2": "disgrifiad", + "itemUpdateLink": "Diweddaru", + "itemDeleteLink": "Dileu", + "itemFocusLink": "Dangos", + "itemVisuallyHidden": "lleoliad", + "details": { + "summary": "Cyfesurynnau", + "type": "Math", + "centroidGridReference": "Canol cyfeirnod grid", + "firstPointGridReference": "Cyfeirnod grid y pwynt cyntaf", + "detailedCoordinates": "Cyfesurynnau manwl" + } + }, "validation": { "descriptionRequired": "Rhowch ddisgrifiad ar gyfer lleoliad [[count]]", "wrongCountry": "Rhaid i leoliad [[count]] fod y tu mewn i [[country]]" diff --git a/src/server/plugins/engine/i18n/translations/en-GB.json b/src/server/plugins/engine/i18n/translations/en-GB.json index 3fbf31ca8..462d14e68 100644 --- a/src/server/plugins/engine/i18n/translations/en-GB.json +++ b/src/server/plugins/engine/i18n/translations/en-GB.json @@ -270,6 +270,47 @@ "geospatialField": { "added_one": "Added [[count]] location", "added_other": "Added [[count]] locations", + "map": { + "helpPanel": { + "label": "How to use this map", + "ledeIntro": "You can add", + "ledeOutro": "to the map.", + "lineText": "a line", + "shapeText": "a shape", + "lineOrShapeText": "a line or shape", + "typesPhrasePoint": "points", + "typesPhraseLine": "lines", + "typesPhraseShape": "shapes", + "typesPhraseOr": "or", + "point1": "Search for a county, place or postcode", + "point2": "Use the + and - icons to zoom in and out", + "point3": "Double‑click, or select 'Done', when you have finished drawing", + "point4": "Give the location a name" + }, + "typeDescriptions": { + "point": "Point", + "line": "Line", + "polygon": "Shape" + }, + "buttons": { + "point": "Add point", + "line": "Add line", + "polygon": "Add shape" + }, + "itemLabel1": "Location", + "itemLabel2": "description", + "itemUpdateLink": "Update", + "itemDeleteLink": "Delete", + "itemFocusLink": "Show", + "itemVisuallyHidden": "location", + "details": { + "summary": "Coordinates", + "type": "Type", + "centroidGridReference": "Centre grid reference", + "firstPointGridReference": "First point grid reference", + "detailedCoordinates": "Detailed coordinates" + } + }, "validation": { "descriptionRequired": "Enter description for location [[count]]", "wrongCountry": "Location [[count]] must be within [[country]]" diff --git a/test/client/javascripts/geospatial-map-i18n.test.js b/test/client/javascripts/geospatial-map-i18n.test.js new file mode 100644 index 000000000..6e82682a2 --- /dev/null +++ b/test/client/javascripts/geospatial-map-i18n.test.js @@ -0,0 +1,31 @@ +import { getTexts } from '~/src/client/javascripts/geospatial-map.js' +import { + ENGLISH_LANG, + WELSH_LANG +} from '~/src/client/javascripts/map-constants.js' +import cy from '~/src/server/plugins/engine/i18n/translations/cy.json' with { type: 'json' } +import enGB from '~/src/server/plugins/engine/i18n/translations/en-GB.json' with { type: 'json' } + +describe('geospatial map translations', () => { + it('uses the shared server translation values for the English map UI', () => { + const texts = getTexts(ENGLISH_LANG) + + expect(texts.panel.label).toBe( + enGB.components.geospatialField.map.helpPanel.label + ) + expect(texts.buttons.Point).toBe( + enGB.components.geospatialField.map.buttons.point + ) + }) + + it('uses the shared server translation values for the Welsh map UI', () => { + const texts = getTexts(WELSH_LANG) + + expect(texts.panel.label).toBe( + cy.components.geospatialField.map.helpPanel.label + ) + expect(texts.buttons.Point).toBe( + cy.components.geospatialField.map.buttons.point + ) + }) +}) From b8102e211a33ec971880d1ce5311144643fb39b1 Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 7 Aug 2026 12:15:54 +0100 Subject: [PATCH 7/9] Import map translations from the JSON translations files for EN, LL and OSGR --- src/client/javascripts/location-map.js | 32 ++++++------------- .../plugins/engine/i18n/translations/cy.json | 11 ++++++- .../engine/i18n/translations/en-GB.json | 11 ++++++- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/client/javascripts/location-map.js b/src/client/javascripts/location-map.js index 1c0acb845..ecb54eb4b 100644 --- a/src/client/javascripts/location-map.js +++ b/src/client/javascripts/location-map.js @@ -19,6 +19,11 @@ import { osGridRefToLatLong } from '~/src/client/javascripts/map.js' import sssiDataset from '~/src/client/javascripts/sssi-dataset.js' +import cy from '~/src/server/plugins/engine/i18n/translations/cy.json' with { type: 'json' } +import enGB from '~/src/server/plugins/engine/i18n/translations/en-GB.json' with { type: 'json' } + +const englishTranslations = enGB.components.locationFieldBase.map +const welshTranslations = cy.components.locationFieldBase.map const LOCATION_FIELD_SELECTOR = 'input.govuk-input' @@ -26,25 +31,8 @@ const LOCATION_FIELD_SELECTOR = 'input.govuk-input' * @type {Record} */ export const languageTexts = { - [ENGLISH_LANG]: { - panel: { - label: 'How to use this map', - point1: 'Search for a place or postcode', - point2: 'Use the + and - icons to zoom in and out', - point3: 'Use a mouse or keyboard to centre the point at the location', - point4: 'Click to add the location to the map' - } - }, - [WELSH_LANG]: { - panel: { - label: "Sut i ddefnyddio'r map hwn", - point1: 'Chwilio am sir, lle neu god post', - point2: 'Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan', - point3: - "Defnyddiwch lyfliwr neu allweddlon i ganolbwyntio'r pwynt yn y lleoliad", - point4: "Cliciwch i ychwanegu'r lleoliad i'r map" - } - } + [ENGLISH_LANG]: englishTranslations, + [WELSH_LANG]: welshTranslations } /** @@ -510,7 +498,7 @@ export function processLocation(config, location, index) { map.addPanel('info', { focus: false, showLabel: true, - label: texts.panel.label, + label: texts.helpPanel.label, mobile: { slot: 'drawer', open: true, @@ -529,7 +517,7 @@ export function processLocation(config, location, index) { dismissible: true, modal: false }, - html: `
    • ${texts.panel.point1}
    • ${texts.panel.point2}
    • ${texts.panel.point3}
    • ${texts.panel.point4}
    ` + html: `
    • ${texts.helpPanel.point1}
    • ${texts.helpPanel.point2}
    • ${texts.helpPanel.point3}
    • ${texts.helpPanel.point4}
    ` }) // Enable the interact plugin @@ -549,7 +537,7 @@ export function processLocation(config, location, index) { /** * @typedef {object} LocationLanguageTexts - * @property {LocationPanelTexts} panel - texts for the info panel + * @property {LocationPanelTexts} helpPanel - texts for the info panel */ /** diff --git a/src/server/plugins/engine/i18n/translations/cy.json b/src/server/plugins/engine/i18n/translations/cy.json index f3d380e18..c5a23f880 100644 --- a/src/server/plugins/engine/i18n/translations/cy.json +++ b/src/server/plugins/engine/i18n/translations/cy.json @@ -267,7 +267,16 @@ }, "locationFieldBase": { - "howToFind": "Sut i ddod o hyd i fanylion lleoliad" + "howToFind": "Sut i ddod o hyd i fanylion lleoliad", + "map": { + "helpPanel": { + "label": "Sut i ddefnyddio'r map hwn", + "point1": "Chwilio am sir, lle neu god post", + "point2": "Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan", + "point3": "Defnyddiwch lyfliwr neu allweddlon i ganolbwyntio'r pwynt yn y lleoliad", + "point4": "Cliciwch i ychwanegu'r lleoliad i'r map" + } + } }, "yesNoField": { diff --git a/src/server/plugins/engine/i18n/translations/en-GB.json b/src/server/plugins/engine/i18n/translations/en-GB.json index 462d14e68..ae3b43198 100644 --- a/src/server/plugins/engine/i18n/translations/en-GB.json +++ b/src/server/plugins/engine/i18n/translations/en-GB.json @@ -247,7 +247,16 @@ }, "locationFieldBase": { - "howToFind": "How to find location details" + "howToFind": "How to find location details", + "map": { + "helpPanel": { + "label": "How to use this map", + "point1": "Search for a place or postcode", + "point2": "Use the + and - icons to zoom in and out", + "point3": "Use a mouse or keyboard to centre the point at the location", + "point4": "Click to add the location to the map" + } + } }, "yesNoField": { From dd6c477ecd58f83355ddb9573ddbfa63b286e523 Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 7 Aug 2026 12:27:42 +0100 Subject: [PATCH 8/9] Fix linting --- .../client/javascripts/geospatial-map-i18n.test.js | 8 ++++---- test/client/javascripts/map.test.js | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/test/client/javascripts/geospatial-map-i18n.test.js b/test/client/javascripts/geospatial-map-i18n.test.js index 6e82682a2..0c34709a5 100644 --- a/test/client/javascripts/geospatial-map-i18n.test.js +++ b/test/client/javascripts/geospatial-map-i18n.test.js @@ -10,10 +10,10 @@ describe('geospatial map translations', () => { it('uses the shared server translation values for the English map UI', () => { const texts = getTexts(ENGLISH_LANG) - expect(texts.panel.label).toBe( + expect(texts.helpPanel.label).toBe( enGB.components.geospatialField.map.helpPanel.label ) - expect(texts.buttons.Point).toBe( + expect(texts.buttons.point).toBe( enGB.components.geospatialField.map.buttons.point ) }) @@ -21,10 +21,10 @@ describe('geospatial map translations', () => { it('uses the shared server translation values for the Welsh map UI', () => { const texts = getTexts(WELSH_LANG) - expect(texts.panel.label).toBe( + expect(texts.helpPanel.label).toBe( cy.components.geospatialField.map.helpPanel.label ) - expect(texts.buttons.Point).toBe( + expect(texts.buttons.point).toBe( cy.components.geospatialField.map.buttons.point ) }) diff --git a/test/client/javascripts/map.test.js b/test/client/javascripts/map.test.js index 0138ec26e..3952f7611 100644 --- a/test/client/javascripts/map.test.js +++ b/test/client/javascripts/map.test.js @@ -314,37 +314,37 @@ describe('Maps Client JS', () => { describe('getHelpPanelHtml', () => { const texts = getTexts(ENGLISH_LANG) it('should handle only point', () => { - expect(getHelpPanelHtml(texts.panel, true, false, false)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, true, false, false)).toBe( '

    You can add points to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Give the location a name
    ' ) }) it('should handle only line', () => { - expect(getHelpPanelHtml(texts.panel, false, true, false)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, false, true, false)).toBe( '

    You can add lines to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line
    • Give the location a name
    ' ) }) it('should handle only shape', () => { - expect(getHelpPanelHtml(texts.panel, false, false, true)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, false, false, true)).toBe( '

    You can add shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a shape
    • Give the location a name
    ' ) }) it('should handle point and line', () => { - expect(getHelpPanelHtml(texts.panel, true, true, false)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, true, true, false)).toBe( '

    You can add points or lines to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line
    • Give the location a name
    ' ) }) it('should handle point and shape', () => { - expect(getHelpPanelHtml(texts.panel, true, false, true)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, true, false, true)).toBe( '

    You can add points or shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a shape
    • Give the location a name
    ' ) }) it('should handle line and shape', () => { - expect(getHelpPanelHtml(texts.panel, false, true, true)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, false, true, true)).toBe( '

    You can add lines or shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line or shape
    • Give the location a name
    ' ) }) it('should handle point, line and shape', () => { - expect(getHelpPanelHtml(texts.panel, true, true, true)).toBe( + expect(getHelpPanelHtml(texts.helpPanel, true, true, true)).toBe( '

    You can add points, lines or shapes to the map.

    • Search for a county, place or postcode
    • Use the + and - icons to zoom in and out
    • Double‑click, or select \'Done\', when you have finished drawing a line or shape
    • Give the location a name
    ' ) }) From 0a1403419d4b2ce636ac09060120fe77406a1fee Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 7 Aug 2026 12:58:04 +0100 Subject: [PATCH 9/9] Fix spacing --- src/client/javascripts/geospatial-map.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/javascripts/geospatial-map.js b/src/client/javascripts/geospatial-map.js index 268bcaa50..5414237a1 100644 --- a/src/client/javascripts/geospatial-map.js +++ b/src/client/javascripts/geospatial-map.js @@ -120,6 +120,7 @@ function getTypeDescription(feature, typeDescriptions) { return typeDescriptions.polygon } } + /** * @param {GeospatialPanelTexts} texts * @param {boolean} allowPoint