Skip to main content
  1. X-GOVUK projects
  2. GOV.UK Form Builder
  3. Localisation

Localisation

The simplest way of adding textual information like labels and hints to forms is to provide strings as arguments to the form helpers.

= f.govuk_text_field :name, label: { text: 'Your full name' }

On larger, more-complex projects, copy is spread throughout the application and often duplicated, making it difficult for content designers to make changes.

Many teams approach this problem by making use of Rails’ excellent localisation functionality, allowing text to be stored in locale dictionaries. This allows editors to make changes without the risk of breaking templates and having to learn templating languages and hunt down content.

You can use HTML in locales if you append the suffix _html to the key.

Populating label, caption and hint text from the localisation data

Note that despite the text attribute being omitted from the label options hash, the other display and formatting parameters can be supplied and work in the normal manner.

Localisation data

helpers:
  label:
    person:
      favourite_kind_of_hat: Which style of hat do you prefer?
  caption:
    person:
      favourite_kind_of_hat: Fashion choices
  hint:
    person:
      favourite_kind_of_hat: |-
        Trilby, Stetson, Deerstalker, Fez, Top and Beret are
        the most-fashionable

Input

= f.govuk_text_field :favourite_kind_of_hat, label: { size: 'l' }
<%= f.govuk_text_field :favourite_kind_of_hat, label: { size: 'l' } %>

Output

Trilby, Stetson, Deerstalker, Fez, Top and Beret are the most-fashionable
<div class="govuk-form-group">
  <label for="person-favourite-kind-of-hat-field" class="govuk-label govuk-label--l">
    <span class="govuk-caption-m">
      Fashion choices
    </span>
    Which style of hat do you prefer?
  </label>
  <div class="govuk-hint" id="person-favourite-kind-of-hat-hint">
    Trilby, Stetson, Deerstalker, Fez, Top and Beret are
    the most-fashionable
  </div>
  <input id="person-favourite-kind-of-hat-field" class="govuk-input" aria-describedby="person-favourite-kind-of-hat-hint" type="text" name="person[favourite_kind_of_hat]" />
</div>

Radio and check box labels use a special key in the locale dictionary composed from the attribute name and the suffix _options. This makes it possible to localise the fieldset legend and each of the individual choices separately.

Populating radio labels from the localisation data

Data

contact_type = Struct.new(:value, keyword_init: true)
contact_types = [
  contact_type.new(value: :email),
  contact_type.new(value: :phone),
  contact_type.new(value: :letter)
]

Localisation data

helpers:
  caption:
    person:
      contact_type: Contacting you
  legend:
    person:
      contact_type: What is your preferred method of contact?
  hint:
    person:
      contact_type_html: |-
        We recommend <strong>email</strong> at the moment
      contact_type_options:
        letter: Please be aware that this can delay your application
  label:
    person:
      contact_type_options:
        email: Email
        phone: Mobile or landline phone
        letter: Postal letter

Input

= f.govuk_collection_radio_buttons :contact_type,
  contact_types, :value, nil
<%= f.govuk_collection_radio_buttons :contact_type, contact_types, :value, nil %>

Output

Contacting you What is your preferred method of contact?
We recommend email at the moment
Please be aware that this can delay your application
<div class="govuk-form-group">
  <fieldset class="govuk-fieldset" aria-describedby="person-contact-type-hint">
    <legend class="govuk-fieldset__legend govuk-fieldset__legend--m">
      <span class="govuk-caption-m">
        Contacting you
      </span>
      What is your preferred method of contact?
    </legend>
    <input value="" autocomplete="off" type="hidden" name="person[contact_type]" id="person_contact_type" />
    <div class="govuk-hint" id="person-contact-type-hint">
      We recommend <strong>
        email
      </strong>
      at the moment
    </div>
    <div class="govuk-radios" data-module="govuk-radios">
      <div class="govuk-radios__item">
        <input id="person-contact-type-email-field" class="govuk-radios__input" type="radio" value="email" name="person[contact_type]" />
        <label for="person-contact-type-email-field" class="govuk-label govuk-radios__label">
          Email
        </label>
      </div>
      <div class="govuk-radios__item">
        <input id="person-contact-type-phone-field" class="govuk-radios__input" type="radio" value="phone" name="person[contact_type]" />
        <label for="person-contact-type-phone-field" class="govuk-label govuk-radios__label">
          Mobile or landline phone
        </label>
      </div>
      <div class="govuk-radios__item">
        <input id="person-contact-type-letter-field" aria-describedby="person-contact-type-letter-hint" class="govuk-radios__input" type="radio" value="letter" name="person[contact_type]" />
        <label for="person-contact-type-letter-field" class="govuk-label govuk-radios__label">
          Postal letter
        </label>
        <div class="govuk-hint govuk-radios__hint" id="person-contact-type-letter-hint">
          Please be aware that this can delay your application
        </div>
      </div>
    </div>
  </fieldset>
</div>

Populating check box labels from the localisation data

Data

department = Struct.new(:id, keyword_init: true)
departments = [
  department.new(id: :sales),
  department.new(id: :marketing),
  department.new(id: :finance),
  department.new(id: :digital)
]

Localisation data

helpers:
  caption:
    person:
      department_ids: Departments
  legend:
    person:
      department_ids: Which department do you work in?
  hint:
    person:
      department_ids: Select all that apply
  label:
    person:
      department_ids_options:
        sales: Sales
        marketing: Marketing
        finance: Finance
        digital: Digital and Technology

Input

= f.govuk_collection_check_boxes :department_ids,
  departments_collection, :id, :id
<%= f.govuk_collection_check_boxes :department_ids, departments_collection, :id, :id %>

Output

Departments Which department do you work in?
Select all that apply
<div class="govuk-form-group">
  <fieldset class="govuk-fieldset" aria-describedby="person-department-ids-hint">
    <legend class="govuk-fieldset__legend govuk-fieldset__legend--m">
      <span class="govuk-caption-m">
        Departments
      </span>
      Which department do you work in?
    </legend>
    <div class="govuk-hint" id="person-department-ids-hint">
      Select all that apply
    </div>
    <div class="govuk-checkboxes" data-module="govuk-checkboxes">
      <input type="hidden" name="person[department_ids][]" value="" autocomplete="off" />
      <div class="govuk-checkboxes__item">
        <input id="person-department-ids-sales-field" class="govuk-checkboxes__input" type="checkbox" value="sales" name="person[department_ids][]" />
        <label for="person-department-ids-sales-field" class="govuk-label govuk-checkboxes__label">
          Sales
        </label>
      </div>
      <div class="govuk-checkboxes__item">
        <input id="person-department-ids-marketing-field" class="govuk-checkboxes__input" type="checkbox" value="marketing" name="person[department_ids][]" />
        <label for="person-department-ids-marketing-field" class="govuk-label govuk-checkboxes__label">
          Marketing
        </label>
      </div>
      <div class="govuk-checkboxes__item">
        <input id="person-department-ids-finance-field" class="govuk-checkboxes__input" type="checkbox" value="finance" name="person[department_ids][]" />
        <label for="person-department-ids-finance-field" class="govuk-label govuk-checkboxes__label">
          Finance
        </label>
      </div>
      <div class="govuk-checkboxes__item">
        <input id="person-department-ids-digital-field" class="govuk-checkboxes__input" type="checkbox" value="digital" name="person[department_ids][]" />
        <label for="person-department-ids-digital-field" class="govuk-label govuk-checkboxes__label">
          Digital and Technology
        </label>
      </div>
    </div>
  </fieldset>
</div>

A more comprehensive example of localised check boxes

The form builder does not keep track of its contents so to ensure the error summary correctly links to the first checkbox link_errors must be set to true

Localisation data

helpers:
  legend:
    person:
      movie_genres: Which movie genres do you prefer?
  hint:
    person:
      other_movie_genres: You can enter as many as you like
      movie_genres: Select all that apply
      movie_genres_options:
        action: War, espionage, martial arts
        comedy: Parody, dark comedy
        horror: Zombies, slasher, found footage
  label:
    person:
      other_movie_genres: Which additional movie genres do you like?
      movie_genres_options:
        action: Action
        comedy: Comedy
        horror: Horror
        other: Other

Input

= f.govuk_check_boxes_fieldset :movie_genres do
  = f.govuk_check_box :movie_genres, :action, link_errors: true
  = f.govuk_check_box :movie_genres, :comedy
  = f.govuk_check_box :movie_genres, :horror
  = f.govuk_check_box :movie_genres, :other do
    = f.govuk_text_field :other_movie_genres
<%= f.govuk_check_boxes_fieldset :movie_genres do %>
  <%= f.govuk_check_box :movie_genres, :action, link_errors: true %>
  <%= f.govuk_check_box :movie_genres, :comedy %>
  <%= f.govuk_check_box :movie_genres, :horror %>
  <%= f.govuk_check_box :movie_genres, :other do %>
    <%= f.govuk_text_field :other_movie_genres %>
  <% end %>
<% end %>

Output

Which movie genres do you prefer?
Select all that apply
War, espionage, martial arts
Parody, dark comedy
Zombies, slasher, found footage
You can enter as many as you like
<div class="govuk-form-group">
  <fieldset class="govuk-fieldset" aria-describedby="person-movie-genres-hint">
    <legend class="govuk-fieldset__legend govuk-fieldset__legend--m">
      Which movie genres do you prefer?
    </legend>
    <div class="govuk-hint" id="person-movie-genres-hint">
      Select all that apply
    </div>
    <input value="" name="person[movie_genres][]" autocomplete="off" type="hidden" id="person_movie_genres" />
    <div class="govuk-checkboxes" data-module="govuk-checkboxes">
      <div class="govuk-checkboxes__item">
        <input id="person-movie-genres-action-field" class="govuk-checkboxes__input" aria-describedby="person-movie-genres-action-hint" type="checkbox" value="action" name="person[movie_genres][]" />
        <label for="person-movie-genres-action-field" class="govuk-label govuk-checkboxes__label">
          Action
        </label>
        <div class="govuk-hint govuk-checkboxes__hint" id="person-movie-genres-action-hint">
          War, espionage, martial arts
        </div>
      </div>
      <div class="govuk-checkboxes__item">
        <input id="person-movie-genres-comedy-field" class="govuk-checkboxes__input" aria-describedby="person-movie-genres-comedy-hint" type="checkbox" value="comedy" name="person[movie_genres][]" />
        <label for="person-movie-genres-comedy-field" class="govuk-label govuk-checkboxes__label">
          Comedy
        </label>
        <div class="govuk-hint govuk-checkboxes__hint" id="person-movie-genres-comedy-hint">
          Parody, dark comedy
        </div>
      </div>
      <div class="govuk-checkboxes__item">
        <input id="person-movie-genres-horror-field" class="govuk-checkboxes__input" aria-describedby="person-movie-genres-horror-hint" type="checkbox" value="horror" name="person[movie_genres][]" />
        <label for="person-movie-genres-horror-field" class="govuk-label govuk-checkboxes__label">
          Horror
        </label>
        <div class="govuk-hint govuk-checkboxes__hint" id="person-movie-genres-horror-hint">
          Zombies, slasher, found footage
        </div>
      </div>
      <div class="govuk-checkboxes__item">
        <input id="person-movie-genres-other-field" class="govuk-checkboxes__input" data-aria-controls="person-movie-genres-other-conditional" type="checkbox" value="other" name="person[movie_genres][]" />
        <label for="person-movie-genres-other-field" class="govuk-label govuk-checkboxes__label">
          Other
        </label>
      </div>
      <div class="govuk-checkboxes__conditional govuk-checkboxes__conditional--hidden" id="person-movie-genres-other-conditional">
        <div class="govuk-form-group">
          <label for="person-other-movie-genres-field" class="govuk-label">
            Which additional movie genres do you like?
          </label>
          <div class="govuk-hint" id="person-other-movie-genres-hint">
            You can enter as many as you like
          </div>
          <input id="person-other-movie-genres-field" class="govuk-input" aria-describedby="person-other-movie-genres-hint" type="text" name="person[other_movie_genres]" />
        </div>
      </div>
    </div>
  </fieldset>
</div>

Customising locale structure

There are many approaches to organising localisation data and while the default will work for most projects, sometimes a different approach can be beneficial. This is especially true when working with external localisation agencies or when dealing with large volumes of copy.

To customise the location of our localisation strings, we can configure the schema as part of the application’s initialisation process.

Contexts

There are four contexts supported by the form builder: label, legend, caption and hint. Custom locale schemas are configured using an array of symbols that match your locale structure.

The special value __context__ is used to represent the current translation context. It will automatically be replaced with either label, legend, caption or hint when the translation key is generated.

When retrieving a localised string the builder first checks whether a contextual schema has been set for the context. If there hasn’t, the localisation_schema_fallback key will be used. It is the only schema set by default.

Captions are rendered inside the corresponding label or legend tag. If the label or legend is hidden or not rendered, the caption won’t be either.

Configuration

Place your configuration in an initializer to ensure it’s loaded every time you start your Rails application.

GOVUKDesignSystemFormBuilder.configure do |conf|
  conf.localisation_schema_fallback = %i(helpers __context__)
  conf.localisation_schema_hint     = %i(copy descriptions __context__ subdivision)
end

Localisation data

helpers:
  label:
    person:
      role: What role do you play?

copy:
  descriptions:
    hint:
      subdivision:
        person:
          role: |-
            Roles may be achieved or ascribed or they can be accidental
            in different situations. An achieved role is a position
            that a person assumes voluntarily which reflects personal
            skills, abilities, and effort.

Input

= f.govuk_text_field :role, label: { size: 'm' }
<%= f.govuk_text_field :role, label: { size: 'm' } %>

Output

Roles may be achieved or ascribed or they can be accidental in different situations. An achieved role is a position that a person assumes voluntarily which reflects personal skills, abilities, and effort.
<div class="govuk-form-group">
  <label for="person-role-field" class="govuk-label govuk-label--m">
    What role do you play?
  </label>
  <div class="govuk-hint" id="person-role-hint">
    Roles may be achieved or ascribed or they can be accidental
    in different situations. An achieved role is a position
    that a person assumes voluntarily which reflects personal
    skills, abilities, and effort.
  </div>
  <input id="person-role-field" class="govuk-input" aria-describedby="person-role-hint" type="text" name="person[role]" />
</div>