@defra/forms-engine-plugin 4.23.0 → 5.0.0-alpha.1

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 (347) hide show
  1. package/.public/assets-manifest.json +3 -5
  2. package/.public/javascripts/im-core.min.js +2 -0
  3. package/.public/javascripts/im-core.min.js.map +1 -0
  4. package/.public/javascripts/shared.min.js +1 -1
  5. package/.public/javascripts/shared.min.js.map +1 -1
  6. package/.public/javascripts/vendor/@defra.min.js +1 -1
  7. package/.public/javascripts/vendor/@defra.min.js.map +1 -1
  8. package/.public/stylesheets/application.min.css +1 -1
  9. package/.public/stylesheets/application.min.css.map +1 -1
  10. package/.server/client/javascripts/geospatial-map.js +8 -6
  11. package/.server/client/javascripts/geospatial-map.js.map +1 -1
  12. package/.server/client/javascripts/location-map.js +8 -1
  13. package/.server/client/javascripts/location-map.js.map +1 -1
  14. package/.server/client/javascripts/map.d.ts +6 -0
  15. package/.server/client/javascripts/map.js +10 -0
  16. package/.server/client/javascripts/map.js.map +1 -1
  17. package/.server/client/javascripts/sssi-dataset.d.ts +34 -0
  18. package/.server/client/javascripts/sssi-dataset.js +101 -0
  19. package/.server/client/javascripts/sssi-dataset.js.map +1 -0
  20. package/.server/client/stylesheets/shared.scss +0 -2
  21. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +0 -14
  22. package/.server/server/forms/simple-form.yaml +106 -3
  23. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +11 -11
  24. package/.server/server/plugins/engine/components/AutocompleteField.js +2 -2
  25. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  26. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +3 -2
  27. package/.server/server/plugins/engine/components/CheckboxesField.js +4 -9
  28. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  29. package/.server/server/plugins/engine/components/ComponentCollection.d.ts +8 -7
  30. package/.server/server/plugins/engine/components/ComponentCollection.js +106 -51
  31. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  32. package/.server/server/plugins/engine/components/DatePartsField.d.ts +8 -6
  33. package/.server/server/plugins/engine/components/DatePartsField.js +27 -9
  34. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  35. package/.server/server/plugins/engine/components/DeclarationField.d.ts +13 -11
  36. package/.server/server/plugins/engine/components/DeclarationField.js +31 -9
  37. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  38. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +8 -5
  39. package/.server/server/plugins/engine/components/EastingNorthingField.js +42 -22
  40. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  41. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +11 -14
  42. package/.server/server/plugins/engine/components/EmailAddressField.js +2 -2
  43. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  44. package/.server/server/plugins/engine/components/FileUploadField.d.ts +14 -12
  45. package/.server/server/plugins/engine/components/FileUploadField.js +22 -12
  46. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  47. package/.server/server/plugins/engine/components/FormComponent.d.ts +21 -12
  48. package/.server/server/plugins/engine/components/FormComponent.js +33 -14
  49. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  50. package/.server/server/plugins/engine/components/GeospatialField.d.ts +15 -12
  51. package/.server/server/plugins/engine/components/GeospatialField.js +21 -13
  52. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  53. package/.server/server/plugins/engine/components/LatLongField.d.ts +8 -5
  54. package/.server/server/plugins/engine/components/LatLongField.js +32 -20
  55. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  56. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +7 -6
  57. package/.server/server/plugins/engine/components/ListFormComponent.js +19 -7
  58. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
  59. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +17 -16
  60. package/.server/server/plugins/engine/components/LocationFieldBase.js +2 -2
  61. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  62. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +8 -3
  63. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +14 -3
  64. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  65. package/.server/server/plugins/engine/components/Markdown.d.ts +8 -7
  66. package/.server/server/plugins/engine/components/Markdown.js +5 -2
  67. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  68. package/.server/server/plugins/engine/components/MonthYearField.d.ts +9 -7
  69. package/.server/server/plugins/engine/components/MonthYearField.js +26 -8
  70. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  71. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +10 -9
  72. package/.server/server/plugins/engine/components/MultilineTextField.js +2 -2
  73. package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
  74. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
  75. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +14 -6
  76. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  77. package/.server/server/plugins/engine/components/NumberField.d.ts +10 -9
  78. package/.server/server/plugins/engine/components/NumberField.js +2 -2
  79. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  80. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +2 -1
  81. package/.server/server/plugins/engine/components/OsGridRefField.js +14 -6
  82. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  83. package/.server/server/plugins/engine/components/PaymentField.d.ts +12 -10
  84. package/.server/server/plugins/engine/components/PaymentField.js +21 -8
  85. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  86. package/.server/server/plugins/engine/components/SelectField.d.ts +11 -11
  87. package/.server/server/plugins/engine/components/SelectField.js +2 -2
  88. package/.server/server/plugins/engine/components/SelectField.js.map +1 -1
  89. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +4 -4
  90. package/.server/server/plugins/engine/components/SelectionControlField.js +2 -2
  91. package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
  92. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +10 -9
  93. package/.server/server/plugins/engine/components/TelephoneNumberField.js +2 -2
  94. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
  95. package/.server/server/plugins/engine/components/UkAddressField.d.ts +12 -10
  96. package/.server/server/plugins/engine/components/UkAddressField.js +29 -17
  97. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  98. package/.server/server/plugins/engine/components/YesNoField.d.ts +3 -0
  99. package/.server/server/plugins/engine/components/YesNoField.js +9 -0
  100. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  101. package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -2
  102. package/.server/server/plugins/engine/components/helpers/components.js +11 -8
  103. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  104. package/.server/server/plugins/engine/components/types.d.ts +8 -1
  105. package/.server/server/plugins/engine/components/types.js.map +1 -1
  106. package/.server/server/plugins/engine/configureEnginePlugin.js +8 -1
  107. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  108. package/.server/server/plugins/engine/form-context.d.ts +1 -0
  109. package/.server/server/plugins/engine/form-context.js +4 -2
  110. package/.server/server/plugins/engine/form-context.js.map +1 -1
  111. package/.server/server/plugins/engine/helpers.d.ts +1 -1
  112. package/.server/server/plugins/engine/helpers.js +8 -13
  113. package/.server/server/plugins/engine/helpers.js.map +1 -1
  114. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +39 -0
  115. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +50 -0
  116. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -0
  117. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +5 -0
  118. package/.server/server/plugins/engine/i18n/createFormTranslator.js +18 -0
  119. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -0
  120. package/.server/server/plugins/engine/i18n/createTranslator.d.ts +9 -0
  121. package/.server/server/plugins/engine/i18n/createTranslator.js +51 -0
  122. package/.server/server/plugins/engine/i18n/createTranslator.js.map +1 -0
  123. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +5 -0
  124. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +57 -0
  125. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -0
  126. package/.server/server/plugins/engine/i18n/index.d.ts +18 -0
  127. package/.server/server/plugins/engine/i18n/index.js +71 -0
  128. package/.server/server/plugins/engine/i18n/index.js.map +1 -0
  129. package/.server/server/plugins/engine/i18n/translations/cy.json +271 -0
  130. package/.server/server/plugins/engine/i18n/translations/en-GB.json +271 -0
  131. package/.server/server/plugins/engine/i18n/types.d.ts +41 -0
  132. package/.server/server/plugins/engine/i18n/types.js +2 -0
  133. package/.server/server/plugins/engine/i18n/types.js.map +1 -0
  134. package/.server/server/plugins/engine/index.js +3 -2
  135. package/.server/server/plugins/engine/index.js.map +1 -1
  136. package/.server/server/plugins/engine/models/FormModel.d.ts +9 -1
  137. package/.server/server/plugins/engine/models/FormModel.js +44 -18
  138. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  139. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +5 -2
  140. package/.server/server/plugins/engine/models/SummaryViewModel.js +44 -18
  141. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  142. package/.server/server/plugins/engine/options.js +2 -1
  143. package/.server/server/plugins/engine/options.js.map +1 -1
  144. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +4 -0
  145. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  146. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  147. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  148. package/.server/server/plugins/engine/outputFormatters/machine/v1.js +7 -6
  149. package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
  150. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +2 -1
  151. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +6 -3
  152. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  153. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +3 -1
  154. package/.server/server/plugins/engine/pageControllers/PageController.js +11 -1
  155. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  156. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +4 -2
  157. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +52 -21
  158. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  159. package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +3 -2
  160. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +63 -26
  161. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  162. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +3 -1
  163. package/.server/server/plugins/engine/pageControllers/StartPageController.js +2 -2
  164. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
  165. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +12 -2
  166. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  167. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +4 -3
  168. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +44 -28
  169. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  170. package/.server/server/plugins/engine/pageControllers/__stubs__/translator.d.ts +2 -0
  171. package/.server/server/plugins/engine/pageControllers/__stubs__/translator.js +24 -0
  172. package/.server/server/plugins/engine/pageControllers/__stubs__/translator.js.map +1 -0
  173. package/.server/server/plugins/engine/pageControllers/errors.d.ts +1 -1
  174. package/.server/server/plugins/engine/pageControllers/errors.js +3 -2
  175. package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
  176. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +3 -2
  177. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +4 -4
  178. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  179. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +7 -3
  180. package/.server/server/plugins/engine/pageControllers/validationOptions.js +34 -33
  181. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  182. package/.server/server/plugins/engine/plugin.js +2 -0
  183. package/.server/server/plugins/engine/plugin.js.map +1 -1
  184. package/.server/server/plugins/engine/routes/index.js +4 -1
  185. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  186. package/.server/server/plugins/engine/routes/questions.js +5 -2
  187. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  188. package/.server/server/plugins/engine/types/index.d.ts +1 -0
  189. package/.server/server/plugins/engine/types/index.js.map +1 -1
  190. package/.server/server/plugins/engine/types/schema.js +1 -0
  191. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  192. package/.server/server/plugins/engine/types.d.ts +12 -1
  193. package/.server/server/plugins/engine/types.js.map +1 -1
  194. package/.server/server/plugins/engine/views/components/_location-field-base.html +4 -2
  195. package/.server/server/plugins/engine/views/components/fileuploadfield.html +3 -3
  196. package/.server/server/plugins/engine/views/components/geospatialfield.html +4 -1
  197. package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +1 -1
  198. package/.server/server/plugins/engine/views/components/osgridreffield.html +1 -1
  199. package/.server/server/plugins/engine/views/components/paymentfield.html +8 -8
  200. package/.server/server/plugins/engine/views/components/ukaddressfield.html +4 -4
  201. package/.server/server/plugins/engine/views/confirmation.html +4 -4
  202. package/.server/server/plugins/engine/views/exit.html +4 -4
  203. package/.server/server/plugins/engine/views/file-upload.html +3 -3
  204. package/.server/server/plugins/engine/views/index.html +3 -3
  205. package/.server/server/plugins/engine/views/item-delete.html +1 -1
  206. package/.server/server/plugins/engine/views/partials/form.html +3 -3
  207. package/.server/server/plugins/engine/views/partials/preview-banner.html +4 -4
  208. package/.server/server/plugins/engine/views/partials/warn-missing-notification-email.html +4 -5
  209. package/.server/server/plugins/engine/views/repeat-list-summary.html +4 -4
  210. package/.server/server/plugins/engine/views/summary.html +14 -7
  211. package/.server/server/plugins/map/routes/index.js +1 -1
  212. package/.server/server/plugins/map/routes/index.js.map +1 -1
  213. package/.server/server/plugins/map/types.d.ts +1 -25
  214. package/.server/server/plugins/map/types.js +1 -16
  215. package/.server/server/plugins/map/types.js.map +1 -1
  216. package/.server/server/plugins/nunjucks/enviroment.test.js +10 -0
  217. package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
  218. package/.server/server/plugins/nunjucks/environment.d.ts +8 -0
  219. package/.server/server/plugins/nunjucks/environment.js +13 -0
  220. package/.server/server/plugins/nunjucks/environment.js.map +1 -1
  221. package/.server/server/plugins/nunjucks/filters/answer.js +9 -1
  222. package/.server/server/plugins/nunjucks/filters/answer.js.map +1 -1
  223. package/.server/server/plugins/nunjucks/filters/answer.test.js +13 -2
  224. package/.server/server/plugins/nunjucks/filters/answer.test.js.map +1 -1
  225. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  226. package/.server/server/plugins/postcode-lookup/models/index.d.ts +24 -19
  227. package/.server/server/plugins/postcode-lookup/models/index.js +83 -76
  228. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  229. package/.server/server/plugins/postcode-lookup/routes/index.js +7 -13
  230. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  231. package/.server/server/plugins/postcode-lookup/types.d.ts +1 -0
  232. package/.server/server/plugins/postcode-lookup/types.js +1 -0
  233. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  234. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  235. package/.server/server/types.d.ts +2 -1
  236. package/.server/server/types.js.map +1 -1
  237. package/.server/server/utils/utils.d.ts +5 -0
  238. package/.server/server/utils/utils.js +16 -0
  239. package/.server/server/utils/utils.js.map +1 -1
  240. package/.server/typings/hapi/index.d.js.map +1 -1
  241. package/package.json +5 -4
  242. package/src/client/javascripts/geospatial-map.js +10 -6
  243. package/src/client/javascripts/location-map.js +11 -0
  244. package/src/client/javascripts/map.js +10 -0
  245. package/src/client/javascripts/sssi-dataset.js +106 -0
  246. package/src/client/stylesheets/shared.scss +0 -2
  247. package/src/server/forms/register-as-a-unicorn-breeder.yaml +0 -14
  248. package/src/server/forms/simple-form.yaml +106 -3
  249. package/src/server/plugins/engine/components/AutocompleteField.ts +3 -6
  250. package/src/server/plugins/engine/components/CheckboxesField.ts +12 -11
  251. package/src/server/plugins/engine/components/ComponentCollection.ts +143 -61
  252. package/src/server/plugins/engine/components/DatePartsField.ts +33 -11
  253. package/src/server/plugins/engine/components/DeclarationField.ts +37 -14
  254. package/src/server/plugins/engine/components/EastingNorthingField.ts +83 -22
  255. package/src/server/plugins/engine/components/EmailAddressField.ts +4 -7
  256. package/src/server/plugins/engine/components/FileUploadField.ts +28 -22
  257. package/src/server/plugins/engine/components/FormComponent.ts +49 -12
  258. package/src/server/plugins/engine/components/GeospatialField.ts +38 -21
  259. package/src/server/plugins/engine/components/LatLongField.ts +52 -21
  260. package/src/server/plugins/engine/components/ListFormComponent.ts +26 -10
  261. package/src/server/plugins/engine/components/LocationFieldBase.ts +5 -8
  262. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +24 -4
  263. package/src/server/plugins/engine/components/Markdown.ts +7 -3
  264. package/src/server/plugins/engine/components/MonthYearField.ts +32 -10
  265. package/src/server/plugins/engine/components/MultilineTextField.ts +5 -8
  266. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +25 -11
  267. package/src/server/plugins/engine/components/NumberField.ts +3 -4
  268. package/src/server/plugins/engine/components/OsGridRefField.ts +25 -9
  269. package/src/server/plugins/engine/components/PaymentField.ts +22 -12
  270. package/src/server/plugins/engine/components/SelectField.ts +3 -6
  271. package/src/server/plugins/engine/components/SelectionControlField.ts +7 -8
  272. package/src/server/plugins/engine/components/TelephoneNumberField.ts +4 -7
  273. package/src/server/plugins/engine/components/UkAddressField.ts +33 -18
  274. package/src/server/plugins/engine/components/YesNoField.ts +10 -0
  275. package/src/server/plugins/engine/components/helpers/components.ts +13 -6
  276. package/src/server/plugins/engine/components/types.ts +9 -0
  277. package/src/server/plugins/engine/configureEnginePlugin.ts +12 -1
  278. package/src/server/plugins/engine/form-context.ts +8 -2
  279. package/src/server/plugins/engine/helpers.ts +10 -18
  280. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +69 -0
  281. package/src/server/plugins/engine/i18n/createFormTranslator.ts +33 -0
  282. package/src/server/plugins/engine/i18n/createTranslator.ts +61 -0
  283. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +61 -0
  284. package/src/server/plugins/engine/i18n/index.ts +80 -0
  285. package/src/server/plugins/engine/i18n/translations/cy.json +271 -0
  286. package/src/server/plugins/engine/i18n/translations/en-GB.json +271 -0
  287. package/src/server/plugins/engine/i18n/types.ts +62 -0
  288. package/src/server/plugins/engine/index.ts +4 -2
  289. package/src/server/plugins/engine/models/FormModel.ts +59 -23
  290. package/src/server/plugins/engine/models/SummaryViewModel.ts +68 -25
  291. package/src/server/plugins/engine/options.js +2 -1
  292. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +5 -0
  293. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -3
  294. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +9 -8
  295. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +7 -3
  296. package/src/server/plugins/engine/pageControllers/PageController.ts +12 -1
  297. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +53 -21
  298. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +66 -29
  299. package/src/server/plugins/engine/pageControllers/StartPageController.ts +7 -2
  300. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +10 -2
  301. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +48 -22
  302. package/src/server/plugins/engine/pageControllers/__stubs__/translator.ts +28 -0
  303. package/src/server/plugins/engine/pageControllers/errors.ts +4 -2
  304. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +11 -4
  305. package/src/server/plugins/engine/pageControllers/validationOptions.ts +43 -53
  306. package/src/server/plugins/engine/plugin.ts +2 -0
  307. package/src/server/plugins/engine/routes/index.ts +9 -1
  308. package/src/server/plugins/engine/routes/questions.ts +5 -1
  309. package/src/server/plugins/engine/types/index.ts +5 -0
  310. package/src/server/plugins/engine/types/schema.ts +1 -0
  311. package/src/server/plugins/engine/types.ts +10 -0
  312. package/src/server/plugins/engine/views/components/_location-field-base.html +4 -2
  313. package/src/server/plugins/engine/views/components/fileuploadfield.html +3 -3
  314. package/src/server/plugins/engine/views/components/geospatialfield.html +4 -1
  315. package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +1 -1
  316. package/src/server/plugins/engine/views/components/osgridreffield.html +1 -1
  317. package/src/server/plugins/engine/views/components/paymentfield.html +8 -8
  318. package/src/server/plugins/engine/views/components/ukaddressfield.html +4 -4
  319. package/src/server/plugins/engine/views/confirmation.html +4 -4
  320. package/src/server/plugins/engine/views/exit.html +4 -4
  321. package/src/server/plugins/engine/views/file-upload.html +3 -3
  322. package/src/server/plugins/engine/views/index.html +3 -3
  323. package/src/server/plugins/engine/views/item-delete.html +1 -1
  324. package/src/server/plugins/engine/views/partials/form.html +3 -3
  325. package/src/server/plugins/engine/views/partials/preview-banner.html +4 -4
  326. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +4 -5
  327. package/src/server/plugins/engine/views/repeat-list-summary.html +4 -4
  328. package/src/server/plugins/engine/views/summary.html +14 -7
  329. package/src/server/plugins/map/routes/index.js +1 -1
  330. package/src/server/plugins/map/types.js +1 -16
  331. package/src/server/plugins/nunjucks/environment.js +14 -0
  332. package/src/server/plugins/nunjucks/filters/answer.js +12 -1
  333. package/src/server/plugins/postcode-lookup/models/index.js +144 -87
  334. package/src/server/plugins/postcode-lookup/routes/index.js +13 -16
  335. package/src/server/plugins/postcode-lookup/types.js +1 -0
  336. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  337. package/src/server/types.ts +2 -0
  338. package/src/server/utils/utils.js +14 -0
  339. package/src/typings/hapi/index.d.ts +1 -0
  340. package/.public/javascripts/im-search-plugin.min.js +0 -2
  341. package/.public/javascripts/im-search-plugin.min.js.map +0 -1
  342. package/.public/javascripts/vendor/preact.min.js +0 -2
  343. package/.public/javascripts/vendor/preact.min.js.map +0 -1
  344. package/.server/server/plugins/engine/components/constants.d.ts +0 -1
  345. package/.server/server/plugins/engine/components/constants.js +0 -2
  346. package/.server/server/plugins/engine/components/constants.js.map +0 -1
  347. package/src/server/plugins/engine/components/constants.ts +0 -1
@@ -0,0 +1,62 @@
1
+ import {
2
+ type ComponentDef,
3
+ type Item,
4
+ type Page,
5
+ type Section
6
+ } from '@defra/forms-model'
7
+
8
+ /**
9
+ * Structure of the `translations` block in a form definition.
10
+ * Also used to derive the valid property keys for the typed tX functions.
11
+ */
12
+ export type FormDefinitionTranslations = Record<
13
+ string,
14
+ {
15
+ pages: Record<string, Partial<{ title: string; repeatTitle: string }>>
16
+ // ComponentDef is a discriminated union; Pick across the full union is not feasible.
17
+ // The four keys below are the complete set of translatable component properties.
18
+ components: Record<
19
+ string,
20
+ Partial<{
21
+ title: string
22
+ hint: string
23
+ content: string
24
+ shortDescription: string
25
+ }>
26
+ >
27
+ sections: Record<string, Partial<Pick<Section, 'title'>>>
28
+ listItems: Record<string, Partial<Pick<Item, 'text' | 'hint'>>>
29
+ form: Record<string, string>
30
+ }
31
+ >
32
+
33
+ type EntityTranslations = FormDefinitionTranslations[string]
34
+
35
+ /**
36
+ * Scoped translator returned by model.createTranslator(language).
37
+ * t — plugin namespace (UI strings, buttons, errors, sub-field labels)
38
+ * tForm — form namespace: form attributes, currently only form name
39
+ * tPage — form namespace: page titles
40
+ * tComponent — form namespace: component titles, hints, shortDescriptions
41
+ * tSection — form namespace: section titles
42
+ * tListItem — form namespace: list item text
43
+ * language - current language
44
+ */
45
+ export interface Translator {
46
+ t: (key: string, opts?: Record<string, unknown>) => string
47
+ tForm: (prop: string) => string
48
+ tPage: (page: Page, prop: keyof EntityTranslations['pages'][string]) => string
49
+ tComponent: (
50
+ component: ComponentDef,
51
+ prop: keyof EntityTranslations['components'][string]
52
+ ) => string
53
+ tSection: (
54
+ section: Section,
55
+ prop: keyof EntityTranslations['sections'][string]
56
+ ) => string
57
+ tListItem: (
58
+ item: Item,
59
+ prop: keyof EntityTranslations['listItems'][string]
60
+ ) => string
61
+ language: string
62
+ }
@@ -8,7 +8,8 @@ import {
8
8
  checkComponentTemplates,
9
9
  checkErrorTemplates,
10
10
  evaluate,
11
- govukRebrand
11
+ govukRebrand,
12
+ tGlobal
12
13
  } from '../nunjucks/environment.js'
13
14
  import * as filters from '../nunjucks/filters/index.js'
14
15
 
@@ -26,7 +27,8 @@ const globals = {
26
27
  checkComponentTemplates,
27
28
  checkErrorTemplates,
28
29
  evaluate,
29
- govukRebrand
30
+ govukRebrand,
31
+ t: tGlobal
30
32
  }
31
33
 
32
34
  export const VIEW_PATH = 'src/server/plugins/engine/views'
@@ -27,6 +27,7 @@ import {
27
27
  } from '@defra/forms-model'
28
28
  import { add, format } from 'date-fns'
29
29
  import { Parser, type Value } from 'expr-eval-fork'
30
+ import { type i18n } from 'i18next'
30
31
  import joi from 'joi'
31
32
 
32
33
  import { logger } from '../../../common/helpers/logging/logger.js'
@@ -40,9 +41,13 @@ import { todayAsDateOnly } from '../date-helper.js'
40
41
  import {
41
42
  findPage,
42
43
  getError,
43
- getPage,
44
- setPageTitles
44
+ getPage
45
45
  } from '../helpers.js'
46
+ import { loadFormTranslations } from '../i18n/createFormTranslator.js'
47
+ import { createTranslator } from '../i18n/createTranslator.js'
48
+ import { extractBaseTranslations } from '../i18n/extractBaseTranslations.js'
49
+ import { createFormI18nInstance } from '../i18n/index.js'
50
+ import { type Translator } from '../i18n/types.js'
46
51
  import { type ExecutableCondition } from './types.js'
47
52
  import { type PageController } from '../pageControllers/PageController.js'
48
53
  import {
@@ -81,6 +86,7 @@ export class FormModel {
81
86
  conditions: Partial<Record<string, ExecutableCondition>>
82
87
  pages: PageControllerClass[]
83
88
  services: Services
89
+ private readonly i18nInstance: i18n
84
90
 
85
91
  controllers?: Record<string, typeof PageController>
86
92
  pageDefMap: Map<string, Page>
@@ -123,7 +129,12 @@ export class FormModel {
123
129
  // by joi so as not to change the source data.
124
130
  def = structuredClone(result.value)
125
131
 
126
- // Add default lists
132
+ const baseTranslations = extractBaseTranslations(def)
133
+ this.i18nInstance = createFormI18nInstance(baseTranslations)
134
+ loadFormTranslations(def, this.i18nInstance)
135
+
136
+ // Add default lists. Yes/No text stored as i18n key constants so they
137
+ // resolve to the user's language at render time via the translator.
127
138
  def.lists.push({
128
139
  id: def.schema === SchemaVersion.V1 ? yesNoListName : yesNoListId,
129
140
  name: '__yesNo',
@@ -132,20 +143,17 @@ export class FormModel {
132
143
  items: [
133
144
  {
134
145
  id: '02900d42-83d1-4c72-a719-c4e8228952fa',
135
- text: 'Yes',
146
+ text: 'components.yesNoField.yes',
136
147
  value: true
137
148
  },
138
149
  {
139
150
  id: 'f39000eb-c51b-4019-8f82-bbda0423f04d',
140
- text: 'No',
151
+ text: 'components.yesNoField.no',
141
152
  value: false
142
153
  }
143
154
  ]
144
155
  })
145
156
 
146
- // Fix up page titles
147
- setPageTitles(def)
148
-
149
157
  this.engine = def.engine
150
158
  this.schemaVersion = def.schema ?? SchemaVersion.V1
151
159
  this.def = def
@@ -213,7 +221,7 @@ export class FormModel {
213
221
  ) {
214
222
  this.pages.push(
215
223
  createPage(this, {
216
- title: 'Form submitted',
224
+ title: 'pages.confirmation.formSubmitted',
217
225
  path: ControllerPath.Status,
218
226
  controller: ControllerType.Status
219
227
  })
@@ -231,6 +239,11 @@ export class FormModel {
231
239
  )
232
240
  }
233
241
 
242
+ /** Returns a scoped translator pair for the given language. */
243
+ createTranslator(language = 'en-GB'): Translator {
244
+ return createTranslator(this.i18nInstance, language)
245
+ }
246
+
234
247
  /**
235
248
  * build the entire model schema from individual pages/sections and filter out answers
236
249
  * for pages which are no longer accessible due to an answer that has been changed
@@ -337,7 +350,8 @@ export class FormModel {
337
350
  getFormContext(
338
351
  request: FormContextRequest,
339
352
  state: FormSubmissionState,
340
- errors?: FormSubmissionError[]
353
+ errors?: FormSubmissionError[],
354
+ translator?: Translator
341
355
  ): FormContext {
342
356
  const { query } = request
343
357
 
@@ -365,11 +379,13 @@ export class FormModel {
365
379
  componentDefMap: this.componentDefMap,
366
380
  pageMap: this.pageMap,
367
381
  componentMap: this.componentMap,
368
- referenceNumber: getReferenceNumber(state)
382
+ referenceNumber: getReferenceNumber(state),
383
+ languages: getAvailableLanguages(this.def),
384
+ translator
369
385
  }
370
386
 
371
387
  // Validate current page
372
- context = validateFormPayload(request, page, context)
388
+ context = validateFormPayload(request, page, context, translator)
373
389
 
374
390
  // Find start page
375
391
  let nextPage = findPage(this, startPath)
@@ -387,7 +403,7 @@ export class FormModel {
387
403
 
388
404
  // Stop at current page
389
405
  if (
390
- this.pageStateIsInvalid(context, nextPage) ||
406
+ this.pageStateIsInvalid(context, nextPage, translator) ||
391
407
  nextPage.path === currentPath
392
408
  ) {
393
409
  break
@@ -448,7 +464,11 @@ export class FormModel {
448
464
  }
449
465
  }
450
466
 
451
- private pageStateIsInvalid(context: FormContext, page: PageControllerClass) {
467
+ private pageStateIsInvalid(
468
+ context: FormContext,
469
+ page: PageControllerClass,
470
+ translator?: Translator
471
+ ) {
452
472
  // Get any list-bound fields on the page
453
473
  const listFields = page.collection.fields.filter(hasListFormField)
454
474
 
@@ -464,7 +484,7 @@ export class FormModel {
464
484
  list.items.filter((item) => item.condition).length > 0
465
485
 
466
486
  if (hasOptionalItems) {
467
- return this.fieldStateIsInvalid(context, field, list)
487
+ return this.fieldStateIsInvalid(context, field, list, translator)
468
488
  }
469
489
  }
470
490
  }
@@ -473,7 +493,8 @@ export class FormModel {
473
493
  private fieldStateIsInvalid(
474
494
  context: FormContext,
475
495
  field: ListFormComponent,
476
- list: List
496
+ list: List,
497
+ translator?: Translator
477
498
  ) {
478
499
  const { evaluationState, state } = context
479
500
 
@@ -503,8 +524,9 @@ export class FormModel {
503
524
  if (isInvalid) {
504
525
  context.errors ??= []
505
526
 
506
- const text =
507
- 'Options are different because you changed a previous answer'
527
+ const text = (translator ?? this.createTranslator('en-GB')).t(
528
+ 'errors.optionsMismatch'
529
+ )
508
530
 
509
531
  context.errors.push({
510
532
  text,
@@ -571,7 +593,8 @@ export class FormModel {
571
593
  function validateFormPayload(
572
594
  request: FormContextRequest,
573
595
  page: PageControllerClass,
574
- context: FormContext
596
+ context: FormContext,
597
+ translator?: Translator
575
598
  ): FormContext {
576
599
  const { collection } = page
577
600
  const { payload, state } = context
@@ -601,10 +624,10 @@ function validateFormPayload(
601
624
  }
602
625
  })
603
626
 
604
- const { value, errors } = collection.validate({
605
- ...payload,
606
- ...update
607
- })
627
+ const { value, errors } = collection.validate(
628
+ { ...payload, ...update },
629
+ translator
630
+ )
608
631
 
609
632
  // Add sanitised payload (ready to save)
610
633
  const formState = page.getStateFromValidForm(request, state, value)
@@ -656,3 +679,16 @@ function getReferenceNumber(state: FormSubmissionState): string {
656
679
 
657
680
  return state.$$__referenceNumber
658
681
  }
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
+ }
@@ -1,4 +1,8 @@
1
- import { SchemaVersion, type Section } from '@defra/forms-model'
1
+ import {
2
+ SchemaVersion,
3
+ type ComponentDef,
4
+ type Section
5
+ } from '@defra/forms-model'
2
6
 
3
7
  import { PaymentField } from '../components/PaymentField.js'
4
8
  import { type PaymentState } from '../components/PaymentField.types.js'
@@ -15,6 +19,7 @@ import {
15
19
  getError,
16
20
  getPageHref
17
21
  } from '../helpers.js'
22
+ import { type Translator } from '../i18n/types.js'
18
23
  import {
19
24
  type Detail,
20
25
  type DetailItem,
@@ -56,22 +61,29 @@ export class SummaryViewModel {
56
61
  allowSaveAndExit = false
57
62
  paymentState?: PaymentState
58
63
  paymentDetails?: CheckAnswers
64
+ t: (key: string, opts?: Record<string, unknown>) => string
65
+ private readonly _translator!: Translator
59
66
  paymentRequired?: boolean
60
67
  paymentPreAuthorized?: boolean
61
68
 
62
69
  constructor(
63
70
  request: FormContextRequest,
64
71
  page: PageControllerClass,
65
- context: FormContext
72
+ context: FormContext,
73
+ translator: Translator
66
74
  ) {
67
75
  const { model } = page
68
76
  const { basePath, def, sections } = model
69
77
  const { isForceAccess } = context
70
78
 
79
+ const { t } = translator
80
+ this.t = t
81
+ this._translator = translator
82
+
71
83
  this.page = page
72
84
  this.pageTitle = page.title
73
85
  if (def.schema === SchemaVersion.V2 && !page.title) {
74
- this.pageTitle = 'Check your answers before sending your form'
86
+ this.pageTitle = t('pages.summary.title')
75
87
  }
76
88
 
77
89
  this.serviceUrl = `/${basePath}`
@@ -85,7 +97,7 @@ export class SummaryViewModel {
85
97
 
86
98
  // Format errors
87
99
  this.errors = result.error?.details.map(getError)
88
- this.details = this.summaryDetails(request, sections)
100
+ this.details = this.summaryDetails(request, sections, translator)
89
101
 
90
102
  // Format check answers
91
103
  this.checkAnswers = this.details.map((detail): CheckAnswers => {
@@ -98,7 +110,7 @@ export class SummaryViewModel {
98
110
  if (!isForceAccess) {
99
111
  items.push({
100
112
  href: item.href,
101
- text: 'Change',
113
+ text: t('pages.summary.change'),
102
114
  classes: 'govuk-link--no-visited-state',
103
115
  visuallyHiddenText: item.label
104
116
  })
@@ -110,7 +122,7 @@ export class SummaryViewModel {
110
122
  },
111
123
  value: {
112
124
  classes: 'app-prose-scope',
113
- html: item.value || 'Not provided'
125
+ html: item.value || t('pages.summary.notProvided')
114
126
  },
115
127
  actions: {
116
128
  items
@@ -125,7 +137,11 @@ export class SummaryViewModel {
125
137
  })
126
138
  }
127
139
 
128
- private summaryDetails(request: FormContextRequest, sections: Section[]) {
140
+ private summaryDetails(
141
+ request: FormContextRequest,
142
+ sections: Section[],
143
+ translator: Translator
144
+ ) {
129
145
  const { context, errors } = this
130
146
  const { relevantPages, state } = context
131
147
 
@@ -143,10 +159,15 @@ export class SummaryViewModel {
143
159
 
144
160
  if (page instanceof RepeatPageController) {
145
161
  items.push(
146
- ItemRepeat(page, state, {
147
- path: page.getSummaryPath(request),
148
- errors
149
- })
162
+ ItemRepeat(
163
+ page,
164
+ state,
165
+ {
166
+ path: page.getSummaryPath(request),
167
+ errors
168
+ },
169
+ translator
170
+ )
150
171
  )
151
172
  } else {
152
173
  for (const field of collection.fields) {
@@ -154,15 +175,20 @@ export class SummaryViewModel {
154
175
  if (field instanceof PaymentField) {
155
176
  continue
156
177
  }
157
- items.push(ItemField(page, state, field, { path, errors }))
178
+ items.push(
179
+ ItemField(page, state, field, { path, errors }, translator)
180
+ )
158
181
  }
159
182
  }
160
183
  })
161
184
 
162
185
  if (items.length) {
186
+ const sectionTitle = section
187
+ ? translator.tSection(section, 'title') || section.title
188
+ : undefined
163
189
  details.push({
164
190
  name: section?.name,
165
- title: section?.title,
191
+ title: sectionTitle,
166
192
  items
167
193
  })
168
194
  }
@@ -182,19 +208,22 @@ function ItemRepeat(
182
208
  options: {
183
209
  path: string
184
210
  errors?: FormSubmissionError[]
185
- }
211
+ },
212
+ translator: Translator
186
213
  ): DetailItemRepeat {
187
214
  const { collection, repeat } = page
188
215
  const { name, title } = repeat.options
189
216
 
190
217
  const values = page.getListFromState(state)
191
- const unit = values.length === 1 ? 'answer' : 'answers'
218
+ const count = values.length
219
+ const value =
220
+ count === 0 ? '' : translator.t('pages.repeater.pageTitle', { count })
192
221
 
193
222
  return {
194
223
  name,
195
224
  label: title,
196
225
  title,
197
- value: values.length ? `You have added ${values.length} ${unit}` : '',
226
+ value,
198
227
  href: getPageHref(page, options.path, {
199
228
  returnUrl: getPageHref(page, page.getSummaryPath())
200
229
  }),
@@ -204,7 +233,7 @@ function ItemRepeat(
204
233
  // Repeater field detail items
205
234
  subItems: values.map((repeatState) =>
206
235
  collection.fields.map((field) =>
207
- ItemField(page, repeatState, field, options)
236
+ ItemField(page, repeatState, field, options, translator)
208
237
  )
209
238
  )
210
239
  }
@@ -221,17 +250,31 @@ export function ItemField(
221
250
  options: {
222
251
  path: string
223
252
  errors?: FormSubmissionError[]
224
- }
253
+ },
254
+ translator: Translator
225
255
  ): DetailItemField {
256
+ // FormComponent doesn't expose shortDescription/title as raw def properties,
257
+ // so build a lookup object with English values to let tComponent's GUID lookup fire.
258
+ const fieldDef = {
259
+ id: field.id,
260
+ type: field.type,
261
+ shortDescription: field.label,
262
+ title: field.title
263
+ } as unknown as ComponentDef
264
+ const rawLabel = translator.tComponent(fieldDef, 'shortDescription')
265
+ const translatedLabel = rawLabel !== '' ? rawLabel : field.label
266
+ const rawTitle = translator.tComponent(fieldDef, 'title')
267
+ const translatedTitle = rawTitle !== '' ? rawTitle : field.title
268
+ const optional =
269
+ field.options.required === false
270
+ ? ` ${translator.t('common.optional')}`
271
+ : ''
226
272
  return {
227
273
  name: field.name,
228
- label: field.title,
229
- title:
230
- field.options.required === false
231
- ? `${field.summaryLabel} (optional)`
232
- : field.summaryLabel,
233
- error: field.getFirstError(options.errors),
234
- value: getAnswer(field, state),
274
+ label: translatedTitle,
275
+ title: `${translatedLabel}${optional}`,
276
+ error: field.getFirstError(translator, options.errors),
277
+ value: getAnswer(field, state, translator, { format: 'summary' }),
235
278
  href: getPageHref(page, options.path, {
236
279
  returnUrl: getPageHref(page, page.getSummaryPath())
237
280
  }),
@@ -25,7 +25,8 @@ const pluginRegistrationOptionsSchema = Joi.object({
25
25
  baseUrl: Joi.string().uri().required(),
26
26
  saveAndExit: Joi.function().optional(),
27
27
  ordnanceSurveyApiKey: Joi.string().optional(),
28
- ordnanceSurveyApiSecret: Joi.string().optional()
28
+ ordnanceSurveyApiSecret: Joi.string().optional(),
29
+ getLanguage: Joi.function().optional()
29
30
  })
30
31
 
31
32
  /**
@@ -49,6 +49,11 @@ export function format(
49
49
  meta.versionMetadata = versionMetadata
50
50
  }
51
51
 
52
+ const definitionLanguage = model.def.metadata?.language
53
+ if (typeof definitionLanguage === 'string') {
54
+ meta.language = definitionLanguage
55
+ }
56
+
52
57
  const main = Object.fromEntries(
53
58
  Object.entries(v2Main).map(([key, value]) => {
54
59
  if (value === undefined) {
@@ -30,6 +30,7 @@ export function format(
30
30
  formStatus: ReturnType<typeof checkFormStatus>,
31
31
  _formMetadata?: FormMetadata
32
32
  ) {
33
+ const translator = model.createTranslator()
33
34
  const { files } = submitResponse.result
34
35
 
35
36
  const formName = escapeMarkdown(model.name)
@@ -72,9 +73,7 @@ export function format(
72
73
  lines.push(`[${filename}](${designerUrl}/file-download/${fileId})\n`)
73
74
  } else {
74
75
  lines.push(
75
- getAnswer(item.field, item.state, {
76
- format: 'email'
77
- })
76
+ getAnswer(item.field, item.state, translator, { format: 'email' })
78
77
  )
79
78
  }
80
79
 
@@ -7,6 +7,7 @@ import { config } from '../../../../../config/index.js'
7
7
  import { getAnswer } from '../../components/helpers/components.js'
8
8
  import { FileUploadField } from '../../components/index.js'
9
9
  import { type checkFormStatus } from '../../helpers.js'
10
+ import { type Translator } from '../../i18n/types.js'
10
11
  import { type FormModel } from '../../models/index.js'
11
12
  import {
12
13
  type DetailItem,
@@ -25,9 +26,10 @@ export function format(
25
26
  _formStatus: ReturnType<typeof checkFormStatus>,
26
27
  _formMetadata?: FormMetadata
27
28
  ) {
29
+ const translator = model.createTranslator()
28
30
  const now = new Date()
29
31
 
30
- const categorisedData = categoriseData(items)
32
+ const categorisedData = categoriseData(items, translator)
31
33
 
32
34
  const data = {
33
35
  meta: {
@@ -68,7 +70,7 @@ export function format(
68
70
  * }
69
71
  * }
70
72
  */
71
- function categoriseData(items: DetailItem[]) {
73
+ function categoriseData(items: DetailItem[], translator: Translator) {
72
74
  const output: {
73
75
  main: Record<string, string>
74
76
  repeaters: Record<string, Record<string, string>[]>
@@ -77,11 +79,11 @@ function categoriseData(items: DetailItem[]) {
77
79
 
78
80
  items.forEach((item) => {
79
81
  if ('subItems' in item) {
80
- output.repeaters[item.name] = extractRepeaters(item)
82
+ output.repeaters[item.name] = extractRepeaters(item, translator)
81
83
  } else if (isFileUploadFieldItem(item)) {
82
84
  output.files[item.name] = extractFileUploads(item)
83
85
  } else {
84
- output.main[item.name] = getAnswer(item.field, item.state, {
86
+ output.main[item.name] = getAnswer(item.field, item.state, translator, {
85
87
  format: 'data'
86
88
  })
87
89
  }
@@ -95,7 +97,7 @@ function categoriseData(items: DetailItem[]) {
95
97
  * @param item - the repeater item
96
98
  * @returns the repeater item
97
99
  */
98
- function extractRepeaters(item: DetailItemRepeat) {
100
+ function extractRepeaters(item: DetailItemRepeat, translator: Translator) {
99
101
  const repeaters: Record<string, string>[] = []
100
102
 
101
103
  item.subItems.forEach((inputRepeaterItem) => {
@@ -105,9 +107,8 @@ function extractRepeaters(item: DetailItemRepeat) {
105
107
  outputRepeaterItem[repeaterComponent.name] = getAnswer(
106
108
  repeaterComponent.field,
107
109
  repeaterComponent.state,
108
- {
109
- format: 'data'
110
- }
110
+ translator,
111
+ { format: 'data' }
111
112
  )
112
113
  })
113
114
 
@@ -14,6 +14,7 @@ import {
14
14
  getError,
15
15
  getExponentialBackoffDelay
16
16
  } from '../helpers.js'
17
+ import { type Translator } from '../i18n/types.js'
17
18
  import { type FormModel } from '../models/index.js'
18
19
  import { QuestionPageController } from './QuestionPageController.js'
19
20
  import { getProxyUrlForLocalDevelopment } from './helpers/index.js'
@@ -187,10 +188,12 @@ export class FileUploadPageController extends QuestionPageController {
187
188
 
188
189
  const { filename } = fileToRemove.status.form.file
189
190
 
191
+ const { t } = this.getTranslator(request)
192
+
190
193
  return h.view(this.fileDeleteViewName, {
191
194
  ...viewModel,
192
195
  context,
193
- backLink: this.getBackLink(request, context),
196
+ backLink: this.getBackLink(request, context, t),
194
197
  pageTitle: `Are you sure you want to remove this file?`,
195
198
  itemTitle: filename,
196
199
  confirmation: { text: 'You cannot recover removed files.' },
@@ -258,14 +261,15 @@ export class FileUploadPageController extends QuestionPageController {
258
261
 
259
262
  getViewModel(
260
263
  request: FormContextRequest,
261
- context: FormContext
264
+ context: FormContext,
265
+ translator: Translator
262
266
  ): FeaturedFormPageViewModel {
263
267
  const { fileUpload } = this
264
268
  const { state } = context
265
269
 
266
270
  const upload = this.getUploadFromState(state)
267
271
 
268
- const viewModel = super.getViewModel(request, context)
272
+ const viewModel = super.getViewModel(request, context, translator)
269
273
  const { components } = viewModel
270
274
 
271
275
  // Featured form component
@@ -15,9 +15,11 @@ import {
15
15
  getStartPath,
16
16
  normalisePath
17
17
  } from '../helpers.js'
18
+ import { type Translator } from '../i18n/types.js'
18
19
  import { type FormModel } from '../models/index.js'
19
20
  import { type ExecutableCondition } from '../models/types.js'
20
21
  import {
22
+ type AnyFormRequest,
21
23
  type FormContext,
22
24
  type PageViewModelBase
23
25
  } from '../types.js'
@@ -28,6 +30,7 @@ import {
28
30
  type FormRequestRefs,
29
31
  type FormResponseToolkit
30
32
  } from '../../../routes/types.js'
33
+ import { resolveLanguage } from '../../../utils/utils.js'
31
34
 
32
35
  export class PageController {
33
36
  /**
@@ -161,6 +164,10 @@ export class PageController {
161
164
  return ControllerPath.Status.valueOf()
162
165
  }
163
166
 
167
+ protected getTranslator(request: AnyFormRequest): Translator {
168
+ return this.model.createTranslator(resolveLanguage(request))
169
+ }
170
+
164
171
  makeGetRouteHandler(): (
165
172
  request: FormRequest,
166
173
  context: FormContext,
@@ -168,7 +175,11 @@ export class PageController {
168
175
  ) => ReturnType<Lifecycle.Method<FormRequestRefs>> {
169
176
  return (request, context, h) => {
170
177
  const { viewModel, viewName } = this
171
- return h.view(viewName, viewModel)
178
+ const { t } = this.getTranslator(request)
179
+ return h.view(viewName, {
180
+ ...viewModel,
181
+ t
182
+ })
172
183
  }
173
184
  }
174
185