On this page

Translations

Pruvious offers excellent internationalization support, allowing you to translate collection records and code strings.


Configuration

You can specify the CMS languages in your nuxt.config.ts file using the language property. Here's an example:

# nuxt.config.ts

export default defineNuxtConfig({
  modules: ['pruvious'],
  pruvious: {
    language: {
      // List of supported languages in the CMS
      supported: [
        { code: 'en', name: 'English' },
        { code: 'de', name: 'Deutsch' },
      ],

      // Default language
      primary: 'en',

      // Whether to prefix the primary language in the URL
      prefixPrimary: false,

      // The local storage key to store the active language
      localStorageKey: 'language',
    },
  },
})

The default primary language supported is English (en). If your website uses a single language other than English, you should tweak the language option to avoid potential SEO issues caused by the generated <html lang="en"> attribute on the website pages.

URL Structure

The page URL paths automatically have the language code of secondary languages added at the beginning. For instance, the German translation of the English page http://localhost/contact would appear as http://localhost/de/kontakt. If you attempt to open http://localhost/en/contact, you will be redirected to http://localhost/contact.

If you want the language code prefix to also apply to the primary language, you can set the language.prefixPrimary parameter to true in the nuxt.config.ts file.

Collections

Collections can be translated into the languages specified in your module options. To enable translations, set the translatable property to true when defining a collection. If you want to exclude certain fields from translation, set the additional.translatable property of those fields to false. This will ensure that the field retains the same value in all defined languages.

Multi-entry

Here is an example of a products collection where all fields except the price field are translatable:

# collections/products.ts

import { defineCollection } from '#pruvious'

export default defineCollection({
  name: 'products',
  mode: 'multi',
  translatable: true,
  fields: {
    name: {
      type: 'text',
      options: {
        required: true,
      },
    },
    price: {
      type: 'number',
      options: {
        required: true,
        decimals: 2,
        min: 0,
      },
      additional: {
        translatable: false,
      },
    },
  },
})

When adding new products to the collection, we must consider the language input. By default, it is set to the code of the primary language. Each defined collection has a language field that stores the record's language code, and a translations field that stores a unique string shared with the record's translations.

To create translations for a record, we need to provide the language and translations field values, along with the other data. Here's an example:

# examples/create-products.ts

import { query } from '#pruvious/server'

const en = await query('products').create({ name: 'Icecream', price: 2.99 })

if (en.success) {
  const de = await query('products').create({
    name: 'Eis',
    language: 'de',
    translations: en.record.translations,
  })

  if (de.success) {
    console.log(en.record.translations === de.record.translations) // true
    console.log(en.record.price === de.record.price) // true
  }
}

Single-entry

The process of defining a translatable single-entry collection is similar to that of multi-entry collections. The only difference is that single-entry collections do not store the translations field in the database, as they are stored in the same table and their relation field is the collection name.

Here's an example of how to read and update a translatable single-entry collection:

# examples/read-update-seo.ts

import { query } from '#pruvious/server'

await query('seo').language('en').read()
await query('seo').language('de').read()

await query('seo').language('en').update({ baseTitle: 'Hello' })
await query('seo').language('de').update({ baseTitle: 'Hallo' })

Instead of specifying the language input in the input fields, we use the language method in the query builder to define it.

Translatable strings

There may be cases where you need to output strings in your components or API responses that is not sourced from a collection. To translate these strings, you can use the special utility functions _ and __ . Here's an example of how to use them in a Vue component:

# components/CopyrightText.vue

<template>
  <span>&copy; {{ year }}. {{ _('All rights reserved') }}.</span>
</template>

<script lang="ts" setup>
import { _ } from '#pruvious/client'

const year = new Date().getFullYear()
</script>

On the server side, the only difference is that you import translation functions from #pruvious/server instead of #pruvious/client, and you need to provide an H3 event or language code as the first argument. On the client side, the language is automatically resolved from the standard language composable. Here's an example:

# server/api/whoami.get.ts

import { _ } from '#pruvious/server'

export default defineEventHandler((event) => {
  if (!event.context.auth.isLoggedIn) {
    setResponseStatus(event, 401)
    return _(event, 'You are not logged in')
  }

  return _(event, 'Hello, $name', { name: event.context.auth.user.firstName })
})

Now that our strings are enclosed in an underscore function, we can add translations for them. To do this, we need to add the files default.en.ts and default.de.ts in the translatable-strings directory of the project. Here's an example for the English language:

# translatable-strings/default.en.ts

import { defineTranslatableStrings } from '#pruvious'

export default defineTranslatableStrings({
  domain: 'default',
  language: 'en',
  strings: {
    'All rights reserved': 'All rights reserved,
    'You are not logged in': 'You are not logged in,
    'Hello, $name': {
      pattern: 'Hello, $name',
      input: { name: 'string' },
    },
  },
})

And for German:

# translatable-strings/default.de.ts

import { defineTranslatableStrings } from '#pruvious'

export default defineTranslatableStrings({
  domain: 'default',
  language: 'de',
  strings: {
    'All rights reserved': 'Alle Rechte vorbehalten,
    'You are not logged in': 'Sie sind nicht angemeldet,
    'Hello, $name': {
      pattern: 'Hallo, $name',
      input: { name: 'string' },
    },
  },
})

The defineTranslatableStrings function allows you to define the following properties (required properties are marked with the R symbol):

PropertyDescription

api

Determines whether the translatable strings can be publicly accessed through the API and used on the client side.

domain R

Represents the domain name for translatable strings. API queries use the domain and language parameters to fetch the corresponding strings. The default domain name simplifies displaying strings by allowing the use _('some text') calls instead of __('default', 'some text'). Moreover, strings from the default are automatically preloaded in Vue components.

guards

Custom guards executed when reading translatable strings via the API route /api/translatable-strings/{domain}/?language={language}. Guards are executed in sequence according to their array order, with the exception that they are not applied to admin users. For more details and examples, refer to the code comments.

language R

Represents the language code for translatable strings. This code should align with a code specified in the language.supported module option.

strings R

Contains key-value pairs of translatable strings. The keys represent text handles, and the values are either translated text or patterns.

Here is a more complex example that uses domains and patterns:

# translatable-strings/custom-domain.de.ts

defineTranslatableStrings({
  domain: 'custom-domain',
  language: 'de',
  strings: {
    'Posts': 'Beiträge',                // Simply displays the text 'Beiträge'
    'Displayed: $count $posts': {
      pattern: 'Angezeigt: $count $posts',
      input: {
        count: 'number',                // Replaces $count in the pattern
      },
      replacements: {
        posts: [                        // The resolved output replaces $entries in the pattern
          {
            conditions: [{ count: 1 }], // If all conditions are met...
            output: 'Beitrag',          // output 'Beitrag'
          },
          'Beiträge',                   // Otherwise, output 'Beiträge'
        ],
      },
    },
  },
})

When outputting strings from non-default domains, we need to use the __ helper to explicitly specify the domain name. Additionally, we need to prefetch the translations on the client side by using the loadTranslatableStrings function (this is automatically done for the default domain). Here's an example:

# components/Posts.vue

<template>
  <div>
    <h2>{{ __('custom-domain', 'Posts') }}</h2>
    <p>{{ __('custom-domain', 'Displayed: $count $entries', { count: posts?.length ?? 0 }) }}</p>
    ...
  </div>
</template>

<script lang="ts" setup>
import { __ ,loadTranslatableStrings} from '#pruvious/client'

defineProps({
  posts: Array,
})

await loadTranslatableStrings('custom-domain')
</script>

You can find more examples in the code comments for each function and its arguments.

Language switcher

Pruvious does not provide a default language switcher, so you need to create one manually. Here's an idea on how to do it:

# components/LanguageSwitcher.vue

<template>
  <ul>
    <li v-for="{ name, code } of languageLabels">
      <a :href="page!.translations[code] ?? page!.path">{{ name }}</a>
    </li>
  </ul>
</template>

<script lang="ts" setup>
import { languageLabels } from '#pruvious'
import { usePage } from '#pruvious/client'

const page = usePage()
</script>

In this context, the translations field will contain the resolved page paths instead of their IDs when populated. Additionally, the page path field is fully resolved and already includes the language prefixes. You can learn more about the usePage composable here.

Middleware

Pruvious includes a middleware that automatically resolves the current language code from the Accept-Language header for all server routes. When retrieving page-like collections, the language of the queried record is used instead of the resolved language from the middleware. However, error messages sent from the server will be in the resolved language.

To disable this middleware, set standardMiddleware.server.language to false in your nuxt.config.ts file.

On the client side, the language is automatically resolved from the current record (page).

Last updated on January 8, 2024 at 18:46