srcdev-nuxt-i18n
v0.1.1
Published
[](https://github.com/srcdev/srcdev-nuxt-i18n/actions/workflows/test.yml) [](https://badge.fury.io/js/srcdev-nuxt-i1
Readme
srcdev-nuxt-i18n
A standalone Nuxt layer providing i18n infrastructure for SRCDEV apps. Consuming apps get locale switching, RTL support, and reusable error translations out of the box via Nuxt's extends mechanism.
What's included
@nuxtjs/i18nv10 configured with 3 locales:en-GB(LTR),zh-CN(LTR),ar-YE(RTL)LocaleSwitchercomponent — renders a button per locale, callssetLocale()on click, available via Nuxt auto-importuseRawLocaleData<T>(path, defaultValue?)— hydration-safe locale data composable with AST normalisation. Required for arrays/objects — see "Known gotchas" belowuseMarkdown()— markdown-it renderer; external links gettarget="_blank" rel="noopener noreferrer"- Base translations —
global.siteName(blank, override in consuming app) and reusableerrors.*keys (404, 500, serverError, general, actions, help, contact) - Build script — merges modular JSON source files into compiled
.tslocale files
Usage
1. Install peer dependencies in your consuming app
npm install @nuxtjs/[email protected] markdown-it2. Extend the layer
// nuxt.config.ts
export default defineNuxtConfig({
extends: ["../srcdev-nuxt-i18n"], // local path, or npm package name once published
})3. Override global.siteName
Create a locale source file in your consuming app:
// i18n-source/locales/global/en-GB.json
{ "global": { "siteName": "Your App Name" } }4. Wire up reactive <html lang>/dir and titleTemplate
Not automatic — add this to your consuming app's app/app.vue (or wherever
your root layout lives):
<script setup lang="ts">
const { locale, locales, t } = useI18n()
const currentLocale = computed(() => locales.value.find(l => l.code === locale.value))
useHead({
htmlAttrs: {
lang: computed(() => currentLocale.value?.language || currentLocale.value?.code || "en"),
dir: computed(() => currentLocale.value?.dir || "ltr"),
},
titleTemplate: computed(() => `%s - ${t("global.siteName")}`),
})
</script>Without this, <html lang> stays static regardless of the active locale.
Claude Code Skills
This package ships Claude Code skills — reference docs for i18n tasks and composables — in the .claude/ directory.
To copy them into your consuming app after install, add a setup:claude script:
"scripts": {
"setup:claude": "cp -r node_modules/srcdev-nuxt-i18n/.claude/skills .claude/skills/srcdev-nuxt-i18n",
"postinstall": "nuxt prepare && npm run setup:claude"
}Then run once to bootstrap:
npm run setup:claudeSkills land in .claude/skills/srcdev-nuxt-i18n/ and never conflict with your own project's skills or skills from other layers.
Using multiple SRCDEV layers
If your app also consumes srcdev-nuxt-components, chain both copy commands so a single postinstall handles all layers:
"scripts": {
"setup:claude": "cp -r node_modules/srcdev-nuxt-components/.claude/skills .claude/skills/srcdev-nuxt-components && cp -r node_modules/srcdev-nuxt-i18n/.claude/skills .claude/skills/srcdev-nuxt-i18n",
"postinstall": "nuxt prepare && npm run setup:claude"
}See .claude/skills/setup-postinstall.md for the full guide including CI setup.
Repo structure
srcdev-nuxt-i18n/
├── package.json
├── nuxt.config.ts ← i18n module config, 3 locales
├── tsconfig.json
├── scripts/
│ └── build-i18n.mjs ← merge JSON → .ts, supports --watch
├── components/
│ └── locale-switcher/
│ └── LocaleSwitcher.vue ← renders a button per locale, calls setLocale()
├── composables/
│ ├── useRawLocaleData.ts ← useRawLocaleData<T>(path, defaultValue?)
│ └── useMarkdown.ts ← useMarkdown() → { renderMarkdown(text) }
├── i18n-source/
│ └── locales/
│ └── global/
│ ├── en-GB.json ← edit these to change source translations
│ ├── zh-CN.json
│ └── ar-YE.json
└── i18n/
└── locales/
├── en-GB.ts ← committed generated files (do not edit)
├── zh-CN.ts
└── ar-YE.tsCommands
npm run dev # run nuxt dev + i18n watch concurrently
npm run build # build:i18n then nuxt build
npm run build:i18n # regenerate i18n/locales/*.ts from i18n-source JSON once
npm run build:i18n:watch # same, with file watching (used automatically by dev)Known gotchas
tm()renders raw AST JSON, not the string. For array/object locale values (e.g. a features list),tm(path)returns compiled Vue I18n message AST nodes, not plain values — using it directly in av-forrenders{"type":0,"start":0,...}as text. Always useuseRawLocaleData<T>(path, default)instead for arrays/objects;$t()remains correct for plain strings.- Testing via a local
file:dependency needs one extra step. npm does not install a linked local package's own dependencies automatically — runnpm installinsidesrcdev-nuxt-i18nitself first, or a consumer's install will fail withCould not load @nuxtjs/i18n. Is it installed?. This samenpm installalso runs this repo's ownpreparescript (nuxt prepare), which is required separately:tsconfig.jsonextends./.nuxt/tsconfig.json, and Vite/jiti resolve the nearesttsconfig.jsonwhen loading.tslocale files at runtime — without it, translations silently fail to load (WARN Failed to load messages for locale...). Neither of these applies to a real npm install.
Notes
langDirresolves relative to<rootDir>/i18n/(not the layer root directly) —resolveI18nDir()resolves to<rootDir>/i18nfirst, thenlangDiris resolved relative to that. Since locale files live ati18n/locales/*.ts, the correct value islangDir: "locales". This applies identically to a consuming app's owni18nconfig (seelocale-add-app-translations.md), not just this layer.- Generated
.tslocale files are committed — consuming apps do not need to run the build script - RTL support (
ar-YE) requires CSS in the consuming app (:dir(rtl)or[dir="rtl"]selectors) — not provided by this layer - Extracted from
srcdev-design-system, which remains the canonical reference for original patterns and decisions
