@defra/forms-engine-plugin 5.0.0-alpha.11 → 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 (101) 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/CheckboxesField.d.ts +20 -2
  19. package/.server/server/plugins/engine/components/CheckboxesField.js +24 -12
  20. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +4 -0
  22. package/.server/server/plugins/engine/components/EastingNorthingField.js +1 -1
  23. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  24. package/.server/server/plugins/engine/components/FileUploadField.d.ts +10 -1
  25. package/.server/server/plugins/engine/components/FileUploadField.js +18 -6
  26. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  27. package/.server/server/plugins/engine/components/GeospatialField.d.ts +22 -0
  28. package/.server/server/plugins/engine/components/GeospatialField.js +33 -9
  29. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  30. package/.server/server/plugins/engine/components/LatLongField.d.ts +4 -0
  31. package/.server/server/plugins/engine/components/LocationFieldBase.js +7 -1
  32. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  33. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +4 -0
  34. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +9 -2
  35. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  36. package/.server/server/plugins/engine/components/helpers/components.js +2 -1
  37. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  38. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +5 -0
  39. package/.server/server/plugins/engine/components/helpers/geospatial.js +25 -16
  40. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  41. package/.server/server/plugins/engine/errors.d.ts +49 -0
  42. package/.server/server/plugins/engine/errors.js +75 -0
  43. package/.server/server/plugins/engine/errors.js.map +1 -0
  44. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  45. package/.server/server/plugins/engine/form-availability.js +3 -2
  46. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  47. package/.server/server/plugins/engine/form-context.js +2 -2
  48. package/.server/server/plugins/engine/form-context.js.map +1 -1
  49. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +1 -1
  50. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +30 -9
  51. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  52. package/.server/server/plugins/engine/i18n/translations/cy.json +117 -11
  53. package/.server/server/plugins/engine/i18n/translations/en-GB.json +80 -11
  54. package/.server/server/plugins/engine/i18n/types.d.ts +1 -0
  55. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  56. package/.server/server/plugins/engine/models/FormModel.js +10 -2
  57. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  58. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  59. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  60. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  61. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  62. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  63. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  64. package/.server/server/plugins/engine/pageControllers/validationOptions.js +1 -1
  65. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  66. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  67. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  68. package/.server/server/plugins/engine/views/components/_location-field-base.html +2 -1
  69. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  70. package/.server/server/plugins/engine/views/components/osgridreffield.html +3 -1
  71. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  72. package/package.json +1 -1
  73. package/src/client/javascripts/geospatial-map.js +169 -47
  74. package/src/client/javascripts/location-map.js +50 -2
  75. package/src/client/javascripts/map-constants.js +3 -0
  76. package/src/client/javascripts/map.js +5 -0
  77. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  78. package/src/server/plugins/engine/components/CheckboxesField.ts +34 -13
  79. package/src/server/plugins/engine/components/EastingNorthingField.ts +1 -1
  80. package/src/server/plugins/engine/components/FileUploadField.ts +26 -6
  81. package/src/server/plugins/engine/components/GeospatialField.ts +44 -11
  82. package/src/server/plugins/engine/components/LocationFieldBase.ts +7 -1
  83. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +9 -2
  84. package/src/server/plugins/engine/components/helpers/components.ts +2 -1
  85. package/src/server/plugins/engine/components/helpers/geospatial.ts +32 -22
  86. package/src/server/plugins/engine/errors.ts +78 -0
  87. package/src/server/plugins/engine/form-availability.ts +8 -2
  88. package/src/server/plugins/engine/form-context.ts +4 -2
  89. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +15 -10
  90. package/src/server/plugins/engine/i18n/translations/cy.json +117 -11
  91. package/src/server/plugins/engine/i18n/translations/en-GB.json +80 -11
  92. package/src/server/plugins/engine/i18n/types.ts +1 -0
  93. package/src/server/plugins/engine/models/FormModel.ts +12 -2
  94. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  95. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  96. package/src/server/plugins/engine/pageControllers/validationOptions.ts +2 -2
  97. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  98. package/src/server/plugins/engine/views/components/_location-field-base.html +2 -1
  99. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  100. package/src/server/plugins/engine/views/components/osgridreffield.html +3 -1
  101. package/src/server/plugins/engine/views/unavailable.html +14 -11
@@ -1,9 +1,14 @@
1
- import { FormStatus, type FormMetadata } from '@defra/forms-model'
1
+ import {
2
+ FormStatus,
3
+ type FormDefinition,
4
+ type FormMetadata
5
+ } from '@defra/forms-model'
2
6
  import Boom from '@hapi/boom'
3
7
 
4
8
  export interface OfflineBoomData {
5
9
  offline: true
6
10
  metadata: FormMetadata
11
+ definition: FormDefinition | undefined
7
12
  }
8
13
 
9
14
  /**
@@ -13,6 +18,7 @@ export interface OfflineBoomData {
13
18
  */
14
19
  export function assertFormAvailable(
15
20
  metadata: FormMetadata,
21
+ definition: FormDefinition | undefined,
16
22
  formState: FormStatus,
17
23
  isPreview: boolean
18
24
  ): void {
@@ -21,7 +27,7 @@ export function assertFormAvailable(
21
27
  formState === FormStatus.Live &&
22
28
  !isPreview
23
29
  ) {
24
- const data: OfflineBoomData = { offline: true, metadata }
30
+ const data: OfflineBoomData = { offline: true, metadata, definition }
25
31
  throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), {
26
32
  statusCode: 503,
27
33
  data
@@ -56,13 +56,14 @@ export async function getFormModel(
56
56
  const formState = resolveState(state)
57
57
 
58
58
  const metadata = await formsService.getFormMetadata(slug)
59
- assertFormAvailable(metadata, formState, isPreview)
60
59
 
61
60
  const definition = await formsService.getFormDefinition(
62
61
  metadata.id,
63
62
  formState
64
63
  )
65
64
 
65
+ assertFormAvailable(metadata, definition, formState, isPreview)
66
+
66
67
  if (!definition) {
67
68
  throw Boom.notFound(
68
69
  `No definition found for form metadata ${metadata.id} (${slug}) ${state}`
@@ -145,7 +146,6 @@ export async function resolveFormModel(
145
146
  const metadata = await formsService.getFormMetadata(slug)
146
147
  const formState = resolveState(state)
147
148
  const isPreview = options.isPreview ?? isPreviewState(state, options)
148
- assertFormAvailable(metadata, formState, isPreview)
149
149
  const stateMetadata = metadata[formState]
150
150
 
151
151
  if (!stateMetadata) {
@@ -199,6 +199,8 @@ export async function resolveFormModel(
199
199
  cache.set(cacheKey, entry)
200
200
  }
201
201
 
202
+ assertFormAvailable(metadata, entry.model.def, formState, isPreview)
203
+
202
204
  return entry.model
203
205
  }
204
206
 
@@ -16,7 +16,9 @@ const opts = {
16
16
  * because Joi cannot resolve custom functions from a plain string.
17
17
  * Plain strings with only built-in tokens ({{#label}}, {{#limit}}) work without wrapping.
18
18
  */
19
- export function buildValidationMessages(t: (key: string) => string) {
19
+ export function buildValidationMessages(
20
+ t: (key: string, options?: Record<string, unknown>) => string
21
+ ) {
20
22
  return {
21
23
  // Expressions — contain lowerFirst custom function
22
24
  required: joi.expression(t('validation.required'), opts) as JoiExpression,
@@ -57,15 +59,18 @@ export function buildValidationMessages(t: (key: string) => string) {
57
59
  dateMax: t('components.dateField.validation.dateMax'),
58
60
 
59
61
  unicode: t('validation.unicode'),
60
- arrayMin: t('validation.arrayMin'),
61
- arrayMax: t('validation.arrayMax'),
62
- arrayLength: t('validation.arrayLength'),
63
- featuresMin: t('validation.featuresMin'),
64
- featuresMax: t('validation.featuresMax'),
65
- featuresLength: t('validation.featuresLength'),
66
- filesMin: t('validation.filesMin'),
67
- filesMax: t('validation.filesMax'),
68
- filesLength: t('validation.filesLength')
62
+ // These generic templates are shared across fields with differing limits, so a
63
+ // fixed singular count is used here — components needing accurate singular/plural
64
+ // text (e.g. FileUploadField) build their own per-instance messages instead.
65
+ arrayMin: t('validation.arrayMin', { count: 1 }),
66
+ arrayMax: t('validation.arrayMax', { count: 1 }),
67
+ arrayLength: t('validation.arrayLength', { count: 1 }),
68
+ featuresMin: t('validation.featuresMin', { count: 1 }),
69
+ featuresMax: t('validation.featuresMax', { count: 1 }),
70
+ featuresLength: t('validation.featuresLength', { count: 1 }),
71
+ filesMin: t('validation.filesMin', { count: 1 }),
72
+ filesMax: t('validation.filesMax', { count: 1 }),
73
+ filesLength: t('validation.filesLength', { count: 1 })
69
74
  }
70
75
  }
71
76
 
@@ -16,15 +16,61 @@
16
16
  "numberMax": "Rhaid i {{#label}} fod yn {{#limit}} neu is",
17
17
  "maxWords": "Rhaid i {{#label}} fod yn {{#limit}} o eiriau neu lai",
18
18
  "unicode": "Mae {{#label}} yn cynnwys nodau annilys, er enghraifft, dashes hirion",
19
- "arrayMin": "Dewiswch o leiaf {{#limit}} o opsiynau o'r rhestr",
20
- "arrayMax": "Dim ond {{#limit}} y gellir eu dewis o'r rhestr",
21
- "arrayLength": "Dewiswch ddim ond {{#limit}} o opsiynau o'r rhestr",
22
- "featuresMin": "Diffiniwch o leiaf {{#limit}} nodwedd",
23
- "featuresMax": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
24
- "featuresLength": "Diffiniwch nodweddion {{#limit}} yn union",
25
- "filesMin": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
26
- "filesMax": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
27
- "filesLength": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau"
19
+ "arrayMin_zero": "Dewiswch o leiaf 1 opsiwn o'r rhestr",
20
+ "arrayMin_one": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
21
+ "arrayMin_two": "Dewiswch o leiaf ddau opsiwn o'r rhestr",
22
+ "arrayMin_few": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
23
+ "arrayMin_many": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
24
+ "arrayMin_other": "Dewiswch o leiaf {{#limit}} opsiwn o'r rhestr",
25
+ "arrayMax_zero": "Dim ond {{#limit}} opsiynau y gellir eu dewis o'r rhestr",
26
+ "arrayMax_one": "Dim ond {{#limit}} opsiwn y gellir ei ddewis o'r rhestr",
27
+ "arrayMax_two": "Dim ond dwy opsiwn y gellir eu dewis o'r rhestr",
28
+ "arrayMax_few": "Dim ond {{#limit}} opsiwn y gellir eu dewis o'r rhestr",
29
+ "arrayMax_many": "Dim ond {{#limit}} opsiwn y gellir eu dewis o'r rhestr",
30
+ "arrayMax_other": "Dim ond {{#limit}} opsiwn y gellir eu dewis o'r rhestr",
31
+ "arrayLength_zero": "Dewiswch 0 opsiwn yn unig o'r rhestr",
32
+ "arrayLength_one": "Dewiswch un opsiwn yn unig o'r rhestr",
33
+ "arrayLength_two": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
34
+ "arrayLength_few": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
35
+ "arrayLength_many": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
36
+ "arrayLength_other": "Dewiswch {{#limit}} opsiwn yn unig o'r rhestr",
37
+
38
+ "featuresMin_zero": "Diffiniwch o leiaf {{#limit}} nodwedd",
39
+ "featuresMin_one": "Diffiniwch o leiaf un nodwedd",
40
+ "featuresMin_two": "Diffiniwch o leiaf {{#limit}} nodwedd",
41
+ "featuresMin_few": "Diffiniwch o leiaf {{#limit}} nodwedd",
42
+ "featuresMin_many": "Diffiniwch o leiaf {{#limit}} nodwedd",
43
+ "featuresMin_other": "Diffiniwch o leiaf {{#limit}} nodwedd",
44
+ "featuresMax_zero": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
45
+ "featuresMax_one": "Dim ond {{#limit}} nodwedd y gellir ei diffinio",
46
+ "featuresMax_two": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
47
+ "featuresMax_few": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
48
+ "featuresMax_many": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
49
+ "featuresMax_other": "Dim ond {{#limit}} nodwedd y gellir eu diffinio",
50
+ "featuresLength_zero": "Diffiniwch yn union {{#limit}} nodwedd",
51
+ "featuresLength_one": "Diffiniwch un nodwedd yn union",
52
+ "featuresLength_two": "Diffiniwch nodweddion {{#limit}} yn union",
53
+ "featuresLength_few": "Diffiniwch nodweddion {{#limit}} yn union",
54
+ "featuresLength_many": "Diffiniwch nodweddion {{#limit}} yn union",
55
+ "featuresLength_other": "Diffiniwch nodweddion {{#limit}} yn union",
56
+ "filesMin_zero": "Rhaid i chi uwchlwytho {{#limit}} ffeil neu fwy",
57
+ "filesMin_one": "Rhaid i chi uwchlwytho un ffeil neu fwy",
58
+ "filesMin_two": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
59
+ "filesMin_few": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
60
+ "filesMin_many": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
61
+ "filesMin_other": "Rhaid i chi uwchlwytho {{#limit}} o ffeiliau neu fwy",
62
+ "filesMax_zero": "Dim ond {{#limit}} ffeil neu lai y gallwch eu huwchlwytho",
63
+ "filesMax_one": "Dim ond un ffeil neu lai y gallwch ei huwchlwytho",
64
+ "filesMax_two": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
65
+ "filesMax_few": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
66
+ "filesMax_many": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
67
+ "filesMax_other": "Dim ond {{#limit}} o ffeiliau neu lai y gallwch eu huwchlwytho",
68
+ "filesLength_zero": "Rhaid i chi uwchlwytho union {{#limit}} ffeil",
69
+ "filesLength_one": "Rhaid i chi uwchlwytho union un ffeil",
70
+ "filesLength_two": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau",
71
+ "filesLength_few": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau",
72
+ "filesLength_many": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau",
73
+ "filesLength_other": "Rhaid i chi uwchlwytho'n union {{#limit}} o ffeiliau"
28
74
  },
29
75
 
30
76
  "errors": {
@@ -44,7 +90,7 @@
44
90
  "and": "a",
45
91
  "england": "Lloegr",
46
92
  "wales": "Cymru",
47
- "northernIreland": "Gogledd Iwerddon",
93
+ "northern-ireland": "Gogledd Iwerddon",
48
94
  "scotland": "Yr Alban",
49
95
  "sendFeedback": "Anfon adborth"
50
96
  },
@@ -161,6 +207,16 @@
161
207
  "bannerForce": "Rhagolwg o ffurflen [[previewMode]] yw hon. Peidiwch â rhoi gwybodaeth bersonol.",
162
208
  "bannerText": "Dyma ragolwg o dudalen ffurflen [[previewMode]] rydych chi'n ei golygu.",
163
209
  "previousPages": "Mae'n dibynnu ar atebion o dudalennau cynharach yn y ffurflen. Yn y fersiwn fyw, bydd angen i ddefnyddwyr gwblhau'r cwestiynau hynny yn gyntaf."
210
+ },
211
+
212
+ "formUnavailable": {
213
+ "title": "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael",
214
+ "subHeading": "Nid yw '[[formTitle]]' ar gael mwyach.",
215
+ "contactDetailsHeading": "Manylion cyswllt ar gyfer [[organisationName]]",
216
+ "telephone": "Ffôn",
217
+ "callCharges": "Dysgwch am gostau galwadau",
218
+ "email": "E-bost",
219
+ "onlineContactForm": "Ffurflen gyswllt ar-lein"
164
220
  }
165
221
  },
166
222
 
@@ -267,7 +323,16 @@
267
323
  },
268
324
 
269
325
  "locationFieldBase": {
270
- "howToFind": "Sut i ddod o hyd i fanylion lleoliad"
326
+ "howToFind": "Sut i ddod o hyd i fanylion lleoliad",
327
+ "map": {
328
+ "helpPanel": {
329
+ "label": "Sut i ddefnyddio'r map hwn",
330
+ "point1": "Chwilio am sir, lle neu god post",
331
+ "point2": "Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan",
332
+ "point3": "Defnyddiwch lyfliwr neu allweddlon i ganolbwyntio'r pwynt yn y lleoliad",
333
+ "point4": "Cliciwch i ychwanegu'r lleoliad i'r map"
334
+ }
335
+ }
271
336
  },
272
337
 
273
338
  "yesNoField": {
@@ -294,6 +359,47 @@
294
359
  "added_few": "Ychwanegwyd [[count]] lleoliad",
295
360
  "added_many": "Ychwanegwyd [[count]] o leoliadau",
296
361
  "added_other": "Ychwanegwyd [[count]] lleoliad",
362
+ "map": {
363
+ "helpPanel": {
364
+ "label": "Sut i ddefnyddio'r map hwn",
365
+ "ledeIntro": "Gallwch ychwanegu",
366
+ "ledeOutro": "i'r map.",
367
+ "lineText": "llinell",
368
+ "shapeText": "siâp",
369
+ "lineOrShapeText": "llinell neu siâp",
370
+ "typesPhrasePoint": "pwyntiau",
371
+ "typesPhraseLine": "lliniau",
372
+ "typesPhraseShape": "siâpiau",
373
+ "typesPhraseOr": "neu",
374
+ "point1": "Chwilio am sir, lle neu god post",
375
+ "point2": "Defnyddiwch yr eiconau + a - i chwyddo i mewn ac allan",
376
+ "point3": "Cliciwch ddwywaith, neu dewiswch 'Wedi gorffen', pan fyddwch wedi",
377
+ "point4": "Rhowch enw i'r lleoliad"
378
+ },
379
+ "typeDescriptions": {
380
+ "point": "Pwynt",
381
+ "line": "Llinell",
382
+ "polygon": "Siâp"
383
+ },
384
+ "buttons": {
385
+ "point": "Ychwanegu pwynt",
386
+ "line": "Ychwanegu llinell",
387
+ "polygon": "Ychwanegu siâp"
388
+ },
389
+ "itemLabel1": "Lleoliad",
390
+ "itemLabel2": "disgrifiad",
391
+ "itemUpdateLink": "Diweddaru",
392
+ "itemDeleteLink": "Dileu",
393
+ "itemFocusLink": "Dangos",
394
+ "itemVisuallyHidden": "lleoliad",
395
+ "details": {
396
+ "summary": "Cyfesurynnau",
397
+ "type": "Math",
398
+ "centroidGridReference": "Canol cyfeirnod grid",
399
+ "firstPointGridReference": "Cyfeirnod grid y pwynt cyntaf",
400
+ "detailedCoordinates": "Cyfesurynnau manwl"
401
+ }
402
+ },
297
403
  "validation": {
298
404
  "descriptionRequired": "Rhowch ddisgrifiad ar gyfer lleoliad [[count]]",
299
405
  "wrongCountry": "Rhaid i leoliad [[count]] fod y tu mewn i [[country]]"
@@ -16,15 +16,24 @@
16
16
  "numberMax": "{{#label}} must be {{#limit}} or lower",
17
17
  "maxWords": "{{#label}} must be {{#limit}} words or fewer",
18
18
  "unicode": "{{#label}} includes invalid characters, for example, long dashes",
19
- "arrayMin": "Select at least {{#limit}} options from the list",
20
- "arrayMax": "Only {{#limit}} can be selected from the list",
21
- "arrayLength": "Select only {{#limit}} options from the list",
22
- "featuresMin": "Define at least {{#limit}} features",
23
- "featuresMax": "Only {{#limit}} features can be defined",
24
- "featuresLength": "Define exactly {{#limit}} features",
25
- "filesMin": "You must upload {{#limit}} files or more",
26
- "filesMax": "You can only upload {{#limit}} files or less",
27
- "filesLength": "You must upload exactly {{#limit}} files"
19
+ "arrayMin_one": "Select at least {{#limit}} option from the list",
20
+ "arrayMin_other": "Select at least {{#limit}} options from the list",
21
+ "arrayMax_one": "Only {{#limit}} option can be selected from the list",
22
+ "arrayMax_other": "Only {{#limit}} options can be selected from the list",
23
+ "arrayLength_one": "Select only {{#limit}} option from the list",
24
+ "arrayLength_other": "Select only {{#limit}} options from the list",
25
+ "featuresMin_one": "Define at least {{#limit}} feature",
26
+ "featuresMin_other": "Define at least {{#limit}} features",
27
+ "featuresMax_one": "Only {{#limit}} feature can be defined",
28
+ "featuresMax_other": "Only {{#limit}} features can be defined",
29
+ "featuresLength_one": "Define exactly {{#limit}} feature",
30
+ "featuresLength_other": "Define exactly {{#limit}} features",
31
+ "filesMin_one": "You must upload {{#limit}} file or more",
32
+ "filesMin_other": "You must upload {{#limit}} files or more",
33
+ "filesMax_one": "You can only upload {{#limit}} file or less",
34
+ "filesMax_other": "You can only upload {{#limit}} files or less",
35
+ "filesLength_one": "You must upload exactly {{#limit}} file",
36
+ "filesLength_other": "You must upload exactly {{#limit}} files"
28
37
  },
29
38
 
30
39
  "errors": {
@@ -44,7 +53,7 @@
44
53
  "and": "and",
45
54
  "england": "England",
46
55
  "wales": "Wales",
47
- "northernIreland": "Northern Ireland",
56
+ "northern-ireland": "Northern Ireland",
48
57
  "scotland": "Scotland",
49
58
  "sendFeedback": "Send feedback"
50
59
  },
@@ -145,6 +154,16 @@
145
154
  "bannerForce": "This is a preview of a [[previewMode]] form. Do not enter personal information.",
146
155
  "bannerText": "This is a preview of a [[previewMode]] form page you are editing.",
147
156
  "previousPages": "It depends on answers from earlier pages in the form. In the live version, users will need to complete those questions first."
157
+ },
158
+
159
+ "formUnavailable": {
160
+ "title": "Sorry, this form is unavailable",
161
+ "subHeading": "'[[formTitle]]' is no longer available.",
162
+ "contactDetailsHeading": "Contact details for [[organisationName]]",
163
+ "telephone": "Telephone",
164
+ "callCharges": "Find out about call charges",
165
+ "email": "Email",
166
+ "onlineContactForm": "Online contact form"
148
167
  }
149
168
  },
150
169
 
@@ -247,7 +266,16 @@
247
266
  },
248
267
 
249
268
  "locationFieldBase": {
250
- "howToFind": "How to find location details"
269
+ "howToFind": "How to find location details",
270
+ "map": {
271
+ "helpPanel": {
272
+ "label": "How to use this map",
273
+ "point1": "Search for a place or postcode",
274
+ "point2": "Use the + and - icons to zoom in and out",
275
+ "point3": "Use a mouse or keyboard to centre the point at the location",
276
+ "point4": "Click to add the location to the map"
277
+ }
278
+ }
251
279
  },
252
280
 
253
281
  "yesNoField": {
@@ -270,6 +298,47 @@
270
298
  "geospatialField": {
271
299
  "added_one": "Added [[count]] location",
272
300
  "added_other": "Added [[count]] locations",
301
+ "map": {
302
+ "helpPanel": {
303
+ "label": "How to use this map",
304
+ "ledeIntro": "You can add",
305
+ "ledeOutro": "to the map.",
306
+ "lineText": "a line",
307
+ "shapeText": "a shape",
308
+ "lineOrShapeText": "a line or shape",
309
+ "typesPhrasePoint": "points",
310
+ "typesPhraseLine": "lines",
311
+ "typesPhraseShape": "shapes",
312
+ "typesPhraseOr": "or",
313
+ "point1": "Search for a county, place or postcode",
314
+ "point2": "Use the + and - icons to zoom in and out",
315
+ "point3": "Double‑click, or select 'Done', when you have finished drawing",
316
+ "point4": "Give the location a name"
317
+ },
318
+ "typeDescriptions": {
319
+ "point": "Point",
320
+ "line": "Line",
321
+ "polygon": "Shape"
322
+ },
323
+ "buttons": {
324
+ "point": "Add point",
325
+ "line": "Add line",
326
+ "polygon": "Add shape"
327
+ },
328
+ "itemLabel1": "Location",
329
+ "itemLabel2": "description",
330
+ "itemUpdateLink": "Update",
331
+ "itemDeleteLink": "Delete",
332
+ "itemFocusLink": "Show",
333
+ "itemVisuallyHidden": "location",
334
+ "details": {
335
+ "summary": "Coordinates",
336
+ "type": "Type",
337
+ "centroidGridReference": "Centre grid reference",
338
+ "firstPointGridReference": "First point grid reference",
339
+ "detailedCoordinates": "Detailed coordinates"
340
+ }
341
+ },
273
342
  "validation": {
274
343
  "descriptionRequired": "Enter description for location [[count]]",
275
344
  "wrongCountry": "Location [[count]] must be within [[country]]"
@@ -24,6 +24,7 @@ export type FormDefinitionTranslations = Record<
24
24
  shortDescription: string
25
25
  errorDescription: string
26
26
  paymentDescription: string
27
+ instructionText: string
27
28
  }>
28
29
  >
29
30
  sections: Record<string, Partial<Pick<Section, 'title'>>>
@@ -39,6 +39,10 @@ import {
39
39
  type Component
40
40
  } from '../components/helpers/components.js'
41
41
  import { todayAsDateOnly } from '../date-helper.js'
42
+ import {
43
+ ConditionBuildError,
44
+ SchemaValidationError
45
+ } from '../errors.js'
42
46
  import {
43
47
  findPage,
44
48
  getError,
@@ -124,7 +128,7 @@ export class FormModel {
124
128
  const result = schema.validate(def, { abortEarly: false })
125
129
 
126
130
  if (result.error) {
127
- throw result.error
131
+ throw new SchemaValidationError(result.error)
128
132
  }
129
133
 
130
134
  // Make a clone of the shallow copy returned
@@ -292,7 +296,13 @@ export class FormModel {
292
296
  })
293
297
 
294
298
  const { name, displayName, value } = condition
295
- const expr = this.toConditionExpression(value, parser)
299
+
300
+ let expr
301
+ try {
302
+ expr = this.toConditionExpression(value, parser)
303
+ } catch (cause) {
304
+ throw new ConditionBuildError(displayName, { cause })
305
+ }
296
306
 
297
307
  const fn = (evaluationState: FormState) => {
298
308
  const ctx = this.toConditionContext(evaluationState, this.conditions)
@@ -1,19 +1,40 @@
1
- import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model'
1
+ import {
2
+ type FormDefinition,
3
+ type FormMetadata,
4
+ type FormMetadataContact
5
+ } from '@defra/forms-model'
6
+
7
+ import { createFormTranslator } from '../i18n/createFormTranslator.js'
8
+ import { getAvailableLanguages } from '../i18n/languages.js'
9
+ import { type Translator } from '../types/index.js'
2
10
 
3
11
  export interface UnavailableViewModel {
4
12
  pageTitle: string
5
13
  formTitle: string
6
14
  organisationName: string
7
15
  contact?: FormMetadataContact
16
+ context: { translator: Translator }
17
+ language: string
18
+ languages: { code: string; name: string }[]
8
19
  }
9
20
 
10
21
  export function unavailableViewModel(
11
- metadata: FormMetadata
22
+ metadata: FormMetadata,
23
+ definition: FormDefinition | undefined,
24
+ language: string
12
25
  ): UnavailableViewModel {
26
+ const translator = createFormTranslator(metadata, definition, language)
27
+ const { t, tForm } = translator
13
28
  return {
14
- pageTitle: 'Sorry, this form is unavailable',
15
- formTitle: metadata.title,
29
+ pageTitle: t('pages.formUnavailable.title'),
30
+ formTitle: tForm('title') || metadata.title,
16
31
  organisationName: metadata.organisation,
17
- contact: metadata.contact
32
+ contact: metadata.contact,
33
+ context: { translator },
34
+ language,
35
+ // Always get Welsh and English
36
+ languages: getAvailableLanguages({
37
+ metadata: { translations: { cy: {} } }
38
+ } as unknown as FormDefinition)
18
39
  }
19
40
  }
@@ -4,6 +4,7 @@ import {
4
4
  type Page
5
5
  } from '@defra/forms-model'
6
6
 
7
+ import { UnknownPageControllerError } from '../../errors.js'
7
8
  import { type FormModel } from '../../models/index.js'
8
9
  import * as PageControllers from '../index.js'
9
10
 
@@ -66,7 +67,7 @@ export function createPage(model: FormModel, pageDef: Page) {
66
67
  }
67
68
 
68
69
  if (typeof controller === 'undefined') {
69
- throw new Error(`Page controller ${pageDef.controller} does not exist`)
70
+ throw new UnknownPageControllerError(pageDef.controller)
70
71
  }
71
72
 
72
73
  return controller
@@ -26,7 +26,7 @@ export const opts = {
26
26
  * import messageTemplate directly and get the English version.
27
27
  */
28
28
  export const messageTemplate: ValidationMessages = buildValidationMessages(
29
- (key) => t(key, 'en-GB')
29
+ (key, options) => t(key, 'en-GB', options)
30
30
  )
31
31
 
32
32
  export const messages: LanguageMessagesExt = {
@@ -79,7 +79,7 @@ export const messagesPre: LanguageMessages =
79
79
  messages as unknown as LanguageMessages
80
80
 
81
81
  export function buildLanguageMessages(
82
- t: (key: string) => string
82
+ t: (key: string, options?: Record<string, unknown>) => string
83
83
  ): LanguageMessages {
84
84
  const vm = buildValidationMessages(t)
85
85
  return {
@@ -2,6 +2,8 @@ import { type Request, type ResponseToolkit, type Server } from '@hapi/hapi'
2
2
 
3
3
  import { isOfflineBoom } from './form-availability.js'
4
4
  import { unavailableViewModel } from './models/unavailable-view-model.js'
5
+ import { type AnyFormRequest } from './types.js'
6
+ import { resolveLanguage } from '../../utils/utils.js'
5
7
 
6
8
  /**
7
9
  * Registers a server-wide onPreResponse extension that intercepts the offline
@@ -17,10 +19,12 @@ export function registerUnavailableResponse(server: Server) {
17
19
  return h.continue
18
20
  }
19
21
 
20
- const { metadata } = response.data
22
+ const { metadata, definition } = response.data
23
+
24
+ const language = resolveLanguage(request as unknown as AnyFormRequest)
21
25
 
22
26
  return h
23
- .view('unavailable', unavailableViewModel(metadata))
27
+ .view('unavailable', unavailableViewModel(metadata, definition, language))
24
28
  .header('Cache-Control', 'no-store, no-cache, must-revalidate')
25
29
  .header('X-Robots-Tag', 'noindex, nofollow')
26
30
  .code(200)
@@ -61,7 +61,7 @@
61
61
  {% set detailsHtml %}
62
62
  {% if component.model.instructionText %}
63
63
  {{ govukDetails({
64
- summaryText: t('components.locationFieldBase.howToFind'),
64
+ summaryText: component.model.instructionSummary,
65
65
  html: component.model.instructionText | markdown | safe,
66
66
  classes: "govuk-!-margin-top-3"
67
67
  }) }}
@@ -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>
@@ -1,32 +1,35 @@
1
1
  {% extends baseLayoutPath %}
2
2
 
3
+ {% set t = context.translator.t %}
4
+ {% set tForm = context.translator.tForm %}
5
+
3
6
  {% block content %}
4
7
  <div class="govuk-grid-row">
5
8
  <div class="govuk-grid-column-two-thirds">
6
- <h1 class="govuk-heading-l">Sorry, this form is unavailable</h1>
7
- <p class="govuk-body">'{{ formTitle }}' is no longer available.</p>
8
- <h2 class="govuk-heading-m">Contact details for {{ organisationName }}</h2>
9
+ <h1 class="govuk-heading-l">{{ t('pages.formUnavailable.title') }}</h1>
10
+ <p class="govuk-body">{{ t('pages.formUnavailable.subHeading', { formTitle: formTitle }) }}</p>
11
+ <h2 class="govuk-heading-m">{{ t('pages.formUnavailable.contactDetailsHeading', { organisationName: organisationName }) }}</h2>
9
12
 
10
13
  {% if contact.phone %}
11
- <h3 class="govuk-heading-s">Telephone</h3>
14
+ <h3 class="govuk-heading-s">{{ t('pages.formUnavailable.telephone') }}</h3>
12
15
  <div class="app-prose-scope">
13
- {{ contact.phone | markdown(3) | safe }}
16
+ {{ tForm('contact.phone') | markdown(3) | safe }}
14
17
  </div>
15
- <p class="govuk-body"><a href="https://www.gov.uk/call-charges" class="govuk-link govuk-link--no-visited-state">Find out about call charges</a></p>
18
+ <p class="govuk-body"><a href="https://www.gov.uk/call-charges" class="govuk-link govuk-link--no-visited-state">{{ t('pages.formUnavailable.callCharges') }}</a></p>
16
19
  {% endif %}
17
20
 
18
21
  {% if contact.email %}
19
- <h3 class="govuk-heading-s">Email</h3>
22
+ <h3 class="govuk-heading-s">{{ t('pages.formUnavailable.email') }}</h3>
20
23
  <ul class="govuk-list">
21
- <li><a class="govuk-link govuk-link--no-visited-state" href="mailto:{{ contact.email.address }}">{{ contact.email.address }}</a></li>
22
- <li>{{ contact.email.responseTime }}</li>
24
+ <li><a class="govuk-link govuk-link--no-visited-state" href="mailto:{{ tForm('contact.email.address') }}">{{ tForm('contact.email.address') }}</a></li>
25
+ <li>{{ tForm('contact.email.responseTime') }}</li>
23
26
  </ul>
24
27
  {% endif %}
25
28
 
26
29
  {% if contact.online %}
27
- <h3 class="govuk-heading-s">Online contact form</h3>
30
+ <h3 class="govuk-heading-s">{{ t('pages.formUnavailable.onlineContactForm') }}</h3>
28
31
  <ul class="govuk-list">
29
- <li><a class="govuk-link govuk-link--no-visited-state" href="{{ contact.online.url }}">{{ contact.online.text }}</a></li>
32
+ <li><a class="govuk-link govuk-link--no-visited-state" href="{{ tForm('contact.online.url') }}">{{ tForm('contact.online.text') }}</a></li>
30
33
  </ul>
31
34
  {% endif %}
32
35
  </div>