@zachhandley/ez-i18n 0.2.2 → 0.3.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/package.json +13 -48
- package/src/components/EzI18nHead.astro +62 -62
- package/src/index.ts +122 -122
- package/src/middleware.ts +61 -61
- package/src/runtime/index.ts +19 -19
- package/src/runtime/store.ts +122 -122
- package/src/types.ts +123 -123
- package/src/utils/index.ts +16 -16
- package/src/utils/translations.ts +418 -418
- package/src/virtual.d.ts +64 -64
- package/src/vite-plugin.ts +698 -698
- package/README.md +0 -371
- package/dist/runtime/react-plugin.d.ts +0 -28
- package/dist/runtime/react-plugin.js +0 -42
- package/dist/runtime/vue-plugin.d.ts +0 -51
- package/dist/runtime/vue-plugin.js +0 -67
- package/src/runtime/react-plugin.ts +0 -79
- package/src/runtime/vue-plugin.ts +0 -138
package/README.md
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
# @zachhandley/ez-i18n
|
|
2
|
-
|
|
3
|
-
Cookie-based i18n for Astro + Vue + React. No URL prefixes, reactive language switching.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @zachhandley/ez-i18n nanostores @nanostores/persistent
|
|
9
|
-
|
|
10
|
-
# If using Vue:
|
|
11
|
-
pnpm add @nanostores/vue
|
|
12
|
-
|
|
13
|
-
# If using React:
|
|
14
|
-
pnpm add @nanostores/react
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
### Astro Config
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
// astro.config.ts
|
|
23
|
-
import { defineConfig } from 'astro/config';
|
|
24
|
-
import vue from '@astrojs/vue';
|
|
25
|
-
import ezI18n from '@zachhandley/ez-i18n';
|
|
26
|
-
|
|
27
|
-
export default defineConfig({
|
|
28
|
-
integrations: [
|
|
29
|
-
vue(),
|
|
30
|
-
ezI18n({
|
|
31
|
-
locales: ['en', 'es', 'fr'],
|
|
32
|
-
defaultLocale: 'en',
|
|
33
|
-
cookieName: 'my-locale', // optional, defaults to 'ez-locale'
|
|
34
|
-
translations: {
|
|
35
|
-
en: './src/i18n/en.json',
|
|
36
|
-
es: './src/i18n/es.json',
|
|
37
|
-
fr: './src/i18n/fr.json',
|
|
38
|
-
},
|
|
39
|
-
}),
|
|
40
|
-
],
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### Translation Files
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"common": {
|
|
49
|
-
"welcome": "Welcome",
|
|
50
|
-
"save": "Save",
|
|
51
|
-
"cancel": "Cancel"
|
|
52
|
-
},
|
|
53
|
-
"auth": {
|
|
54
|
-
"login": "Log in",
|
|
55
|
-
"signup": "Sign up"
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Create similar files for each locale: `src/i18n/en.json`, `src/i18n/es.json`, etc.
|
|
61
|
-
|
|
62
|
-
### Multi-File Translations
|
|
63
|
-
|
|
64
|
-
ez-i18n supports flexible translation file organization:
|
|
65
|
-
|
|
66
|
-
#### Auto-Discovery (Zero Config)
|
|
67
|
-
|
|
68
|
-
Just put your files in `public/i18n/` and ez-i18n will discover them automatically:
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
public/i18n/
|
|
72
|
-
en/
|
|
73
|
-
common.json
|
|
74
|
-
auth.json
|
|
75
|
-
es/
|
|
76
|
-
common.json
|
|
77
|
-
auth.json
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
// astro.config.ts - locales auto-discovered from folder names!
|
|
82
|
-
ezI18n({
|
|
83
|
-
defaultLocale: 'en',
|
|
84
|
-
// No locales or translations needed - auto-discovered
|
|
85
|
-
})
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
#### Base Directory
|
|
89
|
-
|
|
90
|
-
Point to a folder and locales are discovered from subfolders:
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
ezI18n({
|
|
94
|
-
defaultLocale: 'en',
|
|
95
|
-
translations: './src/i18n/', // Discovers en/, es/, fr/ folders
|
|
96
|
-
})
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
#### Per-Locale with Multiple Formats
|
|
100
|
-
|
|
101
|
-
Mix and match different formats per locale:
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
ezI18n({
|
|
105
|
-
locales: ['en', 'es', 'fr', 'de'],
|
|
106
|
-
defaultLocale: 'en',
|
|
107
|
-
translations: {
|
|
108
|
-
en: './src/i18n/en.json', // Single file
|
|
109
|
-
es: './src/i18n/es/', // Folder (all JSONs merged)
|
|
110
|
-
fr: './src/i18n/fr/**/*.json', // Glob pattern
|
|
111
|
-
de: ['./src/i18n/de/common.json', // Array of files
|
|
112
|
-
'./src/i18n/de/auth.json'],
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
#### Merge Order
|
|
118
|
-
|
|
119
|
-
When using multiple files per locale, files are merged **alphabetically by filename**. Later files override earlier ones for conflicting keys.
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
en/
|
|
123
|
-
01-common.json # Loaded first
|
|
124
|
-
02-features.json # Loaded second, overrides common
|
|
125
|
-
99-overrides.json # Loaded last, highest priority
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
#### Path-Based Namespacing
|
|
129
|
-
|
|
130
|
-
When using folder-based translation organization, ez-i18n automatically creates namespaces from your file paths. This is **enabled by default** when using folder-based config.
|
|
131
|
-
|
|
132
|
-
**Example:**
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
public/i18n/
|
|
136
|
-
en/
|
|
137
|
-
auth/
|
|
138
|
-
login.json # { "title": "Sign In", "button": "Log In" }
|
|
139
|
-
signup.json # { "title": "Create Account" }
|
|
140
|
-
common.json # { "welcome": "Welcome" }
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Access translations using dot notation that mirrors the folder structure:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
$t('auth.login.title') // "Sign In"
|
|
147
|
-
$t('auth.login.button') // "Log In"
|
|
148
|
-
$t('auth.signup.title') // "Create Account"
|
|
149
|
-
$t('common.welcome') // "Welcome"
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
**Disable path-based namespacing:**
|
|
153
|
-
|
|
154
|
-
If you prefer to manage namespaces manually within your JSON files, you can disable this feature:
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
ezI18n({
|
|
158
|
-
defaultLocale: 'en',
|
|
159
|
-
translations: './src/i18n/',
|
|
160
|
-
pathBasedNamespacing: false, // Disable automatic path namespacing
|
|
161
|
-
})
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
With `pathBasedNamespacing: false`, the file structure is ignored and keys are used directly from each JSON file.
|
|
165
|
-
|
|
166
|
-
#### Cache File
|
|
167
|
-
|
|
168
|
-
A `.ez-i18n.json` cache file is generated to speed up subsequent builds. Add it to `.gitignore`:
|
|
169
|
-
|
|
170
|
-
```gitignore
|
|
171
|
-
.ez-i18n.json
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Layout Setup
|
|
175
|
-
|
|
176
|
-
Add the `EzI18nHead` component to your layout's head for automatic hydration:
|
|
177
|
-
|
|
178
|
-
```astro
|
|
179
|
-
---
|
|
180
|
-
// src/layouts/Layout.astro
|
|
181
|
-
import EzI18nHead from '@zachhandley/ez-i18n/astro';
|
|
182
|
-
const { locale, translations } = Astro.locals;
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
<html lang={locale}>
|
|
186
|
-
<head>
|
|
187
|
-
<meta charset="utf-8" />
|
|
188
|
-
<EzI18nHead locale={locale} translations={translations} />
|
|
189
|
-
</head>
|
|
190
|
-
<body>
|
|
191
|
-
<slot />
|
|
192
|
-
</body>
|
|
193
|
-
</html>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### In Astro Files
|
|
197
|
-
|
|
198
|
-
```astro
|
|
199
|
-
---
|
|
200
|
-
import { t, locale } from 'ez-i18n:runtime';
|
|
201
|
-
// Or access from locals (auto-loaded by middleware):
|
|
202
|
-
const { locale, translations } = Astro.locals;
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
<h1>{t('common.welcome')}</h1>
|
|
206
|
-
<p>Current locale: {locale}</p>
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### In Vue Components
|
|
210
|
-
|
|
211
|
-
```vue
|
|
212
|
-
<script setup lang="ts">
|
|
213
|
-
import { useI18n } from '@zachhandley/ez-i18n/vue';
|
|
214
|
-
import { translationLoaders } from 'ez-i18n:translations';
|
|
215
|
-
|
|
216
|
-
const { t, locale, setLocale } = useI18n();
|
|
217
|
-
|
|
218
|
-
// Change locale with dynamic translation loading
|
|
219
|
-
async function switchLocale(newLocale: string) {
|
|
220
|
-
await setLocale(newLocale, {
|
|
221
|
-
loadTranslations: translationLoaders[newLocale],
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
</script>
|
|
225
|
-
|
|
226
|
-
<template>
|
|
227
|
-
<!-- Global $t is available automatically -->
|
|
228
|
-
<h1>{{ $t('common.welcome') }}</h1>
|
|
229
|
-
|
|
230
|
-
<!-- Interpolation -->
|
|
231
|
-
<p>{{ $t('greeting', { name: 'World' }) }}</p>
|
|
232
|
-
|
|
233
|
-
<!-- Change language with dynamic loading -->
|
|
234
|
-
<button @click="switchLocale('es')">Español</button>
|
|
235
|
-
<button @click="switchLocale('fr')">Français</button>
|
|
236
|
-
</template>
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Vue Plugin Setup
|
|
240
|
-
|
|
241
|
-
Register the Vue plugin in your entrypoint:
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
// src/_vueEntrypoint.ts
|
|
245
|
-
import type { App } from 'vue';
|
|
246
|
-
import { ezI18nVue } from '@zachhandley/ez-i18n/vue';
|
|
247
|
-
|
|
248
|
-
export default (app: App) => {
|
|
249
|
-
app.use(ezI18nVue);
|
|
250
|
-
};
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### In React Components
|
|
254
|
-
|
|
255
|
-
```tsx
|
|
256
|
-
import { useI18n } from '@zachhandley/ez-i18n/react';
|
|
257
|
-
import { translationLoaders } from 'ez-i18n:translations';
|
|
258
|
-
|
|
259
|
-
function MyComponent() {
|
|
260
|
-
const { t, locale, setLocale } = useI18n();
|
|
261
|
-
|
|
262
|
-
async function switchLocale(newLocale: string) {
|
|
263
|
-
await setLocale(newLocale, {
|
|
264
|
-
loadTranslations: translationLoaders[newLocale],
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return (
|
|
269
|
-
<div>
|
|
270
|
-
<h1>{t('common.welcome')}</h1>
|
|
271
|
-
<p>{t('greeting', { name: 'World' })}</p>
|
|
272
|
-
<button onClick={() => switchLocale('es')}>Español</button>
|
|
273
|
-
<button onClick={() => switchLocale('fr')}>Français</button>
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
## Features
|
|
280
|
-
|
|
281
|
-
- **No URL prefixes** - Locale stored in cookie, not URL path
|
|
282
|
-
- **Reactive** - Language changes update immediately without page reload
|
|
283
|
-
- **SSR compatible** - Proper hydration with server-rendered locale
|
|
284
|
-
- **Vue integration** - Global `$t()`, `$locale`, `$setLocale` in templates
|
|
285
|
-
- **React integration** - `useI18n()` hook for React components
|
|
286
|
-
- **Middleware included** - Auto-detects locale from cookie, query param, or Accept-Language header
|
|
287
|
-
- **Multi-file support** - Organize translations in folders, use globs, or arrays
|
|
288
|
-
- **Auto-discovery** - Automatic locale detection from folder structure
|
|
289
|
-
- **Path-based namespacing** - Automatic namespacing from folder structure (`auth/login.json` becomes `auth.login.*`)
|
|
290
|
-
- **HMR in dev** - Hot reload translation changes without restart
|
|
291
|
-
|
|
292
|
-
## Locale Detection Priority
|
|
293
|
-
|
|
294
|
-
1. `?lang=xx` query parameter
|
|
295
|
-
2. Cookie value
|
|
296
|
-
3. Accept-Language header
|
|
297
|
-
4. Default locale
|
|
298
|
-
|
|
299
|
-
## API
|
|
300
|
-
|
|
301
|
-
### `ezI18n(config)`
|
|
302
|
-
|
|
303
|
-
Astro integration function.
|
|
304
|
-
|
|
305
|
-
| Option | Type | Required | Description |
|
|
306
|
-
|--------|------|----------|-------------|
|
|
307
|
-
| `locales` | `string[]` | No | Supported locale codes (auto-discovered if not provided) |
|
|
308
|
-
| `defaultLocale` | `string` | Yes | Fallback locale |
|
|
309
|
-
| `cookieName` | `string` | No | Cookie name (default: `'ez-locale'`) |
|
|
310
|
-
| `translations` | `string \| Record<string, TranslationPath>` | No | Base directory or per-locale paths (default: `./public/i18n/`) |
|
|
311
|
-
| `pathBasedNamespacing` | `boolean` | No | Auto-namespace translations from folder paths (default: `true` for folder-based config) |
|
|
312
|
-
|
|
313
|
-
**TranslationPath** can be:
|
|
314
|
-
- Single file: `'./src/i18n/en.json'`
|
|
315
|
-
- Folder: `'./src/i18n/en/'`
|
|
316
|
-
- Glob: `'./src/i18n/en/**/*.json'`
|
|
317
|
-
- Array: `['./common.json', './auth.json']`
|
|
318
|
-
|
|
319
|
-
### `EzI18nHead`
|
|
320
|
-
|
|
321
|
-
Astro component for i18n hydration. Place in your layout's `<head>`.
|
|
322
|
-
|
|
323
|
-
```astro
|
|
324
|
-
<EzI18nHead locale={Astro.locals.locale} translations={Astro.locals.translations} />
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
### `$t(key, params?)`
|
|
328
|
-
|
|
329
|
-
Translate a key with optional interpolation.
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
$t('greeting'); // "Hello"
|
|
333
|
-
$t('greeting', { name: 'World' }); // "Hello, {name}" -> "Hello, World"
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### `setLocale(locale, options?)`
|
|
337
|
-
|
|
338
|
-
Change the current locale. Updates cookie and triggers reactive update.
|
|
339
|
-
|
|
340
|
-
```typescript
|
|
341
|
-
// Simple usage
|
|
342
|
-
setLocale('es');
|
|
343
|
-
|
|
344
|
-
// With dynamic translation loading
|
|
345
|
-
import { translationLoaders } from 'ez-i18n:translations';
|
|
346
|
-
setLocale('es', { loadTranslations: translationLoaders['es'] });
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### `useI18n()`
|
|
350
|
-
|
|
351
|
-
Hook for Vue (Composition API) and React.
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
// Vue
|
|
355
|
-
import { useI18n } from '@zachhandley/ez-i18n/vue';
|
|
356
|
-
|
|
357
|
-
// React
|
|
358
|
-
import { useI18n } from '@zachhandley/ez-i18n/react';
|
|
359
|
-
|
|
360
|
-
const { t, locale, setLocale } = useI18n();
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Virtual Modules
|
|
364
|
-
|
|
365
|
-
- `ez-i18n:config` - Static config (locales, defaultLocale, cookieName)
|
|
366
|
-
- `ez-i18n:runtime` - Runtime functions (t, setLocale, initLocale, locale store)
|
|
367
|
-
- `ez-i18n:translations` - Translation loaders (loadTranslations, translationLoaders)
|
|
368
|
-
|
|
369
|
-
## License
|
|
370
|
-
|
|
371
|
-
MIT
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { setLocale } from '@zachhandley/ez-i18n/runtime';
|
|
2
|
-
import { T as TranslateFunction } from '../types-Cd9e7Lkc.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* React hook for i18n
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* import { useI18n } from '@zachhandley/ez-i18n/react';
|
|
9
|
-
*
|
|
10
|
-
* function MyComponent() {
|
|
11
|
-
* const { t, locale, setLocale } = useI18n();
|
|
12
|
-
*
|
|
13
|
-
* return (
|
|
14
|
-
* <div>
|
|
15
|
-
* <h1>{t('common.welcome')}</h1>
|
|
16
|
-
* <p>{t('greeting', { name: 'World' })}</p>
|
|
17
|
-
* <button onClick={() => setLocale('es')}>Español</button>
|
|
18
|
-
* </div>
|
|
19
|
-
* );
|
|
20
|
-
* }
|
|
21
|
-
*/
|
|
22
|
-
declare function useI18n(): {
|
|
23
|
-
t: TranslateFunction;
|
|
24
|
-
locale: string;
|
|
25
|
-
setLocale: typeof setLocale;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export { useI18n };
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// src/runtime/react-plugin.ts
|
|
2
|
-
import { useStore } from "@nanostores/react";
|
|
3
|
-
import { effectiveLocale, translations, setLocale } from "@zachhandley/ez-i18n/runtime";
|
|
4
|
-
function getNestedValue(obj, path) {
|
|
5
|
-
const keys = path.split(".");
|
|
6
|
-
let value = obj;
|
|
7
|
-
for (const key of keys) {
|
|
8
|
-
if (value == null || typeof value !== "object") {
|
|
9
|
-
return void 0;
|
|
10
|
-
}
|
|
11
|
-
value = value[key];
|
|
12
|
-
}
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
function interpolate(str, params) {
|
|
16
|
-
if (!params) return str;
|
|
17
|
-
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
18
|
-
return key in params ? String(params[key]) : match;
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function useI18n() {
|
|
22
|
-
const locale = useStore(effectiveLocale);
|
|
23
|
-
const trans = useStore(translations);
|
|
24
|
-
const t = (key, params) => {
|
|
25
|
-
const value = getNestedValue(trans, key);
|
|
26
|
-
if (typeof value !== "string") {
|
|
27
|
-
if (import.meta.env?.DEV) {
|
|
28
|
-
console.warn("[ez-i18n] Missing translation:", key);
|
|
29
|
-
}
|
|
30
|
-
return key;
|
|
31
|
-
}
|
|
32
|
-
return interpolate(value, params);
|
|
33
|
-
};
|
|
34
|
-
return {
|
|
35
|
-
t,
|
|
36
|
-
locale,
|
|
37
|
-
setLocale
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
export {
|
|
41
|
-
useI18n
|
|
42
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import * as vue from 'vue';
|
|
2
|
-
import { Plugin } from 'vue';
|
|
3
|
-
import { setLocale } from '@zachhandley/ez-i18n/runtime';
|
|
4
|
-
import { T as TranslateFunction } from '../types-Cd9e7Lkc.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Vue plugin that provides global $t(), $locale, and $setLocale
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* // In _vueEntrypoint.ts or main.ts
|
|
11
|
-
* import { ezI18nVue } from '@zachhandley/ez-i18n/vue';
|
|
12
|
-
*
|
|
13
|
-
* export default (app) => {
|
|
14
|
-
* app.use(ezI18nVue);
|
|
15
|
-
* };
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* // In Vue components
|
|
19
|
-
* <template>
|
|
20
|
-
* <h1>{{ $t('welcome.title') }}</h1>
|
|
21
|
-
* <p>{{ $t('welcome.message', { name: userName }) }}</p>
|
|
22
|
-
* <button @click="$setLocale('es')">Español</button>
|
|
23
|
-
* </template>
|
|
24
|
-
*/
|
|
25
|
-
declare const ezI18nVue: Plugin;
|
|
26
|
-
/**
|
|
27
|
-
* Composable for using i18n in Vue components with Composition API
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* <script setup>
|
|
31
|
-
* import { useI18n } from '@zachhandley/ez-i18n/vue';
|
|
32
|
-
*
|
|
33
|
-
* const { t, locale, setLocale } = useI18n();
|
|
34
|
-
* const greeting = t('welcome.greeting');
|
|
35
|
-
* </script>
|
|
36
|
-
*/
|
|
37
|
-
declare function useI18n(): {
|
|
38
|
-
t: TranslateFunction;
|
|
39
|
-
locale: Readonly<vue.Ref<string, string>>;
|
|
40
|
-
setLocale: typeof setLocale;
|
|
41
|
-
};
|
|
42
|
-
declare module 'vue' {
|
|
43
|
-
interface ComponentCustomProperties {
|
|
44
|
-
$t: TranslateFunction;
|
|
45
|
-
/** Current locale (reactive ref from nanostore) */
|
|
46
|
-
$locale: Readonly<vue.Ref<string>>;
|
|
47
|
-
$setLocale: typeof setLocale;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export { ezI18nVue as default, ezI18nVue, useI18n };
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
// src/runtime/vue-plugin.ts
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import { useStore } from "@nanostores/vue";
|
|
4
|
-
import { effectiveLocale, translations, setLocale } from "@zachhandley/ez-i18n/runtime";
|
|
5
|
-
function getNestedValue(obj, path) {
|
|
6
|
-
const keys = path.split(".");
|
|
7
|
-
let value = obj;
|
|
8
|
-
for (const key of keys) {
|
|
9
|
-
if (value == null || typeof value !== "object") {
|
|
10
|
-
return void 0;
|
|
11
|
-
}
|
|
12
|
-
value = value[key];
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
16
|
-
function interpolate(str, params) {
|
|
17
|
-
if (!params) return str;
|
|
18
|
-
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
19
|
-
return key in params ? String(params[key]) : match;
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
function createTranslateFunction(translationsRef) {
|
|
23
|
-
return (key, params) => {
|
|
24
|
-
const trans = translationsRef.value;
|
|
25
|
-
const value = getNestedValue(trans, key);
|
|
26
|
-
if (typeof value !== "string") {
|
|
27
|
-
if (import.meta.env?.DEV) {
|
|
28
|
-
console.warn("[ez-i18n] Missing translation:", key);
|
|
29
|
-
}
|
|
30
|
-
return key;
|
|
31
|
-
}
|
|
32
|
-
return interpolate(value, params);
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
var ezI18nVue = {
|
|
36
|
-
install(app) {
|
|
37
|
-
const locale = useStore(effectiveLocale);
|
|
38
|
-
const trans = useStore(translations);
|
|
39
|
-
const transComputed = computed(() => trans.value);
|
|
40
|
-
const t = createTranslateFunction(transComputed);
|
|
41
|
-
app.config.globalProperties.$t = t;
|
|
42
|
-
app.config.globalProperties.$locale = locale;
|
|
43
|
-
app.config.globalProperties.$setLocale = setLocale;
|
|
44
|
-
app.provide("ez-i18n", {
|
|
45
|
-
t,
|
|
46
|
-
locale,
|
|
47
|
-
setLocale
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
function useI18n() {
|
|
52
|
-
const locale = useStore(effectiveLocale);
|
|
53
|
-
const trans = useStore(translations);
|
|
54
|
-
const transComputed = computed(() => trans.value);
|
|
55
|
-
const t = createTranslateFunction(transComputed);
|
|
56
|
-
return {
|
|
57
|
-
t,
|
|
58
|
-
locale,
|
|
59
|
-
setLocale
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
var vue_plugin_default = ezI18nVue;
|
|
63
|
-
export {
|
|
64
|
-
vue_plugin_default as default,
|
|
65
|
-
ezI18nVue,
|
|
66
|
-
useI18n
|
|
67
|
-
};
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { useStore } from '@nanostores/react';
|
|
2
|
-
// Import from package path (not relative) to ensure shared store instance
|
|
3
|
-
import { effectiveLocale, translations, setLocale } from '@zachhandley/ez-i18n/runtime';
|
|
4
|
-
import type { TranslateFunction } from '../types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Get nested value from object using dot notation
|
|
8
|
-
*/
|
|
9
|
-
function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
|
|
10
|
-
const keys = path.split('.');
|
|
11
|
-
let value: unknown = obj;
|
|
12
|
-
|
|
13
|
-
for (const key of keys) {
|
|
14
|
-
if (value == null || typeof value !== 'object') {
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
|
-
value = (value as Record<string, unknown>)[key];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Interpolate params into string
|
|
25
|
-
*/
|
|
26
|
-
function interpolate(
|
|
27
|
-
str: string,
|
|
28
|
-
params?: Record<string, string | number>
|
|
29
|
-
): string {
|
|
30
|
-
if (!params) return str;
|
|
31
|
-
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
32
|
-
return key in params ? String(params[key]) : match;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* React hook for i18n
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* import { useI18n } from '@zachhandley/ez-i18n/react';
|
|
41
|
-
*
|
|
42
|
-
* function MyComponent() {
|
|
43
|
-
* const { t, locale, setLocale } = useI18n();
|
|
44
|
-
*
|
|
45
|
-
* return (
|
|
46
|
-
* <div>
|
|
47
|
-
* <h1>{t('common.welcome')}</h1>
|
|
48
|
-
* <p>{t('greeting', { name: 'World' })}</p>
|
|
49
|
-
* <button onClick={() => setLocale('es')}>Español</button>
|
|
50
|
-
* </div>
|
|
51
|
-
* );
|
|
52
|
-
* }
|
|
53
|
-
*/
|
|
54
|
-
export function useI18n() {
|
|
55
|
-
const locale = useStore(effectiveLocale);
|
|
56
|
-
const trans = useStore(translations);
|
|
57
|
-
|
|
58
|
-
const t: TranslateFunction = (
|
|
59
|
-
key: string,
|
|
60
|
-
params?: Record<string, string | number>
|
|
61
|
-
): string => {
|
|
62
|
-
const value = getNestedValue(trans, key);
|
|
63
|
-
|
|
64
|
-
if (typeof value !== 'string') {
|
|
65
|
-
if (import.meta.env?.DEV) {
|
|
66
|
-
console.warn('[ez-i18n] Missing translation:', key);
|
|
67
|
-
}
|
|
68
|
-
return key;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return interpolate(value, params);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
t,
|
|
76
|
-
locale,
|
|
77
|
-
setLocale,
|
|
78
|
-
};
|
|
79
|
-
}
|