nuxt-easy-texts - key-based texts/translations for Nuxt 3
- Define translatable texts using keys and default texts
- Load and override texts and translations at runtime
- Support for plural text strings
- Customize how text extractions are generated (JSON, PO file, GraphQL, ...)
- Debug mode to render the key instead of the text
Why not...?
This module was created to specifically solve a single problem: Having an easy
way to collect translatable texts and load their translations at runtime (for
example from an API endpoint). The compiled build will only contain the keys and
no more texts. They always have to be provided at runtime.
It is not meant as a full replacement for something like
vue-i18n or
nuxt/i18n, which offer way more features such as
language based routing, SEO, etc.
If the only thing you want to do is collect texts in your code and load them at
runtime, this module is for you!
<template>
<div>{{ $texts('homeTitle', 'Welcome!') }}</div>
</template>
Setup
Install module
npm install --save nuxt-easy-texts
export default defineNuxtConfig({
modules: ['nuxt-easy-texts'],
easyTexts: {
generators: [
{
outputPath: './texts.json',
generate: 'json',
},
],
},
})
Use texts
The module provides two properties on the Nuxt and Vue instance: $texts
and
$textsPlural
. You can directly use them in your Vue components (and they are
reactive!):
<template>
<div>
<div>{{ $texts('homeTitle', 'Welcome!') }}</div>
<div>
{{ $textsPlural('counter', counter, 'One item', '@count items') }}
</div>
</div>
</template>
<script lang="ts" setup>
const counter = ref(1)
</script>
Alternatively, you can also use them from the useEasyTexts()
composable:
const { $texts, $textsPlural } = useEasyTexts()
const text = computed(() => $texts('backToTop', 'Back to top'))
Configure loader
All texts are loaded at runtime. How and where the texts are loaded is up to
you. To define a loader, create the following file in
~/app/easyTexts.loader.ts
:
import { defineEasyTextsLoader } from '#nuxt-easy-texts/types'
export default defineEasyTextsLoader(() => {
const language = useCurrentLanguage()
return {
load(): Promise<Record<string, string | [string, string]>> {
return $fetch('/api/load-texts', {
query: {
language: language.value,
},
})
},
}
})
nuxt-easy-texts will call this method inside its plugin, right before it injects
$texts
and $textsPlural
. Make sure that anything that requires these two
methods is loaded after this plugin is initialized.
How it works
nuxt-easy-texts searches your Vue components and JS/TS files for all calls to
the $texts
and $textsPlural
methods. Because of that, there are a few things
that won't work:
- Rename methods: The name of the method must be exactly
$texts
or
$textsPlural
- you can't rename them (e.g.
const { $texts: getText } = useEasyTexts()
will not work) - Use variables: You can only use string literals inside both methods. Something
like
{{ $texts('language_' + language) }}
will not work
Output files
You can completely customize the generated output file (or generate mutliple
files):
export default defineNuxtConfig({
modules: ['nuxt-easy-texts'],
easyTexts: {
generators: [
{
outputPath: './texts.json',
generate: 'json',
},
{
outputPath: './texts.txt',
generate: (extractions) => {
return extractions
.map((v) => {
if (v.type === 'text') {
return `${v.fullKey}: ${v.defaultText}`
}
return `${v.fullKey}: ${v.singular} | ${v.plural}`
})
.join('\n')
},
},
],
},
})
Generate a GraphQL fragment for the drupal/texts module
If you use the drupal/texts module, you
can use the builtin drupal-graphql-texts
generate option. It will generate a
fragment file that can be used to load the translations in a GraphQL query.
export default defineNuxtConfig({
modules: ['nuxt-easy-texts'],
easyTexts: {
generators: [
{
outputPath: './fragment.easyTexts.graphql',
generate: 'drupal-graphql-texts',
},
],
},
})
The resulting file will look like this:
fragment easyTexts on TextsLoader {
homeTitle: getText(key: "homeTitle", default: "Welcome!")
counter: getTextPlural(
key: "counter"
singular: "One item"
plural: "@count items"
) {
singular
plural
}
}
Load the texts
All default/singular/plural texts in your code are compiled away, for example:
const { $texts, $textsPlural } = useEasyTexts()
const title = $texts('footerCopyright', '© 2024 ACME')
const counter = ref(1)
const countText = $texts('countText', counter.value, 'One item', '@count items')
This is compiled to:
const { $texts, $textsPlural } = useEasyTexts()
const title = $texts('footerCopyright')
const counter = ref(1)
const countText = $textsPlural('countText', counter.value)
nuxt-easy-texts requires that every text string is loaded at runtime. The
default texts are not available at runtime in any way. You could however
generate a JSON file with a custom generate
method and then return said JSON
file in your loaders load()
method.
Text keys and contexts
Providing context is also possible. You can do that by prefixing the key with a
string followed by a dot:
<template>
<div>{{ $texts('cart.addButton', 'Add to cart') }}</div>
</template>
This will generate the following extraction:
{
"fullKey": "cart.addButton",
"key": "addButton",
"context": "cart",
"defaultText": "Add to cart",
"type": "text",
"filePath": "/var/www/example/components/AddToCart.vue"
}
Debug Mode
This is helpful if you want to offer content editors in a CMS a way to easily
find which text key is currently being shown.
const { $texts, toggleDebug } = useEasyTexts()
const goBackText = computed(() => $texts('goBack', 'Go back to homepage'))
console.log(goBackText.value)
toggleDebug()
console.log(goBackText.value)
Changing languages (reloading texts)
If the language or any context (like country) changes you may want to reload all
texts. To do that, implement a reloadTrigger
method in your loader. The method
should return a computed property. nuxt-easy-texts adds a watcher to this
property on the client side and will reload the texts by calling load()
again
on your loader when the value changes.
import { defineEasyTextsLoader } from '#nuxt-easy-texts/types'
export default defineEasyTextsLoader(() => {
const language = useCurrentLanguage()
return {
reloadTrigger() {
return computed(() => language.value)
},
}
})
NOTE: The watcher is only added client-side, not during SSR! The
loader.load()
method is called exactly once during SSR and it must return the
correct translations.
Because the loaded texts are stored using useState()
they are part of the
payload. So during hydration, the loader.load()
method is not called.
Reference
Configuration reference
export default defineNuxtConfig({
modules: ['nuxt-easy-texts'],
easyTexts: {
pattern: [
'./components/**/*.{js,ts,vue}',
'./layouts/**/*.{js,ts,vue}',
'./layers/**/*.{js,ts,vue}',
'./pages/**/*.{js,ts,vue}',
'./composables/**/*.{js,ts,vue}',
],
generators: [
{
outputPath: './texts.json',
generate: 'json',
},
],
globalTexts: { learnMore: 'Learn more' },
},
})
useEasyTexts() composable
const {
$texts,
$textsPlural,
isDebug,
enableDebug,
disableDebug,
toggleDebug,
} = useEasyTexts()
$texts
Use a single text string
$texts('backToHome', 'Back to homepage')
$textsPlural
Use text string with a singular and plural option.
const cartCount = ref(2)
$textsPlural('cartItems', cartCount.value, '1 items', '@count items')
isDebug: ComputeRef
Check if debug mode is enabled.
enabledDebug: () => void
Enable debug mode.
disableDebug: () => void
Disable debug mode.
toggleDebug: () => void
Toggle debug mode.