@defra/forms-engine-plugin 5.0.0-alpha.12 → 5.0.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.public/javascripts/shared.min.js +1 -1
  2. package/.public/javascripts/shared.min.js.map +1 -1
  3. package/.server/client/javascripts/geospatial-map.d.ts +153 -5
  4. package/.server/client/javascripts/geospatial-map.js +147 -46
  5. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  6. package/.server/client/javascripts/location-map.d.ts +38 -0
  7. package/.server/client/javascripts/location-map.js +42 -2
  8. package/.server/client/javascripts/location-map.js.map +1 -1
  9. package/.server/client/javascripts/map-constants.d.ts +3 -0
  10. package/.server/client/javascripts/map-constants.js +4 -0
  11. package/.server/client/javascripts/map-constants.js.map +1 -0
  12. package/.server/client/javascripts/map.d.ts +4 -0
  13. package/.server/client/javascripts/map.js +5 -0
  14. package/.server/client/javascripts/map.js.map +1 -1
  15. package/.server/server/plugins/engine/__stubs__/definitions.d.ts +29 -0
  16. package/.server/server/plugins/engine/__stubs__/definitions.js +125 -0
  17. package/.server/server/plugins/engine/__stubs__/definitions.js.map +1 -0
  18. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +2 -0
  19. package/.server/server/plugins/engine/components/GeospatialField.d.ts +1 -0
  20. package/.server/server/plugins/engine/components/GeospatialField.js +1 -0
  21. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  22. package/.server/server/plugins/engine/components/LatLongField.d.ts +2 -0
  23. package/.server/server/plugins/engine/components/LocationFieldBase.js +3 -0
  24. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  25. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +2 -0
  26. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +2 -1
  27. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  28. package/.server/server/plugins/engine/components/helpers/components.js +2 -1
  29. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  30. package/.server/server/plugins/engine/errors.d.ts +49 -0
  31. package/.server/server/plugins/engine/errors.js +75 -0
  32. package/.server/server/plugins/engine/errors.js.map +1 -0
  33. package/.server/server/plugins/engine/i18n/translations/cy.json +51 -1
  34. package/.server/server/plugins/engine/i18n/translations/en-GB.json +51 -1
  35. package/.server/server/plugins/engine/models/FormModel.js +10 -2
  36. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  37. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  38. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  39. package/.server/server/plugins/engine/views/components/_location-field-base.html +1 -0
  40. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  41. package/.server/server/plugins/engine/views/components/osgridreffield.html +3 -1
  42. package/package.json +1 -1
  43. package/src/client/javascripts/geospatial-map.js +169 -47
  44. package/src/client/javascripts/location-map.js +50 -2
  45. package/src/client/javascripts/map-constants.js +3 -0
  46. package/src/client/javascripts/map.js +5 -0
  47. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  48. package/src/server/plugins/engine/components/GeospatialField.ts +1 -0
  49. package/src/server/plugins/engine/components/LocationFieldBase.ts +4 -0
  50. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +2 -1
  51. package/src/server/plugins/engine/components/helpers/components.ts +2 -1
  52. package/src/server/plugins/engine/errors.ts +78 -0
  53. package/src/server/plugins/engine/i18n/translations/cy.json +51 -1
  54. package/src/server/plugins/engine/i18n/translations/en-GB.json +51 -1
  55. package/src/server/plugins/engine/models/FormModel.ts +12 -2
  56. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  57. package/src/server/plugins/engine/views/components/_location-field-base.html +1 -0
  58. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  59. package/src/server/plugins/engine/views/components/osgridreffield.html +3 -1
@@ -1,4 +1,5 @@
1
1
  import { ControllerType, controllerNameFromPath } from '@defra/forms-model';
2
+ import { UnknownPageControllerError } from "../../errors.js";
2
3
  import * as PageControllers from "../index.js";
3
4
  /**
4
5
  * Creates page instance for each {@link Page} type
@@ -44,7 +45,7 @@ export function createPage(model, pageDef) {
44
45
  }
45
46
  }
46
47
  if (typeof controller === 'undefined') {
47
- throw new Error(`Page controller ${pageDef.controller} does not exist`);
48
+ throw new UnknownPageControllerError(pageDef.controller);
48
49
  }
49
50
  return controller;
50
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pages.js","names":["ControllerType","controllerNameFromPath","PageControllers","createPage","model","pageDef","controllerName","controller","QuestionPageController","Start","StartPageController","Page","Terminal","TerminalPageController","Summary","SummaryPageController","Status","StatusPageController","FileUpload","FileUploadPageController","Repeat","RepeatPageController","controllers","Ctrl","Error"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/helpers/pages.ts"],"sourcesContent":["import {\n ControllerType,\n controllerNameFromPath,\n type Page\n} from '@defra/forms-model'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport * as PageControllers from '~/src/server/plugins/engine/pageControllers/index.js'\n\nexport type PageControllerClass = InstanceType<PageControllerType>\nexport type PageControllerType =\n (typeof PageControllers)[keyof typeof PageControllers]\n\n/**\n * Creates page instance for each {@link Page} type\n */\nexport function createPage(model: FormModel, pageDef: Page) {\n const controllerName = controllerNameFromPath(pageDef.controller)\n\n if (!pageDef.controller) {\n return new PageControllers.QuestionPageController(model, pageDef)\n }\n\n // Patch legacy controllers\n if (controllerName && pageDef.controller !== controllerName) {\n pageDef.controller = controllerName\n }\n\n let controller: PageControllerClass | undefined\n\n switch (pageDef.controller) {\n case ControllerType.Start:\n controller = new PageControllers.StartPageController(model, pageDef)\n break\n\n case ControllerType.Page:\n controller = new PageControllers.QuestionPageController(model, pageDef)\n break\n\n case ControllerType.Terminal:\n controller = new PageControllers.TerminalPageController(model, pageDef)\n break\n\n case ControllerType.Summary:\n controller = new PageControllers.SummaryPageController(model, pageDef)\n break\n\n case ControllerType.Status:\n controller = new PageControllers.StatusPageController(model, pageDef)\n break\n\n case ControllerType.FileUpload:\n controller = new PageControllers.FileUploadPageController(model, pageDef)\n break\n\n case ControllerType.Repeat:\n controller = new PageControllers.RepeatPageController(model, pageDef)\n break\n }\n\n if (typeof controller === 'undefined') {\n if (model.controllers?.[pageDef.controller]) {\n const Ctrl = model.controllers[pageDef.controller]\n controller = new Ctrl(model, pageDef) as unknown as PageControllerClass // type assertion needed because TS can't verify custom controller structure\n }\n }\n\n if (typeof controller === 'undefined') {\n throw new Error(`Page controller ${pageDef.controller} does not exist`)\n }\n\n return controller\n}\n"],"mappings":"AAAA,SACEA,cAAc,EACdC,sBAAsB,QAEjB,oBAAoB;AAG3B,OAAO,KAAKC,eAAe;AAM3B;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACC,KAAgB,EAAEC,OAAa,EAAE;EAC1D,MAAMC,cAAc,GAAGL,sBAAsB,CAACI,OAAO,CAACE,UAAU,CAAC;EAEjE,IAAI,CAACF,OAAO,CAACE,UAAU,EAAE;IACvB,OAAO,IAAIL,eAAe,CAACM,sBAAsB,CAACJ,KAAK,EAAEC,OAAO,CAAC;EACnE;;EAEA;EACA,IAAIC,cAAc,IAAID,OAAO,CAACE,UAAU,KAAKD,cAAc,EAAE;IAC3DD,OAAO,CAACE,UAAU,GAAGD,cAAc;EACrC;EAEA,IAAIC,UAA2C;EAE/C,QAAQF,OAAO,CAACE,UAAU;IACxB,KAAKP,cAAc,CAACS,KAAK;MACvBF,UAAU,GAAG,IAAIL,eAAe,CAACQ,mBAAmB,CAACN,KAAK,EAAEC,OAAO,CAAC;MACpE;IAEF,KAAKL,cAAc,CAACW,IAAI;MACtBJ,UAAU,GAAG,IAAIL,eAAe,CAACM,sBAAsB,CAACJ,KAAK,EAAEC,OAAO,CAAC;MACvE;IAEF,KAAKL,cAAc,CAACY,QAAQ;MAC1BL,UAAU,GAAG,IAAIL,eAAe,CAACW,sBAAsB,CAACT,KAAK,EAAEC,OAAO,CAAC;MACvE;IAEF,KAAKL,cAAc,CAACc,OAAO;MACzBP,UAAU,GAAG,IAAIL,eAAe,CAACa,qBAAqB,CAACX,KAAK,EAAEC,OAAO,CAAC;MACtE;IAEF,KAAKL,cAAc,CAACgB,MAAM;MACxBT,UAAU,GAAG,IAAIL,eAAe,CAACe,oBAAoB,CAACb,KAAK,EAAEC,OAAO,CAAC;MACrE;IAEF,KAAKL,cAAc,CAACkB,UAAU;MAC5BX,UAAU,GAAG,IAAIL,eAAe,CAACiB,wBAAwB,CAACf,KAAK,EAAEC,OAAO,CAAC;MACzE;IAEF,KAAKL,cAAc,CAACoB,MAAM;MACxBb,UAAU,GAAG,IAAIL,eAAe,CAACmB,oBAAoB,CAACjB,KAAK,EAAEC,OAAO,CAAC;MACrE;EACJ;EAEA,IAAI,OAAOE,UAAU,KAAK,WAAW,EAAE;IACrC,IAAIH,KAAK,CAACkB,WAAW,GAAGjB,OAAO,CAACE,UAAU,CAAC,EAAE;MAC3C,MAAMgB,IAAI,GAAGnB,KAAK,CAACkB,WAAW,CAACjB,OAAO,CAACE,UAAU,CAAC;MAClDA,UAAU,GAAG,IAAIgB,IAAI,CAACnB,KAAK,EAAEC,OAAO,CAAmC,EAAC;IAC1E;EACF;EAEA,IAAI,OAAOE,UAAU,KAAK,WAAW,EAAE;IACrC,MAAM,IAAIiB,KAAK,CAAC,mBAAmBnB,OAAO,CAACE,UAAU,iBAAiB,CAAC;EACzE;EAEA,OAAOA,UAAU;AACnB","ignoreList":[]}
1
+ {"version":3,"file":"pages.js","names":["ControllerType","controllerNameFromPath","UnknownPageControllerError","PageControllers","createPage","model","pageDef","controllerName","controller","QuestionPageController","Start","StartPageController","Page","Terminal","TerminalPageController","Summary","SummaryPageController","Status","StatusPageController","FileUpload","FileUploadPageController","Repeat","RepeatPageController","controllers","Ctrl"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/helpers/pages.ts"],"sourcesContent":["import {\n ControllerType,\n controllerNameFromPath,\n type Page\n} from '@defra/forms-model'\n\nimport { UnknownPageControllerError } from '~/src/server/plugins/engine/errors.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport * as PageControllers from '~/src/server/plugins/engine/pageControllers/index.js'\n\nexport type PageControllerClass = InstanceType<PageControllerType>\nexport type PageControllerType =\n (typeof PageControllers)[keyof typeof PageControllers]\n\n/**\n * Creates page instance for each {@link Page} type\n */\nexport function createPage(model: FormModel, pageDef: Page) {\n const controllerName = controllerNameFromPath(pageDef.controller)\n\n if (!pageDef.controller) {\n return new PageControllers.QuestionPageController(model, pageDef)\n }\n\n // Patch legacy controllers\n if (controllerName && pageDef.controller !== controllerName) {\n pageDef.controller = controllerName\n }\n\n let controller: PageControllerClass | undefined\n\n switch (pageDef.controller) {\n case ControllerType.Start:\n controller = new PageControllers.StartPageController(model, pageDef)\n break\n\n case ControllerType.Page:\n controller = new PageControllers.QuestionPageController(model, pageDef)\n break\n\n case ControllerType.Terminal:\n controller = new PageControllers.TerminalPageController(model, pageDef)\n break\n\n case ControllerType.Summary:\n controller = new PageControllers.SummaryPageController(model, pageDef)\n break\n\n case ControllerType.Status:\n controller = new PageControllers.StatusPageController(model, pageDef)\n break\n\n case ControllerType.FileUpload:\n controller = new PageControllers.FileUploadPageController(model, pageDef)\n break\n\n case ControllerType.Repeat:\n controller = new PageControllers.RepeatPageController(model, pageDef)\n break\n }\n\n if (typeof controller === 'undefined') {\n if (model.controllers?.[pageDef.controller]) {\n const Ctrl = model.controllers[pageDef.controller]\n controller = new Ctrl(model, pageDef) as unknown as PageControllerClass // type assertion needed because TS can't verify custom controller structure\n }\n }\n\n if (typeof controller === 'undefined') {\n throw new UnknownPageControllerError(pageDef.controller)\n }\n\n return controller\n}\n"],"mappings":"AAAA,SACEA,cAAc,EACdC,sBAAsB,QAEjB,oBAAoB;AAE3B,SAASC,0BAA0B;AAEnC,OAAO,KAAKC,eAAe;AAM3B;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACC,KAAgB,EAAEC,OAAa,EAAE;EAC1D,MAAMC,cAAc,GAAGN,sBAAsB,CAACK,OAAO,CAACE,UAAU,CAAC;EAEjE,IAAI,CAACF,OAAO,CAACE,UAAU,EAAE;IACvB,OAAO,IAAIL,eAAe,CAACM,sBAAsB,CAACJ,KAAK,EAAEC,OAAO,CAAC;EACnE;;EAEA;EACA,IAAIC,cAAc,IAAID,OAAO,CAACE,UAAU,KAAKD,cAAc,EAAE;IAC3DD,OAAO,CAACE,UAAU,GAAGD,cAAc;EACrC;EAEA,IAAIC,UAA2C;EAE/C,QAAQF,OAAO,CAACE,UAAU;IACxB,KAAKR,cAAc,CAACU,KAAK;MACvBF,UAAU,GAAG,IAAIL,eAAe,CAACQ,mBAAmB,CAACN,KAAK,EAAEC,OAAO,CAAC;MACpE;IAEF,KAAKN,cAAc,CAACY,IAAI;MACtBJ,UAAU,GAAG,IAAIL,eAAe,CAACM,sBAAsB,CAACJ,KAAK,EAAEC,OAAO,CAAC;MACvE;IAEF,KAAKN,cAAc,CAACa,QAAQ;MAC1BL,UAAU,GAAG,IAAIL,eAAe,CAACW,sBAAsB,CAACT,KAAK,EAAEC,OAAO,CAAC;MACvE;IAEF,KAAKN,cAAc,CAACe,OAAO;MACzBP,UAAU,GAAG,IAAIL,eAAe,CAACa,qBAAqB,CAACX,KAAK,EAAEC,OAAO,CAAC;MACtE;IAEF,KAAKN,cAAc,CAACiB,MAAM;MACxBT,UAAU,GAAG,IAAIL,eAAe,CAACe,oBAAoB,CAACb,KAAK,EAAEC,OAAO,CAAC;MACrE;IAEF,KAAKN,cAAc,CAACmB,UAAU;MAC5BX,UAAU,GAAG,IAAIL,eAAe,CAACiB,wBAAwB,CAACf,KAAK,EAAEC,OAAO,CAAC;MACzE;IAEF,KAAKN,cAAc,CAACqB,MAAM;MACxBb,UAAU,GAAG,IAAIL,eAAe,CAACmB,oBAAoB,CAACjB,KAAK,EAAEC,OAAO,CAAC;MACrE;EACJ;EAEA,IAAI,OAAOE,UAAU,KAAK,WAAW,EAAE;IACrC,IAAIH,KAAK,CAACkB,WAAW,GAAGjB,OAAO,CAACE,UAAU,CAAC,EAAE;MAC3C,MAAMgB,IAAI,GAAGnB,KAAK,CAACkB,WAAW,CAACjB,OAAO,CAACE,UAAU,CAAC;MAClDA,UAAU,GAAG,IAAIgB,IAAI,CAACnB,KAAK,EAAEC,OAAO,CAAmC,EAAC;IAC1E;EACF;EAEA,IAAI,OAAOE,UAAU,KAAK,WAAW,EAAE;IACrC,MAAM,IAAIN,0BAA0B,CAACI,OAAO,CAACE,UAAU,CAAC;EAC1D;EAEA,OAAOA,UAAU;AACnB","ignoreList":[]}
@@ -73,6 +73,7 @@
73
73
  <div id="{{component.model.name}}" class="govuk-form-group app-location-field {{ "govuk-form-group--error" if hasErrors }}"
74
74
  data-locationtype="{{component.type | lower}}"
75
75
  data-country="{{component.model.country}}"
76
+ data-lang="{{component.model.lang}}"
76
77
  data-maplayers="{{component.model.mapLayers}}">
77
78
  {{ govukFieldset({
78
79
  legend: {
@@ -2,6 +2,7 @@
2
2
 
3
3
  {% macro GeospatialField(component) %}
4
4
  <div class="app-geospatial-field"
5
+ data-lang="{{component.model.lang}}"
5
6
  data-country="{{component.model.country}}"
6
7
  data-geometryTypes="{{component.model.geometryTypes}}"
7
8
  data-maplayers="{{component.model.mapLayers}}">
@@ -3,7 +3,9 @@
3
3
 
4
4
  {% macro OsGridRefField(component) %}
5
5
  {% set hasErrors = component.model.errorMessage %}
6
- <div class="govuk-form-group app-location-field {{ "govuk-form-group--error" if hasErrors }}" data-locationtype="{{component.type | lower}}">
6
+ <div class="govuk-form-group app-location-field {{ "govuk-form-group--error" if hasErrors }}"
7
+ data-locationtype="{{component.type | lower}}"
8
+ data-lang="{{component.model.lang}}">
7
9
  <div class="app-location-field-inputs">
8
10
  {{ TextField(component) }}
9
11
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "5.0.0-alpha.12",
3
+ "version": "5.0.0-alpha.13",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -4,6 +4,11 @@ import createDatasetsPlugin from '@defra/interactive-map/plugins/datasets'
4
4
  import createDrawPlugin from '@defra/interactive-map/plugins/draw-ml'
5
5
  import { bbox } from '@turf/bbox'
6
6
 
7
+ import {
8
+ DEFAULT_LANG,
9
+ ENGLISH_LANG,
10
+ WELSH_LANG
11
+ } from './map-constants.js'
7
12
  import {
8
13
  EVENTS,
9
14
  createMap,
@@ -15,11 +20,31 @@ import {
15
20
  } from './map.js'
16
21
  import sssiDataset from './sssi-dataset.js'
17
22
  import { formatDelimtedList } from './utils.js'
23
+ import cy from '../../server/plugins/engine/i18n/translations/cy.json' with { type: 'json' }
24
+ import enGB from '../../server/plugins/engine/i18n/translations/en-GB.json' with { type: 'json' }
25
+
26
+ const englishTranslations = enGB.components.geospatialField.map
27
+ const welshTranslations = cy.components.geospatialField.map
28
+
29
+ /**
30
+ * @type {Record<LanguageCode, GeospatialLanguageTexts>}
31
+ */
32
+ export const languageTexts = {
33
+ [ENGLISH_LANG]: englishTranslations,
34
+ [WELSH_LANG]: welshTranslations
35
+ }
36
+
37
+ /**
38
+ * Get the map texts for the given language code
39
+ * @param {LanguageCode} lang - the lanugae code
40
+ */
41
+ export function getTexts(lang = DEFAULT_LANG) {
42
+ return languageTexts[lang]
43
+ }
18
44
 
19
45
  const helpPanelConfig = {
20
46
  focus: false,
21
47
  showLabel: true,
22
- label: 'How to use this map',
23
48
  mobile: {
24
49
  slot: 'drawer',
25
50
  open: true,
@@ -41,59 +66,79 @@ const helpPanelConfig = {
41
66
  }
42
67
 
43
68
  /**
69
+ * @param {GeospatialPanelTexts} texts
44
70
  * @param {boolean} allowLine
45
71
  * @param {boolean} allowShape
46
72
  */
47
- function getLineOrShapeText(allowLine, allowShape) {
73
+ function getLineOrShapeText(texts, allowLine, allowShape) {
48
74
  if (allowLine && allowShape) {
49
- return 'a line or shape'
75
+ return texts.lineOrShapeText
50
76
  }
51
77
  if (allowLine) {
52
- return 'a line'
78
+ return texts.lineText
53
79
  }
54
80
  if (allowShape) {
55
- return 'a shape'
81
+ return texts.shapeText
56
82
  }
57
83
  return ''
58
84
  }
59
85
 
60
86
  /**
87
+ * @param {GeospatialPanelTexts} texts
61
88
  * @param {boolean} allowPoint
62
89
  * @param {boolean} allowLine
63
90
  * @param {boolean} allowShape
64
91
  */
65
- function getAllowedTypesPhrase(allowPoint, allowLine, allowShape) {
92
+ function getAllowedTypesPhrase(texts, allowPoint, allowLine, allowShape) {
66
93
  const items = []
67
94
 
68
95
  if (allowPoint) {
69
- items.push('points')
96
+ items.push(texts.typesPhrasePoint)
70
97
  }
71
98
  if (allowLine) {
72
- items.push('lines')
99
+ items.push(texts.typesPhraseLine)
73
100
  }
74
101
  if (allowShape) {
75
- items.push('shapes')
102
+ items.push(texts.typesPhraseShape)
76
103
  }
77
104
 
78
- return formatDelimtedList(items, ',', 'or')
105
+ return formatDelimtedList(items, ',', texts.typesPhraseOr)
79
106
  }
80
107
 
81
108
  /**
109
+ * Gets the type description for a given feature
110
+ * @param {Feature} feature - the geojson feature
111
+ * @param {GeometryTexts} typeDescriptions
112
+ */
113
+ function getTypeDescription(feature, typeDescriptions) {
114
+ switch (feature.geometry.type) {
115
+ case 'Point':
116
+ return typeDescriptions.point
117
+ case 'LineString':
118
+ return typeDescriptions.line
119
+ case 'Polygon':
120
+ return typeDescriptions.polygon
121
+ }
122
+ }
123
+
124
+ /**
125
+ * @param {GeospatialPanelTexts} texts
82
126
  * @param {boolean} allowPoint
83
127
  * @param {boolean} allowLine
84
128
  * @param {boolean} allowShape
85
129
  */
86
- export function getHelpPanelHtml(allowPoint, allowLine, allowShape) {
87
- const lineOrShapeText = getLineOrShapeText(allowLine, allowShape)
88
- const doneExtra = lineOrShapeText
89
- ? `<li>Double‑click, or select 'Done', when you have finished drawing ${lineOrShapeText}</li>`
130
+ export function getHelpPanelHtml(texts, allowPoint, allowLine, allowShape) {
131
+ const lineOrShapeText = getLineOrShapeText(texts, allowLine, allowShape)
132
+ const point3 = lineOrShapeText
133
+ ? `<li>${texts.point3} ${lineOrShapeText}</li>`
90
134
  : ''
91
135
  const allowedTypesText = getAllowedTypesPhrase(
136
+ texts,
92
137
  allowPoint,
93
138
  allowLine,
94
139
  allowShape
95
140
  )
96
- return `<p class="govuk-body-s govuk-!-margin-bottom-2">You can add ${allowedTypesText} to the map.</p><ul class="govuk-list govuk-list--number govuk-body-s"><li>Search for a county, place or postcode</li><li>Use the + and - icons to zoom in and out</li>${doneExtra}<li>Give the location a name</li></ul>`
141
+ return `<p class="govuk-body-s govuk-!-margin-bottom-2">${texts.ledeIntro} ${allowedTypesText} ${texts.ledeOutro}</p><ul class="govuk-list govuk-list--number govuk-body-s"><li>${texts.point1}</li><li>${texts.point2}</li>${point3}<li>${texts.point4}</li></ul>`
97
142
  }
98
143
 
99
144
  const lineFeatureProperties = {
@@ -108,15 +153,6 @@ const polygonFeatureProperties = {
108
153
  strokeWidth: 2
109
154
  }
110
155
 
111
- /**
112
- * @type {Record<'Point' | 'LineString' | 'Polygon', string>}
113
- */
114
- const typeDescriptions = {
115
- Point: 'Point',
116
- LineString: 'Line',
117
- Polygon: 'Shape'
118
- }
119
-
120
156
  const POINT_SVG =
121
157
  '<path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" /><circle cx="12" cy="10" r="3" />'
122
158
  const POLYGON_SVG =
@@ -199,19 +235,30 @@ export function processGeospatial(config, geospatial, index) {
199
235
  const options = {
200
236
  geometryTypes
201
237
  }
238
+ const lang = /** @type {LanguageCode} */ (
239
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
240
+ geospatial.dataset.lang || DEFAULT_LANG
241
+ )
202
242
  const uiManager = getUIManager(
203
243
  geojson,
204
244
  map,
205
245
  mapId,
206
246
  listEl,
207
247
  geospatialInput,
208
- options
248
+ options,
249
+ lang
209
250
  )
210
251
 
252
+ const languageManager = {
253
+ lang,
254
+ texts: getTexts(lang)
255
+ }
256
+
211
257
  /**
212
258
  * @type {Context}
213
259
  */
214
260
  const context = {
261
+ languageManager,
215
262
  map,
216
263
  featuresManager,
217
264
  activeFeatureManager,
@@ -251,15 +298,17 @@ export function addFeatureToMap(feature, drawPlugin, map) {
251
298
  * @param {string} mapId - the ID of the map
252
299
  * @param {boolean} [disabled] - render the list with disabled links
253
300
  * @param {boolean} [readonly] - render the list in readonly mode
301
+ * @param {LanguageCode} [lang] - the language for the feature details
254
302
  */
255
303
  export function createFeaturesHTML(
256
304
  features,
257
305
  mapId,
258
306
  disabled = false,
259
- readonly = false
307
+ readonly = false,
308
+ lang = DEFAULT_LANG
260
309
  ) {
261
310
  return `<dl class="govuk-summary-list">
262
- ${features.map((feature, index) => createFeatureHTML(feature, index, mapId, disabled, readonly)).join('\n')}
311
+ ${features.map((feature, index) => createFeatureHTML(feature, index, mapId, disabled, readonly, lang)).join('\n')}
263
312
  </dl>`
264
313
  }
265
314
 
@@ -279,14 +328,17 @@ export function focusFeature(feature, mapProvider) {
279
328
  * @param {string} mapId - the ID of the map
280
329
  * @param {boolean} [disabled] - render the list with disabled links
281
330
  * @param {boolean} [readonly] - render the list item in readonly mode
331
+ * @param {LanguageCode} [lang] - the language for the feature details
282
332
  */
283
333
  export function createFeatureHTML(
284
334
  feature,
285
335
  index,
286
336
  mapId,
287
337
  disabled = false,
288
- readonly = false
338
+ readonly = false,
339
+ lang = DEFAULT_LANG
289
340
  ) {
341
+ const texts = getTexts(lang)
290
342
  const flattened = feature.geometry.coordinates.flat(2)
291
343
 
292
344
  const points = []
@@ -302,28 +354,28 @@ export function createFeatureHTML(
302
354
  // Change action link
303
355
  const changeAction = () => `<li class="govuk-summary-list__actions-list-item">
304
356
  <a class="govuk-link govuk-link--no-visited-state ${disabled ? 'govuk-link--disabled' : ''}" href="#${mapId}" data-action="edit" data-id="${feature.id}"
305
- data-type="${feature.geometry.type}">Update<span class="govuk-visually-hidden"> location</span></a>
357
+ data-type="${feature.geometry.type}">${texts.itemUpdateLink}<span class="govuk-visually-hidden"> ${texts.itemVisuallyHidden}</span></a>
306
358
  </li>`
307
359
 
308
360
  // Delete action link
309
361
  const deleteAction = () => `<li class="govuk-summary-list__actions-list-item">
310
362
  <a class="govuk-link govuk-link--no-visited-state ${disabled ? 'govuk-link--disabled' : ''}" href="#" data-action="delete" data-id="${feature.id}"
311
- data-type="${feature.geometry.type}">Delete<span class="govuk-visually-hidden"> location</span></a>
363
+ data-type="${feature.geometry.type}">${texts.itemDeleteLink}<span class="govuk-visually-hidden"> ${texts.itemVisuallyHidden}</span></a>
312
364
  </li>`
313
365
 
314
366
  // Focus action link
315
367
  const focusAction = () => `<li class="govuk-summary-list__actions-list-item">
316
- <a class="govuk-link govuk-link--no-visited-state" href="#${mapId}" data-action="focus" data-id="${feature.id}">Show<span class="govuk-visually-hidden"> location</span></a>
368
+ <a class="govuk-link govuk-link--no-visited-state" href="#${mapId}" data-action="focus" data-id="${feature.id}">${texts.itemFocusLink}<span class="govuk-visually-hidden"> ${texts.itemVisuallyHidden}</span></a>
317
369
  </li>`
318
370
 
319
371
  const links = readonly ? focusAction() : `${changeAction()}${deleteAction()}`
320
-
321
372
  const actions = `<ul class="govuk-summary-list__actions-list">${links}</ul>`
373
+ const typeDescription = getTypeDescription(feature, texts.typeDescriptions)
322
374
 
323
375
  return `<div class="govuk-summary-list__row govuk-summary-list__row--no-border">
324
376
  <dt class="govuk-summary-list__key">
325
377
  <div class="govuk-form-group">
326
- <label class="govuk-label govuk-label--s" ${readonly ? '' : `for="description_${index}"`}>Location ${index + 1} description</label>
378
+ <label class="govuk-label govuk-label--s" ${readonly ? '' : `for="description_${index}"`}>${texts.itemLabel1} ${index + 1} ${texts.itemLabel2}</label>
327
379
  ${description}
328
380
  </div>
329
381
  </dt>
@@ -334,24 +386,24 @@ export function createFeatureHTML(
334
386
  <div class="govuk-summary-list__row">
335
387
  <details class="govuk-details govuk-!-margin-bottom-2">
336
388
  <summary class="govuk-details__summary">
337
- <span class="govuk-details__summary-text">Coordinates</span>
389
+ <span class="govuk-details__summary-text">${texts.details.summary}</span>
338
390
  </summary>
339
391
  <div class="govuk-details__text">
340
392
  <dl class="govuk-summary-list">
341
393
  <div class="govuk-summary-list__row">
342
- <dt class="govuk-summary-list__key">Type</dt>
343
- <dd class="govuk-summary-list__value">${typeDescriptions[feature.geometry.type]}</dd>
394
+ <dt class="govuk-summary-list__key">${texts.details.type}</dt>
395
+ <dd class="govuk-summary-list__value">${typeDescription}</dd>
344
396
  </div>
345
397
  <div class="govuk-summary-list__row">
346
- <dt class="govuk-summary-list__key">Centre grid reference</dt>
398
+ <dt class="govuk-summary-list__key">${texts.details.centroidGridReference}</dt>
347
399
  <dd class="govuk-summary-list__value">${feature.properties.centroidGridReference}</dd>
348
400
  </div>
349
401
  <div class="govuk-summary-list__row">
350
- <dt class="govuk-summary-list__key">First point grid reference</dt>
402
+ <dt class="govuk-summary-list__key">${texts.details.firstPointGridReference}</dt>
351
403
  <dd class="govuk-summary-list__value">${feature.properties.coordinateGridReference}</dd>
352
404
  </div>
353
405
  <div class="govuk-summary-list__row">
354
- <dt class="govuk-summary-list__key">Detailed coordinates</dt>
406
+ <dt class="govuk-summary-list__key">${texts.details.detailedCoordinates}</dt>
355
407
  <dd class="govuk-summary-list__value">
356
408
  <ol class="govuk-list govuk-list--number">${coordinates}</ol>
357
409
  </dd>
@@ -510,11 +562,24 @@ function getFeaturesManager(geojson) {
510
562
  * @param {string} mapId - the ID of the map
511
563
  * @param {HTMLDivElement} listEl - where to render the feature list
512
564
  * @param {Function} renderValue - function that renders the features JSON into the hidden textarea
565
+ * @param {LanguageCode} [lang] - the language for the feature details
513
566
  * @returns {RenderList}
514
567
  */
515
- function getListRenderer(geojson, mapId, listEl, renderValue) {
568
+ function getListRenderer(
569
+ geojson,
570
+ mapId,
571
+ listEl,
572
+ renderValue,
573
+ lang = DEFAULT_LANG
574
+ ) {
516
575
  return function renderList(disabled = false) {
517
- const html = createFeaturesHTML(geojson.features, mapId, disabled)
576
+ const html = createFeaturesHTML(
577
+ geojson.features,
578
+ mapId,
579
+ disabled,
580
+ false,
581
+ lang
582
+ )
518
583
 
519
584
  listEl.innerHTML = html
520
585
 
@@ -542,6 +607,7 @@ function getValueRenderer(geojson, geospatialInput) {
542
607
  * @param {HTMLDivElement} listEl - where to render the feature list
543
608
  * @param {HTMLTextAreaElement} geospatialInput - the geospatial textarea
544
609
  * @param { UIManagerOptions | undefined } options - extra options such as allowable geometry types
610
+ * @param {LanguageCode} [lang] - the language for the feature details
545
611
  */
546
612
  export function getUIManager(
547
613
  geojson,
@@ -549,7 +615,8 @@ export function getUIManager(
549
615
  mapId,
550
616
  listEl,
551
617
  geospatialInput,
552
- options
618
+ options,
619
+ lang = DEFAULT_LANG
553
620
  ) {
554
621
  /**
555
622
  * Get a CSV list of geometry types the user can create
@@ -594,7 +661,7 @@ export function getUIManager(
594
661
  }
595
662
 
596
663
  const renderValue = getValueRenderer(geojson, geospatialInput)
597
- const renderList = getListRenderer(geojson, mapId, listEl, renderValue)
664
+ const renderList = getListRenderer(geojson, mapId, listEl, renderValue, lang)
598
665
 
599
666
  /** @type {UIManager} */
600
667
  return {
@@ -661,17 +728,19 @@ function onMapReadyFactory(context) {
661
728
  const allowPoint = types.includes('point')
662
729
  const allowLine = types.includes('line')
663
730
  const allowShape = types.includes('shape')
731
+ const { texts } = context.languageManager
664
732
 
665
733
  // Add info panel
666
734
  map.addPanel('info', {
667
735
  ...helpPanelConfig,
668
- html: getHelpPanelHtml(allowPoint, allowLine, allowShape)
736
+ label: texts.helpPanel.label,
737
+ html: getHelpPanelHtml(texts.helpPanel, allowPoint, allowLine, allowShape)
669
738
  })
670
739
 
671
740
  if (allowPoint) {
672
741
  map.addButton('btnAddPoint', {
673
742
  variant: 'tertiary',
674
- label: 'Add point',
743
+ label: texts.buttons.point,
675
744
  iconSvgContent: POINT_SVG,
676
745
  onClick: () => {
677
746
  resetActiveFeature()
@@ -688,7 +757,7 @@ function onMapReadyFactory(context) {
688
757
  if (allowShape) {
689
758
  map.addButton('btnAddPolygon', {
690
759
  variant: 'tertiary',
691
- label: 'Add shape',
760
+ label: texts.buttons.polygon,
692
761
  iconSvgContent: POLYGON_SVG,
693
762
  onClick: () => {
694
763
  resetActiveFeature()
@@ -705,7 +774,7 @@ function onMapReadyFactory(context) {
705
774
  if (allowLine) {
706
775
  map.addButton('btnAddLine', {
707
776
  variant: 'tertiary',
708
- label: 'Add line',
777
+ label: texts.buttons.line,
709
778
  iconSvgContent: LINE_SVG,
710
779
  onClick: () => {
711
780
  resetActiveFeature()
@@ -1129,6 +1198,7 @@ function onListElKeydownFactory() {
1129
1198
  * Renders the features into the list
1130
1199
  * @callback RenderList
1131
1200
  * @param {boolean} [disabled] - whether to render the list with disabled links
1201
+ * @param {LanguageCode} [lang] - the language for the feature details
1132
1202
  * @returns {void}
1133
1203
  */
1134
1204
 
@@ -1183,8 +1253,59 @@ function onListElKeydownFactory() {
1183
1253
  * @property {GetAllowableGeometryTypes} getAllowableGeometryTypes - function that returns the array of geometry types a user can create
1184
1254
  */
1185
1255
 
1256
+ /**
1257
+ * @typedef {object} GeometryTexts
1258
+ * @property {string} point - the label for a point feature in the list
1259
+ * @property {string} line - the label for a line string feature in the list
1260
+ * @property {string} polygon - the label for a polygon feature in the list
1261
+ */
1262
+
1263
+ /**
1264
+ * @typedef {object} GeospatialPanelTexts
1265
+ * @property {string} label - the label for the info panel
1266
+ * @property {string} ledeIntro - the lede intro text for the info panel
1267
+ * @property {string} ledeOutro - the lede outro text for the info panel
1268
+ * @property {string} lineText - the label for the info panel line text
1269
+ * @property {string} shapeText - the label for the info panel shape text
1270
+ * @property {string} point1 - the text for bullet point 1 in the info panel
1271
+ * @property {string} point2 - the text for bullet point 2 in the info panel
1272
+ * @property {string} point3 - the text for bullet point 3 in the info panel
1273
+ * @property {string} point4 - the text for bullet point 4 in the info panel
1274
+ * @property {string} lineOrShapeText - the label for the info panel line or shape text
1275
+ * @property {string} typesPhrasePoint - the label for the info panel types phrase point
1276
+ * @property {string} typesPhraseLine - the label for the info panel types phrase line
1277
+ * @property {string} typesPhraseShape - the label for the info panel types phrase shape
1278
+ * @property {string} typesPhraseOr - the label for the info panel types phrase "or"
1279
+ */
1280
+
1281
+ /**
1282
+ * @typedef {object} GeospatialLanguageTexts
1283
+ * @property {GeometryTexts} typeDescriptions - the descriptions of the geometry types
1284
+ * @property {GeospatialPanelTexts} helpPanel - texts for the info panel
1285
+ * @property {GeometryTexts} buttons - the labels for the action buttons
1286
+ * @property {string} itemLabel1 - the first part of the label for a feature in the list
1287
+ * @property {string} itemLabel2 - the second part of the label for a feature in the list
1288
+ * @property {string} itemUpdateLink - the label for the "update" link for a feature in the list
1289
+ * @property {string} itemDeleteLink - the label for the "delete" link for a feature in the list
1290
+ * @property {string} itemFocusLink - the label for the "focus" link for a feature in the list
1291
+ * @property {string} itemVisuallyHidden - visually hidden text to add context to action links
1292
+ * @property {object} details - labels for the details section of a feature in the list
1293
+ * @property {string} details.summary - label for the summary of a feature in the list
1294
+ * @property {string} details.type - label for the type of a feature in the list
1295
+ * @property {string} details.centroidGridReference - label for the centroid grid reference of a feature in the list
1296
+ * @property {string} details.firstPointGridReference - label for the first point grid reference of a feature in the list
1297
+ * @property {string} details.detailedCoordinates - label for the detailed coordinates of a feature in the list
1298
+ */
1299
+
1300
+ /**
1301
+ * @typedef {object} LanguageManager
1302
+ * @property {LanguageCode} lang - the language for the interactive map
1303
+ * @property {GeospatialLanguageTexts} texts - the descriptions of the geometry types
1304
+ */
1305
+
1186
1306
  /**
1187
1307
  * @typedef {object} Context
1308
+ * @property {LanguageManager} languageManager - the language manager for the interactive map
1188
1309
  * @property {InteractiveMap} map - the interactive map
1189
1310
  * @property {MapLibreMap} [mapProvider] - the interactive map provider
1190
1311
  * @property {FeaturesManager} featuresManager - the features manager
@@ -1195,5 +1316,6 @@ function onListElKeydownFactory() {
1195
1316
  */
1196
1317
 
1197
1318
  /**
1319
+ * @import { LanguageCode } from './map.js'
1198
1320
  * @import { MapLibreMap, UIManagerOptions } from './map.js'
1199
1321
  */
@@ -1,6 +1,11 @@
1
1
  // @ts-expect-error - no types
2
2
  import createDatasetsPlugin from '@defra/interactive-map/plugins/datasets'
3
3
 
4
+ import {
5
+ DEFAULT_LANG,
6
+ ENGLISH_LANG,
7
+ WELSH_LANG
8
+ } from './map-constants.js'
4
9
  import {
5
10
  EVENTS,
6
11
  centerMap,
@@ -14,9 +19,30 @@ import {
14
19
  osGridRefToLatLong
15
20
  } from './map.js'
16
21
  import sssiDataset from './sssi-dataset.js'
22
+ import cy from '../../server/plugins/engine/i18n/translations/cy.json' with { type: 'json' }
23
+ import enGB from '../../server/plugins/engine/i18n/translations/en-GB.json' with { type: 'json' }
24
+
25
+ const englishTranslations = enGB.components.locationFieldBase.map
26
+ const welshTranslations = cy.components.locationFieldBase.map
17
27
 
18
28
  const LOCATION_FIELD_SELECTOR = 'input.govuk-input'
19
29
 
30
+ /**
31
+ * @type {Record<LanguageCode, LocationLanguageTexts>}
32
+ */
33
+ export const languageTexts = {
34
+ [ENGLISH_LANG]: englishTranslations,
35
+ [WELSH_LANG]: welshTranslations
36
+ }
37
+
38
+ /**
39
+ * Get the map texts for the given language code
40
+ * @param {LanguageCode} lang - the lanugae code
41
+ */
42
+ export function getTexts(lang = DEFAULT_LANG) {
43
+ return languageTexts[lang]
44
+ }
45
+
20
46
  /**
21
47
  * Gets initial map config for a location field
22
48
  * @param {HTMLDivElement} locationField - the location field element
@@ -437,6 +463,11 @@ export function processLocation(config, location, index) {
437
463
  initConfig.plugins = [createDatasetsPlugin({ datasets })]
438
464
  }
439
465
 
466
+ const lang = /** @type {LanguageCode} */ (
467
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
468
+ location.dataset.lang || DEFAULT_LANG
469
+ )
470
+
440
471
  const { map, interactPlugin } = createMap(mapId, initConfig, config)
441
472
 
442
473
  map.on(
@@ -461,11 +492,13 @@ export function processLocation(config, location, index) {
461
492
  throw new Error('Not implemented')
462
493
  }
463
494
 
495
+ const texts = getTexts(lang)
496
+
464
497
  // Add info panel
465
498
  map.addPanel('info', {
466
499
  focus: false,
467
500
  showLabel: true,
468
- label: 'How to use the map',
501
+ label: texts.helpPanel.label,
469
502
  mobile: {
470
503
  slot: 'drawer',
471
504
  open: true,
@@ -484,7 +517,7 @@ export function processLocation(config, location, index) {
484
517
  dismissible: true,
485
518
  modal: false
486
519
  },
487
- html: '<ul><li>Search for a place or postcode</li><li>Use the + and - icons to zoom in and out</li><li>Use a mouse or keyboard to centre the point at the location</li><li>Click to add the location to the map</li></ul>'
520
+ html: `<ul><li>${texts.helpPanel.point1}</li><li>${texts.helpPanel.point2}</li><li>${texts.helpPanel.point3}</li><li>${texts.helpPanel.point4}</li></ul>`
488
521
  })
489
522
 
490
523
  // Enable the interact plugin
@@ -494,5 +527,20 @@ export function processLocation(config, location, index) {
494
527
  }
495
528
 
496
529
  /**
530
+ * @typedef {object} LocationPanelTexts
531
+ * @property {string} label - the label for the info panel
532
+ * @property {string} point1 - the text for bullet point 1 in the info panel
533
+ * @property {string} point2 - the text for bullet point 2 in the info panel
534
+ * @property {string} point3 - the text for bullet point 3 in the info panel
535
+ * @property {string} point4 - the text for bullet point 4 in the info panel
536
+ */
537
+
538
+ /**
539
+ * @typedef {object} LocationLanguageTexts
540
+ * @property {LocationPanelTexts} helpPanel - texts for the info panel
541
+ */
542
+
543
+ /**
544
+ * @import { LanguageCode } from './map.js'
497
545
  * @import { InteractiveMap, InteractiveMapInitConfig, MapCenter, MapLibreMap, MapsEnvironmentConfig } from './map.js'
498
546
  */
@@ -0,0 +1,3 @@
1
+ export const ENGLISH_LANG = 'en-GB'
2
+ export const WELSH_LANG = 'cy'
3
+ export const DEFAULT_LANG = ENGLISH_LANG
@@ -460,3 +460,8 @@ export function getMapCountryLayers(apiPath, country) {
460
460
  /**
461
461
  * @import { Feature } from '../../server/plugins/engine/types.js'
462
462
  */
463
+
464
+ /**
465
+ * Supported language codes
466
+ * @typedef {('en-GB'|'cy')} LanguageCode
467
+ */