Internationalization
Maizzle supports internationalization through vue-i18n. This lets you create email templates in multiple languages using translation keys that resolve to localized strings at build time.
Starter project
The quickest way to get started is with maizzle/starter-i18n, which has a working example of the setup described in this guide.
npx maizzle new maizzle/starter-i18n --install
Installation
To add internalization to an existing project, start by installing vue-i18n:
npm install vue-i18n
Setup
Locale files
Create a locales directory in your project root with a JSON file for each language:
locales/
├── en.json
└── fr.json
{
"greeting": "Hello, World!",
"farewell": "Goodbye!"
}
Configuration
Import your locale files and register vue-i18n as a Vue plugin:
import { defineConfig } from '@maizzle/framework'
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import fr from './locales/fr.json'
export default defineConfig({
vue: {
plugins: () => [
createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: { en, fr },
}),
],
},
})
Pass plugins as a factory (() => [...]) instead of a plain array. vue-i18n is stateful and setting locale.value in one template mutates the shared instance and leaks into the next render. The factory form gives each template a fresh i18n instance.
Usage
Global locale
With the config above, all templates render using the default locale ('en' in this example). Use $t() to output translated strings:
<template>
<Layout>
<Heading level="1">{{ $t('greeting') }}</Heading>
<Text>{{ $t('farewell') }}</Text>
</Layout>
</template>
This outputs Hello, World! and Goodbye! because the default locale is set to 'en'.
Per-template locale
To render a specific template in a different language, use useI18n() in <script setup>:
<script setup>
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
locale.value = 'fr'
</script>
<template>
<Layout>
<Heading level="1">{{ $t('greeting') }}</Heading>
</Layout>
</template>
This outputs "Bonjour, le monde !" regardless of the default locale in the config.
Using t() instead of $t()
When using useI18n(), you can destructure the t function if you prefer:
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
<template>
<Layout>
<Heading level="1">{{ t('greeting') }}</Heading>
<Text>{{ t('farewell') }}</Text>
</Layout>
</template>
Both $t() and t() work the same way, only difference is that $t() is globally available while t() requires the useI18n() import.
Building the same template in multiple languages
A common pattern for email localization is to build the same template once per language.
To get started, create a wrapper template for each locale:
emails/
├── welcome/
│ ├── en.vue
│ └── fr.vue
<script setup>
import { useI18n } from 'vue-i18n'
const { locale } = useI18n()
locale.value = 'en'
</script>
<template>
<Welcome />
</template>
Then, create a shared components/Welcome.vue component that uses $t() for all translatable strings. Each wrapper sets the locale and renders the same component, producing one HTML file per language in your output.
<template>
<Layout>
<Heading level="1">{{ $t('greeting') }}</Heading>
<Text>{{ $t('farewell') }}</Text>
</Layout>
</template>
Adding a new language
- Create a new locale file, e.g.
locales/ro.json - Import it in
maizzle.config.tsand add it to themessagesobject:
import en from './locales/en.json'
import fr from './locales/fr.json'
import ro from './locales/ro.json'
// ...
plugins: () => [
createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: { en, fr, ro }, }),
],
The fallbackLocale option ensures that if a key is missing in ro.json, it falls back to the English translation instead of showing the raw key.
Preview locale changes in dev
Static imports like import en from './locales/en.json' are cached by Node.js, so editing a locale file during development won't update the preview.
To enable HMR when making updates to translations, use readFileSync instead so the files are read fresh each time the config is re-evaluated:
import { defineConfig } from '@maizzle/framework'
import { createI18n } from 'vue-i18n'
import { readFileSync } from 'node:fs'
const en = JSON.parse(readFileSync('./locales/en.json', 'utf-8'))
const fr = JSON.parse(readFileSync('./locales/fr.json', 'utf-8'))
export default defineConfig({
vue: {
plugins: () => [
createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: { en, fr },
}),
],
},
})
Maizzle will automatically watch the locales directory in your project root for changes and re-render templates when a locale file is updated.
If you store locale files somewhere else, add the path to the watch array in the config:
import { defineConfig } from '@maizzle/framework'
export default defineConfig({
server: {
watch: ['./messages/**/*.json'],
},
})