Architecture
This page follows a translation from the JSON file in locales/ to $t() on screen, and names the file behind each step. Read it before you change the build, the server loader, the payload route or the client storage. App authors do not need it: the supported knobs are listed under Public surface, and the Guide explains them.
Overview
Build
| Step | Source |
|---|---|
Layers are read in order (extended layers first, the project last); additionalTranslationDirs before translationDir | src/module.ts (rootDirs) |
premerged (default): root + page + fallback chain merged into one {page}/{locale}/data.json per page and locale, in .nuxt/i18n-merged | src/build/build.ts (preMergeLocales) |
source: a compact copy of the source files in .nuxt/i18n-source, merged per request at runtime | src/build/build.ts (buildTranslationSourceLayers), @i18n-micro/utils/merge-source |
Fallback chain: global fallbackLocale, then each locale's fallbackLocale link, then the locale | @i18n-micro/utils/merge-source (buildFallbackLocaleChain) |
dateBuild: a hash of the translation files unless set | src/module.ts (resolveDateBuild), src/build/payload-stats.ts |
Templates: #build/i18n.strategy.mjs (public config + createI18nStrategy), i18n.config.mjs (private config, Nitro alias #i18n-internal/config), i18n.plural.mjs | src/module.ts (registerI18nTemplates), src/plural-template.ts |
Where the tree goes depends on the Nitro preset, decided in nitro:init. A preset that serves public/ from disk (node-server) gets the tree copied to public/<apiBaseUrl>/ and reads it with readFile (src/runtime/server/payload-source.fs.ts). Edge, function and serveStatic: "inline" presets embed it as Nitro serverAssets under assets:i18n (payload-source.assets.ts). Both readers reject paths that leave the payload root.
Server
- Middleware
src/runtime/server/middleware/i18n.global.tsruns on every request except internals (/api,/_nuxt,/__*, the payload route, static files). It detects the request locale once (detectRequestLocaleinserver/utils/locale-detector.ts) and storesevent.context.i18n = { locale, translations, load, config }.translationsis theindexchunk whenserverTranslationPreloadis on. - Loader
server/utils/server-loader.ts(loadTranslationsFromServer(locale, page)): in dev it reads the live files on every call; withapiBaseServerHostit fetches the payload from that host; otherwise it reads through#i18n-internal/payload-source(one file inpremerged, a runtime merge insource). A premerged page without its own folder shares the root entry, so made-up page names cannot add entries. WithcacheStorage,server/internal/shared-cache.tssits between the process cache and the source, unless the dictionaries are embedded and no host is set. - Server cache
server/internal/server-translation-cache.ts: one LRUCacheControlper process onglobalThis[Symbol.for('__NUXT_I18N_SERVER_CACHE_CC__')], at mostcacheMaxSizeentries (1000 when unset),cacheTtlfrom write. Entries hold the data and its pre-serialized JSON; concurrent loads of one key share one read. It is emptied on restart and, in dev, whenever a translation file changes (translation-watcher.ts). - Route
server/routes/i18n.tsserves/{apiBaseUrl}/:page/:locale/data.jsonfrom the loader's pre-serialized JSON, 404 for a disabled or unknown locale. Outside dev, whenhttpCacheDurationis above 0, it setspublic, max-age=<httpCacheDuration>, immutableonly when the request's?v=equalsdateBuild; otherwisepublic, max-age=0, must-revalidate. AthttpCacheDuration: 0it sends noCache-Controlheader.
Client and SSR runtime
- Loader plugin
src/runtime/plugins/01.plugin.tsresolves the initial locale, configures storage, loads the first chunk and registers the helpers. During SSR the chunk comes fromevent.context.i18n.load(no HTTP); dictionaries are never written into the HTML payload. The server adds a<link rel="preload" as="fetch">for the chunk the browser will fetch while hydrating. TranslationStoragesrc/runtime/utils/storage.ts: a module-level instance whoseCacheControllives onglobalThis[Symbol.for('__NUXT_I18N_STORAGE_CC__')], keyedlocale:routeName. On the server a chunk's root is frozen in place, because every request shares it; the browser stores the object it fetched. Bounded bycacheMaxSize(1000 on the server when unset, unbounded in the browser), expired bycacheTtl.NuxtI18nsrc/runtime/utils/nuxt-i18n.ts(extendsBaseI18nfrom@i18n-micro/core): one per app. It holds the chunk map (storage.translations, FIFO-evicted withcacheMaxSize), the active dictionary, and the outgoing dictionary.- Navigation does not merge.
applySwitchContextinstalls the new chunk as the active dictionary; on a same-locale navigation the old one stays as a fallback layer so the leaving page still renders its keys.finishTransitiondrops it when the leaving page's element leaves the document, watched from the commit, or onpage:finishfor a page with no element. A parent still into.matchedkeeps it until the next navigation. - Patches merge:
$mergeTranslations,$loadPageTranslationsandi18n:registerdeep-merge into the stored chunk and, when it is active, into the dictionary. Stored trees are never edited in place. NuxtTranslationLoaderdedupes in-flight loads per key and keeps a switch generation, so a slow load cannot overwrite a newer navigation.reload()($clearCache, dev HMR) clears both caches and re-applies the current chunk without blanking the screen.- Dev HMR: a Vite plugin in
src/module.tsswallows updates of translation files and sendsi18n-micro:update; the client plugin fromsrc/hmr-plugin.tscalls$clearCache(). The dev server reads live files, so nothing on the server needs invalidating.
Public surface
Everything above is internal and may change in any release. Apps may rely on:
| Need | Use |
|---|---|
| Reload translations in the browser | $clearCache() |
| Add or patch translations at runtime | $mergeTranslations, $loadPageTranslations, i18n:register |
| Bound or expire the caches | cacheMaxSize, cacheTtl |
| HTTP caching of payloads | httpCacheDuration, dateBuild |
| Where payloads are written | translationPayloads, Deployment |
| Translations in a Nitro handler | Server routes |