@astroscope/i18n 0.2.2 → 0.2.4

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.
@@ -23,7 +23,12 @@ function getManifest() {
23
23
  ...key,
24
24
  // make file paths relative to project root for better readability
25
25
  // and avoiding leaking absolute paths from dev or ci environments
26
- file: state.projectRoot ? path.relative(state.projectRoot, key.file) : key.file
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
+ })
27
32
  }));
28
33
  return { keys, chunks: state.chunkManifest, imports: state.importsManifest };
29
34
  }
@@ -46,6 +51,8 @@ async function extractKeysFromFile(options) {
46
51
  import("@babel/core"),
47
52
  import("./babel-plugin-GOB6FMZ2.js")
48
53
  ]);
54
+ const isTypeScript = TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) || filename.endsWith(".astro");
55
+ const isJSX = JSX_EXTENSIONS.some((ext) => filename.endsWith(ext));
49
56
  const result = await transformAsync(code, {
50
57
  filename,
51
58
  sourceMaps: stripFallbacks,
@@ -53,10 +60,7 @@ async function extractKeysFromFile(options) {
53
60
  babelrc: false,
54
61
  configFile: false,
55
62
  parserOpts: {
56
- plugins: [
57
- ...TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) ? ["typescript"] : [],
58
- ...JSX_EXTENSIONS.some((ext) => filename.endsWith(ext)) ? ["jsx"] : []
59
- ]
63
+ plugins: [...isTypeScript ? ["typescript"] : [], ...isJSX ? ["jsx"] : []]
60
64
  },
61
65
  plugins: [
62
66
  [
@@ -84,18 +88,21 @@ var KeyStore = class {
84
88
  constructor(logger) {
85
89
  this.logger = logger;
86
90
  }
87
- extractedKeys = [];
91
+ /** All key occurrences (may have duplicates) */
92
+ occurrences = [];
88
93
  fileToKeys = /* @__PURE__ */ new Map();
89
94
  filesWithI18n = /* @__PURE__ */ new Set();
90
95
  /**
91
- * Add keys for a file, replacing any existing keys for that file.
96
+ * Add key occurrences for a file, replacing any existing occurrences for that file.
92
97
  */
93
98
  addFileKeys(filename, keys) {
94
- const oldKeys = this.fileToKeys.get(filename);
95
99
  const newKeyStrings = keys.map((k) => k.key);
100
+ const oldKeys = this.fileToKeys.get(filename);
96
101
  if (oldKeys) {
97
- const added = newKeyStrings.filter((k) => !oldKeys.includes(k));
98
- const removed = oldKeys.filter((k) => !newKeyStrings.includes(k));
102
+ const uniqueNew = [...new Set(newKeyStrings)];
103
+ const uniqueOld = [...new Set(oldKeys)];
104
+ const added = uniqueNew.filter((k) => !uniqueOld.includes(k));
105
+ const removed = uniqueOld.filter((k) => !uniqueNew.includes(k));
99
106
  const file = filename.split("/").pop();
100
107
  if (added.length > 0 || removed.length > 0) {
101
108
  const parts = [];
@@ -105,23 +112,47 @@ var KeyStore = class {
105
112
  }
106
113
  }
107
114
  if (oldKeys) {
108
- for (let i = this.extractedKeys.length - 1; i >= 0; i--) {
109
- if (this.extractedKeys[i]?.file === filename) {
110
- this.extractedKeys.splice(i, 1);
115
+ for (let i = this.occurrences.length - 1; i >= 0; i--) {
116
+ if (this.occurrences[i]?.file === filename) {
117
+ this.occurrences.splice(i, 1);
111
118
  }
112
119
  }
113
120
  }
114
121
  this.filesWithI18n.add(filename);
115
122
  if (keys.length > 0) {
116
- this.extractedKeys.push(...keys);
123
+ this.occurrences.push(...keys);
117
124
  }
118
125
  this.fileToKeys.set(filename, newKeyStrings);
119
126
  }
127
+ /**
128
+ * Get deduplicated keys with all file locations merged.
129
+ * Last occurrence's meta wins for each key.
130
+ */
131
+ get extractedKeys() {
132
+ const keyMap = /* @__PURE__ */ new Map();
133
+ for (const occurrence of this.occurrences) {
134
+ const existing = keyMap.get(occurrence.key);
135
+ const fileLocation = `${occurrence.file}:${occurrence.line}`;
136
+ if (existing) {
137
+ if (!existing.files.includes(fileLocation)) {
138
+ existing.files.push(fileLocation);
139
+ }
140
+ existing.meta = occurrence.meta;
141
+ } else {
142
+ keyMap.set(occurrence.key, {
143
+ key: occurrence.key,
144
+ meta: occurrence.meta,
145
+ files: [fileLocation]
146
+ });
147
+ }
148
+ }
149
+ return Array.from(keyMap.values());
150
+ }
120
151
  /**
121
152
  * Get count of unique keys.
122
153
  */
123
154
  get uniqueKeyCount() {
124
- return new Set(this.extractedKeys.map((k) => k.key)).size;
155
+ return new Set(this.occurrences.map((k) => k.key)).size;
125
156
  }
126
157
  /**
127
158
  * Merge another store's data into this one.
@@ -133,7 +164,13 @@ var KeyStore = class {
133
164
  for (const file of other.filesWithI18n) {
134
165
  this.filesWithI18n.add(file);
135
166
  }
136
- this.extractedKeys.push(...other.extractedKeys);
167
+ this.occurrences.push(...other.occurrences);
168
+ }
169
+ /**
170
+ * Internal access to occurrences (for merge)
171
+ */
172
+ get occurrencesList() {
173
+ return this.occurrences;
137
174
  }
138
175
  };
139
176
 
@@ -196,6 +233,10 @@ var MANIFEST_FILE_NAME = "i18n-manifest.json";
196
233
  function i18nVitePlugin(options) {
197
234
  const { logger } = options;
198
235
  const store = new KeyStore(logger);
236
+ const syncGlobalState = () => {
237
+ const state = getGlobalState();
238
+ state.extractedKeys = store.extractedKeys;
239
+ };
199
240
  let isBuild = false;
200
241
  let isSSR = false;
201
242
  let projectRoot = "";
@@ -205,7 +246,6 @@ function i18nVitePlugin(options) {
205
246
  configResolved(config) {
206
247
  const state = getGlobalState();
207
248
  state.projectRoot = config.root;
208
- state.extractedKeys = store.extractedKeys;
209
249
  isBuild = config.command === "build";
210
250
  isSSR = !!config.build.ssr;
211
251
  projectRoot = config.root;
@@ -216,6 +256,7 @@ function i18nVitePlugin(options) {
216
256
  }
217
257
  const result = await scan(projectRoot, logger);
218
258
  store.merge(result);
259
+ syncGlobalState();
219
260
  logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);
220
261
  },
221
262
  resolveId(id) {
@@ -262,6 +303,7 @@ export function getManifest() { return _getManifest(); }
262
303
  stripFallbacks: isBuild
263
304
  });
264
305
  store.addFileKeys(filename, result.keys);
306
+ syncGlobalState();
265
307
  if (isBuild && result.code) {
266
308
  return { code: result.code, map: result.map };
267
309
  }
@@ -1,5 +1,5 @@
1
- import { a as ExtractionManifest } from '../types-DTUho6gi.js';
2
- export { C as ChunkManifest, E as ExtractedKey } from '../types-DTUho6gi.js';
1
+ import { a as ExtractionManifest } from '../types-moTgocy7.js';
2
+ export { C as ChunkManifest, E as ExtractedKey } from '../types-moTgocy7.js';
3
3
  import { AstroIntegrationLogger } from 'astro';
4
4
  import { Plugin } from 'vite';
5
5
  import '../types-DNRFj1XA.js';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getManifest,
3
3
  i18nVitePlugin
4
- } from "../chunk-FW6TPWMT.js";
4
+ } from "../chunk-3VF2YMJG.js";
5
5
  import "../chunk-4A5BKHFN.js";
6
6
  export {
7
7
  getManifest,
@@ -1,4 +1,4 @@
1
- import { a as ExtractionManifest } from './types-DTUho6gi.js';
1
+ import { a as ExtractionManifest } from './types-moTgocy7.js';
2
2
  import { T as TranslationMeta, C as CompiledTranslations, R as RawTranslations } from './types-DNRFj1XA.js';
3
3
 
4
4
  /**
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { AstroIntegration, MiddlewareHandler, APIContext } from 'astro';
2
- export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-BBAAVYz_.js';
2
+ export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-CbSjmzlu.js';
3
3
  import { ExcludePattern } from '@astroscope/excludes';
4
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
+ export { E as ExtractedKey, a as ExtractionManifest } from './types-moTgocy7.js';
6
6
 
7
7
  interface I18nOptions {
8
8
  }
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  i18nVitePlugin
3
- } from "./chunk-FW6TPWMT.js";
3
+ } from "./chunk-3VF2YMJG.js";
4
4
  import {
5
5
  i18n,
6
6
  runWithContext
@@ -1,7 +1,7 @@
1
- import { a as I18nContext } from './i18n-BBAAVYz_.js';
2
- export { i as i18n } from './i18n-BBAAVYz_.js';
1
+ import { a as I18nContext } from './i18n-CbSjmzlu.js';
2
+ export { i as i18n } from './i18n-CbSjmzlu.js';
3
3
  export { I as I18nClientState } from './types-DNRFj1XA.js';
4
- import './types-DTUho6gi.js';
4
+ import './types-moTgocy7.js';
5
5
 
6
6
  /**
7
7
  * Get the current i18n context
@@ -1,13 +1,13 @@
1
1
  import { T as TranslationMeta } from './types-DNRFj1XA.js';
2
2
 
3
3
  /**
4
- * Extracted metadata for a single translation key
4
+ * Deduplicated translation key with all file locations (used in manifest)
5
5
  */
6
6
  type ExtractedKey = {
7
7
  key: string;
8
8
  meta: TranslationMeta;
9
- file: string;
10
- line: number;
9
+ /** All locations where this key is used (file:line format) */
10
+ files: string[];
11
11
  };
12
12
  /**
13
13
  * Chunk-to-keys mapping produced by Vite plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/i18n",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "i18n for Astro + React islands — automatic tree-shaking, parallel loading, any translation source",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",