Skip to content

feat/df-1065: Welsh translation for plugin#436

Open
jbarnsley10 wants to merge 120 commits into
mainfrom
feature/i18n-linting
Open

feat/df-1065: Welsh translation for plugin#436
jbarnsley10 wants to merge 120 commits into
mainfrom
feature/i18n-linting

Conversation

@jbarnsley10

@jbarnsley10 jbarnsley10 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Proposed change

Welsh translation for plugin

Jira ticket: DF-1065

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Misc. (documentation, build updates, etc)

Checklist

  • You have executed this code locally and it performs as expected.
  • You have added tests to verify your code works.
  • You have added code comments and JSDoc, where appropriate.
  • There is no commented-out code.
  • You have added developer docs in README.md and docs/* (where appropriate, e.g. new features).
  • The tests are passing (npm run test).
  • The linting checks are passing (npm run lint).
  • The code has been formatted (npm run format).

Adds buildValidationMessages(t) — a factory that accepts a language-bound
translation function and returns a set of Joi message templates (JoiExpressions
for lowerFirst-containing strings, plain template strings for the rest).

Also adds require('@defra/forms-model') to jest.setup.cjs to align Joi's
internal Symbol state across the babel-jest ESM/CJS module boundary; without
this, JoiExpression objects fail Template.isTemplate() checks inside Joi's
.messages() method when resetModules: true clears the registry between tests.
…key, lang)

Remove the lang parameter from getValidationConfig/getErrorTemplates in
NationalGridFieldNumberField and OsGridRefField; fix LocationFieldBase
constructor call that was passing model.language as an argument.
Static getAllPossibleErrors() now constructs the result directly using
the module-level t() at en-GB, matching the pattern used by
EastingNorthingField.
…l.language)

Remove free t() import from all five page controllers; replace every
t(key, lang/this.model.language, opts) call with this.model.t(key, opts).
Includes getBackLink lang var removal and all RepeatPageController
list-summary translation calls.
The two inline controller.model mocks in the dispatcher error tests
were missing the t() method after PaymentField.ts was updated to call
model.t() instead of the free t(key, lang) function.
Adds x-pirate locale (arrr), registers it in i18next, and wires up
simple-form-pirate.yaml in localFormsService. Visit /simple-form-pirate
to experience the high seas.
Apply pirate flavour to every remaining bland key — date field labels,
lat/long errors, address fields, button labels, repeater actions, and
payment strings. Comedy matters everywhere.

@Scullyon Scullyon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good from the parts that I've looked at!

Would be good to have some docs when you're happy with the solution and perhaps some diagrams.

Question about the translation caching: does it cache translations per form until the service is restarted? If so, when we start serving thousands of forms, is there a risk to memory?

"added_other": "Ychwanegwyd [[count]] lleoliad",
"validation": {
"descriptionRequired": "Rhowch ddisgrifiad ar gyfer lleoliad [[count]]",
"wrongCountry": "Rhaid i leoliad [[count]] fod y tu mewn i [[country]]"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing from the en-GB lookup. Perhaps a tool is required to scan the source code, extract all the required keys and then highlight any missing gaps?

{{ govukButton({
text: "Pay and submit" if paymentPending else ("Accept and submit" if isDeclaration else "Submit"),
classes: "prevent-multiple-clicks",
text: t('pages.summary.acceptAndSubmit') if isDeclaration else t('pages.summary.submit'),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to reduce the conditions here (eg if paymentPending is gone)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected logic. Somehow the additional condition got lost

return i18next.t(key, { lng: language, ...options })
}

export function createFormI18nInstance(formEnGb: BaseTranslations) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you're nearly done and happy with everything, it would be good to get some useful doc-comments on functions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few doc comments as well as the READMEs

@Scullyon

Copy link
Copy Markdown
Contributor

Looks good from the parts that I've looked at!

Would be good to have some docs when you're happy with the solution and perhaps some diagrams.

Question about the translation caching: does it cache translations per form until the service is restarted? If so, when we start serving thousands of forms, is there a risk to memory?

Have just seen that you've added a Markdown doc file, will take a look.

@alexluckett alexluckett left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since component method signatures require a Translator and FormDefinitionTranslations, it's worth adding both of those to src/server/plugins/engine/types/index.ts so they become importable by a consumer via the import from ./types.

docs/features/code-based/page-controllers.md needs updating to show the new getViewModel signature (with the new translator instance)

Comment on lines +123 to +125
const language =
(yar.get('language') as string | undefined) ?? options.language ?? 'en-GB'
const translator = formModel.createTranslator(language)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs updating to use the new getLanguage hook that the plugin consumer provides. They might not store the language in yar.

const language = resolveLanguage(summaryRequest)


return getAnswer(/** @type {Field} */ (component), context.relevantState)
const field = /** @type {Field} */ (component)
const translator = field.model.createTranslator()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will default to english as createTranslator receives no language preference, we need to use the translator already provided.

we should be able to change change this to const { translator } = context rather than creating a new one

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, plus also left in a fallback

Comment thread src/server/plugins/engine/helpers.ts Outdated

const answer = getAnswer(component as Field, globals.context.relevantState)
const field = component as Field
const translator = field.model.createTranslator()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will default to english as createTranslator receives no language preference, we need to use the translator already provided.

we should be able to change change this to const { translator } = globals.context rather than creating a new one

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, plus also left in a fallback

| undefined
}

export function setPageTitles(def: FormDefinition) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed on purpose?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The logic that handles 'page title' vs 'first question title' (for translation purposes) needs to know if the page title is set in the definition, or left blank. The call to setPageTitles was masking this so it was impossible to tell if the page title was originally blank.
The logic has moved elsewhere so the translator can determine which part to translate.

item: Item,
prop: keyof EntityTranslations['listItems'][string]
) => string
language: string

@alexluckett alexluckett Jul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

language can be removed, it's unused

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. It is used in runner, but I could extend the type in runner as not used in plugin

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants