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>© {{ 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):
Property | Description |
---|---|
| Determines whether the translatable strings can be publicly accessed through the API and used on the client side. |
| Represents the domain name for translatable strings. API queries use the |
| 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. |
| Represents the language code for translatable strings. This code should align with a code specified in the |
| 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 July 31, 2024 at 07:40