From ee07d592870c06a1eb4114a314c5baf087dbc56b Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Wed, 5 Aug 2026 10:09:23 +0100 Subject: [PATCH 1/7] Stash for context switch --- .../plugins/engine/i18n/translations/cy.json | 6 ++++ .../engine/i18n/translations/en-GB.json | 6 ++++ .../engine/models/unavailable-view-model.ts | 29 ++++++++++++++++--- .../plugins/engine/unavailable-response.ts | 6 +++- .../plugins/engine/views/unavailable.html | 4 +-- 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/server/plugins/engine/i18n/translations/cy.json b/src/server/plugins/engine/i18n/translations/cy.json index d518b5151..c0f8b1830 100644 --- a/src/server/plugins/engine/i18n/translations/cy.json +++ b/src/server/plugins/engine/i18n/translations/cy.json @@ -155,6 +155,12 @@ "bannerForce": "Rhagolwg o ffurflen [[previewMode]] yw hon. Peidiwch â rhoi gwybodaeth bersonol.", "bannerText": "Dyma ragolwg o dudalen ffurflen [[previewMode]] rydych chi'n ei golygu.", "previousPages": "Mae'n dibynnu ar atebion o dudalennau cynharach yn y ffurflen. Yn y fersiwn fyw, bydd angen i ddefnyddwyr gwblhau'r cwestiynau hynny yn gyntaf." + }, + + "formUnavailable": { + "title": "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael", + "subHeading": "Nid yw '[[formTitle]]' ar gael mwyach.", + "contactDetailsHeading": "Manylion cyswllt ar gyfer [[organisationName]]" } }, diff --git a/src/server/plugins/engine/i18n/translations/en-GB.json b/src/server/plugins/engine/i18n/translations/en-GB.json index 1615ae794..101b88450 100644 --- a/src/server/plugins/engine/i18n/translations/en-GB.json +++ b/src/server/plugins/engine/i18n/translations/en-GB.json @@ -139,6 +139,12 @@ "bannerForce": "This is a preview of a [[previewMode]] form. Do not enter personal information.", "bannerText": "This is a preview of a [[previewMode]] form page you are editing.", "previousPages": "It depends on answers from earlier pages in the form. In the live version, users will need to complete those questions first." + }, + + "formUnavailable": { + "title": "Sorry, this form is unavailable", + "subHeading": "'[[formTitle]]' is no longer available.", + "contactDetailsHeading": "Contact details for [[organisationName]]" } }, diff --git a/src/server/plugins/engine/models/unavailable-view-model.ts b/src/server/plugins/engine/models/unavailable-view-model.ts index f69d477b9..6d68d8a77 100644 --- a/src/server/plugins/engine/models/unavailable-view-model.ts +++ b/src/server/plugins/engine/models/unavailable-view-model.ts @@ -1,19 +1,40 @@ -import { type FormMetadata, type FormMetadataContact } from '@defra/forms-model' +import { + type FormDefinition, + type FormMetadata, + type FormMetadataContact +} from '@defra/forms-model' + +import { t } from '~/src/server/plugins/engine/i18n/index.js' +import { getAvailableLanguages } from '~/src/server/plugins/engine/i18n/languages.js' export interface UnavailableViewModel { pageTitle: string formTitle: string organisationName: string contact?: FormMetadataContact + t: ( + key: string, + language: string, + options?: Record + ) => string + language: string + languages: { code: string; name: string }[] } export function unavailableViewModel( - metadata: FormMetadata + metadata: FormMetadata, + language: string ): UnavailableViewModel { return { - pageTitle: 'Sorry, this form is unavailable', + pageTitle: t('pages.formUnavailable.title', language), formTitle: metadata.title, organisationName: metadata.organisation, - contact: metadata.contact + contact: metadata.contact, + t, + language, + // Always get Welsh and English + languages: getAvailableLanguages({ + metadata: { translations: { cy: {} } } + } as unknown as FormDefinition) } } diff --git a/src/server/plugins/engine/unavailable-response.ts b/src/server/plugins/engine/unavailable-response.ts index 768924b79..d4b59eacc 100644 --- a/src/server/plugins/engine/unavailable-response.ts +++ b/src/server/plugins/engine/unavailable-response.ts @@ -2,6 +2,8 @@ import { type Request, type ResponseToolkit, type Server } from '@hapi/hapi' import { isOfflineBoom } from '~/src/server/plugins/engine/form-availability.js' import { unavailableViewModel } from '~/src/server/plugins/engine/models/unavailable-view-model.js' +import { type AnyFormRequest } from '~/src/server/plugins/engine/types.js' +import { resolveLanguage } from '~/src/server/utils/utils.js' /** * Registers a server-wide onPreResponse extension that intercepts the offline @@ -19,8 +21,10 @@ export function registerUnavailableResponse(server: Server) { const { metadata } = response.data + const language = resolveLanguage(request as unknown as AnyFormRequest) + return h - .view('unavailable', unavailableViewModel(metadata)) + .view('unavailable', unavailableViewModel(metadata, language)) .header('Cache-Control', 'no-store, no-cache, must-revalidate') .header('X-Robots-Tag', 'noindex, nofollow') .code(200) diff --git a/src/server/plugins/engine/views/unavailable.html b/src/server/plugins/engine/views/unavailable.html index 6d61a5ff8..b0d512bcf 100644 --- a/src/server/plugins/engine/views/unavailable.html +++ b/src/server/plugins/engine/views/unavailable.html @@ -3,9 +3,7 @@ {% block content %}
-

Sorry, this form is unavailable

-

'{{ formTitle }}' is no longer available.

-

Contact details for {{ organisationName }}

+

{{ t('pages.formUnavailable.title', language) }}

{% if contact.phone %}

Telephone

From 2f6831aca602e9e266063b94ff1a3515336bc14a Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Wed, 5 Aug 2026 11:01:30 +0100 Subject: [PATCH 2/7] Stash for context switch2 --- src/server/plugins/engine/form-availability.ts | 6 ++++-- src/server/plugins/engine/form-context.ts | 6 ++++-- src/server/plugins/engine/models/unavailable-view-model.ts | 4 ++++ src/server/plugins/engine/unavailable-response.ts | 4 ++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/server/plugins/engine/form-availability.ts b/src/server/plugins/engine/form-availability.ts index de140534e..6bcb1f67a 100644 --- a/src/server/plugins/engine/form-availability.ts +++ b/src/server/plugins/engine/form-availability.ts @@ -1,9 +1,10 @@ -import { FormStatus, type FormMetadata } from '@defra/forms-model' +import { FormStatus, type FormDefinition, type FormMetadata } from '@defra/forms-model' import Boom from '@hapi/boom' export interface OfflineBoomData { offline: true metadata: FormMetadata + definition: FormDefinition | undefined } /** @@ -13,6 +14,7 @@ export interface OfflineBoomData { */ export function assertFormAvailable( metadata: FormMetadata, + definition: FormDefinition | undefined, formState: FormStatus, isPreview: boolean ): void { @@ -21,7 +23,7 @@ export function assertFormAvailable( formState === FormStatus.Live && !isPreview ) { - const data: OfflineBoomData = { offline: true, metadata } + const data: OfflineBoomData = { offline: true, metadata, definition } throw Boom.boomify(new Error(`Form ${metadata.slug} is offline`), { statusCode: 503, data diff --git a/src/server/plugins/engine/form-context.ts b/src/server/plugins/engine/form-context.ts index 304cf252d..5af70f920 100644 --- a/src/server/plugins/engine/form-context.ts +++ b/src/server/plugins/engine/form-context.ts @@ -56,13 +56,14 @@ export async function getFormModel( const formState = resolveState(state) const metadata = await formsService.getFormMetadata(slug) - assertFormAvailable(metadata, formState, isPreview) const definition = await formsService.getFormDefinition( metadata.id, formState ) + assertFormAvailable(metadata, definition, formState, isPreview) + if (!definition) { throw Boom.notFound( `No definition found for form metadata ${metadata.id} (${slug}) ${state}` @@ -145,7 +146,6 @@ export async function resolveFormModel( const metadata = await formsService.getFormMetadata(slug) const formState = resolveState(state) const isPreview = options.isPreview ?? isPreviewState(state, options) - assertFormAvailable(metadata, formState, isPreview) const stateMetadata = metadata[formState] if (!stateMetadata) { @@ -199,6 +199,8 @@ export async function resolveFormModel( cache.set(cacheKey, entry) } + assertFormAvailable(metadata, entry.model.def, formState, isPreview) + return entry.model } diff --git a/src/server/plugins/engine/models/unavailable-view-model.ts b/src/server/plugins/engine/models/unavailable-view-model.ts index 6d68d8a77..d3c3b7ecb 100644 --- a/src/server/plugins/engine/models/unavailable-view-model.ts +++ b/src/server/plugins/engine/models/unavailable-view-model.ts @@ -23,8 +23,12 @@ export interface UnavailableViewModel { export function unavailableViewModel( metadata: FormMetadata, + definition: FormDefinition | undefined, language: string ): UnavailableViewModel { + const formTranslations = definition + ? definition.metadata.translations[language] + : {} return { pageTitle: t('pages.formUnavailable.title', language), formTitle: metadata.title, diff --git a/src/server/plugins/engine/unavailable-response.ts b/src/server/plugins/engine/unavailable-response.ts index d4b59eacc..5e9fd57a3 100644 --- a/src/server/plugins/engine/unavailable-response.ts +++ b/src/server/plugins/engine/unavailable-response.ts @@ -19,12 +19,12 @@ export function registerUnavailableResponse(server: Server) { return h.continue } - const { metadata } = response.data + const { metadata, definition } = response.data const language = resolveLanguage(request as unknown as AnyFormRequest) return h - .view('unavailable', unavailableViewModel(metadata, language)) + .view('unavailable', unavailableViewModel(metadata, definition, language)) .header('Cache-Control', 'no-store, no-cache, must-revalidate') .header('X-Robots-Tag', 'noindex, nofollow') .code(200) From 5ebf1e5b70f6103408e459eb5e0d9ce0e3e21d01 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Wed, 5 Aug 2026 12:46:31 +0100 Subject: [PATCH 3/7] Stash for rebase --- .../plugins/engine/i18n/translations/cy.json | 6 ++++- .../engine/i18n/translations/en-GB.json | 6 ++++- .../engine/models/unavailable-view-model.ts | 23 +++++++++---------- .../FileUploadPageController.ts | 1 + .../plugins/engine/views/unavailable.html | 4 +++- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/server/plugins/engine/i18n/translations/cy.json b/src/server/plugins/engine/i18n/translations/cy.json index 7c47fbdcf..b4f38d9de 100644 --- a/src/server/plugins/engine/i18n/translations/cy.json +++ b/src/server/plugins/engine/i18n/translations/cy.json @@ -160,7 +160,11 @@ "formUnavailable": { "title": "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael", "subHeading": "Nid yw '[[formTitle]]' ar gael mwyach.", - "contactDetailsHeading": "Manylion cyswllt ar gyfer [[organisationName]]" + "contactDetailsHeading": "Manylion cyswllt ar gyfer [[organisationName]]", + "telephone": "Ffôn", + "callCharges": "Dysgwch am gostau galwadau", + "email": "E-bost", + "onlineContactForm": "Ffurflen gyswllt ar-lein" } }, diff --git a/src/server/plugins/engine/i18n/translations/en-GB.json b/src/server/plugins/engine/i18n/translations/en-GB.json index 625b1efab..3cc300a56 100644 --- a/src/server/plugins/engine/i18n/translations/en-GB.json +++ b/src/server/plugins/engine/i18n/translations/en-GB.json @@ -144,7 +144,11 @@ "formUnavailable": { "title": "Sorry, this form is unavailable", "subHeading": "'[[formTitle]]' is no longer available.", - "contactDetailsHeading": "Contact details for [[organisationName]]" + "contactDetailsHeading": "Contact details for [[organisationName]]", + "telephone": "Telephone", + "callCharges": "Find out about call charges", + "email": "Email", + "onlineContactForm": "Online contact form" } }, diff --git a/src/server/plugins/engine/models/unavailable-view-model.ts b/src/server/plugins/engine/models/unavailable-view-model.ts index d3c3b7ecb..893415b28 100644 --- a/src/server/plugins/engine/models/unavailable-view-model.ts +++ b/src/server/plugins/engine/models/unavailable-view-model.ts @@ -4,7 +4,7 @@ import { type FormMetadataContact } from '@defra/forms-model' -import { t } from '~/src/server/plugins/engine/i18n/index.js' +import { createFormTranslator } from '~/src/server/plugins/engine/i18n/createFormTranslator.js' import { getAvailableLanguages } from '~/src/server/plugins/engine/i18n/languages.js' export interface UnavailableViewModel { @@ -12,29 +12,28 @@ export interface UnavailableViewModel { formTitle: string organisationName: string contact?: FormMetadataContact - t: ( - key: string, - language: string, - options?: Record - ) => string + t: (key: string, options?: Record) => string language: string languages: { code: string; name: string }[] } +function getTranslation(t: (key: string, options?: Record) => string, key: string, fallback: string) { + +} + export function unavailableViewModel( metadata: FormMetadata, definition: FormDefinition | undefined, language: string ): UnavailableViewModel { - const formTranslations = definition - ? definition.metadata.translations[language] - : {} + const translator = createFormTranslator(metadata, definition, language) + const { t } = translator return { - pageTitle: t('pages.formUnavailable.title', language), - formTitle: metadata.title, + pageTitle: t('pages.formUnavailable.title'), + formTitle: t('form.title') ?? metadata.title, organisationName: metadata.organisation, contact: metadata.contact, - t, + t: translator.t, language, // Always get Welsh and English languages: getAvailableLanguages({ diff --git a/src/server/plugins/engine/pageControllers/FileUploadPageController.ts b/src/server/plugins/engine/pageControllers/FileUploadPageController.ts index dc730130e..ed3d98821 100644 --- a/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +++ b/src/server/plugins/engine/pageControllers/FileUploadPageController.ts @@ -286,6 +286,7 @@ export class FileUploadPageController extends QuestionPageController { uploadingLabel: t('components.fileUploadField.uploading') } + console.log('formComponent.model', formComponent.model) const index = components.indexOf(formComponent) const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl) diff --git a/src/server/plugins/engine/views/unavailable.html b/src/server/plugins/engine/views/unavailable.html index b0d512bcf..e773ffb42 100644 --- a/src/server/plugins/engine/views/unavailable.html +++ b/src/server/plugins/engine/views/unavailable.html @@ -3,7 +3,9 @@ {% block content %}
-

{{ t('pages.formUnavailable.title', language) }}

+

{{ t('pages.formUnavailable.title') }}

+

{{ t('pages.formUnavailable.subHeading', { formTitle: formTitle }) }}

+

{{ t('pages.formUnavailable.contactDetailsHeading', { organisationName: organisationName }) }}

{% if contact.phone %}

Telephone

From 66abc2371797a43dfa9f65c68b28270aa7d7ea73 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Wed, 5 Aug 2026 13:41:56 +0100 Subject: [PATCH 4/7] Final translation elements --- .../plugins/engine/form-availability.test.ts | 5 +++ .../plugins/engine/form-availability.ts | 6 ++- .../models/unavailable-view-model.test.ts | 33 +++++++++++----- .../engine/models/unavailable-view-model.ts | 13 +++---- .../FileUploadPageController.ts | 1 - .../engine/unavailable-response.test.ts | 38 +++++++++++++++---- .../plugins/engine/views/unavailable.html | 19 ++++++---- 7 files changed, 79 insertions(+), 36 deletions(-) diff --git a/src/server/plugins/engine/form-availability.test.ts b/src/server/plugins/engine/form-availability.test.ts index 341c76275..10f52e2d5 100644 --- a/src/server/plugins/engine/form-availability.test.ts +++ b/src/server/plugins/engine/form-availability.test.ts @@ -13,6 +13,7 @@ describe('form-availability', () => { expect(() => assertFormAvailable( { ...metadata, offline: false }, + undefined, FormStatus.Live, false ) @@ -20,6 +21,7 @@ describe('form-availability', () => { expect(() => assertFormAvailable( { ...metadata, offline: undefined }, + undefined, FormStatus.Live, false ) @@ -30,6 +32,7 @@ describe('form-availability', () => { expect(() => assertFormAvailable( { ...metadata, offline: false }, + undefined, FormStatus.Draft, true ) @@ -37,6 +40,7 @@ describe('form-availability', () => { expect(() => assertFormAvailable( { ...metadata, offline: undefined }, + undefined, FormStatus.Live, true ) @@ -47,6 +51,7 @@ describe('form-availability', () => { expect(() => assertFormAvailable( { ...metadata, offline: true }, + undefined, FormStatus.Live, false ) diff --git a/src/server/plugins/engine/form-availability.ts b/src/server/plugins/engine/form-availability.ts index 6bcb1f67a..d040fa6ea 100644 --- a/src/server/plugins/engine/form-availability.ts +++ b/src/server/plugins/engine/form-availability.ts @@ -1,4 +1,8 @@ -import { FormStatus, type FormDefinition, type FormMetadata } from '@defra/forms-model' +import { + FormStatus, + type FormDefinition, + type FormMetadata +} from '@defra/forms-model' import Boom from '@hapi/boom' export interface OfflineBoomData { diff --git a/src/server/plugins/engine/models/unavailable-view-model.test.ts b/src/server/plugins/engine/models/unavailable-view-model.test.ts index bc8f2ad80..2f7d9b909 100644 --- a/src/server/plugins/engine/models/unavailable-view-model.test.ts +++ b/src/server/plugins/engine/models/unavailable-view-model.test.ts @@ -1,24 +1,37 @@ -import { type FormMetadata } from '@defra/forms-model' - +import { CY, EN_GB } from '~/src/server/constants.js' import { unavailableViewModel } from '~/src/server/plugins/engine/models/unavailable-view-model.js' import { metadata } from '~/test/fixtures/form.js' describe('unavailableViewModel', () => { it('should return the correct view model with basic metadata', () => { - const result = unavailableViewModel(metadata) + const result = unavailableViewModel(metadata, undefined, EN_GB) expect(result).toEqual({ pageTitle: 'Sorry, this form is unavailable', formTitle: 'Test form', + language: 'en-GB', + languages: [ + { code: 'en-GB', name: 'English' }, + { code: 'cy', name: 'Cymraeg' } + ], organisationName: 'Defra', - phoneLines: undefined + contact: undefined, + context: expect.any(Object) }) }) - it('should strip the organisation suffix if present', () => { - const result = unavailableViewModel({ - ...metadata, - organisation: 'Rural Payments Agency - RPA' - } as FormMetadata) - expect(result.organisationName).toBe('Rural Payments Agency - RPA') + it('should handle Welsh', () => { + const result = unavailableViewModel(metadata, undefined, CY) + expect(result).toEqual({ + pageTitle: "Mae'n ddrwg gennyf, nid yw'r ffurflen hon ar gael", + formTitle: 'Test form', + language: 'cy', + languages: [ + { code: 'en-GB', name: 'English' }, + { code: 'cy', name: 'Cymraeg' } + ], + organisationName: 'Defra', + contact: undefined, + context: expect.any(Object) + }) }) }) diff --git a/src/server/plugins/engine/models/unavailable-view-model.ts b/src/server/plugins/engine/models/unavailable-view-model.ts index 893415b28..b79643b48 100644 --- a/src/server/plugins/engine/models/unavailable-view-model.ts +++ b/src/server/plugins/engine/models/unavailable-view-model.ts @@ -6,34 +6,31 @@ import { import { createFormTranslator } from '~/src/server/plugins/engine/i18n/createFormTranslator.js' import { getAvailableLanguages } from '~/src/server/plugins/engine/i18n/languages.js' +import { type Translator } from '~/src/server/plugins/engine/types/index.js' export interface UnavailableViewModel { pageTitle: string formTitle: string organisationName: string contact?: FormMetadataContact - t: (key: string, options?: Record) => string + context: { translator: Translator } language: string languages: { code: string; name: string }[] } -function getTranslation(t: (key: string, options?: Record) => string, key: string, fallback: string) { - -} - export function unavailableViewModel( metadata: FormMetadata, definition: FormDefinition | undefined, language: string ): UnavailableViewModel { const translator = createFormTranslator(metadata, definition, language) - const { t } = translator + const { t, tForm } = translator return { pageTitle: t('pages.formUnavailable.title'), - formTitle: t('form.title') ?? metadata.title, + formTitle: tForm('title') || metadata.title, organisationName: metadata.organisation, contact: metadata.contact, - t: translator.t, + context: { translator }, language, // Always get Welsh and English languages: getAvailableLanguages({ diff --git a/src/server/plugins/engine/pageControllers/FileUploadPageController.ts b/src/server/plugins/engine/pageControllers/FileUploadPageController.ts index 2c548e605..57db13145 100644 --- a/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +++ b/src/server/plugins/engine/pageControllers/FileUploadPageController.ts @@ -294,7 +294,6 @@ export class FileUploadPageController extends QuestionPageController { noFileChosenText: t('components.fileUploadField.noFileChosen') } - console.log('formComponent.model', formComponent.model) const index = components.indexOf(formComponent) const proxyUrl = getProxyUrlForLocalDevelopment(upload?.uploadUrl) diff --git a/src/server/plugins/engine/unavailable-response.test.ts b/src/server/plugins/engine/unavailable-response.test.ts index d5aca4118..7dc3e116a 100644 --- a/src/server/plugins/engine/unavailable-response.test.ts +++ b/src/server/plugins/engine/unavailable-response.test.ts @@ -6,7 +6,9 @@ import { type Server } from '@hapi/hapi' +import { EN_GB } from '~/src/server/constants.js' import * as availability from '~/src/server/plugins/engine/form-availability.js' +import { type Translator } from '~/src/server/plugins/engine/i18n/types.js' import * as viewModel from '~/src/server/plugins/engine/models/unavailable-view-model.js' import { registerUnavailableResponse } from '~/src/server/plugins/engine/unavailable-response.js' import { metadata } from '~/test/fixtures/form.js' @@ -56,7 +58,14 @@ describe('registerUnavailableResponse', () => { statusCode: 503, data: offlineData }) - const mockRequest = { response: mockResponse } as Request + const mockRequest = { + response: mockResponse, + server: { + plugins: { + 'forms-engine-plugin': {} + } + } + } as Request const mockViewResponse = { header: jest.fn().mockReturnThis(), @@ -75,18 +84,31 @@ describe('registerUnavailableResponse', () => { .mockReturnValue({ pageTitle: 'Unavailable', formTitle: 'Test', - organisationName: 'Defra' + organisationName: 'Defra', + language: EN_GB, + languages: [ + { code: 'en-GB', name: 'English' }, + { code: 'cy', name: 'Cymraeg' } + ], + context: { translator: {} as unknown as Translator } }) const result = await extensionHandler(mockRequest, mockH) expect(availability.isOfflineBoom).toHaveBeenCalledWith(mockResponse) - expect(viewModel.unavailableViewModel).toHaveBeenCalledWith(metadata) - expect(mockH.view).toHaveBeenCalledWith('unavailable', { - pageTitle: 'Unavailable', - formTitle: 'Test', - organisationName: 'Defra' - }) + expect(viewModel.unavailableViewModel).toHaveBeenCalledWith( + metadata, + undefined, + 'en-GB' + ) + expect(mockH.view).toHaveBeenCalledWith( + 'unavailable', + expect.objectContaining({ + pageTitle: 'Unavailable', + formTitle: 'Test', + organisationName: 'Defra' + }) + ) expect(mockViewResponse.header).toHaveBeenCalledWith( 'Cache-Control', 'no-store, no-cache, must-revalidate' diff --git a/src/server/plugins/engine/views/unavailable.html b/src/server/plugins/engine/views/unavailable.html index e773ffb42..4ce14a5a6 100644 --- a/src/server/plugins/engine/views/unavailable.html +++ b/src/server/plugins/engine/views/unavailable.html @@ -1,5 +1,8 @@ {% extends baseLayoutPath %} +{% set t = context.translator.t %} +{% set tForm = context.translator.tForm %} + {% block content %}
@@ -8,25 +11,25 @@

{{ t('pages.formUnavailable.title') }}

{{ t('pages.formUnavailable.contactDetailsHeading', { organisationName: organisationName }) }}

{% if contact.phone %} -

Telephone

+

{{ t('pages.formUnavailable.telephone') }}

- {{ contact.phone | markdown(3) | safe }} + {{ tForm('contact.phone') | markdown(3) | safe }}
-

Find out about call charges

+

{{ t('pages.formUnavailable.callCharges') }}

{% endif %} {% if contact.email %} -

Email

+

{{ t('pages.formUnavailable.email') }}

{% endif %} {% if contact.online %} -

Online contact form

+

{{ t('pages.formUnavailable.onlineContactForm') }}

{% endif %}
From 5ff54d1c986bf19c1aea4f2a693322d3995c77bb Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Wed, 5 Aug 2026 16:59:22 +0100 Subject: [PATCH 5/7] Fixes location instructions when no JS --- src/server/plugins/engine/components/LocationFieldBase.ts | 4 +++- .../plugins/engine/components/LocationFieldHelpers.ts | 8 +++++++- src/server/plugins/engine/i18n/types.ts | 1 + .../engine/views/components/_location-field-base.html | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/server/plugins/engine/components/LocationFieldBase.ts b/src/server/plugins/engine/components/LocationFieldBase.ts index 34a9905a4..22480c3b5 100644 --- a/src/server/plugins/engine/components/LocationFieldBase.ts +++ b/src/server/plugins/engine/components/LocationFieldBase.ts @@ -125,9 +125,11 @@ export abstract class LocationFieldBase extends FormComponent { const viewModel = super.getViewModel(context) if (this.instructionText) { + const { tComponent } = context.translator return { ...viewModel, - instructionText: this.instructionText + instructionText: + tComponent(this.def, 'instructionText') || this.instructionText } } diff --git a/src/server/plugins/engine/components/LocationFieldHelpers.ts b/src/server/plugins/engine/components/LocationFieldHelpers.ts index 92b1d8de6..14431d9bc 100644 --- a/src/server/plugins/engine/components/LocationFieldHelpers.ts +++ b/src/server/plugins/engine/components/LocationFieldHelpers.ts @@ -198,9 +198,15 @@ export function getLocationFieldViewModel( } if (component.options.instructionText) { + const { t, tComponent } = context.translator + return { ...result, - instructionText: component.options.instructionText + instructionSummary: t('components.locationFieldBase.howToFind'), + instructionText: + tComponent(component.def, 'instructionText') || + component.options.instructionText, + t } } diff --git a/src/server/plugins/engine/i18n/types.ts b/src/server/plugins/engine/i18n/types.ts index f9aaf3cfb..89efed627 100644 --- a/src/server/plugins/engine/i18n/types.ts +++ b/src/server/plugins/engine/i18n/types.ts @@ -24,6 +24,7 @@ export type FormDefinitionTranslations = Record< shortDescription: string errorDescription: string paymentDescription: string + instructionText: string }> > sections: Record>> diff --git a/src/server/plugins/engine/views/components/_location-field-base.html b/src/server/plugins/engine/views/components/_location-field-base.html index 59c2f040d..cc600194e 100644 --- a/src/server/plugins/engine/views/components/_location-field-base.html +++ b/src/server/plugins/engine/views/components/_location-field-base.html @@ -61,7 +61,7 @@ {% set detailsHtml %} {% if component.model.instructionText %} {{ govukDetails({ - summaryText: t('components.locationFieldBase.howToFind'), + summaryText: component.model.instructionSummary, html: component.model.instructionText | markdown | safe, classes: "govuk-!-margin-top-3" }) }} From 479f13d81f56afe54f6aa9526d2b983b41721a22 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 7 Aug 2026 13:09:53 +0100 Subject: [PATCH 6/7] Removed unnecessary fallback --- src/server/plugins/engine/components/LocationFieldHelpers.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/server/plugins/engine/components/LocationFieldHelpers.ts b/src/server/plugins/engine/components/LocationFieldHelpers.ts index 14431d9bc..3d66cdf29 100644 --- a/src/server/plugins/engine/components/LocationFieldHelpers.ts +++ b/src/server/plugins/engine/components/LocationFieldHelpers.ts @@ -203,9 +203,7 @@ export function getLocationFieldViewModel( return { ...result, instructionSummary: t('components.locationFieldBase.howToFind'), - instructionText: - tComponent(component.def, 'instructionText') || - component.options.instructionText, + instructionText: tComponent(component.def, 'instructionText'), t } } From bbc3cab35aef5d67f7d9ecf3d4a18e7e2e330027 Mon Sep 17 00:00:00 2001 From: Jez Barnsley Date: Fri, 7 Aug 2026 13:28:37 +0100 Subject: [PATCH 7/7] Reinstated fallback --- src/server/plugins/engine/components/LocationFieldHelpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/plugins/engine/components/LocationFieldHelpers.ts b/src/server/plugins/engine/components/LocationFieldHelpers.ts index 3d66cdf29..14431d9bc 100644 --- a/src/server/plugins/engine/components/LocationFieldHelpers.ts +++ b/src/server/plugins/engine/components/LocationFieldHelpers.ts @@ -203,7 +203,9 @@ export function getLocationFieldViewModel( return { ...result, instructionSummary: t('components.locationFieldBase.howToFind'), - instructionText: tComponent(component.def, 'instructionText'), + instructionText: + tComponent(component.def, 'instructionText') || + component.options.instructionText, t } }