Translate Related Pages and Files

Translate the pages and files that belong to a view along with it – Kirby Modules, a page's files, or any model a Kirby query reaches.

Some content lives outside the page an editor has open: the module pages of a page builder, the metadata of a page's files, the children of an overview page. The cascade option names these models with a Kirby query, and every translation started from the view translates them too.

The model whose view starts the translation is the host. The models its cascade option names are its cascade.

Name the Cascade

Set cascade on the view button or the section of the host's blueprint. It takes one Kirby query or a list of them:

buttons:
  open: true
  preview: true
  settings: true
  content-translator:
    cascade: page.files
  languages: true
  status: true

On the view button, cascade needs the map form shown above. A map replaces Kirby's default buttons, so keep naming every button you want.

A translation cascades only when the view button or section it starts from sets cascade. With both in one blueprint, give them the same queries.

A query can use kirby, site, and model – the host itself – plus the alias of the host's own type: page, file, or site. The null-safe operator keeps a query valid while the model it starts from does not exist yet:

cascade: page.find("modules")?.children

Only pages and files take part, drafts included when a query such as page.drafts reaches them. The host itself and the models the current user may not open or edit are left out. A query that fails or finds nothing adds nothing and shows no error, so a typo shows up only as a missing or too-low count in the dialog before a translation.

Every model of the cascade costs a translation per language. A query like site.index translates the whole site from one button.

The Cascade Is Saved Directly

The cascade is always translated from the saved content of the default language and saved directly in the target language, so Discard on the host does not revert it. The dialog before a translation states how many pages and files come along.

  • Batch translation translates the host and its cascade into every selected language.
  • Single-language translation saves the cascade in the current language, while the host's fields stay in the form for review. Nothing is cascaded while the default language is open, while another user edits the host, or without the permission to update the host.
  • Import does not cascade.

A model of the cascade is translated by the host's options:

  • The host's fieldTypes, kirbyTags, and systemPrompt apply. A content-translator section or view button on the model's own blueprint is not read, but translate: false on its fields holds.
  • includeFields and excludeFields from the host's blueprint do not apply, since they name the host's fields. Those set in the global configuration do.
  • A page gets a translated title when the host's title option is on and the current user may change that page's title.
  • The slug is never translated.

Three things hold a model of the cascade back, and the translation results say which:

  • Another user edits it. The remaining models are still translated.
  • Its default language has unsaved changes. The translation would miss them, so save or discard them first.
  • A target language has unsaved changes. Only that language of the model is held back, as for the host.

Kirby Modules

Kirby Modules stores the modules of a section as child pages of a container page named after the section. For a section named modules, one line in the host's blueprint translates them along with the page:

site/blueprints/pages/default.yml
buttons:
  open: true
  preview: true
  settings: true
  content-translator:
    cascade: page.find("modules")?.children
  languages: true
  status: true

sections:
  modules:
    type: modules

For more than one modules section, list one query per section. A section named sidebar keeps its modules in page.find("sidebar").

The query reads the container's children rather than Kirby Modules' page.modules method, because that method leaves out hidden modules, and new modules start hidden.

For modules that contain modules, collect the whole tree and keep only the module pages:

cascade: page.find("modules")?.index.filterBy("isModule", true)

Module blueprints set changeTitle: false by default, so module titles stay as they are.

From PHP

The PHP API has no cascade. Loop over the same models instead:

$targetLanguage = 'de';
$defaultLanguage = kirby()->defaultLanguage()->code();
$page = page('about');

$models = [$page, ...$page->files(), ...($page->find('modules')?->children() ?? [])];

foreach ($models as $model) {
    $translator = $model->translator();
    $translator->copyContent($targetLanguage, $defaultLanguage);
    $translator->translateContent($targetLanguage, $targetLanguage, $defaultLanguage);
}