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
- Component resolution: where the langcode suffix sits in the full lookup order.
- Decoupled routing: how prefixed paths reach Drupal at all.
- Use the Druxt client directly: the
prefixparameter on every method.