@i18now/nuxt
@i18now/nuxt
Nuxt module for i18now. Loads translations from the i18now CDN (or from pulled local files), syncs missing keys to your project during development, and ships a pull CLI so you can bake translations into your build.
Built on top of @nuxtjs/i18n. You keep the full @nuxtjs/i18n API — this module adds loading, sync, and a build-time pull step on top.
Install
One-shot setup (recommended)
npx @i18now/nuxt init --project-id=YOUR_PROJECT_ID --api-key=YOUR_API_KEY
Writes .env, installs @nuxtjs/i18n + @i18now/nuxt, edits nuxt.config.ts with sensible defaults, and drops a test page at /i18now-test.
Manual
bun add @i18now/nuxt @nuxtjs/i18n
# or
npm install @i18now/nuxt @nuxtjs/i18n
nuxt.config.ts:
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n', '@i18now/nuxt'],
i18n: {
defaultLocale: 'en',
locales: [{ code: 'en' }, { code: 'es' }],
strategy: 'no_prefix',
},
i18now: {
projectId: process.env.I18NOW_PROJECT_ID!,
apiKey: process.env.I18NOW_API_KEY, // optional, enables dev-time key sync
mode: 'cdn', // 'cdn' (default) | 'local'
},
})
Put
@i18now/nuxtafter@nuxtjs/i18ninmodules. Order matters — the i18now plugin hooks into vue-i18n after@nuxtjs/i18ncreates it.
The two modes
@i18now/nuxt has exactly one switch you need to think about: where translations come from at runtime.
mode: 'cdn' (default)
At every SSR render and client mount, the module fetches {cdnUrl}/{projectId}/publish/{environment}/{locale}.json and merges the translations into vue-i18n. Locale switches re-fetch.
Pick this when:
- You want translations to update the moment you publish from the i18now dashboard (no redeploy).
- Your traffic is low-to-medium and CDN latency (~20-50ms) is acceptable on the SSR path.
- You do not want to store translations in your repo.
i18now: {
projectId: process.env.I18NOW_PROJECT_ID!,
mode: 'cdn', // can be omitted — this is the default
}
mode: 'local'
The module does no runtime CDN fetch. Translations are loaded by @nuxtjs/i18n from disk, from files you pull at build time with npx @i18now/nuxt pull.
Pick this when:
- You want zero runtime network calls on the SSR path (air-gapped, edge, or high-traffic).
- You want deterministic builds — the exact translation set in prod matches what was on the CDN at build time.
- You want to ship translations in the same immutable artifact as your code.
i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', file: 'en.json' }, // file: tells @nuxtjs/i18n to load from disk
{ code: 'es', file: 'es.json' },
],
},
i18now: {
projectId: process.env.I18NOW_PROJECT_ID!,
mode: 'local',
}
Add to package.json:
{
"scripts": {
"build": "i18now-nuxt pull && nuxt build"
}
}
Every build fetches the latest published translations, writes them to i18n/locales/*.json, then lets @nuxtjs/i18n bundle them natively.
You can combine this with your CI pipeline: fail the build if
pullfails withi18now-nuxt pull --fail-fast(all locales must 200). By default, partial failures still let the build proceed with stale data for missing locales.
The pull CLI
The CLI auto-discovers everything from nuxt.config.ts. The common case is zero flags:
npx @i18now/nuxt pull
Output:
@i18now/nuxt v0.0.9 pull
project: cc2cb2e5-6c57-4782-9da1-edd23ecde64d
cdn: https://cdn.i18now.com
env: dev
locales: en, es
out: i18n/locales
✓ en (116 keys → i18n/locales/en.json)
✓ es (116 keys → i18n/locales/es.json)
Done. 2 ok, 0 failed.
Resolution order
Every value has three tiers of resolution. First match wins.
| Field | 1. Flag | 2. nuxt.config.ts |
3. Fallback |
|---|---|---|---|
projectId |
--project-id=<id> |
i18now.projectId (incl. process.env.X) |
— (error) |
locales |
--locales=en,es |
i18n.locales (array of strings or { code }) |
CDN _locales.json |
cdnUrl |
--cdn-url=<url> |
i18now.cdnUrl |
https://cdn.i18now.com |
env |
--env=<name> |
i18now.environment |
dev |
outDir |
--out-dir=<path> |
— | i18n/locales |
All flags
npx @i18now/nuxt pull [options]
--project-id=<id> Project ID (fallback: nuxt.config)
--locales=a,b,c Locale codes (fallback: nuxt.config, then CDN _locales.json)
--cdn-url=<url> CDN base URL (default: https://cdn.i18now.com)
--env=<name> Environment (default: dev)
--out-dir=<path> Output directory (default: i18n/locales — matches @nuxtjs/i18n langDir)
When to use which invocation
| Situation | Command |
|---|---|
nuxt.config.ts has i18now.projectId + i18n.locales |
npx @i18now/nuxt pull |
| You want every published locale, even ones not yet in config | Remove locales from config, run npx @i18now/nuxt pull — falls back to CDN _locales.json |
| Pull a preview environment from CI | npx @i18now/nuxt pull --env=stage |
| One-off pull for a specific project | npx @i18now/nuxt pull --project-id=abc --locales=en,fr |
Sync (dev-only key capture)
Sync is independent of mode. It activates when both are true:
- You set
apiKeyini18nowconfig. - The Nuxt server is running in dev (
nuxt.options.dev).
When active, the client plugin intercepts $t('key', 'Default text') calls. Any key not already present on the CDN is batched and POSTed to {host}/api/v1/projects/{projectId}/sync.
<template>
<!-- Writing `t('new.key', 'Save')` in dev auto-creates that key in your -->
<!-- i18now project. No dashboard trip required. -->
<button>{{ $t('button.save', 'Save') }}</button>
</template>
Safety
apiKeyis only included inruntimeConfig.publicwhennuxt.options.dev === true. Production bundles never see it.syncIn(default['development']) gates runtime activation againstprocess.env.NODE_ENV.- Never add
'production'or'staging'tosyncIn— it would expose your API key to end users and flood your project with traffic. The module prints a loud error if you do.
Configuration reference
interface ModuleOptions {
projectId: string; // required
apiKey: string; // optional — if set, enables dev sync
mode?: 'cdn' | 'local'; // default 'cdn'
host?: string; // default 'https://i18now.com'
cdnUrl?: string; // default 'https://cdn.i18now.com'
environment?: string; // default 'dev'
syncIn?: string[]; // default ['development']
locale?: string; // default 'en'
}
| Option | Purpose | When it matters |
|---|---|---|
projectId |
Identifies your i18now project | Always |
apiKey |
Auth for dev-time sync | Only if you want missing-key auto-capture |
mode |
Runtime loader selection | Always |
host |
i18now API host (for sync POSTs) | When sync is active |
cdnUrl |
CDN host (for runtime fetch + pull CLI) | When mode: 'cdn' or you call pull |
environment |
Which published env to read | Always |
syncIn |
Runtime gate for sync | When sync is active |
locale |
Initial locale before @nuxtjs/i18n boots |
Rarely |
TypeScript
The module augments @nuxt/schema:
import type { ModuleOptions, I18nowPublicRuntimeConfig } from '@i18now/nuxt'
NuxtConfig.i18nowis typed → you get autocomplete innuxt.config.ts.useRuntimeConfig().public.i18nowis typed asI18nowPublicRuntimeConfig.
No // @ts-expect-error needed anywhere.
Composables
Auto-imported by the module — no manual import needed.
useI18now()
Drop-in replacement for useI18n() from vue-i18n. Adds nothing visible in production — only useful because it participates in dev-time sync detection.
<script setup lang="ts">
const { t, locale, setLocale } = useI18now()
</script>
<template>
<p>{{ t('home.title', 'Welcome') }}</p>
</template>
useI18nowLocales()
Fetches the full list of locales published to the i18now dashboard from {cdnUrl}/{projectId}/publish/{env}/_locales.json. Useful for rendering a language switcher that auto-updates when you publish a new locale.
<script setup lang="ts">
const { locales, sourceLocale, pending } = useI18nowLocales()
const { locale, setLocale } = useI18now()
</script>
<template>
<select v-if="!pending" :value="locale" @change="setLocale(($event.target as HTMLSelectElement).value)">
<option v-for="l in locales" :key="l.code" :value="l.code">{{ l.nativeName }}</option>
</select>
</template>
Lazy + client-only — does not add overhead to SSR.
Scenarios
1. Fastest path to green — CDN mode with sync
// nuxt.config.ts
i18now: {
projectId: process.env.I18NOW_PROJECT_ID!,
apiKey: process.env.I18NOW_API_KEY,
},
i18n: {
defaultLocale: 'en',
locales: [{ code: 'en' }, { code: 'es' }],
strategy: 'no_prefix',
},
- No pull step.
- Write
t('new.key', 'Default')in dev → key auto-appears in your i18now project. - Ship to prod with identical config —
apiKeyis stripped from the bundle, sync is dormant.
2. Build-time bake — local mode
// nuxt.config.ts
i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', file: 'en.json' },
{ code: 'es', file: 'es.json' },
],
strategy: 'no_prefix',
},
i18now: {
projectId: process.env.I18NOW_PROJECT_ID!,
apiKey: process.env.I18NOW_API_KEY,
mode: 'local',
},
// package.json
"scripts": {
"build": "i18now-nuxt pull && nuxt build"
}
bun run buildpulls translations, then builds.- Production makes zero runtime network calls for translations.
.gitignoreshould containi18n/locales/— those files are derived artifacts.
3. Pull once in CI, commit the files
Same config as scenario 2, but in CI:
# .github/workflows/translate.yml
- run: npx @i18now/nuxt pull
- run: git add i18n/locales && git diff --staged --quiet || git commit -m "chore: sync translations"
- run: git push
Useful if you want PR review over translation diffs before they ship.
4. Multiple environments from the same CDN
i18now: {
projectId: process.env.I18NOW_PROJECT_ID!,
environment: process.env.DEPLOY_ENV ?? 'dev', // 'dev' | 'stage' | 'prod'
},
Pull from a specific environment:
npx @i18now/nuxt pull --env=stage
5. Local development against a local i18now server
i18now: {
projectId: 'your-project-id',
apiKey: 'your-api-key',
host: 'http://localhost:3220',
cdnUrl: 'http://localhost:3220/cdn', // or whatever your local CDN proxy serves
},
6. Dynamic locales — auto-discover from CDN
Drop locales from i18n config, drop --locales from pull:
i18n: { defaultLocale: 'en' },
npx @i18now/nuxt pull # falls back to CDN _locales.json
Every locale you publish on the dashboard appears in your build with no code changes. Combine with useI18nowLocales() for a fully dynamic UI.
Troubleshooting
mode: 'local' but keys render as raw strings
You either forgot to run pull, or @nuxtjs/i18n doesn't know about the files.
- Check
i18n/locales/*.jsonexists. Runnpx @i18now/nuxt pull. - Each locale in
i18n.localesneeds afile:field.{ code: 'en' }alone won't load anything in local mode; use{ code: 'en', file: 'en.json' }.
Sync never fires
- Is
apiKeyset ini18nowconfig? - Is
process.env.NODE_ENVinsyncIn? Default is['development']— if you run dev with a differentNODE_ENV, add it here. - Open DevTools Network tab, filter by
i18now.com— you should seePOST /api/v1/projects/:id/syncwithin 5 seconds of thet()call (batched).
"Unknown command pull" with bun x
bun x @i18now/nuxt resolves to the wrong bin. Use one of:
"build": "i18now-nuxt pull && nuxt build" // best — uses node_modules/.bin
"build": "npx @i18now/nuxt pull && nuxt build" // works
"build": "bunx i18now-nuxt pull && nuxt build" // bunx with bin name, not pkg name
Pull 404s for a locale you just published
- CDN caches publishes — wait 30-60s.
- Confirm the environment:
curl "https://cdn.i18now.com/{projectId}/publish/{env}/_locales.json"— is the locale in the list?
How the plugins are wired
Four combinations of mode × sync produce different plugin registrations.
| sync off | sync on (dev + apiKey) | |
|---|---|---|
mode: 'cdn' |
CDN server plugin + CDN client plugin | CDN server plugin + full client plugin (load + sync + $t patch) |
mode: 'local' |
No plugins registered (module is a no-op at runtime — @nuxtjs/i18n handles everything) |
Sync-only client plugin (sync + dedup via one CDN existingKeys fetch) |
In production the sync client plugin is never registered. Production mode: 'local' ships zero i18now runtime code.
Playground
Minimal playground in playground/ runs on port 3227.
bun dev:playground
License
MIT
Related Packages
Translation HMR utilities for Nuxt I18n Micro (granular subpath imports).
Node.js utilities for loading translations and locale data in Nuxt I18n Micro.
Core utilities for translations, formatting, and locale routing in Nuxt I18n Mic...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.0.14 | ^3.0 | MIT | |
| 0.0.13 | ^3.0 | MIT | |
| 0.0.12 | ^3.0 | MIT | |
| 0.0.11 | ^3.0 | MIT | |
| 0.0.10 | ^3.0 | MIT | |
| 0.0.9 | ^3.0 | MIT | |
| 0.0.8 | ^3.0 | MIT | |
| 0.0.7 | ^3.0 | MIT | |
| 0.0.6 | ^3.0 | MIT | |
| 0.0.5 | ^3.0 | MIT | |
| 0.0.4 | ^3.0 | MIT | |
| 0.0.3 | ^3.0 | MIT | |
| 0.0.2 | ^3.0 | MIT | |
| 0.0.1 | ^3.0 | MIT |