@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
@@ -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>
@@ -0,0 +1,33 @@
1
+ import { type FormDefinition } from '@defra/forms-model'
2
+ import { type i18n } from 'i18next'
3
+
4
+ import { createTranslator } from './createTranslator.js'
5
+ import { extractBaseTranslations } from './extractBaseTranslations.js'
6
+ import { createFormI18nInstance } from './index.js'
7
+ import {
8
+ type FormDefinitionTranslations,
9
+ type Translator
10
+ } from './types.js'
11
+
12
+ export function createFormTranslator(
13
+ def: FormDefinition,
14
+ language: string
15
+ ): Translator {
16
+ const baseTranslations = extractBaseTranslations(def)
17
+ const i18nInstance = createFormI18nInstance(baseTranslations)
18
+ loadFormTranslations(def, i18nInstance)
19
+
20
+ return createTranslator(i18nInstance, language)
21
+ }
22
+
23
+ export function loadFormTranslations(def: FormDefinition, i18nInstance: i18n) {
24
+ const formTranslations = def.metadata?.translations as
25
+ | FormDefinitionTranslations
26
+ | undefined
27
+
28
+ if (formTranslations) {
29
+ for (const [lng, resources] of Object.entries(formTranslations)) {
30
+ i18nInstance.addResourceBundle(lng, 'form', resources, true, true)
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,61 @@
1
+ import { type i18n } from 'i18next'
2
+
3
+ import { type Translator } from './types.js'
4
+
5
+ /**
6
+ * Creates a translator for the specified language, decorated with utility functions
7
+ * for translating specific parts of the form definition.
8
+ * @param i18nInstance - instance of i18next which gets created on startup (reads the boilerplate files en-GB.json and cy.json)
9
+ * @param language - requested language
10
+ */
11
+ export function createTranslator(
12
+ i18nInstance: i18n,
13
+ language = 'en-GB'
14
+ ): Translator {
15
+ const t = (key: string, opts?: Record<string, unknown>): string =>
16
+ i18nInstance.t(key, { lng: language, ns: 'plugin', ...opts })
17
+
18
+ const resolveContent = (
19
+ entity: { id?: string },
20
+ entityType: string,
21
+ prop: string
22
+ ): string => {
23
+ if (!entity.id) {
24
+ const raw = (entity as Record<string, unknown>)[prop]
25
+ if (typeof raw !== 'string') return ''
26
+ // t() resolves i18next key constants (sub-field labels); returns raw string unchanged if not a key
27
+ return t(raw)
28
+ }
29
+ const key = `${entityType}.${entity.id}.${prop}`
30
+ const result = i18nInstance.t(key, {
31
+ lng: language,
32
+ ns: 'form'
33
+ })
34
+ if (result === key) {
35
+ // No form translation found — fall through to t(raw) so plugin i18n
36
+ // key constants (e.g. 'components.yesNoField.yes') are still resolved.
37
+ const raw = (entity as Record<string, unknown>)[prop]
38
+ if (typeof raw !== 'string') return ''
39
+ return t(raw)
40
+ }
41
+ return result
42
+ }
43
+
44
+ const resolveFormContent = (prop: string) => {
45
+ const key = `form.${prop}`
46
+ return i18nInstance.t(key, { lng: language, ns: 'form' })
47
+ }
48
+
49
+ return {
50
+ t,
51
+ tForm: (prop) => resolveFormContent(prop),
52
+ tPage: (entity, prop) => resolveContent(entity, 'pages', prop as string),
53
+ tComponent: (entity, prop) =>
54
+ resolveContent(entity, 'components', prop as string),
55
+ tSection: (entity, prop) =>
56
+ resolveContent(entity, 'sections', prop as string),
57
+ tListItem: (entity, prop) =>
58
+ resolveContent(entity, 'listItems', prop as string),
59
+ language
60
+ }
61
+ }
@@ -0,0 +1,61 @@
1
+ import {
2
+ hasComponentsEvenIfNoNext,
3
+ type FormDefinition
4
+ } from '@defra/forms-model'
5
+
6
+ import { type FormDefinitionTranslations } from './types.js'
7
+
8
+ type BaseTranslations = FormDefinitionTranslations[string]
9
+
10
+ export function extractBaseTranslations(def: FormDefinition): BaseTranslations {
11
+ const pages: BaseTranslations['pages'] = {}
12
+ const components: BaseTranslations['components'] = {}
13
+ const sections: BaseTranslations['sections'] = {}
14
+ const listItems: BaseTranslations['listItems'] = {}
15
+ const form: BaseTranslations['form'] = {}
16
+
17
+ for (const page of def.pages) {
18
+ if (page.id && page.title) {
19
+ pages[page.id] = { title: page.title }
20
+ }
21
+
22
+ if (hasComponentsEvenIfNoNext(page)) {
23
+ for (const component of page.components) {
24
+ if (!component.id) continue
25
+
26
+ const entry: BaseTranslations['components'][string] = {}
27
+
28
+ if (component.title) entry.title = component.title
29
+ if ('hint' in component && component.hint) entry.hint = component.hint
30
+ if ('content' in component && component.content) {
31
+ entry.content = component.content
32
+ }
33
+ if ('shortDescription' in component && component.shortDescription) {
34
+ entry.shortDescription = component.shortDescription
35
+ }
36
+
37
+ if (Object.keys(entry).length) {
38
+ components[component.id] = entry
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ for (const section of def.sections) {
45
+ if (section.id && section.title) {
46
+ sections[section.id] = { title: section.title }
47
+ }
48
+ }
49
+
50
+ for (const list of def.lists) {
51
+ for (const item of list.items) {
52
+ if (item.id && item.text) {
53
+ listItems[item.id] = { text: item.text }
54
+ }
55
+ }
56
+ }
57
+
58
+ form.title = def.name ?? ''
59
+
60
+ return { pages, components, sections, listItems, form }
61
+ }