@zachhandley/ez-i18n 0.3.18 → 0.4.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/README.md CHANGED
@@ -5,7 +5,7 @@ Cookie-based i18n for Astro. Ships the Astro integration plus the shared runtime
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- pnpm add @zachhandley/ez-i18n nanostores @nanostores/persistent
8
+ pnpm add @zachhandley/ez-i18n nanostores
9
9
  ```
10
10
 
11
11
  ## Astro Setup
@@ -13,9 +13,11 @@ declare function getNestedValue(obj: Record<string, unknown>, path: string): unk
13
13
  */
14
14
  declare function interpolate(str: string, params?: Record<string, string | number>): string;
15
15
  /**
16
- * Client-side locale preference (persisted to localStorage)
16
+ * Client-side locale preference
17
+ * Note: Persistence is handled via cookies (set by middleware) and the hydration script
18
+ * that syncs localStorage. We use a regular atom to avoid proxy issues in Cloudflare Workers.
17
19
  */
18
- declare const localePreference: nanostores.WritableAtom<string>;
20
+ declare const localePreference: nanostores.PreinitializedWritableAtom<string> & object;
19
21
  /**
20
22
  * Effective locale - uses server locale if set, otherwise client preference
21
23
  */
@@ -1,6 +1,5 @@
1
1
  // src/runtime/store.ts
2
2
  import { atom, computed } from "nanostores";
3
- import { persistentAtom } from "@nanostores/persistent";
4
3
  function getNestedValue(obj, path) {
5
4
  const keys = path.split(".");
6
5
  let value = obj;
@@ -19,10 +18,7 @@ function interpolate(str, params) {
19
18
  });
20
19
  }
21
20
  var serverLocale = atom(null);
22
- var localePreference = persistentAtom("ez-locale", "en", {
23
- encode: (value) => value,
24
- decode: (value) => value
25
- });
21
+ var localePreference = atom("en");
26
22
  var effectiveLocale = computed(
27
23
  [serverLocale, localePreference],
28
24
  (server, client) => server ?? client
@@ -81,6 +77,47 @@ function getLocale() {
81
77
  function getTranslations() {
82
78
  return translations.get();
83
79
  }
80
+ var embeddedI18nPattern = /\[i18n:([^\]|]+)(?:\|([^\]]+))?]/g;
81
+ function parseEmbeddedParams(paramString) {
82
+ if (!paramString) return {};
83
+ const params = new URLSearchParams(paramString);
84
+ const result = {};
85
+ for (const [key, value] of params) {
86
+ result[key] = value;
87
+ }
88
+ return result;
89
+ }
90
+ function mergeEmbeddedParams(embeddedParams, overrideParams) {
91
+ const embeddedKeys = Object.keys(embeddedParams);
92
+ if (embeddedKeys.length === 0) return void 0;
93
+ const merged = { ...embeddedParams };
94
+ if (overrideParams) {
95
+ for (const key of embeddedKeys) {
96
+ if (key in overrideParams) {
97
+ merged[key] = overrideParams[key];
98
+ }
99
+ }
100
+ }
101
+ return merged;
102
+ }
103
+ function formatEmbeddedString(str, overrideParams, translateKey) {
104
+ if (!str.includes("[i18n:")) return str;
105
+ return str.replace(embeddedI18nPattern, (_match, embeddedKey, paramString) => {
106
+ const embeddedParams = parseEmbeddedParams(paramString);
107
+ const mergedParams = mergeEmbeddedParams(embeddedParams, overrideParams);
108
+ return translateKey(embeddedKey, mergedParams);
109
+ });
110
+ }
111
+ function translateKeyWithTranslations(trans, key, params) {
112
+ const value = getNestedValue(trans, key);
113
+ if (typeof value !== "string") {
114
+ if (typeof import.meta !== "undefined" && import.meta.env?.DEV) {
115
+ console.warn("[ez-i18n] Missing translation:", key);
116
+ }
117
+ return key;
118
+ }
119
+ return interpolate(value, params);
120
+ }
84
121
  function getTranslationsWithSSRFallback() {
85
122
  const trans = translations.get();
86
123
  if (Object.keys(trans).length === 0) {
@@ -93,26 +130,20 @@ function getTranslationsWithSSRFallback() {
93
130
  }
94
131
  function t(key, params) {
95
132
  const trans = getTranslationsWithSSRFallback();
96
- const value = getNestedValue(trans, key);
97
- if (typeof value !== "string") {
98
- if (typeof import.meta !== "undefined" && import.meta.env?.DEV) {
99
- console.warn("[ez-i18n] Missing translation:", key);
100
- }
101
- return key;
133
+ const translate = (lookupKey, lookupParams) => translateKeyWithTranslations(trans, lookupKey, lookupParams);
134
+ if (key.includes("[i18n:")) {
135
+ return formatEmbeddedString(key, params, translate);
102
136
  }
103
- return interpolate(value, params);
137
+ return translate(key, params);
104
138
  }
105
139
  function tc(key, params) {
106
140
  return computed(translations, (trans) => {
107
141
  const effectiveTrans = Object.keys(trans).length === 0 ? globalThis.__EZ_I18N__?.translations ?? trans : trans;
108
- const value = getNestedValue(effectiveTrans, key);
109
- if (typeof value !== "string") {
110
- if (typeof import.meta !== "undefined" && import.meta.env?.DEV) {
111
- console.warn("[ez-i18n] Missing translation:", key);
112
- }
113
- return key;
142
+ const translate = (lookupKey, lookupParams) => translateKeyWithTranslations(effectiveTrans, lookupKey, lookupParams);
143
+ if (key.includes("[i18n:")) {
144
+ return formatEmbeddedString(key, params, translate);
114
145
  }
115
- return interpolate(value, params);
146
+ return translate(key, params);
116
147
  });
117
148
  }
118
149
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zachhandley/ez-i18n",
3
- "version": "0.3.18",
3
+ "version": "v0.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -56,12 +56,10 @@
56
56
  "tinyglobby": "^0.2.15"
57
57
  },
58
58
  "peerDependencies": {
59
- "@nanostores/persistent": "^0.10.0",
60
59
  "astro": "^4.0.0 || ^5.0.0",
61
60
  "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0"
62
61
  },
63
62
  "devDependencies": {
64
- "@nanostores/persistent": "^0.10.2",
65
63
  "@types/node": "^22.0.0",
66
64
  "astro": "^5.1.1",
67
65
  "nanostores": "^0.11.3",