Web i18n vue i18n
Skill agents-inc/skills/dist/plugins/web-i18n-vue-i18n/skills/web-i18n-vue-i18n
The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code
npx -y skills add agents-inc/skills --skill web-i18n-vue-i18nAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Type-safe i18n for Vue 3 Composition API
SKILL.md
14.9 KB, as published. Nobody here has run it
vue-i18n Internationalization Patterns
Quick Guide: Use vue-i18n v11+ for type-safe internationalization in Vue 3.
useI18ncomposable for translations,d()for dates,n()for numbers,i18n-tcomponent for rich text. Setlegacy: falsefor Composition API mode (Legacy API is deprecated in v11, removed in v12).
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST set legacy: false in createI18n for Composition API mode)
(You MUST use a SINGLE useI18n() call per component - destructure all needed functions from one call)
(You MUST await locale message loading before setting locale.value - setting locale before messages are loaded shows raw keys)
(You MUST use named constants for locale codes - NO inline locale strings)
</critical_requirements>
Auto-detection: vue-i18n, useI18n, createI18n, i18n-t, i18n-d, i18n-n, locale detection, pluralization, Vue 3 i18n, Composition API i18n
When to use:
- Implementing internationalization in Vue 3 applications
- Rendering localized messages with interpolation and pluralization
- Formatting dates, numbers, and currency per locale
- Setting up locale-based routing and lazy loading
- Building type-safe translation systems with TypeScript
Key patterns covered:
- Project setup with createI18n and Composition API
- useI18n composable for messages, dates, numbers
- Pluralization with pipe syntax and custom rules
- Component interpolation with i18n-t, i18n-d, i18n-n
- Lazy loading translations for performance
- TypeScript integration for type-safe keys
When NOT to use:
- Simple single-locale applications (skip i18n complexity)
- Legacy Vue 2 applications (use vue-i18n v8)
- Non-Vue applications (use framework-specific i18n solution)
Detailed Resources:
- examples/core.md -- Setup, useI18n, interpolation, pluralization, component interpolation, TypeScript, locale switching
- examples/formatting.md -- DateTime formats, number formats, i18n-d/i18n-n components with scoped slots
- examples/lazy-loading.md -- Dynamic imports, route-based loading, feature splitting, error handling, SSR
- reference.md -- Decision frameworks, anti-patterns, checklists, pluralization rules, migration notes
<philosophy>
Philosophy
vue-i18n follows the principle of locale-aware, reactive rendering with support for complex message formatting. Translations are organized as JSON objects, loaded globally or per-component. The Composition API mode (legacy: false) provides a modern, type-safe approach using the useI18n composable.
Core principles:
- Composition API first: Use
useI18n()composable withlegacy: falsefor modern Vue 3 patterns - Single composable call: Destructure all functions (
t,d,n,locale) from ONEuseI18n()call - Locale reactivity: Locale changes automatically trigger re-renders via Vue's reactivity system
- Message format standard: Use pipe-separated plurals and named interpolation for translator-friendly messages
<patterns>
Core Patterns
Pattern 1: Project Setup
Set up vue-i18n with Composition API mode using the standard file structure.
File Structure
src/
i18n/
index.ts # Main i18n configuration
types.ts # TypeScript type declarations
locales/
en.json # English translations
ja.json # Japanese translations
fr.json # French translations
main.ts # App entry with i18n plugin
Configuration
// src/i18n/index.ts
import { createI18n } from "vue-i18n";
import en from "../locales/en.json";
export const SUPPORTED_LOCALES = ["en", "ja", "fr"] as const;
export const DEFAULT_LOCALE = "en";
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
export const i18n = createI18n({
legacy: false, // REQUIRED for Composition API
locale: DEFAULT_LOCALE,
fallbackLocale: DEFAULT_LOCALE,
// globalInjection defaults to true - injects $t, $d, $n into templates
messages: {
en,
},
});
Why good: legacy: false enables Composition API mode, named constants for locales enable type-safe usage, fallbackLocale prevents missing translation errors, globalInjection enables template shorthand (default true since v9.2)
// main.ts
import { createApp } from "vue";
import { i18n } from "./i18n";
import App from "./App.vue";
const app = createApp(App);
app.use(i18n);
app.mount("#app");
Why good: i18n plugin registered once at app root, all components inherit translation capability
Pattern 2: useI18n Composable
Use the useI18n composable in components for translations, formatting, and locale management.
Basic Usage
<script setup lang="ts">
import { useI18n } from "vue-i18n";
// CRITICAL: Single call, destructure all needed functions
const { t, d, n, locale, availableLocales } = useI18n();
const switchLocale = (newLocale: string) => {
locale.value = newLocale;
};
</script>
<template>
<h1>{{ t("greeting") }}</h1>
<p>{{ t("messages.welcome", { name: "Vue" }) }}</p>
<p>{{ d(new Date(), "long") }}</p>
<p>{{ n(1000, "currency") }}</p>
<select :value="locale" @change="switchLocale($event.target.value)">
<option v-for="loc in availableLocales" :key="loc" :value="loc">
{{ loc }}
</option>
</select>
</template>
Why good: single useI18n call prevents sync issues, locale.value is reactive and triggers re-renders, destructuring provides all needed functions
<!-- BAD - Multiple useI18n calls cause sync issues -->
<script setup lang="ts">
const { t } = useI18n();
const { locale } = useI18n(); // WRONG: Second call!
const { d } = useI18n(); // WRONG: Third call!
</script>
Why bad: multiple useI18n calls create separate instances that may not stay synchronized, leads to subtle bugs
Pattern 3: Message Interpolation
Use named placeholders and linked messages for flexible translations.
Named Interpolation
// locales/en.json
{
"greeting": "Hello, {name}!",
"items": "You have {count} items in your cart.",
"email": "{account}{'@'}{domain}"
}
const { t } = useI18n();
t("greeting", { name: "John" }); // "Hello, John!"
t("items", { count: 5 }); // "You have 5 items in your cart."
t("email", { account: "user", domain: "example.com" }); // "[email protected]"
Why good: named placeholders are explicit and refactorable, literal interpolation ({'@'}) escapes special characters
Linked Messages
{
"app": {
"name": "My App"
},
"welcome": "Welcome to @:app.name!",
"brand": "vue i18n",
"message": {
"upper": "@.upper:brand",
"lower": "@.lower:brand",
"capitalize": "@.capitalize:brand"
}
}
t("welcome"); // "Welcome to My App!"
t("message.upper"); // "VUE I18N"
t("message.capitalize"); // "Vue i18n"
Why good: linked messages (@:key) avoid duplication, built-in modifiers (upper, lower, capitalize) transform referenced values
Pattern 4: Pluralization
Use pipe-separated syntax for plural forms with automatic {n} and {count} injection.
Basic Plural Syntax
{
"car": "car | cars",
"apple": "no apples | one apple | {count} apples",
"items": "no items | {n} item | {n} items"
}
const { t } = useI18n();
t("car", 1); // "car"
t("car", 2); // "cars"
t("apple", 0); // "no apples"
t("apple", 1); // "one apple"
t("apple", 10); // "10 apples"
t("items", 5); // "5 items"
Why good: pipe syntax is translator-friendly, {n} and {count} are auto-injected with the plural value, three forms handle zero/one/many
Custom Plural Rules
// For languages with complex rules (Russian, Arabic, Polish)
const i18n = createI18n({
legacy: false,
locale: "ru",
pluralRules: {
ru: (choice: number, choicesLength: number) => {
if (choice === 0) return 0;
const teen = choice > 10 && choice < 20;
const endsWithOne = choice % 10 === 1;
if (!teen && endsWithOne) return 1;
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) return 2;
return choicesLength < 4 ? 2 : 3;
},
},
messages: {
ru: {
apple: "нет яблок | {n} яблоко | {n} яблока | {n} яблок",
},
},
});
Why good: custom pluralRules handle languages with more than two forms, function receives choice count and returns index into plural array
Pattern 5: Component Interpolation
Use i18n-t, i18n-d, and i18n-n components for rich text with Vue components inside translations.
i18n-t for Rich Text
{
"tos": "I agree to the {terms}.",
"termsLink": "Terms of Service"
}
<template>
<i18n-t keypath="tos" tag="p">
<template #terms>
<a href="/terms">{{ t("termsLink") }}</a>
</template>
</i18n-t>
</template>
Why good: translation string stays translatable, Vue components can be inserted via named slots, tag prop controls wrapper element
i18n-t with Pluralization
{
"items": "no items | {n} item | {n} items"
}
<template>
<i18n-t keypath="items" :plural="count" tag="p">
<template #n>
<strong>{{ count }}</strong>
</template>
</i18n-t>
</template>
<script setup lang="ts">
import { ref } from "vue";
const count = ref(5);
</script>
Why good: plural value passed via :plural prop, #n slot allows styling the number, result: "5 items"
i18n-d and i18n-n for Styled Parts
<template>
<!-- DateTime with styled parts -->
<i18n-d :value="date" format="long" tag="time">
<template #month="{ month }">
<span class="month">{{ month }}</span>
</template>
<template #day="{ day }">
<span class="day">{{ day }}</span>
</template>
</i18n-d>
<!-- Number with styled parts -->
<i18n-n :value="price" format="currency" tag="span">
<template #currency="{ currency }">
<span class="currency-symbol">{{ currency }}</span>
</template>
<template #integer="{ integer }">
<span class="integer">{{ integer }}</span>
</template>
</i18n-n>
</template>
Why good: scoped slots expose formatted parts (month, day, currency, integer), enables fine-grained styling of formatted values
Pattern 6: DateTime and Number Formatting
Configure and use locale-aware formatting for dates, times, and numbers.
DateTime Format Configuration
const datetimeFormats = {
"en-US": {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
hour: "numeric",
minute: "numeric",
},
},
"ja-JP": {
short: {
year: "numeric",
month: "short",
day: "numeric",
},
long: {
year: "numeric",
month: "long",
day: "numeric",
weekday: "long",
hour: "numeric",
minute: "numeric",
hour12: false,
},
},
};
const i18n = createI18n({
legacy: false,
locale: "en-US",
datetimeFormats, // Note: camelCase, not dateTimeFormats
});
const { d } = useI18n();
d(new Date(), "short"); // "Apr 19, 2024"
d(new Date(), "long"); // "Friday, April 19, 2024 at 2:30 PM"
Why good: named formats ensure consistency across app, locale-specific formats handle cultural differences (12h vs 24h time)
Number Format Configuration
const numberFormats = {
"en-US": {
currency: {
style: "currency",
currency: "USD",
notation: "standard",
},
decimal: {
style: "decimal",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
},
percent: {
style: "percent",
useGrouping: false,
},
},
"ja-JP": {
currency: {
style: "currency",
currency: "JPY",
useGrouping: true,
currencyDisplay: "symbol",
},
},
};
const i18n = createI18n({
legacy: false,
locale: "en-US",
numberFormats,
});
const { n } = useI18n();
n(10000, "currency"); // "$10,000.00"
n(0.15, "percent"); // "15%"
Why good: Intl.NumberFormat under the hood, handles locale-specific separators and symbols automatically
</patterns><integration>
Integration Guide
vue-i18n integrates with Vue's reactivity system for automatic re-renders on locale change.
Locale state guidance:
- Locale state is managed by vue-i18n -- use
locale.valuefrom useI18n to read/write - Locale changes are reactive -- all components using
t(),d(),n()update automatically globalInjectiondefaults totrue, injecting$t,$d,$ninto templates
Locale-based routing: vue-i18n works with navigation guards to load translations before route renders. See examples/lazy-loading.md for patterns.
</integration><red_flags>
RED FLAGS
- Missing
legacy: false-- defaults to deprecated Options API mode (removed in v12) - Multiple
useI18n()calls in same component -- creates separate instances that desync - Hardcoded locale strings -- use named constants for type safety
- Missing
fallbackLocale-- missing translations cause visible errors instead of graceful fallback - Using
v-htmlwith translations -- XSS vulnerability, use<i18n-t>instead - String concatenation -- word order varies by language, use complete messages with interpolation
- Setting locale before messages load -- shows raw keys, always await loading first
- Using
dateTimeFormatsinstead ofdatetimeFormats-- the config key uses lowercase 't' - Using
$tc()-- removed in v11, uset()with count parameter - Using
v-tdirective -- deprecated in v11, removed in v12, uset()or<i18n-t>
Gotchas & Edge Cases:
locale.valueis a ref -- assign with.value, not direct assignment@:linked.messagesyntax only works with global scope, not local scope- Custom
pluralRulesfunction returns an index into the array, not the form itself
See reference.md for full anti-pattern code examples and decision frameworks.
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST set legacy: false in createI18n for Composition API mode)
(You MUST use a SINGLE useI18n() call per component - destructure all needed functions from one call)
(You MUST await locale message loading before setting locale.value - setting locale before messages are loaded shows raw keys)
(You MUST use named constants for locale codes - NO inline locale strings)
Failure to follow these rules will break i18n reactivity and cause translation inconsistencies.
</critical_reminders>