@fluenti/vue-i18n-compat
v0.6.3
Published
Progressive migration bridge between vue-i18n and Fluenti — share locale, translate across both libraries
Downloads
2,309
Maintainers
Readme
@fluenti/vue-i18n-compat
Drop-in migration from vue-i18n to Fluenti. Keep your existing $t() calls, swap one plugin, and adopt compile-time i18n at your own pace.
Why?
Rewriting every translation call in a large Vue app is impractical. This package lets you:
- Keep existing code working --
$t(),$tc(),$te(), and the Composition API all behave the same way they always have. - Add Fluenti incrementally -- new features use ICU MessageFormat and compile-time transforms; legacy messages stay in vue-i18n format until you are ready to move them.
- Share locale state -- switching language in one library automatically updates the other, so users never see mixed-language UI.
- Remove it when you are done -- once every message is migrated, swap
@fluenti/vue-i18n-compatfor@fluenti/vueand delete vue-i18n.
Quick Start
1. Install
pnpm add @fluenti/vue-i18n-compat @fluenti/core @fluenti/vue
# vue ^3.5 and vue-i18n ^9 || ^10 are peer dependencies2. Swap the Plugin
Before (vue-i18n only):
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: { greeting: 'Hello!', farewell: 'Goodbye, {name}!' },
ja: { greeting: 'こんにちは!', farewell: 'さようなら、{name}!' },
},
})
const app = createApp(App)
app.use(i18n)
app.mount('#app')After (bridge installed, existing code unchanged):
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
import { createFluenti } from '@fluenti/vue'
import { createFluentBridge } from '@fluenti/vue-i18n-compat'
// Your existing vue-i18n setup -- nothing changes here
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: { greeting: 'Hello!', farewell: 'Goodbye, {name}!' },
ja: { greeting: 'こんにちは!', farewell: 'さようなら、{name}!' },
},
})
// Fluenti setup (empty for now -- you will add messages here over time)
const fluenti = createFluenti({
locale: 'en',
fallbackLocale: 'en',
messages: { en: {}, ja: {} },
})
// Bridge: one plugin to rule them both
const bridge = createFluentBridge({ vueI18n: i18n, fluenti })
const app = createApp(App)
app.use(bridge) // replaces app.use(i18n)
app.mount('#app')3. Existing Code Works
Every $t('greeting') in your templates and every useI18n() in your <script setup> blocks keeps working. No changes required.
<template>
<!-- These calls hit vue-i18n, just like before -->
<h1>{{ $t('greeting') }}</h1>
<p>{{ $t('farewell', { name: 'Alice' }) }}</p>
</template>4. Gradually Migrate
Move messages one at a time. When a key exists in both libraries, the bridge resolves it from Fluenti first (configurable).
// Before: vue-i18n format
{ greeting: 'Hello!' }
// After: ICU MessageFormat in Fluenti
{ greeting: 'Hello!' } // simple strings are identical
// Before: vue-i18n plural (pipe syntax)
{ items: 'no items | one item | {count} items' }
// After: ICU plural in Fluenti
{ items: '{count, plural, =0 {no items} one {one item} other {# items}}' }Once a key is in Fluenti, delete it from the vue-i18n messages object. When every key has been moved, replace the bridge with @fluenti/vue directly:
- import { createFluentBridge } from '@fluenti/vue-i18n-compat'
+ import { createFluenti } from '@fluenti/vue'
- app.use(bridge)
+ app.use(fluenti)Usage
Composition API
<script setup>
import { useI18n } from '@fluenti/vue-i18n-compat'
const { t, tc, te, locale, setLocale, availableLocales } = useI18n()
</script>
<template>
<h1>{{ t('greeting') }}</h1> <!-- vue-i18n key -->
<p>{{ t('welcome') }}</p> <!-- Fluenti key -->
<p v-if="te('greeting')">Exists!</p> <!-- checks both libraries -->
<p>{{ tc('items', count) }}</p> <!-- pipe-separated or ICU plurals -->
<button @click="setLocale('ja')">日本語</button>
</template>Options API
$t, $te, and $tc are overridden globally -- existing templates work without changes.
<template>
<p>{{ $t('greeting') }}</p>
<p>{{ $tc('items', 3) }}</p>
<p v-if="$te('farewell')">{{ $t('farewell', { name: 'Bob' }) }}</p>
</template>How It Works
┌────────────┐ locale sync ┌─────────┐
│ vue-i18n │◄───────────────►│ Fluenti │
└─────┬──────┘ └────┬────┘
│ ┌──────────────┐ │
└─────►│ Bridge │◄──────┘
│ t / tc / te │
└──────────────┘The bridge installs both libraries as a single Vue plugin. Locale state is synced bidirectionally -- switching language in one library automatically updates the other. Translation lookups fall through: if the primary library does not have a key, the bridge checks the other.
API Reference
createFluentBridge(options)
Creates the bridge plugin. Call app.use(bridge) instead of app.use(i18n).
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| vueI18n | VueI18nInstance | required | vue-i18n instance from createI18n() |
| fluenti | FluentiPlugin | required | Fluenti plugin from createFluenti() |
| priority | 'fluenti-first' \| 'vue-i18n-first' | 'fluenti-first' | Which library to check first for translations |
useI18n()
Returns the BridgeContext. Must be called inside a component where the bridge plugin is installed.
BridgeContext
| Property / Method | Type | Description |
|-------------------|------|-------------|
| t(key, values?) | string | Translate -- checks both libraries per priority |
| tc(key, count, values?) | string | Pluralized translation (pipe syntax or ICU) |
| te(key, locale?) | boolean | Check if key exists in either library |
| tm(key) | unknown | Get the raw message object |
| d(value, style?) | string | Format a date |
| n(value, style?) | string | Format a number |
| format(message, values?) | string | Format an ICU message string directly |
| locale | Ref<string> | Reactive locale (synced across both libraries) |
| setLocale(locale) | Promise<void> | Change locale (syncs to both libraries) |
| availableLocales | ComputedRef<string[]> | Merged locales from both libraries |
| isLoading | Ref<boolean> | Whether Fluenti is loading a locale chunk |
| fluenti | FluentiContext | Access the underlying Fluenti context |
| vueI18n | VueI18nGlobal | Access the underlying vue-i18n global composer |
Compatibility Matrix
| vue-i18n feature | Supported | Notes |
|------------------|-----------|-------|
| $t() / t() | Yes | Bridged with fallthrough to both libraries |
| $tc() / tc() | Yes | Pipe-separated plurals via vue-i18n; ICU plurals via Fluenti |
| $te() / te() | Yes | Returns true if key exists in either library |
| $tm() / tm() | Yes | Returns raw message from priority library |
| $d() / d() | Yes | Delegates to Fluenti's date formatter |
| $n() / n() | Yes | Delegates to Fluenti's number formatter |
| useI18n() | Yes | Returns unified BridgeContext |
| Composition API mode | Yes | Requires legacy: false in vue-i18n |
| Legacy API mode | No | Use legacy: false -- the bridge requires the Composition API |
| Component interpolation (<i18n-t>) | No | Use Fluenti's <Trans> component instead |
| Custom directives (v-t) | No | Fluenti uses v-t as a compile-time node transform |
| Per-component i18n blocks | No | Use Fluenti's file-based catalogs or defineMessages() |
Migration Checklist
- Install the bridge alongside your existing vue-i18n setup
- Verify your app works without any other changes
- Write new features using Fluenti messages (ICU MessageFormat)
- Move existing messages from vue-i18n format to ICU format, one file at a time
- Remove empty vue-i18n locale objects as they become unnecessary
- When all messages are migrated, replace the bridge with
@fluenti/vueand uninstall vue-i18n
Documentation
Full documentation at fluenti.dev.
