@astroscope/i18n 0.1.0 → 0.2.1

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.
Files changed (188) hide show
  1. package/README.md +23 -1
  2. package/dist/{chunk-FAKBSLNW.js → chunk-7MSGHEST.js} +14 -7
  3. package/dist/{chunk-JJEDOJBR.js → chunk-EO4FUQXR.js} +49 -18
  4. package/dist/{chunk-QFAAGCP5.js → chunk-RHWEV4L2.js} +58 -1
  5. package/dist/client/directives/idle.d.ts +4 -1
  6. package/dist/client/directives/idle.js +13 -6
  7. package/dist/client/directives/load.d.ts +4 -1
  8. package/dist/client/directives/load.js +6 -6
  9. package/dist/client/directives/media.d.ts +4 -1
  10. package/dist/client/directives/media.js +16 -6
  11. package/dist/client/directives/only.d.ts +6 -1
  12. package/dist/client/directives/only.js +6 -6
  13. package/dist/client/directives/utils.d.ts +6 -5
  14. package/dist/client/directives/utils.js +3 -3
  15. package/dist/client/directives/visible.d.ts +4 -1
  16. package/dist/client/directives/visible.js +23 -6
  17. package/dist/client/t.d.ts +2 -2
  18. package/dist/extraction/index.d.ts +3 -3
  19. package/dist/extraction/index.js +1 -1
  20. package/dist/{i18n-BGfrSNM8.d.ts → i18n-mTHTcD8K.d.ts} +9 -2
  21. package/dist/index.d.ts +26 -3
  22. package/dist/index.js +6 -2
  23. package/dist/internal.d.ts +4 -4
  24. package/dist/internal.js +1 -1
  25. package/dist/server/t.d.ts +1 -1
  26. package/dist/server/t.js +1 -1
  27. package/dist/{types-BaaSiz9n.d.ts → types-BeDFfFIL.d.ts} +1 -1
  28. package/dist/{types-Bzp6KxsI.d.ts → types-DNRFj1XA.d.ts} +1 -0
  29. package/dist/{types-DsNK_9sI.d.ts → types-DTUho6gi.d.ts} +7 -1
  30. package/package.json +8 -7
  31. package/src/components/I18nScript.astro +2 -23
  32. package/dist/babel-plugin-FUTQRKLT.js +0 -121
  33. package/dist/chunk-22ZZFFC2.js +0 -159
  34. package/dist/chunk-2PSXD3JJ.js +0 -144
  35. package/dist/chunk-3VI62RLT.js +0 -176
  36. package/dist/chunk-4DAQPWXI.js +0 -36
  37. package/dist/chunk-4OGX6AF5.js +0 -10
  38. package/dist/chunk-4PHYKXO3.js +0 -227
  39. package/dist/chunk-4UBFISFY.js +0 -376
  40. package/dist/chunk-5DERF6DV.js +0 -266
  41. package/dist/chunk-5KGNHFTC.js +0 -344
  42. package/dist/chunk-5PP4D2VK.js +0 -23
  43. package/dist/chunk-6BW3PN23.js +0 -14
  44. package/dist/chunk-6ESQ4JQI.js +0 -263
  45. package/dist/chunk-A7MPXMOP.js +0 -202
  46. package/dist/chunk-AWZ2SQDW.js +0 -149
  47. package/dist/chunk-AYY2YIBQ.js +0 -52
  48. package/dist/chunk-BMNEJFTD.js +0 -56
  49. package/dist/chunk-BSXQGEUK.js +0 -16
  50. package/dist/chunk-BSYTJFGF.js +0 -21
  51. package/dist/chunk-BVPXEGTT.js +0 -12
  52. package/dist/chunk-C4DJYWF6.js +0 -193
  53. package/dist/chunk-C5EJYE2P.js +0 -245
  54. package/dist/chunk-CLNOQHMS.js +0 -254
  55. package/dist/chunk-E5MCIRSU.js +0 -144
  56. package/dist/chunk-E7ZJS7WX.js +0 -162
  57. package/dist/chunk-ESFGCYYH.js +0 -24
  58. package/dist/chunk-FHBVI3TL.js +0 -372
  59. package/dist/chunk-FI5KZ5IO.js +0 -17
  60. package/dist/chunk-H5R6YMOL.js +0 -233
  61. package/dist/chunk-H62K3FGA.js +0 -236
  62. package/dist/chunk-HDG2R6XT.js +0 -211
  63. package/dist/chunk-HGXU5QAK.js +0 -344
  64. package/dist/chunk-HHRMR3TM.js +0 -17
  65. package/dist/chunk-HS4HVBAD.js +0 -127
  66. package/dist/chunk-IPJWKKHJ.js +0 -324
  67. package/dist/chunk-ITTL4V7J.js +0 -24
  68. package/dist/chunk-IWUBCMOK.js +0 -128
  69. package/dist/chunk-JJBBY2GR.js +0 -14
  70. package/dist/chunk-JTUKXNIJ.js +0 -23
  71. package/dist/chunk-K7YULG4A.js +0 -14
  72. package/dist/chunk-KNONI6DP.js +0 -89
  73. package/dist/chunk-LMTEO22Y.js +0 -17
  74. package/dist/chunk-LRLZEX4D.js +0 -147
  75. package/dist/chunk-MFKPLUAN.js +0 -241
  76. package/dist/chunk-MIWXQOO3.js +0 -316
  77. package/dist/chunk-MJRTAWPR.js +0 -261
  78. package/dist/chunk-MZEPQCXX.js +0 -249
  79. package/dist/chunk-NHHTSFA7.js +0 -237
  80. package/dist/chunk-NTTXM7AG.js +0 -23
  81. package/dist/chunk-NZFAVJXR.js +0 -192
  82. package/dist/chunk-OFNFSDW3.js +0 -13
  83. package/dist/chunk-PG3XDNGF.js +0 -227
  84. package/dist/chunk-PVEVTPNK.js +0 -139
  85. package/dist/chunk-QAO4BXS4.js +0 -244
  86. package/dist/chunk-QCTULQQK.js +0 -34
  87. package/dist/chunk-QKND2KCA.js +0 -163
  88. package/dist/chunk-QZEGY6RJ.js +0 -151
  89. package/dist/chunk-RAE2L6FT.js +0 -193
  90. package/dist/chunk-S3ABJBU3.js +0 -255
  91. package/dist/chunk-SVDVCPCV.js +0 -236
  92. package/dist/chunk-T35ZUWTL.js +0 -153
  93. package/dist/chunk-T6WV5DW6.js +0 -156
  94. package/dist/chunk-TGUXVDA3.js +0 -82
  95. package/dist/chunk-TYMO7K3A.js +0 -173
  96. package/dist/chunk-V4RBFLQL.js +0 -23
  97. package/dist/chunk-V4XWJXXZ.js +0 -23
  98. package/dist/chunk-VQCY3OKK.js +0 -258
  99. package/dist/chunk-WLGOJPDJ.js +0 -15
  100. package/dist/chunk-WUEPDJIL.js +0 -259
  101. package/dist/chunk-X3L7WZLY.js +0 -254
  102. package/dist/chunk-XDD62ZM3.js +0 -255
  103. package/dist/chunk-XFXXEDRD.js +0 -36
  104. package/dist/chunk-XKYOR5UK.js +0 -8
  105. package/dist/chunk-Y26535SI.js +0 -239
  106. package/dist/chunk-Y3PQX5TX.js +0 -152
  107. package/dist/chunk-Y4BE2V6F.js +0 -347
  108. package/dist/chunk-Z4IZYDIA.js +0 -24
  109. package/dist/chunk-Z52G4CYY.js +0 -14
  110. package/dist/chunk-ZGLUYWVM.js +0 -128
  111. package/dist/client/import-with-i18n.d.ts +0 -16
  112. package/dist/client/import-with-i18n.js +0 -8
  113. package/dist/client-directives/idle.d.ts +0 -9
  114. package/dist/client-directives/idle.js +0 -15
  115. package/dist/client-directives/load.d.ts +0 -9
  116. package/dist/client-directives/load.js +0 -15
  117. package/dist/client-directives/media.d.ts +0 -9
  118. package/dist/client-directives/media.js +0 -15
  119. package/dist/client-directives/only.d.ts +0 -9
  120. package/dist/client-directives/only.js +0 -15
  121. package/dist/client-directives/utils.d.ts +0 -9
  122. package/dist/client-directives/utils.js +0 -7
  123. package/dist/client-directives/visible.d.ts +0 -9
  124. package/dist/client-directives/visible.js +0 -15
  125. package/dist/i18n-3cZHoHwP.d.ts +0 -86
  126. package/dist/i18n-BHU81LMA.d.ts +0 -55
  127. package/dist/i18n-BagoeH4_.d.ts +0 -62
  128. package/dist/i18n-BeL84090.d.ts +0 -77
  129. package/dist/i18n-BeRWDfHi.d.ts +0 -55
  130. package/dist/i18n-Bfs2PVcf.d.ts +0 -79
  131. package/dist/i18n-BrL7p3aN.d.ts +0 -99
  132. package/dist/i18n-CNQNCFU0.d.ts +0 -55
  133. package/dist/i18n-CX_uU8V2.d.ts +0 -80
  134. package/dist/i18n-Cr1QZCwe.d.ts +0 -64
  135. package/dist/i18n-CzTRxGeX.d.ts +0 -80
  136. package/dist/i18n-DUOFiC7g.d.ts +0 -77
  137. package/dist/i18n-DcF47BHS.d.ts +0 -77
  138. package/dist/i18n-DifWvtaO.d.ts +0 -60
  139. package/dist/i18n-DumSqX1t.d.ts +0 -55
  140. package/dist/i18n-fWTJw12h.d.ts +0 -80
  141. package/dist/import-with-i18n.d.ts +0 -23
  142. package/dist/import-with-i18n.js +0 -7
  143. package/dist/init.d.ts +0 -2
  144. package/dist/init.js +0 -8
  145. package/dist/middleware-entrypoint-C0Drh9E_.d.ts +0 -29
  146. package/dist/middleware-entrypoint-mEl66IUy.d.ts +0 -29
  147. package/dist/middleware-entrypoint.d.ts +0 -5
  148. package/dist/middleware-entrypoint.js +0 -30
  149. package/dist/server/middleware-entrypoint.d.ts +0 -2
  150. package/dist/server/middleware-entrypoint.js +0 -28
  151. package/dist/t-BY_C9lZ1.d.ts +0 -30
  152. package/dist/t-CG3yDiEa.d.ts +0 -30
  153. package/dist/t-CHsBfrPr.d.ts +0 -30
  154. package/dist/t-DQpXaDhP.d.ts +0 -30
  155. package/dist/t.client.d.ts +0 -11
  156. package/dist/t.client.js +0 -35
  157. package/dist/t.server.d.ts +0 -22
  158. package/dist/t.server.js +0 -12
  159. package/dist/types-8uKCwFda.d.ts +0 -24
  160. package/dist/types-BC6K-pID.d.ts +0 -110
  161. package/dist/types-BUV7BZML.d.ts +0 -55
  162. package/dist/types-BWGB322F.d.ts +0 -46
  163. package/dist/types-B_-RxW4A.d.ts +0 -7
  164. package/dist/types-Bgl2V5xo.d.ts +0 -102
  165. package/dist/types-BwhB6IT4.d.ts +0 -111
  166. package/dist/types-C6rOJFY2.d.ts +0 -24
  167. package/dist/types-C7lD-Woj.d.ts +0 -50
  168. package/dist/types-CLWZeQhD.d.ts +0 -54
  169. package/dist/types-COXt613w.d.ts +0 -19
  170. package/dist/types-CUlkbl9-.d.ts +0 -50
  171. package/dist/types-CVv7GGPj.d.ts +0 -46
  172. package/dist/types-CiIDN2sW.d.ts +0 -64
  173. package/dist/types-Cr86qjYl.d.ts +0 -58
  174. package/dist/types-CxnObiEO.d.ts +0 -7
  175. package/dist/types-D44mFUjA.d.ts +0 -97
  176. package/dist/types-D85m9UTC.d.ts +0 -137
  177. package/dist/types-DWhsmSPP.d.ts +0 -102
  178. package/dist/types-DdprXGJ1.d.ts +0 -46
  179. package/dist/types-DhVJ2ZIo.d.ts +0 -43
  180. package/dist/types-DnbOuM1L.d.ts +0 -7
  181. package/dist/types-DvWBr9Xp.d.ts +0 -17
  182. package/dist/types-DwkIalh6.d.ts +0 -24
  183. package/dist/types-OYRgjtE_.d.ts +0 -110
  184. package/dist/types-SV8S05GX.d.ts +0 -7
  185. package/dist/types-TYn9WnxY.d.ts +0 -46
  186. package/dist/types-aXCBbdhq.d.ts +0 -55
  187. package/dist/types-gn4aNKGx.d.ts +0 -54
  188. package/dist/types-nippsMkm.d.ts +0 -54
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @astroscope/i18n
2
2
 
3
+ > **Note:** This package is in active development. APIs may change between versions.
4
+
3
5
  i18n for Astro + React islands — automatic tree-shaking, parallel loading, any translation source.
4
6
 
5
7
  ## Why this library?
@@ -28,7 +30,7 @@ i18n for Astro + React islands — automatic tree-shaking, parallel loading, any
28
30
  ## Installation
29
31
 
30
32
  ```bash
31
- npm install @astroscope/i18n @astroscope/boot
33
+ npm install @astroscope/i18n @astroscope/boot @astroscope/excludes
32
34
  ```
33
35
 
34
36
  ## Usage
@@ -51,6 +53,8 @@ export default defineConfig({
51
53
 
52
54
  ### 2. Configure i18n in your boot file
53
55
 
56
+ **VERY IMPORTANT:** `i18n.configure` must be awaited during boot before handling any requests!
57
+
54
58
  ```ts
55
59
  // src/boot.ts
56
60
  import { i18n, type RawTranslations } from '@astroscope/i18n';
@@ -95,6 +99,24 @@ export const onRequest = sequence(
95
99
  );
96
100
  ```
97
101
 
102
+ By default, `RECOMMENDED_EXCLUDES` (static assets like `/_astro/`) are excluded from locale context setup. To customize:
103
+
104
+ ```ts
105
+ import { sequence } from 'astro:middleware';
106
+ import { createI18nChunkMiddleware, createI18nMiddleware, i18n } from '@astroscope/i18n';
107
+ import { RECOMMENDED_EXCLUDES } from '@astroscope/excludes';
108
+
109
+ export const onRequest = sequence(
110
+ createI18nChunkMiddleware(),
111
+ createI18nMiddleware({
112
+ locale: (ctx) =>
113
+ ctx.cookies.get('locale')?.value ??
114
+ i18n.getConfig().defaultLocale,
115
+ exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
116
+ }),
117
+ );
118
+ ```
119
+
98
120
  ### 4. Add `<I18nScript />` to your layout
99
121
 
100
122
  Inject translations into the page for hydrated components:
@@ -4,21 +4,28 @@ import {
4
4
  } from "./chunk-4A5BKHFN.js";
5
5
 
6
6
  // src/client/directives/utils.ts
7
- async function loadI18nForChunk(el) {
8
- const componentUrl = el.getAttribute("component-url");
9
- if (!componentUrl) return;
10
- const chunkName = componentUrlToChunkName(componentUrl);
11
- const i18n = window.__i18n__;
7
+ function prefetchTranslation(chunkName, i18n) {
12
8
  const hash = i18n.hashes[chunkName];
13
9
  if (hash) {
14
10
  const url = buildI18nChunkUrl(i18n.locale, chunkName, hash);
15
- await import(
11
+ import(
16
12
  /* @vite-ignore */
17
13
  url
18
14
  );
19
15
  }
20
16
  }
17
+ function warmUpI18nForChunk(el) {
18
+ const componentUrl = el.getAttribute("component-url");
19
+ if (!componentUrl) return;
20
+ const chunkName = componentUrlToChunkName(componentUrl);
21
+ const i18n = window.__i18n__;
22
+ prefetchTranslation(chunkName, i18n);
23
+ const deps = i18n.imports[chunkName] ?? [];
24
+ for (const dep of deps) {
25
+ prefetchTranslation(dep, i18n);
26
+ }
27
+ }
21
28
 
22
29
  export {
23
- loadI18nForChunk
30
+ warmUpI18nForChunk
24
31
  };
@@ -11,6 +11,7 @@ function getGlobalState() {
11
11
  g[I18N_MANIFEST_GLOBAL_KEY] = {
12
12
  extractedKeys: [],
13
13
  chunkManifest: {},
14
+ importsManifest: {},
14
15
  projectRoot: ""
15
16
  };
16
17
  }
@@ -24,7 +25,7 @@ function getManifest() {
24
25
  // and avoiding leaking absolute paths from dev or ci environments
25
26
  file: state.projectRoot ? path.relative(state.projectRoot, key.file) : key.file
26
27
  }));
27
- return { keys, chunks: state.chunkManifest };
28
+ return { keys, chunks: state.chunkManifest, imports: state.importsManifest };
28
29
  }
29
30
 
30
31
  // src/extraction/vite-plugin.ts
@@ -135,6 +136,10 @@ export function getManifest() { return _getManifest(); }
135
136
  generateBundle(_, bundle) {
136
137
  const state = getGlobalState();
137
138
  state.chunkManifest = {};
139
+ state.importsManifest = {};
140
+ const chunksWithI18n = /* @__PURE__ */ new Set();
141
+ const chunkNameToFileName = /* @__PURE__ */ new Map();
142
+ const directImports = /* @__PURE__ */ new Map();
138
143
  for (const [fileName, chunk] of Object.entries(bundle)) {
139
144
  if (chunk.type === "chunk" && chunk.moduleIds) {
140
145
  const keys = /* @__PURE__ */ new Set();
@@ -151,24 +156,50 @@ export function getManifest() { return _getManifest(); }
151
156
  state.chunkManifest[chunkName] = Array.from(keys);
152
157
  }
153
158
  if (hasI18nModule) {
154
- const loaderCode = `
155
- /* @astroscope/i18n loader */
156
- if (typeof window !== 'undefined' && window.__i18n__) {
157
- const __i18n__ = window.__i18n__;
158
- const __i18n_hash__ = __i18n__.hashes[${JSON.stringify(chunkName)}];
159
- if (__i18n_hash__) {
160
- await import(\`/_i18n/\${__i18n__.locale}/${chunkName}.\${__i18n_hash__}.js\`);
161
- }
162
- }
163
- /* end @astroscope/i18n loader */
164
- `;
165
- const s = new MagicString(chunk.code);
166
- s.prepend(loaderCode);
167
- chunk.code = s.toString();
168
- if (chunk.map) {
169
- chunk.map = s.generateMap({ hires: true });
170
- }
159
+ chunksWithI18n.add(chunkName);
171
160
  }
161
+ chunkNameToFileName.set(chunkName, fileName);
162
+ const imports = [];
163
+ for (const importedFile of chunk.imports) {
164
+ const baseName = importedFile.replace(/^.*\//, "").replace(/\.js$/, "");
165
+ const importedChunkName = chunkIdToName(baseName);
166
+ imports.push(importedChunkName);
167
+ }
168
+ directImports.set(chunkName, imports);
169
+ }
170
+ }
171
+ const flattenImports = (chunkName, visited) => {
172
+ if (visited.has(chunkName)) return [];
173
+ visited.add(chunkName);
174
+ const result = /* @__PURE__ */ new Set();
175
+ const imports = directImports.get(chunkName) ?? [];
176
+ for (const imported of imports) {
177
+ if (state.chunkManifest[imported]) {
178
+ result.add(imported);
179
+ }
180
+ for (const descendant of flattenImports(imported, visited)) {
181
+ result.add(descendant);
182
+ }
183
+ }
184
+ visited.delete(chunkName);
185
+ return Array.from(result);
186
+ };
187
+ for (const chunkName of directImports.keys()) {
188
+ const flattened = flattenImports(chunkName, /* @__PURE__ */ new Set());
189
+ if (flattened.length > 0) {
190
+ state.importsManifest[chunkName] = flattened;
191
+ }
192
+ }
193
+ for (const [fileName, chunk] of Object.entries(bundle)) {
194
+ if (chunk.type !== "chunk") continue;
195
+ const chunkName = chunkIdToName(fileName.replace(/\.js$/, ""));
196
+ if (!chunksWithI18n.has(chunkName)) continue;
197
+ const loaderCode = `if(typeof window!=='undefined'&&window.__i18n__){const _=window.__i18n__,h=_.hashes[${JSON.stringify(chunkName)}];if(h)await import(\`/_i18n/\${_.locale}/${chunkName}.\${h}.js\`);}`;
198
+ const s = new MagicString(chunk.code);
199
+ s.prepend(loaderCode);
200
+ chunk.code = s.toString();
201
+ if (chunk.map) {
202
+ chunk.map = s.generateMap({ hires: true });
172
203
  }
173
204
  }
174
205
  logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${extractedKeys.length} keys`);
@@ -24,6 +24,21 @@ function computeAllChunkHashes(translations, manifest) {
24
24
  return hashes;
25
25
  }
26
26
 
27
+ // src/server/utils.ts
28
+ var LOWERCASE_A = 97;
29
+ function indexToLetter(index) {
30
+ return String.fromCharCode(LOWERCASE_A + index);
31
+ }
32
+ function generateBB26(index) {
33
+ let name = "";
34
+ let remaining = index;
35
+ do {
36
+ name = indexToLetter(remaining % 26) + name;
37
+ remaining = Math.floor(remaining / 26) - 1;
38
+ } while (remaining >= 0);
39
+ return name;
40
+ }
41
+
27
42
  // src/server/i18n.ts
28
43
  var I18nSingleton = class {
29
44
  // normalized user config with defaults applied
@@ -36,6 +51,8 @@ var I18nSingleton = class {
36
51
  compiledCache = /* @__PURE__ */ new Map();
37
52
  // locale -> chunk name -> content hash (for cache busting)
38
53
  hashCache = /* @__PURE__ */ new Map();
54
+ // locale -> inline script for I18nScript component
55
+ scriptCache = /* @__PURE__ */ new Map();
39
56
  // manifest getter (set by init module, provides live data in dev mode)
40
57
  manifestGetter = null;
41
58
  async configure(config) {
@@ -88,6 +105,7 @@ var I18nSingleton = class {
88
105
  this.mergedCache.delete(locale);
89
106
  this.compiledCache.delete(locale);
90
107
  this.hashCache.delete(locale);
108
+ this.scriptCache.delete(locale);
91
109
  const { chunks } = this.getManifest();
92
110
  if (Object.keys(chunks).length > 0) {
93
111
  this.hashCache.set(locale, computeAllChunkHashes(raw, chunks));
@@ -99,7 +117,7 @@ var I18nSingleton = class {
99
117
  return cached;
100
118
  }
101
119
  const raw = this.rawCache.get(locale) ?? {};
102
- const manifest = this.manifestGetter?.() ?? { keys: [], chunks: {} };
120
+ const manifest = this.manifestGetter?.() ?? { keys: [], chunks: {}, imports: {} };
103
121
  if (manifest.keys.length === 0) {
104
122
  return raw;
105
123
  }
@@ -135,6 +153,44 @@ var I18nSingleton = class {
135
153
  getHashes(locale) {
136
154
  return this.hashCache.get(locale) ?? {};
137
155
  }
156
+ /**
157
+ * Get the inline script for I18nScript component.
158
+ * Cached per locale, invalidated when translations change.
159
+ */
160
+ getClientScript(locale) {
161
+ const cached = this.scriptCache.get(locale);
162
+ if (cached) {
163
+ return cached;
164
+ }
165
+ const script = this.createClientScript(locale);
166
+ this.scriptCache.set(locale, script);
167
+ return script;
168
+ }
169
+ createClientScript(locale) {
170
+ const { chunks, imports } = this.getManifest();
171
+ const hasChunks = Object.keys(chunks).length > 0;
172
+ if (!hasChunks) {
173
+ const raw = this.getTranslations(locale);
174
+ return `window.__i18n__=${JSON.stringify({ locale, hashes: {}, imports: {}, translations: raw })};`;
175
+ }
176
+ const hashes = this.getHashes(locale);
177
+ const allChunks = new Set(Object.keys(hashes));
178
+ for (const deps of Object.values(imports)) {
179
+ deps.forEach((d) => allChunks.add(d));
180
+ }
181
+ const chunkToVar = /* @__PURE__ */ new Map();
182
+ let varIndex = 0;
183
+ for (const chunk of allChunks) {
184
+ chunkToVar.set(chunk, generateBB26(varIndex++));
185
+ }
186
+ const varDecls = [...chunkToVar.entries()].map(([chunk, v]) => `${v}=${JSON.stringify(chunk)}`).join(",");
187
+ const hashesObj = Object.entries(hashes).map(([chunk, hash]) => `[${chunkToVar.get(chunk)}]:${JSON.stringify(hash)}`).join(",");
188
+ const importsObj = Object.entries(imports).filter(([chunk]) => chunkToVar.has(chunk)).map(([chunk, deps]) => {
189
+ const validDeps = deps.filter((d) => chunkToVar.has(d));
190
+ return validDeps.length > 0 ? `[${chunkToVar.get(chunk)}]:[${validDeps.map((d) => chunkToVar.get(d)).join(",")}]` : null;
191
+ }).filter(Boolean).join(",");
192
+ return `(()=>{var ${varDecls};window.__i18n__={locale:${JSON.stringify(locale)},hashes:{${hashesObj}},imports:{${importsObj}},translations:{}};})();`;
193
+ }
138
194
  /**
139
195
  * Clear translations for specified locale(s).
140
196
  * @param locales - Locale(s) to clear, or all if not specified
@@ -146,6 +202,7 @@ var I18nSingleton = class {
146
202
  this.mergedCache.delete(locale);
147
203
  this.compiledCache.delete(locale);
148
204
  this.hashCache.delete(locale);
205
+ this.scriptCache.delete(locale);
149
206
  }
150
207
  }
151
208
  resolveLocales(locales) {
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
2
2
 
3
3
  /**
4
4
  * i18n-aware client:idle directive
5
- * Loads translations in parallel before delegating to Astro's original idle directive
5
+ *
6
+ * Based on Astro's idle directive (MIT License)
7
+ * https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/idle.ts
6
8
  */
9
+
7
10
  declare const idleDirective: ClientDirective;
8
11
 
9
12
  export { idleDirective as default };
@@ -1,13 +1,20 @@
1
1
  import {
2
- loadI18nForChunk
3
- } from "../../chunk-FAKBSLNW.js";
2
+ warmUpI18nForChunk
3
+ } from "../../chunk-7MSGHEST.js";
4
4
  import "../../chunk-4A5BKHFN.js";
5
5
 
6
6
  // src/client/directives/idle.ts
7
- import originalIdle from "astro/client/idle.js";
8
- var idleDirective = async (load, options, el) => {
9
- await loadI18nForChunk(el);
10
- return originalIdle(load, options, el);
7
+ var idleDirective = (load, _options, el) => {
8
+ const cb = async () => {
9
+ warmUpI18nForChunk(el);
10
+ const hydrate = await load();
11
+ await hydrate();
12
+ };
13
+ if ("requestIdleCallback" in window) {
14
+ window.requestIdleCallback(cb);
15
+ } else {
16
+ setTimeout(cb, 200);
17
+ }
11
18
  };
12
19
  var idle_default = idleDirective;
13
20
  export {
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
2
2
 
3
3
  /**
4
4
  * i18n-aware client:load directive
5
- * Loads translations in parallel before delegating to Astro's original load directive
5
+ *
6
+ * Based on Astro's load directive (MIT License)
7
+ * https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/load.ts
6
8
  */
9
+
7
10
  declare const loadDirective: ClientDirective;
8
11
 
9
12
  export { loadDirective as default };
@@ -1,13 +1,13 @@
1
1
  import {
2
- loadI18nForChunk
3
- } from "../../chunk-FAKBSLNW.js";
2
+ warmUpI18nForChunk
3
+ } from "../../chunk-7MSGHEST.js";
4
4
  import "../../chunk-4A5BKHFN.js";
5
5
 
6
6
  // src/client/directives/load.ts
7
- import originalLoad from "astro/client/load.js";
8
- var loadDirective = async (load, options, el) => {
9
- await loadI18nForChunk(el);
10
- return originalLoad(load, options, el);
7
+ var loadDirective = async (load, _options, el) => {
8
+ warmUpI18nForChunk(el);
9
+ const hydrate = await load();
10
+ await hydrate();
11
11
  };
12
12
  var load_default = loadDirective;
13
13
  export {
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
2
2
 
3
3
  /**
4
4
  * i18n-aware client:media directive
5
- * Loads translations in parallel before delegating to Astro's original media directive
5
+ *
6
+ * Based on Astro's media directive (MIT License)
7
+ * https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/media.ts
6
8
  */
9
+
7
10
  declare const mediaDirective: ClientDirective;
8
11
 
9
12
  export { mediaDirective as default };
@@ -1,13 +1,23 @@
1
1
  import {
2
- loadI18nForChunk
3
- } from "../../chunk-FAKBSLNW.js";
2
+ warmUpI18nForChunk
3
+ } from "../../chunk-7MSGHEST.js";
4
4
  import "../../chunk-4A5BKHFN.js";
5
5
 
6
6
  // src/client/directives/media.ts
7
- import originalMedia from "astro/client/media.js";
8
- var mediaDirective = async (load, options, el) => {
9
- await loadI18nForChunk(el);
10
- return originalMedia(load, options, el);
7
+ var mediaDirective = (load, options, el) => {
8
+ const cb = async () => {
9
+ warmUpI18nForChunk(el);
10
+ const hydrate = await load();
11
+ await hydrate();
12
+ };
13
+ if (options.value) {
14
+ const mql = matchMedia(options.value);
15
+ if (mql.matches) {
16
+ cb();
17
+ } else {
18
+ mql.addEventListener("change", cb, { once: true });
19
+ }
20
+ }
11
21
  };
12
22
  var media_default = mediaDirective;
13
23
  export {
@@ -2,8 +2,13 @@ import { ClientDirective } from 'astro';
2
2
 
3
3
  /**
4
4
  * i18n-aware client:only directive
5
- * Loads translations in parallel before delegating to Astro's original only directive
5
+ *
6
+ * Based on Astro's load directive (MIT License)
7
+ * https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/load.ts
8
+ *
9
+ * client:only works the same as client:load - immediate hydration
6
10
  */
11
+
7
12
  declare const onlyDirective: ClientDirective;
8
13
 
9
14
  export { onlyDirective as default };
@@ -1,13 +1,13 @@
1
1
  import {
2
- loadI18nForChunk
3
- } from "../../chunk-FAKBSLNW.js";
2
+ warmUpI18nForChunk
3
+ } from "../../chunk-7MSGHEST.js";
4
4
  import "../../chunk-4A5BKHFN.js";
5
5
 
6
6
  // src/client/directives/only.ts
7
- import originalOnly from "astro/client/only.js";
8
- var onlyDirective = async (load, options, el) => {
9
- await loadI18nForChunk(el);
10
- return originalOnly(load, options, el);
7
+ var onlyDirective = async (load, _options, el) => {
8
+ warmUpI18nForChunk(el);
9
+ const hydrate = await load();
10
+ await hydrate();
11
11
  };
12
12
  var only_default = onlyDirective;
13
13
  export {
@@ -1,10 +1,11 @@
1
- import '../../types-B_-RxW4A.js';
2
- import '../../types-Bzp6KxsI.js';
1
+ import '../../types-BeDFfFIL.js';
2
+ import '../../types-DNRFj1XA.js';
3
3
 
4
4
  /**
5
- * Load translations for a chunk before hydration
5
+ * Warm up translation cache for a chunk and all its descendants (fire and forget)
6
6
  * Extracts chunk name from astro-island's component-url attribute
7
+ * Uses flattened imports map from window.__i18n__ to prefetch all nested translations
7
8
  */
8
- declare function loadI18nForChunk(el: HTMLElement): Promise<void>;
9
+ declare function warmUpI18nForChunk(el: HTMLElement): void;
9
10
 
10
- export { loadI18nForChunk };
11
+ export { warmUpI18nForChunk };
@@ -1,7 +1,7 @@
1
1
  import {
2
- loadI18nForChunk
3
- } from "../../chunk-FAKBSLNW.js";
2
+ warmUpI18nForChunk
3
+ } from "../../chunk-7MSGHEST.js";
4
4
  import "../../chunk-4A5BKHFN.js";
5
5
  export {
6
- loadI18nForChunk
6
+ warmUpI18nForChunk
7
7
  };
@@ -2,8 +2,11 @@ import { ClientDirective } from 'astro';
2
2
 
3
3
  /**
4
4
  * i18n-aware client:visible directive
5
- * Loads translations in parallel before delegating to Astro's original visible directive
5
+ *
6
+ * Based on Astro's visible directive (MIT License)
7
+ * https://github.com/withastro/astro/blob/main/packages/astro/src/runtime/client/visible.ts
6
8
  */
9
+
7
10
  declare const visibleDirective: ClientDirective;
8
11
 
9
12
  export { visibleDirective as default };
@@ -1,13 +1,30 @@
1
1
  import {
2
- loadI18nForChunk
3
- } from "../../chunk-FAKBSLNW.js";
2
+ warmUpI18nForChunk
3
+ } from "../../chunk-7MSGHEST.js";
4
4
  import "../../chunk-4A5BKHFN.js";
5
5
 
6
6
  // src/client/directives/visible.ts
7
- import originalVisible from "astro/client/visible.js";
8
- var visibleDirective = async (load, options, el) => {
9
- await loadI18nForChunk(el);
10
- return originalVisible(load, options, el);
7
+ var visibleDirective = (load, options, el) => {
8
+ const cb = async () => {
9
+ warmUpI18nForChunk(el);
10
+ const hydrate = await load();
11
+ await hydrate();
12
+ };
13
+ const rawOptions = typeof options.value === "object" ? options.value : void 0;
14
+ const ioOptions = {
15
+ rootMargin: rawOptions?.rootMargin
16
+ };
17
+ const io = new IntersectionObserver((entries) => {
18
+ for (const entry of entries) {
19
+ if (!entry.isIntersecting) continue;
20
+ io.disconnect();
21
+ cb();
22
+ break;
23
+ }
24
+ }, ioOptions);
25
+ for (const child of el.children) {
26
+ io.observe(child);
27
+ }
11
28
  };
12
29
  var visible_default = visibleDirective;
13
30
  export {
@@ -1,5 +1,5 @@
1
- import { a as TranslateFunction } from '../types-Bzp6KxsI.js';
2
- import '../types-B_-RxW4A.js';
1
+ import { a as TranslateFunction } from '../types-DNRFj1XA.js';
2
+ import '../types-BeDFfFIL.js';
3
3
 
4
4
  declare function getLocale(): string;
5
5
  /**
@@ -1,8 +1,8 @@
1
- import { a as ExtractionManifest } from '../types-8uKCwFda.js';
2
- export { C as ChunkManifest, E as ExtractedKey } from '../types-8uKCwFda.js';
1
+ import { a as ExtractionManifest } from '../types-DTUho6gi.js';
2
+ export { C as ChunkManifest, E as ExtractedKey } from '../types-DTUho6gi.js';
3
3
  import { AstroIntegrationLogger } from 'astro';
4
4
  import { Plugin } from 'vite';
5
- import '../types-Bzp6KxsI.js';
5
+ import '../types-DNRFj1XA.js';
6
6
 
7
7
  /**
8
8
  * Manifest state management for i18n extraction
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getManifest,
3
3
  i18nVitePlugin
4
- } from "../chunk-JJEDOJBR.js";
4
+ } from "../chunk-EO4FUQXR.js";
5
5
  import "../chunk-4A5BKHFN.js";
6
6
  export {
7
7
  getManifest,
@@ -1,5 +1,5 @@
1
- import { a as ExtractionManifest } from './types-8uKCwFda.js';
2
- import { T as TranslationMeta, C as CompiledTranslations, R as RawTranslations } from './types-Bzp6KxsI.js';
1
+ import { a as ExtractionManifest } from './types-DTUho6gi.js';
2
+ import { T as TranslationMeta, C as CompiledTranslations, R as RawTranslations } from './types-DNRFj1XA.js';
3
3
 
4
4
  /**
5
5
  * Fallback behavior when translation is missing
@@ -30,6 +30,7 @@ declare class I18nSingleton {
30
30
  private mergedCache;
31
31
  private compiledCache;
32
32
  private hashCache;
33
+ private scriptCache;
33
34
  private manifestGetter;
34
35
  configure(config: I18nConfig): Promise<void>;
35
36
  isConfigured(): boolean;
@@ -44,6 +45,12 @@ declare class I18nSingleton {
44
45
  */
45
46
  getManifest(): ExtractionManifest;
46
47
  getHashes(locale: string): Record<string, string>;
48
+ /**
49
+ * Get the inline script for I18nScript component.
50
+ * Cached per locale, invalidated when translations change.
51
+ */
52
+ getClientScript(locale: string): string;
53
+ private createClientScript;
47
54
  /**
48
55
  * Clear translations for specified locale(s).
49
56
  * @param locales - Locale(s) to clear, or all if not specified
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { AstroIntegration, MiddlewareHandler, APIContext } from 'astro';
2
- export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-BGfrSNM8.js';
3
- export { R as RawTranslations, T as TranslationMeta } from './types-Bzp6KxsI.js';
4
- export { E as ExtractedKey, a as ExtractionManifest } from './types-8uKCwFda.js';
2
+ export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-mTHTcD8K.js';
3
+ import { ExcludePattern } from '@astroscope/excludes';
4
+ export { R as RawTranslations, T as TranslationMeta } from './types-DNRFj1XA.js';
5
+ export { E as ExtractedKey, a as ExtractionManifest } from './types-DTUho6gi.js';
5
6
 
6
7
  interface I18nOptions {
7
8
  }
@@ -17,6 +18,28 @@ declare function i18nIntegration(_options?: I18nOptions): AstroIntegration;
17
18
 
18
19
  type I18nMiddlewareOptions = {
19
20
  locale: (ctx: APIContext) => string;
21
+ /**
22
+ * Patterns to exclude from locale context setup.
23
+ * Defaults to RECOMMENDED_EXCLUDES if not provided.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { RECOMMENDED_EXCLUDES } from '@astroscope/excludes';
28
+ *
29
+ * // extend defaults
30
+ * createI18nMiddleware({
31
+ * locale: (ctx) => ctx.locals.session?.locale ?? 'en',
32
+ * exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
33
+ * })
34
+ *
35
+ * // disable excludes entirely
36
+ * createI18nMiddleware({
37
+ * locale: (ctx) => ctx.locals.session?.locale ?? 'en',
38
+ * exclude: [],
39
+ * })
40
+ * ```
41
+ */
42
+ exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;
20
43
  };
21
44
  /**
22
45
  * Create the i18n chunk middleware that serves translation chunks at `/_i18n/` endpoints.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  i18nVitePlugin
3
- } from "./chunk-JJEDOJBR.js";
3
+ } from "./chunk-EO4FUQXR.js";
4
4
  import {
5
5
  i18n,
6
6
  runWithContext
7
- } from "./chunk-QFAAGCP5.js";
7
+ } from "./chunk-RHWEV4L2.js";
8
8
  import "./chunk-KNDYKTHE.js";
9
9
  import "./chunk-4A5BKHFN.js";
10
10
 
@@ -55,6 +55,7 @@ ${attributes}
55
55
  }
56
56
 
57
57
  // src/server/middleware.ts
58
+ import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/excludes";
58
59
  var I18N_ENDPOINT_PREFIX = "/_i18n/";
59
60
  function createI18nChunkMiddleware() {
60
61
  return (ctx, next) => {
@@ -111,6 +112,9 @@ function createI18nChunkMiddleware() {
111
112
  }
112
113
  function createI18nMiddleware(options) {
113
114
  return (ctx, next) => {
115
+ if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {
116
+ return next();
117
+ }
114
118
  const locale = options.locale(ctx);
115
119
  const context = {
116
120
  locale,
@@ -1,7 +1,7 @@
1
- import { a as I18nContext } from './i18n-BGfrSNM8.js';
2
- export { i as i18n } from './i18n-BGfrSNM8.js';
3
- export { I as I18nClientState } from './types-Bzp6KxsI.js';
4
- import './types-8uKCwFda.js';
1
+ import { a as I18nContext } from './i18n-mTHTcD8K.js';
2
+ export { i as i18n } from './i18n-mTHTcD8K.js';
3
+ export { I as I18nClientState } from './types-DNRFj1XA.js';
4
+ import './types-DTUho6gi.js';
5
5
 
6
6
  /**
7
7
  * Get the current i18n context