Multilingual site
How an agent adds a second locale to a Meridian site with dev locale add, gives routes per-locale paths, translates interface strings and labels, seeds translated documents, adds a language switcher and verifies hreflang and the translated pages.
Each document has one shared structure and per-locale text. Fields that hold text (text, slug, portable_text) differ per locale; values such as numbers, selects, places and relations are shared by every locale and written once. A locale’s URLs carry a prefix (/cy/...) by default, and each route may give each locale its own path words.
An asset field is both: describe type shows it as translate locale because its per-use alt text and caption are per locale, but the chosen image and its focal point are shared. A cy block does not repeat the field, and giving it a different asset there replaces the image for every locale. For a different image per locale, declare the field with structure: per_locale (Localisation).
Adding a locale touches these files. When you forget one, check warns about those under “check warns” and says nothing about those under “check is silent”:
flowchart LR
add["dev locale add cy"] --> loc["config/locales.yaml"]
add --> site["sites/main/site.yaml"]
subgraph warned["check warns"]
labels["schema labels"]
strings["strings/cy.yaml<br/>(once the file exists)"]
end
subgraph silent["check is silent"]
ep["endpoints.yaml<br/>input labels"]
nav["seed/navigation<br/>item labels"]
mail["email templates"]
rt["routes.yaml paths"]
end
site --> you["You translate"]
you --> warned
you --> silentAdd the locale
engine-dmo dev locale add cy --label Cymraeg --fallback en --site main --apply # config/locales.yaml (written)
- { code: cy, id: loc_0vb8pv7bqm, label: Cymraeg, fallback: [en] } # config/sites/main/site.yaml (written)
locales:
enabled: [en, cy]
default: en The site’s locales.strategy decides the addresses: prefix (the default: every locale but the default under /<code>/), prefix-all, or domain with a host per locale in locales.domains. Set the new locale’s timezone and currency in locales.yaml if they differ from the source locale’s.
After this, check warns at every schema label without a cy translation (“the label of field photo has no translation for cy”). Add them: label: { en: Photo, cy: Llun }. Give the site’s name too:
engine-dmo dev config set sites/main/site.yaml site.name.cy "Croeso Banbury" dev config set rewrites the map in key order ({ cy: ..., en: ... }), which is harmless.
Give routes per-locale paths
# config/sites/main/routes.yaml
routes:
- type: walk
id: rte_34agwcmxkh # keep the id
uri: { en: "/walks/{slug}", cy: "/teithiau/{slug}" }
index: { en: /walks, cy: /teithiau }
paginate: 12
facets: [difficulty]
special:
search: { en: /search, cy: /chwilio } A route given per locale needs a value for every locale the site enables.
Translate the interface strings
Copy config/strings/en.yaml to config/strings/cy.yaml with the same keys. check says nothing about a locale with no strings file at all; it lists missing keys only once cy.yaml exists, so create the file first:
# config/strings/cy.yaml
strings:
a11y.skip: Neidio i'r prif gynnwys
nav.main: Prif
nav.footer: Troedyn
walks.count:
zero: "{count} taith"
one: "{count} daith"
two: "{count} daith"
few: "{count} taith"
many: "{count} taith"
other: "{count} taith" Plural strings take the locale’s CLDR categories (zero, one, two, few, many, other); Welsh uses all six, English one and other. check warns with the list of keys a locale is missing (cy is missing 14 of 18 strings: ...); a missing key falls back down the locale’s chain. Translate every key before handing back. Never machine-translate without telling the person.
Seed translated documents
# seed/documents/walk/cherwell-towpath.yaml
locales:
en:
title: Cherwell towpath
slug: cherwell-towpath
summary: A flat riverside walk along the canal towpath from the town centre.
distanceKm: 6.5
difficulty: easy
cy:
title: Llwybr tynnu Cherwell
slug: llwybr-tynnu-cherwell
summary: Taith wastad ar hyd llwybr tynnu'r gamlas o ganol y dref.
publish: [en, cy] The cy block gives only the translated text: distanceKm, difficulty and the photo are shared. A locale not in publish stays a draft, and its page does not exist in that locale.
The home page keeps the slug routes.yaml names in home: in every locale. With home: { type: page, slug: home }, the Welsh home page is the cy block with slug: home, served at /cy/; a cy slug such as hafan makes it an ordinary page at /cy/hafan, and /cy/ answers 404.
# seed/documents/page/home.yaml
locales:
en:
title: Welcome
slug: home
cy:
title: Croeso
slug: home
publish: [en, cy] Add the locale to seed/seed.yaml as well (locales: [en, cy]). That list limits only the analytics seed apply generates; documents load in every locale they give either way.
Translate what check does not
- Form labels. Each endpoint input’s
labelinconfig/sites/<site>/endpoints.yaml, and eachmessagein itsvalidation:label: { en: Your name, cy: Eich enw }. Without one the Welsh form shows the English label. - Menu labels. Each item in
seed/navigation/*.yaml:label: { en: Walks, cy: Teithiau }. - Emails. Email templates have no
t(): it prints nothing there. Branch onlocaleinside each block (Enquiry form). - Select options. An option with no
labelat all prints its value in every locale.
Add a language switcher
{# in templates/_shared/base.html, inside the header #}
{{ localeSwitcher() }} localeSwitcher() prints a labelled <nav> of the locales the current page is published in, the current one with aria-current="page" and each link with hreflang and lang; it prints nothing on a page that exists in one locale. Label it with the string nav.language. head() writes the hreflang alternates and x-default, and <html lang> comes from locale.code.
Verify
engine-dmo check
engine-dmo schema apply
engine-dmo seed reset --confirm && engine-dmo seed apply
engine-dmo render /cy/teithiau/llwybr-tynnu-cherwell --json
engine-dmo render /teithiau/llwybr-tynnu-cherwell --locale cy --json Then render the Welsh home page and a form page: engine-dmo render /cy/ --json and engine-dmo render /cy/cysylltu --json, and read the <label>s in html.
The page answers 200 with "locale": "cy", <html lang="cy">, a canonical on /cy/teithiau/..., hreflang alternates for en, cy and x-default, and the Welsh strings. --locale takes the unprefixed path and adds the prefix for you. Run engine-dmo a11y audit --all, which covers every locale’s pages in the sitemap. See Localisation.
Enquiry form
How an agent adds a contact form or a form about one listing to a Meridian site with dev endpoint add, writes the response, error and email templates the endpoint names, places the form on a page, and verifies it with check, render and a scripted post.
Search page
How an agent adds a search page to a Meridian site with the special search route and search.html, what browse mode returns without a search service, how to connect one, and how to verify the page renders and is accessible.