@zachhandley/ez-i18n 0.3.1 → 0.3.3
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/README.md +88 -0
- package/dist/index.d.ts +2 -2
- package/dist/middleware.js +14 -0
- package/dist/{types-Cd9e7Lkc.d.ts → types-Cg77gLzO.d.ts} +2 -0
- package/dist/utils/index.d.ts +1 -1
- package/package.json +3 -2
- package/src/middleware.ts +21 -0
- package/src/types.ts +2 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @zachhandley/ez-i18n
|
|
2
|
+
|
|
3
|
+
Cookie-based i18n for Astro. Ships the Astro integration plus the shared runtime stores used by the React/Vue bindings.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @zachhandley/ez-i18n nanostores @nanostores/persistent
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Astro Setup
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// astro.config.ts
|
|
15
|
+
import { defineConfig } from 'astro/config';
|
|
16
|
+
import vue from '@astrojs/vue';
|
|
17
|
+
import ezI18n from '@zachhandley/ez-i18n';
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
integrations: [
|
|
21
|
+
vue(),
|
|
22
|
+
ezI18n({
|
|
23
|
+
locales: ['en', 'es', 'fr'],
|
|
24
|
+
defaultLocale: 'en',
|
|
25
|
+
translations: {
|
|
26
|
+
en: './src/i18n/en.json',
|
|
27
|
+
es: './src/i18n/es.json',
|
|
28
|
+
},
|
|
29
|
+
}),
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Add `EzI18nHead` to your layout to hydrate the locale + translations on the client:
|
|
35
|
+
|
|
36
|
+
```astro
|
|
37
|
+
---
|
|
38
|
+
import EzI18nHead from '@zachhandley/ez-i18n/astro';
|
|
39
|
+
const { locale, translations } = Astro.locals;
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
<html lang={locale}>
|
|
43
|
+
<head>
|
|
44
|
+
<EzI18nHead locale={locale} translations={translations} />
|
|
45
|
+
</head>
|
|
46
|
+
<body><slot /></body>
|
|
47
|
+
</html>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Translations
|
|
51
|
+
|
|
52
|
+
Place JSON files per locale (auto-discovered in `public/i18n/` by default):
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"common": {
|
|
57
|
+
"welcome": "Welcome",
|
|
58
|
+
"save": "Save",
|
|
59
|
+
"cancel": "Cancel"
|
|
60
|
+
},
|
|
61
|
+
"auth": {
|
|
62
|
+
"login": "Log in",
|
|
63
|
+
"signup": "Sign up"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use dot notation and `{placeholder}` interpolation:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { t, locale, setLocale } from 'ez-i18n:runtime';
|
|
72
|
+
|
|
73
|
+
t('common.welcome'); // "Welcome"
|
|
74
|
+
t('auth.signup'); // "Sign up"
|
|
75
|
+
t('common.countdown', { seconds: 5 }); // "Ready in 5 seconds"
|
|
76
|
+
await setLocale('es');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Framework Bindings
|
|
80
|
+
|
|
81
|
+
- React: `@zachhandley/ez-i18n-react`
|
|
82
|
+
- Vue 3: `@zachhandley/ez-i18n-vue`
|
|
83
|
+
|
|
84
|
+
Both reuse the runtime stores provided by this package.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroIntegration } from 'astro';
|
|
2
|
-
import { E as EzI18nConfig } from './types-
|
|
3
|
-
export { T as TranslateFunction } from './types-
|
|
2
|
+
import { E as EzI18nConfig } from './types-Cg77gLzO.js';
|
|
3
|
+
export { T as TranslateFunction } from './types-Cg77gLzO.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* ez-i18n Astro integration
|
package/dist/middleware.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
// src/middleware.ts
|
|
2
2
|
import { defineMiddleware } from "astro:middleware";
|
|
3
|
+
function createT(translations) {
|
|
4
|
+
return (key, params) => {
|
|
5
|
+
const keys = key.split(".");
|
|
6
|
+
let value = translations;
|
|
7
|
+
for (const k of keys) {
|
|
8
|
+
if (value == null || typeof value !== "object") return key;
|
|
9
|
+
value = value[k];
|
|
10
|
+
}
|
|
11
|
+
if (typeof value !== "string") return key;
|
|
12
|
+
if (!params) return value;
|
|
13
|
+
return value.replace(/\{(\w+)\}/g, (_, p) => String(params[p] ?? `{${p}}`));
|
|
14
|
+
};
|
|
15
|
+
}
|
|
3
16
|
var onRequest = defineMiddleware(async ({ cookies, request, locals }, next) => {
|
|
4
17
|
const { locales, defaultLocale, cookieName } = await import("ez-i18n:config");
|
|
5
18
|
const url = new URL(request.url);
|
|
@@ -22,6 +35,7 @@ var onRequest = defineMiddleware(async ({ cookies, request, locals }, next) => {
|
|
|
22
35
|
} catch {
|
|
23
36
|
locals.translations = {};
|
|
24
37
|
}
|
|
38
|
+
locals.t = createT(locals.translations);
|
|
25
39
|
if (langParam && langParam !== cookieValue && locales.includes(langParam)) {
|
|
26
40
|
cookies.set(cookieName, locale, {
|
|
27
41
|
path: "/",
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LocaleTranslationPath, a as TranslationsConfig, b as TranslationCache } from '../types-
|
|
1
|
+
import { L as LocaleTranslationPath, a as TranslationsConfig, b as TranslationCache } from '../types-Cg77gLzO.js';
|
|
2
2
|
|
|
3
3
|
type PathType = 'file' | 'folder' | 'glob' | 'array';
|
|
4
4
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zachhandley/ez-i18n",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
|
-
"src"
|
|
33
|
+
"src",
|
|
34
|
+
"README.md"
|
|
34
35
|
],
|
|
35
36
|
"keywords": [
|
|
36
37
|
"astro",
|
package/src/middleware.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { defineMiddleware } from 'astro:middleware';
|
|
2
|
+
import type { TranslateFunction } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a server-side translation function for the given translations object
|
|
6
|
+
*/
|
|
7
|
+
function createT(translations: Record<string, unknown>): TranslateFunction {
|
|
8
|
+
return (key: string, params?: Record<string, string | number>): string => {
|
|
9
|
+
const keys = key.split('.');
|
|
10
|
+
let value: unknown = translations;
|
|
11
|
+
for (const k of keys) {
|
|
12
|
+
if (value == null || typeof value !== 'object') return key;
|
|
13
|
+
value = (value as Record<string, unknown>)[k];
|
|
14
|
+
}
|
|
15
|
+
if (typeof value !== 'string') return key;
|
|
16
|
+
if (!params) return value;
|
|
17
|
+
return value.replace(/\{(\w+)\}/g, (_, p) => String(params[p] ?? `{${p}}`));
|
|
18
|
+
};
|
|
19
|
+
}
|
|
2
20
|
|
|
3
21
|
/**
|
|
4
22
|
* Locale detection middleware for ez-i18n
|
|
@@ -48,6 +66,9 @@ export const onRequest = defineMiddleware(async ({ cookies, request, locals }, n
|
|
|
48
66
|
locals.translations = {};
|
|
49
67
|
}
|
|
50
68
|
|
|
69
|
+
// Create server-side translation function
|
|
70
|
+
locals.t = createT(locals.translations);
|
|
71
|
+
|
|
51
72
|
// Update cookie if changed via query param
|
|
52
73
|
if (langParam && langParam !== cookieValue && locales.includes(langParam)) {
|
|
53
74
|
cookies.set(cookieName, locale, {
|