@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 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-Cd9e7Lkc.js';
3
- export { T as TranslateFunction } from './types-Cd9e7Lkc.js';
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
@@ -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: "/",
@@ -91,6 +91,8 @@ declare global {
91
91
  locale: string;
92
92
  /** Loaded translations for the current locale */
93
93
  translations: Record<string, unknown>;
94
+ /** Server-side translation function */
95
+ t: TranslateFunction;
94
96
  }
95
97
  }
96
98
  }
@@ -1,4 +1,4 @@
1
- import { L as LocaleTranslationPath, a as TranslationsConfig, b as TranslationCache } from '../types-Cd9e7Lkc.js';
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.1",
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, {
package/src/types.ts CHANGED
@@ -118,6 +118,8 @@ declare global {
118
118
  locale: string;
119
119
  /** Loaded translations for the current locale */
120
120
  translations: Record<string, unknown>;
121
+ /** Server-side translation function */
122
+ t: TranslateFunction;
121
123
  }
122
124
  }
123
125
  }