agentsclimarketplace

Web i18n vue i18n

Skill agents-inc/skills/src/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

Install
npx -y skills add agents-inc/skills --skill web-i18n-vue-i18n

Assembled 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. useI18n composable for translations, d() for dates, n() for numbers, i18n-t component for rich text. Set legacy: false for 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:

  1. Composition API first: Use useI18n() composable with legacy: false for modern Vue 3 patterns
  2. Single composable call: Destructure all functions (t, d, n, locale) from ONE useI18n() call
  3. Locale reactivity: Locale changes automatically trigger re-renders via Vue's reactivity system
  4. Message format standard: Use pipe-separated plurals and named interpolation for translator-friendly messages
</philosophy>
<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.value from useI18n to read/write
  • Locale changes are reactive -- all components using t(), d(), n() update automatically
  • globalInjection defaults to true, injecting $t, $d, $n into 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-html with 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 dateTimeFormats instead of datetimeFormats -- the config key uses lowercase 't'
  • Using $tc() -- removed in v11, use t() with count parameter
  • Using v-t directive -- deprecated in v11, removed in v12, use t() or <i18n-t>

Gotchas & Edge Cases:

  • locale.value is a ref -- assign with .value, not direct assignment
  • @:linked.message syntax only works with global scope, not local scope
  • Custom pluralRules function 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>

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.