@fluenti/vue-i18n-compat 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +237 -0
- package/dist/bridge.d.ts +22 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/composable.d.ts +15 -0
- package/dist/composable.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fluenti Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# @fluenti/vue-i18n-compat
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fluenti/vue-i18n-compat)
|
|
4
|
+
[](https://bundlephobia.com/package/@fluenti/vue-i18n-compat)
|
|
5
|
+
[](https://github.com/usefluenti/fluenti/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
**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.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why?
|
|
12
|
+
|
|
13
|
+
Rewriting every translation call in a large Vue app is impractical. This package lets you:
|
|
14
|
+
|
|
15
|
+
- **Keep existing code working** -- `$t()`, `$tc()`, `$te()`, and the Composition API all behave the same way they always have.
|
|
16
|
+
- **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.
|
|
17
|
+
- **Share locale state** -- switching language in one library automatically updates the other, so users never see mixed-language UI.
|
|
18
|
+
- **Remove it when you are done** -- once every message is migrated, swap `@fluenti/vue-i18n-compat` for `@fluenti/vue` and delete vue-i18n.
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @fluenti/vue-i18n-compat @fluenti/core @fluenti/vue
|
|
26
|
+
# vue ^3.5 and vue-i18n ^9 || ^10 are peer dependencies
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. Swap the Plugin
|
|
30
|
+
|
|
31
|
+
**Before** (vue-i18n only):
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createApp } from 'vue'
|
|
35
|
+
import { createI18n } from 'vue-i18n'
|
|
36
|
+
|
|
37
|
+
const i18n = createI18n({
|
|
38
|
+
legacy: false,
|
|
39
|
+
locale: 'en',
|
|
40
|
+
messages: {
|
|
41
|
+
en: { greeting: 'Hello!', farewell: 'Goodbye, {name}!' },
|
|
42
|
+
ja: { greeting: 'こんにちは!', farewell: 'さようなら、{name}!' },
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const app = createApp(App)
|
|
47
|
+
app.use(i18n)
|
|
48
|
+
app.mount('#app')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**After** (bridge installed, existing code unchanged):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createApp } from 'vue'
|
|
55
|
+
import { createI18n } from 'vue-i18n'
|
|
56
|
+
import { createFluentVue } from '@fluenti/vue'
|
|
57
|
+
import { createFluentBridge } from '@fluenti/vue-i18n-compat'
|
|
58
|
+
|
|
59
|
+
// Your existing vue-i18n setup -- nothing changes here
|
|
60
|
+
const i18n = createI18n({
|
|
61
|
+
legacy: false,
|
|
62
|
+
locale: 'en',
|
|
63
|
+
messages: {
|
|
64
|
+
en: { greeting: 'Hello!', farewell: 'Goodbye, {name}!' },
|
|
65
|
+
ja: { greeting: 'こんにちは!', farewell: 'さようなら、{name}!' },
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Fluenti setup (empty for now -- you will add messages here over time)
|
|
70
|
+
const fluenti = createFluentVue({
|
|
71
|
+
locale: 'en',
|
|
72
|
+
fallbackLocale: 'en',
|
|
73
|
+
messages: { en: {}, ja: {} },
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Bridge: one plugin to rule them both
|
|
77
|
+
const bridge = createFluentBridge({ vueI18n: i18n, fluenti })
|
|
78
|
+
|
|
79
|
+
const app = createApp(App)
|
|
80
|
+
app.use(bridge) // replaces app.use(i18n)
|
|
81
|
+
app.mount('#app')
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Existing Code Works
|
|
85
|
+
|
|
86
|
+
Every `$t('greeting')` in your templates and every `useI18n()` in your `<script setup>` blocks keeps working. No changes required.
|
|
87
|
+
|
|
88
|
+
```vue
|
|
89
|
+
<template>
|
|
90
|
+
<!-- These calls hit vue-i18n, just like before -->
|
|
91
|
+
<h1>{{ $t('greeting') }}</h1>
|
|
92
|
+
<p>{{ $t('farewell', { name: 'Alice' }) }}</p>
|
|
93
|
+
</template>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 4. Gradually Migrate
|
|
97
|
+
|
|
98
|
+
Move messages one at a time. When a key exists in both libraries, the bridge resolves it from Fluenti first (configurable).
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// Before: vue-i18n format
|
|
102
|
+
{ greeting: 'Hello!' }
|
|
103
|
+
|
|
104
|
+
// After: ICU MessageFormat in Fluenti
|
|
105
|
+
{ greeting: 'Hello!' } // simple strings are identical
|
|
106
|
+
|
|
107
|
+
// Before: vue-i18n plural (pipe syntax)
|
|
108
|
+
{ items: 'no items | one item | {count} items' }
|
|
109
|
+
|
|
110
|
+
// After: ICU plural in Fluenti
|
|
111
|
+
{ items: '{count, plural, =0 {no items} one {one item} other {# items}}' }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
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:
|
|
115
|
+
|
|
116
|
+
```diff
|
|
117
|
+
- import { createFluentBridge } from '@fluenti/vue-i18n-compat'
|
|
118
|
+
+ import { createFluentVue } from '@fluenti/vue'
|
|
119
|
+
|
|
120
|
+
- app.use(bridge)
|
|
121
|
+
+ app.use(fluenti)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
### Composition API
|
|
127
|
+
|
|
128
|
+
```vue
|
|
129
|
+
<script setup>
|
|
130
|
+
import { useI18n } from '@fluenti/vue-i18n-compat'
|
|
131
|
+
|
|
132
|
+
const { t, tc, te, locale, setLocale, availableLocales } = useI18n()
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<template>
|
|
136
|
+
<h1>{{ t('greeting') }}</h1> <!-- vue-i18n key -->
|
|
137
|
+
<p>{{ t('welcome') }}</p> <!-- Fluenti key -->
|
|
138
|
+
<p v-if="te('greeting')">Exists!</p> <!-- checks both libraries -->
|
|
139
|
+
<p>{{ tc('items', count) }}</p> <!-- pipe-separated or ICU plurals -->
|
|
140
|
+
|
|
141
|
+
<button @click="setLocale('ja')">日本語</button>
|
|
142
|
+
</template>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Options API
|
|
146
|
+
|
|
147
|
+
`$t`, `$te`, and `$tc` are overridden globally -- existing templates work without changes.
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<template>
|
|
151
|
+
<p>{{ $t('greeting') }}</p>
|
|
152
|
+
<p>{{ $tc('items', 3) }}</p>
|
|
153
|
+
<p v-if="$te('farewell')">{{ $t('farewell', { name: 'Bob' }) }}</p>
|
|
154
|
+
</template>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## How It Works
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
┌────────────┐ locale sync ┌─────────┐
|
|
161
|
+
│ vue-i18n │◄───────────────►│ Fluenti │
|
|
162
|
+
└─────┬──────┘ └────┬────┘
|
|
163
|
+
│ ┌──────────────┐ │
|
|
164
|
+
└─────►│ Bridge │◄──────┘
|
|
165
|
+
│ t / tc / te │
|
|
166
|
+
└──────────────┘
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
### `createFluentBridge(options)`
|
|
174
|
+
|
|
175
|
+
Creates the bridge plugin. Call `app.use(bridge)` instead of `app.use(i18n)`.
|
|
176
|
+
|
|
177
|
+
| Option | Type | Default | Description |
|
|
178
|
+
|--------|------|---------|-------------|
|
|
179
|
+
| `vueI18n` | `VueI18nInstance` | **required** | vue-i18n instance from `createI18n()` |
|
|
180
|
+
| `fluenti` | `FluentVuePlugin` | **required** | Fluenti plugin from `createFluentVue()` |
|
|
181
|
+
| `priority` | `'fluenti-first' \| 'vue-i18n-first'` | `'fluenti-first'` | Which library to check first for translations |
|
|
182
|
+
|
|
183
|
+
### `useI18n()`
|
|
184
|
+
|
|
185
|
+
Returns the `BridgeContext`. Must be called inside a component where the bridge plugin is installed.
|
|
186
|
+
|
|
187
|
+
### `BridgeContext`
|
|
188
|
+
|
|
189
|
+
| Property / Method | Type | Description |
|
|
190
|
+
|-------------------|------|-------------|
|
|
191
|
+
| `t(key, values?)` | `string` | Translate -- checks both libraries per priority |
|
|
192
|
+
| `tc(key, count, values?)` | `string` | Pluralized translation (pipe syntax or ICU) |
|
|
193
|
+
| `te(key, locale?)` | `boolean` | Check if key exists in either library |
|
|
194
|
+
| `tm(key)` | `unknown` | Get the raw message object |
|
|
195
|
+
| `d(value, style?)` | `string` | Format a date |
|
|
196
|
+
| `n(value, style?)` | `string` | Format a number |
|
|
197
|
+
| `format(message, values?)` | `string` | Format an ICU message string directly |
|
|
198
|
+
| `locale` | `Ref<string>` | Reactive locale (synced across both libraries) |
|
|
199
|
+
| `setLocale(locale)` | `Promise<void>` | Change locale (syncs to both libraries) |
|
|
200
|
+
| `availableLocales` | `ComputedRef<string[]>` | Merged locales from both libraries |
|
|
201
|
+
| `isLoading` | `Ref<boolean>` | Whether Fluenti is loading a locale chunk |
|
|
202
|
+
| `fluenti` | `FluentVueContext` | Access the underlying Fluenti context |
|
|
203
|
+
| `vueI18n` | `VueI18nGlobal` | Access the underlying vue-i18n global composer |
|
|
204
|
+
|
|
205
|
+
## Compatibility Matrix
|
|
206
|
+
|
|
207
|
+
| vue-i18n feature | Supported | Notes |
|
|
208
|
+
|------------------|-----------|-------|
|
|
209
|
+
| `$t()` / `t()` | Yes | Bridged with fallthrough to both libraries |
|
|
210
|
+
| `$tc()` / `tc()` | Yes | Pipe-separated plurals via vue-i18n; ICU plurals via Fluenti |
|
|
211
|
+
| `$te()` / `te()` | Yes | Returns `true` if key exists in either library |
|
|
212
|
+
| `$tm()` / `tm()` | Yes | Returns raw message from priority library |
|
|
213
|
+
| `$d()` / `d()` | Yes | Delegates to Fluenti's date formatter |
|
|
214
|
+
| `$n()` / `n()` | Yes | Delegates to Fluenti's number formatter |
|
|
215
|
+
| `useI18n()` | Yes | Returns unified `BridgeContext` |
|
|
216
|
+
| Composition API mode | Yes | Requires `legacy: false` in vue-i18n |
|
|
217
|
+
| Legacy API mode | No | Use `legacy: false` -- the bridge requires the Composition API |
|
|
218
|
+
| Component interpolation (`<i18n-t>`) | No | Use Fluenti's `<Trans>` component instead |
|
|
219
|
+
| Custom directives (`v-t`) | No | Fluenti uses `v-t` as a compile-time node transform |
|
|
220
|
+
| Per-component `i18n` blocks | No | Use Fluenti's file-based catalogs or `defineMessages()` |
|
|
221
|
+
|
|
222
|
+
## Migration Checklist
|
|
223
|
+
|
|
224
|
+
1. Install the bridge alongside your existing vue-i18n setup
|
|
225
|
+
2. Verify your app works without any other changes
|
|
226
|
+
3. Write new features using Fluenti messages (ICU MessageFormat)
|
|
227
|
+
4. Move existing messages from vue-i18n format to ICU format, one file at a time
|
|
228
|
+
5. Remove empty vue-i18n locale objects as they become unnecessary
|
|
229
|
+
6. When all messages are migrated, replace the bridge with `@fluenti/vue` and uninstall vue-i18n
|
|
230
|
+
|
|
231
|
+
## Documentation
|
|
232
|
+
|
|
233
|
+
Full documentation at [fluenti.dev](https://fluenti.dev).
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
[MIT](https://github.com/usefluenti/fluenti/blob/main/LICENSE)
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InjectionKey } from 'vue';
|
|
2
|
+
import { BridgeOptions, BridgeContext, BridgePlugin } from './types';
|
|
3
|
+
/** Injection key for the bridge context */
|
|
4
|
+
export declare const BRIDGE_KEY: InjectionKey<BridgeContext>;
|
|
5
|
+
/**
|
|
6
|
+
* Create a bridge plugin that connects vue-i18n and fluenti.
|
|
7
|
+
*
|
|
8
|
+
* Both libraries are installed, locale is synced bidirectionally,
|
|
9
|
+
* and translation lookups fall through from one library to the other.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const bridge = createFluentBridge({
|
|
14
|
+
* vueI18n: i18n,
|
|
15
|
+
* fluenti: fluent,
|
|
16
|
+
* priority: 'fluenti-first',
|
|
17
|
+
* })
|
|
18
|
+
* app.use(bridge)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function createFluentBridge(options: BridgeOptions): BridgePlugin;
|
|
22
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,YAAY,EAAmB,MAAM,KAAK,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEzE,2CAA2C;AAC3C,eAAO,MAAM,UAAU,EAAE,YAAY,CAAC,aAAa,CAA4B,CAAA;AAE/E;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CA0HvE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BridgeContext } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Unified composable that provides access to the bridge context.
|
|
4
|
+
*
|
|
5
|
+
* Returns a `BridgeContext` with translation methods that check both
|
|
6
|
+
* vue-i18n and fluenti catalogs, locale management that syncs across
|
|
7
|
+
* both libraries, and direct access to each underlying instance.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const { t, tc, te, locale, setLocale, fluenti, vueI18n } = useI18n()
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function useI18n(): BridgeContext;
|
|
15
|
+
//# sourceMappingURL=composable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composable.d.ts","sourceRoot":"","sources":["../src/composable.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,IAAI,aAAa,CASvC"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`vue`);var t=Symbol(`fluenti-bridge`);function n(n){let{vueI18n:r,fluenti:i,priority:a=`fluenti-first`}=n,o=i.global,s=r.global,c=!1;function l(e,t){if(!c){c=!0;try{e===`fluenti`?s.locale.value!==t&&(s.locale.value=t):o.locale.value!==t&&o.setLocale(t)}finally{c=!1}}}function u(e,t){let n=typeof e==`string`?e:e.id;return a===`fluenti-first`?o.te(n)?o.t(e,t):s.t(n,t??{}):s.te(n)?s.t(n,t??{}):o.t(e,t)}function d(e,t,n){return a===`fluenti-first`&&o.te(e)?o.t(e,{count:t,...n}):s.tc?s.tc(e,t,n??{}):s.t(e,{count:t,...n})}function f(e,t){return o.te(e,t)||s.te(e,t)}function p(e){if(a===`fluenti-first`){let t=o.tm(e);if(t!==void 0)return t}return s.tm(e)}let m=(0,e.computed)(()=>[...new Set([...o.getLocales(),...s.availableLocales])].sort()),h={t:u,tc:d,te:f,tm:p,d:o.d,n:o.n,format:o.format,locale:o.locale,setLocale:async e=>{await o.setLocale(e),l(`fluenti`,e)},availableLocales:m,isLoading:o.isLoading,fluenti:o,vueI18n:s};return{install(n){n.use(r),n.use(i),(0,e.watch)(o.locale,e=>{l(`fluenti`,e)}),(0,e.watch)(s.locale,e=>{l(`vue-i18n`,e)}),n.config.globalProperties.$t=u,n.config.globalProperties.$te=f,n.config.globalProperties.$tc=d,n.provide(t,h)},global:h}}function r(){let n=(0,e.inject)(t);if(!n)throw Error(`[@fluenti/vue-i18n-compat] useI18n() requires the bridge plugin to be installed. Call app.use(bridge) where bridge = createFluentBridge({ vueI18n, fluenti }).`);return n}exports.BRIDGE_KEY=t,exports.createFluentBridge=n,exports.useI18n=r;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/bridge.ts","../src/composable.ts"],"sourcesContent":["import { type App, type InjectionKey, computed, watch } from 'vue'\nimport type { BridgeOptions, BridgeContext, BridgePlugin } from './types'\n\n/** Injection key for the bridge context */\nexport const BRIDGE_KEY: InjectionKey<BridgeContext> = Symbol('fluenti-bridge')\n\n/**\n * Create a bridge plugin that connects vue-i18n and fluenti.\n *\n * Both libraries are installed, locale is synced bidirectionally,\n * and translation lookups fall through from one library to the other.\n *\n * @example\n * ```ts\n * const bridge = createFluentBridge({\n * vueI18n: i18n,\n * fluenti: fluent,\n * priority: 'fluenti-first',\n * })\n * app.use(bridge)\n * ```\n */\nexport function createFluentBridge(options: BridgeOptions): BridgePlugin {\n const { vueI18n, fluenti, priority = 'fluenti-first' } = options\n const fluentCtx = fluenti.global\n const vueI18nGlobal = vueI18n.global\n\n // --- Locale sync (bidirectional, with guard to prevent loops) ---\n let syncing = false\n\n function syncLocale(source: 'fluenti' | 'vue-i18n', newLocale: string) {\n if (syncing) return\n syncing = true\n try {\n if (source === 'fluenti') {\n if (vueI18nGlobal.locale.value !== newLocale) {\n vueI18nGlobal.locale.value = newLocale\n }\n } else {\n if (fluentCtx.locale.value !== newLocale) {\n fluentCtx.setLocale(newLocale)\n }\n }\n } finally {\n syncing = false\n }\n }\n\n // --- Bridged translation functions ---\n\n function bridgedT(key: string | { id: string; message?: string }, values?: Record<string, unknown>): string {\n const stringKey = typeof key === 'string' ? key : key.id\n\n if (priority === 'fluenti-first') {\n if (fluentCtx.te(stringKey)) {\n return fluentCtx.t(key, values)\n }\n return vueI18nGlobal.t(stringKey, values ?? {})\n } else {\n if (vueI18nGlobal.te(stringKey)) {\n return vueI18nGlobal.t(stringKey, values ?? {})\n }\n return fluentCtx.t(key, values)\n }\n }\n\n function bridgedTc(key: string, count: number, values?: Record<string, unknown>): string {\n // If fluenti has the key, use ICU plural resolution via t()\n if (priority === 'fluenti-first' && fluentCtx.te(key)) {\n return fluentCtx.t(key, { count, ...values })\n }\n // Fall back to vue-i18n's tc (which handles pipe-separated plurals)\n if (vueI18nGlobal.tc) {\n return vueI18nGlobal.tc(key, count, values ?? {})\n }\n // vue-i18n v10+ removed tc, use t with count\n return vueI18nGlobal.t(key, { count, ...values } as any)\n }\n\n function bridgedTe(key: string, locale?: string): boolean {\n return fluentCtx.te(key, locale) || vueI18nGlobal.te(key, locale)\n }\n\n function bridgedTm(key: string): unknown {\n // Prefer fluenti raw message if available\n if (priority === 'fluenti-first') {\n const raw = fluentCtx.tm(key)\n if (raw !== undefined) return raw\n }\n return vueI18nGlobal.tm(key)\n }\n\n const availableLocales = computed(() => {\n const locales = new Set<string>([\n ...fluentCtx.getLocales(),\n ...vueI18nGlobal.availableLocales,\n ])\n return [...locales].sort()\n })\n\n const context: BridgeContext = {\n t: bridgedT,\n tc: bridgedTc,\n te: bridgedTe,\n tm: bridgedTm,\n d: fluentCtx.d,\n n: fluentCtx.n,\n format: fluentCtx.format,\n locale: fluentCtx.locale,\n setLocale: async (locale: string) => {\n await fluentCtx.setLocale(locale)\n // setLocale already triggers the watcher, but ensure sync for non-reactive paths\n syncLocale('fluenti', locale)\n },\n availableLocales,\n isLoading: fluentCtx.isLoading,\n fluenti: fluentCtx,\n vueI18n: vueI18nGlobal,\n }\n\n return {\n install(app: App) {\n // Install both plugins\n app.use(vueI18n)\n app.use(fluenti)\n\n // Set up locale watchers (must be done after install since refs may be set up during install)\n watch(fluentCtx.locale, (newLocale) => {\n syncLocale('fluenti', newLocale)\n })\n watch(vueI18nGlobal.locale, (newLocale) => {\n syncLocale('vue-i18n', newLocale)\n })\n\n // Override global properties with bridged versions\n app.config.globalProperties['$t'] = bridgedT\n app.config.globalProperties['$te'] = bridgedTe\n app.config.globalProperties['$tc'] = bridgedTc\n\n // Provide bridge context\n app.provide(BRIDGE_KEY, context)\n },\n global: context,\n }\n}\n","import { inject } from 'vue'\nimport { BRIDGE_KEY } from './bridge'\nimport type { BridgeContext } from './types'\n\n/**\n * Unified composable that provides access to the bridge context.\n *\n * Returns a `BridgeContext` with translation methods that check both\n * vue-i18n and fluenti catalogs, locale management that syncs across\n * both libraries, and direct access to each underlying instance.\n *\n * @example\n * ```ts\n * const { t, tc, te, locale, setLocale, fluenti, vueI18n } = useI18n()\n * ```\n */\nexport function useI18n(): BridgeContext {\n const ctx = inject(BRIDGE_KEY)\n if (!ctx) {\n throw new Error(\n '[@fluenti/vue-i18n-compat] useI18n() requires the bridge plugin to be installed. ' +\n 'Call app.use(bridge) where bridge = createFluentBridge({ vueI18n, fluenti }).',\n )\n }\n return ctx\n}\n"],"mappings":"wFAIA,IAAa,EAA0C,OAAO,iBAAiB,CAkB/E,SAAgB,EAAmB,EAAsC,CACvE,GAAM,CAAE,UAAS,UAAS,WAAW,iBAAoB,EACnD,EAAY,EAAQ,OACpB,EAAgB,EAAQ,OAG1B,EAAU,GAEd,SAAS,EAAW,EAAgC,EAAmB,CACjE,MACJ,GAAU,GACV,GAAI,CACE,IAAW,UACT,EAAc,OAAO,QAAU,IACjC,EAAc,OAAO,MAAQ,GAG3B,EAAU,OAAO,QAAU,GAC7B,EAAU,UAAU,EAAU,QAG1B,CACR,EAAU,KAMd,SAAS,EAAS,EAAgD,EAA0C,CAC1G,IAAM,EAAY,OAAO,GAAQ,SAAW,EAAM,EAAI,GAWpD,OATE,IAAa,gBACX,EAAU,GAAG,EAAU,CAClB,EAAU,EAAE,EAAK,EAAO,CAE1B,EAAc,EAAE,EAAW,GAAU,EAAE,CAAC,CAE3C,EAAc,GAAG,EAAU,CACtB,EAAc,EAAE,EAAW,GAAU,EAAE,CAAC,CAE1C,EAAU,EAAE,EAAK,EAAO,CAInC,SAAS,EAAU,EAAa,EAAe,EAA0C,CAUvF,OARI,IAAa,iBAAmB,EAAU,GAAG,EAAI,CAC5C,EAAU,EAAE,EAAK,CAAE,QAAO,GAAG,EAAQ,CAAC,CAG3C,EAAc,GACT,EAAc,GAAG,EAAK,EAAO,GAAU,EAAE,CAAC,CAG5C,EAAc,EAAE,EAAK,CAAE,QAAO,GAAG,EAAQ,CAAQ,CAG1D,SAAS,EAAU,EAAa,EAA0B,CACxD,OAAO,EAAU,GAAG,EAAK,EAAO,EAAI,EAAc,GAAG,EAAK,EAAO,CAGnE,SAAS,EAAU,EAAsB,CAEvC,GAAI,IAAa,gBAAiB,CAChC,IAAM,EAAM,EAAU,GAAG,EAAI,CAC7B,GAAI,IAAQ,IAAA,GAAW,OAAO,EAEhC,OAAO,EAAc,GAAG,EAAI,CAG9B,IAAM,GAAA,EAAA,EAAA,cAKG,CAAC,GAJQ,IAAI,IAAY,CAC9B,GAAG,EAAU,YAAY,CACzB,GAAG,EAAc,iBAClB,CAAC,CACiB,CAAC,MAAM,CAC1B,CAEI,EAAyB,CAC7B,EAAG,EACH,GAAI,EACJ,GAAI,EACJ,GAAI,EACJ,EAAG,EAAU,EACb,EAAG,EAAU,EACb,OAAQ,EAAU,OAClB,OAAQ,EAAU,OAClB,UAAW,KAAO,IAAmB,CACnC,MAAM,EAAU,UAAU,EAAO,CAEjC,EAAW,UAAW,EAAO,EAE/B,mBACA,UAAW,EAAU,UACrB,QAAS,EACT,QAAS,EACV,CAED,MAAO,CACL,QAAQ,EAAU,CAEhB,EAAI,IAAI,EAAQ,CAChB,EAAI,IAAI,EAAQ,EAGhB,EAAA,EAAA,OAAM,EAAU,OAAS,GAAc,CACrC,EAAW,UAAW,EAAU,EAChC,EACF,EAAA,EAAA,OAAM,EAAc,OAAS,GAAc,CACzC,EAAW,WAAY,EAAU,EACjC,CAGF,EAAI,OAAO,iBAAiB,GAAQ,EACpC,EAAI,OAAO,iBAAiB,IAAS,EACrC,EAAI,OAAO,iBAAiB,IAAS,EAGrC,EAAI,QAAQ,EAAY,EAAQ,EAElC,OAAQ,EACT,CC/HH,SAAgB,GAAyB,CACvC,IAAM,GAAA,EAAA,EAAA,QAAa,EAAW,CAC9B,GAAI,CAAC,EACH,MAAU,MACR,iKAED,CAEH,OAAO"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,YAAY,EACV,aAAa,EACb,aAAa,EACb,YAAY,EACZ,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,SAAS,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { computed as e, inject as t, watch as n } from "vue";
|
|
2
|
+
//#region src/bridge.ts
|
|
3
|
+
var r = Symbol("fluenti-bridge");
|
|
4
|
+
function i(t) {
|
|
5
|
+
let { vueI18n: i, fluenti: a, priority: o = "fluenti-first" } = t, s = a.global, c = i.global, l = !1;
|
|
6
|
+
function u(e, t) {
|
|
7
|
+
if (!l) {
|
|
8
|
+
l = !0;
|
|
9
|
+
try {
|
|
10
|
+
e === "fluenti" ? c.locale.value !== t && (c.locale.value = t) : s.locale.value !== t && s.setLocale(t);
|
|
11
|
+
} finally {
|
|
12
|
+
l = !1;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function d(e, t) {
|
|
17
|
+
let n = typeof e == "string" ? e : e.id;
|
|
18
|
+
return o === "fluenti-first" ? s.te(n) ? s.t(e, t) : c.t(n, t ?? {}) : c.te(n) ? c.t(n, t ?? {}) : s.t(e, t);
|
|
19
|
+
}
|
|
20
|
+
function f(e, t, n) {
|
|
21
|
+
return o === "fluenti-first" && s.te(e) ? s.t(e, {
|
|
22
|
+
count: t,
|
|
23
|
+
...n
|
|
24
|
+
}) : c.tc ? c.tc(e, t, n ?? {}) : c.t(e, {
|
|
25
|
+
count: t,
|
|
26
|
+
...n
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function p(e, t) {
|
|
30
|
+
return s.te(e, t) || c.te(e, t);
|
|
31
|
+
}
|
|
32
|
+
function m(e) {
|
|
33
|
+
if (o === "fluenti-first") {
|
|
34
|
+
let t = s.tm(e);
|
|
35
|
+
if (t !== void 0) return t;
|
|
36
|
+
}
|
|
37
|
+
return c.tm(e);
|
|
38
|
+
}
|
|
39
|
+
let h = e(() => [...new Set([...s.getLocales(), ...c.availableLocales])].sort()), g = {
|
|
40
|
+
t: d,
|
|
41
|
+
tc: f,
|
|
42
|
+
te: p,
|
|
43
|
+
tm: m,
|
|
44
|
+
d: s.d,
|
|
45
|
+
n: s.n,
|
|
46
|
+
format: s.format,
|
|
47
|
+
locale: s.locale,
|
|
48
|
+
setLocale: async (e) => {
|
|
49
|
+
await s.setLocale(e), u("fluenti", e);
|
|
50
|
+
},
|
|
51
|
+
availableLocales: h,
|
|
52
|
+
isLoading: s.isLoading,
|
|
53
|
+
fluenti: s,
|
|
54
|
+
vueI18n: c
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
install(e) {
|
|
58
|
+
e.use(i), e.use(a), n(s.locale, (e) => {
|
|
59
|
+
u("fluenti", e);
|
|
60
|
+
}), n(c.locale, (e) => {
|
|
61
|
+
u("vue-i18n", e);
|
|
62
|
+
}), e.config.globalProperties.$t = d, e.config.globalProperties.$te = p, e.config.globalProperties.$tc = f, e.provide(r, g);
|
|
63
|
+
},
|
|
64
|
+
global: g
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/composable.ts
|
|
69
|
+
function a() {
|
|
70
|
+
let e = t(r);
|
|
71
|
+
if (!e) throw Error("[@fluenti/vue-i18n-compat] useI18n() requires the bridge plugin to be installed. Call app.use(bridge) where bridge = createFluentBridge({ vueI18n, fluenti }).");
|
|
72
|
+
return e;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { r as BRIDGE_KEY, i as createFluentBridge, a as useI18n };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/bridge.ts","../src/composable.ts"],"sourcesContent":["import { type App, type InjectionKey, computed, watch } from 'vue'\nimport type { BridgeOptions, BridgeContext, BridgePlugin } from './types'\n\n/** Injection key for the bridge context */\nexport const BRIDGE_KEY: InjectionKey<BridgeContext> = Symbol('fluenti-bridge')\n\n/**\n * Create a bridge plugin that connects vue-i18n and fluenti.\n *\n * Both libraries are installed, locale is synced bidirectionally,\n * and translation lookups fall through from one library to the other.\n *\n * @example\n * ```ts\n * const bridge = createFluentBridge({\n * vueI18n: i18n,\n * fluenti: fluent,\n * priority: 'fluenti-first',\n * })\n * app.use(bridge)\n * ```\n */\nexport function createFluentBridge(options: BridgeOptions): BridgePlugin {\n const { vueI18n, fluenti, priority = 'fluenti-first' } = options\n const fluentCtx = fluenti.global\n const vueI18nGlobal = vueI18n.global\n\n // --- Locale sync (bidirectional, with guard to prevent loops) ---\n let syncing = false\n\n function syncLocale(source: 'fluenti' | 'vue-i18n', newLocale: string) {\n if (syncing) return\n syncing = true\n try {\n if (source === 'fluenti') {\n if (vueI18nGlobal.locale.value !== newLocale) {\n vueI18nGlobal.locale.value = newLocale\n }\n } else {\n if (fluentCtx.locale.value !== newLocale) {\n fluentCtx.setLocale(newLocale)\n }\n }\n } finally {\n syncing = false\n }\n }\n\n // --- Bridged translation functions ---\n\n function bridgedT(key: string | { id: string; message?: string }, values?: Record<string, unknown>): string {\n const stringKey = typeof key === 'string' ? key : key.id\n\n if (priority === 'fluenti-first') {\n if (fluentCtx.te(stringKey)) {\n return fluentCtx.t(key, values)\n }\n return vueI18nGlobal.t(stringKey, values ?? {})\n } else {\n if (vueI18nGlobal.te(stringKey)) {\n return vueI18nGlobal.t(stringKey, values ?? {})\n }\n return fluentCtx.t(key, values)\n }\n }\n\n function bridgedTc(key: string, count: number, values?: Record<string, unknown>): string {\n // If fluenti has the key, use ICU plural resolution via t()\n if (priority === 'fluenti-first' && fluentCtx.te(key)) {\n return fluentCtx.t(key, { count, ...values })\n }\n // Fall back to vue-i18n's tc (which handles pipe-separated plurals)\n if (vueI18nGlobal.tc) {\n return vueI18nGlobal.tc(key, count, values ?? {})\n }\n // vue-i18n v10+ removed tc, use t with count\n return vueI18nGlobal.t(key, { count, ...values } as any)\n }\n\n function bridgedTe(key: string, locale?: string): boolean {\n return fluentCtx.te(key, locale) || vueI18nGlobal.te(key, locale)\n }\n\n function bridgedTm(key: string): unknown {\n // Prefer fluenti raw message if available\n if (priority === 'fluenti-first') {\n const raw = fluentCtx.tm(key)\n if (raw !== undefined) return raw\n }\n return vueI18nGlobal.tm(key)\n }\n\n const availableLocales = computed(() => {\n const locales = new Set<string>([\n ...fluentCtx.getLocales(),\n ...vueI18nGlobal.availableLocales,\n ])\n return [...locales].sort()\n })\n\n const context: BridgeContext = {\n t: bridgedT,\n tc: bridgedTc,\n te: bridgedTe,\n tm: bridgedTm,\n d: fluentCtx.d,\n n: fluentCtx.n,\n format: fluentCtx.format,\n locale: fluentCtx.locale,\n setLocale: async (locale: string) => {\n await fluentCtx.setLocale(locale)\n // setLocale already triggers the watcher, but ensure sync for non-reactive paths\n syncLocale('fluenti', locale)\n },\n availableLocales,\n isLoading: fluentCtx.isLoading,\n fluenti: fluentCtx,\n vueI18n: vueI18nGlobal,\n }\n\n return {\n install(app: App) {\n // Install both plugins\n app.use(vueI18n)\n app.use(fluenti)\n\n // Set up locale watchers (must be done after install since refs may be set up during install)\n watch(fluentCtx.locale, (newLocale) => {\n syncLocale('fluenti', newLocale)\n })\n watch(vueI18nGlobal.locale, (newLocale) => {\n syncLocale('vue-i18n', newLocale)\n })\n\n // Override global properties with bridged versions\n app.config.globalProperties['$t'] = bridgedT\n app.config.globalProperties['$te'] = bridgedTe\n app.config.globalProperties['$tc'] = bridgedTc\n\n // Provide bridge context\n app.provide(BRIDGE_KEY, context)\n },\n global: context,\n }\n}\n","import { inject } from 'vue'\nimport { BRIDGE_KEY } from './bridge'\nimport type { BridgeContext } from './types'\n\n/**\n * Unified composable that provides access to the bridge context.\n *\n * Returns a `BridgeContext` with translation methods that check both\n * vue-i18n and fluenti catalogs, locale management that syncs across\n * both libraries, and direct access to each underlying instance.\n *\n * @example\n * ```ts\n * const { t, tc, te, locale, setLocale, fluenti, vueI18n } = useI18n()\n * ```\n */\nexport function useI18n(): BridgeContext {\n const ctx = inject(BRIDGE_KEY)\n if (!ctx) {\n throw new Error(\n '[@fluenti/vue-i18n-compat] useI18n() requires the bridge plugin to be installed. ' +\n 'Call app.use(bridge) where bridge = createFluentBridge({ vueI18n, fluenti }).',\n )\n }\n return ctx\n}\n"],"mappings":";;AAIA,IAAa,IAA0C,OAAO,iBAAiB;AAkB/E,SAAgB,EAAmB,GAAsC;CACvE,IAAM,EAAE,YAAS,YAAS,cAAW,oBAAoB,GACnD,IAAY,EAAQ,QACpB,IAAgB,EAAQ,QAG1B,IAAU;CAEd,SAAS,EAAW,GAAgC,GAAmB;AACjE,UACJ;OAAU;AACV,OAAI;AACF,IAAI,MAAW,YACT,EAAc,OAAO,UAAU,MACjC,EAAc,OAAO,QAAQ,KAG3B,EAAU,OAAO,UAAU,KAC7B,EAAU,UAAU,EAAU;aAG1B;AACR,QAAU;;;;CAMd,SAAS,EAAS,GAAgD,GAA0C;EAC1G,IAAM,IAAY,OAAO,KAAQ,WAAW,IAAM,EAAI;AAWpD,SATE,MAAa,kBACX,EAAU,GAAG,EAAU,GAClB,EAAU,EAAE,GAAK,EAAO,GAE1B,EAAc,EAAE,GAAW,KAAU,EAAE,CAAC,GAE3C,EAAc,GAAG,EAAU,GACtB,EAAc,EAAE,GAAW,KAAU,EAAE,CAAC,GAE1C,EAAU,EAAE,GAAK,EAAO;;CAInC,SAAS,EAAU,GAAa,GAAe,GAA0C;AAUvF,SARI,MAAa,mBAAmB,EAAU,GAAG,EAAI,GAC5C,EAAU,EAAE,GAAK;GAAE;GAAO,GAAG;GAAQ,CAAC,GAG3C,EAAc,KACT,EAAc,GAAG,GAAK,GAAO,KAAU,EAAE,CAAC,GAG5C,EAAc,EAAE,GAAK;GAAE;GAAO,GAAG;GAAQ,CAAQ;;CAG1D,SAAS,EAAU,GAAa,GAA0B;AACxD,SAAO,EAAU,GAAG,GAAK,EAAO,IAAI,EAAc,GAAG,GAAK,EAAO;;CAGnE,SAAS,EAAU,GAAsB;AAEvC,MAAI,MAAa,iBAAiB;GAChC,IAAM,IAAM,EAAU,GAAG,EAAI;AAC7B,OAAI,MAAQ,KAAA,EAAW,QAAO;;AAEhC,SAAO,EAAc,GAAG,EAAI;;CAG9B,IAAM,IAAmB,QAKhB,CAAC,GAJQ,IAAI,IAAY,CAC9B,GAAG,EAAU,YAAY,EACzB,GAAG,EAAc,iBAClB,CAAC,CACiB,CAAC,MAAM,CAC1B,EAEI,IAAyB;EAC7B,GAAG;EACH,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,GAAG,EAAU;EACb,GAAG,EAAU;EACb,QAAQ,EAAU;EAClB,QAAQ,EAAU;EAClB,WAAW,OAAO,MAAmB;AAGnC,GAFA,MAAM,EAAU,UAAU,EAAO,EAEjC,EAAW,WAAW,EAAO;;EAE/B;EACA,WAAW,EAAU;EACrB,SAAS;EACT,SAAS;EACV;AAED,QAAO;EACL,QAAQ,GAAU;AAmBhB,GAjBA,EAAI,IAAI,EAAQ,EAChB,EAAI,IAAI,EAAQ,EAGhB,EAAM,EAAU,SAAS,MAAc;AACrC,MAAW,WAAW,EAAU;KAChC,EACF,EAAM,EAAc,SAAS,MAAc;AACzC,MAAW,YAAY,EAAU;KACjC,EAGF,EAAI,OAAO,iBAAiB,KAAQ,GACpC,EAAI,OAAO,iBAAiB,MAAS,GACrC,EAAI,OAAO,iBAAiB,MAAS,GAGrC,EAAI,QAAQ,GAAY,EAAQ;;EAElC,QAAQ;EACT;;;;AC/HH,SAAgB,IAAyB;CACvC,IAAM,IAAM,EAAO,EAAW;AAC9B,KAAI,CAAC,EACH,OAAU,MACR,iKAED;AAEH,QAAO"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ComputedRef, Ref } from 'vue';
|
|
2
|
+
import { FluentVuePlugin, FluentVueContext } from '@fluenti/vue';
|
|
3
|
+
/** Lookup priority when resolving translations across both libraries */
|
|
4
|
+
export type BridgePriority = 'fluenti-first' | 'vue-i18n-first';
|
|
5
|
+
/** Options for creating the bridge plugin */
|
|
6
|
+
export interface BridgeOptions {
|
|
7
|
+
/** The vue-i18n instance (from createI18n()) */
|
|
8
|
+
vueI18n: VueI18nInstance;
|
|
9
|
+
/** The fluenti Vue plugin (from createFluentVue()) */
|
|
10
|
+
fluenti: FluentVuePlugin;
|
|
11
|
+
/** Which library to check first when resolving translations (default: 'fluenti-first') */
|
|
12
|
+
priority?: BridgePriority;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Minimal vue-i18n instance interface.
|
|
16
|
+
* We don't import vue-i18n types directly to avoid hard coupling.
|
|
17
|
+
*/
|
|
18
|
+
export interface VueI18nInstance {
|
|
19
|
+
install: (app: any) => void;
|
|
20
|
+
global: VueI18nGlobal;
|
|
21
|
+
}
|
|
22
|
+
/** Minimal vue-i18n global composer interface */
|
|
23
|
+
export interface VueI18nGlobal {
|
|
24
|
+
locale: Ref<string>;
|
|
25
|
+
t: (key: string, ...args: any[]) => string;
|
|
26
|
+
te: (key: string, locale?: string) => boolean;
|
|
27
|
+
tm: (key: string) => unknown;
|
|
28
|
+
tc?: (key: string, count: number, ...args: any[]) => string;
|
|
29
|
+
d: (value: Date | number, ...args: any[]) => string;
|
|
30
|
+
n: (value: number, ...args: any[]) => string;
|
|
31
|
+
availableLocales: string[];
|
|
32
|
+
}
|
|
33
|
+
/** Context returned by the bridge's useI18n() composable */
|
|
34
|
+
export interface BridgeContext {
|
|
35
|
+
/** Translate a message — checks both libraries per priority setting */
|
|
36
|
+
t(key: string | {
|
|
37
|
+
id: string;
|
|
38
|
+
message?: string;
|
|
39
|
+
}, values?: Record<string, unknown>): string;
|
|
40
|
+
/** Pluralized translation (delegates to vue-i18n for unmigrated plural messages) */
|
|
41
|
+
tc(key: string, count: number, values?: Record<string, unknown>): string;
|
|
42
|
+
/** Check if a translation key exists in either library */
|
|
43
|
+
te(key: string, locale?: string): boolean;
|
|
44
|
+
/** Get raw message (from vue-i18n) */
|
|
45
|
+
tm(key: string): unknown;
|
|
46
|
+
/** Format a date */
|
|
47
|
+
d(value: Date | number, style?: string): string;
|
|
48
|
+
/** Format a number */
|
|
49
|
+
n(value: number, style?: string): string;
|
|
50
|
+
/** Format an ICU message string directly */
|
|
51
|
+
format(message: string, values?: Record<string, unknown>): string;
|
|
52
|
+
/** Reactive locale ref (synced across both libraries) */
|
|
53
|
+
locale: Readonly<Ref<string>>;
|
|
54
|
+
/** Change locale (syncs to both libraries) */
|
|
55
|
+
setLocale(locale: string): Promise<void>;
|
|
56
|
+
/** All available locales from both libraries */
|
|
57
|
+
availableLocales: ComputedRef<string[]>;
|
|
58
|
+
/** Whether fluenti is loading a locale chunk */
|
|
59
|
+
isLoading: Readonly<Ref<boolean>>;
|
|
60
|
+
/** Access the underlying fluenti context */
|
|
61
|
+
fluenti: FluentVueContext;
|
|
62
|
+
/** Access the underlying vue-i18n global composer */
|
|
63
|
+
vueI18n: VueI18nGlobal;
|
|
64
|
+
}
|
|
65
|
+
/** Return type of createFluentBridge() */
|
|
66
|
+
export interface BridgePlugin {
|
|
67
|
+
/** Vue plugin install method */
|
|
68
|
+
install(app: any): void;
|
|
69
|
+
/** The global bridge context */
|
|
70
|
+
global: BridgeContext;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluenti/vue-i18n-compat",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Progressive migration bridge between vue-i18n and Fluenti — share locale, translate across both libraries",
|
|
6
|
+
"homepage": "https://fluenti.dev",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/usefluenti/fluenti.git",
|
|
10
|
+
"directory": "packages/vue-i18n-compat"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/usefluenti/fluenti/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"i18n",
|
|
21
|
+
"vue-i18n",
|
|
22
|
+
"compat",
|
|
23
|
+
"migration",
|
|
24
|
+
"bridge",
|
|
25
|
+
"fluenti",
|
|
26
|
+
"vue"
|
|
27
|
+
],
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"module": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"import": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"require": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"default": "./dist/index.cjs"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"vue": "^3.5",
|
|
48
|
+
"vue-i18n": "^9 || ^10"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@fluenti/core": "0.1.0",
|
|
52
|
+
"@fluenti/vue": "0.1.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@vitest/coverage-v8": "^4",
|
|
56
|
+
"typescript": "^5.9",
|
|
57
|
+
"vite": "^8",
|
|
58
|
+
"vite-plugin-dts": "^4",
|
|
59
|
+
"vitest": "^4",
|
|
60
|
+
"vue": "^3.5",
|
|
61
|
+
"vue-i18n": "^10",
|
|
62
|
+
"@vue/test-utils": "^2",
|
|
63
|
+
"happy-dom": "^20"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "vite build",
|
|
67
|
+
"dev": "vite build --watch",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"typecheck": "tsc --noEmit"
|
|
70
|
+
}
|
|
71
|
+
}
|