nuxt-easy-texts
v1.0.3
Published
Easy key-based text extractor and loader for Nuxt 3
Downloads
582
Readme
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()
// Must be wrapped in computed() to remain reactive.
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]>> {
// Load the texts in the current language from an API route.
// Method should return an object whose properties are the full keys and
// the values are either a string (for $texts keys) or an array of
// exactly two strings (for $textsPlural keys).
//
// For our two examples above, the loader would return the following:
// {
// homeTitle: 'Welcome!',
// counter: ['One item', '@count items'],
// }
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: [
// Generates a JSON file of all raw extractions.
{
outputPath: './texts.json',
generate: 'json',
},
// Provide a custom method to generate the output file.
{
outputPath: './texts.txt',
generate: (extractions) => {
// Generates a simple text file with one extraction per line.
return extractions
.map((v) => {
// Handle single texts.
if (v.type === 'text') {
return `${v.fullKey}: ${v.defaultText}`
}
// Handle plural texts.
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'))
// Logs 'Go back to homepage'
console.log(goBackText.value)
toggleDebug()
// Logs 'goBack'
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: {
// Define which files to include.
pattern: [
'./components/**/*.{js,ts,vue}',
'./layouts/**/*.{js,ts,vue}',
'./layers/**/*.{js,ts,vue}',
'./pages/**/*.{js,ts,vue}',
'./composables/**/*.{js,ts,vue}',
],
// Define what and where to generate extractions.
generators: [
{
outputPath: './texts.json',
generate: 'json',
},
],
// Provide text keys and default values at build time.
// Useful if you have keys that can't be extracted for some reason.
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.