@defra/forms-engine-plugin 5.0.0-alpha.2 → 5.0.0-alpha.21

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 (346) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.LICENSE.txt +7 -0
  3. package/.public/javascripts/application.min.js.map +1 -1
  4. package/.public/javascripts/shared.min.js +1 -1
  5. package/.public/javascripts/shared.min.js.LICENSE.txt +7 -0
  6. package/.public/javascripts/shared.min.js.map +1 -1
  7. package/.public/stylesheets/application.min.css.map +1 -1
  8. package/.server/client/javascripts/file-upload.js +6 -3
  9. package/.server/client/javascripts/file-upload.js.map +1 -1
  10. package/.server/client/javascripts/geospatial-map.d.ts +153 -5
  11. package/.server/client/javascripts/geospatial-map.js +157 -77
  12. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  13. package/.server/client/javascripts/govuk.js +2 -1
  14. package/.server/client/javascripts/govuk.js.map +1 -1
  15. package/.server/client/javascripts/location-map.d.ts +44 -0
  16. package/.server/client/javascripts/location-map.js +54 -5
  17. package/.server/client/javascripts/location-map.js.map +1 -1
  18. package/.server/client/javascripts/map-constants.d.ts +3 -0
  19. package/.server/client/javascripts/map-constants.js +4 -0
  20. package/.server/client/javascripts/map-constants.js.map +1 -0
  21. package/.server/client/javascripts/map.d.ts +10 -0
  22. package/.server/client/javascripts/map.js +42 -0
  23. package/.server/client/javascripts/map.js.map +1 -1
  24. package/.server/client/javascripts/maps.d.ts +5 -0
  25. package/.server/client/javascripts/maps.js +6 -0
  26. package/.server/client/javascripts/maps.js.map +1 -0
  27. package/.server/client/javascripts/shared.d.ts +1 -4
  28. package/.server/client/javascripts/shared.js +1 -4
  29. package/.server/client/javascripts/shared.js.map +1 -1
  30. package/.server/client/javascripts/sssi-dataset.d.ts +18 -20
  31. package/.server/client/javascripts/sssi-dataset.js +37 -39
  32. package/.server/client/javascripts/sssi-dataset.js.map +1 -1
  33. package/.server/client/stylesheets/maps.scss +4 -0
  34. package/.server/client/stylesheets/shared.scss +1 -5
  35. package/.server/server/constants.d.ts +2 -0
  36. package/.server/server/constants.js +2 -0
  37. package/.server/server/constants.js.map +1 -1
  38. package/.server/server/plugins/engine/__stubs__/definitions.d.ts +29 -0
  39. package/.server/server/plugins/engine/__stubs__/definitions.js +125 -0
  40. package/.server/server/plugins/engine/__stubs__/definitions.js.map +1 -0
  41. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +9 -3
  42. package/.server/server/plugins/engine/components/AutocompleteField.js +12 -0
  43. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  44. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +21 -0
  45. package/.server/server/plugins/engine/components/CheckboxesField.js +30 -5
  46. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  47. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -0
  48. package/.server/server/plugins/engine/components/ComponentBase.js +2 -0
  49. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  50. package/.server/server/plugins/engine/components/ComponentCollection.js +11 -6
  51. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  52. package/.server/server/plugins/engine/components/DatePartsField.d.ts +6 -3
  53. package/.server/server/plugins/engine/components/DatePartsField.js +12 -7
  54. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  55. package/.server/server/plugins/engine/components/DeclarationField.d.ts +5 -2
  56. package/.server/server/plugins/engine/components/DeclarationField.js +6 -4
  57. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  58. package/.server/server/plugins/engine/components/Details.d.ts +1 -0
  59. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +33 -2
  60. package/.server/server/plugins/engine/components/EastingNorthingField.js +110 -54
  61. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  62. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +4 -1
  63. package/.server/server/plugins/engine/components/EmailAddressField.js +2 -1
  64. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  65. package/.server/server/plugins/engine/components/FileUploadField.d.ts +21 -0
  66. package/.server/server/plugins/engine/components/FileUploadField.js +35 -3
  67. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  68. package/.server/server/plugins/engine/components/FormComponent.d.ts +5 -1
  69. package/.server/server/plugins/engine/components/FormComponent.js +5 -2
  70. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  71. package/.server/server/plugins/engine/components/GeospatialField.d.ts +23 -0
  72. package/.server/server/plugins/engine/components/GeospatialField.js +33 -9
  73. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  74. package/.server/server/plugins/engine/components/Html.d.ts +1 -0
  75. package/.server/server/plugins/engine/components/InsetText.d.ts +1 -0
  76. package/.server/server/plugins/engine/components/LatLongField.d.ts +32 -2
  77. package/.server/server/plugins/engine/components/LatLongField.js +85 -48
  78. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  79. package/.server/server/plugins/engine/components/List.d.ts +1 -0
  80. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +1 -0
  81. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +8 -2
  82. package/.server/server/plugins/engine/components/LocationFieldBase.js +25 -6
  83. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  84. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +12 -3
  85. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +21 -13
  86. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  87. package/.server/server/plugins/engine/components/Markdown.d.ts +1 -0
  88. package/.server/server/plugins/engine/components/Markdown.js +1 -1
  89. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  90. package/.server/server/plugins/engine/components/MonthYearField.d.ts +5 -2
  91. package/.server/server/plugins/engine/components/MonthYearField.js +13 -9
  92. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  93. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +1 -0
  94. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
  95. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +4 -3
  96. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  97. package/.server/server/plugins/engine/components/NotificationBanner.d.ts +1 -0
  98. package/.server/server/plugins/engine/components/NumberField.d.ts +5 -0
  99. package/.server/server/plugins/engine/components/NumberField.js +16 -0
  100. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  101. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +103 -1
  102. package/.server/server/plugins/engine/components/OsGridRefField.js +13 -3
  103. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  104. package/.server/server/plugins/engine/components/PaymentField.d.ts +3 -0
  105. package/.server/server/plugins/engine/components/PaymentField.js +17 -6
  106. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  107. package/.server/server/plugins/engine/components/SelectField.d.ts +1 -0
  108. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +1 -0
  109. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +1 -0
  110. package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -0
  111. package/.server/server/plugins/engine/components/UkAddressField.js +23 -8
  112. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  113. package/.server/server/plugins/engine/components/YesNoField.d.ts +5 -2
  114. package/.server/server/plugins/engine/components/YesNoField.js +17 -8
  115. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  116. package/.server/server/plugins/engine/components/helpers/components.js +3 -3
  117. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  118. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +8 -1
  119. package/.server/server/plugins/engine/components/helpers/geospatial.js +56 -15
  120. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -1
  121. package/.server/server/plugins/engine/components/helpers/index.d.ts +7 -0
  122. package/.server/server/plugins/engine/components/helpers/index.js +13 -1
  123. package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
  124. package/.server/server/plugins/engine/components/types.d.ts +1 -0
  125. package/.server/server/plugins/engine/components/types.js.map +1 -1
  126. package/.server/server/plugins/engine/errors.d.ts +49 -0
  127. package/.server/server/plugins/engine/errors.js +75 -0
  128. package/.server/server/plugins/engine/errors.js.map +1 -0
  129. package/.server/server/plugins/engine/form-availability.d.ts +3 -2
  130. package/.server/server/plugins/engine/form-availability.js +3 -2
  131. package/.server/server/plugins/engine/form-availability.js.map +1 -1
  132. package/.server/server/plugins/engine/form-context.js +2 -2
  133. package/.server/server/plugins/engine/form-context.js.map +1 -1
  134. package/.server/server/plugins/engine/helpers.d.ts +22 -2
  135. package/.server/server/plugins/engine/helpers.js +44 -5
  136. package/.server/server/plugins/engine/helpers.js.map +1 -1
  137. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +4 -1
  138. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +31 -7
  139. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -1
  140. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +4 -4
  141. package/.server/server/plugins/engine/i18n/createFormTranslator.js +33 -4
  142. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
  143. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +1 -1
  144. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +9 -0
  145. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -1
  146. package/.server/server/plugins/engine/i18n/form.d.ts +13 -0
  147. package/.server/server/plugins/engine/i18n/form.js +35 -0
  148. package/.server/server/plugins/engine/i18n/form.js.map +1 -0
  149. package/.server/server/plugins/engine/i18n/languages.d.ts +10 -0
  150. package/.server/server/plugins/engine/i18n/languages.js +19 -0
  151. package/.server/server/plugins/engine/i18n/languages.js.map +1 -0
  152. package/.server/server/plugins/engine/i18n/translations/cy.json +192 -9
  153. package/.server/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  154. package/.server/server/plugins/engine/i18n/types.d.ts +3 -0
  155. package/.server/server/plugins/engine/i18n/types.js.map +1 -1
  156. package/.server/server/plugins/engine/index.d.ts +4 -0
  157. package/.server/server/plugins/engine/index.js +4 -0
  158. package/.server/server/plugins/engine/index.js.map +1 -1
  159. package/.server/server/plugins/engine/models/FormModel.d.ts +0 -4
  160. package/.server/server/plugins/engine/models/FormModel.js +35 -23
  161. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  162. package/.server/server/plugins/engine/models/SummaryViewModel.js +9 -4
  163. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  164. package/.server/server/plugins/engine/models/types.d.ts +28 -1
  165. package/.server/server/plugins/engine/models/types.js.map +1 -1
  166. package/.server/server/plugins/engine/models/unavailable-view-model.d.ts +11 -2
  167. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  168. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  169. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +14 -6
  170. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  171. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  172. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  173. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
  174. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
  175. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  176. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +7 -1
  177. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +56 -10
  178. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  179. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  180. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -3
  181. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  182. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +3 -4
  183. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  184. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +10 -4
  185. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  186. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  187. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  188. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +35 -2
  189. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +100 -3
  190. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  191. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  192. package/.server/server/plugins/engine/pageControllers/validationOptions.js +24 -3
  193. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  194. package/.server/server/plugins/engine/types/enums.d.ts +3 -3
  195. package/.server/server/plugins/engine/types/enums.js +5 -4
  196. package/.server/server/plugins/engine/types/enums.js.map +1 -1
  197. package/.server/server/plugins/engine/types/schema.js +10 -3
  198. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  199. package/.server/server/plugins/engine/types.d.ts +18 -2
  200. package/.server/server/plugins/engine/types.js.map +1 -1
  201. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  202. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  203. package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
  204. package/.server/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  205. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  206. package/.server/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  207. package/.server/server/plugins/engine/views/components/osgridreffield.html +4 -1
  208. package/.server/server/plugins/engine/views/components/paymentfield.html +1 -1
  209. package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  210. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  211. package/.server/server/plugins/engine/views/partials/preview-banner.html +3 -1
  212. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  213. package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
  214. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  215. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
  216. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
  217. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
  218. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  219. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  220. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  221. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  222. package/.server/server/plugins/payment/helper.d.ts +11 -1
  223. package/.server/server/plugins/payment/helper.js +18 -2
  224. package/.server/server/plugins/payment/helper.js.map +1 -1
  225. package/.server/server/plugins/payment/helper.test.js +2 -1
  226. package/.server/server/plugins/payment/helper.test.js.map +1 -1
  227. package/.server/server/plugins/payment/service.d.ts +2 -1
  228. package/.server/server/plugins/payment/service.js +5 -2
  229. package/.server/server/plugins/payment/service.js.map +1 -1
  230. package/.server/server/plugins/payment/service.test.js +7 -3
  231. package/.server/server/plugins/payment/service.test.js.map +1 -1
  232. package/.server/server/plugins/payment/types.d.ts +4 -0
  233. package/.server/server/plugins/payment/types.js +1 -0
  234. package/.server/server/plugins/payment/types.js.map +1 -1
  235. package/.server/server/plugins/postcode-lookup/models/index.d.ts +249 -3
  236. package/.server/server/plugins/postcode-lookup/models/index.js +87 -33
  237. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  238. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
  239. package/.server/server/plugins/postcode-lookup/routes/index.js +47 -15
  240. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  241. package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
  242. package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
  243. package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
  244. package/.server/server/plugins/postcode-lookup/types.js +6 -1
  245. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  246. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  247. package/.server/server/utils/type-utils.d.ts +1 -2
  248. package/.server/server/utils/type-utils.js +0 -3
  249. package/.server/server/utils/type-utils.js.map +1 -1
  250. package/.server/server/utils/utils.d.ts +6 -0
  251. package/.server/server/utils/utils.js +18 -1
  252. package/.server/server/utils/utils.js.map +1 -1
  253. package/.server/server/utils/utils.test.js +21 -1
  254. package/.server/server/utils/utils.test.js.map +1 -1
  255. package/package.json +20 -5
  256. package/src/client/javascripts/file-upload.js +11 -3
  257. package/src/client/javascripts/geospatial-map.js +176 -81
  258. package/src/client/javascripts/govuk.js +2 -0
  259. package/src/client/javascripts/location-map.js +60 -4
  260. package/src/client/javascripts/map-constants.js +3 -0
  261. package/src/client/javascripts/map.js +47 -0
  262. package/src/client/javascripts/maps.js +5 -0
  263. package/src/client/javascripts/shared.js +1 -4
  264. package/src/client/javascripts/sssi-dataset.js +39 -41
  265. package/src/client/stylesheets/maps.scss +4 -0
  266. package/src/client/stylesheets/shared.scss +1 -5
  267. package/src/server/constants.js +2 -0
  268. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  269. package/src/server/plugins/engine/components/AutocompleteField.ts +13 -0
  270. package/src/server/plugins/engine/components/CheckboxesField.ts +38 -5
  271. package/src/server/plugins/engine/components/ComponentBase.ts +2 -0
  272. package/src/server/plugins/engine/components/ComponentCollection.ts +16 -11
  273. package/src/server/plugins/engine/components/DatePartsField.ts +20 -9
  274. package/src/server/plugins/engine/components/DeclarationField.ts +7 -6
  275. package/src/server/plugins/engine/components/EastingNorthingField.ts +185 -89
  276. package/src/server/plugins/engine/components/EmailAddressField.ts +2 -1
  277. package/src/server/plugins/engine/components/FileUploadField.ts +44 -3
  278. package/src/server/plugins/engine/components/FormComponent.ts +6 -5
  279. package/src/server/plugins/engine/components/GeospatialField.ts +47 -9
  280. package/src/server/plugins/engine/components/LatLongField.ts +128 -58
  281. package/src/server/plugins/engine/components/LocationFieldBase.ts +30 -7
  282. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +28 -14
  283. package/src/server/plugins/engine/components/Markdown.ts +2 -3
  284. package/src/server/plugins/engine/components/MonthYearField.ts +20 -15
  285. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -3
  286. package/src/server/plugins/engine/components/NumberField.ts +21 -1
  287. package/src/server/plugins/engine/components/OsGridRefField.ts +22 -3
  288. package/src/server/plugins/engine/components/PaymentField.ts +15 -5
  289. package/src/server/plugins/engine/components/UkAddressField.ts +30 -7
  290. package/src/server/plugins/engine/components/YesNoField.ts +17 -9
  291. package/src/server/plugins/engine/components/helpers/components.ts +3 -3
  292. package/src/server/plugins/engine/components/helpers/geospatial.ts +84 -21
  293. package/src/server/plugins/engine/components/helpers/index.ts +19 -0
  294. package/src/server/plugins/engine/components/types.ts +1 -0
  295. package/src/server/plugins/engine/errors.ts +78 -0
  296. package/src/server/plugins/engine/form-availability.ts +8 -2
  297. package/src/server/plugins/engine/form-context.ts +4 -2
  298. package/src/server/plugins/engine/helpers.ts +52 -5
  299. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +16 -8
  300. package/src/server/plugins/engine/i18n/createFormTranslator.ts +62 -11
  301. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
  302. package/src/server/plugins/engine/i18n/form.ts +54 -0
  303. package/src/server/plugins/engine/i18n/languages.ts +19 -0
  304. package/src/server/plugins/engine/i18n/translations/cy.json +192 -9
  305. package/src/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  306. package/src/server/plugins/engine/i18n/types.ts +3 -0
  307. package/src/server/plugins/engine/index.ts +7 -0
  308. package/src/server/plugins/engine/models/FormModel.ts +46 -23
  309. package/src/server/plugins/engine/models/SummaryViewModel.ts +6 -5
  310. package/src/server/plugins/engine/models/types.ts +31 -0
  311. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  312. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +18 -7
  313. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
  314. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
  315. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +60 -9
  316. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +10 -5
  317. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +2 -2
  318. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
  319. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  320. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +143 -4
  321. package/src/server/plugins/engine/pageControllers/validationOptions.ts +29 -4
  322. package/src/server/plugins/engine/types/enums.ts +4 -3
  323. package/src/server/plugins/engine/types/schema.ts +17 -4
  324. package/src/server/plugins/engine/types.ts +19 -1
  325. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  326. package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
  327. package/src/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  328. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  329. package/src/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  330. package/src/server/plugins/engine/views/components/osgridreffield.html +4 -1
  331. package/src/server/plugins/engine/views/components/paymentfield.html +1 -1
  332. package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  333. package/src/server/plugins/engine/views/file-upload.html +1 -1
  334. package/src/server/plugins/engine/views/partials/preview-banner.html +3 -1
  335. package/src/server/plugins/engine/views/unavailable.html +14 -11
  336. package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  337. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  338. package/src/server/plugins/payment/helper.js +22 -2
  339. package/src/server/plugins/payment/service.js +6 -2
  340. package/src/server/plugins/payment/types.js +1 -0
  341. package/src/server/plugins/postcode-lookup/models/index.js +75 -43
  342. package/src/server/plugins/postcode-lookup/routes/index.js +73 -16
  343. package/src/server/plugins/postcode-lookup/types.js +6 -1
  344. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  345. package/src/server/utils/type-utils.ts +1 -11
  346. package/src/server/utils/utils.js +19 -1
@@ -45,6 +45,7 @@ export class PaymentField extends FormComponent {
45
45
  declare options: PaymentFieldComponent['options']
46
46
  declare formSchema: ObjectSchema
47
47
  declare stateSchema: ObjectSchema
48
+ declare def: PaymentFieldComponent
48
49
  isAppendageStateSingleObject = true
49
50
 
50
51
  constructor(
@@ -54,6 +55,7 @@ export class PaymentField extends FormComponent {
54
55
  super(def, props)
55
56
 
56
57
  this.options = def.options
58
+ this.def = def
57
59
 
58
60
  const paymentStateSchema = joi
59
61
  .object({
@@ -61,6 +63,7 @@ export class PaymentField extends FormComponent {
61
63
  reference: joi.string().required(),
62
64
  amount: joi.number().required(),
63
65
  description: joi.string().required(),
66
+ descriptionInEng: joi.string().optional(),
64
67
  uuid: joi.string().uuid().required(),
65
68
  formId: joi.string().required(),
66
69
  isLivePayment: joi.boolean().required(),
@@ -110,6 +113,8 @@ export class PaymentField extends FormComponent {
110
113
  getViewModel(context: RenderContext) {
111
114
  const { payload } = context
112
115
  const viewModel = super.getViewModel(context)
116
+ const { translator } = context
117
+ const { tComponent } = translator
113
118
 
114
119
  // Payload is pre-populated from state if a payment has already been made
115
120
  const paymentState = this.isPaymentState(payload[this.name] as unknown)
@@ -124,7 +129,8 @@ export class PaymentField extends FormComponent {
124
129
  return {
125
130
  ...viewModel,
126
131
  amount: formatCurrency(amount),
127
- description: this.options.description,
132
+ description:
133
+ tComponent(this.def, 'paymentDescription') || this.options.description,
128
134
  paymentState
129
135
  }
130
136
  }
@@ -234,8 +240,9 @@ export class PaymentField extends FormComponent {
234
240
  ): Promise<unknown> {
235
241
  const { options, name: componentName } = args.component
236
242
  const { model } = args.controller
237
- const language = resolveLanguage(request)
238
- const { t } = model.createTranslator(language)
243
+ const { language, t, tComponent } = model.createTranslator(
244
+ resolveLanguage(request)
245
+ )
239
246
  const state = await args.controller.getState(request)
240
247
  const { baseUrl } = getPluginOptions(request.server)
241
248
  const summaryUrl = `${baseUrl}/${model.basePath}/summary`
@@ -267,7 +274,8 @@ export class PaymentField extends FormComponent {
267
274
  return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)
268
275
  }
269
276
 
270
- const description = options.description
277
+ const description =
278
+ tComponent(args.component, 'paymentDescription') || options.description
271
279
 
272
280
  const slug = `/${model.basePath}`
273
281
 
@@ -287,7 +295,7 @@ export class PaymentField extends FormComponent {
287
295
  payCallbackUrl,
288
296
  reference,
289
297
  isLivePayment,
290
- { formId, slug },
298
+ { formId, slug, language },
291
299
  prefilledEmail
292
300
  )
293
301
 
@@ -308,6 +316,7 @@ export class PaymentField extends FormComponent {
308
316
  reference,
309
317
  amount: resolvedAmount,
310
318
  description,
319
+ descriptionInEng: options.description,
311
320
  paymentId: payment.paymentId,
312
321
  componentName,
313
322
  returnUrl: summaryUrl,
@@ -444,6 +453,7 @@ export interface PaymentSessionData {
444
453
  reference: string
445
454
  amount: number
446
455
  description: string
456
+ descriptionInEng?: string
447
457
  paymentId: string
448
458
  componentName: string
449
459
  returnUrl: string
@@ -1,4 +1,9 @@
1
- import { ComponentType, type UkAddressFieldComponent } from '@defra/forms-model'
1
+ import {
2
+ ComponentType,
3
+ FormStatus,
4
+ type FormMetadata,
5
+ type UkAddressFieldComponent
6
+ } from '@defra/forms-model'
2
7
  import { type ObjectSchema } from 'joi'
3
8
  import lowerFirst from 'lodash/lowerFirst.js'
4
9
 
@@ -9,6 +14,8 @@ import {
9
14
  } from './FormComponent.js'
10
15
  import { TextField } from './TextField.js'
11
16
  import { type RenderContext } from './types.js'
17
+ import { getCachedPluginTranslator } from '../i18n/form.js'
18
+ import { getAvailableLanguages } from '../i18n/languages.js'
12
19
  import { type Translator } from '../i18n/types.js'
13
20
  import {
14
21
  type FormRequestPayload,
@@ -207,7 +214,7 @@ export class UkAddressField extends FormComponent {
207
214
 
208
215
  /**
209
216
  * For screen readers, only hide legend visually. This can be overridden
210
- * by single component {@link QuestionPageController | `showTitle` handling}
217
+ * by single component (see QuestionPageController and showTitle handling)
211
218
  */
212
219
  classes: options.hideTitle
213
220
  ? 'govuk-visually-hidden'
@@ -290,7 +297,6 @@ export class UkAddressField extends FormComponent {
290
297
  )
291
298
  }
292
299
 
293
- // eslint-disable-next-line @typescript-eslint/require-await
294
300
  static async dispatcher(
295
301
  request: FormRequestPayload,
296
302
  h: FormResponseToolkit,
@@ -299,14 +305,31 @@ export class UkAddressField extends FormComponent {
299
305
  const { controller, component } = args
300
306
  const language = resolveLanguage(request)
301
307
 
308
+ const availableLanguages = getAvailableLanguages(controller.model.def)
309
+ const languages = availableLanguages.length
310
+ ? { languages: availableLanguages }
311
+ : {}
312
+
313
+ const translator = await getCachedPluginTranslator(
314
+ request,
315
+ { id: controller.model.formId } as unknown as FormMetadata,
316
+ args.isLive ? FormStatus.Live : FormStatus.Draft,
317
+ language
318
+ )
319
+ const { tComponent, tForm } = translator
320
+
302
321
  return dispatch(request, h, {
303
- formName: controller.model.name,
322
+ formName: tForm('title'),
323
+ formId: controller.model.formId,
324
+ formStatus: args.isLive ? FormStatus.Live : FormStatus.Draft,
325
+ componentId: component.id ?? 'unknown',
304
326
  componentName: component.name,
305
- componentHint: component.hint,
306
- componentTitle: component.title || controller.title,
327
+ componentHint: tComponent(component, 'hint'),
328
+ componentTitle: tComponent(component, 'title') || controller.title,
307
329
  step: args.actionArgs.step,
308
330
  sourceUrl: args.sourceUrl,
309
- language
331
+ language,
332
+ ...languages
310
333
  })
311
334
  }
312
335
  }
@@ -4,7 +4,6 @@ import {
4
4
  yesNoListName,
5
5
  type YesNoFieldComponent
6
6
  } from '@defra/forms-model'
7
- import { type LanguageMessages } from 'joi'
8
7
 
9
8
  import { SelectionControlField } from './SelectionControlField.js'
10
9
  import { addClassOptionIfNone } from './helpers/index.js'
@@ -12,7 +11,6 @@ import { buildValidationMessages } from '../i18n/buildValidationMessages.js'
12
11
  import { type Translator } from '../i18n/types.js'
13
12
  import { messageTemplate } from '../pageControllers/validationOptions.js'
14
13
  import { type ErrorMessageTemplateList } from '../types.js'
15
- import { convertToLanguageMessages } from '../../../utils/type-utils.js'
16
14
 
17
15
  /**
18
16
  * @description
@@ -45,21 +43,31 @@ export class YesNoField extends SelectionControlField {
45
43
  formSchema = formSchema.optional()
46
44
  }
47
45
 
48
- formSchema = formSchema.messages(
49
- convertToLanguageMessages({
46
+ if (options.customValidationMessage) {
47
+ const message = options.customValidationMessage
48
+
49
+ formSchema = formSchema.messages({
50
+ 'any.required': message
51
+ })
52
+ } else if (options.customValidationMessages) {
53
+ formSchema = formSchema.messages(options.customValidationMessages)
54
+ } else {
55
+ formSchema = formSchema.messages({
50
56
  'any.required': messageTemplate.selectYesNoRequired
51
57
  })
52
- )
58
+ }
53
59
 
54
60
  this.formSchema = formSchema
55
61
  this.options = options
56
62
  }
57
63
 
58
- getValidationMessagesOverride(translator: Translator): LanguageMessages {
64
+ getValidationMessagesOverride(translator: Translator) {
59
65
  const { selectYesNoRequired } = buildValidationMessages(translator.t)
60
- return convertToLanguageMessages({
61
- 'any.required': selectYesNoRequired
62
- })
66
+ return {
67
+ [this.name]: {
68
+ 'any.required': selectYesNoRequired
69
+ }
70
+ }
63
71
  }
64
72
 
65
73
  /**
@@ -6,6 +6,7 @@ import { ListFormComponent } from '../ListFormComponent.js'
6
6
  import { escapeMarkdown } from './index.js'
7
7
  import * as Components from '../index.js'
8
8
  import { markdown } from '../markdownParser.js'
9
+ import { UnknownComponentTypeError } from '../../errors.js'
9
10
  import { type Translator } from '../../i18n/types.js'
10
11
  import { type FormState } from '../../types.js'
11
12
 
@@ -210,7 +211,7 @@ export function createComponent(
210
211
  }
211
212
 
212
213
  if (typeof component === 'undefined') {
213
- throw new Error(`Component type ${def.type} does not exist`)
214
+ throw new UnknownComponentTypeError(def.type)
214
215
  }
215
216
 
216
217
  return component
@@ -347,8 +348,7 @@ export function getAnswerMarkdown(
347
348
  field instanceof Components.EastingNorthingField ||
348
349
  field instanceof Components.LatLongField
349
350
  ) {
350
- const contextValue = field.getContextValueFromState(state)
351
- answerEscaped = contextValue ? `${contextValue}\n` : ''
351
+ answerEscaped = answer ? `${answer}\n` : ''
352
352
  } else if (field instanceof Components.GeospatialField) {
353
353
  const features = field.getFormValueFromState(state)
354
354
  const value = field.getDisplayStringFromFormValue(features, translator)
@@ -1,12 +1,16 @@
1
1
  import {
2
- GeospatialFieldOptionsCountryEnum,
3
2
  type GeospatialFieldComponent,
4
3
  type GeospatialFieldOptionsCountry
5
4
  } from '@defra/forms-model'
6
5
  import Bourne from '@hapi/bourne'
7
6
  import { booleanWithin } from '@turf/boolean-within'
7
+ import { point } from '@turf/helpers'
8
+ // @ts-expect-error - no types
9
+ import OsGridRef from 'geodesy/osgridref.js'
8
10
  import JoiBase, { type CustomValidator } from 'joi'
9
11
 
12
+ import { type EastingNorthingField } from '../EastingNorthingField.js'
13
+ import { type EastingNorthingState } from '../types.js'
10
14
  import {
11
15
  type Coordinates,
12
16
  type Feature,
@@ -15,13 +19,6 @@ import {
15
19
  } from '../../types.js'
16
20
  import { countries } from '../../../map/routes/index.js'
17
21
 
18
- const countriesDesc: Record<GeospatialFieldOptionsCountryEnum, string> = {
19
- [GeospatialFieldOptionsCountryEnum.England]: 'England',
20
- [GeospatialFieldOptionsCountryEnum.NorthernIreland]: 'Northern Ireland',
21
- [GeospatialFieldOptionsCountryEnum.Scotland]: 'Scotland',
22
- [GeospatialFieldOptionsCountryEnum.Wales]: 'Wales'
23
- }
24
-
25
22
  const Joi = JoiBase.extend({
26
23
  type: 'array',
27
24
  base: JoiBase.array(),
@@ -98,25 +95,43 @@ const featureSchema = Joi.object<Feature>().keys({
98
95
  geometry: featureGeometrySchema
99
96
  })
100
97
 
101
- function applySchemaConstraints(
102
- schema: JoiBase.ArraySchema<Feature[]>,
103
- def: GeospatialFieldComponent
98
+ export function determineLimits(
99
+ def: GeospatialFieldComponent,
100
+ isOptional: boolean
104
101
  ) {
105
- const { options, schema: constraints } = def
106
- const isOptional = options.required === false
107
-
102
+ const { schema: constraints } = def
103
+ const limits: { min?: number; max?: number; length?: number } = {}
108
104
  if (typeof constraints?.length === 'number') {
109
- schema = schema.length(constraints.length)
105
+ limits.length = constraints.length
110
106
  } else {
111
107
  if (typeof constraints?.min === 'number') {
112
- schema = schema.min(constraints.min)
108
+ limits.min = constraints.min
113
109
  } else if (!isOptional) {
114
- schema = schema.min(1)
110
+ limits.min = 1
115
111
  }
116
112
 
117
- schema = schema.max(
118
- typeof constraints?.max === 'number' ? constraints.max : 50
119
- )
113
+ limits.max = typeof constraints?.max === 'number' ? constraints.max : 50
114
+ }
115
+ return limits
116
+ }
117
+
118
+ function applySchemaConstraints(
119
+ schema: JoiBase.ArraySchema<Feature[]>,
120
+ def: GeospatialFieldComponent
121
+ ) {
122
+ const { options } = def
123
+ const isOptional = options.required === false
124
+
125
+ const limits = determineLimits(def, isOptional)
126
+
127
+ if (limits.length !== undefined) {
128
+ schema = schema.length(limits.length)
129
+ }
130
+ if (limits.min !== undefined) {
131
+ schema = schema.min(limits.min)
132
+ }
133
+ if (limits.max !== undefined) {
134
+ schema = schema.max(limits.max)
120
135
  }
121
136
 
122
137
  if (isOptional) {
@@ -156,7 +171,7 @@ export function getGeospatialSchema(
156
171
 
157
172
  if (!result) {
158
173
  return helpers.error('any.custom', {
159
- country: countriesDesc[country as GeospatialFieldOptionsCountryEnum]
174
+ country
160
175
  })
161
176
  }
162
177
 
@@ -171,3 +186,51 @@ export function getGeospatialSchema(
171
186
  def
172
187
  )
173
188
  }
189
+
190
+ export function getEastingNorthingCountryValidator(
191
+ field: EastingNorthingField,
192
+ country: GeospatialFieldOptionsCountry
193
+ ) {
194
+ const validateCountryBounds: CustomValidator = (value, helpers) => {
195
+ const countryFeature = countries.features.find(
196
+ (feature) => feature.id === country
197
+ )
198
+
199
+ if (!countryFeature) {
200
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
201
+ return value
202
+ }
203
+
204
+ const eastingNorthing = field.getFormValueFromState(
205
+ value as EastingNorthingState
206
+ )
207
+
208
+ if (!eastingNorthing) {
209
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
210
+ return value
211
+ }
212
+
213
+ const gridRef = new OsGridRef(
214
+ eastingNorthing.easting,
215
+ eastingNorthing.northing
216
+ )
217
+ const latLong = gridRef.toLatLon()
218
+ const coord = point([latLong.longitude, latLong.latitude])
219
+ const within = booleanWithin(coord, countryFeature)
220
+
221
+ if (!within) {
222
+ return helpers.error(
223
+ 'any.custom',
224
+ {
225
+ country
226
+ },
227
+ { path: [field.name] }
228
+ )
229
+ }
230
+
231
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
232
+ return value
233
+ }
234
+
235
+ return validateCountryBounds
236
+ }
@@ -2,6 +2,8 @@ import { type ComponentDef } from '@defra/forms-model'
2
2
  import joi, { type JoiExpression, type ReferenceOptions } from 'joi'
3
3
  import lowerFirst from 'lodash/lowerFirst.js'
4
4
 
5
+ import { type Translator } from '../../i18n/types.js'
6
+
5
7
  /**
6
8
  * Prevent Markdown formatting
7
9
  * @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}
@@ -66,3 +68,20 @@ export const lowerFirstExpressionOptions = {
66
68
  */
67
69
  export const createLowerFirstExpression = (template: string): JoiExpression =>
68
70
  joi.expression(template, lowerFirstExpressionOptions) as JoiExpression
71
+
72
+ /**
73
+ * Translate a component label, in precedence order of errorDescription, then shortDesciption, then title
74
+ * @param {ComponentDef} component
75
+ * @param {Translator} translator
76
+ */
77
+ export function getTranslatedLabel(
78
+ component: ComponentDef,
79
+ translator: Translator
80
+ ) {
81
+ const { tComponent } = translator
82
+ const translatedLabel =
83
+ tComponent(component, 'errorDescription') ||
84
+ tComponent(component, 'shortDescription') ||
85
+ tComponent(component, 'title')
86
+ return translatedLabel
87
+ }
@@ -124,6 +124,7 @@ export interface ViewModel extends Record<string, unknown> {
124
124
  upload?: {
125
125
  count: number
126
126
  summaryList: SummaryList
127
+ uploadingLabel?: string
127
128
  }
128
129
  }
129
130
 
@@ -0,0 +1,78 @@
1
+ import { type ValidationError } from 'joi'
2
+
3
+ /**
4
+ * Base class for errors caused by an invalid form definition — whether it
5
+ * fails schema validation or passes the schema but cannot be used by the
6
+ * engine. Never thrown directly — throw a subclass. Consumers (e.g.
7
+ * forms-runner error pages) detect the family with
8
+ * `instanceof InvalidFormDefinitionError`.
9
+ *
10
+ * When subclassing, remember that the message and the messages of any
11
+ * `cause` chain are shown to form authors on preview error pages. Only
12
+ * describe the problem with the form itself — never include sensitive
13
+ * details such as file paths, configuration values or secrets.
14
+ */
15
+ export class InvalidFormDefinitionError extends Error {
16
+ constructor(message: string, options?: ErrorOptions) {
17
+ super(message, options)
18
+ this.name = 'InvalidFormDefinitionError'
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Thrown when a condition in the definition cannot be compiled into an
24
+ * evaluatable expression (e.g. it references the wrong component or list).
25
+ */
26
+ export class ConditionBuildError extends InvalidFormDefinitionError {
27
+ public readonly conditionName: string
28
+
29
+ constructor(conditionName: string, options?: ErrorOptions) {
30
+ super(`Failed to build condition '${conditionName}'`, options)
31
+ this.name = 'ConditionBuildError'
32
+ this.conditionName = conditionName
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Thrown when a page names a controller that is neither built in nor
38
+ * registered by the host application.
39
+ */
40
+ export class UnknownPageControllerError extends InvalidFormDefinitionError {
41
+ public readonly controllerName: string
42
+
43
+ constructor(controllerName: string) {
44
+ super(`Page controller ${controllerName} does not exist`)
45
+ this.name = 'UnknownPageControllerError'
46
+ this.controllerName = controllerName
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Thrown when a component declares a type with no registered implementation.
52
+ */
53
+ export class UnknownComponentTypeError extends InvalidFormDefinitionError {
54
+ public readonly componentType: string
55
+
56
+ constructor(componentType: string) {
57
+ super(`Component type ${componentType} does not exist`)
58
+ this.name = 'UnknownComponentTypeError'
59
+ this.componentType = componentType
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Thrown when a form definition fails Joi schema validation. The raw
65
+ * ValidationError is preserved as `cause` so consumers can reach the
66
+ * per-field details; the message carries Joi's own summary so log lines
67
+ * remain diagnostic for consumers that only read `error.message`.
68
+ */
69
+ export class SchemaValidationError extends InvalidFormDefinitionError {
70
+ // Error types `cause` as unknown; this constructor only accepts a Joi
71
+ // ValidationError, so narrow the declaration for consumers (type-only).
72
+ declare cause: ValidationError
73
+
74
+ constructor(cause: ValidationError) {
75
+ super(`Invalid form definition: ${cause.message}`, { cause })
76
+ this.name = 'SchemaValidationError'
77
+ }
78
+ }
@@ -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
 
@@ -8,13 +8,14 @@ import {
8
8
  } from '@defra/forms-model'
9
9
  import Boom from '@hapi/boom'
10
10
  import { type ResponseToolkit, type Server } from '@hapi/hapi'
11
- import { format, parseISO } from 'date-fns'
11
+ import { format, parseISO, type Locale } from 'date-fns'
12
+ import { cy, enGB } from 'date-fns/locale'
12
13
  import { StatusCodes } from 'http-status-codes'
13
14
  import { type Schema, type ValidationErrorItem } from 'joi'
14
15
  import { Liquid } from 'liquidjs'
15
16
 
16
17
  import { logger } from '../../common/helpers/logging/logger.js'
17
- import { FORM_VERSION_METADATA_KEY } from '../../constants.js'
18
+ import { CY, EN_GB, FORM_VERSION_METADATA_KEY } from '../../constants.js'
18
19
  import {
19
20
  getAnswer,
20
21
  type Field
@@ -300,19 +301,24 @@ export function checkEmailAddressForLiveFormSubmission(
300
301
 
301
302
  /**
302
303
  * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates
304
+ * @param language - the target language for translations
303
305
  * @param [details] - provided by {@link Schema.validate}
304
306
  */
305
307
  export function getErrors(
308
+ language: string | undefined,
306
309
  details?: ValidationErrorItem[]
307
310
  ): FormSubmissionError[] | undefined {
308
311
  if (!details?.length) {
309
312
  return
310
313
  }
311
314
 
312
- return details.map(getError)
315
+ return details.map((detail) => getError(language, detail))
313
316
  }
314
317
 
315
- export function getError(detail: ValidationErrorItem): FormSubmissionError {
318
+ export function getError(
319
+ language: string | undefined,
320
+ detail: ValidationErrorItem
321
+ ): FormSubmissionError {
316
322
  const { context, message, path } = detail
317
323
 
318
324
  const name = context?.key ?? ''
@@ -320,7 +326,10 @@ export function getError(detail: ValidationErrorItem): FormSubmissionError {
320
326
 
321
327
  const text = message.replace(
322
328
  /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,
323
- (text) => format(parseISO(text), 'd MMMM yyyy')
329
+ (text) =>
330
+ format(parseISO(text), 'd MMMM yyyy', {
331
+ locale: toLanguageLocale(language)
332
+ })
324
333
  )
325
334
 
326
335
  return {
@@ -410,3 +419,41 @@ export function getFormVersion(
410
419
  | FormVersionMetadata
411
420
  | undefined
412
421
  }
422
+
423
+ const payLanguageCodes = {
424
+ [EN_GB]: 'en',
425
+ [CY]: 'cy'
426
+ } as Record<string, string>
427
+
428
+ const languageLocales = {
429
+ [EN_GB]: enGB,
430
+ [CY]: cy
431
+ } as Record<string, Locale>
432
+
433
+ const defaultLanguageCode = 'en'
434
+ const defaultLanguageLocale = enGB
435
+
436
+ /**
437
+ * Converts a BCP 47 language tag to the ISO 639-1 code GOV.UK Pay accepts.
438
+ * Defaults to English if the language is not supported.
439
+ * @param { string | undefined } language - BCP 47 language code
440
+ * @returns {string} language code required by GOV.UK Pay
441
+ */
442
+ export function toPayLanguageCode(language: string | undefined): string {
443
+ return payLanguageCodes[language ?? 'unknown'] ?? defaultLanguageCode
444
+ }
445
+
446
+ /**
447
+ * Converts a BCP 47 language tag to the date-fns Locale (for date formatting).
448
+ * Defaults to English if the language is not supported.
449
+ * @param { string | undefined } language - BCP 47 language code
450
+ * @returns {Locale} language locale
451
+ */
452
+ export function toLanguageLocale(language: string | undefined): Locale {
453
+ return languageLocales[language ?? 'unknown'] ?? defaultLanguageLocale
454
+ }
455
+
456
+ /**
457
+ * @import { FormsService } from '../../types.js'
458
+ * @import { Locale } from 'date-fns/locale'
459
+ */