Skip to content

Serve content in multiple languages

Fetch and render translated Drupal content with langcode prefixes, from the route to the store to language-specific theme components.

Before you start: this guide assumes a working Druxt site (see Getting started) with at least two languages enabled in Drupal and URL path prefixes configured (Configuration → Regional and language → Languages).

Multilingual support runs through every layer: the router resolves prefixed paths, the client and store fetch translated resources, module components take a langcode prop, and the theme layer resolves language-specific components. This guide walks one translated page through all four.

Patch the router

Translated route resolution needs one patch on decoupled_router until #3111456 is released:

"drupal/decoupled_router": {
  "https://www.drupal.org/project/decoupled_router/issues/3111456#comment-15211077": "https://www.drupal.org/files/issues/2023-08-30/decoupled_router-3111456-resolve_lang-66.patch"
}

The symptom without it is quiet: prefixed routes resolve, but to the default language. The demo backend, which does not carry the patch, shows it live: /router/translate-path?path=/es/recipes answers with the /en/recipes resource.

Menus need jsonapi_menu_items 1.2.4 or later. Translated Views routes do not resolve yet, tracked in druxt#3273228.

Fetch a translated resource

The DruxtClient resource methods (getResource, getCollection, getCollectionAll, getIndex) and the matching DruxtStore actions take a langcode prefix, falling back to the backend's default language when omitted:

this.$store.dispatch('druxt/getResource', {
  type: 'node--page',
  id: 'd8dfd355-7f2f-4fc3-a149-288e4e293bdd',
  prefix: 'es',
});

(Take a real id from your backend's /jsonapi/node/page listing; demo ids change with every reinstall.) The prefix maps straight onto Drupal's prefixed endpoints: /es/jsonapi/... serves the Spanish variants.

Render in a language

Module components take a langcode prop, and expose the resolved language (prop or fallback) as the computed lang:

<DruxtEntity type="node--page" :id="id" langcode="es" />

On a routed page you rarely set this by hand: the wildcard route carries the language of the path it resolved, and the components inherit it.

Theme per language

The component suggestion chain tries a langcode-suffixed variant of every candidate first, so a language-specific wrapper is just a more specific file:

components/druxt/entity/node/page/Default.vue     every language
components/druxt/entity/node/page/DefaultEs.vue   Spanish only

DefaultEs.vue wins for langcode: 'es'. Every other language renders Default.vue. Use it for the cases where translation changes the design: reversed text direction, longer labels, a language-specific asset.

Offer a language switch

The minimal switcher links the current path across prefixes, letting Drupal's router resolve each variant:

<template>
  <nav>
    <NuxtLink v-for="code of ['en', 'es']" :key="code" :to="prefixed(code)">
      {{ code.toUpperCase() }}
    </NuxtLink>
  </nav>
</template>

<script>
export default {
  methods: {
    prefixed(code) {
      return '/' + code + this.$route.path.replace(/^\/(en|es)(?=\/|$)/, '');
    },
  },
};
</script>

List the codes your Drupal site actually enables, matching its configured path prefixes. A missing translation resolves to the entity's fallback language rather than a 404.

Where to go next