From 9180e84489cbb5f91153d8627c3b8f7e610727ed Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Thu, 23 Jul 2026 14:48:15 +0100 Subject: [PATCH 1/4] Passes selected language to downstream systems --- src/server/constants.js | 1 + src/server/i18n/form.ts | 3 ++- src/server/i18n/index.test.ts | 5 ++--- src/server/i18n/translations/cy.json | 2 ++ src/server/i18n/translations/en-GB.json | 2 ++ src/server/models/save-and-exit.test.js | 3 ++- ...PageWithConfirmationEmailController.test.ts | 5 +++-- src/server/plugins/nunjucks/context.test.js | 3 ++- src/server/services/outputService.test.js | 18 ++++++++++++++++-- src/server/services/outputService.ts | 1 + src/server/utils/utils.js | 3 ++- src/server/views/layout.html | 3 ++- test/form/confirmation-email.test.js | 5 +++-- 13 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/server/constants.js b/src/server/constants.js index c687dfdc1..8bcdbd60e 100644 --- a/src/server/constants.js +++ b/src/server/constants.js @@ -2,3 +2,4 @@ export const PREVIEW_PATH_PREFIX = '/preview' export const ERROR_PREVIEW_PATH_PREFIX = '/error-preview' export const FORM_PREFIX = '/form' export const SAVE_AND_EXIT_PAYLOAD = 'SAVE_AND_EXIT_PAYLOAD' +export const EN_GB = 'en-GB' diff --git a/src/server/i18n/form.ts b/src/server/i18n/form.ts index 80a1514ea..0f7fe149e 100644 --- a/src/server/i18n/form.ts +++ b/src/server/i18n/form.ts @@ -8,6 +8,7 @@ import { import { type i18n } from 'i18next' import { LRUCache } from 'lru-cache' +import { EN_GB } from '~/src/server/constants.js' import { createFormI18nInstance, extractMetadataBaseTranslations, @@ -107,7 +108,7 @@ export function createFormTranslator( export function createTranslator( i18nInstance: i18n, languages: { name: string; code: string }[], - language = 'en-GB' + language = EN_GB ): Translator { const t = (key: string, opts?: Record): string => i18nInstance.t(key, { lng: language, ...opts }) diff --git a/src/server/i18n/index.test.ts b/src/server/i18n/index.test.ts index e0bca00d0..c52ce88bf 100644 --- a/src/server/i18n/index.test.ts +++ b/src/server/i18n/index.test.ts @@ -2,6 +2,7 @@ import { type FormDefinition } from '@defra/forms-model' import { type Request } from '@hapi/hapi' import { type i18n } from 'i18next' +import { EN_GB } from '~/src/server/constants.js' import { extractTranslations, t } from '~/src/server/i18n/index.js' import { resolveLanguage } from '~/src/server/utils/utils.js' @@ -29,9 +30,7 @@ describe('Runner i18n', () => { describe('resolveLanguage()', () => { it('returns the default language', () => { const blankRequest = {} as unknown as Request - expect(resolveLanguage(blankRequest.query, blankRequest.yar)).toBe( - 'en-GB' - ) + expect(resolveLanguage(blankRequest.query, blankRequest.yar)).toBe(EN_GB) }) it('returns the language set in the session', () => { diff --git a/src/server/i18n/translations/cy.json b/src/server/i18n/translations/cy.json index 7a74816c1..b5586d552 100644 --- a/src/server/i18n/translations/cy.json +++ b/src/server/i18n/translations/cy.json @@ -274,6 +274,8 @@ "title": "Statws y gwasanaeth" }, "phaseBanner": { + "alphaStage": "Alffa", + "betaStage": "Beta", "feedbackLinkNewTab": "rhowch eich adborth (yn agor mewn tab newydd)", "feedbackLinkEmail": "rhowch eich adborth drwy e-bost", "text": "Gwasanaeth newydd yw hwn. Helpwch ni i'w wella a [[feedbackLink]]." diff --git a/src/server/i18n/translations/en-GB.json b/src/server/i18n/translations/en-GB.json index 790191a19..18bbce62e 100644 --- a/src/server/i18n/translations/en-GB.json +++ b/src/server/i18n/translations/en-GB.json @@ -274,6 +274,8 @@ "title": "Service status" }, "phaseBanner": { + "alphaStage": "Alpha", + "betaStage": "Beta", "feedbackLinkNewTab": "give your feedback (opens in new tab)", "feedbackLinkEmail": "give your feedback by email", "text": "This is a new service. Help us improve it and [[feedbackLink]]." diff --git a/src/server/models/save-and-exit.test.js b/src/server/models/save-and-exit.test.js index 0f7d3c03f..7aaee668d 100644 --- a/src/server/models/save-and-exit.test.js +++ b/src/server/models/save-and-exit.test.js @@ -1,3 +1,4 @@ +import { EN_GB } from '~/src/server/constants.js' import { createFormTranslator } from '~/src/server/i18n/form.js' import { lockedOutViewModel } from '~/src/server/models/save-and-exit.js' @@ -12,7 +13,7 @@ describe('Save and exit models', () => { }) const definition = /** @type {FormDefinition} */ ({}) - const translator = createFormTranslator(form, definition, 'en-GB') + const translator = createFormTranslator(form, definition, EN_GB) test('should construct live resume url', () => { const link = /** @type {SaveAndExitResumeDetails} */ ({ diff --git a/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts b/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts index 8694642ea..79694ac05 100644 --- a/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts +++ b/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts @@ -15,6 +15,7 @@ import { } from '@defra/forms-model' import { type ResponseObject } from '@hapi/hapi' +import { EN_GB } from '~/src/server/constants.js' import { SummaryPageWithConfirmationEmailController, getUserConfirmationEmailAddress @@ -41,7 +42,7 @@ describe('SummaryPageWithConfirmationEmailController', () => { basePath: 'test' }) - translator = model.createTranslator('en-GB') + translator = model.createTranslator(EN_GB) // Create a mock page for SummaryPageWithConfirmationEmailController const mockPage = { @@ -61,7 +62,7 @@ describe('SummaryPageWithConfirmationEmailController', () => { }, query: {}, app: { model } - } as unknown as FormRequest) + }) }) describe('handle errors', () => { diff --git a/src/server/plugins/nunjucks/context.test.js b/src/server/plugins/nunjucks/context.test.js index 646a0db56..9949f6e91 100644 --- a/src/server/plugins/nunjucks/context.test.js +++ b/src/server/plugins/nunjucks/context.test.js @@ -1,6 +1,7 @@ import { tmpdir } from 'node:os' import { config } from '~/src/config/index.js' +import { EN_GB } from '~/src/server/constants.js' import { context } from '~/src/server/plugins/nunjucks/context.js' describe('Nunjucks context', () => { @@ -123,7 +124,7 @@ describe('Nunjucks context', () => { query: {}, response: { statusCode: 200 }, state: {}, - app: { language: 'en-GB' }, + app: { language: EN_GB }, path: '/test', url: { search: '' }, plugins: {} diff --git a/src/server/services/outputService.test.js b/src/server/services/outputService.test.js index 4c946b012..4e106ff71 100644 --- a/src/server/services/outputService.test.js +++ b/src/server/services/outputService.test.js @@ -3,6 +3,7 @@ import { checkFormStatus } from '@defra/forms-engine-plugin/engine/helpers.js' import { getFormatter } from '@defra/forms-engine-plugin/engine/outputFormatters/index.js' import { FormStatus } from '@defra/forms-engine-plugin/types' +import { EN_GB } from '~/src/server/constants.js' import { publishFormAdapterEvent } from '~/src/server/messaging/formAdapterEventPublisher.js' import { getFormMetadataById } from '~/src/server/services/formsService.js' import { @@ -150,6 +151,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -191,6 +193,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -237,6 +240,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -277,7 +281,8 @@ describe('OutputService', () => { mockModel, 'test@example.com', mockItems, - mockSubmitResponse + mockSubmitResponse, + EN_GB ) expect(checkFormStatus).toHaveBeenCalledWith(mockRequest.params) @@ -307,6 +312,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) ).rejects.toThrow('Formatter failed') @@ -327,6 +333,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) ).rejects.toThrow(SyntaxError) @@ -360,6 +367,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) ).rejects.toThrow('SNS publish failed') @@ -390,7 +398,8 @@ describe('OutputService', () => { mockModel, 'test@example.com', mockItems, - mockSubmitResponse + mockSubmitResponse, + EN_GB ) ).rejects.toThrow('Formatter failed') @@ -422,6 +431,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -454,6 +464,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -489,6 +500,7 @@ describe('OutputService', () => { undefined, // This parameter is ignored, using undefined to match test intent mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -555,6 +567,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) @@ -692,6 +705,7 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, + EN_GB, mockFormMetadata ) diff --git a/src/server/services/outputService.ts b/src/server/services/outputService.ts index c2cdce35f..725af2d74 100644 --- a/src/server/services/outputService.ts +++ b/src/server/services/outputService.ts @@ -42,6 +42,7 @@ export class OutputService implements IOutputService { _emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, + _language: string, formMetadata?: FormMetadata ): Promise { const submissionRef = context.referenceNumber diff --git a/src/server/utils/utils.js b/src/server/utils/utils.js index 8b21e6789..9b996148d 100644 --- a/src/server/utils/utils.js +++ b/src/server/utils/utils.js @@ -1,6 +1,7 @@ import { getTraceId } from '@defra/hapi-tracing' import { config } from '~/src/config/index.js' +import { EN_GB } from '~/src/server/constants.js' /** * Returns a set of headers to use in an HTTP request, merging them with any existing headers in options. @@ -37,7 +38,7 @@ export function getFeedbackFormLink(formId) { * @returns {string} - the resolved language code */ export function resolveLanguage(query, yar) { - const defaultLang = 'en-GB' + const defaultLang = EN_GB query ??= {} diff --git a/src/server/views/layout.html b/src/server/views/layout.html index 13fda2a2d..3f1000b53 100644 --- a/src/server/views/layout.html +++ b/src/server/views/layout.html @@ -149,8 +149,9 @@ {% endif %} {%- endset %} + {% set stageKey = 'layout.phaseBanner.' + phaseTag + 'Stage' %} {{ govukPhaseBanner({ - tag: { text: phaseTag | capitalize }, + tag: { text: tR(stageKey) | capitalize }, html: tR('layout.phaseBanner.text', { feedbackLink: feedbackLinkHtml }) }) }} {% endif %} diff --git a/test/form/confirmation-email.test.js b/test/form/confirmation-email.test.js index f8d26121c..983cb86db 100644 --- a/test/form/confirmation-email.test.js +++ b/test/form/confirmation-email.test.js @@ -5,7 +5,7 @@ import { FormAction } from '@defra/forms-engine-plugin/types' import { within } from '@testing-library/dom' import { StatusCodes } from 'http-status-codes' -import { FORM_PREFIX } from '~/src/server/constants.js' +import { EN_GB, FORM_PREFIX } from '~/src/server/constants.js' import { createServer } from '~/src/server/index.js' import { getFormMetadata } from '~/src/server/services/formsService.js' import * as fixtures from '~/test/fixtures/index.js' @@ -372,7 +372,8 @@ describe('Confirmation email', () => { repeaters: [], retrievalKey: 'enrique.chase@defra.gov.uk', sessionId: expect.any(String), - referenceNumber: expect.any(String) + referenceNumber: expect.any(String), + language: EN_GB }) expect(response.statusCode).toBe(StatusCodes.SEE_OTHER) From 05859a46773af009ab30849cc1fa4713ddb42504 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 24 Jul 2026 11:10:21 +0100 Subject: [PATCH 2/4] Removed extra language param now that removed from plugin --- package-lock.json | 2 +- package.json | 2 +- src/server/services/outputService.test.js | 18 ++---------------- src/server/services/outputService.ts | 1 - test/form/confirmation-email.test.js | 4 ++-- 5 files changed, 6 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ae16d020..3e54cb5fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@aws-sdk/client-sns": "^3.1079.0", - "@defra/forms-engine-plugin": "^5.0.0-alpha.4", + "@defra/forms-engine-plugin": "^5.0.0-alpha.6", "@defra/forms-model": "^3.0.686", "@defra/hapi-tracing": "^1.30.0", "@elastic/ecs-pino-format": "^1.5.0", diff --git a/package.json b/package.json index b00663dcd..e5e76564f 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@aws-sdk/client-sns": "^3.1079.0", - "@defra/forms-engine-plugin": "^5.0.0-alpha.4", + "@defra/forms-engine-plugin": "^5.0.0-alpha.6", "@defra/forms-model": "^3.0.686", "@defra/hapi-tracing": "^1.30.0", "@elastic/ecs-pino-format": "^1.5.0", diff --git a/src/server/services/outputService.test.js b/src/server/services/outputService.test.js index 4e106ff71..4c946b012 100644 --- a/src/server/services/outputService.test.js +++ b/src/server/services/outputService.test.js @@ -3,7 +3,6 @@ import { checkFormStatus } from '@defra/forms-engine-plugin/engine/helpers.js' import { getFormatter } from '@defra/forms-engine-plugin/engine/outputFormatters/index.js' import { FormStatus } from '@defra/forms-engine-plugin/types' -import { EN_GB } from '~/src/server/constants.js' import { publishFormAdapterEvent } from '~/src/server/messaging/formAdapterEventPublisher.js' import { getFormMetadataById } from '~/src/server/services/formsService.js' import { @@ -151,7 +150,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -193,7 +191,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -240,7 +237,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -281,8 +277,7 @@ describe('OutputService', () => { mockModel, 'test@example.com', mockItems, - mockSubmitResponse, - EN_GB + mockSubmitResponse ) expect(checkFormStatus).toHaveBeenCalledWith(mockRequest.params) @@ -312,7 +307,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) ).rejects.toThrow('Formatter failed') @@ -333,7 +327,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) ).rejects.toThrow(SyntaxError) @@ -367,7 +360,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) ).rejects.toThrow('SNS publish failed') @@ -398,8 +390,7 @@ describe('OutputService', () => { mockModel, 'test@example.com', mockItems, - mockSubmitResponse, - EN_GB + mockSubmitResponse ) ).rejects.toThrow('Formatter failed') @@ -431,7 +422,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -464,7 +454,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -500,7 +489,6 @@ describe('OutputService', () => { undefined, // This parameter is ignored, using undefined to match test intent mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -567,7 +555,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) @@ -705,7 +692,6 @@ describe('OutputService', () => { 'test@example.com', mockItems, mockSubmitResponse, - EN_GB, mockFormMetadata ) diff --git a/src/server/services/outputService.ts b/src/server/services/outputService.ts index 725af2d74..c2cdce35f 100644 --- a/src/server/services/outputService.ts +++ b/src/server/services/outputService.ts @@ -42,7 +42,6 @@ export class OutputService implements IOutputService { _emailAddress: string, items: DetailItem[], submitResponse: SubmitResponsePayload, - _language: string, formMetadata?: FormMetadata ): Promise { const submissionRef = context.referenceNumber diff --git a/test/form/confirmation-email.test.js b/test/form/confirmation-email.test.js index 983cb86db..155af1a54 100644 --- a/test/form/confirmation-email.test.js +++ b/test/form/confirmation-email.test.js @@ -5,7 +5,7 @@ import { FormAction } from '@defra/forms-engine-plugin/types' import { within } from '@testing-library/dom' import { StatusCodes } from 'http-status-codes' -import { EN_GB, FORM_PREFIX } from '~/src/server/constants.js' +import { FORM_PREFIX } from '~/src/server/constants.js' import { createServer } from '~/src/server/index.js' import { getFormMetadata } from '~/src/server/services/formsService.js' import * as fixtures from '~/test/fixtures/index.js' @@ -373,7 +373,7 @@ describe('Confirmation email', () => { retrievalKey: 'enrique.chase@defra.gov.uk', sessionId: expect.any(String), referenceNumber: expect.any(String), - language: EN_GB + language: undefined }) expect(response.statusCode).toBe(StatusCodes.SEE_OTHER) From 89624fbb00f5a0b647a60540f502bf2217f17926 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 24 Jul 2026 11:31:38 +0100 Subject: [PATCH 3/4] Plugin bump --- package-lock.json | 20 ++++++++++---------- package.json | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e54cb5fd..a3b64a890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@aws-sdk/client-sns": "^3.1079.0", - "@defra/forms-engine-plugin": "^5.0.0-alpha.6", + "@defra/forms-engine-plugin": "^5.0.0-alpha.7", "@defra/forms-model": "^3.0.686", "@defra/hapi-tracing": "^1.30.0", "@elastic/ecs-pino-format": "^1.5.0", @@ -11131,9 +11131,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "dev": true, "funding": [ { @@ -12195,9 +12195,9 @@ } }, "node_modules/immutable": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", - "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", "dev": true, "license": "MIT" }, @@ -19731,9 +19731,9 @@ "dev": true }, "node_modules/svgo": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", - "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz", + "integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e5e76564f..2ee435424 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "license": "SEE LICENSE IN LICENSE", "dependencies": { "@aws-sdk/client-sns": "^3.1079.0", - "@defra/forms-engine-plugin": "^5.0.0-alpha.6", + "@defra/forms-engine-plugin": "^5.0.0-alpha.7", "@defra/forms-model": "^3.0.686", "@defra/hapi-tracing": "^1.30.0", "@elastic/ecs-pino-format": "^1.5.0", From 4fc72e5949e23cab72ddcef2c9f211271a398c15 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 24 Jul 2026 11:37:54 +0100 Subject: [PATCH 4/4] Fixed type --- .../plugins/SummaryPageWithConfirmationEmailController.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts b/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts index 79694ac05..dfd8a7b8b 100644 --- a/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts +++ b/src/server/plugins/SummaryPageWithConfirmationEmailController.test.ts @@ -62,7 +62,7 @@ describe('SummaryPageWithConfirmationEmailController', () => { }, query: {}, app: { model } - }) + } as FormRequest) }) describe('handle errors', () => {