web-i18n-vue-i18n-2c6afb — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-i18n-vue-i18n-2c6afb (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide:createI18n({ legacy: false })selects Composition API mode, anduseI18n()returnstfor messages,dfor dates,nfor numbers andlocaleas a writable ref. Plurals are pipe-separated rather than ICU.<i18n-t>,<i18n-d>and<i18n-n>put components and styling inside a formatted result. Version boundary: Legacy API mode, thev-tdirective and the Rails%{var}format are deprecated in v11 and removed in v12;$tc()is already gone.
Detailed Resources:
<i18n-t>/<i18n-d>, types, locale switching<i18n-d>/<i18n-n> scoped slots, dynamic currencyuseI18n() reads the instancecreated by createI18n. Follow examples/core.md.
useI18n({ messages: { en: { ... } } }).Linked messages (@:key) resolve against global messages only, so a locally-scoped message cannot reference one.
setLocaleMessage after a dynamic import, andthe locale is not switched until the import resolves. Follow examples/lazy-loading.md.
<critical_requirements>
Set `legacy: false` in `createI18n`. It is what enables useI18n(); the default is the Options API mode that v11 deprecates and v12 removes.
Take everything from one `useI18n()` call per component. Destructure t, d, n and locale together — separate calls can resolve to separate composer instances that then disagree about the current locale.
Await the message load before assigning `locale.value`. Switching first renders the raw keys until the import resolves.
Set `fallbackLocale`. Without it a key missing from the active locale renders as the key rather than as the default locale's text.
</critical_requirements>
Auto-detection: vue-i18n, useI18n, createI18n, legacy: false, setLocaleMessage, i18n-t, i18n-d, i18n-n, keypath, pluralRules, datetimeFormats, numberFormats, fallbackLocale, globalInjection
Applies to:
Handled elsewhere:
to locales; the hook shape is in examples/lazy-loading.md
locale is an ordinary ref and needs nothing special<philosophy>
Messages are plain JSON resolved through a reactive locale ref, so a locale change re-renders every component that read a message — no subscription, no invalidation step. Two consequences shape the API. Because the composer holds that ref, one component wants one composer, which is why repeated useI18n() calls are the recurring bug. And because t() returns a string rather than a VNode, anything needing markup inside a translation goes through <i18n-t> instead — which is also what keeps v-html out of translated content.
</philosophy>
<patterns>
One instance, registered once on the app.
export const i18n = createI18n({
legacy: false, // enables useI18n()
locale: DEFAULT_LOCALE,
fallbackLocale: DEFAULT_LOCALE,
messages: { en },
});
app.use(i18n);globalInjection defaults to true, which is what puts $t, $d and $n in templates.
Full code: examples/core.md
One call, everything destructured from it.
<script setup lang="ts">
const { t, d, n, locale, availableLocales } = useI18n();
</script>
<template>
<h1>{{ t("dashboard.title") }}</h1>
<p>{{ d(updatedAt, "long") }} · {{ n(total, "currency") }}</p>
</template>Full code: examples/core.md
Named placeholders, {'@'} to escape a literal, and @:key to reference another message with an optional case modifier.
{
"app": { "name": "My App" },
"greeting": "Hello, {name}!",
"welcome": "Welcome to @:app.name!",
"shout": "@.upper:app.name"
}Full code: examples/core.md
Pipe-separated forms rather than ICU. {n} and {count} both resolve to the value passed as the second argument.
{
"car": "car | cars",
"apple": "no apples | one apple | {count} apples"
}Languages needing more than three forms take a pluralRules function per locale, which returns the index of the form to use.
Full code: examples/core.md
<i18n-t> puts components into a message through named slots, keeping the sentence whole.
<i18n-t keypath="tos" tag="p">
<template #terms>
<a href="/terms">{{ t("termsLink") }}</a>
</template>
</i18n-t>:plural selects the form, and <i18n-d> / <i18n-n> expose each formatted part — month, day, currency, integer — as its own scoped slot.
Full code: examples/core.md, scoped-slot styling in examples/formatting.md
Define the formats per locale once and refer to them by name, so a change lands everywhere.
createI18n({
legacy: false,
locale: "en-US",
datetimeFormats, // note the lowercase 't'
numberFormats,
});
d(new Date(), "long"); // "Friday, April 19, 2024 at 2:30 PM"
n(10000, "currency"); // "$10,000.00"Full code: examples/formatting.md
Register the messages, then move the locale — never the other way round.
export async function setLocale(locale: SupportedLocale): Promise<void> {
const messages = await import(`../locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages.default);
i18n.global.locale.value = locale;
document.documentElement.setAttribute("lang", locale);
}Full code: examples/lazy-loading.md
Augment DefineLocaleMessage with the shape of a message file, and DefineDateTimeFormat / DefineNumberFormat with the format names.
declare module "vue-i18n" {
export interface DefineLocaleMessage extends MessageSchema {}
}A wrong key, or a format name that was never defined, then fails at compile time.
Full code: examples/core.md
</patterns>
<red_flags>
Breaks at runtime:
createI18n without legacy: false — useI18n() is unavailable, and the Options API mode itfalls back to is removed in v12
locale directly instead of locale.value — it is a ref, so the assignment does nothing$tc() — removed in v11; t(key, count) replaces itv-t directive — deprecated in v11 and removed in v12; use t() or <i18n-t>dateTimeFormats as the config key — the option is datetimeFormats, and the misspelled key issilently ignored, so every named format resolves to nothing
Surprising behaviour:
useI18n() calls in one component can yield two composers, and a locale change in one isinvisible to the other
locale.value before the messages load renders the raw keys until the import resolvesfallbackLocale, a key missing from the active locale renders as the key itself@:linked.key resolves against global messages only — it finds nothing from a locally-scopeduseI18n({ messages })
pluralRules function returns the index of the form, not the formt() returns a string, so markup in a message needs <i18n-t>; reaching for v-html instead putswhatever is in the message straight into the DOM
lang attribute does not follow locale — update it on every switch; screenreaders choose their pronunciation from it, and crawlers read it as the page's language
Anti-patterns with the code that fixes them, and the full v8→v9 and v11/v12 migration tables: reference.md.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.