@ilingo/vue
v6.1.1
Published
Vue 3 integration for ilingo — provide/inject, reactive locale, the <ITranslate> component, a v-t directive, and the useTranslation composable.
Maintainers
Readme
Table of Contents
Installation
npm install @ilingo/vue --saveUsage
import { install } from '@ilingo/vue';
import { MemoryStore, defineCatalog, defineLocale, defineNamespace, defineTranslations } from 'ilingo';
import { createApp } from 'vue';
const store = new MemoryStore({
data: defineCatalog([
// locale: de
defineLocale('de', [
// namespace: app
defineNamespace('app', [
defineTranslations({ key: 'Hallo mein Name ist {{name}}' }),
]),
]),
// locale: en
defineLocale('en', [
defineNamespace('app', [
defineTranslations({ key: 'Hello my name is {{name}}' }),
]),
]),
]),
})
const app = createApp(/* */);
install(app, {
store,
});
app.mount('#app');
<script setup>
import { injectLocale, useTranslation } from '@ilingo/vue';
const locale = injectLocale();
const set = (value) => {
locale.value = value;
}
const translation = useTranslation({
namespace: 'app',
key: 'key',
data: {
name: 'Paul'
}
});
</script>
<template>
<div>
<ITranslate path="app.key" :data="{'name': 'Peter'}"/>
<!-- Hello my name is Peter -->
</div>
<div>
{{ translation }}
<!-- Hello my name is Paul -->
</div>
<button type="button" @click.prevent="set('en')">
en
</button>
<button type="button" @click.prevent="set('de')">
de
</button>
</template>SSR: the first render is not a placeholder
useTranslation (and <ITranslate>, <ITranslateT>, useScopedCatalog().t) wraps the asynchronous Ilingo.get() in a computedAsync, which needs an initial value before its promise settles. That initial value comes from Ilingo.getSync(), the synchronous read path, so with an in-memory catalog the first render is already the real string rather than the namespace.key placeholder (#988).
For server-side rendering that removes a whole class of bugs: the translated string is what lands in the SSR markup, and the client's first render matches it instead of warning
[Vue warn]: Hydration text mismatch in th
- rendered on server: "Name"
- expected on client: "app.name"No opt-in, no payload plumbing. Stores that need I/O (a cold LoaderStore or FSStore, a remote adapter) can't answer synchronously. Those keys still start at the placeholder and settle a tick later; warm them before rendering if the server output matters. See the SSR recipe and getSync.
<ITranslateT>: slot-aware interpolation
<ITranslateT> lets a message string carry slot placeholders alongside the usual {{var}} interpolations. Each {slot} placeholder in the message is filled by a named scoped slot, so you can drop arbitrary VNodes (links, icons, bold runs) inline without splitting the message across multiple keys.
Message: "Hi {{user}}, please {cta} to continue."
<ITranslateT path="app.welcome" :data="{ user: 'Peter' }">
<template #cta>
<a href="/start">get started</a>
</template>
</ITranslateT>
<!-- → <span>Hi Peter, please <a href="/start">get started</a> to continue.</span> -->- Rendered tag:
<span>by default. Override withtag="p"etc.; passtag=""to render a fragment with no wrapper. - Unfilled slot placeholders stay as literal
{slot}text (no throw). {{var}}placeholders that have no matchingdatakey stay as literal{{var}}.
v-t directive
v-t writes the translation into the element's textContent and reacts to locale changes without remounting the element.
<p v-t="'app.greeting'"></p>
<p v-t="{ path: 'app.greet', data: { name: 'Peter' } }"></p>
<p v-t="{ namespace: 'cart', key: 'items', count: 3 }"></p>The directive is registered globally during install(). Opt out per-app via install(app, { store, directives: false }).
useScopedCatalog: per-component message scope
Some components (modals, embedded widgets, marketing sections) carry their own strings. useScopedCatalog creates an Ilingo instance whose stores resolve scoped messages first, then fall back to the parent app's stores. The scoped instance is provided to descendants; siblings outside the component still see the parent's stores.
<script setup>
import { useScopedCatalog, useTranslation } from '@ilingo/vue';
import { defineCatalog, defineLocale, defineNamespace, defineTranslations } from 'ilingo';
// Returns { instance, t }. Use `t` inside the same component because
// Vue's provide/inject can't reach the current setup's own provides.
const { t } = useScopedCatalog({
messages: defineCatalog([
defineLocale('en', [
defineNamespace('modal', [
defineTranslations({ greeting: 'Welcome to the modal!' }),
]),
]),
]),
});
const greeting = t({ namespace: 'modal', key: 'greeting' });
</script>Descendants can use plain useTranslation: they get the scoped instance via inject. On unmount, Vue's provides become unreachable and the scoped instance is garbage-collected.
License
Made with 💚
Published under MIT License.
