Skip to main content

Making GOV.UK Pay available in Welsh

Posted by: , Posted on: - Categories: GOV.UK

GOV.UK Pay Welshe translation on screen

Over 850,000 people speak Welsh and many UK government services are available in this language.

Some services asked us if we could offer GOV.UK Pay payment pages in Welsh and the Healthcare Inspectorate Wales is the first organisation to use the feature. Here’s how we did it.

Choosing Welsh payment pages

When a service wants to send a user to GOV.UK Pay to pay for something, the service creates a payment by calling our API with some JSON. A request to create a card payment looks like this:


  "amount": 2500,
  "description": "Application fee",

  "reference": "123456",

  "return_url": "

We added an optional new "language" property to allow services to say they want to display payment pages in Welsh:


   "amount": 2500,

   "description": "Application fee",

   "reference": "123456",

   "return_url": "",

   "language": "cy"


Here, "cy" is the ISO 639-1 language code for Welsh. Using the ISO standard helps with consistency and interoperability.

A service can also send "en" to get English payment pages. If a service does not send a "language" property at all, we default to English to keep backwards compatibility.

A service will usually create a payment with GOV.UK Pay immediately before the user is ready to pay. The service should already know whether the user prefers English or Welsh by this point, so it makes sense for the service to tell GOV.UK Pay what to use.

Internationalisation and localisation

You might be surprised to learn that translating text into Welsh was only a small part of the work. Most of the effort went into making GOV.UK Pay easy to translate. Up until this point, GOV.UK Pay was only available in English and a lot of our code assumed this was the only language.

Adapting software to make it easy to translate is called internationalisation, or i18n for short (because there are 18 letters between the initial ‘i’ and the last ‘n’). Translating into a different language is known as localisation or l10n.

Most of the text users see on payment pages is fixed and predictable. For example, ‘Name on card’ is always the same for every payment. A phrase like ‘Your payment has been declined’, which does not appear during every transaction, is also always the same when it does appear.

A gif showing a page alternating from English to Welsh

Text like this is usually part of the HTML we send to users’ browsers. We use the Nunjucks templating language to produce this HTML. To support Welsh, we first had to internationalise our templates.

Fortunately, we were already using the i18n Node.js package, which provides internationalisation support for templates. Rather than containing the actual text users see, internationalised templates contain language-independent references to the terms to display.

Here’s what a Nunjucks template that displays the “Name on card” label looks like if it’s not internationalised:

<label>Name on card</label>

Here’s what it looks like when it’s internationalised:

<label>{{ __("cardDetails.cardholderName") }}</label>

"cardDetails.cardholderName" is a reference to a term in a JSON file named en.json, which contains the English localisations of all the terms:


 "cardDetails": {

    "cardNo": "Card number",

    "cardholderName": "Name on card",

    "billingAddress": "Billing address"


  "commonButtons": {

    "continueButton": "Continue",

    "confirmButton": "Confirm payment"



We had already internationalised many of our templates like this. However, we still had some English text in some of our templates. We internationalised these remaining templates, moving the English text to the en.json file.

We also found some English text outside of our templates. Some of our error messages were produced by controllers and passed to templates when necessary. Our browser-side Ajax card number validation code displayed content directly from JSON responses. It took a while to track down all the English text!

Once we finished, we had a single en.json file containing all our English text. This made it easy for us to send content to a Welsh translator.

After the text was translated, we put the Welsh translations into a cy.json file. Then we added some code to switch the payment pages between English and Welsh for each payment. This looks at the ISO language code specified when the payment was created and matches it to the appropriate localisation JSON file.

Translating dynamic text into Welsh

In addition to fixed text, our payment pages also contain dynamic text we do not control. For example, each payment page has a summary box with a description of the payment and a header with the name of the service.

Some of this dynamic text, like the payment description, is provided by the service when they create the payment. We did not need to do anything here. We tell services to provide a Welsh description whenever they create a Welsh payment.

The service name was trickier. Service names are not specific to individual payments and they are not part of the code. Service names are stored in a database and each service can change its name whenever it wants.

In the database, there’s a 'services' table, which has one row for each service. This table has a column called 'name' to hold the service name.

It would have been easy to add another column, say 'welsh_service_name', for the Welsh translation.But this would not be flexible. If we added support for more languages, we’d have to add more and more columns. Most services probably would not be available in all the new languages, so the new columns would have NULL values in most rows.

Instead, we decided to add a new 'service_names' table. This table has three important columns:

  • 'service_id' is a foreign key referencing the service in the services table
  • 'language' is an ISO language code like “en” or “cy”
  • 'name' is the translated service name

To add a Welsh translation of a service name, we add a new row to the table. If a service doesn’t have a Welsh translation of its service name, it doesn’t need a row.

If we add support for more languages in the future, the 'service_names' table can hold the translated service names in that language without any changes to the table’s schema.

We added some code to display the appropriate service name on the payment pages based on the language of the payment. If the page is shown in Welsh but we do not have a Welsh translation of the service name, we fall back to English.

Finally, we added a feature to the GOV.UK Pay admin site to allow each service to add a Welsh translation of its name, storing it in the 'service_names' table. Unlike English, the Welsh translation is optional.

Adding support for more languages in future

We made design choices that will make it easier to add support for new languages in the future. If we wanted to add another language we’d have to:

  • add the new language’s ISO code to the list of ones we allow when creating a payment
  • add a localisation JSON file with the translations
  • update the admin site to allow services to set a translated service name in the new language

Importantly, we would not have to make any changes to:

  • our Nunjucks templates
  • the code that serves our payment pages
  • our database schema

We would have to make more changes to support languages that are very different to English and Welsh. For example, Arabic is written from right to left, which would require changes to our templates. But the support we have now is good enough for many languages spoken by people all around the world.

If you have any questions about how to integrate Welsh language into your service leave us a comment below.

Sharing and comments

Share this page


  1. Comment by Sam Read posted on

    This is all great. Have you got any thoughts about how you might guard against someone adding an untranslated string into the templates in the future?

    • Replies to Sam Read>

      Comment by Alex Bishop posted on

      Hey Sam. Thanks for your question. We don’t have any technical measures to stop an untranslated string slipping in to the templates at the moment. Hopefully anyone trying to add one would notice that the templates don’t have literal strings in them and realise they’re making a mistake! If not, I would hope it would be spotted in code review (we never release code to production unless at least two people have looked at it).

      If it does become a problem, we could try to write a pre-commit hook to analyse the template and look for non-internationalised text. But that might be tricky to get right: with something like <input type="text" name="foo" value="This text actually appears on the page"> the ‘value’ attribute needs to be internationalised but the ‘type’ and ‘name’ attributes don’t because they never get shown to the user.

  2. Comment by Owen Blacker posted on

    So the obvious next question is when does Gàidhlig get the treatment that Cymraeg has benefitted from? ?

    • Replies to Owen Blacker>

      Comment by Alex Bishop posted on

      It’s all driven by demand. So far, I’m not aware we’ve had any services request we provide payment pages in Gaelic.

      As discussed in the post, adding a new language is now not technically difficult. If a service is able to supply Gaelic translations of all the English terms (essentially, take an en.json file and produce an equivalent gd.json file), we could have Gaelic payment pages up and running quite quickly.

      • Replies to Alex Bishop>

        Comment by Owen Blacker posted on

        Nice; sounds like one for Bòrd na Gàidhlig to get onto ?

        Diolch am eich ateb!