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

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 (339) 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 +19 -17
  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/unavailable-view-model.d.ts +11 -2
  165. package/.server/server/plugins/engine/models/unavailable-view-model.js +23 -4
  166. package/.server/server/plugins/engine/models/unavailable-view-model.js.map +1 -1
  167. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +3 -5
  168. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  169. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  170. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  171. package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
  172. package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
  173. package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
  174. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +7 -1
  175. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +56 -10
  176. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  177. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
  178. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +7 -3
  179. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  180. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +3 -4
  181. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  182. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +10 -4
  183. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  184. package/.server/server/plugins/engine/pageControllers/helpers/pages.js +2 -1
  185. package/.server/server/plugins/engine/pageControllers/helpers/pages.js.map +1 -1
  186. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +1 -1
  187. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +3 -3
  188. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  189. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +1 -1
  190. package/.server/server/plugins/engine/pageControllers/validationOptions.js +24 -3
  191. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  192. package/.server/server/plugins/engine/types/schema.js +1 -0
  193. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  194. package/.server/server/plugins/engine/types.d.ts +5 -0
  195. package/.server/server/plugins/engine/types.js.map +1 -1
  196. package/.server/server/plugins/engine/unavailable-response.js +5 -2
  197. package/.server/server/plugins/engine/unavailable-response.js.map +1 -1
  198. package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
  199. package/.server/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  200. package/.server/server/plugins/engine/views/components/geospatialfield.html +1 -0
  201. package/.server/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  202. package/.server/server/plugins/engine/views/components/osgridreffield.html +4 -1
  203. package/.server/server/plugins/engine/views/components/paymentfield.html +1 -1
  204. package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  205. package/.server/server/plugins/engine/views/file-upload.html +1 -1
  206. package/.server/server/plugins/engine/views/partials/preview-banner.html +3 -1
  207. package/.server/server/plugins/engine/views/unavailable.html +14 -11
  208. package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
  209. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  210. package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
  211. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
  212. package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
  213. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  214. package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
  215. package/.server/server/plugins/nunjucks/filters/index.js +1 -0
  216. package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
  217. package/.server/server/plugins/payment/helper.d.ts +11 -1
  218. package/.server/server/plugins/payment/helper.js +18 -2
  219. package/.server/server/plugins/payment/helper.js.map +1 -1
  220. package/.server/server/plugins/payment/helper.test.js +2 -1
  221. package/.server/server/plugins/payment/helper.test.js.map +1 -1
  222. package/.server/server/plugins/payment/service.d.ts +2 -1
  223. package/.server/server/plugins/payment/service.js +5 -2
  224. package/.server/server/plugins/payment/service.js.map +1 -1
  225. package/.server/server/plugins/payment/service.test.js +7 -3
  226. package/.server/server/plugins/payment/service.test.js.map +1 -1
  227. package/.server/server/plugins/payment/types.d.ts +4 -0
  228. package/.server/server/plugins/payment/types.js +1 -0
  229. package/.server/server/plugins/payment/types.js.map +1 -1
  230. package/.server/server/plugins/postcode-lookup/models/index.d.ts +249 -3
  231. package/.server/server/plugins/postcode-lookup/models/index.js +87 -33
  232. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  233. package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
  234. package/.server/server/plugins/postcode-lookup/routes/index.js +47 -15
  235. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  236. package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
  237. package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
  238. package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
  239. package/.server/server/plugins/postcode-lookup/types.js +6 -1
  240. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  241. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  242. package/.server/server/utils/type-utils.d.ts +1 -2
  243. package/.server/server/utils/type-utils.js +0 -3
  244. package/.server/server/utils/type-utils.js.map +1 -1
  245. package/.server/server/utils/utils.d.ts +6 -0
  246. package/.server/server/utils/utils.js +18 -1
  247. package/.server/server/utils/utils.js.map +1 -1
  248. package/.server/server/utils/utils.test.js +21 -1
  249. package/.server/server/utils/utils.test.js.map +1 -1
  250. package/package.json +20 -5
  251. package/src/client/javascripts/file-upload.js +11 -3
  252. package/src/client/javascripts/geospatial-map.js +176 -81
  253. package/src/client/javascripts/govuk.js +2 -0
  254. package/src/client/javascripts/location-map.js +60 -4
  255. package/src/client/javascripts/map-constants.js +3 -0
  256. package/src/client/javascripts/map.js +47 -0
  257. package/src/client/javascripts/maps.js +5 -0
  258. package/src/client/javascripts/shared.js +1 -4
  259. package/src/client/javascripts/sssi-dataset.js +39 -41
  260. package/src/client/stylesheets/maps.scss +4 -0
  261. package/src/client/stylesheets/shared.scss +1 -5
  262. package/src/server/constants.js +2 -0
  263. package/src/server/plugins/engine/__stubs__/definitions.ts +155 -0
  264. package/src/server/plugins/engine/components/AutocompleteField.ts +13 -0
  265. package/src/server/plugins/engine/components/CheckboxesField.ts +38 -5
  266. package/src/server/plugins/engine/components/ComponentBase.ts +2 -0
  267. package/src/server/plugins/engine/components/ComponentCollection.ts +16 -11
  268. package/src/server/plugins/engine/components/DatePartsField.ts +20 -9
  269. package/src/server/plugins/engine/components/DeclarationField.ts +7 -6
  270. package/src/server/plugins/engine/components/EastingNorthingField.ts +185 -89
  271. package/src/server/plugins/engine/components/EmailAddressField.ts +2 -1
  272. package/src/server/plugins/engine/components/FileUploadField.ts +44 -3
  273. package/src/server/plugins/engine/components/FormComponent.ts +6 -5
  274. package/src/server/plugins/engine/components/GeospatialField.ts +47 -9
  275. package/src/server/plugins/engine/components/LatLongField.ts +128 -58
  276. package/src/server/plugins/engine/components/LocationFieldBase.ts +30 -7
  277. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +28 -14
  278. package/src/server/plugins/engine/components/Markdown.ts +2 -3
  279. package/src/server/plugins/engine/components/MonthYearField.ts +20 -15
  280. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +9 -3
  281. package/src/server/plugins/engine/components/NumberField.ts +21 -1
  282. package/src/server/plugins/engine/components/OsGridRefField.ts +22 -3
  283. package/src/server/plugins/engine/components/PaymentField.ts +15 -5
  284. package/src/server/plugins/engine/components/UkAddressField.ts +30 -7
  285. package/src/server/plugins/engine/components/YesNoField.ts +17 -9
  286. package/src/server/plugins/engine/components/helpers/components.ts +3 -3
  287. package/src/server/plugins/engine/components/helpers/geospatial.ts +84 -21
  288. package/src/server/plugins/engine/components/helpers/index.ts +19 -0
  289. package/src/server/plugins/engine/components/types.ts +1 -0
  290. package/src/server/plugins/engine/errors.ts +78 -0
  291. package/src/server/plugins/engine/form-availability.ts +8 -2
  292. package/src/server/plugins/engine/form-context.ts +4 -2
  293. package/src/server/plugins/engine/helpers.ts +52 -5
  294. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +16 -8
  295. package/src/server/plugins/engine/i18n/createFormTranslator.ts +62 -11
  296. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
  297. package/src/server/plugins/engine/i18n/form.ts +54 -0
  298. package/src/server/plugins/engine/i18n/languages.ts +19 -0
  299. package/src/server/plugins/engine/i18n/translations/cy.json +192 -9
  300. package/src/server/plugins/engine/i18n/translations/en-GB.json +125 -7
  301. package/src/server/plugins/engine/i18n/types.ts +3 -0
  302. package/src/server/plugins/engine/index.ts +7 -0
  303. package/src/server/plugins/engine/models/FormModel.ts +21 -17
  304. package/src/server/plugins/engine/models/SummaryViewModel.ts +6 -5
  305. package/src/server/plugins/engine/models/unavailable-view-model.ts +26 -5
  306. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +3 -6
  307. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
  308. package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
  309. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +60 -9
  310. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +10 -5
  311. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +2 -2
  312. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +15 -4
  313. package/src/server/plugins/engine/pageControllers/helpers/pages.ts +2 -1
  314. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +6 -3
  315. package/src/server/plugins/engine/pageControllers/validationOptions.ts +29 -4
  316. package/src/server/plugins/engine/types/schema.ts +1 -0
  317. package/src/server/plugins/engine/types.ts +5 -0
  318. package/src/server/plugins/engine/unavailable-response.ts +6 -2
  319. package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
  320. package/src/server/plugins/engine/views/components/fileuploadfield.html +1 -1
  321. package/src/server/plugins/engine/views/components/geospatialfield.html +1 -0
  322. package/src/server/plugins/engine/views/components/multilinetextfield.html +12 -1
  323. package/src/server/plugins/engine/views/components/osgridreffield.html +4 -1
  324. package/src/server/plugins/engine/views/components/paymentfield.html +1 -1
  325. package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
  326. package/src/server/plugins/engine/views/file-upload.html +1 -1
  327. package/src/server/plugins/engine/views/partials/preview-banner.html +3 -1
  328. package/src/server/plugins/engine/views/unavailable.html +14 -11
  329. package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
  330. package/src/server/plugins/nunjucks/filters/index.js +1 -0
  331. package/src/server/plugins/payment/helper.js +22 -2
  332. package/src/server/plugins/payment/service.js +6 -2
  333. package/src/server/plugins/payment/types.js +1 -0
  334. package/src/server/plugins/postcode-lookup/models/index.js +75 -43
  335. package/src/server/plugins/postcode-lookup/routes/index.js +73 -16
  336. package/src/server/plugins/postcode-lookup/types.js +6 -1
  337. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  338. package/src/server/utils/type-utils.ts +1 -11
  339. package/src/server/utils/utils.js +19 -1
@@ -14,7 +14,26 @@
14
14
  "numberInteger": "{{#label}} must be a whole number",
15
15
  "numberMin": "{{#label}} must be {{#limit}} or higher",
16
16
  "numberMax": "{{#label}} must be {{#limit}} or lower",
17
- "maxWords": "{{#label}} must be {{#limit}} words or fewer"
17
+ "maxWords": "{{#label}} must be {{#limit}} words or fewer",
18
+ "unicode": "{{#label}} includes invalid characters, for example, long dashes",
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"
18
37
  },
19
38
 
20
39
  "errors": {
@@ -29,7 +48,14 @@
29
48
  "continue": "Continue",
30
49
  "saveAndExit": "Save and exit",
31
50
  "draft": "draft",
32
- "live": "live"
51
+ "live": "live",
52
+ "or": "or",
53
+ "and": "and",
54
+ "england": "England",
55
+ "wales": "Wales",
56
+ "northern-ireland": "Northern Ireland",
57
+ "scotland": "Scotland",
58
+ "sendFeedback": "Send feedback"
33
59
  },
34
60
 
35
61
  "pages": {
@@ -60,6 +86,8 @@
60
86
  "totalAmount": "Total amount",
61
87
  "reference": "Reference",
62
88
  "dateOfPayment": "Date of payment",
89
+ "fileUpload_one": "Uploaded [[count]] file",
90
+ "fileUpload_other": "Uploaded [[count]] files",
63
91
  "submissionFailed": "There was a problem and your form was not submitted. Try submitting the form again",
64
92
  "submissionFailedContactSuffix": " or you can [[contactUsLink]] and quote your reference number to arrange a refund",
65
93
  "contactUsLinkText": "contact us (opens in new tab)",
@@ -89,7 +117,18 @@
89
117
 
90
118
  "fileUpload": {
91
119
  "upload": "Upload file",
92
- "maxFilesReached": "You have reached the maximum number of files. Please remove a file to upload more."
120
+ "maxFilesReached": "You have reached the maximum number of files. Please remove a file to upload more.",
121
+ "remove": "Remove",
122
+ "removeFileTitle": "Are you sure you want to remove this file?",
123
+ "removeFileBody": "You cannot recover removed files.",
124
+ "removeFileButton": "Remove file",
125
+ "cancel": "Cancel",
126
+ "FILE_VIRUS": "The selected file contains a virus",
127
+ "FILE_EMPTY": "The selected file is empty",
128
+ "FILE_TOO_LARGE": "The selected file must be smaller than [[maxFileSizeFormatted]]",
129
+ "FILE_INVALID_TYPE": "The selected file must be a [[fileExtensions]]",
130
+ "FILE_UPLOAD_FAILED": "The selected file could not be uploaded - try again",
131
+ "FILE_ERROR_UNKNOWN": "Unknown error"
93
132
  },
94
133
 
95
134
  "exit": {
@@ -111,9 +150,20 @@
111
150
  "body": "If you want to receive a test email, [[link]] you want form submissions to go to.",
112
151
  "linkText": "enter the email address (opens in new tab)"
113
152
  },
153
+ "bannerTitle": "Important",
114
154
  "bannerForce": "This is a preview of a [[previewMode]] form. Do not enter personal information.",
115
155
  "bannerText": "This is a preview of a [[previewMode]] form page you are editing.",
116
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"
117
167
  }
118
168
  },
119
169
 
@@ -199,7 +249,8 @@
199
249
  "eastingDigits": "{{#label}} for [[fieldLabel]] must be between 1 and 6 digits",
200
250
  "northingDigits": "{{#label}} for [[fieldLabel]] must be between 1 and 7 digits",
201
251
  "eastingRange": "{{#label}} for [[fieldLabel]] must be between [[min]] and [[max]]",
202
- "northingRange": "{{#label}} for [[fieldLabel]] must be between [[min]] and [[max]]"
252
+ "northingRange": "{{#label}} for [[fieldLabel]] must be between [[min]] and [[max]]",
253
+ "wrongCountry": "Location must be within [[country]]"
203
254
  },
204
255
 
205
256
  "nationalGridField": {
@@ -215,7 +266,16 @@
215
266
  },
216
267
 
217
268
  "locationFieldBase": {
218
- "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
+ }
219
279
  },
220
280
 
221
281
  "yesNoField": {
@@ -238,6 +298,47 @@
238
298
  "geospatialField": {
239
299
  "added_one": "Added [[count]] location",
240
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
+ },
241
342
  "validation": {
242
343
  "descriptionRequired": "Enter description for location [[count]]",
243
344
  "wrongCountry": "Location [[count]] must be within [[country]]"
@@ -245,7 +346,7 @@
245
346
  },
246
347
 
247
348
  "paymentField": {
248
- "detailsRequired": "Payment details required",
349
+ "detailsRequired": "Payment required",
249
350
  "pendingTransaction": "You may see a pending transaction in your bank account but you will only be charged when you submit the form.",
250
351
  "addPaymentDetails": "Add payment details",
251
352
  "cannotTakePayment": "There is a problem and we cannot take a payment. Contact us (details in the footer of this form) or save your progress and return to the form later.",
@@ -261,11 +362,28 @@
261
362
  },
262
363
 
263
364
  "fileUploadField": {
365
+ "uploading": "Uploading…",
264
366
  "uploaded": "Uploaded",
265
367
  "uploadFailed": "There was a problem with your uploaded files. Re-upload them before submitting the form again.",
266
368
  "uploadedFilesHeading": "Uploaded files",
267
369
  "filesCount_one": "[[count]] file uploaded",
268
- "filesCount_other": "[[count]] files uploaded"
370
+ "filesCount_other": "[[count]] files uploaded",
371
+ "chooseFile": "Choose file",
372
+ "dropFile": "or drop file",
373
+ "noFileChosen": "No file chosen"
374
+ },
375
+
376
+ "characterCount": {
377
+ "_comment": "Note the use of %{count} instead of [[count]]. This is because we are passing to the govuk template, not passing to Joi",
378
+ "underLimit": {
379
+ "one": "You have %{count} character remaining",
380
+ "other": "You have %{count} characters remaining"
381
+ },
382
+ "atLimit": "You have 0 characters remaining",
383
+ "overLimit": {
384
+ "one": "You have %{count} character too many",
385
+ "other": "You have %{count} characters too many"
386
+ }
269
387
  }
270
388
  }
271
389
  }
@@ -22,6 +22,9 @@ export type FormDefinitionTranslations = Record<
22
22
  hint: string
23
23
  content: string
24
24
  shortDescription: string
25
+ errorDescription: string
26
+ paymentDescription: string
27
+ instructionText: string
25
28
  }>
26
29
  >
27
30
  sections: Record<string, Partial<Pick<Section, 'title'>>>
@@ -22,6 +22,13 @@ export {
22
22
  resolveFormModel
23
23
  } from './form-context.js'
24
24
  export * from './form-availability.js'
25
+ export { getAvailableLanguages } from './i18n/languages.js'
26
+ export {
27
+ extractMetadataBaseTranslations,
28
+ loadFormTranslations
29
+ } from './i18n/createFormTranslator.js'
30
+ export { extractBaseTranslations } from './i18n/extractBaseTranslations.js'
31
+ export { applyUrlParam } from '../nunjucks/filters/applyUrlParam.js'
25
32
 
26
33
  const globals = {
27
34
  checkComponentTemplates,
@@ -31,6 +31,7 @@ import { type i18n } from 'i18next'
31
31
  import joi from 'joi'
32
32
 
33
33
  import { logger } from '../../../common/helpers/logging/logger.js'
34
+ import { EN_GB } from '../../../constants.js'
34
35
  import { type ListFormComponent } from '../components/ListFormComponent.js'
35
36
  import {} from '../components/YesNoField.js'
36
37
  import {
@@ -38,6 +39,10 @@ import {
38
39
  type Component
39
40
  } from '../components/helpers/components.js'
40
41
  import { todayAsDateOnly } from '../date-helper.js'
42
+ import {
43
+ ConditionBuildError,
44
+ SchemaValidationError
45
+ } from '../errors.js'
41
46
  import {
42
47
  findPage,
43
48
  getError,
@@ -47,6 +52,7 @@ import { loadFormTranslations } from '../i18n/createFormTranslator.js'
47
52
  import { createTranslator } from '../i18n/createTranslator.js'
48
53
  import { extractBaseTranslations } from '../i18n/extractBaseTranslations.js'
49
54
  import { createFormI18nInstance } from '../i18n/index.js'
55
+ import { getAvailableLanguages } from '../i18n/languages.js'
50
56
  import { type Translator } from '../i18n/types.js'
51
57
  import { type ExecutableCondition } from './types.js'
52
58
  import { type PageController } from '../pageControllers/PageController.js'
@@ -122,7 +128,7 @@ export class FormModel {
122
128
  const result = schema.validate(def, { abortEarly: false })
123
129
 
124
130
  if (result.error) {
125
- throw result.error
131
+ throw new SchemaValidationError(result.error)
126
132
  }
127
133
 
128
134
  // Make a clone of the shallow copy returned
@@ -240,7 +246,12 @@ export class FormModel {
240
246
  }
241
247
 
242
248
  /** Returns a scoped translator pair for the given language. */
243
- createTranslator(language = 'en-GB'): Translator {
249
+ createTranslator(language = EN_GB): Translator {
250
+ // @ts-expect-error - dynamic language lookup
251
+ if (!this.def.metadata?.translations?.[language]) {
252
+ // If not translations defined in the FormDefinition, always default to English
253
+ language = EN_GB
254
+ }
244
255
  return createTranslator(this.i18nInstance, language)
245
256
  }
246
257
 
@@ -285,7 +296,13 @@ export class FormModel {
285
296
  })
286
297
 
287
298
  const { name, displayName, value } = condition
288
- 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
+ }
289
306
 
290
307
  const fn = (evaluationState: FormState) => {
291
308
  const ctx = this.toConditionContext(evaluationState, this.conditions)
@@ -662,7 +679,7 @@ function validateFormState(
662
679
 
663
680
  // Add relevant state errors
664
681
  if (error) {
665
- const errorsState = error.details.map(getError)
682
+ const errorsState = error.details.map((item) => getError(EN_GB, item))
666
683
  return { ...context, errors: errors.concat(errorsState) }
667
684
  }
668
685
 
@@ -679,16 +696,3 @@ function getReferenceNumber(state: FormSubmissionState): string {
679
696
 
680
697
  return state.$$__referenceNumber
681
698
  }
682
-
683
- export function getAvailableLanguages(
684
- def: FormDefinition
685
- ): { name: string; code: string }[] {
686
- // @ts-expect-error - dynamic property
687
- if (def.metadata?.translations?.cy) {
688
- return [
689
- { name: 'English', code: 'en-GB' },
690
- { name: 'Cymraeg', code: 'cy' }
691
- ]
692
- }
693
- return []
694
- }
@@ -4,6 +4,7 @@ import {
4
4
  type Section
5
5
  } from '@defra/forms-model'
6
6
 
7
+ import { EN_GB } from '../../../constants.js'
7
8
  import { PaymentField } from '../components/PaymentField.js'
8
9
  import { type PaymentState } from '../components/PaymentField.types.js'
9
10
  import {
@@ -96,7 +97,7 @@ export class SummaryViewModel {
96
97
  .validate(this.context.relevantState, { ...opts, stripUnknown: true })
97
98
 
98
99
  // Format errors
99
- this.errors = result.error?.details.map(getError)
100
+ this.errors = result.error?.details.map((item) => getError(EN_GB, item))
100
101
  this.details = this.summaryDetails(request, sections, translator)
101
102
 
102
103
  // Format check answers
@@ -212,12 +213,12 @@ function ItemRepeat(
212
213
  translator: Translator
213
214
  ): DetailItemRepeat {
214
215
  const { collection, repeat } = page
215
- const { name, title } = repeat.options
216
-
216
+ const { name } = repeat.options
217
+ const { t, tPage } = translator
218
+ const title = tPage(page.pageDef, 'repeatTitle') || repeat.options.title
217
219
  const values = page.getListFromState(state)
218
220
  const count = values.length
219
- const value =
220
- count === 0 ? '' : translator.t('pages.repeater.pageTitle', { count })
221
+ const value = count === 0 ? '' : t('pages.repeater.pageTitle', { count })
221
222
 
222
223
  return {
223
224
  name,
@@ -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
  }
@@ -3,6 +3,7 @@ import {
3
3
  type SubmitResponsePayload
4
4
  } from '@defra/forms-model'
5
5
 
6
+ import { EN_GB } from '../../../../constants.js'
6
7
  import {
7
8
  getFormVersion,
8
9
  type checkFormStatus
@@ -42,18 +43,14 @@ export function format(
42
43
  formSlug: formMetadata?.slug ?? '',
43
44
  status: formStatus.state,
44
45
  isPreview: formStatus.isPreview,
45
- notificationEmail: formMetadata?.notificationEmail ?? ''
46
+ notificationEmail: formMetadata?.notificationEmail ?? '',
47
+ language: context.translator?.language ?? EN_GB
46
48
  }
47
49
 
48
50
  if (versionMetadata) {
49
51
  meta.versionMetadata = versionMetadata
50
52
  }
51
53
 
52
- const definitionLanguage = model.def.metadata?.language
53
- if (typeof definitionLanguage === 'string') {
54
- meta.language = definitionLanguage
55
- }
56
-
57
54
  const main = Object.fromEntries(
58
55
  Object.entries(v2Main).map(([key, value]) => {
59
56
  if (value === undefined) {
@@ -5,6 +5,7 @@ import {
5
5
  import { addDays, format as dateFormat } from 'date-fns'
6
6
 
7
7
  import { config } from '../../../../../config/index.js'
8
+ import { EN_GB } from '../../../../constants.js'
8
9
  import { getAnswer } from '../../components/helpers/components.js'
9
10
  import { escapeMarkdown } from '../../components/helpers/index.js'
10
11
  import { PaymentField } from '../../components/index.js'
@@ -116,7 +117,7 @@ function appendPaymentSection(paymentItems: DetailItem[], lines: string[]) {
116
117
 
117
118
  const formattedAmount = formatCurrency(paymentState.amount)
118
119
  const dateOfPayment = paymentState.preAuth?.createdAt
119
- ? formatPaymentDate(paymentState.preAuth.createdAt)
120
+ ? formatPaymentDate(paymentState.preAuth.createdAt, EN_GB)
120
121
  : ''
121
122
 
122
123
  lines.push(
@@ -28,6 +28,7 @@ interface PaymentOutput {
28
28
  reference: string
29
29
  amount: number
30
30
  description: string
31
+ descriptionInEng?: string
31
32
  createdAt: string
32
33
  }
33
34
 
@@ -93,6 +94,7 @@ export function format(
93
94
  * reference: 'REF-123',
94
95
  * amount: 10.00,
95
96
  * description: 'Application fee',
97
+ * descriptionInEng: 'Applicaiton fee',
96
98
  * createdAt: '2025-01-23T10:30:00.000Z'
97
99
  * }
98
100
  * }
@@ -204,6 +206,7 @@ function extractPayment(
204
206
  reference: paymentState.reference,
205
207
  amount: paymentState.amount,
206
208
  description: paymentState.description,
209
+ descriptionInEng: item.field.def.options.description,
207
210
  createdAt: paymentState.preAuth?.createdAt ?? ''
208
211
  }
209
212
  }
@@ -42,6 +42,7 @@ import {
42
42
  type FormRequestPayload,
43
43
  type FormResponseToolkit
44
44
  } from '../../../routes/types.js'
45
+ import { joinWithOr } from '../../../utils/utils.js'
45
46
 
46
47
  const MAX_UPLOADS = 25
47
48
  const CDP_UPLOAD_TIMEOUT_MS = 60000 // 1 minute
@@ -188,17 +189,19 @@ export class FileUploadPageController extends QuestionPageController {
188
189
 
189
190
  const { filename } = fileToRemove.status.form.file
190
191
 
191
- const { t } = this.getTranslator(request)
192
+ const translator = this.getTranslator(request)
193
+ const { t } = translator
192
194
 
193
195
  return h.view(this.fileDeleteViewName, {
194
196
  ...viewModel,
195
197
  context,
196
198
  backLink: this.getBackLink(request, context, t),
197
- pageTitle: `Are you sure you want to remove this file?`,
199
+ pageTitle: t('pages.fileUpload.removeFileTitle'),
198
200
  itemTitle: filename,
199
- confirmation: { text: 'You cannot recover removed files.' },
200
- buttonConfirm: { text: 'Remove file' },
201
- buttonCancel: { text: 'Cancel' }
201
+ confirmation: { text: t('pages.fileUpload.removeFileBody') },
202
+ buttonConfirm: { text: t('pages.fileUpload.removeFileButton') },
203
+ buttonCancel: { text: t('pages.fileUpload.cancel') },
204
+ t
202
205
  } satisfies ItemDeletePageViewModel)
203
206
  }
204
207
  }
@@ -224,7 +227,7 @@ export class FileUploadPageController extends QuestionPageController {
224
227
  }
225
228
  }
226
229
 
227
- getErrors(details?: ValidationErrorItem[]) {
230
+ getErrors(details?: ValidationErrorItem[], language?: string) {
228
231
  const { fileUpload } = this
229
232
 
230
233
  if (details) {
@@ -237,7 +240,7 @@ export class FileUploadPageController extends QuestionPageController {
237
240
  if (!isUploadError || isUploadRootError) {
238
241
  // The error is for the root of the upload or another
239
242
  // field on the page so defer to the getError helper
240
- errors.push(getError(error))
243
+ errors.push(getError(language, error))
241
244
  } else {
242
245
  const { context, path, type } = error
243
246
 
@@ -266,6 +269,7 @@ export class FileUploadPageController extends QuestionPageController {
266
269
  ): FeaturedFormPageViewModel {
267
270
  const { fileUpload } = this
268
271
  const { state } = context
272
+ const { t } = translator
269
273
 
270
274
  const upload = this.getUploadFromState(state)
271
275
 
@@ -277,6 +281,19 @@ export class FileUploadPageController extends QuestionPageController {
277
281
  ({ model }) => model.id === fileUpload.name
278
282
  )
279
283
 
284
+ formComponent.model.upload = {
285
+ count: formComponent.model.upload?.count ?? 0,
286
+ summaryList: formComponent.model.upload?.summaryList ?? { rows: [] },
287
+ uploadingLabel: t('components.fileUploadField.uploading')
288
+ }
289
+ formComponent.model = {
290
+ ...formComponent.model,
291
+ javascript: true,
292
+ chooseFilesButtonText: t('components.fileUploadField.chooseFile'),
293
+ dropInstructionText: t('components.fileUploadField.dropFile'),
294
+ noFileChosenText: t('components.fileUploadField.noFileChosen')
295
+ }
296
+
280
297
  const index = components.indexOf(formComponent)
281
298
 
282
299
  const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl)
@@ -290,7 +307,8 @@ export class FileUploadPageController extends QuestionPageController {
290
307
  // Split out components before/after
291
308
  componentsBefore: components.slice(0, index),
292
309
  components: components.slice(index),
293
- proxyUrl
310
+ proxyUrl,
311
+ t
294
312
  }
295
313
  }
296
314
 
@@ -441,6 +459,9 @@ export class FileUploadPageController extends QuestionPageController {
441
459
 
442
460
  const allErrors: FormSubmissionError[] = []
443
461
 
462
+ const translator = this.getTranslator(request)
463
+ const { t } = translator
464
+
444
465
  for (const file of uploadedFiles) {
445
466
  if (file.fileStatus === FileStatus.complete) {
446
467
  const perFileState: FileState = {
@@ -455,7 +476,14 @@ export class FileUploadPageController extends QuestionPageController {
455
476
  // Collect the error for rejected/pending files.
456
477
  const { fileUpload } = this
457
478
  const name = fileUpload.name
458
- const text = file.errorMessage ?? 'Unknown error'
479
+ const errorParams = this.expandErrorParams(
480
+ file.errorParams,
481
+ t('common.or')
482
+ )
483
+ const text = t(
484
+ `pages.fileUpload.${file.errorCode ?? 'FILE_ERROR_UNKNOWN'}`,
485
+ errorParams
486
+ )
459
487
  allErrors.push({ path: [name], href: `#${name}`, name, text })
460
488
  }
461
489
  }
@@ -472,6 +500,29 @@ export class FileUploadPageController extends QuestionPageController {
472
500
  }
473
501
  }
474
502
 
503
+ /**
504
+ * Process the error parameters so any arrays get expanded (joined), ready to be passed into the error message
505
+ * @param errorParams - received from the file upload (may be a number, a string, or a string array)
506
+ * @param finalSeparator - 'or' in English, and 'neu' in Welsh
507
+ */
508
+ private expandErrorParams(
509
+ errorParams: Record<string, number | string | string[]> | undefined,
510
+ finalSeparator: string
511
+ ) {
512
+ if (!errorParams) {
513
+ return {}
514
+ }
515
+ const expanded = {} as Record<string, number | string>
516
+ for (const [key, value] of Object.entries(errorParams)) {
517
+ if (Array.isArray(value)) {
518
+ expanded[key] = joinWithOr(value, finalSeparator)
519
+ } else {
520
+ expanded[key] = value
521
+ }
522
+ }
523
+ return expanded
524
+ }
525
+
475
526
  /**
476
527
  * Checks the payload for a file getting removed
477
528
  * and removes it from the upload files if found
@@ -174,9 +174,14 @@ export class QuestionPageController extends PageController {
174
174
  if (!pageTitle) {
175
175
  pageTitle = labelOrLegend.text
176
176
  }
177
- labelOrLegend.text = isOptional
178
- ? `${pageTitle} ${t('common.optional')}`
179
- : pageTitle
177
+
178
+ // The question text may have already appended 'optional' in the relevant language
179
+ const optionalWord = t('common.optional')
180
+ const optionalAlreadyAppended = pageTitle.endsWith(` ${optionalWord}`)
181
+ labelOrLegend.text =
182
+ isOptional && !optionalAlreadyAppended
183
+ ? `${pageTitle} ${optionalWord}`
184
+ : pageTitle
180
185
  }
181
186
 
182
187
  // Fallback if still no pageTitle set
@@ -359,8 +364,8 @@ export class QuestionPageController extends PageController {
359
364
  return this.collection.getStateFromValidForm(payload)
360
365
  }
361
366
 
362
- getErrors(details?: ValidationErrorItem[]) {
363
- return getErrors(details)
367
+ getErrors(details?: ValidationErrorItem[], language?: string) {
368
+ return getErrors(language, details)
364
369
  }
365
370
 
366
371
  async getState(request: AnyFormRequest) {
@@ -406,9 +406,9 @@ export class RepeatPageController extends QuestionPageController {
406
406
  const { query } = request
407
407
  const { isForceAccess, errors } = context
408
408
 
409
- const { t } = translator
409
+ const { t, tPage } = translator
410
410
 
411
- const { title } = repeat.options
411
+ const title = tPage(this.pageDef, 'repeatTitle') || repeat.options.title
412
412
 
413
413
  const summaryList: SummaryList = {
414
414
  classes: 'govuk-summary-list--long-actions',