Localization
Make a theme multilingual: translate interface strings with the t filter, and build locale-aware links and a language switcher
A site can publish its content in more than one language. The first segment of the URL sets the active language: /cy/whats-on renders the Welsh version of /whats-on. /fr/whats-on renders the French version. The site's default language has no prefix: /whats-on is the default language.
A theme needs to do two things to support this:
- Translate the interface strings the theme itself supplies: button labels, headings, the "Read more" you hardcoded. Basker already returns content typed into the admin in the active language. The theme only has to translate its own text.
- Keep links in the active language, so a visitor browsing in Welsh stays in Welsh throughout the site.
Most of the second point is automatic. The first uses the t filter and a set of translation files.
Translating interface strings
Wrap each piece of theme-supplied text in the t filter. Give it a key:
<a href="{{ event.url }}">{{ "event.read_more" | t }}</a>
<h2>{{ "home.upcoming_heading" | t }}</h2>Provide the translations in a locales/ folder at the root of the theme, one JSON file per language code. Each file is a flat map of key to translated string.
my-theme/
├── locales/
│ ├── en.json
│ ├── cy.json
│ └── fr.json
├── blocks/
├── layouts/
└── templates/locales/en.json:
{
"event.read_more": "Read more",
"home.upcoming_heading": "What's on"
}locales/cy.json:
{
"event.read_more": "Darllen mwy",
"home.upcoming_heading": "Beth sydd ymlaen"
}When a page renders, Basker first loads the file for the active language. If that file is missing or malformed, it tries the default language, then the other configured languages. The t filter looks up the key in the file that was loaded. If the key is absent, it returns the key unchanged. Keep the same keys in every language file.
Key names are yours to choose. Group them however suits the theme (nav.home, footer.copyright, event.book_now). Use the same keys across every language file.
Locale-aware links
Internal links stay in the active language on their own. Every record exposes a url (and relativePath) that already carries the language prefix when one applies:
{% for item in navigation %}
<a href="{{ item.relativePath }}">{{ item.title }}</a>
{% endfor %}
<a href="{{ event.url }}">{{ event.title }}</a>When you view the site at /cy/..., those links render as /cy/whats-on, /cy/events/moonclock, and so on. On the default language they render with no prefix. As long as links come from a record's url or relativePath, or from navigation, nothing extra is needed.
For links you build by hand (a hardcoded path, or a URL assembled from parts), prefix them with locale_prefix:
<a href="{{ locale_prefix }}/contact">{{ "nav.contact" | t }}</a>locale_prefix returns /cy (or /fr, and so on) on a non-default language, and an empty string on the default language. The same template works in every language as a result.
A language switcher
Use localization to list the available languages and identify the active one. Record paths already include the current locale prefix. Remove the prefix before you build links for the other languages:
{% assign l10n = localization %}
{% assign current_path = page.relativePath %}
{% if locale_prefix != blank %}
{% assign current_path = current_path | remove_first: locale_prefix %}
{% endif %}
{% if l10n.available_languages.size > 1 %}
<nav class="language-switcher" aria-label="Language">
{% for lang in l10n.available_languages %}
{% if lang == l10n.default_language %}
{% assign href = current_path %}
{% else %}
{% assign href = "/" | append: lang | append: current_path %}
{% endif %}
<a href="{{ href }}"{% if lang == l10n.current_language %} aria-current="true"{% endif %}>
{{ lang | upcase }}
</a>
{% endfor %}
</nav>
{% endif %}current_path is the unprefixed path of the current page. Add each language code to this path to link to the same page in that language. Give the default language no prefix.
Basker configures the available languages per site. The theme does not control this. localization.available_languages reflects that configuration. If a theme hardcodes a fixed list of languages, it will drift out of sync.
Reference
The functions and filter used here:
| Name | Returns |
|---|---|
{{ "key" | t }} | The translation for key in the active language, or the key itself if untranslated. See Liquid filters. |
locale | The active language code, e.g. cy. |
locale_prefix | /cy on a non-default language, empty string on the default. |
localization | { available_languages, current_language, default_language }. |
routes | { root_url }: the site root, prefixed for the active language. |
See Template context for the full list of global functions.
Related
- Liquid filters: the
tfilter reference. - Template context: every global function and the data records expose.
- Writing layouts: where the
<html lang>attribute and language switcher usually live.