@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/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zachhandley/ez-i18n",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"description": "Cookie-based i18n for Astro
|
|
7
|
+
"description": "Cookie-based i18n for Astro. Core package with Astro integration and runtime.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -13,10 +13,6 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"import": "./dist/index.js"
|
|
15
15
|
},
|
|
16
|
-
"./vue": {
|
|
17
|
-
"types": "./dist/runtime/vue-plugin.d.ts",
|
|
18
|
-
"import": "./dist/runtime/vue-plugin.js"
|
|
19
|
-
},
|
|
20
16
|
"./middleware": {
|
|
21
17
|
"import": "./dist/middleware.js"
|
|
22
18
|
},
|
|
@@ -27,10 +23,6 @@
|
|
|
27
23
|
"./astro": {
|
|
28
24
|
"import": "./src/components/EzI18nHead.astro"
|
|
29
25
|
},
|
|
30
|
-
"./react": {
|
|
31
|
-
"types": "./dist/runtime/react-plugin.d.ts",
|
|
32
|
-
"import": "./dist/runtime/react-plugin.js"
|
|
33
|
-
},
|
|
34
26
|
"./utils": {
|
|
35
27
|
"types": "./dist/utils/index.d.ts",
|
|
36
28
|
"import": "./dist/utils/index.js"
|
|
@@ -40,21 +32,11 @@
|
|
|
40
32
|
"dist",
|
|
41
33
|
"src"
|
|
42
34
|
],
|
|
43
|
-
"scripts": {
|
|
44
|
-
"build": "tsup",
|
|
45
|
-
"dev": "tsup --watch",
|
|
46
|
-
"typecheck": "tsc --noEmit",
|
|
47
|
-
"prepublishOnly": "pnpm build",
|
|
48
|
-
"release": "pnpm build && npm publish",
|
|
49
|
-
"release:dry": "pnpm build && npm publish --dry-run"
|
|
50
|
-
},
|
|
51
35
|
"keywords": [
|
|
52
36
|
"astro",
|
|
53
37
|
"astro-integration",
|
|
54
38
|
"i18n",
|
|
55
39
|
"internationalization",
|
|
56
|
-
"vue",
|
|
57
|
-
"react",
|
|
58
40
|
"cookie-based",
|
|
59
41
|
"no-url-prefix"
|
|
60
42
|
],
|
|
@@ -63,7 +45,8 @@
|
|
|
63
45
|
"homepage": "https://github.com/zachhandley/ez-i18n#readme",
|
|
64
46
|
"repository": {
|
|
65
47
|
"type": "git",
|
|
66
|
-
"url": "git+https://github.com/zachhandley/ez-i18n.git"
|
|
48
|
+
"url": "git+https://github.com/zachhandley/ez-i18n.git",
|
|
49
|
+
"directory": "packages/core"
|
|
67
50
|
},
|
|
68
51
|
"bugs": {
|
|
69
52
|
"url": "https://github.com/zachhandley/ez-i18n/issues"
|
|
@@ -73,40 +56,22 @@
|
|
|
73
56
|
},
|
|
74
57
|
"peerDependencies": {
|
|
75
58
|
"@nanostores/persistent": "^0.10.0",
|
|
76
|
-
"@nanostores/react": "^0.7.0 || ^0.8.0 || ^1.0.0",
|
|
77
|
-
"@nanostores/vue": "^0.10.0",
|
|
78
59
|
"astro": "^4.0.0 || ^5.0.0",
|
|
79
|
-
"nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0"
|
|
80
|
-
"react": "^18.0.0 || ^19.0.0",
|
|
81
|
-
"vue": "^3.4.0"
|
|
82
|
-
},
|
|
83
|
-
"peerDependenciesMeta": {
|
|
84
|
-
"vue": {
|
|
85
|
-
"optional": true
|
|
86
|
-
},
|
|
87
|
-
"react": {
|
|
88
|
-
"optional": true
|
|
89
|
-
},
|
|
90
|
-
"@nanostores/vue": {
|
|
91
|
-
"optional": true
|
|
92
|
-
},
|
|
93
|
-
"@nanostores/react": {
|
|
94
|
-
"optional": true
|
|
95
|
-
}
|
|
60
|
+
"nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0"
|
|
96
61
|
},
|
|
97
62
|
"devDependencies": {
|
|
98
63
|
"@nanostores/persistent": "^0.10.2",
|
|
99
|
-
"@nanostores/react": "^1.0.0",
|
|
100
|
-
"@nanostores/vue": "^0.10.0",
|
|
101
64
|
"@types/node": "^22.0.0",
|
|
102
|
-
"@types/react": "^19.2.7",
|
|
103
65
|
"astro": "^5.1.1",
|
|
104
66
|
"nanostores": "^0.11.3",
|
|
105
|
-
"react": "^19.2.1",
|
|
106
67
|
"tsup": "^8.3.5",
|
|
107
68
|
"typescript": "^5.7.2",
|
|
108
|
-
"vite": "^6.0.3"
|
|
109
|
-
"vue": "^3.5.13"
|
|
69
|
+
"vite": "^6.0.3"
|
|
110
70
|
},
|
|
111
|
-
"
|
|
112
|
-
|
|
71
|
+
"scripts": {
|
|
72
|
+
"build": "tsup",
|
|
73
|
+
"dev": "tsup --watch",
|
|
74
|
+
"typecheck": "tsc --noEmit",
|
|
75
|
+
"clean": "rm -rf dist"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* EzI18nHead - Astro component for i18n hydration setup
|
|
4
|
-
*
|
|
5
|
-
* Place this in your layout's <head> to properly initialize
|
|
6
|
-
* the i18n runtime with server-side loaded translations.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ---
|
|
10
|
-
* import EzI18nHead from '@zachhandley/ez-i18n/astro';
|
|
11
|
-
* const { locale, translations } = Astro.locals;
|
|
12
|
-
* ---
|
|
13
|
-
* <html lang={locale}>
|
|
14
|
-
* <head>
|
|
15
|
-
* <EzI18nHead locale={locale} translations={translations} />
|
|
16
|
-
* </head>
|
|
17
|
-
* <body><slot /></body>
|
|
18
|
-
* </html>
|
|
19
|
-
*/
|
|
20
|
-
interface Props {
|
|
21
|
-
/** Current locale code from Astro.locals.locale */
|
|
22
|
-
locale: string;
|
|
23
|
-
/** Translations object from Astro.locals.translations */
|
|
24
|
-
translations: Record<string, unknown>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const { locale, translations } = Astro.props;
|
|
28
|
-
|
|
29
|
-
// Serialize translations for inline script
|
|
30
|
-
const serializedTranslations = JSON.stringify(translations);
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
<script
|
|
34
|
-
is:inline
|
|
35
|
-
data-ez-i18n-locale={locale}
|
|
36
|
-
data-ez-i18n-translations={serializedTranslations}
|
|
37
|
-
>
|
|
38
|
-
// Initialize ez-i18n runtime with server-provided values
|
|
39
|
-
(function() {
|
|
40
|
-
const script = document.currentScript;
|
|
41
|
-
if (!script) return;
|
|
42
|
-
|
|
43
|
-
const locale = script.dataset.ezI18nLocale;
|
|
44
|
-
const translations = JSON.parse(script.dataset.ezI18nTranslations || '{}');
|
|
45
|
-
|
|
46
|
-
// Store for runtime initialization
|
|
47
|
-
window.__EZ_I18N_INIT__ = { locale, translations };
|
|
48
|
-
})();
|
|
49
|
-
</script>
|
|
50
|
-
|
|
51
|
-
<script>
|
|
52
|
-
// Import and initialize the runtime stores
|
|
53
|
-
import { initLocale, setTranslations } from '@zachhandley/ez-i18n/runtime';
|
|
54
|
-
|
|
55
|
-
// Get initialization data from inline script
|
|
56
|
-
const initData = (window as any).__EZ_I18N_INIT__;
|
|
57
|
-
if (initData) {
|
|
58
|
-
initLocale(initData.locale, initData.translations);
|
|
59
|
-
setTranslations(initData.translations);
|
|
60
|
-
delete (window as any).__EZ_I18N_INIT__;
|
|
61
|
-
}
|
|
62
|
-
</script>
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* EzI18nHead - Astro component for i18n hydration setup
|
|
4
|
+
*
|
|
5
|
+
* Place this in your layout's <head> to properly initialize
|
|
6
|
+
* the i18n runtime with server-side loaded translations.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ---
|
|
10
|
+
* import EzI18nHead from '@zachhandley/ez-i18n/astro';
|
|
11
|
+
* const { locale, translations } = Astro.locals;
|
|
12
|
+
* ---
|
|
13
|
+
* <html lang={locale}>
|
|
14
|
+
* <head>
|
|
15
|
+
* <EzI18nHead locale={locale} translations={translations} />
|
|
16
|
+
* </head>
|
|
17
|
+
* <body><slot /></body>
|
|
18
|
+
* </html>
|
|
19
|
+
*/
|
|
20
|
+
interface Props {
|
|
21
|
+
/** Current locale code from Astro.locals.locale */
|
|
22
|
+
locale: string;
|
|
23
|
+
/** Translations object from Astro.locals.translations */
|
|
24
|
+
translations: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { locale, translations } = Astro.props;
|
|
28
|
+
|
|
29
|
+
// Serialize translations for inline script
|
|
30
|
+
const serializedTranslations = JSON.stringify(translations);
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<script
|
|
34
|
+
is:inline
|
|
35
|
+
data-ez-i18n-locale={locale}
|
|
36
|
+
data-ez-i18n-translations={serializedTranslations}
|
|
37
|
+
>
|
|
38
|
+
// Initialize ez-i18n runtime with server-provided values
|
|
39
|
+
(function() {
|
|
40
|
+
const script = document.currentScript;
|
|
41
|
+
if (!script) return;
|
|
42
|
+
|
|
43
|
+
const locale = script.dataset.ezI18nLocale;
|
|
44
|
+
const translations = JSON.parse(script.dataset.ezI18nTranslations || '{}');
|
|
45
|
+
|
|
46
|
+
// Store for runtime initialization
|
|
47
|
+
window.__EZ_I18N_INIT__ = { locale, translations };
|
|
48
|
+
})();
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<script>
|
|
52
|
+
// Import and initialize the runtime stores
|
|
53
|
+
import { initLocale, setTranslations } from '@zachhandley/ez-i18n/runtime';
|
|
54
|
+
|
|
55
|
+
// Get initialization data from inline script
|
|
56
|
+
const initData = (window as any).__EZ_I18N_INIT__;
|
|
57
|
+
if (initData) {
|
|
58
|
+
initLocale(initData.locale, initData.translations);
|
|
59
|
+
setTranslations(initData.translations);
|
|
60
|
+
delete (window as any).__EZ_I18N_INIT__;
|
|
61
|
+
}
|
|
62
|
+
</script>
|
package/src/index.ts
CHANGED
|
@@ -1,122 +1,122 @@
|
|
|
1
|
-
import type { AstroIntegration, HookParameters } from 'astro';
|
|
2
|
-
import type { EzI18nConfig } from './types';
|
|
3
|
-
import { vitePlugin, resolveConfig } from './vite-plugin';
|
|
4
|
-
|
|
5
|
-
export type { EzI18nConfig, TranslateFunction } from './types';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* ez-i18n Astro integration
|
|
9
|
-
*
|
|
10
|
-
* Provides cookie-based i18n without URL prefixes.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* // astro.config.ts
|
|
14
|
-
* import ezI18n from '@zachhandley/ez-i18n';
|
|
15
|
-
*
|
|
16
|
-
* export default defineConfig({
|
|
17
|
-
* integrations: [
|
|
18
|
-
* ezI18n({
|
|
19
|
-
* locales: ['en', 'es', 'fr'],
|
|
20
|
-
* defaultLocale: 'en',
|
|
21
|
-
* translations: {
|
|
22
|
-
* en: './src/i18n/en.json',
|
|
23
|
-
* es: './src/i18n/es.json',
|
|
24
|
-
* },
|
|
25
|
-
* }),
|
|
26
|
-
* ],
|
|
27
|
-
* });
|
|
28
|
-
*/
|
|
29
|
-
export default function ezI18n(config: EzI18nConfig): AstroIntegration {
|
|
30
|
-
const resolved = resolveConfig(config);
|
|
31
|
-
|
|
32
|
-
return {
|
|
33
|
-
name: 'ez-i18n',
|
|
34
|
-
hooks: {
|
|
35
|
-
'astro:config:setup': ({
|
|
36
|
-
updateConfig,
|
|
37
|
-
addMiddleware,
|
|
38
|
-
injectScript,
|
|
39
|
-
}: HookParameters<'astro:config:setup'>) => {
|
|
40
|
-
// Add Vite plugin for virtual modules
|
|
41
|
-
updateConfig({
|
|
42
|
-
vite: {
|
|
43
|
-
plugins: [vitePlugin(config)],
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Add locale detection middleware
|
|
48
|
-
addMiddleware({
|
|
49
|
-
entrypoint: '@zachhandley/ez-i18n/middleware',
|
|
50
|
-
order: 'pre',
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Inject hydration script to sync localStorage with cookie
|
|
54
|
-
// This prevents hydration mismatch when server and client disagree
|
|
55
|
-
const hydrationScript = `
|
|
56
|
-
(function() {
|
|
57
|
-
try {
|
|
58
|
-
var cookieName = ${JSON.stringify(resolved.cookieName)};
|
|
59
|
-
var stored = localStorage.getItem(cookieName);
|
|
60
|
-
var cookieMatch = document.cookie.match(new RegExp(cookieName + '=([^;]+)'));
|
|
61
|
-
var cookie = cookieMatch ? cookieMatch[1] : null;
|
|
62
|
-
if (cookie && stored !== cookie) {
|
|
63
|
-
localStorage.setItem(cookieName, cookie);
|
|
64
|
-
}
|
|
65
|
-
} catch (e) {}
|
|
66
|
-
})();
|
|
67
|
-
`;
|
|
68
|
-
injectScript('head-inline', hydrationScript);
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
'astro:config:done': ({
|
|
72
|
-
injectTypes,
|
|
73
|
-
}: HookParameters<'astro:config:done'>) => {
|
|
74
|
-
// Inject type declarations for virtual modules
|
|
75
|
-
injectTypes({
|
|
76
|
-
filename: 'virtual.d.ts',
|
|
77
|
-
content: `\
|
|
78
|
-
declare module 'ez-i18n:config' {
|
|
79
|
-
/** List of all supported locale codes */
|
|
80
|
-
export const locales: readonly string[];
|
|
81
|
-
/** Default locale when no preference is detected */
|
|
82
|
-
export const defaultLocale: string;
|
|
83
|
-
/** Cookie name used to store locale preference */
|
|
84
|
-
export const cookieName: string;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
declare module 'ez-i18n:runtime' {
|
|
88
|
-
import type { ReadableAtom } from 'nanostores';
|
|
89
|
-
/** Reactive store containing the current locale */
|
|
90
|
-
export const locale: ReadableAtom<string>;
|
|
91
|
-
/**
|
|
92
|
-
* Translate a key to the current locale
|
|
93
|
-
* @param key - Dot-notation key (e.g., 'common.welcome')
|
|
94
|
-
* @param params - Optional interpolation params for {placeholder} syntax
|
|
95
|
-
*/
|
|
96
|
-
export function t(key: string, params?: Record<string, string | number>): string;
|
|
97
|
-
/**
|
|
98
|
-
* Set the current locale and persist to cookie/localStorage
|
|
99
|
-
* @param locale - Locale code to switch to
|
|
100
|
-
* @param cookieName - Optional custom cookie name
|
|
101
|
-
*/
|
|
102
|
-
export function setLocale(locale: string, cookieName?: string): Promise<void>;
|
|
103
|
-
/**
|
|
104
|
-
* Initialize the locale store with translations
|
|
105
|
-
* @param locale - Initial locale code
|
|
106
|
-
* @param translations - Optional initial translations object
|
|
107
|
-
*/
|
|
108
|
-
export function initLocale(locale: string, translations?: Record<string, unknown>): void;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
declare module 'ez-i18n:translations' {
|
|
112
|
-
/** Load translations for a specific locale */
|
|
113
|
-
export function loadTranslations(locale: string): Promise<Record<string, unknown>>;
|
|
114
|
-
/** Get the translation loader map from config */
|
|
115
|
-
export const translationLoaders: Record<string, () => Promise<{ default: Record<string, unknown> }>>;
|
|
116
|
-
}
|
|
117
|
-
`,
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
1
|
+
import type { AstroIntegration, HookParameters } from 'astro';
|
|
2
|
+
import type { EzI18nConfig } from './types';
|
|
3
|
+
import { vitePlugin, resolveConfig } from './vite-plugin';
|
|
4
|
+
|
|
5
|
+
export type { EzI18nConfig, TranslateFunction } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ez-i18n Astro integration
|
|
9
|
+
*
|
|
10
|
+
* Provides cookie-based i18n without URL prefixes.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // astro.config.ts
|
|
14
|
+
* import ezI18n from '@zachhandley/ez-i18n';
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* integrations: [
|
|
18
|
+
* ezI18n({
|
|
19
|
+
* locales: ['en', 'es', 'fr'],
|
|
20
|
+
* defaultLocale: 'en',
|
|
21
|
+
* translations: {
|
|
22
|
+
* en: './src/i18n/en.json',
|
|
23
|
+
* es: './src/i18n/es.json',
|
|
24
|
+
* },
|
|
25
|
+
* }),
|
|
26
|
+
* ],
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
export default function ezI18n(config: EzI18nConfig): AstroIntegration {
|
|
30
|
+
const resolved = resolveConfig(config);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
name: 'ez-i18n',
|
|
34
|
+
hooks: {
|
|
35
|
+
'astro:config:setup': ({
|
|
36
|
+
updateConfig,
|
|
37
|
+
addMiddleware,
|
|
38
|
+
injectScript,
|
|
39
|
+
}: HookParameters<'astro:config:setup'>) => {
|
|
40
|
+
// Add Vite plugin for virtual modules
|
|
41
|
+
updateConfig({
|
|
42
|
+
vite: {
|
|
43
|
+
plugins: [vitePlugin(config)],
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Add locale detection middleware
|
|
48
|
+
addMiddleware({
|
|
49
|
+
entrypoint: '@zachhandley/ez-i18n/middleware',
|
|
50
|
+
order: 'pre',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Inject hydration script to sync localStorage with cookie
|
|
54
|
+
// This prevents hydration mismatch when server and client disagree
|
|
55
|
+
const hydrationScript = `
|
|
56
|
+
(function() {
|
|
57
|
+
try {
|
|
58
|
+
var cookieName = ${JSON.stringify(resolved.cookieName)};
|
|
59
|
+
var stored = localStorage.getItem(cookieName);
|
|
60
|
+
var cookieMatch = document.cookie.match(new RegExp(cookieName + '=([^;]+)'));
|
|
61
|
+
var cookie = cookieMatch ? cookieMatch[1] : null;
|
|
62
|
+
if (cookie && stored !== cookie) {
|
|
63
|
+
localStorage.setItem(cookieName, cookie);
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {}
|
|
66
|
+
})();
|
|
67
|
+
`;
|
|
68
|
+
injectScript('head-inline', hydrationScript);
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
'astro:config:done': ({
|
|
72
|
+
injectTypes,
|
|
73
|
+
}: HookParameters<'astro:config:done'>) => {
|
|
74
|
+
// Inject type declarations for virtual modules
|
|
75
|
+
injectTypes({
|
|
76
|
+
filename: 'virtual.d.ts',
|
|
77
|
+
content: `\
|
|
78
|
+
declare module 'ez-i18n:config' {
|
|
79
|
+
/** List of all supported locale codes */
|
|
80
|
+
export const locales: readonly string[];
|
|
81
|
+
/** Default locale when no preference is detected */
|
|
82
|
+
export const defaultLocale: string;
|
|
83
|
+
/** Cookie name used to store locale preference */
|
|
84
|
+
export const cookieName: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
declare module 'ez-i18n:runtime' {
|
|
88
|
+
import type { ReadableAtom } from 'nanostores';
|
|
89
|
+
/** Reactive store containing the current locale */
|
|
90
|
+
export const locale: ReadableAtom<string>;
|
|
91
|
+
/**
|
|
92
|
+
* Translate a key to the current locale
|
|
93
|
+
* @param key - Dot-notation key (e.g., 'common.welcome')
|
|
94
|
+
* @param params - Optional interpolation params for {placeholder} syntax
|
|
95
|
+
*/
|
|
96
|
+
export function t(key: string, params?: Record<string, string | number>): string;
|
|
97
|
+
/**
|
|
98
|
+
* Set the current locale and persist to cookie/localStorage
|
|
99
|
+
* @param locale - Locale code to switch to
|
|
100
|
+
* @param cookieName - Optional custom cookie name
|
|
101
|
+
*/
|
|
102
|
+
export function setLocale(locale: string, cookieName?: string): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Initialize the locale store with translations
|
|
105
|
+
* @param locale - Initial locale code
|
|
106
|
+
* @param translations - Optional initial translations object
|
|
107
|
+
*/
|
|
108
|
+
export function initLocale(locale: string, translations?: Record<string, unknown>): void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare module 'ez-i18n:translations' {
|
|
112
|
+
/** Load translations for a specific locale */
|
|
113
|
+
export function loadTranslations(locale: string): Promise<Record<string, unknown>>;
|
|
114
|
+
/** Get the translation loader map from config */
|
|
115
|
+
export const translationLoaders: Record<string, () => Promise<{ default: Record<string, unknown> }>>;
|
|
116
|
+
}
|
|
117
|
+
`,
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
package/src/middleware.ts
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { defineMiddleware } from 'astro:middleware';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Locale detection middleware for ez-i18n
|
|
5
|
-
*
|
|
6
|
-
* Detection priority:
|
|
7
|
-
* 1. ?lang query parameter (allows explicit switching)
|
|
8
|
-
* 2. Cookie value
|
|
9
|
-
* 3. Accept-Language header
|
|
10
|
-
* 4. Default locale
|
|
11
|
-
*/
|
|
12
|
-
export const onRequest = defineMiddleware(async ({ cookies, request, locals }, next) => {
|
|
13
|
-
// Import config from virtual module (provided by vite-plugin)
|
|
14
|
-
const { locales, defaultLocale, cookieName } = await import('ez-i18n:config');
|
|
15
|
-
|
|
16
|
-
const url = new URL(request.url);
|
|
17
|
-
|
|
18
|
-
// Priority 1: Query parameter
|
|
19
|
-
const langParam = url.searchParams.get('lang');
|
|
20
|
-
|
|
21
|
-
// Priority 2: Cookie
|
|
22
|
-
const cookieValue = cookies.get(cookieName)?.value;
|
|
23
|
-
|
|
24
|
-
// Priority 3: Accept-Language header
|
|
25
|
-
const acceptLang = request.headers.get('accept-language');
|
|
26
|
-
const browserLang = acceptLang?.split(',')[0]?.split('-')[0];
|
|
27
|
-
|
|
28
|
-
// Determine locale with priority
|
|
29
|
-
let locale = defaultLocale;
|
|
30
|
-
|
|
31
|
-
if (langParam && locales.includes(langParam)) {
|
|
32
|
-
locale = langParam;
|
|
33
|
-
} else if (cookieValue && locales.includes(cookieValue)) {
|
|
34
|
-
locale = cookieValue;
|
|
35
|
-
} else if (browserLang && locales.includes(browserLang)) {
|
|
36
|
-
locale = browserLang;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Set locale on locals for use in pages
|
|
40
|
-
locals.locale = locale;
|
|
41
|
-
|
|
42
|
-
// Load translations for the current locale
|
|
43
|
-
try {
|
|
44
|
-
const { loadTranslations } = await import('ez-i18n:translations');
|
|
45
|
-
locals.translations = await loadTranslations(locale);
|
|
46
|
-
} catch {
|
|
47
|
-
// Fallback to empty translations if loader not configured
|
|
48
|
-
locals.translations = {};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Update cookie if changed via query param
|
|
52
|
-
if (langParam && langParam !== cookieValue && locales.includes(langParam)) {
|
|
53
|
-
cookies.set(cookieName, locale, {
|
|
54
|
-
path: '/',
|
|
55
|
-
maxAge: 60 * 60 * 24 * 365, // 1 year
|
|
56
|
-
sameSite: 'lax',
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return next();
|
|
61
|
-
});
|
|
1
|
+
import { defineMiddleware } from 'astro:middleware';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Locale detection middleware for ez-i18n
|
|
5
|
+
*
|
|
6
|
+
* Detection priority:
|
|
7
|
+
* 1. ?lang query parameter (allows explicit switching)
|
|
8
|
+
* 2. Cookie value
|
|
9
|
+
* 3. Accept-Language header
|
|
10
|
+
* 4. Default locale
|
|
11
|
+
*/
|
|
12
|
+
export const onRequest = defineMiddleware(async ({ cookies, request, locals }, next) => {
|
|
13
|
+
// Import config from virtual module (provided by vite-plugin)
|
|
14
|
+
const { locales, defaultLocale, cookieName } = await import('ez-i18n:config');
|
|
15
|
+
|
|
16
|
+
const url = new URL(request.url);
|
|
17
|
+
|
|
18
|
+
// Priority 1: Query parameter
|
|
19
|
+
const langParam = url.searchParams.get('lang');
|
|
20
|
+
|
|
21
|
+
// Priority 2: Cookie
|
|
22
|
+
const cookieValue = cookies.get(cookieName)?.value;
|
|
23
|
+
|
|
24
|
+
// Priority 3: Accept-Language header
|
|
25
|
+
const acceptLang = request.headers.get('accept-language');
|
|
26
|
+
const browserLang = acceptLang?.split(',')[0]?.split('-')[0];
|
|
27
|
+
|
|
28
|
+
// Determine locale with priority
|
|
29
|
+
let locale = defaultLocale;
|
|
30
|
+
|
|
31
|
+
if (langParam && locales.includes(langParam)) {
|
|
32
|
+
locale = langParam;
|
|
33
|
+
} else if (cookieValue && locales.includes(cookieValue)) {
|
|
34
|
+
locale = cookieValue;
|
|
35
|
+
} else if (browserLang && locales.includes(browserLang)) {
|
|
36
|
+
locale = browserLang;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Set locale on locals for use in pages
|
|
40
|
+
locals.locale = locale;
|
|
41
|
+
|
|
42
|
+
// Load translations for the current locale
|
|
43
|
+
try {
|
|
44
|
+
const { loadTranslations } = await import('ez-i18n:translations');
|
|
45
|
+
locals.translations = await loadTranslations(locale);
|
|
46
|
+
} catch {
|
|
47
|
+
// Fallback to empty translations if loader not configured
|
|
48
|
+
locals.translations = {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Update cookie if changed via query param
|
|
52
|
+
if (langParam && langParam !== cookieValue && locales.includes(langParam)) {
|
|
53
|
+
cookies.set(cookieName, locale, {
|
|
54
|
+
path: '/',
|
|
55
|
+
maxAge: 60 * 60 * 24 * 365, // 1 year
|
|
56
|
+
sameSite: 'lax',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return next();
|
|
61
|
+
});
|
package/src/runtime/index.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime exports for ez-i18n
|
|
3
|
-
*
|
|
4
|
-
* This module is imported by the ez-i18n:runtime virtual module
|
|
5
|
-
* and can also be used directly in Vue components
|
|
6
|
-
*/
|
|
7
|
-
export {
|
|
8
|
-
effectiveLocale,
|
|
9
|
-
translations,
|
|
10
|
-
localePreference,
|
|
11
|
-
localeLoading,
|
|
12
|
-
initLocale,
|
|
13
|
-
setLocale,
|
|
14
|
-
setTranslations,
|
|
15
|
-
getLocale,
|
|
16
|
-
getTranslations,
|
|
17
|
-
} from './store';
|
|
18
|
-
|
|
19
|
-
export type { TranslationLoader } from './store';
|
|
1
|
+
/**
|
|
2
|
+
* Runtime exports for ez-i18n
|
|
3
|
+
*
|
|
4
|
+
* This module is imported by the ez-i18n:runtime virtual module
|
|
5
|
+
* and can also be used directly in Vue components
|
|
6
|
+
*/
|
|
7
|
+
export {
|
|
8
|
+
effectiveLocale,
|
|
9
|
+
translations,
|
|
10
|
+
localePreference,
|
|
11
|
+
localeLoading,
|
|
12
|
+
initLocale,
|
|
13
|
+
setLocale,
|
|
14
|
+
setTranslations,
|
|
15
|
+
getLocale,
|
|
16
|
+
getTranslations,
|
|
17
|
+
} from './store';
|
|
18
|
+
|
|
19
|
+
export type { TranslationLoader } from './store';
|