Skip to main content
ElasticFunnels can serve your site in several languages. There are two ways to build it, and one brand can use both: Either way, a page in a language renders in that language, gets <html lang>, reciprocal hreflang links, a self canonical and sitemap alternates, and can show a language switcher and a “this page is also in Romanian” prompt. See SEO for multilingual sites and Language prompt and switcher. Nothing changes for a brand that does not use it: a page without a language renders exactly as before.
Prefer to watch it? The Academy lesson Make a multilingual website shows the setup in five short clips: languages, a page’s language, adding a translation, keeping it up to date, and the Language URLs settings.

Set up your languages

Both models start with the brand’s languages. Open Pages → Advanced → Translations. The Languages list in Pages, Advanced, Translations: English marked Default with the / prefix, Español under /es/ and Română under /ro/, with the Add language button and the edit icon on each row
1

Add a language

Click Add language and enter:
2

Add the default language too

Add your main language (usually en) and switch on Default language. Without it, ElasticFunnels cannot tell which version is the main one for hreflang="x-default" or for text fallbacks.
Codes are matched exactly. pt and pt-br are two languages: add both if you need both. A language that pages or components use can’t be deleted or have its code changed; the error says how many use it.

Linked pages: one page per language

Each language version is its own page with its own slug, HTML, SEO title and description, revisions and split tests. One page is the original; the others are its translations. Together they are a translation group. The CLI calls the original the source.

Give the original its language

Open the original page’s settings and the Language tab. Set Page language to the language it is written in. Not set keeps the page as it was before languages existed. Page settings, Language tab of the original page: Page language set to English, the Translations panel listing the English original and the Romanian translation, and the Add translation in, URL and Add translation controls for Español

Make a translation

In the Translations panel on the same tab, pick a language in Add translation in, check the URL, and click Add translation. ElasticFunnels makes a draft copy of the original in that language, links the two, and proposes the URL <prefix>/<original slug>: Change the URL to a translated one if you want (ro/despre). Then translate the copy, the SEO title and description, and publish the page. If the brand has no languages yet, the panel says Add your languages first. with a link to Go to Translations. The panel lists every version: Original, (this one) for the page you are on, the language of each, and links to Open the page and Open its settings. With the CLI:
If the Romanian page already exists, link it instead of copying. This is done with the CLI:
A linked page’s Language tab names its original, for example This page is a translation of About. Click Unlink and save to take it out of the group; it keeps its content and language. Rules:
  • A group has at most one page per language.
  • One level only: a translation’s original is never itself a translation. Link every translation to the same original.
  • An original with no language counts as the default language.
  • Unlinking or deleting a translation leaves the other pages as they are. Deleting the original makes its first translation the new original.

When the original changes

A translation remembers the text of its original at the moment it was made or last marked up to date (the SEO title, the SEO description and the page’s visible text; markup, scripts and styles do not count). When the original’s text changes, the translation shows Out of date in the Translations panel. Update the translation, then click Mark up to date. In the CLI, ef i18n check lists every stale and missing translation and exits with an error when one is stale, so you can use it before you publish:

Components in a language

A page in Romanian that includes the header component uses the Romanian translation of header when there is one, and the base header otherwise. Make a component translation the same way as a page translation, from the component’s Translations panel (it asks for a Code instead of a URL), or with the CLI:
You can also keep one component for every language and write its texts as {{ t('nav.home') }}. Those follow the page’s language too (see below). Pick by what differs: For links in shared navigation, use page_url('<slug>') instead of writing the path. It returns the URL of that page’s translation in the current page’s language, and the page itself when there is no translation:
On /ro/despre this links to /ro, /ro/features and /ro/pricing when those translations exist.

Funnels keep the visitor’s language

When a funnel step or a /page?id= link sends a visitor to a page that has a translation in the language of the page they are on, they land on that translation. Build the funnel once, with the original pages.

Dictionary pages: one page, texts per language

A dictionary page is one page for every language. Its texts are keys, and the brand dictionary holds one value per language.

Add texts

The Texts section of Pages → Advanced → Translations lists every text in every language, with a Language column. Pick a language in the filter to see one. The Texts list under Translations on All languages: each key with its language code and text, the Language filter, search, Bulk edit and Add text
  1. Click Add text.
  2. Pick the Language. It starts on the brand’s default language.
  3. Enter a Key (for example hero.title) and the Text, then save.
If you save a text in a language the list is not showing, the list switches to All languages so you can see it. Keys use dot notation to group related texts: hero.title, checkout.cta, faq.refund_policy. Use letters, numbers, underscores and dots. The same key repeats in every language: that is how ElasticFunnels knows the English hero.title and the Romanian hero.title are the same text. Bulk edit opens a spreadsheet-style editor for one language at a time. If the list is on All languages, it switches to the default language first. Add or change many keys, then click Save all changes. The batch saves together: if a row fails, nothing is saved. With the CLI, the dictionary is one JSON file per language:

Use texts in a page

A missing text falls back. t() and | t look in the page’s language, then the default language, then ElasticFunnels’ built-in texts, and finally show the last part of the key (hero.title shows title). data-ef-hl uses the default language’s text, then keeps the element’s own markup. getTranslation() returns '' when no language has the key. So a key only needs a value in the languages where it differs, and visitors never see a raw key such as hero.title.

Prefix routing

In Pages → Advanced → Translations, under Language URLs, switch on Serve pages under language folders (prefix routing), or run ef i18n settings --prefix-routing on. Then a request for /<prefix>/<slug> serves the dictionary page <slug> in that language: The Language URLs settings under Translations: the Serve pages under language folders switch, Language suggestion set to Built-in prompt, and Fallback language for search engines A real page always wins: if a page with the slug ro/about exists, /ro/about serves it. Prefix routing only applies to pages without a language; a linked page in a language is served at its own slug. Each prefixed URL has its own canonical, and gets hreflang and sitemap alternates like a linked page.

Using translations in backend scripts

Backend scripts (<script scope="backend">) have the same helpers, all async:
There is no pipe filter in backend scripts: use await t('key'). See Backend scripts → Data functions.

Which language a page renders in

  1. The page’s language. A page with a language always renders in it, whatever the visitor’s browser or a ?hl= parameter says.
  2. The URL prefix, for a dictionary page served under prefix routing.
  3. For a page with neither, the old order still applies, first match wins: a hl or locale placeholder in a wildcard route (such as the slug docs/{hl}), then ?hl=, ?locale= or ?lang=, then the visitor’s session, the domain’s and the brand’s language setting, the browser’s Accept-Language, and finally the default language.
The result is matched to a brand language exactly. When nothing matches, the default language is used.
A slug cannot start with a placeholder, so {hl}/about never matches a URL. For /ro/about, make a linked page with the slug ro/about, or turn on prefix routing for a dictionary page.
?hl= keeps working on pages without a language, which suits checkouts that need the visitor’s language from the page before. It gives one URL for every language, so search engines can index only one version: use linked pages or prefix routing for pages you want found in search.

Template variables

Every page in a translation group, and every page served under a prefix, has these in its template: A switcher written in the template:
The Language prompt and switcher page has a switcher that fills itself in, and the prompt that suggests a visitor’s language.

Permissions

Languages and texts follow the Translations permissions in your role: view, create, update, delete. Page translations follow the Pages permissions.

Patterns and pitfalls

  • Set a default language before you link pages, so x-default and the text fallback have a target.
  • Keep keys stable. Renaming a key in the dictionary does not change your templates. Rename only when the meaning of the text changes.
  • Do not redirect by language in a script. Search engines crawl mostly from the US with no Accept-Language, so a redirect hides your other versions. The language prompt suggests instead.
  • Keep a translation offline until it is ready. A page with the status draft still answers on its live URL, and a new translation starts as a draft copy of the original’s text. ef pages settings ro/about --status offline hides it; set it to published when the copy is translated.