@astroscope/i18n 0.3.3 → 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
@@ -421,6 +421,23 @@ For `:currency` and `:unit`, the required option (`currency` or `unit`) can be:
421
421
  - `'throw'` — Throw an error
422
422
  - `(key, meta) => string` — Custom function
423
423
 
424
+ ## ESLint Plugin
425
+
426
+ Use [`@astroscope/eslint-plugin-i18n`](../eslint-plugin-i18n) to enforce correct `t()` usage, catch build-time extraction issues, and ensure i18n-aware hydration directives.
427
+
428
+ ```bash
429
+ npm install -D @astroscope/eslint-plugin-i18n
430
+ ```
431
+
432
+ ```js
433
+ // eslint.config.js
434
+ import i18n from '@astroscope/eslint-plugin-i18n';
435
+
436
+ export default [
437
+ i18n.configs.recommended,
438
+ ];
439
+ ```
440
+
424
441
  ## License
425
442
 
426
443
  MIT
@@ -0,0 +1,36 @@
1
+ // src/extraction/manifest.ts
2
+ import path from "path";
3
+ var I18N_MANIFEST_GLOBAL_KEY = "__astroscope_i18n_manifest__";
4
+ function getGlobalState() {
5
+ const g = globalThis;
6
+ if (!g[I18N_MANIFEST_GLOBAL_KEY]) {
7
+ g[I18N_MANIFEST_GLOBAL_KEY] = {
8
+ extractedKeys: [],
9
+ chunkManifest: {},
10
+ importsManifest: {},
11
+ projectRoot: "",
12
+ version: 0
13
+ };
14
+ }
15
+ return g[I18N_MANIFEST_GLOBAL_KEY];
16
+ }
17
+ function getManifest() {
18
+ const state = getGlobalState();
19
+ const keys = state.extractedKeys.map((key) => ({
20
+ ...key,
21
+ // make file paths relative to project root for better readability
22
+ // and avoiding leaking absolute paths from dev or ci environments
23
+ files: key.files.map((fileLocation) => {
24
+ const [filePath, line] = fileLocation.split(":");
25
+ if (!filePath) return fileLocation;
26
+ const relativePath = state.projectRoot ? path.relative(state.projectRoot, filePath) : filePath;
27
+ return line ? `${relativePath}:${line}` : relativePath;
28
+ })
29
+ }));
30
+ return { keys, chunks: state.chunkManifest, imports: state.importsManifest };
31
+ }
32
+
33
+ export {
34
+ getGlobalState,
35
+ getManifest
36
+ };
@@ -1,41 +1,14 @@
1
+ import {
2
+ getGlobalState,
3
+ getManifest
4
+ } from "./chunk-FYKC3DAZ.js";
1
5
  import {
2
6
  chunkIdToName
3
7
  } from "./chunk-4A5BKHFN.js";
4
8
 
5
- // src/extraction/manifest.ts
6
- import path from "path";
7
- var I18N_MANIFEST_GLOBAL_KEY = "__astroscope_i18n_manifest__";
8
- function getGlobalState() {
9
- const g = globalThis;
10
- if (!g[I18N_MANIFEST_GLOBAL_KEY]) {
11
- g[I18N_MANIFEST_GLOBAL_KEY] = {
12
- extractedKeys: [],
13
- chunkManifest: {},
14
- importsManifest: {},
15
- projectRoot: ""
16
- };
17
- }
18
- return g[I18N_MANIFEST_GLOBAL_KEY];
19
- }
20
- function getManifest() {
21
- const state = getGlobalState();
22
- const keys = state.extractedKeys.map((key) => ({
23
- ...key,
24
- // make file paths relative to project root for better readability
25
- // and avoiding leaking absolute paths from dev or ci environments
26
- files: key.files.map((fileLocation) => {
27
- const [filePath, line] = fileLocation.split(":");
28
- if (!filePath) return fileLocation;
29
- const relativePath = state.projectRoot ? path.relative(state.projectRoot, filePath) : filePath;
30
- return line ? `${relativePath}:${line}` : relativePath;
31
- })
32
- }));
33
- return { keys, chunks: state.chunkManifest, imports: state.importsManifest };
34
- }
35
-
36
9
  // src/extraction/vite-plugin.ts
37
10
  import fs2 from "fs";
38
- import path2 from "path";
11
+ import path from "path";
39
12
  import MagicString from "magic-string";
40
13
 
41
14
  // src/extraction/extract.ts
@@ -337,9 +310,9 @@ function i18nVitePlugin(options) {
337
310
  const syncGlobalState = () => {
338
311
  const state = getGlobalState();
339
312
  state.extractedKeys = store.extractedKeys;
313
+ state.version++;
340
314
  };
341
315
  let isBuild = false;
342
- let isSSR = false;
343
316
  let projectRoot = "";
344
317
  return {
345
318
  name: "@astroscope/i18n/extract",
@@ -348,7 +321,6 @@ function i18nVitePlugin(options) {
348
321
  const state = getGlobalState();
349
322
  state.projectRoot = config.root;
350
323
  isBuild = config.command === "build";
351
- isSSR = !!config.build.ssr;
352
324
  projectRoot = config.root;
353
325
  },
354
326
  async configureServer(server) {
@@ -485,9 +457,9 @@ export function getManifest() { return _getManifest(); }
485
457
  }
486
458
  },
487
459
  writeBundle(outputOptions) {
488
- if (!isSSR && outputOptions.dir) {
489
- const chunksDir = path2.resolve(outputOptions.dir, "..", "server", "chunks");
490
- const manifestPath = path2.join(chunksDir, MANIFEST_FILE_NAME);
460
+ if (this.environment.name === "client" && outputOptions.dir) {
461
+ const chunksDir = path.resolve(outputOptions.dir, "..", "server", "chunks");
462
+ const manifestPath = path.join(chunksDir, MANIFEST_FILE_NAME);
491
463
  if (fs2.existsSync(chunksDir)) {
492
464
  fs2.writeFileSync(manifestPath, JSON.stringify(getManifest()));
493
465
  } else {
@@ -499,6 +471,5 @@ export function getManifest() { return _getManifest(); }
499
471
  }
500
472
 
501
473
  export {
502
- getManifest,
503
474
  i18nVitePlugin
504
475
  };
@@ -1,6 +1,9 @@
1
1
  import {
2
2
  compileTranslations
3
3
  } from "./chunk-33JVAFGQ.js";
4
+ import {
5
+ getGlobalState
6
+ } from "./chunk-FYKC3DAZ.js";
4
7
 
5
8
  // src/extraction/hash.ts
6
9
  import { createHash } from "crypto";
@@ -57,6 +60,8 @@ var I18nSingleton = class {
57
60
  chunkCache = /* @__PURE__ */ new Map();
58
61
  // manifest getter (set by init module, provides live data in dev mode)
59
62
  manifestGetter = null;
63
+ // tracks the manifest version to detect when derived caches need invalidation
64
+ manifestVersion = 0;
60
65
  async configure(config) {
61
66
  if (!config.locales?.length) {
62
67
  throw new Error("i18n.configure(): locales array is required and must not be empty");
@@ -108,12 +113,18 @@ var I18nSingleton = class {
108
113
  this.compiledCache.delete(locale);
109
114
  this.hashCache.delete(locale);
110
115
  this.scriptCache.delete(locale);
116
+ for (const key of this.chunkCache.keys()) {
117
+ if (key.startsWith(`${locale}:`)) {
118
+ this.chunkCache.delete(key);
119
+ }
120
+ }
111
121
  const { chunks } = this.getManifest();
112
122
  if (Object.keys(chunks).length > 0) {
113
123
  this.hashCache.set(locale, computeAllChunkHashes(raw, chunks));
114
124
  }
115
125
  }
116
126
  getTranslations(locale) {
127
+ this.invalidateIfManifestChanged();
117
128
  const cached = this.mergedCache.get(locale);
118
129
  if (cached) {
119
130
  return cached;
@@ -244,6 +255,20 @@ var I18nSingleton = class {
244
255
  }
245
256
  }
246
257
  }
258
+ /**
259
+ * invalidate derived caches when the extraction manifest has changed (dev mode HMR)
260
+ */
261
+ invalidateIfManifestChanged() {
262
+ const { version } = getGlobalState();
263
+ if (version === this.manifestVersion) {
264
+ return;
265
+ }
266
+ this.manifestVersion = version;
267
+ this.mergedCache.clear();
268
+ this.compiledCache.clear();
269
+ this.scriptCache.clear();
270
+ this.chunkCache.clear();
271
+ }
247
272
  resolveLocales(locales) {
248
273
  if (!locales) {
249
274
  return this.config?.locales ?? [];
@@ -1,5 +1,5 @@
1
1
  import { R as RichComponents } from '../rich-CQUhdwBc.js';
2
- import { a as TranslateFunction, T as TranslationMeta } from '../types-DNRFj1XA.js';
2
+ import { T as TranslationMeta, a as TranslateFunction } from '../types-DNRFj1XA.js';
3
3
  import '../types-BeDFfFIL.js';
4
4
 
5
5
  declare function getLocale(): string;
@@ -1,7 +1,9 @@
1
1
  import {
2
- getManifest,
3
2
  i18nVitePlugin
4
- } from "../chunk-Z3LDPCYD.js";
3
+ } from "../chunk-IJXSX5DY.js";
4
+ import {
5
+ getManifest
6
+ } from "../chunk-FYKC3DAZ.js";
5
7
  import "../chunk-4A5BKHFN.js";
6
8
  export {
7
9
  getManifest,
@@ -34,6 +34,7 @@ declare class I18nSingleton {
34
34
  private scriptCache;
35
35
  private chunkCache;
36
36
  private manifestGetter;
37
+ private manifestVersion;
37
38
  configure(config: I18nConfig): Promise<void>;
38
39
  isConfigured(): boolean;
39
40
  getConfig(): NormalizedConfig;
@@ -64,6 +65,10 @@ declare class I18nSingleton {
64
65
  * @param locales - Locale(s) to clear, or all if not specified
65
66
  */
66
67
  clear(locales?: string | string[] | undefined): void;
68
+ /**
69
+ * invalidate derived caches when the extraction manifest has changed (dev mode HMR)
70
+ */
71
+ private invalidateIfManifestChanged;
67
72
  private resolveLocales;
68
73
  }
69
74
  declare const i18n: I18nSingleton;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { AstroIntegration, MiddlewareHandler, APIContext } from 'astro';
1
+ import { AstroIntegration, APIContext, MiddlewareHandler } from 'astro';
2
2
  import { I as I18nOptions } from './types-B_8vAmvv.js';
3
- export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-DaLnNvss.js';
3
+ export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-BmK1MjH7.js';
4
4
  import { ExcludePattern } from '@astroscope/excludes';
5
5
  export { R as RawTranslations, T as TranslationMeta } from './types-DNRFj1XA.js';
6
6
  export { E as ExtractedKey, a as ExtractionManifest } from './types-moTgocy7.js';
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  i18nVitePlugin
3
- } from "./chunk-Z3LDPCYD.js";
3
+ } from "./chunk-IJXSX5DY.js";
4
4
  import {
5
5
  i18n,
6
6
  runWithContext
7
- } from "./chunk-MSJHXRJB.js";
7
+ } from "./chunk-LXW5WYBS.js";
8
8
  import "./chunk-33JVAFGQ.js";
9
+ import "./chunk-FYKC3DAZ.js";
9
10
  import "./chunk-4A5BKHFN.js";
10
11
 
11
12
  // src/integration/integration.ts
@@ -1,5 +1,5 @@
1
- import { a as I18nContext } from './i18n-DaLnNvss.js';
2
- export { i as i18n } from './i18n-DaLnNvss.js';
1
+ import { a as I18nContext } from './i18n-BmK1MjH7.js';
2
+ export { i as i18n } from './i18n-BmK1MjH7.js';
3
3
  export { I as I18nClientState } from './types-DNRFj1XA.js';
4
4
  import './types-moTgocy7.js';
5
5
 
package/dist/internal.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  getContext,
3
3
  i18n
4
- } from "./chunk-MSJHXRJB.js";
4
+ } from "./chunk-LXW5WYBS.js";
5
5
  import "./chunk-33JVAFGQ.js";
6
+ import "./chunk-FYKC3DAZ.js";
6
7
  export {
7
8
  getContext,
8
9
  i18n
@@ -1,5 +1,5 @@
1
1
  import { R as RichComponents } from '../rich-CQUhdwBc.js';
2
- import { a as TranslateFunction, T as TranslationMeta } from '../types-DNRFj1XA.js';
2
+ import { T as TranslationMeta, a as TranslateFunction } from '../types-DNRFj1XA.js';
3
3
 
4
4
  /**
5
5
  * Get the current locale from request context.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getContext,
3
3
  i18n
4
- } from "../chunk-MSJHXRJB.js";
4
+ } from "../chunk-LXW5WYBS.js";
5
5
  import {
6
6
  normalizeMeta,
7
7
  partsToNodes
@@ -10,6 +10,7 @@ import {
10
10
  compileMessage,
11
11
  formatMessageToParts
12
12
  } from "../chunk-33JVAFGQ.js";
13
+ import "../chunk-FYKC3DAZ.js";
13
14
 
14
15
  // src/server/translate.ts
15
16
  var fallbackCache = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/i18n",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "i18n for Astro + React islands — MessageFormat 2, automatic tree-shaking, parallel loading, any translation source",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -84,27 +84,27 @@
84
84
  "scripts": {
85
85
  "build": "tsup src/index.ts src/internal.ts src/extraction/index.ts src/server/translate.ts src/client/translate.ts src/client/directives/load.ts src/client/directives/idle.ts src/client/directives/visible.ts src/client/directives/media.ts src/client/directives/only.ts src/client/directives/utils.ts --format esm --dts --external astro --external astro/client/load.js --external astro/client/idle.js --external astro/client/visible.js --external astro/client/media.js --external astro/client/only.js --external virtual:@astroscope/i18n/manifest --external @babel/core",
86
86
  "typecheck": "tsc --noEmit",
87
- "lint": "eslint 'src/**/*.ts'",
88
- "lint:fix": "eslint 'src/**/*.ts' --fix"
87
+ "lint": "eslint src",
88
+ "lint:fix": "eslint src --fix"
89
89
  },
90
90
  "dependencies": {
91
- "@astrojs/compiler": "^2.13.0",
91
+ "@astrojs/compiler": "^3.0.0",
92
92
  "@babel/core": "^7.29.0",
93
93
  "@babel/preset-typescript": "^7.28.5",
94
94
  "messageformat": "^4.0.0",
95
- "glob": "^13.0.1",
95
+ "glob": "^13.0.6",
96
96
  "magic-string": "^0.30.21"
97
97
  },
98
98
  "devDependencies": {
99
99
  "@babel/types": "^7.29.0",
100
100
  "@types/babel__core": "^7.20.5",
101
- "astro": "^5.17.1",
101
+ "astro": "^6.0.2",
102
102
  "tsup": "^8.5.1",
103
103
  "typescript": "^5.9.3",
104
- "vite": "^6.4.1"
104
+ "vite": "^7.3.1"
105
105
  },
106
106
  "peerDependencies": {
107
- "@astroscope/excludes": "^0.2.0",
108
- "astro": "^5.0.0"
107
+ "@astroscope/excludes": "^0.3.0",
108
+ "astro": "^6.0.0"
109
109
  }
110
110
  }