Guide 01 · Developer docs

Choosing how to build

Decide where the pages of a Core dna site are composed before writing code: on the platform, in your own application, or on the platform with a few pieces filled in by the browser. The decision tree, each shape in depth, multi-brand and languages, and a table and case studies to make the call.

L2
Unit 05 · Multi-brand and multi-language

Languages and the language switcher

Each translation is a record of its own, linked to its source. Which records carry language versions, how to choose language-aware URLs, a switcher that leads to the same content in the other language, and how translation helpers differ by engine.

Who this is for. The developer planning a multi-language build. After this page you know how translations are stored, which record types have them, how to lay out language URLs, and how to build a switcher that keeps the visitor on the same content.

Language belongs in this unit because its mechanisms are the multi-brand ones: records linked to each other, URLs the project chooses, and rendering that respects the page cache. This page is the overview for the build decision; the detail is in Guide 2 and Guide 5, linked below.

A translated page is a separate page, with its own revisions, its own path and its own language. The translations are linked: one record is the source, and the others are its related records. Nothing else is shared between them, so each language is edited and published on its own, and each has its own cached copy because each has its own path. How the link is wired, and which record types have it, is on Translations: source and related records.

Which records have language versions

RecordLanguage versions
PagesYes
Book pagesYes
Blogs and blog postsYes
FAQ pages and questionsYes
Books and unitsNo: the containers have none, while the book pages inside them do

Language-aware URLs

A translated page's URL is its own path, like any other page's: the platform's routing adds no language prefix or language hostname of its own. Choose a convention at the start of the project, such as a parent page per language so that paths read /en/... and /es/..., and apply it consistently with ordinary pages and custom URLs. Because the language is then part of the path, the template reads the language from the record it renders, never from the visitor.

A language switcher that keeps the visitor's place

A switcher leads to the same content in the other language, not to that language's home page. getOtherLanguageVersions() on the page returns every language version, the current page included, keyed by language code, plus an x-default entry for the client's default language. It collects them from the current record, its related records and its source's related records, so it gives the same set from any version.

Print each version's own path with the shorthand fullPath, which calls the record's own method, and leave the switcher out when the list holds nothing beyond the current page and x-default:

Template code
{$versions = $page->getOtherLanguageVersions()}
{if count($versions) > 2}
  <nav class="language-switcher">
    {foreach $versions as $code => $version}
      {if $code != 'x-default'}
        <a href="{$version->fullPath}" hreflang="{$code}" lang="{$code}">{$code|upper}</a>
      {/if}
    {/foreach}
  </nav>
{/if}
{% set versions = page.getOtherLanguageVersions() %}
{% if versions|length > 2 %}
  <nav class="language-switcher">
    {% for code, version in versions %}
      {% if code != 'x-default' %}
        <a href="{{ version.fullPath }}" hreflang="{{ code }}" lang="{{ code }}">{{ code|upper }}</a>
      {% endif %}
    {% endfor %}
  </nav>
{% endif %}

Book pages, blogs and blog posts, and FAQ pages and questions have the same method, so the same switcher works on each of them. The full switcher, with the cases around it, is on Translated content and the language switcher.

Translation helpers differ by engine

Interface strings, the labels and messages a template writes itself, are translated differently in the two engines, so weigh this when choosing the engine for a multi-language project. Smarty has helpers registered by the platform: the {_} function, the {__} block and the |_ modifier, all calling the same translator. Twig has no translation tag or filter registered; every template receives the translator itself as the global __, so {{ __.translate('key') }} takes the same arguments, and a multi-language Twig site usually wraps it in a macro of its own. The detail, with worked examples, is on Translating in Smarty and Translating in Twig.

Go deeper

Was this page helpful?

Your answer goes to the docs team.