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

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 (309) hide show
  1. package/.server/server/forms/register-as-a-unicorn-breeder.yaml +0 -14
  2. package/.server/server/forms/simple-form.yaml +106 -3
  3. package/.server/server/plugins/engine/components/AutocompleteField.d.ts +11 -11
  4. package/.server/server/plugins/engine/components/AutocompleteField.js +2 -2
  5. package/.server/server/plugins/engine/components/AutocompleteField.js.map +1 -1
  6. package/.server/server/plugins/engine/components/CheckboxesField.d.ts +3 -2
  7. package/.server/server/plugins/engine/components/CheckboxesField.js +4 -9
  8. package/.server/server/plugins/engine/components/CheckboxesField.js.map +1 -1
  9. package/.server/server/plugins/engine/components/ComponentCollection.d.ts +8 -7
  10. package/.server/server/plugins/engine/components/ComponentCollection.js +106 -51
  11. package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
  12. package/.server/server/plugins/engine/components/DatePartsField.d.ts +8 -6
  13. package/.server/server/plugins/engine/components/DatePartsField.js +27 -9
  14. package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
  15. package/.server/server/plugins/engine/components/DeclarationField.d.ts +13 -11
  16. package/.server/server/plugins/engine/components/DeclarationField.js +31 -9
  17. package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
  18. package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +6 -5
  19. package/.server/server/plugins/engine/components/EastingNorthingField.js +42 -22
  20. package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
  21. package/.server/server/plugins/engine/components/EmailAddressField.d.ts +11 -14
  22. package/.server/server/plugins/engine/components/EmailAddressField.js +2 -2
  23. package/.server/server/plugins/engine/components/EmailAddressField.js.map +1 -1
  24. package/.server/server/plugins/engine/components/FileUploadField.d.ts +14 -12
  25. package/.server/server/plugins/engine/components/FileUploadField.js +22 -12
  26. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  27. package/.server/server/plugins/engine/components/FormComponent.d.ts +21 -12
  28. package/.server/server/plugins/engine/components/FormComponent.js +33 -14
  29. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  30. package/.server/server/plugins/engine/components/GeospatialField.d.ts +14 -12
  31. package/.server/server/plugins/engine/components/GeospatialField.js +19 -13
  32. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -1
  33. package/.server/server/plugins/engine/components/LatLongField.d.ts +6 -5
  34. package/.server/server/plugins/engine/components/LatLongField.js +32 -20
  35. package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
  36. package/.server/server/plugins/engine/components/ListFormComponent.d.ts +7 -6
  37. package/.server/server/plugins/engine/components/ListFormComponent.js +19 -7
  38. package/.server/server/plugins/engine/components/ListFormComponent.js.map +1 -1
  39. package/.server/server/plugins/engine/components/LocationFieldBase.d.ts +17 -16
  40. package/.server/server/plugins/engine/components/LocationFieldBase.js +2 -2
  41. package/.server/server/plugins/engine/components/LocationFieldBase.js.map +1 -1
  42. package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +3 -3
  43. package/.server/server/plugins/engine/components/LocationFieldHelpers.js +2 -2
  44. package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
  45. package/.server/server/plugins/engine/components/Markdown.d.ts +8 -7
  46. package/.server/server/plugins/engine/components/Markdown.js +5 -2
  47. package/.server/server/plugins/engine/components/Markdown.js.map +1 -1
  48. package/.server/server/plugins/engine/components/MonthYearField.d.ts +9 -7
  49. package/.server/server/plugins/engine/components/MonthYearField.js +26 -8
  50. package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
  51. package/.server/server/plugins/engine/components/MultilineTextField.d.ts +10 -9
  52. package/.server/server/plugins/engine/components/MultilineTextField.js +2 -2
  53. package/.server/server/plugins/engine/components/MultilineTextField.js.map +1 -1
  54. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.d.ts +2 -1
  55. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js +14 -6
  56. package/.server/server/plugins/engine/components/NationalGridFieldNumberField.js.map +1 -1
  57. package/.server/server/plugins/engine/components/NumberField.d.ts +10 -9
  58. package/.server/server/plugins/engine/components/NumberField.js +2 -2
  59. package/.server/server/plugins/engine/components/NumberField.js.map +1 -1
  60. package/.server/server/plugins/engine/components/OsGridRefField.d.ts +2 -1
  61. package/.server/server/plugins/engine/components/OsGridRefField.js +14 -6
  62. package/.server/server/plugins/engine/components/OsGridRefField.js.map +1 -1
  63. package/.server/server/plugins/engine/components/PaymentField.d.ts +12 -10
  64. package/.server/server/plugins/engine/components/PaymentField.js +21 -8
  65. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  66. package/.server/server/plugins/engine/components/SelectField.d.ts +11 -11
  67. package/.server/server/plugins/engine/components/SelectField.js +2 -2
  68. package/.server/server/plugins/engine/components/SelectField.js.map +1 -1
  69. package/.server/server/plugins/engine/components/SelectionControlField.d.ts +4 -4
  70. package/.server/server/plugins/engine/components/SelectionControlField.js +2 -2
  71. package/.server/server/plugins/engine/components/SelectionControlField.js.map +1 -1
  72. package/.server/server/plugins/engine/components/TelephoneNumberField.d.ts +10 -9
  73. package/.server/server/plugins/engine/components/TelephoneNumberField.js +2 -2
  74. package/.server/server/plugins/engine/components/TelephoneNumberField.js.map +1 -1
  75. package/.server/server/plugins/engine/components/UkAddressField.d.ts +12 -10
  76. package/.server/server/plugins/engine/components/UkAddressField.js +29 -17
  77. package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
  78. package/.server/server/plugins/engine/components/YesNoField.d.ts +3 -0
  79. package/.server/server/plugins/engine/components/YesNoField.js +9 -0
  80. package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
  81. package/.server/server/plugins/engine/components/helpers/components.d.ts +3 -2
  82. package/.server/server/plugins/engine/components/helpers/components.js +11 -8
  83. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  84. package/.server/server/plugins/engine/components/types.d.ts +8 -1
  85. package/.server/server/plugins/engine/components/types.js.map +1 -1
  86. package/.server/server/plugins/engine/configureEnginePlugin.js +8 -1
  87. package/.server/server/plugins/engine/configureEnginePlugin.js.map +1 -1
  88. package/.server/server/plugins/engine/form-context.d.ts +1 -0
  89. package/.server/server/plugins/engine/form-context.js +4 -2
  90. package/.server/server/plugins/engine/form-context.js.map +1 -1
  91. package/.server/server/plugins/engine/helpers.d.ts +1 -1
  92. package/.server/server/plugins/engine/helpers.js +8 -13
  93. package/.server/server/plugins/engine/helpers.js.map +1 -1
  94. package/.server/server/plugins/engine/i18n/buildValidationMessages.d.ts +39 -0
  95. package/.server/server/plugins/engine/i18n/buildValidationMessages.js +50 -0
  96. package/.server/server/plugins/engine/i18n/buildValidationMessages.js.map +1 -0
  97. package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +5 -0
  98. package/.server/server/plugins/engine/i18n/createFormTranslator.js +18 -0
  99. package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -0
  100. package/.server/server/plugins/engine/i18n/createTranslator.d.ts +9 -0
  101. package/.server/server/plugins/engine/i18n/createTranslator.js +51 -0
  102. package/.server/server/plugins/engine/i18n/createTranslator.js.map +1 -0
  103. package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +5 -0
  104. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +57 -0
  105. package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -0
  106. package/.server/server/plugins/engine/i18n/index.d.ts +18 -0
  107. package/.server/server/plugins/engine/i18n/index.js +71 -0
  108. package/.server/server/plugins/engine/i18n/index.js.map +1 -0
  109. package/.server/server/plugins/engine/i18n/translations/cy.json +271 -0
  110. package/.server/server/plugins/engine/i18n/translations/en-GB.json +271 -0
  111. package/.server/server/plugins/engine/i18n/types.d.ts +41 -0
  112. package/.server/server/plugins/engine/i18n/types.js +2 -0
  113. package/.server/server/plugins/engine/i18n/types.js.map +1 -0
  114. package/.server/server/plugins/engine/index.js +3 -2
  115. package/.server/server/plugins/engine/index.js.map +1 -1
  116. package/.server/server/plugins/engine/models/FormModel.d.ts +9 -1
  117. package/.server/server/plugins/engine/models/FormModel.js +44 -18
  118. package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
  119. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +5 -2
  120. package/.server/server/plugins/engine/models/SummaryViewModel.js +44 -18
  121. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  122. package/.server/server/plugins/engine/options.js +2 -1
  123. package/.server/server/plugins/engine/options.js.map +1 -1
  124. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js +4 -0
  125. package/.server/server/plugins/engine/outputFormatters/adapter/v1.js.map +1 -1
  126. package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
  127. package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
  128. package/.server/server/plugins/engine/outputFormatters/machine/v1.js +7 -6
  129. package/.server/server/plugins/engine/outputFormatters/machine/v1.js.map +1 -1
  130. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +2 -1
  131. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +6 -3
  132. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  133. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +3 -1
  134. package/.server/server/plugins/engine/pageControllers/PageController.js +11 -1
  135. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  136. package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +4 -2
  137. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +52 -21
  138. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  139. package/.server/server/plugins/engine/pageControllers/RepeatPageController.d.ts +3 -2
  140. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js +63 -26
  141. package/.server/server/plugins/engine/pageControllers/RepeatPageController.js.map +1 -1
  142. package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +3 -1
  143. package/.server/server/plugins/engine/pageControllers/StartPageController.js +2 -2
  144. package/.server/server/plugins/engine/pageControllers/StartPageController.js.map +1 -1
  145. package/.server/server/plugins/engine/pageControllers/StatusPageController.js +12 -2
  146. package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
  147. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +4 -3
  148. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +44 -28
  149. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  150. package/.server/server/plugins/engine/pageControllers/__stubs__/translator.d.ts +2 -0
  151. package/.server/server/plugins/engine/pageControllers/__stubs__/translator.js +24 -0
  152. package/.server/server/plugins/engine/pageControllers/__stubs__/translator.js.map +1 -0
  153. package/.server/server/plugins/engine/pageControllers/errors.d.ts +1 -1
  154. package/.server/server/plugins/engine/pageControllers/errors.js +3 -2
  155. package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
  156. package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +3 -2
  157. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +4 -4
  158. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  159. package/.server/server/plugins/engine/pageControllers/validationOptions.d.ts +7 -3
  160. package/.server/server/plugins/engine/pageControllers/validationOptions.js +34 -33
  161. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  162. package/.server/server/plugins/engine/plugin.js +2 -0
  163. package/.server/server/plugins/engine/plugin.js.map +1 -1
  164. package/.server/server/plugins/engine/routes/index.js +4 -1
  165. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  166. package/.server/server/plugins/engine/routes/questions.js +5 -2
  167. package/.server/server/plugins/engine/routes/questions.js.map +1 -1
  168. package/.server/server/plugins/engine/types/index.d.ts +1 -0
  169. package/.server/server/plugins/engine/types/index.js.map +1 -1
  170. package/.server/server/plugins/engine/types/schema.js +1 -0
  171. package/.server/server/plugins/engine/types/schema.js.map +1 -1
  172. package/.server/server/plugins/engine/types.d.ts +12 -1
  173. package/.server/server/plugins/engine/types.js.map +1 -1
  174. package/.server/server/plugins/engine/views/components/_location-field-base.html +1 -1
  175. package/.server/server/plugins/engine/views/components/fileuploadfield.html +3 -3
  176. package/.server/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +1 -1
  177. package/.server/server/plugins/engine/views/components/osgridreffield.html +1 -1
  178. package/.server/server/plugins/engine/views/components/paymentfield.html +8 -8
  179. package/.server/server/plugins/engine/views/components/ukaddressfield.html +4 -4
  180. package/.server/server/plugins/engine/views/confirmation.html +4 -4
  181. package/.server/server/plugins/engine/views/exit.html +4 -4
  182. package/.server/server/plugins/engine/views/file-upload.html +3 -3
  183. package/.server/server/plugins/engine/views/index.html +3 -3
  184. package/.server/server/plugins/engine/views/item-delete.html +1 -1
  185. package/.server/server/plugins/engine/views/partials/form.html +3 -3
  186. package/.server/server/plugins/engine/views/partials/preview-banner.html +4 -4
  187. package/.server/server/plugins/engine/views/partials/warn-missing-notification-email.html +4 -5
  188. package/.server/server/plugins/engine/views/repeat-list-summary.html +4 -4
  189. package/.server/server/plugins/engine/views/summary.html +14 -7
  190. package/.server/server/plugins/nunjucks/enviroment.test.js +10 -0
  191. package/.server/server/plugins/nunjucks/enviroment.test.js.map +1 -1
  192. package/.server/server/plugins/nunjucks/environment.d.ts +8 -0
  193. package/.server/server/plugins/nunjucks/environment.js +13 -0
  194. package/.server/server/plugins/nunjucks/environment.js.map +1 -1
  195. package/.server/server/plugins/nunjucks/filters/answer.js +9 -1
  196. package/.server/server/plugins/nunjucks/filters/answer.js.map +1 -1
  197. package/.server/server/plugins/nunjucks/filters/answer.test.js +13 -2
  198. package/.server/server/plugins/nunjucks/filters/answer.test.js.map +1 -1
  199. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  200. package/.server/server/plugins/postcode-lookup/models/index.d.ts +24 -19
  201. package/.server/server/plugins/postcode-lookup/models/index.js +83 -76
  202. package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
  203. package/.server/server/plugins/postcode-lookup/routes/index.js +7 -13
  204. package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
  205. package/.server/server/plugins/postcode-lookup/types.d.ts +1 -0
  206. package/.server/server/plugins/postcode-lookup/types.js +1 -0
  207. package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
  208. package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  209. package/.server/server/types.d.ts +2 -1
  210. package/.server/server/types.js.map +1 -1
  211. package/.server/server/utils/utils.d.ts +5 -0
  212. package/.server/server/utils/utils.js +16 -0
  213. package/.server/server/utils/utils.js.map +1 -1
  214. package/.server/typings/hapi/index.d.js.map +1 -1
  215. package/package.json +3 -2
  216. package/src/server/forms/register-as-a-unicorn-breeder.yaml +0 -14
  217. package/src/server/forms/simple-form.yaml +106 -3
  218. package/src/server/plugins/engine/components/AutocompleteField.ts +3 -6
  219. package/src/server/plugins/engine/components/CheckboxesField.ts +12 -11
  220. package/src/server/plugins/engine/components/ComponentCollection.ts +143 -61
  221. package/src/server/plugins/engine/components/DatePartsField.ts +33 -11
  222. package/src/server/plugins/engine/components/DeclarationField.ts +37 -14
  223. package/src/server/plugins/engine/components/EastingNorthingField.ts +83 -22
  224. package/src/server/plugins/engine/components/EmailAddressField.ts +4 -7
  225. package/src/server/plugins/engine/components/FileUploadField.ts +28 -22
  226. package/src/server/plugins/engine/components/FormComponent.ts +49 -12
  227. package/src/server/plugins/engine/components/GeospatialField.ts +36 -21
  228. package/src/server/plugins/engine/components/LatLongField.ts +52 -21
  229. package/src/server/plugins/engine/components/ListFormComponent.ts +26 -10
  230. package/src/server/plugins/engine/components/LocationFieldBase.ts +5 -8
  231. package/src/server/plugins/engine/components/LocationFieldHelpers.ts +3 -3
  232. package/src/server/plugins/engine/components/Markdown.ts +7 -3
  233. package/src/server/plugins/engine/components/MonthYearField.ts +32 -10
  234. package/src/server/plugins/engine/components/MultilineTextField.ts +5 -8
  235. package/src/server/plugins/engine/components/NationalGridFieldNumberField.ts +25 -11
  236. package/src/server/plugins/engine/components/NumberField.ts +3 -4
  237. package/src/server/plugins/engine/components/OsGridRefField.ts +25 -9
  238. package/src/server/plugins/engine/components/PaymentField.ts +22 -12
  239. package/src/server/plugins/engine/components/SelectField.ts +3 -6
  240. package/src/server/plugins/engine/components/SelectionControlField.ts +7 -8
  241. package/src/server/plugins/engine/components/TelephoneNumberField.ts +4 -7
  242. package/src/server/plugins/engine/components/UkAddressField.ts +33 -18
  243. package/src/server/plugins/engine/components/YesNoField.ts +10 -0
  244. package/src/server/plugins/engine/components/helpers/components.ts +13 -6
  245. package/src/server/plugins/engine/components/types.ts +9 -0
  246. package/src/server/plugins/engine/configureEnginePlugin.ts +12 -1
  247. package/src/server/plugins/engine/form-context.ts +8 -2
  248. package/src/server/plugins/engine/helpers.ts +10 -18
  249. package/src/server/plugins/engine/i18n/buildValidationMessages.ts +69 -0
  250. package/src/server/plugins/engine/i18n/createFormTranslator.ts +33 -0
  251. package/src/server/plugins/engine/i18n/createTranslator.ts +61 -0
  252. package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +61 -0
  253. package/src/server/plugins/engine/i18n/index.ts +80 -0
  254. package/src/server/plugins/engine/i18n/translations/cy.json +271 -0
  255. package/src/server/plugins/engine/i18n/translations/en-GB.json +271 -0
  256. package/src/server/plugins/engine/i18n/types.ts +62 -0
  257. package/src/server/plugins/engine/index.ts +4 -2
  258. package/src/server/plugins/engine/models/FormModel.ts +59 -23
  259. package/src/server/plugins/engine/models/SummaryViewModel.ts +68 -25
  260. package/src/server/plugins/engine/options.js +2 -1
  261. package/src/server/plugins/engine/outputFormatters/adapter/v1.ts +5 -0
  262. package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -3
  263. package/src/server/plugins/engine/outputFormatters/machine/v1.ts +9 -8
  264. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +7 -3
  265. package/src/server/plugins/engine/pageControllers/PageController.ts +12 -1
  266. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +53 -21
  267. package/src/server/plugins/engine/pageControllers/RepeatPageController.ts +66 -29
  268. package/src/server/plugins/engine/pageControllers/StartPageController.ts +7 -2
  269. package/src/server/plugins/engine/pageControllers/StatusPageController.ts +10 -2
  270. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +48 -22
  271. package/src/server/plugins/engine/pageControllers/__stubs__/translator.ts +28 -0
  272. package/src/server/plugins/engine/pageControllers/errors.ts +4 -2
  273. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +11 -4
  274. package/src/server/plugins/engine/pageControllers/validationOptions.ts +43 -53
  275. package/src/server/plugins/engine/plugin.ts +2 -0
  276. package/src/server/plugins/engine/routes/index.ts +9 -1
  277. package/src/server/plugins/engine/routes/questions.ts +5 -1
  278. package/src/server/plugins/engine/types/index.ts +5 -0
  279. package/src/server/plugins/engine/types/schema.ts +1 -0
  280. package/src/server/plugins/engine/types.ts +10 -0
  281. package/src/server/plugins/engine/views/components/_location-field-base.html +1 -1
  282. package/src/server/plugins/engine/views/components/fileuploadfield.html +3 -3
  283. package/src/server/plugins/engine/views/components/nationalgridfieldnumberfield.html +1 -1
  284. package/src/server/plugins/engine/views/components/osgridreffield.html +1 -1
  285. package/src/server/plugins/engine/views/components/paymentfield.html +8 -8
  286. package/src/server/plugins/engine/views/components/ukaddressfield.html +4 -4
  287. package/src/server/plugins/engine/views/confirmation.html +4 -4
  288. package/src/server/plugins/engine/views/exit.html +4 -4
  289. package/src/server/plugins/engine/views/file-upload.html +3 -3
  290. package/src/server/plugins/engine/views/index.html +3 -3
  291. package/src/server/plugins/engine/views/item-delete.html +1 -1
  292. package/src/server/plugins/engine/views/partials/form.html +3 -3
  293. package/src/server/plugins/engine/views/partials/preview-banner.html +4 -4
  294. package/src/server/plugins/engine/views/partials/warn-missing-notification-email.html +4 -5
  295. package/src/server/plugins/engine/views/repeat-list-summary.html +4 -4
  296. package/src/server/plugins/engine/views/summary.html +14 -7
  297. package/src/server/plugins/nunjucks/environment.js +14 -0
  298. package/src/server/plugins/nunjucks/filters/answer.js +12 -1
  299. package/src/server/plugins/postcode-lookup/models/index.js +144 -87
  300. package/src/server/plugins/postcode-lookup/routes/index.js +13 -16
  301. package/src/server/plugins/postcode-lookup/types.js +1 -0
  302. package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
  303. package/src/server/types.ts +2 -0
  304. package/src/server/utils/utils.js +14 -0
  305. package/src/typings/hapi/index.d.ts +1 -0
  306. package/.server/server/plugins/engine/components/constants.d.ts +0 -1
  307. package/.server/server/plugins/engine/components/constants.js +0 -2
  308. package/.server/server/plugins/engine/components/constants.js.map +0 -1
  309. package/src/server/plugins/engine/components/constants.ts +0 -1
@@ -11,10 +11,12 @@ import { logger } from '../../../common/helpers/logging/logger.js'
11
11
  import { COMPONENT_STATE_ERROR } from '../../../constants.js'
12
12
  import { FormComponent } from './FormComponent.js'
13
13
  import { type PaymentState } from './PaymentField.types.js'
14
+ import { type RenderContext } from './types.js'
14
15
  import {
15
16
  createError,
16
17
  getPluginOptions
17
18
  } from '../helpers.js'
19
+ import { type Translator } from '../i18n/types.js'
18
20
  import { type FormModel } from '../models/index.js'
19
21
  import {
20
22
  PaymentErrorTypes,
@@ -28,10 +30,8 @@ import {
28
30
  } from '../types/index.js'
29
31
  import {
30
32
  type ErrorMessageTemplateList,
31
- type FormPayload,
32
33
  type FormState,
33
34
  type FormStateValue,
34
- type FormSubmissionError,
35
35
  type FormSubmissionState,
36
36
  type PaymentExternalArgs
37
37
  } from '../types.js'
@@ -39,6 +39,7 @@ import {
39
39
  createPaymentService,
40
40
  formatCurrency
41
41
  } from '../../payment/helper.js'
42
+ import { resolveLanguage } from '../../../utils/utils.js'
42
43
 
43
44
  export class PaymentField extends FormComponent {
44
45
  declare options: PaymentFieldComponent['options']
@@ -93,7 +94,10 @@ export class PaymentField extends FormComponent {
93
94
  return this.isPaymentState(value) ? value : undefined
94
95
  }
95
96
 
96
- getDisplayStringFromState(state: FormSubmissionState): string {
97
+ getDisplayStringFromState(
98
+ state: FormSubmissionState,
99
+ _translator: Translator
100
+ ): string {
97
101
  const value = this.getPaymentStateFromState(state)
98
102
 
99
103
  if (!value) {
@@ -103,8 +107,9 @@ export class PaymentField extends FormComponent {
103
107
  return `${formatCurrency(value.amount)} - ${value.description}`
104
108
  }
105
109
 
106
- getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
107
- const viewModel = super.getViewModel(payload, errors)
110
+ getViewModel(context: RenderContext) {
111
+ const { payload } = context
112
+ const viewModel = super.getViewModel(context)
108
113
 
109
114
  // Payload is pre-populated from state if a payment has already been made
110
115
  const paymentState = this.isPaymentState(payload[this.name] as unknown)
@@ -229,7 +234,8 @@ export class PaymentField extends FormComponent {
229
234
  ): Promise<unknown> {
230
235
  const { options, name: componentName } = args.component
231
236
  const { model } = args.controller
232
-
237
+ const language = resolveLanguage(request)
238
+ const { t } = model.createTranslator(language)
233
239
  const state = await args.controller.getState(request)
234
240
  const { baseUrl } = getPluginOptions(request.server)
235
241
  const summaryUrl = `${baseUrl}/${model.basePath}/summary`
@@ -287,8 +293,8 @@ export class PaymentField extends FormComponent {
287
293
 
288
294
  if (!payment) {
289
295
  const message = isLivePayment
290
- ? '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.'
291
- : 'Add a valid test API key before you can preview the payment journey.'
296
+ ? t('components.paymentField.cannotTakePayment')
297
+ : t('components.paymentField.testApiKey')
292
298
  const govukError = createError(componentName, message)
293
299
  request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)
294
300
  return h
@@ -323,6 +329,9 @@ export class PaymentField extends FormComponent {
323
329
  _metadata: FormMetadata,
324
330
  context: FormContext
325
331
  ): Promise<void> {
332
+ const language = resolveLanguage(request)
333
+ const { t } = this.model.createTranslator(language)
334
+
326
335
  // Zero-amount bypass — no capture needed
327
336
  const resolvedAmount = PaymentField.resolveAmount(
328
337
  this.options,
@@ -338,7 +347,7 @@ export class PaymentField extends FormComponent {
338
347
  if (!paymentState) {
339
348
  throw new PaymentPreAuthError(
340
349
  this,
341
- 'Complete the payment to continue',
350
+ t('components.paymentField.completePayment'),
342
351
  true,
343
352
  PaymentErrorTypes.PaymentIncomplete
344
353
  )
@@ -367,7 +376,8 @@ export class PaymentField extends FormComponent {
367
376
  PaymentSubmissionError.checkPaymentAmount(
368
377
  status.amount,
369
378
  resolvedAmount,
370
- this
379
+ this,
380
+ t
371
381
  )
372
382
 
373
383
  if (status.state.status === 'success') {
@@ -378,7 +388,7 @@ export class PaymentField extends FormComponent {
378
388
  if (status.state.status !== 'capturable') {
379
389
  throw new PaymentPreAuthError(
380
390
  this,
381
- 'Your payment authorisation has expired. Please add your payment details again.',
391
+ t('components.paymentField.paymentExpired'),
382
392
  true,
383
393
  PaymentErrorTypes.PaymentExpired
384
394
  )
@@ -392,7 +402,7 @@ export class PaymentField extends FormComponent {
392
402
  if (!captured) {
393
403
  throw new PaymentPreAuthError(
394
404
  this,
395
- 'There was a problem and your form was not submitted. Try submitting the form again.',
405
+ t('components.paymentField.submissionFailed'),
396
406
  false
397
407
  )
398
408
  }
@@ -4,10 +4,7 @@ import {
4
4
  } from '@defra/forms-model'
5
5
 
6
6
  import { ListFormComponent } from './ListFormComponent.js'
7
- import {
8
- type FormPayload,
9
- type FormSubmissionError
10
- } from '../types.js'
7
+ import { type RenderContext } from './types.js'
11
8
 
12
9
  export class SelectField extends ListFormComponent {
13
10
  declare options:
@@ -33,8 +30,8 @@ export class SelectField extends ListFormComponent {
33
30
  this.options = options
34
31
  }
35
32
 
36
- getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
37
- const viewModel = super.getViewModel(payload, errors)
33
+ getViewModel(context: RenderContext) {
34
+ const viewModel = super.getViewModel(context)
38
35
  let { items } = viewModel
39
36
 
40
37
  items = [{ value: '' }, ...items]
@@ -1,20 +1,19 @@
1
1
  import { ListFormComponent } from './ListFormComponent.js'
2
- import { type ListItem } from './types.js'
3
- import { messageTemplate } from '../pageControllers/validationOptions.js'
4
2
  import {
5
- type ErrorMessageTemplateList,
6
- type FormPayload,
7
- type FormSubmissionError
8
- } from '../types.js'
3
+ type ListItem,
4
+ type RenderContext
5
+ } from './types.js'
6
+ import { messageTemplate } from '../pageControllers/validationOptions.js'
7
+ import { type ErrorMessageTemplateList } from '../types.js'
9
8
 
10
9
  /**
11
10
  * "Selection controls" are checkboxes and radios (and switches), as per Material UI nomenclature.
12
11
  */
13
12
  export class SelectionControlField extends ListFormComponent {
14
- getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
13
+ getViewModel(context: RenderContext) {
15
14
  const { options } = this
16
15
 
17
- const viewModel = super.getViewModel(payload, errors)
16
+ const viewModel = super.getViewModel(context)
18
17
  let { fieldset, items, label } = viewModel
19
18
 
20
19
  fieldset ??= {
@@ -7,12 +7,9 @@ import {
7
7
  INVALID_ERROR_CODE,
8
8
  joi
9
9
  } from './helpers/telephone.js'
10
+ import { type RenderContext } from './types.js'
10
11
  import { messageTemplate } from '../pageControllers/validationOptions.js'
11
- import {
12
- type ErrorMessageTemplateList,
13
- type FormPayload,
14
- type FormSubmissionError
15
- } from '../types.js'
12
+ import { type ErrorMessageTemplateList } from '../types.js'
16
13
 
17
14
  export class TelephoneNumberField extends FormComponent {
18
15
  declare options: TelephoneNumberFieldComponent['options']
@@ -59,8 +56,8 @@ export class TelephoneNumberField extends FormComponent {
59
56
  this.options = options
60
57
  }
61
58
 
62
- getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
63
- const viewModel = super.getViewModel(payload, errors)
59
+ getViewModel(context: RenderContext) {
60
+ const viewModel = super.getViewModel(context)
64
61
  const { attributes } = viewModel
65
62
 
66
63
  attributes.autocomplete = 'tel'
@@ -8,7 +8,8 @@ import {
8
8
  isFormState
9
9
  } from './FormComponent.js'
10
10
  import { TextField } from './TextField.js'
11
- import { type QuestionPageController } from '../pageControllers/QuestionPageController.js'
11
+ import { type RenderContext } from './types.js'
12
+ import { type Translator } from '../i18n/types.js'
12
13
  import {
13
14
  type FormRequestPayload,
14
15
  type FormResponseToolkit
@@ -23,6 +24,7 @@ import {
23
24
  type PostcodeLookupExternalArgs
24
25
  } from '../types.js'
25
26
  import { dispatch } from '../../postcode-lookup/routes/index.js'
27
+ import { resolveLanguage } from '../../../utils/utils.js'
26
28
 
27
29
  export class UkAddressField extends FormComponent {
28
30
  declare options: UkAddressFieldComponent['options']
@@ -41,13 +43,12 @@ export class UkAddressField extends FormComponent {
41
43
  const isRequired = options.required !== false
42
44
  const hideOptional = !!options.optionalText
43
45
  const hideTitle = !!options.hideTitle
44
-
45
46
  this.collection = new ComponentCollection(
46
47
  [
47
48
  {
48
49
  type: ComponentType.TextField,
49
50
  name: `${name}__uprn`,
50
- title: 'UPRN',
51
+ title: 'components.addressField.uprn',
51
52
  schema: {},
52
53
  options: {
53
54
  required: false,
@@ -57,7 +58,7 @@ export class UkAddressField extends FormComponent {
57
58
  {
58
59
  type: ComponentType.TextField,
59
60
  name: `${name}__addressLine1`,
60
- title: 'Address line 1',
61
+ title: 'components.addressField.line1',
61
62
  schema: { max: 100 },
62
63
  options: {
63
64
  autocomplete: 'address-line1',
@@ -68,7 +69,7 @@ export class UkAddressField extends FormComponent {
68
69
  {
69
70
  type: ComponentType.TextField,
70
71
  name: `${name}__addressLine2`,
71
- title: 'Address line 2',
72
+ title: 'components.addressField.line2',
72
73
  schema: { max: 100 },
73
74
  options: {
74
75
  autocomplete: 'address-line2',
@@ -79,7 +80,7 @@ export class UkAddressField extends FormComponent {
79
80
  {
80
81
  type: ComponentType.TextField,
81
82
  name: `${name}__town`,
82
- title: 'Town or city',
83
+ title: 'components.addressField.town',
83
84
  schema: { max: 100 },
84
85
  options: {
85
86
  autocomplete: 'address-level2',
@@ -91,7 +92,7 @@ export class UkAddressField extends FormComponent {
91
92
  {
92
93
  type: ComponentType.TextField,
93
94
  name: `${name}__county`,
94
- title: 'County',
95
+ title: 'components.addressField.county',
95
96
  schema: { max: 100 },
96
97
  options: {
97
98
  autocomplete: 'address-level1',
@@ -102,7 +103,7 @@ export class UkAddressField extends FormComponent {
102
103
  {
103
104
  type: ComponentType.TextField,
104
105
  name: `${name}__postcode`,
105
- title: 'Postcode',
106
+ title: 'components.addressField.postcode',
106
107
  schema: {
107
108
  regex: '^[a-zA-Z]{1,2}\\d[a-zA-Z\\d]?\\s?\\d[a-zA-Z]{2}$'
108
109
  },
@@ -143,23 +144,32 @@ export class UkAddressField extends FormComponent {
143
144
  return this.getContextValueFromFormValue(value)
144
145
  }
145
146
 
146
- getDisplayStringFromFormValue(value: UkAddressState | undefined): string {
147
+ getDisplayStringFromFormValue(
148
+ value: UkAddressState | undefined,
149
+ _translator: Translator
150
+ ): string {
147
151
  return this.getContextValueFromFormValue(value)?.join(', ') ?? ''
148
152
  }
149
153
 
150
- getDisplayStringFromState(state: FormSubmissionState) {
154
+ getDisplayStringFromState(
155
+ state: FormSubmissionState,
156
+ translator: Translator
157
+ ) {
151
158
  const value = this.getFormValueFromState(state)
152
159
 
153
- return this.getDisplayStringFromFormValue(value)
160
+ return this.getDisplayStringFromFormValue(value, translator)
154
161
  }
155
162
 
156
163
  /**
157
164
  * Returns one error per child field
158
165
  */
159
166
  getViewErrors(
167
+ translator: Translator,
160
168
  errors?: FormSubmissionError[]
161
169
  ): FormSubmissionError[] | undefined {
162
- const uniqueErrors = this.getErrors(errors)?.filter(
170
+ const { t } = translator
171
+
172
+ const uniqueErrors = this.getErrors(translator, errors)?.filter(
163
173
  (error, index, self) =>
164
174
  index === self.findIndex((err) => err.name === error.name)
165
175
  )
@@ -174,7 +184,9 @@ export class UkAddressField extends FormComponent {
174
184
  name,
175
185
  path: [name],
176
186
  href: `#${name}`,
177
- text: `Enter ${lowerFirst(label)}`
187
+ text: t('components.addressField.enterAddress', {
188
+ shortDescription: lowerFirst(label)
189
+ })
178
190
  }
179
191
  ]
180
192
  }
@@ -182,10 +194,11 @@ export class UkAddressField extends FormComponent {
182
194
  return uniqueErrors
183
195
  }
184
196
 
185
- getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
197
+ getViewModel(context: RenderContext) {
198
+ const { payload, translator } = context
186
199
  const { collection, name, options } = this
187
200
 
188
- const viewModel = super.getViewModel(payload, errors)
201
+ const viewModel = super.getViewModel(context)
189
202
  let { fieldset, hint, label } = viewModel
190
203
 
191
204
  fieldset ??= {
@@ -209,7 +222,7 @@ export class UkAddressField extends FormComponent {
209
222
  }
210
223
  }
211
224
 
212
- const components = collection.getViewModel(payload, errors)
225
+ const components = collection.getViewModel(context)
213
226
 
214
227
  // Hide UPRN
215
228
  const uprn = components.at(0)
@@ -224,7 +237,7 @@ export class UkAddressField extends FormComponent {
224
237
  const usePostcodeLookup = this.shouldUsePostcodeLookup()
225
238
 
226
239
  const value = usePostcodeLookup
227
- ? this.getDisplayStringFromState(payload)
240
+ ? this.getDisplayStringFromState(payload, translator)
228
241
  : undefined
229
242
 
230
243
  return {
@@ -284,6 +297,7 @@ export class UkAddressField extends FormComponent {
284
297
  args: PostcodeLookupExternalArgs
285
298
  ) {
286
299
  const { controller, component } = args
300
+ const language = resolveLanguage(request)
287
301
 
288
302
  return dispatch(request, h, {
289
303
  formName: controller.model.name,
@@ -291,7 +305,8 @@ export class UkAddressField extends FormComponent {
291
305
  componentHint: component.hint,
292
306
  componentTitle: component.title || controller.title,
293
307
  step: args.actionArgs.step,
294
- sourceUrl: args.sourceUrl
308
+ sourceUrl: args.sourceUrl,
309
+ language
295
310
  })
296
311
  }
297
312
  }
@@ -4,9 +4,12 @@ import {
4
4
  yesNoListName,
5
5
  type YesNoFieldComponent
6
6
  } from '@defra/forms-model'
7
+ import { type LanguageMessages } from 'joi'
7
8
 
8
9
  import { SelectionControlField } from './SelectionControlField.js'
9
10
  import { addClassOptionIfNone } from './helpers/index.js'
11
+ import { buildValidationMessages } from '../i18n/buildValidationMessages.js'
12
+ import { type Translator } from '../i18n/types.js'
10
13
  import { messageTemplate } from '../pageControllers/validationOptions.js'
11
14
  import { type ErrorMessageTemplateList } from '../types.js'
12
15
  import { convertToLanguageMessages } from '../../../utils/type-utils.js'
@@ -52,6 +55,13 @@ export class YesNoField extends SelectionControlField {
52
55
  this.options = options
53
56
  }
54
57
 
58
+ getValidationMessagesOverride(translator: Translator): LanguageMessages {
59
+ const { selectYesNoRequired } = buildValidationMessages(translator.t)
60
+ return convertToLanguageMessages({
61
+ 'any.required': selectYesNoRequired
62
+ })
63
+ }
64
+
55
65
  /**
56
66
  * For error preview page that shows all possible errors on a component
57
67
  */
@@ -6,6 +6,7 @@ import { ListFormComponent } from '../ListFormComponent.js'
6
6
  import { escapeMarkdown } from './index.js'
7
7
  import * as Components from '../index.js'
8
8
  import { markdown } from '../markdownParser.js'
9
+ import { type Translator } from '../../i18n/types.js'
9
10
  import { type FormState } from '../../types.js'
10
11
 
11
12
  // All component instances
@@ -221,6 +222,7 @@ export function createComponent(
221
222
  export function getAnswer(
222
223
  field: Field,
223
224
  state: FormState,
225
+ translator: Translator,
224
226
  options: {
225
227
  format:
226
228
  | 'data' // Submission data
@@ -230,7 +232,7 @@ export function getAnswer(
230
232
  ) {
231
233
  // Use escaped display text for GOV.UK Notify emails
232
234
  if (options.format === 'email') {
233
- return getAnswerMarkdown(field, state, { format: 'email' })
235
+ return getAnswerMarkdown(field, state, translator, { format: 'email' })
234
236
  }
235
237
 
236
238
  // Use context value for submission data
@@ -248,12 +250,15 @@ export function getAnswer(
248
250
  field instanceof Components.LatLongField
249
251
  ) {
250
252
  return markdown
251
- .parse(getAnswerMarkdown(field, state), { async: false })
253
+ .parse(
254
+ getAnswerMarkdown(field, state, translator, { format: 'summary' }),
255
+ { async: false }
256
+ )
252
257
  .trim()
253
258
  }
254
259
 
255
260
  // Use display text for check answers summary (single line)
256
- return field.getDisplayStringFromState(state)
261
+ return field.getDisplayStringFromState(state, translator)
257
262
  }
258
263
 
259
264
  /**
@@ -262,13 +267,14 @@ export function getAnswer(
262
267
  export function getAnswerMarkdown(
263
268
  field: Field,
264
269
  state: FormState,
270
+ translator: Translator,
265
271
  options: {
266
272
  format:
267
273
  | 'email' // GOV.UK Notify emails
268
274
  | 'summary' // Check answers summary
269
275
  } = { format: 'summary' }
270
276
  ) {
271
- const answer = field.getDisplayStringFromState(state)
277
+ const answer = field.getDisplayStringFromState(state, translator)
272
278
 
273
279
  // Use escaped display text
274
280
  let answerEscaped = `${escapeMarkdown(answer)}\n`
@@ -305,7 +311,8 @@ export function getAnswerMarkdown(
305
311
  // Append bullet points
306
312
  answerEscaped += items
307
313
  .map((item) => {
308
- const label = escapeMarkdown(item.text)
314
+ const resolvedText = translator.tListItem(item, 'text') || item.text
315
+ const label = escapeMarkdown(resolvedText)
309
316
  const value = escapeMarkdown(`(${item.value})`)
310
317
 
311
318
  let line = label
@@ -344,7 +351,7 @@ export function getAnswerMarkdown(
344
351
  answerEscaped = contextValue ? `${contextValue}\n` : ''
345
352
  } else if (field instanceof Components.GeospatialField) {
346
353
  const features = field.getFormValueFromState(state)
347
- const value = field.getDisplayStringFromFormValue(features)
354
+ const value = field.getDisplayStringFromFormValue(features, translator)
348
355
 
349
356
  answerEscaped = value ? `${value}\n` : ''
350
357
  }
@@ -1,11 +1,20 @@
1
1
  import { type ComponentType, type Item } from '@defra/forms-model'
2
2
 
3
+ import { type Translator } from '../i18n/types.js'
3
4
  import {
5
+ type FormPayload,
4
6
  type FormSubmissionError,
5
7
  type FormValue,
6
8
  type SummaryList
7
9
  } from '../types.js'
8
10
 
11
+ export interface RenderContext {
12
+ payload: FormPayload
13
+ errors: FormSubmissionError[] | undefined
14
+ translator: Translator
15
+ isForceAccess?: boolean
16
+ }
17
+
9
18
  export type ComponentText = {
10
19
  classes?: string
11
20
  attributes?: string | Record<string, string>
@@ -1,6 +1,8 @@
1
1
  import { join, parse } from 'node:path'
2
2
 
3
3
  import { type FormDefinition } from '@defra/forms-model'
4
+ import { type RequestQuery } from '@hapi/hapi'
5
+ import { type Yar } from '@hapi/yar'
4
6
 
5
7
  import { FORM_PREFIX } from '../../constants.js'
6
8
  import { FormModel } from './models/FormModel.js'
@@ -67,7 +69,16 @@ export const configureEnginePlugin = async (
67
69
  baseUrl: 'http://localhost:3009', // always runs locally
68
70
  saveAndExit,
69
71
  ordnanceSurveyApiKey,
70
- ordnanceSurveyApiSecret
72
+ ordnanceSurveyApiSecret,
73
+ getLanguage: (query: RequestQuery = {}, yar?: Yar) => {
74
+ const defaultLang = 'en-GB'
75
+
76
+ if (yar && 'language' in query) {
77
+ yar.set('language', query.language)
78
+ }
79
+
80
+ return yar?.get('language') ?? defaultLang
81
+ }
71
82
  }
72
83
  }
73
84
  }
@@ -13,6 +13,7 @@ import { type PageController } from './pageControllers/PageController.js'
13
13
  import { TerminalPageController } from './pageControllers/index.js'
14
14
  import * as defaultServices from './services/index.js'
15
15
  import {
16
+ type AnyFormRequest,
16
17
  type AnyRequest,
17
18
  type FormContext,
18
19
  type FormContextRequest,
@@ -21,6 +22,7 @@ import {
21
22
  } from './types.js'
22
23
  import { FormStatus } from '../../routes/types.js'
23
24
  import { type Services } from '../../types.js'
25
+ import { resolveLanguage } from '../../utils/utils.js'
24
26
 
25
27
  type JourneyState = FormStatus | 'preview'
26
28
 
@@ -36,6 +38,7 @@ export interface FormModelOptions {
36
38
 
37
39
  export interface FormContextOptions extends FormModelOptions {
38
40
  errors?: FormSubmissionError[]
41
+ language?: string
39
42
  }
40
43
 
41
44
  type SummaryRequest = FormContextRequest & {
@@ -119,10 +122,14 @@ export async function getFormContext(
119
122
  $$__referenceNumber: cachedState.$$__referenceNumber
120
123
  } as unknown as FormSubmissionState
121
124
 
125
+ const language = resolveLanguage(summaryRequest as unknown as AnyFormRequest)
126
+ const translator = formModel.createTranslator(language)
127
+
122
128
  return formModel.getFormContext(
123
129
  summaryRequest,
124
130
  formState,
125
- options.errors ?? []
131
+ options.errors ?? [],
132
+ translator
126
133
  )
127
134
  }
128
135
 
@@ -148,7 +155,6 @@ export async function resolveFormModel(
148
155
  }
149
156
 
150
157
  // The models cache is created lazily per server instance
151
-
152
158
  server.app.models ??= new Map<string, { model: FormModel; updatedAt: Date }>()
153
159
 
154
160
  const cache = server.app.models
@@ -2,8 +2,6 @@ import {
2
2
  ControllerPath,
3
3
  Engine,
4
4
  getErrorMessage,
5
- hasComponents,
6
- isFormType,
7
5
  type ComponentDef,
8
6
  type FormDefinition,
9
7
  type Page
@@ -109,7 +107,16 @@ engine.registerFilter('answer', function (name: string) {
109
107
  return
110
108
  }
111
109
 
112
- const answer = getAnswer(component as Field, globals.context.relevantState)
110
+ const field = component as Field
111
+ const { translator } = globals.context
112
+ const answer = getAnswer(
113
+ field,
114
+ globals.context.relevantState,
115
+ translator ?? field.model.createTranslator(),
116
+ {
117
+ format: 'summary'
118
+ }
119
+ )
113
120
 
114
121
  return answer
115
122
  })
@@ -403,18 +410,3 @@ export function getFormVersion(
403
410
  | FormVersionMetadata
404
411
  | undefined
405
412
  }
406
-
407
- export function setPageTitles(def: FormDefinition) {
408
- def.pages.forEach((page) => {
409
- if (!page.title) {
410
- if (hasComponents(page)) {
411
- // Set the page title from the first form component
412
- const firstFormComponent = page.components.find((component) =>
413
- isFormType(component.type)
414
- )
415
-
416
- page.title = firstFormComponent?.title ?? ''
417
- }
418
- }
419
- })
420
- }
@@ -0,0 +1,69 @@
1
+ import joi, { type JoiExpression, type ReferenceOptions } from 'joi'
2
+
3
+ import { lowerFirstPreserveProperNouns } from '../components/helpers/index.js'
4
+
5
+ const opts = {
6
+ functions: {
7
+ lowerFirst: lowerFirstPreserveProperNouns
8
+ }
9
+ } as ReferenceOptions
10
+
11
+ /**
12
+ * Builds a set of translated Joi message templates for a given language.
13
+ * Called once per FormModel at construction time.
14
+ *
15
+ * Strings containing {{lowerFirst(#label)}} must be wrapped in joi.expression()
16
+ * because Joi cannot resolve custom functions from a plain string.
17
+ * Plain strings with only built-in tokens ({{#label}}, {{#limit}}) work without wrapping.
18
+ */
19
+ export function buildValidationMessages(t: (key: string) => string) {
20
+ return {
21
+ // Expressions — contain lowerFirst custom function
22
+ required: joi.expression(t('validation.required'), opts) as JoiExpression,
23
+ selectRequired: joi.expression(
24
+ t('validation.selectRequired'),
25
+ opts
26
+ ) as JoiExpression,
27
+ pattern: joi.expression(t('validation.pattern'), opts) as JoiExpression,
28
+ format: joi.expression(t('validation.format'), opts) as JoiExpression,
29
+ declarationRequired: joi.expression(
30
+ t('components.declarationField.validation.declarationRequired'),
31
+ opts
32
+ ) as JoiExpression,
33
+ objectRequired: joi.expression(
34
+ t('validation.objectRequired'),
35
+ opts
36
+ ) as JoiExpression,
37
+ objectMissing: joi.expression(
38
+ t('validation.objectMissing'),
39
+ opts
40
+ ) as JoiExpression,
41
+
42
+ // Plain Joi template strings — no lowerFirst
43
+ selectYesNoRequired: t(
44
+ 'components.yesNoField.validation.selectYesNoRequired'
45
+ ),
46
+ max: t('validation.max'),
47
+ min: t('validation.min'),
48
+ minMax: t('validation.minMax'),
49
+ number: t('validation.number'),
50
+ numberPrecision: t('validation.numberPrecision'),
51
+ numberInteger: t('validation.numberInteger'),
52
+ numberMin: t('validation.numberMin'),
53
+ numberMax: t('validation.numberMax'),
54
+ maxWords: t('validation.maxWords'),
55
+ dateFormat: t('components.dateField.validation.dateFormat'),
56
+ dateMin: t('components.dateField.validation.dateMin'),
57
+ dateMax: t('components.dateField.validation.dateMax'),
58
+
59
+ unicode: 'validation.unicode',
60
+ arrayMin: 'validation.array.min',
61
+ arrayMax: 'validation.array.max',
62
+ arrayLength: 'validation.array.length',
63
+ featuresMin: 'validation.features.min',
64
+ featuresMax: 'validation.features.max',
65
+ featuresLength: 'validation.features.length'
66
+ }
67
+ }
68
+
69
+ export type ValidationMessages = ReturnType<typeof buildValidationMessages>