@defra/forms-engine-plugin 5.0.0-alpha.7 → 5.0.0-alpha.9
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.
- package/.public/javascripts/application.min.js.map +1 -1
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/javascripts/maps.d.ts +5 -0
- package/.server/client/javascripts/maps.js +6 -0
- package/.server/client/javascripts/maps.js.map +1 -0
- package/.server/client/javascripts/shared.d.ts +1 -4
- package/.server/client/javascripts/shared.js +1 -4
- package/.server/client/javascripts/shared.js.map +1 -1
- package/.server/client/stylesheets/maps.scss +4 -0
- package/.server/client/stylesheets/shared.scss +1 -5
- package/.server/server/constants.d.ts +1 -0
- package/.server/server/constants.js +2 -1
- package/.server/server/constants.js.map +1 -1
- package/.server/server/plugins/engine/components/ComponentCollection.js +10 -6
- package/.server/server/plugins/engine/components/ComponentCollection.js.map +1 -1
- package/.server/server/plugins/engine/components/DatePartsField.d.ts +4 -2
- package/.server/server/plugins/engine/components/DatePartsField.js +14 -11
- package/.server/server/plugins/engine/components/DatePartsField.js.map +1 -1
- package/.server/server/plugins/engine/components/DeclarationField.d.ts +4 -2
- package/.server/server/plugins/engine/components/DeclarationField.js +5 -3
- package/.server/server/plugins/engine/components/DeclarationField.js.map +1 -1
- package/.server/server/plugins/engine/components/EastingNorthingField.d.ts +24 -2
- package/.server/server/plugins/engine/components/EastingNorthingField.js +93 -52
- package/.server/server/plugins/engine/components/EastingNorthingField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.d.ts +4 -1
- package/.server/server/plugins/engine/components/FormComponent.js +3 -0
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/LatLongField.d.ts +24 -2
- package/.server/server/plugins/engine/components/LatLongField.js +85 -46
- package/.server/server/plugins/engine/components/LatLongField.js.map +1 -1
- package/.server/server/plugins/engine/components/LocationFieldHelpers.d.ts +3 -2
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js +10 -11
- package/.server/server/plugins/engine/components/LocationFieldHelpers.js.map +1 -1
- package/.server/server/plugins/engine/components/MonthYearField.d.ts +4 -2
- package/.server/server/plugins/engine/components/MonthYearField.js +16 -13
- package/.server/server/plugins/engine/components/MonthYearField.js.map +1 -1
- package/.server/server/plugins/engine/components/PaymentField.d.ts +1 -0
- package/.server/server/plugins/engine/components/PaymentField.js +9 -5
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js +22 -7
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/YesNoField.d.ts +3 -2
- package/.server/server/plugins/engine/components/YesNoField.js +5 -3
- package/.server/server/plugins/engine/components/YesNoField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/components.js +1 -2
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/index.d.ts +7 -0
- package/.server/server/plugins/engine/components/helpers/index.js +13 -1
- package/.server/server/plugins/engine/components/helpers/index.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +21 -2
- package/.server/server/plugins/engine/helpers.js +43 -5
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/i18n/createFormTranslator.d.ts +4 -4
- package/.server/server/plugins/engine/i18n/createFormTranslator.js +27 -5
- package/.server/server/plugins/engine/i18n/createFormTranslator.js.map +1 -1
- package/.server/server/plugins/engine/i18n/extractBaseTranslations.d.ts +1 -1
- package/.server/server/plugins/engine/i18n/extractBaseTranslations.js +9 -0
- package/.server/server/plugins/engine/i18n/extractBaseTranslations.js.map +1 -1
- package/.server/server/plugins/engine/i18n/form.d.ts +13 -0
- package/.server/server/plugins/engine/i18n/form.js +35 -0
- package/.server/server/plugins/engine/i18n/form.js.map +1 -0
- package/.server/server/plugins/engine/i18n/languages.d.ts +10 -0
- package/.server/server/plugins/engine/i18n/languages.js +19 -0
- package/.server/server/plugins/engine/i18n/languages.js.map +1 -0
- package/.server/server/plugins/engine/i18n/translations/cy.json +3 -1
- package/.server/server/plugins/engine/i18n/translations/en-GB.json +3 -1
- package/.server/server/plugins/engine/index.d.ts +3 -0
- package/.server/server/plugins/engine/index.js +3 -0
- package/.server/server/plugins/engine/index.js.map +1 -1
- package/.server/server/plugins/engine/models/FormModel.d.ts +0 -4
- package/.server/server/plugins/engine/models/FormModel.js +9 -15
- package/.server/server/plugins/engine/models/FormModel.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js +2 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +2 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +2 -0
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js +2 -0
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +2 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +6 -3
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js +3 -3
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +1 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +1 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/ukaddressfield.html +1 -1
- package/.server/server/plugins/nunjucks/filters/applyUrlParam.d.ts +9 -0
- package/.server/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
- package/.server/server/plugins/nunjucks/filters/applyUrlParam.js.map +1 -0
- package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js +17 -0
- package/.server/server/plugins/nunjucks/filters/applyUrlParam.test.js.map +1 -0
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/nunjucks/filters/index.d.ts +1 -0
- package/.server/server/plugins/nunjucks/filters/index.js +1 -0
- package/.server/server/plugins/nunjucks/filters/index.js.map +1 -1
- package/.server/server/plugins/payment/helper.d.ts +11 -1
- package/.server/server/plugins/payment/helper.js +18 -2
- package/.server/server/plugins/payment/helper.js.map +1 -1
- package/.server/server/plugins/payment/helper.test.js +2 -1
- package/.server/server/plugins/payment/helper.test.js.map +1 -1
- package/.server/server/plugins/payment/service.d.ts +2 -1
- package/.server/server/plugins/payment/service.js +5 -2
- package/.server/server/plugins/payment/service.js.map +1 -1
- package/.server/server/plugins/payment/service.test.js +7 -3
- package/.server/server/plugins/payment/service.test.js.map +1 -1
- package/.server/server/plugins/payment/types.d.ts +4 -0
- package/.server/server/plugins/payment/types.js +1 -0
- package/.server/server/plugins/payment/types.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/models/index.d.ts +247 -3
- package/.server/server/plugins/postcode-lookup/models/index.js +86 -33
- package/.server/server/plugins/postcode-lookup/models/index.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/routes/index.d.ts +15 -1
- package/.server/server/plugins/postcode-lookup/routes/index.js +47 -15
- package/.server/server/plugins/postcode-lookup/routes/index.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/routes/index.test.js +84 -0
- package/.server/server/plugins/postcode-lookup/routes/index.test.js.map +1 -0
- package/.server/server/plugins/postcode-lookup/types.d.ts +12 -0
- package/.server/server/plugins/postcode-lookup/types.js +6 -1
- package/.server/server/plugins/postcode-lookup/types.js.map +1 -1
- package/.server/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
- package/package.json +7 -1
- package/src/client/javascripts/maps.js +5 -0
- package/src/client/javascripts/shared.js +1 -4
- package/src/client/stylesheets/maps.scss +4 -0
- package/src/client/stylesheets/shared.scss +1 -5
- package/src/server/constants.js +2 -1
- package/src/server/plugins/engine/components/ComponentCollection.ts +15 -9
- package/src/server/plugins/engine/components/DatePartsField.ts +16 -12
- package/src/server/plugins/engine/components/DeclarationField.ts +6 -4
- package/src/server/plugins/engine/components/EastingNorthingField.ts +156 -88
- package/src/server/plugins/engine/components/FormComponent.ts +4 -1
- package/src/server/plugins/engine/components/LatLongField.ts +129 -57
- package/src/server/plugins/engine/components/LocationFieldHelpers.ts +14 -11
- package/src/server/plugins/engine/components/MonthYearField.ts +16 -11
- package/src/server/plugins/engine/components/PaymentField.ts +9 -4
- package/src/server/plugins/engine/components/UkAddressField.ts +29 -6
- package/src/server/plugins/engine/components/YesNoField.ts +6 -5
- package/src/server/plugins/engine/components/helpers/components.ts +1 -2
- package/src/server/plugins/engine/components/helpers/index.ts +19 -0
- package/src/server/plugins/engine/helpers.ts +51 -5
- package/src/server/plugins/engine/i18n/createFormTranslator.ts +43 -12
- package/src/server/plugins/engine/i18n/extractBaseTranslations.ts +7 -1
- package/src/server/plugins/engine/i18n/form.ts +54 -0
- package/src/server/plugins/engine/i18n/languages.ts +19 -0
- package/src/server/plugins/engine/i18n/translations/cy.json +3 -1
- package/src/server/plugins/engine/i18n/translations/en-GB.json +3 -1
- package/src/server/plugins/engine/index.ts +6 -0
- package/src/server/plugins/engine/models/FormModel.ts +9 -15
- package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -1
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +2 -1
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +3 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +2 -2
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +2 -2
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +9 -3
- package/src/server/plugins/engine/pageControllers/helpers/submission.ts +6 -3
- package/src/server/plugins/engine/types/schema.ts +1 -0
- package/src/server/plugins/engine/types.ts +1 -0
- package/src/server/plugins/engine/views/components/ukaddressfield.html +1 -1
- package/src/server/plugins/nunjucks/filters/applyUrlParam.js +17 -0
- package/src/server/plugins/nunjucks/filters/index.js +1 -0
- package/src/server/plugins/payment/helper.js +22 -2
- package/src/server/plugins/payment/service.js +6 -2
- package/src/server/plugins/payment/types.js +1 -0
- package/src/server/plugins/postcode-lookup/models/index.js +75 -43
- package/src/server/plugins/postcode-lookup/routes/index.js +73 -16
- package/src/server/plugins/postcode-lookup/types.js +6 -1
- package/src/server/plugins/postcode-lookup/views/postcode-lookup-details.html +3 -3
|
@@ -63,6 +63,7 @@ export class PaymentField extends FormComponent {
|
|
|
63
63
|
reference: joi.string().required(),
|
|
64
64
|
amount: joi.number().required(),
|
|
65
65
|
description: joi.string().required(),
|
|
66
|
+
descriptionInEng: joi.string().optional(),
|
|
66
67
|
uuid: joi.string().uuid().required(),
|
|
67
68
|
formId: joi.string().required(),
|
|
68
69
|
isLivePayment: joi.boolean().required(),
|
|
@@ -239,8 +240,9 @@ export class PaymentField extends FormComponent {
|
|
|
239
240
|
): Promise<unknown> {
|
|
240
241
|
const { options, name: componentName } = args.component
|
|
241
242
|
const { model } = args.controller
|
|
242
|
-
const language =
|
|
243
|
-
|
|
243
|
+
const { language, t, tComponent } = model.createTranslator(
|
|
244
|
+
resolveLanguage(request)
|
|
245
|
+
)
|
|
244
246
|
const state = await args.controller.getState(request)
|
|
245
247
|
const { baseUrl } = getPluginOptions(request.server)
|
|
246
248
|
const summaryUrl = `${baseUrl}/${model.basePath}/summary`
|
|
@@ -272,7 +274,8 @@ export class PaymentField extends FormComponent {
|
|
|
272
274
|
return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)
|
|
273
275
|
}
|
|
274
276
|
|
|
275
|
-
const description =
|
|
277
|
+
const description =
|
|
278
|
+
tComponent(args.component, 'paymentDescription') || options.description
|
|
276
279
|
|
|
277
280
|
const slug = `/${model.basePath}`
|
|
278
281
|
|
|
@@ -292,7 +295,7 @@ export class PaymentField extends FormComponent {
|
|
|
292
295
|
payCallbackUrl,
|
|
293
296
|
reference,
|
|
294
297
|
isLivePayment,
|
|
295
|
-
{ formId, slug },
|
|
298
|
+
{ formId, slug, language },
|
|
296
299
|
prefilledEmail
|
|
297
300
|
)
|
|
298
301
|
|
|
@@ -313,6 +316,7 @@ export class PaymentField extends FormComponent {
|
|
|
313
316
|
reference,
|
|
314
317
|
amount: resolvedAmount,
|
|
315
318
|
description,
|
|
319
|
+
descriptionInEng: options.description,
|
|
316
320
|
paymentId: payment.paymentId,
|
|
317
321
|
componentName,
|
|
318
322
|
returnUrl: summaryUrl,
|
|
@@ -449,6 +453,7 @@ export interface PaymentSessionData {
|
|
|
449
453
|
reference: string
|
|
450
454
|
amount: number
|
|
451
455
|
description: string
|
|
456
|
+
descriptionInEng?: string
|
|
452
457
|
paymentId: string
|
|
453
458
|
componentName: string
|
|
454
459
|
returnUrl: string
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ComponentType,
|
|
3
|
+
FormStatus,
|
|
4
|
+
type FormMetadata,
|
|
5
|
+
type UkAddressFieldComponent
|
|
6
|
+
} from '@defra/forms-model'
|
|
2
7
|
import { type ObjectSchema } from 'joi'
|
|
3
8
|
import lowerFirst from 'lodash/lowerFirst.js'
|
|
4
9
|
|
|
@@ -9,6 +14,8 @@ import {
|
|
|
9
14
|
} from './FormComponent.js'
|
|
10
15
|
import { TextField } from './TextField.js'
|
|
11
16
|
import { type RenderContext } from './types.js'
|
|
17
|
+
import { getCachedPluginTranslator } from '../i18n/form.js'
|
|
18
|
+
import { getAvailableLanguages } from '../i18n/languages.js'
|
|
12
19
|
import { type Translator } from '../i18n/types.js'
|
|
13
20
|
import {
|
|
14
21
|
type FormRequestPayload,
|
|
@@ -290,7 +297,6 @@ export class UkAddressField extends FormComponent {
|
|
|
290
297
|
)
|
|
291
298
|
}
|
|
292
299
|
|
|
293
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
294
300
|
static async dispatcher(
|
|
295
301
|
request: FormRequestPayload,
|
|
296
302
|
h: FormResponseToolkit,
|
|
@@ -299,14 +305,31 @@ export class UkAddressField extends FormComponent {
|
|
|
299
305
|
const { controller, component } = args
|
|
300
306
|
const language = resolveLanguage(request)
|
|
301
307
|
|
|
308
|
+
const availableLanguages = getAvailableLanguages(controller.model.def)
|
|
309
|
+
const languages = availableLanguages.length
|
|
310
|
+
? { languages: availableLanguages }
|
|
311
|
+
: {}
|
|
312
|
+
|
|
313
|
+
const translator = await getCachedPluginTranslator(
|
|
314
|
+
request,
|
|
315
|
+
{ id: controller.model.formId } as unknown as FormMetadata,
|
|
316
|
+
args.isLive ? FormStatus.Live : FormStatus.Draft,
|
|
317
|
+
language
|
|
318
|
+
)
|
|
319
|
+
const { tComponent, tForm } = translator
|
|
320
|
+
|
|
302
321
|
return dispatch(request, h, {
|
|
303
|
-
formName:
|
|
322
|
+
formName: tForm('title'),
|
|
323
|
+
formId: controller.model.formId,
|
|
324
|
+
formStatus: args.isLive ? FormStatus.Live : FormStatus.Draft,
|
|
325
|
+
componentId: component.id ?? 'unknown',
|
|
304
326
|
componentName: component.name,
|
|
305
|
-
componentHint: component
|
|
306
|
-
componentTitle: component
|
|
327
|
+
componentHint: tComponent(component, 'hint'),
|
|
328
|
+
componentTitle: tComponent(component, 'title') || controller.title,
|
|
307
329
|
step: args.actionArgs.step,
|
|
308
330
|
sourceUrl: args.sourceUrl,
|
|
309
|
-
language
|
|
331
|
+
language,
|
|
332
|
+
...languages
|
|
310
333
|
})
|
|
311
334
|
}
|
|
312
335
|
}
|
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
yesNoListName,
|
|
5
5
|
type YesNoFieldComponent
|
|
6
6
|
} from '@defra/forms-model'
|
|
7
|
-
import { type LanguageMessages } from 'joi'
|
|
8
7
|
|
|
9
8
|
import { SelectionControlField } from './SelectionControlField.js'
|
|
10
9
|
import { addClassOptionIfNone } from './helpers/index.js'
|
|
@@ -55,11 +54,13 @@ export class YesNoField extends SelectionControlField {
|
|
|
55
54
|
this.options = options
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
getValidationMessagesOverride(translator: Translator)
|
|
57
|
+
getValidationMessagesOverride(translator: Translator) {
|
|
59
58
|
const { selectYesNoRequired } = buildValidationMessages(translator.t)
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
return {
|
|
60
|
+
[this.name]: convertToLanguageMessages({
|
|
61
|
+
'any.required': selectYesNoRequired
|
|
62
|
+
})
|
|
63
|
+
}
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
@@ -347,8 +347,7 @@ export function getAnswerMarkdown(
|
|
|
347
347
|
field instanceof Components.EastingNorthingField ||
|
|
348
348
|
field instanceof Components.LatLongField
|
|
349
349
|
) {
|
|
350
|
-
|
|
351
|
-
answerEscaped = contextValue ? `${contextValue}\n` : ''
|
|
350
|
+
answerEscaped = answer ? `${answer}\n` : ''
|
|
352
351
|
} else if (field instanceof Components.GeospatialField) {
|
|
353
352
|
const features = field.getFormValueFromState(state)
|
|
354
353
|
const value = field.getDisplayStringFromFormValue(features, translator)
|
|
@@ -2,6 +2,8 @@ import { type ComponentDef } from '@defra/forms-model'
|
|
|
2
2
|
import joi, { type JoiExpression, type ReferenceOptions } from 'joi'
|
|
3
3
|
import lowerFirst from 'lodash/lowerFirst.js'
|
|
4
4
|
|
|
5
|
+
import { type Translator } from '../../i18n/types.js'
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Prevent Markdown formatting
|
|
7
9
|
* @see {@link https://pandoc.org/chunkedhtml-demo/8.11-backslash-escapes.html}
|
|
@@ -66,3 +68,20 @@ export const lowerFirstExpressionOptions = {
|
|
|
66
68
|
*/
|
|
67
69
|
export const createLowerFirstExpression = (template: string): JoiExpression =>
|
|
68
70
|
joi.expression(template, lowerFirstExpressionOptions) as JoiExpression
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Translate a component label, in precedence order of errorDescription, then shortDesciption, then title
|
|
74
|
+
* @param {ComponentDef} component
|
|
75
|
+
* @param {Translator} translator
|
|
76
|
+
*/
|
|
77
|
+
export function getTranslatedLabel(
|
|
78
|
+
component: ComponentDef,
|
|
79
|
+
translator: Translator
|
|
80
|
+
) {
|
|
81
|
+
const { tComponent } = translator
|
|
82
|
+
const translatedLabel =
|
|
83
|
+
tComponent(component, 'errorDescription') ||
|
|
84
|
+
tComponent(component, 'shortDescription') ||
|
|
85
|
+
tComponent(component, 'title')
|
|
86
|
+
return translatedLabel
|
|
87
|
+
}
|
|
@@ -8,13 +8,14 @@ import {
|
|
|
8
8
|
} from '@defra/forms-model'
|
|
9
9
|
import Boom from '@hapi/boom'
|
|
10
10
|
import { type ResponseToolkit, type Server } from '@hapi/hapi'
|
|
11
|
-
import { format, parseISO } from 'date-fns'
|
|
11
|
+
import { format, parseISO, type Locale } from 'date-fns'
|
|
12
|
+
import { cy, enGB } from 'date-fns/locale'
|
|
12
13
|
import { StatusCodes } from 'http-status-codes'
|
|
13
14
|
import { type Schema, type ValidationErrorItem } from 'joi'
|
|
14
15
|
import { Liquid } from 'liquidjs'
|
|
15
16
|
|
|
16
17
|
import { logger } from '../../common/helpers/logging/logger.js'
|
|
17
|
-
import { FORM_VERSION_METADATA_KEY } from '../../constants.js'
|
|
18
|
+
import { CY, EN_GB, FORM_VERSION_METADATA_KEY } from '../../constants.js'
|
|
18
19
|
import {
|
|
19
20
|
getAnswer,
|
|
20
21
|
type Field
|
|
@@ -303,16 +304,20 @@ export function checkEmailAddressForLiveFormSubmission(
|
|
|
303
304
|
* @param [details] - provided by {@link Schema.validate}
|
|
304
305
|
*/
|
|
305
306
|
export function getErrors(
|
|
307
|
+
language: string | undefined,
|
|
306
308
|
details?: ValidationErrorItem[]
|
|
307
309
|
): FormSubmissionError[] | undefined {
|
|
308
310
|
if (!details?.length) {
|
|
309
311
|
return
|
|
310
312
|
}
|
|
311
313
|
|
|
312
|
-
return details.map(getError)
|
|
314
|
+
return details.map((detail) => getError(language, detail))
|
|
313
315
|
}
|
|
314
316
|
|
|
315
|
-
export function getError(
|
|
317
|
+
export function getError(
|
|
318
|
+
language: string | undefined,
|
|
319
|
+
detail: ValidationErrorItem
|
|
320
|
+
): FormSubmissionError {
|
|
316
321
|
const { context, message, path } = detail
|
|
317
322
|
|
|
318
323
|
const name = context?.key ?? ''
|
|
@@ -320,7 +325,10 @@ export function getError(detail: ValidationErrorItem): FormSubmissionError {
|
|
|
320
325
|
|
|
321
326
|
const text = message.replace(
|
|
322
327
|
/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/,
|
|
323
|
-
(text) =>
|
|
328
|
+
(text) =>
|
|
329
|
+
format(parseISO(text), 'd MMMM yyyy', {
|
|
330
|
+
locale: toLanguageLocale(language)
|
|
331
|
+
})
|
|
324
332
|
)
|
|
325
333
|
|
|
326
334
|
return {
|
|
@@ -410,3 +418,41 @@ export function getFormVersion(
|
|
|
410
418
|
| FormVersionMetadata
|
|
411
419
|
| undefined
|
|
412
420
|
}
|
|
421
|
+
|
|
422
|
+
const payLanguageCodes = {
|
|
423
|
+
[EN_GB]: 'en',
|
|
424
|
+
[CY]: 'cy'
|
|
425
|
+
} as Record<string, string>
|
|
426
|
+
|
|
427
|
+
const languageLocales = {
|
|
428
|
+
[EN_GB]: enGB,
|
|
429
|
+
[CY]: cy
|
|
430
|
+
} as Record<string, Locale>
|
|
431
|
+
|
|
432
|
+
const defaultLanguageCode = 'en'
|
|
433
|
+
const defaultLanguageLocale = enGB
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Converts a BCP 47 language tag to the ISO 639-1 code GOV.UK Pay accepts.
|
|
437
|
+
* Defaults to English if the language is not supported.
|
|
438
|
+
* @param { string | undefined } language - BCP 47 language code
|
|
439
|
+
* @returns {string} language code required by GOV.UK Pay
|
|
440
|
+
*/
|
|
441
|
+
export function toPayLanguageCode(language: string | undefined): string {
|
|
442
|
+
return payLanguageCodes[language ?? 'unknown'] ?? defaultLanguageCode
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Converts a BCP 47 language tag to the date-fns Locale (for date formatting).
|
|
447
|
+
* Defaults to English if the language is not supported.
|
|
448
|
+
* @param { string | undefined } language - BCP 47 language code
|
|
449
|
+
* @returns {Locale} language locale
|
|
450
|
+
*/
|
|
451
|
+
export function toLanguageLocale(language: string | undefined): Locale {
|
|
452
|
+
return languageLocales[language ?? 'unknown'] ?? defaultLanguageLocale
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @import { FormsService } from '../../types.js'
|
|
457
|
+
* @import { Locale } from 'date-fns/locale'
|
|
458
|
+
*/
|
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
import {
|
|
2
2
|
yesNoListNoItemId,
|
|
3
3
|
yesNoListYesItemId,
|
|
4
|
-
type FormDefinition
|
|
4
|
+
type FormDefinition,
|
|
5
|
+
type FormMetadata
|
|
5
6
|
} from '@defra/forms-model'
|
|
6
7
|
import { type i18n } from 'i18next'
|
|
7
8
|
|
|
9
|
+
import { CY } from '../../../constants.js'
|
|
8
10
|
import { createTranslator } from './createTranslator.js'
|
|
9
11
|
import { extractBaseTranslations } from './extractBaseTranslations.js'
|
|
10
12
|
import { createFormI18nInstance } from './index.js'
|
|
11
|
-
import {
|
|
12
|
-
type FormDefinitionTranslations,
|
|
13
|
-
type Translator
|
|
14
|
-
} from './types.js'
|
|
13
|
+
import { type FormDefinitionTranslations } from './types.js'
|
|
15
14
|
|
|
16
15
|
export function createFormTranslator(
|
|
17
|
-
|
|
16
|
+
metadata: FormMetadata,
|
|
17
|
+
definition: FormDefinition | undefined,
|
|
18
18
|
language: string
|
|
19
|
-
)
|
|
20
|
-
const baseTranslations = extractBaseTranslations(
|
|
19
|
+
) {
|
|
20
|
+
const baseTranslations = extractBaseTranslations(definition)
|
|
21
21
|
const i18nInstance = createFormI18nInstance(baseTranslations)
|
|
22
|
-
loadFormTranslations(
|
|
22
|
+
loadFormTranslations(definition, i18nInstance)
|
|
23
|
+
extractMetadataBaseTranslations(metadata, i18nInstance)
|
|
24
|
+
|
|
25
|
+
const translator = createTranslator(i18nInstance, language)
|
|
23
26
|
|
|
24
|
-
return
|
|
27
|
+
return translator
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
export function loadFormTranslations(
|
|
30
|
+
export function loadFormTranslations(
|
|
31
|
+
def: FormDefinition | undefined,
|
|
32
|
+
i18nInstance: i18n
|
|
33
|
+
) {
|
|
34
|
+
if (!def) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
const formTranslations = def.metadata?.translations as
|
|
29
39
|
| FormDefinitionTranslations
|
|
30
40
|
| undefined
|
|
@@ -34,7 +44,7 @@ export function loadFormTranslations(def: FormDefinition, i18nInstance: i18n) {
|
|
|
34
44
|
i18nInstance.addResourceBundle(lng, 'form', resources, true, true)
|
|
35
45
|
|
|
36
46
|
// Temporary workaround - until we develop a better solution
|
|
37
|
-
if (lng ===
|
|
47
|
+
if (lng === CY) {
|
|
38
48
|
i18nInstance.addResource(
|
|
39
49
|
lng,
|
|
40
50
|
'form',
|
|
@@ -51,3 +61,24 @@ export function loadFormTranslations(def: FormDefinition, i18nInstance: i18n) {
|
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
}
|
|
64
|
+
|
|
65
|
+
export function extractMetadataBaseTranslations(
|
|
66
|
+
metadata: FormMetadata | undefined,
|
|
67
|
+
i18nInstance: i18n
|
|
68
|
+
) {
|
|
69
|
+
if (metadata) {
|
|
70
|
+
const translations = {
|
|
71
|
+
'form.title': metadata.title,
|
|
72
|
+
'form.contact.email.address': metadata.contact?.email?.address ?? '',
|
|
73
|
+
'form.contact.email.responseTime':
|
|
74
|
+
metadata.contact?.email?.responseTime ?? '',
|
|
75
|
+
'form.contact.online.url': metadata.contact?.online?.url ?? '',
|
|
76
|
+
'form.contact.online.text': metadata.contact?.online?.text ?? '',
|
|
77
|
+
'form.contact.phone': metadata.contact?.phone ?? '',
|
|
78
|
+
'form.submissionGuidance': metadata.submissionGuidance ?? '',
|
|
79
|
+
'form.privacyNoticeText': metadata.privacyNoticeText ?? '',
|
|
80
|
+
'form.privacyNoticeUrl': metadata.privacyNoticeUrl ?? ''
|
|
81
|
+
}
|
|
82
|
+
i18nInstance.addResourceBundle('en-GB', 'form', translations, true, true)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -7,13 +7,19 @@ import { type FormDefinitionTranslations } from './types.js'
|
|
|
7
7
|
|
|
8
8
|
type BaseTranslations = FormDefinitionTranslations[string]
|
|
9
9
|
|
|
10
|
-
export function extractBaseTranslations(
|
|
10
|
+
export function extractBaseTranslations(
|
|
11
|
+
def: FormDefinition | undefined
|
|
12
|
+
): BaseTranslations {
|
|
11
13
|
const pages: BaseTranslations['pages'] = {}
|
|
12
14
|
const components: BaseTranslations['components'] = {}
|
|
13
15
|
const sections: BaseTranslations['sections'] = {}
|
|
14
16
|
const listItems: BaseTranslations['listItems'] = {}
|
|
15
17
|
const form: BaseTranslations['form'] = {}
|
|
16
18
|
|
|
19
|
+
if (!def) {
|
|
20
|
+
return { pages, components, sections, listItems, form }
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
for (const page of def.pages) {
|
|
18
24
|
if (page.id && page.title) {
|
|
19
25
|
pages[page.id] = { title: page.title }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type FormMetadata, type FormStatus } from '@defra/forms-model'
|
|
2
|
+
import Boom from '@hapi/boom'
|
|
3
|
+
import { LRUCache } from 'lru-cache'
|
|
4
|
+
|
|
5
|
+
import { getPluginOptions } from '../helpers.js'
|
|
6
|
+
import { createFormTranslator } from './createFormTranslator.js'
|
|
7
|
+
import { type Translator } from './types.js'
|
|
8
|
+
import { type AnyFormRequest } from '../types.js'
|
|
9
|
+
|
|
10
|
+
const cache = new LRUCache({
|
|
11
|
+
max: 100,
|
|
12
|
+
ttl: 1000 * 60 * 15 // 15 minutes
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get translator for plugin, for the current form's metadata (as well as the plugin-specific boilerplate).
|
|
17
|
+
* This is an async call so we can read the form definition inside this call.
|
|
18
|
+
* See docs/features/multi-language.md for the language and translation flow overview.
|
|
19
|
+
* @param {AnyFormRequest} request
|
|
20
|
+
* @param {FormMetadata} metadata - the metadata of the form
|
|
21
|
+
* @param {FormStatus} status - the form status to use when retrieving the definition
|
|
22
|
+
* @param {string} language - the language to use for the translator
|
|
23
|
+
*/
|
|
24
|
+
export async function getCachedPluginTranslator(
|
|
25
|
+
request: AnyFormRequest,
|
|
26
|
+
metadata: FormMetadata,
|
|
27
|
+
status: FormStatus,
|
|
28
|
+
language: string
|
|
29
|
+
) {
|
|
30
|
+
const key = `${metadata.id}-${status}-${language}-plugin-meta`
|
|
31
|
+
|
|
32
|
+
if (cache.has(key)) {
|
|
33
|
+
return cache.get(key) as unknown as Translator
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { services } = getPluginOptions(request.server)
|
|
37
|
+
|
|
38
|
+
const definition = await services?.formsService.getFormDefinition(
|
|
39
|
+
metadata.id,
|
|
40
|
+
status
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if (!definition) {
|
|
44
|
+
throw Boom.notFound(
|
|
45
|
+
`FormDefinition not found for form ${metadata.id} and status ${status}`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const translator = createFormTranslator(metadata, definition, language)
|
|
50
|
+
|
|
51
|
+
cache.set(key, translator)
|
|
52
|
+
|
|
53
|
+
return translator
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type FormDefinition } from '@defra/forms-model'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a list of available languages.
|
|
5
|
+
* Empty array if only English, or an array of English and Welsh if translations have been defined.
|
|
6
|
+
* @param {FormDefinition} def
|
|
7
|
+
*/
|
|
8
|
+
export function getAvailableLanguages(
|
|
9
|
+
def: FormDefinition
|
|
10
|
+
): { name: string; code: string }[] {
|
|
11
|
+
// @ts-expect-error - dynamic property
|
|
12
|
+
if (def.metadata?.translations?.cy) {
|
|
13
|
+
return [
|
|
14
|
+
{ name: 'English', code: 'en-GB' },
|
|
15
|
+
{ name: 'Cymraeg', code: 'cy' }
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
return []
|
|
19
|
+
}
|
|
@@ -22,6 +22,12 @@ export {
|
|
|
22
22
|
resolveFormModel
|
|
23
23
|
} from './form-context.js'
|
|
24
24
|
export * from './form-availability.js'
|
|
25
|
+
export { getAvailableLanguages } from './i18n/languages.js'
|
|
26
|
+
export {
|
|
27
|
+
extractMetadataBaseTranslations,
|
|
28
|
+
loadFormTranslations
|
|
29
|
+
} from './i18n/createFormTranslator.js'
|
|
30
|
+
export { extractBaseTranslations } from './i18n/extractBaseTranslations.js'
|
|
25
31
|
|
|
26
32
|
const globals = {
|
|
27
33
|
checkComponentTemplates,
|
|
@@ -31,6 +31,7 @@ import { type i18n } from 'i18next'
|
|
|
31
31
|
import joi from 'joi'
|
|
32
32
|
|
|
33
33
|
import { logger } from '../../../common/helpers/logging/logger.js'
|
|
34
|
+
import { EN_GB } from '../../../constants.js'
|
|
34
35
|
import { type ListFormComponent } from '../components/ListFormComponent.js'
|
|
35
36
|
import {} from '../components/YesNoField.js'
|
|
36
37
|
import {
|
|
@@ -47,6 +48,7 @@ import { loadFormTranslations } from '../i18n/createFormTranslator.js'
|
|
|
47
48
|
import { createTranslator } from '../i18n/createTranslator.js'
|
|
48
49
|
import { extractBaseTranslations } from '../i18n/extractBaseTranslations.js'
|
|
49
50
|
import { createFormI18nInstance } from '../i18n/index.js'
|
|
51
|
+
import { getAvailableLanguages } from '../i18n/languages.js'
|
|
50
52
|
import { type Translator } from '../i18n/types.js'
|
|
51
53
|
import { type ExecutableCondition } from './types.js'
|
|
52
54
|
import { type PageController } from '../pageControllers/PageController.js'
|
|
@@ -240,7 +242,12 @@ export class FormModel {
|
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
/** Returns a scoped translator pair for the given language. */
|
|
243
|
-
createTranslator(language =
|
|
245
|
+
createTranslator(language = EN_GB): Translator {
|
|
246
|
+
// @ts-expect-error - dynamic language lookup
|
|
247
|
+
if (!this.def.metadata?.translations?.[language]) {
|
|
248
|
+
// If not translations defined in the FormDefinition, always default to English
|
|
249
|
+
language = EN_GB
|
|
250
|
+
}
|
|
244
251
|
return createTranslator(this.i18nInstance, language)
|
|
245
252
|
}
|
|
246
253
|
|
|
@@ -662,7 +669,7 @@ function validateFormState(
|
|
|
662
669
|
|
|
663
670
|
// Add relevant state errors
|
|
664
671
|
if (error) {
|
|
665
|
-
const errorsState = error.details.map(getError)
|
|
672
|
+
const errorsState = error.details.map((item) => getError(EN_GB, item))
|
|
666
673
|
return { ...context, errors: errors.concat(errorsState) }
|
|
667
674
|
}
|
|
668
675
|
|
|
@@ -679,16 +686,3 @@ function getReferenceNumber(state: FormSubmissionState): string {
|
|
|
679
686
|
|
|
680
687
|
return state.$$__referenceNumber
|
|
681
688
|
}
|
|
682
|
-
|
|
683
|
-
export function getAvailableLanguages(
|
|
684
|
-
def: FormDefinition
|
|
685
|
-
): { name: string; code: string }[] {
|
|
686
|
-
// @ts-expect-error - dynamic property
|
|
687
|
-
if (def.metadata?.translations?.cy) {
|
|
688
|
-
return [
|
|
689
|
-
{ name: 'English', code: 'en-GB' },
|
|
690
|
-
{ name: 'Cymraeg', code: 'cy' }
|
|
691
|
-
]
|
|
692
|
-
}
|
|
693
|
-
return []
|
|
694
|
-
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type Section
|
|
5
5
|
} from '@defra/forms-model'
|
|
6
6
|
|
|
7
|
+
import { EN_GB } from '../../../constants.js'
|
|
7
8
|
import { PaymentField } from '../components/PaymentField.js'
|
|
8
9
|
import { type PaymentState } from '../components/PaymentField.types.js'
|
|
9
10
|
import {
|
|
@@ -96,7 +97,7 @@ export class SummaryViewModel {
|
|
|
96
97
|
.validate(this.context.relevantState, { ...opts, stripUnknown: true })
|
|
97
98
|
|
|
98
99
|
// Format errors
|
|
99
|
-
this.errors = result.error?.details.map(getError)
|
|
100
|
+
this.errors = result.error?.details.map((item) => getError(EN_GB, item))
|
|
100
101
|
this.details = this.summaryDetails(request, sections, translator)
|
|
101
102
|
|
|
102
103
|
// Format check answers
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import { addDays, format as dateFormat } from 'date-fns'
|
|
6
6
|
|
|
7
7
|
import { config } from '../../../../../config/index.js'
|
|
8
|
+
import { EN_GB } from '../../../../constants.js'
|
|
8
9
|
import { getAnswer } from '../../components/helpers/components.js'
|
|
9
10
|
import { escapeMarkdown } from '../../components/helpers/index.js'
|
|
10
11
|
import { PaymentField } from '../../components/index.js'
|
|
@@ -116,7 +117,7 @@ function appendPaymentSection(paymentItems: DetailItem[], lines: string[]) {
|
|
|
116
117
|
|
|
117
118
|
const formattedAmount = formatCurrency(paymentState.amount)
|
|
118
119
|
const dateOfPayment = paymentState.preAuth?.createdAt
|
|
119
|
-
? formatPaymentDate(paymentState.preAuth.createdAt)
|
|
120
|
+
? formatPaymentDate(paymentState.preAuth.createdAt, EN_GB)
|
|
120
121
|
: ''
|
|
121
122
|
|
|
122
123
|
lines.push(
|
|
@@ -28,6 +28,7 @@ interface PaymentOutput {
|
|
|
28
28
|
reference: string
|
|
29
29
|
amount: number
|
|
30
30
|
description: string
|
|
31
|
+
descriptionInEng?: string
|
|
31
32
|
createdAt: string
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -93,6 +94,7 @@ export function format(
|
|
|
93
94
|
* reference: 'REF-123',
|
|
94
95
|
* amount: 10.00,
|
|
95
96
|
* description: 'Application fee',
|
|
97
|
+
* descriptionInEng: 'Applicaiton fee',
|
|
96
98
|
* createdAt: '2025-01-23T10:30:00.000Z'
|
|
97
99
|
* }
|
|
98
100
|
* }
|
|
@@ -204,6 +206,7 @@ function extractPayment(
|
|
|
204
206
|
reference: paymentState.reference,
|
|
205
207
|
amount: paymentState.amount,
|
|
206
208
|
description: paymentState.description,
|
|
209
|
+
descriptionInEng: item.field.def.options.description,
|
|
207
210
|
createdAt: paymentState.preAuth?.createdAt ?? ''
|
|
208
211
|
}
|
|
209
212
|
}
|
|
@@ -226,7 +226,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
getErrors(details?: ValidationErrorItem[]) {
|
|
229
|
+
getErrors(details?: ValidationErrorItem[], language?: string) {
|
|
230
230
|
const { fileUpload } = this
|
|
231
231
|
|
|
232
232
|
if (details) {
|
|
@@ -239,7 +239,7 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
239
239
|
if (!isUploadError || isUploadRootError) {
|
|
240
240
|
// The error is for the root of the upload or another
|
|
241
241
|
// field on the page so defer to the getError helper
|
|
242
|
-
errors.push(getError(error))
|
|
242
|
+
errors.push(getError(language, error))
|
|
243
243
|
} else {
|
|
244
244
|
const { context, path, type } = error
|
|
245
245
|
|
|
@@ -364,8 +364,8 @@ export class QuestionPageController extends PageController {
|
|
|
364
364
|
return this.collection.getStateFromValidForm(payload)
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
getErrors(details?: ValidationErrorItem[]) {
|
|
368
|
-
return getErrors(details)
|
|
367
|
+
getErrors(details?: ValidationErrorItem[], language?: string) {
|
|
368
|
+
return getErrors(language, details)
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
async getState(request: AnyFormRequest) {
|