@i18now/nuxt

Nuxt module for loading, syncing, and managing i18now translations.
1,931
Install
npm install @i18now/nuxt
Latest Version:0.0.14
Vue:3
License:MIT
Last Updated:Aug 22, 2026
Links: npm
Maintainer: adakausa

@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

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/nuxt after @nuxtjs/i18n in modules. Order matters — the i18now plugin hooks into vue-i18n after @nuxtjs/i18n creates 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 pull fails with i18now-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:

  1. You set apiKey in i18now config.
  2. 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

  • apiKey is only included in runtimeConfig.public when nuxt.options.dev === true. Production bundles never see it.
  • syncIn (default ['development']) gates runtime activation against process.env.NODE_ENV.
  • Never add 'production' or 'staging' to syncIn — 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.i18now is typed → you get autocomplete in nuxt.config.ts.
  • useRuntimeConfig().public.i18now is typed as I18nowPublicRuntimeConfig.

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 — apiKey is 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 build pulls translations, then builds.
  • Production makes zero runtime network calls for translations.
  • .gitignore should contain i18n/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/*.json exists. Run npx @i18now/nuxt pull.
  • Each locale in i18n.locales needs a file: field. { code: 'en' } alone won't load anything in local mode; use { code: 'en', file: 'en.json' }.

Sync never fires

  • Is apiKey set in i18now config?
  • Is process.env.NODE_ENV in syncIn? Default is ['development'] — if you run dev with a different NODE_ENV, add it here.
  • Open DevTools Network tab, filter by i18now.com — you should see POST /api/v1/projects/:id/sync within 5 seconds of the t() 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

@i18n-micro/hmr

Translation HMR utilities for Nuxt I18n Micro (granular subpath imports).

51,795 251
@i18n-micro/node

Node.js utilities for loading translations and locale data in Nuxt I18n Micro.

4,423 251
@lingva/vue

Vue adapter for Lingva localization

754 0
@i18n-micro/core

Core utilities for translations, formatting, and locale routing in Nuxt I18n Mic...

199,259 251

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