@astroscope/i18n 2.1.1 → 2.1.2

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 (44) hide show
  1. package/README.md +4 -4
  2. package/dist/{babel-plugin-CWafmw5z.js → babel-plugin-DJZS9L_W.js} +28 -30
  3. package/dist/babel-plugin-DJZS9L_W.js.map +1 -0
  4. package/dist/client/translate.d.ts +2 -2
  5. package/dist/client/translate.js +2 -2
  6. package/dist/client/translate.js.map +1 -1
  7. package/dist/{context-CgqIoo00.js → context-Cq9-KsBw.js} +31 -26
  8. package/dist/context-Cq9-KsBw.js.map +1 -0
  9. package/dist/extraction/index.d.ts +1 -1
  10. package/dist/extraction/index.d.ts.map +1 -1
  11. package/dist/extraction/index.js +2 -2
  12. package/dist/{i18n-Czkrfb_4.d.ts → i18n-Ca5mVXlq.d.ts} +6 -8
  13. package/dist/i18n-Ca5mVXlq.d.ts.map +1 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +7 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/internal.d.ts +2 -2
  19. package/dist/internal.js +1 -2
  20. package/dist/{manifest-Cma68XM-.js → manifest-X-UhW6tH.js} +2 -2
  21. package/dist/manifest-X-UhW6tH.js.map +1 -0
  22. package/dist/{middleware-OnRVbxvs.js → middleware-SkXWqtl3.js} +5 -4
  23. package/dist/middleware-SkXWqtl3.js.map +1 -0
  24. package/dist/{rich-CHisXE-A.js → rich-BDqjhv2h.js} +31 -20
  25. package/dist/rich-BDqjhv2h.js.map +1 -0
  26. package/dist/server/chunk-middleware-entrypoint.js +1 -1
  27. package/dist/server/translate.d.ts +2 -2
  28. package/dist/server/translate.d.ts.map +1 -1
  29. package/dist/server/translate.js +3 -3
  30. package/dist/server/translate.js.map +1 -1
  31. package/dist/{types-f-SNJdvf.d.ts → types-BC-tQ30r.d.ts} +2 -2
  32. package/dist/{types-f-SNJdvf.d.ts.map → types-BC-tQ30r.d.ts.map} +1 -1
  33. package/dist/{types-DU8QHch4.d.ts → types-BjTD5hUT.d.ts} +6 -4
  34. package/dist/{types-DU8QHch4.d.ts.map → types-BjTD5hUT.d.ts.map} +1 -1
  35. package/dist/{vite-plugin-C-jEpd8t.js → vite-plugin-BPYFFBQp.js} +77 -72
  36. package/dist/vite-plugin-BPYFFBQp.js.map +1 -0
  37. package/package.json +10 -9
  38. package/dist/babel-plugin-CWafmw5z.js.map +0 -1
  39. package/dist/context-CgqIoo00.js.map +0 -1
  40. package/dist/i18n-Czkrfb_4.d.ts.map +0 -1
  41. package/dist/manifest-Cma68XM-.js.map +0 -1
  42. package/dist/middleware-OnRVbxvs.js.map +0 -1
  43. package/dist/rich-CHisXE-A.js.map +0 -1
  44. package/dist/vite-plugin-C-jEpd8t.js.map +0 -1
@@ -32,6 +32,31 @@ function addKeyIfNeeded(element, key) {
32
32
  function isMarkupPart(part) {
33
33
  return part.type === "markup";
34
34
  }
35
+ function applyMarkupPart(part, stack, result, components, nextKey) {
36
+ const target = stack.length > 0 ? stack.at(-1).children : result;
37
+ switch (part.kind) {
38
+ case "open":
39
+ stack.push({
40
+ name: part.name,
41
+ children: []
42
+ });
43
+ break;
44
+ case "close": {
45
+ const frame = stack.pop();
46
+ if (!frame) break;
47
+ const component = components[frame.name];
48
+ const parent = stack.length > 0 ? stack.at(-1).children : result;
49
+ if (component) parent.push(addKeyIfNeeded(component(frame.children), nextKey()));
50
+ else parent.push(...frame.children);
51
+ break;
52
+ }
53
+ case "standalone": {
54
+ const component = components[part.name];
55
+ if (component) target.push(addKeyIfNeeded(component([]), nextKey()));
56
+ break;
57
+ }
58
+ }
59
+ }
35
60
  /**
36
61
  * Convert MF2 formatToParts() output to a tree of nodes.
37
62
  * Works with any JSX runtime (Astro, React, etc.) - the callbacks
@@ -51,25 +76,11 @@ function partsToNodes(parts, components) {
51
76
  const result = [];
52
77
  const stack = [];
53
78
  let keyIndex = 0;
79
+ const nextKey = () => `rich-${keyIndex++}`;
54
80
  for (const part of parts) {
55
- const target = stack.length > 0 ? stack[stack.length - 1].children : result;
56
- if (isMarkupPart(part)) {
57
- if (part.kind === "open") stack.push({
58
- name: part.name,
59
- children: []
60
- });
61
- else if (part.kind === "close") {
62
- const frame = stack.pop();
63
- if (!frame) continue;
64
- const component = components[frame.name];
65
- const parent = stack.length > 0 ? stack[stack.length - 1].children : result;
66
- if (component) parent.push(addKeyIfNeeded(component(frame.children), `rich-${keyIndex++}`));
67
- else parent.push(...frame.children);
68
- } else if (part.kind === "standalone") {
69
- const component = components[part.name];
70
- if (component) target.push(addKeyIfNeeded(component([]), `rich-${keyIndex++}`));
71
- }
72
- } else if (part.type === "text") target.push(part.value);
81
+ const target = stack.length > 0 ? stack.at(-1).children : result;
82
+ if (isMarkupPart(part)) applyMarkupPart(part, stack, result, components, nextKey);
83
+ else if (part.type === "text") target.push(part.value);
73
84
  else if (part.type === "bidiIsolation") {} else if ("value" in part && part.value !== void 0 && part.value !== null) {
74
85
  const stringValue = String(part.value);
75
86
  target.push(stringValue);
@@ -77,11 +88,11 @@ function partsToNodes(parts, components) {
77
88
  }
78
89
  while (stack.length > 0) {
79
90
  const frame = stack.pop();
80
- (stack.length > 0 ? stack[stack.length - 1].children : result).push(...frame.children);
91
+ (stack.length > 0 ? stack.at(-1).children : result).push(...frame.children);
81
92
  }
82
93
  return result;
83
94
  }
84
95
  //#endregion
85
96
  export { normalizeMeta as n, partsToNodes as t };
86
97
 
87
- //# sourceMappingURL=rich-CHisXE-A.js.map
98
+ //# sourceMappingURL=rich-BDqjhv2h.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-BDqjhv2h.js","names":[],"sources":["../src/shared/meta.ts","../src/shared/rich.ts"],"sourcesContent":["import type { TranslationMeta } from './types.js';\n\n/**\n * Normalize meta argument to TranslationMeta object\n */\nexport function normalizeMeta(meta: TranslationMeta | string): TranslationMeta {\n return typeof meta === 'string' ? { fallback: meta } : meta;\n}\n","import type { MessageMarkupPart, MessagePart } from 'messageformat';\n\n// MessagePart<string> covers all built-in part types\ntype Part = MessagePart<string>;\n\n/**\n * Component callbacks for rich text rendering.\n * Each key maps to a function that wraps children in a component.\n *\n * @example\n * ```tsx\n * const components: RichComponents = {\n * link: (children) => <a href=\"/tos\">{children}</a>,\n * bold: (children) => <strong>{children}</strong>,\n * };\n * ```\n */\nexport type RichComponents<T = unknown> = Record<string, (children: (string | T)[]) => T>;\n\ntype StackFrame<T> = {\n name: string;\n children: (string | T)[];\n};\n\n// react element symbols - Symbol.for() returns the same symbol React uses internally\nconst REACT_ELEMENT_TYPE = Symbol.for('react.element');\nconst REACT_TRANSITIONAL_ELEMENT_TYPE = Symbol.for('react.transitional.element');\n\ntype ReactElementLike = {\n $$typeof: symbol;\n key: string | null;\n};\n\n/**\n * Duck-type check for React elements without importing React.\n * Uses Symbol.for() which returns the same symbol React uses internally.\n */\nfunction isReactElement(value: unknown): value is ReactElementLike {\n if (typeof value !== 'object' || value === null || !('$$typeof' in value)) {\n return false;\n }\n\n const type = (value as ReactElementLike).$$typeof;\n\n return type === REACT_ELEMENT_TYPE || type === REACT_TRANSITIONAL_ELEMENT_TYPE;\n}\n\n/**\n * Add a key to a React element if it doesn't have one.\n * Creates a shallow copy since React elements are frozen.\n */\nfunction addKeyIfNeeded<T>(element: T, key: string): T {\n if (isReactElement(element) && element.key === null) {\n return { ...element, key } as T;\n }\n\n return element;\n}\n\nfunction isMarkupPart(part: Part): part is MessageMarkupPart {\n return part.type === 'markup';\n}\n\nfunction applyMarkupPart<T>(\n part: MessageMarkupPart,\n stack: StackFrame<T>[],\n result: (string | T)[],\n components: RichComponents<T>,\n nextKey: () => string,\n): void {\n const target = stack.length > 0 ? stack.at(-1)!.children : result;\n\n switch (part.kind) {\n case 'open': {\n stack.push({ name: part.name, children: [] });\n\n break;\n }\n\n case 'close': {\n const frame = stack.pop();\n\n if (!frame) {\n // mismatched close tag, ignore\n break;\n }\n\n const component = components[frame.name];\n const parent = stack.length > 0 ? stack.at(-1)!.children : result;\n\n if (component) {\n parent.push(addKeyIfNeeded(component(frame.children), nextKey()));\n } else {\n // no component for this tag, just flatten children\n parent.push(...frame.children);\n }\n\n break;\n }\n\n case 'standalone': {\n // self-closing: {#icon/}\n const component = components[part.name];\n\n if (component) {\n target.push(addKeyIfNeeded(component([]), nextKey()));\n }\n\n break;\n }\n }\n}\n\n/**\n * Convert MF2 formatToParts() output to a tree of nodes.\n * Works with any JSX runtime (Astro, React, etc.) - the callbacks\n * return whatever JSX elements the active runtime compiles to.\n *\n * @example\n * MF2 message: \"Read our {#link}Terms{/link}\"\n * Parts: [\n * { type: 'text', value: 'Read our ' },\n * { type: 'markup', kind: 'open', name: 'link' },\n * { type: 'text', value: 'Terms' },\n * { type: 'markup', kind: 'close', name: 'link' },\n * ]\n * Result: ['Read our ', <a href=\"/tos\">Terms</a>]\n */\nexport function partsToNodes<T>(parts: Part[], components: RichComponents<T>): (string | T)[] {\n const result: (string | T)[] = [];\n const stack: StackFrame<T>[] = [];\n let keyIndex = 0;\n const nextKey = () => `rich-${keyIndex++}`;\n\n for (const part of parts) {\n const target = stack.length > 0 ? stack.at(-1)!.children : result;\n\n if (isMarkupPart(part)) {\n applyMarkupPart(part, stack, result, components, nextKey);\n } else if (part.type === 'text') {\n // plain text content - MessageTextPart.value is a string\n target.push(part.value as string);\n } else if (part.type === 'bidiIsolation') {\n // bidi isolation marks are direction control characters, not content\n } else {\n if ('value' in part && part.value !== undefined && part.value !== null) {\n const stringValue = String(part.value);\n\n target.push(stringValue);\n }\n }\n }\n\n // handle unclosed tags by flattening remaining stack frames\n while (stack.length > 0) {\n const frame = stack.pop()!;\n const parent = stack.length > 0 ? stack.at(-1)!.children : result;\n\n parent.push(...frame.children);\n }\n\n return result;\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,MAAiD;CAC7E,OAAO,OAAO,SAAS,WAAW,EAAE,UAAU,KAAK,IAAI;AACzD;;;ACkBA,MAAM,qBAAqB,OAAO,IAAI,eAAe;AACrD,MAAM,kCAAkC,OAAO,IAAI,4BAA4B;;;;;AAW/E,SAAS,eAAe,OAA2C;CACjE,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,EAAE,cAAc,QACjE,OAAO;CAGT,MAAM,OAAQ,MAA2B;CAEzC,OAAO,SAAS,sBAAsB,SAAS;AACjD;;;;;AAMA,SAAS,eAAkB,SAAY,KAAgB;CACrD,IAAI,eAAe,OAAO,KAAK,QAAQ,QAAQ,MAC7C,OAAO;EAAE,GAAG;EAAS;CAAI;CAG3B,OAAO;AACT;AAEA,SAAS,aAAa,MAAuC;CAC3D,OAAO,KAAK,SAAS;AACvB;AAEA,SAAS,gBACP,MACA,OACA,QACA,YACA,SACM;CACN,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW;CAE3D,QAAQ,KAAK,MAAb;EACE,KAAK;GACH,MAAM,KAAK;IAAE,MAAM,KAAK;IAAM,UAAU,CAAC;GAAE,CAAC;GAE5C;EAGF,KAAK,SAAS;GACZ,MAAM,QAAQ,MAAM,IAAI;GAExB,IAAI,CAAC,OAEH;GAGF,MAAM,YAAY,WAAW,MAAM;GACnC,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW;GAE3D,IAAI,WACF,OAAO,KAAK,eAAe,UAAU,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC;QAGhE,OAAO,KAAK,GAAG,MAAM,QAAQ;GAG/B;EACF;EAEA,KAAK,cAAc;GAEjB,MAAM,YAAY,WAAW,KAAK;GAElC,IAAI,WACF,OAAO,KAAK,eAAe,UAAU,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;GAGtD;EACF;CACF;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,aAAgB,OAAe,YAA+C;CAC5F,MAAM,SAAyB,CAAC;CAChC,MAAM,QAAyB,CAAC;CAChC,IAAI,WAAW;CACf,MAAM,gBAAgB,QAAQ;CAE9B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW;EAE3D,IAAI,aAAa,IAAI,GACnB,gBAAgB,MAAM,OAAO,QAAQ,YAAY,OAAO;OACnD,IAAI,KAAK,SAAS,QAEvB,OAAO,KAAK,KAAK,KAAe;OAC3B,IAAI,KAAK,SAAS,iBAAiB,CAE1C,OACE,IAAI,WAAW,QAAQ,KAAK,UAAU,KAAA,KAAa,KAAK,UAAU,MAAM;GACtE,MAAM,cAAc,OAAO,KAAK,KAAK;GAErC,OAAO,KAAK,WAAW;EACzB;CAEJ;CAGA,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,QAAQ,MAAM,IAAI;EAGxB,CAFe,MAAM,SAAS,IAAI,MAAM,GAAG,EAAE,CAAC,CAAE,WAAW,OAAA,CAEpD,KAAK,GAAG,MAAM,QAAQ;CAC/B;CAEA,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { t as createI18nChunkMiddleware } from "../middleware-OnRVbxvs.js";
1
+ import { t as createI18nChunkMiddleware } from "../middleware-SkXWqtl3.js";
2
2
  //#region src/server/chunk-middleware-entrypoint.ts
3
3
  const onRequest = createI18nChunkMiddleware();
4
4
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import { t as RichComponents } from "../rich-BWszYAzS.js";
2
- import { a as TranslationMeta, i as TranslateFunction } from "../types-DU8QHch4.js";
2
+ import { a as TranslationMeta, i as TranslateFunction } from "../types-BjTD5hUT.js";
3
3
  //#region src/server/translate.d.ts
4
4
  /**
5
5
  * Get the current locale from request context.
@@ -23,7 +23,7 @@ export declare const t: TranslateFunction;
23
23
  * @param key - Translation key
24
24
  * @param meta - Fallback string or meta object with fallback (used as message template)
25
25
  * @param components - Map of tag names to component callbacks
26
- * @param values - Runtime interpolation values (ICU MessageFormat)
26
+ * @param values - Runtime interpolation values (MessageFormat 2)
27
27
  *
28
28
  * @example
29
29
  * ```tsx
@@ -1 +1 @@
1
- {"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/server/translate.ts"],"mappings":";;;;;;;wBAiDgB;;;;;;;;qBAYH,GAAG;;;;;;;;;;;;;;;;;;;;wBA8DA,KAAK,aACnB,aACA,OAAO,sCACP,aAAa,eAAe,gBAC5B,SAAS,gDACE"}
1
+ {"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/server/translate.ts"],"mappings":";;;;;;;wBAqDgB;;;;;;;;qBAYH,GAAG;;;;;;;;;;;;;;;;;;;;wBA2DA,KAAK,aACnB,aACA,OAAO,sCACP,aAAa,eAAe,gBAC5B,SAAS,gDACE"}
@@ -1,6 +1,6 @@
1
- import { r as i18n, t as getContext } from "../context-CgqIoo00.js";
1
+ import { r as i18n, t as getContext } from "../context-Cq9-KsBw.js";
2
2
  import { r as formatMessageToParts, t as compileMessage } from "../compiler-CkOVGWU5.js";
3
- import { n as normalizeMeta, t as partsToNodes } from "../rich-CHisXE-A.js";
3
+ import { n as normalizeMeta, t as partsToNodes } from "../rich-BDqjhv2h.js";
4
4
  import { createCounter } from "@astroscope/node/telemetry";
5
5
  import { log } from "@astroscope/node/log";
6
6
  //#region src/server/translate.ts
@@ -76,7 +76,7 @@ const t = ((key, meta, values) => {
76
76
  * @param key - Translation key
77
77
  * @param meta - Fallback string or meta object with fallback (used as message template)
78
78
  * @param components - Map of tag names to component callbacks
79
- * @param values - Runtime interpolation values (ICU MessageFormat)
79
+ * @param values - Runtime interpolation values (MessageFormat 2)
80
80
  *
81
81
  * @example
82
82
  * ```tsx
@@ -1 +1 @@
1
- {"version":3,"file":"translate.js","names":[],"sources":["../../src/server/translate.ts"],"sourcesContent":["import { log } from '@astroscope/node/log';\nimport { createCounter } from '@astroscope/node/telemetry';\nimport { compileMessage, formatMessageToParts } from '../shared/compiler.js';\nimport { normalizeMeta } from '../shared/meta.js';\nimport { type RichComponents, partsToNodes } from '../shared/rich.js';\nimport type { CompiledTranslation, TranslateFunction, TranslationMeta } from '../shared/types.js';\nimport { getContext } from './context.js';\nimport { i18n } from './i18n.js';\nimport type { FallbackBehavior } from './types.js';\n\n// cache for fallbacks compiled outside request context (e.g. during build)\nconst fallbackCache = new Map<string, CompiledTranslation>();\n\nconst missing = createCounter('astro.i18n.missing', {\n scope: '@astroscope/i18n',\n description: 'Translation lookups that fell back (a key once per locale and translations version for t())',\n unit: '{lookup}',\n});\n\n// the key stays out of the metric label (unbounded) and goes to the log instead\nfunction reportMissing(key: string, locale: string): void {\n missing.add(1, { 'astro.i18n.locale': locale });\n log.debug({ key, locale }, 'missing translation');\n}\n\n/**\n * Apply fallback behavior when translation is missing\n */\nfunction applyFallback(key: string, meta: TranslationMeta, fallback: FallbackBehavior): string {\n if (typeof fallback === 'function') {\n return fallback(key, meta);\n }\n\n switch (fallback) {\n case 'key':\n return key;\n case 'fallback':\n return meta.fallback || key;\n case 'throw':\n throw new Error(`Missing translation for key: ${key}`);\n default:\n return meta.fallback || key;\n }\n}\n\n/**\n * Get the current locale from request context.\n * Returns defaultLocale if called outside a request (e.g. during build).\n */\nexport function getLocale(): string {\n const ctx = getContext();\n return ctx?.locale ?? i18n.getConfig().defaultLocale;\n}\n\n/**\n * Server-side translate function\n *\n * Call patterns:\n * - Development: t('key', 'fallback') or t('key', 'fallback', values)\n * - Production: t('key') or t('key', undefined, values)\n */\nexport const t: TranslateFunction = ((\n key: string,\n meta?: TranslationMeta | string,\n values?: Record<string, unknown>,\n): string => {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n\n if (!ctx) {\n // no context = probably during build or outside request\n // use cached compiled fallback to avoid recompiling on every call\n const defaultLocale = i18n.getConfig().defaultLocale;\n const fallbackStr = normalizedMeta.fallback || key;\n const cacheKey = `${defaultLocale}:${fallbackStr}`;\n let compiled = fallbackCache.get(cacheKey);\n\n if (!compiled) {\n compiled = compileMessage(defaultLocale, fallbackStr);\n fallbackCache.set(cacheKey, compiled);\n }\n\n return compiled(values);\n }\n\n const compiled = ctx.translations[key];\n\n if (!compiled) {\n reportMissing(key, ctx.locale);\n\n const fallbackValue = applyFallback(key, normalizedMeta, ctx.fallback);\n\n // compile and cache the fallback for consistency\n const compiledFallback = compileMessage(ctx.locale, fallbackValue);\n\n ctx.translations[key] = compiledFallback;\n\n return compiledFallback(values);\n }\n\n return compiled(values);\n}) as TranslateFunction;\n\n/**\n * Server-side rich text translation function\n *\n * Parses MF2 markup syntax and applies component callbacks to wrap content.\n * Works with any JSX runtime (Astro, React during SSR, etc.).\n *\n * @param key - Translation key\n * @param meta - Fallback string or meta object with fallback (used as message template)\n * @param components - Map of tag names to component callbacks\n * @param values - Runtime interpolation values (ICU MessageFormat)\n *\n * @example\n * ```tsx\n * rich('tos', 'Read our {#link}Terms{/link}', {\n * link: (children) => <a href=\"/tos\">{children}</a>\n * })\n * // Returns: ['Read our ', <a href=\"/tos\">Terms</a>]\n * ```\n */\nexport function rich<T = unknown>(\n key: string,\n meta?: TranslationMeta | string | undefined,\n components?: RichComponents<T> | undefined,\n values?: Record<string, unknown> | undefined,\n): (string | T)[] {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n const locale = ctx?.locale ?? i18n.getConfig().defaultLocale;\n\n const translated = ctx?.rawTranslations[key];\n\n if (ctx && !translated) {\n reportMissing(key, ctx.locale);\n }\n\n const raw = translated || normalizedMeta.fallback || key;\n\n const parts = formatMessageToParts(locale, raw, values);\n\n return partsToNodes(parts, components ?? {});\n}\n"],"mappings":";;;;;;AAWA,MAAM,gCAAgB,IAAI,IAAiC;AAE3D,MAAM,UAAU,cAAc,sBAAsB;CAClD,OAAO;CACP,aAAa;CACb,MAAM;AACR,CAAC;AAGD,SAAS,cAAc,KAAa,QAAsB;CACxD,QAAQ,IAAI,GAAG,EAAE,qBAAqB,OAAO,CAAC;CAC9C,IAAI,MAAM;EAAE;EAAK;CAAO,GAAG,qBAAqB;AAClD;;;;AAKA,SAAS,cAAc,KAAa,MAAuB,UAAoC;CAC7F,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,KAAK,IAAI;CAG3B,QAAQ,UAAR;EACE,KAAK,OACH,OAAO;EACT,KAAK,YACH,OAAO,KAAK,YAAY;EAC1B,KAAK,SACH,MAAM,IAAI,MAAM,gCAAgC,KAAK;EACvD,SACE,OAAO,KAAK,YAAY;CAC5B;AACF;;;;;AAMA,SAAgB,YAAoB;CAElC,OADY,WACH,CAAC,EAAE,UAAU,KAAK,UAAU,CAAC,CAAC;AACzC;;;;;;;;AASA,MAAa,MACX,KACA,MACA,WACW;CACX,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,MAAM,WAAW;CAEvB,IAAI,CAAC,KAAK;EAGR,MAAM,gBAAgB,KAAK,UAAU,CAAC,CAAC;EACvC,MAAM,cAAc,eAAe,YAAY;EAC/C,MAAM,WAAW,GAAG,cAAc,GAAG;EACrC,IAAI,WAAW,cAAc,IAAI,QAAQ;EAEzC,IAAI,CAAC,UAAU;GACb,WAAW,eAAe,eAAe,WAAW;GACpD,cAAc,IAAI,UAAU,QAAQ;EACtC;EAEA,OAAO,SAAS,MAAM;CACxB;CAEA,MAAM,WAAW,IAAI,aAAa;CAElC,IAAI,CAAC,UAAU;EACb,cAAc,KAAK,IAAI,MAAM;EAE7B,MAAM,gBAAgB,cAAc,KAAK,gBAAgB,IAAI,QAAQ;EAGrE,MAAM,mBAAmB,eAAe,IAAI,QAAQ,aAAa;EAEjE,IAAI,aAAa,OAAO;EAExB,OAAO,iBAAiB,MAAM;CAChC;CAEA,OAAO,SAAS,MAAM;AACxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,KACd,KACA,MACA,YACA,QACgB;CAChB,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,MAAM,WAAW;CACvB,MAAM,SAAS,KAAK,UAAU,KAAK,UAAU,CAAC,CAAC;CAE/C,MAAM,aAAa,KAAK,gBAAgB;CAExC,IAAI,OAAO,CAAC,YACV,cAAc,KAAK,IAAI,MAAM;CAG/B,MAAM,MAAM,cAAc,eAAe,YAAY;CAErD,MAAM,QAAQ,qBAAqB,QAAQ,KAAK,MAAM;CAEtD,OAAO,aAAa,OAAO,cAAc,CAAC,CAAC;AAC7C"}
1
+ {"version":3,"file":"translate.js","names":[],"sources":["../../src/server/translate.ts"],"sourcesContent":["import { log } from '@astroscope/node/log';\nimport { createCounter } from '@astroscope/node/telemetry';\nimport { compileMessage, formatMessageToParts } from '../shared/compiler.js';\nimport { normalizeMeta } from '../shared/meta.js';\nimport { type RichComponents, partsToNodes } from '../shared/rich.js';\nimport type { CompiledTranslation, TranslateFunction, TranslationMeta } from '../shared/types.js';\nimport { getContext } from './context.js';\nimport { i18n } from './i18n.js';\nimport type { FallbackBehavior } from './types.js';\n\n// cache for fallbacks compiled outside request context (e.g. during build)\nconst fallbackCache = new Map<string, CompiledTranslation>();\n\nconst missing = createCounter('astro.i18n.missing', {\n scope: '@astroscope/i18n',\n description: 'Translation lookups that fell back (a key once per locale and translations version for t())',\n unit: '{lookup}',\n});\n\n// the key stays out of the metric label (unbounded) and goes to the log instead\nfunction reportMissing(key: string, locale: string): void {\n missing.add(1, { 'astro.i18n.locale': locale });\n log.debug({ key, locale }, 'missing translation');\n}\n\n/**\n * Apply fallback behavior when translation is missing\n */\nfunction applyFallback(key: string, meta: TranslationMeta, fallback: FallbackBehavior): string {\n if (typeof fallback === 'function') {\n return fallback(key, meta);\n }\n\n switch (fallback) {\n case 'key': {\n return key;\n }\n case 'fallback': {\n return meta.fallback || key;\n }\n case 'throw': {\n throw new Error(`Missing translation for key: ${key}`);\n }\n default: {\n return meta.fallback || key;\n }\n }\n}\n\n/**\n * Get the current locale from request context.\n * Returns defaultLocale if called outside a request (e.g. during build).\n */\nexport function getLocale(): string {\n const ctx = getContext();\n return ctx?.locale ?? i18n.getConfig().defaultLocale;\n}\n\n/**\n * Server-side translate function\n *\n * Call patterns:\n * - Development: t('key', 'fallback') or t('key', 'fallback', values)\n * - Production: t('key') or t('key', undefined, values)\n */\nexport const t: TranslateFunction = ((\n key: string,\n meta?: TranslationMeta | string,\n values?: Record<string, unknown>,\n): string => {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n\n if (!ctx) {\n const defaultLocale = i18n.getConfig().defaultLocale;\n const fallbackStr = normalizedMeta.fallback || key;\n const cacheKey = `${defaultLocale}:${fallbackStr}`;\n let compiled = fallbackCache.get(cacheKey);\n\n if (!compiled) {\n compiled = compileMessage(defaultLocale, fallbackStr);\n fallbackCache.set(cacheKey, compiled);\n }\n\n return compiled(values);\n }\n\n const compiled = ctx.translations[key];\n\n if (!compiled) {\n reportMissing(key, ctx.locale);\n\n const fallbackValue = applyFallback(key, normalizedMeta, ctx.fallback);\n\n const compiledFallback = compileMessage(ctx.locale, fallbackValue);\n\n ctx.translations[key] = compiledFallback;\n\n return compiledFallback(values);\n }\n\n return compiled(values);\n}) as TranslateFunction;\n\n/**\n * Server-side rich text translation function\n *\n * Parses MF2 markup syntax and applies component callbacks to wrap content.\n * Works with any JSX runtime (Astro, React during SSR, etc.).\n *\n * @param key - Translation key\n * @param meta - Fallback string or meta object with fallback (used as message template)\n * @param components - Map of tag names to component callbacks\n * @param values - Runtime interpolation values (MessageFormat 2)\n *\n * @example\n * ```tsx\n * rich('tos', 'Read our {#link}Terms{/link}', {\n * link: (children) => <a href=\"/tos\">{children}</a>\n * })\n * // Returns: ['Read our ', <a href=\"/tos\">Terms</a>]\n * ```\n */\nexport function rich<T = unknown>(\n key: string,\n meta?: TranslationMeta | string | undefined,\n components?: RichComponents<T> | undefined,\n values?: Record<string, unknown> | undefined,\n): (string | T)[] {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const ctx = getContext();\n const locale = ctx?.locale ?? i18n.getConfig().defaultLocale;\n\n const translated = ctx?.rawTranslations[key];\n\n if (ctx && !translated) {\n reportMissing(key, ctx.locale);\n }\n\n const raw = translated || normalizedMeta.fallback || key;\n\n const parts = formatMessageToParts(locale, raw, values);\n\n return partsToNodes(parts, components ?? {});\n}\n"],"mappings":";;;;;;AAWA,MAAM,gCAAgB,IAAI,IAAiC;AAE3D,MAAM,UAAU,cAAc,sBAAsB;CAClD,OAAO;CACP,aAAa;CACb,MAAM;AACR,CAAC;AAGD,SAAS,cAAc,KAAa,QAAsB;CACxD,QAAQ,IAAI,GAAG,EAAE,qBAAqB,OAAO,CAAC;CAC9C,IAAI,MAAM;EAAE;EAAK;CAAO,GAAG,qBAAqB;AAClD;;;;AAKA,SAAS,cAAc,KAAa,MAAuB,UAAoC;CAC7F,IAAI,OAAO,aAAa,YACtB,OAAO,SAAS,KAAK,IAAI;CAG3B,QAAQ,UAAR;EACE,KAAK,OACH,OAAO;EAET,KAAK,YACH,OAAO,KAAK,YAAY;EAE1B,KAAK,SACH,MAAM,IAAI,MAAM,gCAAgC,KAAK;EAEvD,SACE,OAAO,KAAK,YAAY;CAE5B;AACF;;;;;AAMA,SAAgB,YAAoB;CAElC,OADY,WACH,CAAC,EAAE,UAAU,KAAK,UAAU,CAAC,CAAC;AACzC;;;;;;;;AASA,MAAa,MACX,KACA,MACA,WACW;CACX,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,MAAM,WAAW;CAEvB,IAAI,CAAC,KAAK;EACR,MAAM,gBAAgB,KAAK,UAAU,CAAC,CAAC;EACvC,MAAM,cAAc,eAAe,YAAY;EAC/C,MAAM,WAAW,GAAG,cAAc,GAAG;EACrC,IAAI,WAAW,cAAc,IAAI,QAAQ;EAEzC,IAAI,CAAC,UAAU;GACb,WAAW,eAAe,eAAe,WAAW;GACpD,cAAc,IAAI,UAAU,QAAQ;EACtC;EAEA,OAAO,SAAS,MAAM;CACxB;CAEA,MAAM,WAAW,IAAI,aAAa;CAElC,IAAI,CAAC,UAAU;EACb,cAAc,KAAK,IAAI,MAAM;EAE7B,MAAM,gBAAgB,cAAc,KAAK,gBAAgB,IAAI,QAAQ;EAErE,MAAM,mBAAmB,eAAe,IAAI,QAAQ,aAAa;EAEjE,IAAI,aAAa,OAAO;EAExB,OAAO,iBAAiB,MAAM;CAChC;CAEA,OAAO,SAAS,MAAM;AACxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,KACd,KACA,MACA,YACA,QACgB;CAChB,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,MAAM,WAAW;CACvB,MAAM,SAAS,KAAK,UAAU,KAAK,UAAU,CAAC,CAAC;CAE/C,MAAM,aAAa,KAAK,gBAAgB;CAExC,IAAI,OAAO,CAAC,YACV,cAAc,KAAK,IAAI,MAAM;CAG/B,MAAM,MAAM,cAAc,eAAe,YAAY;CAErD,MAAM,QAAQ,qBAAqB,QAAQ,KAAK,MAAM;CAEtD,OAAO,aAAa,OAAO,cAAc,CAAC,CAAC;AAC7C"}
@@ -1,4 +1,4 @@
1
- import { a as TranslationMeta } from "./types-DU8QHch4.js";
1
+ import { a as TranslationMeta } from "./types-BjTD5hUT.js";
2
2
  //#region src/extraction/types.d.ts
3
3
  /**
4
4
  * Deduplicated translation key with all file locations (used in manifest)
@@ -28,4 +28,4 @@ type ExtractionManifest = {
28
28
  };
29
29
  //#endregion
30
30
  export { ExtractedKey as n, ExtractionManifest as r, ChunkManifest as t };
31
- //# sourceMappingURL=types-f-SNJdvf.d.ts.map
31
+ //# sourceMappingURL=types-BC-tQ30r.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-f-SNJdvf.d.ts","names":[],"sources":["../src/extraction/types.ts"],"mappings":";;;;;KA8BY;EACV;EACA,MAAM;;EAEN;;;;;KAMU,gBAAgB;;;;KAKhB;EACV,MAAM;EACN,QAAQ;;;;;;EAMR"}
1
+ {"version":3,"file":"types-BC-tQ30r.d.ts","names":[],"sources":["../src/extraction/types.ts"],"mappings":";;;;;KA8BY;EACV;EACA,MAAM;;EAEN;;;;;KAMU,gBAAgB;;;;KAKhB;EACV,MAAM;EACN,QAAQ;;;;;;EAMR"}
@@ -41,14 +41,16 @@ type I18nClientState = {
41
41
  *
42
42
  * @param key - translation key
43
43
  * @param meta - fallback string or meta object with fallback
44
- * @param values - runtime interpolation values (ICU MessageFormat)
44
+ * @param values - runtime interpolation values (MessageFormat 2)
45
45
  *
46
46
  * @example
47
47
  * t('checkout.title', 'Order Summary')
48
- * t('cart.items', '{count, plural, one {# item} other {# items}}', { count: itemCount })
49
- * t('cart.total', { fallback: 'Total: {amount}' }, { amount: '$49.99' })
48
+ * t('cart.items', '.input {$count :number}\n.match $count\none {{{$count} item}}\n* {{{$count} items}}', {
49
+ * count: itemCount,
50
+ * })
51
+ * t('cart.total', { fallback: 'Total: {$amount}' }, { amount: '$49.99' })
50
52
  */
51
53
  type TranslateFunction = (key: string, meta: TranslationMeta | string, values?: Record<string, unknown> | undefined) => string;
52
54
  //#endregion
53
55
  export { TranslationMeta as a, TranslateFunction as i, I18nClientState as n, RawTranslations as r, CompiledTranslations as t };
54
- //# sourceMappingURL=types-DU8QHch4.d.ts.map
56
+ //# sourceMappingURL=types-BjTD5hUT.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-DU8QHch4.d.ts","names":[],"sources":["../src/shared/types.ts"],"mappings":";;;;KAGY;EACV;EACA;;;;;KAMU;EACV;EACA,YAAY,eAAe;EAC3B;;;;;KAMU,uBAAuB,SAAS;;;;KAKhC,uBAAuB,eAAe;;;;KAKtC,kBAAkB;;;;;;KAOlB;EACV;EACA,QAAQ;EACR,cAAc;;;;;;;;;;;;;;KAeJ,qBACV,aACA,MAAM,0BACN,SAAS"}
1
+ {"version":3,"file":"types-BjTD5hUT.d.ts","names":[],"sources":["../src/shared/types.ts"],"mappings":";;;;KAGY;EACV;EACA;;;;;KAMU;EACV;EACA,YAAY,eAAe;EAC3B;;;;;KAMU,uBAAuB,SAAS;;;;KAKhC,uBAAuB,eAAe;;;;KAKtC,kBAAkB;;;;;;KAOlB;EACV;EACA,QAAQ;EACR,cAAc;;;;;;;;;;;;;;;;KAiBJ,qBACV,aACA,MAAM,0BACN,SAAS"}
@@ -1,5 +1,5 @@
1
1
  import { n as chunkIdToName } from "./url-CifeaDzl.js";
2
- import { n as getManifest, t as getGlobalState } from "./manifest-Cma68XM-.js";
2
+ import { n as getManifest, t as getGlobalState } from "./manifest-X-UhW6tH.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import MagicString from "magic-string";
@@ -24,7 +24,7 @@ async function extractKeysFromFile(options) {
24
24
  const { filename, code, stripFallbacks = false, onKeyExtracted } = options;
25
25
  const keys = [];
26
26
  const errors = [];
27
- const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([import("@babel/core"), import("./babel-plugin-CWafmw5z.js")]);
27
+ const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([import("@babel/core"), import("./babel-plugin-DJZS9L_W.js")]);
28
28
  const isTypeScript = TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) || filename.endsWith(".astro");
29
29
  const isJSX = JSX_EXTENSIONS.some((ext) => filename.endsWith(ext));
30
30
  const result = await transformAsync(code, {
@@ -75,47 +75,6 @@ var KeyStore = class {
75
75
  this.consistency = consistency;
76
76
  }
77
77
  /**
78
- * Add key occurrences for a file, replacing any existing occurrences for that file.
79
- */
80
- addFileKeys(filename, keys) {
81
- const newKeyStrings = keys.map((k) => k.key);
82
- const oldKeys = this.fileToKeys.get(filename);
83
- if (oldKeys) {
84
- const uniqueNew = [...new Set(newKeyStrings)];
85
- const uniqueOld = [...new Set(oldKeys)];
86
- const added = uniqueNew.filter((k) => !uniqueOld.includes(k));
87
- const removed = uniqueOld.filter((k) => !uniqueNew.includes(k));
88
- const file = filename.split("/").pop();
89
- if (added.length > 0 || removed.length > 0) {
90
- const parts = [];
91
- if (added.length > 0) parts.push(`+${added.length}`);
92
- if (removed.length > 0) parts.push(`-${removed.length}`);
93
- this.logger.info(`hmr: ${parts.join(" ")} key(s) in ${file}`);
94
- }
95
- }
96
- if (oldKeys) {
97
- for (let i = this.occurrences.length - 1; i >= 0; i--) if (this.occurrences[i]?.file === filename) this.occurrences.splice(i, 1);
98
- }
99
- this.filesWithI18n.add(filename);
100
- if (this.consistency !== "off") this.checkConsistency(keys);
101
- if (keys.length > 0) this.occurrences.push(...keys);
102
- this.fileToKeys.set(filename, newKeyStrings);
103
- }
104
- /**
105
- * Record the non-extractable t() calls found in a file, replacing any
106
- * previously recorded for it.
107
- */
108
- addFileErrors(filename, errors) {
109
- if (errors.length > 0) this.errorsByFile.set(filename, errors);
110
- else this.errorsByFile.delete(filename);
111
- }
112
- /**
113
- * All non-extractable t() calls found so far, across every scanned file.
114
- */
115
- get extractionErrors() {
116
- return Array.from(this.errorsByFile.values()).flat();
117
- }
118
- /**
119
78
  * Check new keys for inconsistencies with existing occurrences.
120
79
  */
121
80
  checkConsistency(newKeys) {
@@ -163,7 +122,8 @@ var KeyStore = class {
163
122
  serializeVariables(meta) {
164
123
  if (!meta.variables) return "";
165
124
  const sorted = {};
166
- for (const key of Object.keys(meta.variables).sort()) sorted[key] = meta.variables[key];
125
+ const names = Object.keys(meta.variables).toSorted((a, b) => a.localeCompare(b));
126
+ for (const key of names) sorted[key] = meta.variables[key];
167
127
  return JSON.stringify(sorted);
168
128
  }
169
129
  /**
@@ -180,6 +140,47 @@ var KeyStore = class {
180
140
  } else this.logger.warn(message);
181
141
  }
182
142
  /**
143
+ * Add key occurrences for a file, replacing any existing occurrences for that file.
144
+ */
145
+ addFileKeys(filename, keys) {
146
+ const newKeyStrings = keys.map((k) => k.key);
147
+ const oldKeys = this.fileToKeys.get(filename);
148
+ if (oldKeys) {
149
+ const uniqueNew = [...new Set(newKeyStrings)];
150
+ const uniqueOld = [...new Set(oldKeys)];
151
+ const added = uniqueNew.filter((k) => !uniqueOld.includes(k));
152
+ const removed = uniqueOld.filter((k) => !uniqueNew.includes(k));
153
+ const file = filename.split("/").pop();
154
+ if (added.length > 0 || removed.length > 0) {
155
+ const parts = [];
156
+ if (added.length > 0) parts.push(`+${added.length}`);
157
+ if (removed.length > 0) parts.push(`-${removed.length}`);
158
+ this.logger.info(`hmr: ${parts.join(" ")} key(s) in ${file}`);
159
+ }
160
+ }
161
+ if (oldKeys) {
162
+ for (let i = this.occurrences.length - 1; i >= 0; i--) if (this.occurrences[i]?.file === filename) this.occurrences.splice(i, 1);
163
+ }
164
+ this.filesWithI18n.add(filename);
165
+ if (this.consistency !== "off") this.checkConsistency(keys);
166
+ if (keys.length > 0) this.occurrences.push(...keys);
167
+ this.fileToKeys.set(filename, newKeyStrings);
168
+ }
169
+ /**
170
+ * Record the non-extractable t() calls found in a file, replacing any
171
+ * previously recorded for it.
172
+ */
173
+ addFileErrors(filename, errors) {
174
+ if (errors.length > 0) this.errorsByFile.set(filename, errors);
175
+ else this.errorsByFile.delete(filename);
176
+ }
177
+ /**
178
+ * All non-extractable t() calls found so far, across every scanned file.
179
+ */
180
+ get extractionErrors() {
181
+ return this.errorsByFile.values().toArray().flat();
182
+ }
183
+ /**
183
184
  * Check if any error-level inconsistencies were found.
184
185
  * Call this at the end of build to determine if it should fail.
185
186
  */
@@ -198,13 +199,16 @@ var KeyStore = class {
198
199
  if (existing) {
199
200
  if (!existing.files.includes(fileLocation)) existing.files.push(fileLocation);
200
201
  existing.meta = occurrence.meta;
201
- } else keyMap.set(occurrence.key, {
202
- key: occurrence.key,
203
- meta: occurrence.meta,
204
- files: [fileLocation]
205
- });
202
+ } else {
203
+ const key = {
204
+ key: occurrence.key,
205
+ meta: occurrence.meta,
206
+ files: [fileLocation]
207
+ };
208
+ keyMap.set(occurrence.key, key);
209
+ }
206
210
  }
207
- return Array.from(keyMap.values());
211
+ return keyMap.values().toArray();
208
212
  }
209
213
  /**
210
214
  * Get count of unique keys.
@@ -239,7 +243,7 @@ var KeyStore = class {
239
243
  * cannot be mapped keep the position babel gave them.
240
244
  */
241
245
  function mapErrorsToSource(errors, map) {
242
- if (errors.length === 0 || !map) return errors;
246
+ if (!map || errors.length === 0) return errors;
243
247
  let tracer;
244
248
  try {
245
249
  tracer = new TraceMap(map);
@@ -290,12 +294,13 @@ async function scan(options) {
290
294
  const { projectRoot, logger, consistency } = options;
291
295
  const store = new KeyStore(logger, consistency);
292
296
  const files = [];
293
- for await (const entry of fs.promises.glob(GLOB_PATTERN, {
297
+ const entries = fs.promises.glob(GLOB_PATTERN, {
294
298
  cwd: projectRoot,
295
299
  exclude: (name) => name === "node_modules"
296
- })) files.push(path.resolve(projectRoot, entry));
300
+ });
301
+ for await (const entry of entries) files.push(path.resolve(projectRoot, entry));
297
302
  const results = await Promise.all(files.map(async (file) => {
298
- let code = await fs.promises.readFile(file, "utf-8");
303
+ let code = await fs.promises.readFile(file, "utf8");
299
304
  if (!code.includes("@astroscope/i18n/translate")) return {
300
305
  file,
301
306
  keys: null,
@@ -349,7 +354,7 @@ function formatExtractionError(error, projectRoot) {
349
354
  */
350
355
  function formatExtractionErrors(errors, projectRoot) {
351
356
  const list = errors.map((e) => ` - ${formatExtractionError(e, projectRoot)}`).join("\n");
352
- return `i18n: ${errors.length} t() call(s) have a fallback that cannot be read at build time.\n\n${list}\n\nProduction builds strip the fallback argument, so these keys would render as their own key string. Fallbacks must be static string literals — pass runtime values as MF2 variables instead:\n t('some.key', 'Hello {$name}', { name })`;
357
+ return `i18n: ${errors.length} t() call(s) have a fallback that cannot be read at build time.\n\n${list}\n\nProduction builds strip the fallback argument, so these keys would render as their own key string. Fallbacks must be static string literals — pass runtime values as MF2 variables instead:\n t('some.key', 'Hello {\$name}', { name })`;
353
358
  }
354
359
  /**
355
360
  * Vite plugin for i18n extraction, chunk mapping, and loader injection
@@ -381,7 +386,7 @@ function i18nVitePlugin(options) {
381
386
  projectRoot = config.root;
382
387
  },
383
388
  async configureServer(server) {
384
- if (server.config.isProduction || isBuild) return;
389
+ if (isBuild || server.config.isProduction) return;
385
390
  const result = await scan({
386
391
  projectRoot,
387
392
  logger,
@@ -396,8 +401,8 @@ function i18nVitePlugin(options) {
396
401
  if (isVirtualModuleId(id)) return `\0${id}`;
397
402
  },
398
403
  load(id) {
399
- if (isResolvedVirtualModuleId(id)) {
400
- if (isBuild) return `
404
+ if (!isResolvedVirtualModuleId(id)) return;
405
+ if (isBuild) return `
401
406
  import { readFileSync } from 'node:fs';
402
407
  import { fileURLToPath } from 'node:url';
403
408
  import { dirname, join } from 'node:path';
@@ -408,15 +413,14 @@ const manifestJson = JSON.parse(readFileSync(join(__dirname, '${MANIFEST_FILE_NA
408
413
  export const manifest = manifestJson;
409
414
  export function getManifest() { return manifestJson; }
410
415
  `;
411
- return `
416
+ return `
412
417
  import { getManifest as _getManifest } from '@astroscope/i18n/extraction';
413
418
  export const manifest = { keys: [], chunks: {}, scripts: [] };
414
419
  export function getManifest() { return _getManifest(); }
415
420
  `;
416
- }
417
421
  },
418
422
  async transform(code, filename) {
419
- if (!ALL_EXTENSIONS.some((ext) => filename.endsWith(ext))) return null;
423
+ if (ALL_EXTENSIONS.every((ext) => !filename.endsWith(ext))) return null;
420
424
  if (filename.includes("node_modules")) return null;
421
425
  if (!code.includes("@astroscope/i18n/translate")) return null;
422
426
  const result = await extractKeysFromFile({
@@ -443,7 +447,8 @@ export function getManifest() { return _getManifest(); }
443
447
  const chunkNameToFileName = /* @__PURE__ */ new Map();
444
448
  const directImports = /* @__PURE__ */ new Map();
445
449
  const scriptEntries = [];
446
- for (const [fileName, chunk] of Object.entries(bundle)) if (chunk.type === "chunk" && chunk.moduleIds) {
450
+ for (const [fileName, chunk] of Object.entries(bundle)) {
451
+ if (!(chunk.type === "chunk" && chunk.moduleIds)) continue;
447
452
  const keys = /* @__PURE__ */ new Set();
448
453
  let hasI18nModule = false;
449
454
  for (const moduleId of chunk.moduleIds) {
@@ -451,7 +456,7 @@ export function getManifest() { return _getManifest(); }
451
456
  if (store.filesWithI18n.has(moduleId)) hasI18nModule = true;
452
457
  }
453
458
  const chunkName = chunkIdToName(fileName.replace(/\.js$/, ""));
454
- if (keys.size) state.chunkManifest[chunkName] = Array.from(keys);
459
+ if (keys.size > 0) state.chunkManifest[chunkName] = [...keys];
455
460
  if (hasI18nModule) chunksWithI18n.add(chunkName);
456
461
  chunkNameToFileName.set(chunkName, fileName);
457
462
  const imports = [];
@@ -466,8 +471,9 @@ export function getManifest() { return _getManifest(); }
466
471
  const walkScripts = (chunkName, visited) => {
467
472
  if (visited.has(chunkName)) return;
468
473
  visited.add(chunkName);
469
- if (state.chunkManifest[chunkName]) scriptChunks.add(chunkName);
470
- for (const imported of directImports.get(chunkName) ?? []) walkScripts(imported, visited);
474
+ if (Object.hasOwn(state.chunkManifest, chunkName)) scriptChunks.add(chunkName);
475
+ const imports = directImports.get(chunkName) ?? [];
476
+ for (const imported of imports) walkScripts(imported, visited);
471
477
  };
472
478
  const visited = /* @__PURE__ */ new Set();
473
479
  for (const entry of scriptEntries) walkScripts(entry, visited);
@@ -488,16 +494,15 @@ export function getManifest() { return _getManifest(); }
488
494
  if (consistency === "error" && store.hasErrors) throw new Error("i18n: build failed due to translation key inconsistencies");
489
495
  },
490
496
  writeBundle(outputOptions) {
491
- if (this.environment.name === "client" && outputOptions.dir) {
492
- const chunksDir = path.resolve(outputOptions.dir, "..", "server", "chunks");
493
- const manifestPath = path.join(chunksDir, MANIFEST_FILE_NAME);
494
- if (fs.existsSync(chunksDir)) fs.writeFileSync(manifestPath, JSON.stringify(getManifest()));
495
- else logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);
496
- }
497
+ if (!(this.environment.name === "client" && outputOptions.dir)) return;
498
+ const chunksDir = path.resolve(outputOptions.dir, "..", "server", "chunks");
499
+ const manifestPath = path.join(chunksDir, MANIFEST_FILE_NAME);
500
+ if (fs.existsSync(chunksDir)) fs.writeFileSync(manifestPath, JSON.stringify(getManifest()));
501
+ else logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);
497
502
  }
498
503
  };
499
504
  }
500
505
  //#endregion
501
506
  export { i18nVitePlugin as t };
502
507
 
503
- //# sourceMappingURL=vite-plugin-C-jEpd8t.js.map
508
+ //# sourceMappingURL=vite-plugin-BPYFFBQp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin-BPYFFBQp.js","names":[],"sources":["../src/extraction/extract.ts","../src/extraction/key-store.ts","../src/extraction/source-map.ts","../src/extraction/scan.ts","../src/extraction/vite-plugin.ts"],"sourcesContent":["import type { FileResult, PluginItem } from '@babel/core';\nimport type { ExtractedKeyOccurrence, ExtractionError } from './types.js';\n\n/** TypeScript extensions (need 'typescript' babel plugin) */\nexport const TS_EXTENSIONS = ['.ts', '.tsx'] as const;\n\n/** JSX extensions (need 'jsx' babel plugin) */\nexport const JSX_EXTENSIONS = ['.jsx', '.tsx'] as const;\n\n/** Plain JS extensions */\nexport const JS_EXTENSIONS = ['.js', '.jsx'] as const;\n\n/**\n * Extensions that can be parsed directly by Babel (raw source files).\n * Used for eager scanning before Vite processes them.\n */\nexport const BABEL_EXTENSIONS = [...TS_EXTENSIONS, ...JS_EXTENSIONS] as const;\n\n/**\n * All extensions that may contain t() calls.\n * Includes .astro which requires Astro's compiler before Babel can parse it.\n */\nexport const ALL_EXTENSIONS = [...BABEL_EXTENSIONS, '.astro'] as const;\n\nexport type ExtractOptions = {\n filename: string;\n code: string;\n stripFallbacks?: boolean | undefined;\n onKeyExtracted?: ((key: ExtractedKeyOccurrence) => void) | undefined;\n};\n\nexport type ExtractResult = {\n keys: ExtractedKeyOccurrence[];\n /** t() calls whose meta could not be read statically */\n errors: ExtractionError[];\n code: string | null;\n map: FileResult['map'];\n};\n\n/**\n * Extract keys from a single file using Babel.\n * Optionally strips fallbacks (for production builds).\n */\nexport async function extractKeysFromFile(options: ExtractOptions): Promise<ExtractResult> {\n const { filename, code, stripFallbacks = false, onKeyExtracted } = options;\n\n const keys: ExtractedKeyOccurrence[] = [];\n const errors: ExtractionError[] = [];\n\n // dynamic imports prevent Babel from being bundled into server runtime\n const [{ transformAsync }, { i18nExtractPlugin }] = await Promise.all([\n import('@babel/core'),\n import('./babel-plugin.js'),\n ]);\n\n // treat .astro files as TypeScript (otherwise type imports fail)\n const isTypeScript = TS_EXTENSIONS.some((ext) => filename.endsWith(ext)) || filename.endsWith('.astro');\n const isJSX = JSX_EXTENSIONS.some((ext) => filename.endsWith(ext));\n\n const result = await transformAsync(code, {\n filename,\n sourceMaps: stripFallbacks, // only need source maps when transforming\n babelrc: false,\n configFile: false,\n parserOpts: {\n plugins: [...(isTypeScript ? ['typescript' as const] : []), ...(isJSX ? ['jsx' as const] : [])],\n },\n plugins: [\n [\n i18nExtractPlugin,\n {\n stripFallbacks,\n onKeyExtracted: (key: ExtractedKeyOccurrence) => {\n keys.push(key);\n onKeyExtracted?.(key);\n },\n onExtractionError: (error: ExtractionError) => {\n errors.push(error);\n },\n },\n ] as PluginItem,\n ],\n });\n\n return {\n keys,\n errors,\n code: result?.code ?? null,\n map: result?.map ?? null,\n };\n}\n","import type { AstroIntegrationLogger } from 'astro';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport type { TranslationMeta } from '../shared/types.js';\nimport type { ExtractedKey, ExtractedKeyOccurrence, ExtractionError } from './types.js';\n\n/**\n * Inconsistency between two occurrences of the same translation key\n */\nexport type KeyInconsistency = {\n key: string;\n field: 'fallback' | 'description' | 'variables';\n locations: [string, string];\n values: [string, string];\n};\n\n/**\n * Store for extracted i18n keys.\n * Stores occurrences and produces deduplicated keys with all file locations.\n */\nexport class KeyStore {\n /** All key occurrences (may have duplicates) */\n private readonly occurrences: ExtractedKeyOccurrence[] = [];\n\n /** Tracks reported inconsistencies to avoid duplicate warnings */\n private readonly reportedInconsistencies = new Set<string>();\n\n /** Whether an error-level inconsistency was found (for build failure) */\n private hasInconsistencyError = false;\n\n /** Non-extractable t() meta, per file (replaced wholesale on HMR) */\n private readonly errorsByFile = new Map<string, ExtractionError[]>();\n\n readonly fileToKeys = new Map<string, string[]>();\n readonly filesWithI18n = new Set<string>();\n\n constructor(\n private readonly logger: AstroIntegrationLogger,\n private readonly consistency: ConsistencyCheckLevel = 'error',\n ) {}\n\n /**\n * Check new keys for inconsistencies with existing occurrences.\n */\n private checkConsistency(newKeys: ExtractedKeyOccurrence[]): void {\n const existingByKey = new Map<string, ExtractedKeyOccurrence>();\n\n for (const occ of this.occurrences) {\n if (!existingByKey.has(occ.key)) {\n existingByKey.set(occ.key, occ);\n }\n }\n\n for (const newKey of newKeys) {\n const existing = existingByKey.get(newKey.key);\n\n if (!existing) continue;\n\n const inconsistencies = this.findInconsistencies(newKey, existing);\n\n for (const inc of inconsistencies) {\n this.reportInconsistency(inc);\n }\n }\n }\n\n /**\n * Compare two occurrences and find any metadata inconsistencies.\n */\n private findInconsistencies(a: ExtractedKeyOccurrence, b: ExtractedKeyOccurrence): KeyInconsistency[] {\n const result: KeyInconsistency[] = [];\n const locA = `${a.file}:${a.line}`;\n const locB = `${b.file}:${b.line}`;\n\n if (a.meta.fallback !== b.meta.fallback) {\n result.push({\n key: a.key,\n field: 'fallback',\n locations: [locA, locB],\n values: [a.meta.fallback, b.meta.fallback],\n });\n }\n\n if (a.meta.description !== b.meta.description) {\n result.push({\n key: a.key,\n field: 'description',\n locations: [locA, locB],\n values: [a.meta.description ?? '(none)', b.meta.description ?? '(none)'],\n });\n }\n\n const varsA = this.serializeVariables(a.meta);\n const varsB = this.serializeVariables(b.meta);\n\n if (varsA !== varsB) {\n result.push({\n key: a.key,\n field: 'variables',\n locations: [locA, locB],\n values: [varsA || '(none)', varsB || '(none)'],\n });\n }\n\n return result;\n }\n\n /**\n * Serialize variables for comparison.\n */\n private serializeVariables(meta: TranslationMeta): string {\n if (!meta.variables) return '';\n\n // sort keys for consistent comparison\n const sorted: Record<string, unknown> = {};\n const names = Object.keys(meta.variables).toSorted((a, b) => a.localeCompare(b));\n\n for (const key of names) {\n sorted[key] = meta.variables[key];\n }\n\n return JSON.stringify(sorted);\n }\n\n /**\n * Report an inconsistency via logger.\n */\n private reportInconsistency(inc: KeyInconsistency): void {\n // keyed on key+field, not locations — one report per inconsistent key\n const incKey = `${inc.key}:${inc.field}`;\n\n if (this.reportedInconsistencies.has(incKey)) return;\n\n this.reportedInconsistencies.add(incKey);\n\n const message =\n `inconsistent ${inc.field} for key \"${inc.key}\"\\n` +\n ` ${inc.locations[0]}: ${JSON.stringify(inc.values[0])}\\n` +\n ` ${inc.locations[1]}: ${JSON.stringify(inc.values[1])}`;\n\n if (this.consistency === 'error') {\n this.logger.error(message);\n this.hasInconsistencyError = true;\n } else {\n this.logger.warn(message);\n }\n }\n\n /**\n * Add key occurrences for a file, replacing any existing occurrences for that file.\n */\n addFileKeys(filename: string, keys: ExtractedKeyOccurrence[]): void {\n const newKeyStrings = keys.map((k) => k.key);\n const oldKeys = this.fileToKeys.get(filename);\n\n if (oldKeys) {\n const uniqueNew = [...new Set(newKeyStrings)];\n const uniqueOld = [...new Set(oldKeys)];\n const added = uniqueNew.filter((k) => !uniqueOld.includes(k));\n const removed = uniqueOld.filter((k) => !uniqueNew.includes(k));\n const file = filename.split('/').pop();\n\n if (added.length > 0 || removed.length > 0) {\n const parts: string[] = [];\n\n if (added.length > 0) parts.push(`+${added.length}`);\n if (removed.length > 0) parts.push(`-${removed.length}`);\n\n this.logger.info(`hmr: ${parts.join(' ')} key(s) in ${file}`);\n }\n }\n\n if (oldKeys) {\n for (let i = this.occurrences.length - 1; i >= 0; i--) {\n if (this.occurrences[i]?.file === filename) {\n this.occurrences.splice(i, 1);\n }\n }\n }\n\n this.filesWithI18n.add(filename);\n\n // check for inconsistencies before adding new keys\n if (this.consistency !== 'off') {\n this.checkConsistency(keys);\n }\n\n if (keys.length > 0) {\n this.occurrences.push(...keys);\n }\n\n this.fileToKeys.set(filename, newKeyStrings);\n }\n\n /**\n * Record the non-extractable t() calls found in a file, replacing any\n * previously recorded for it.\n */\n addFileErrors(filename: string, errors: ExtractionError[]): void {\n if (errors.length > 0) {\n this.errorsByFile.set(filename, errors);\n } else {\n this.errorsByFile.delete(filename);\n }\n }\n\n /**\n * All non-extractable t() calls found so far, across every scanned file.\n */\n get extractionErrors(): ExtractionError[] {\n return this.errorsByFile.values().toArray().flat();\n }\n\n /**\n * Check if any error-level inconsistencies were found.\n * Call this at the end of build to determine if it should fail.\n */\n get hasErrors(): boolean {\n return this.hasInconsistencyError;\n }\n\n /**\n * Get deduplicated keys with all file locations merged.\n * Last occurrence's meta wins for each key.\n */\n get extractedKeys(): ExtractedKey[] {\n const keyMap = new Map<string, ExtractedKey>();\n\n for (const occurrence of this.occurrences) {\n const existing = keyMap.get(occurrence.key);\n const fileLocation = `${occurrence.file}:${occurrence.line}`;\n\n if (existing) {\n if (!existing.files.includes(fileLocation)) {\n existing.files.push(fileLocation);\n }\n\n existing.meta = occurrence.meta;\n } else {\n const key: ExtractedKey = {\n key: occurrence.key,\n meta: occurrence.meta,\n files: [fileLocation],\n };\n\n keyMap.set(occurrence.key, key);\n }\n }\n\n return keyMap.values().toArray();\n }\n\n /**\n * Get count of unique keys.\n */\n get uniqueKeyCount(): number {\n return new Set(this.occurrences.map((k) => k.key)).size;\n }\n\n /**\n * Merge another store's data into this one.\n */\n merge(other: KeyStore): void {\n for (const [file, keys] of other.fileToKeys) {\n this.fileToKeys.set(file, keys);\n }\n\n for (const [file, errors] of other.errorsByFile) {\n this.errorsByFile.set(file, errors);\n }\n\n for (const file of other.filesWithI18n) {\n this.filesWithI18n.add(file);\n }\n\n if (this.consistency !== 'off') {\n this.checkConsistency(other.occurrences);\n }\n\n this.occurrences.push(...other.occurrences);\n }\n\n /**\n * Internal access to occurrences (for merge)\n */\n get occurrencesList(): ExtractedKeyOccurrence[] {\n return this.occurrences;\n }\n}\n","import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping';\nimport type { ExtractionError } from './types.js';\n\n/**\n * Rewrite compiled-output positions back to the authored source.\n *\n * Babel parses .astro files after Astro's compiler has rewritten them, so the\n * positions it reports refer to code the developer never wrote. Errors that\n * cannot be mapped keep the position babel gave them.\n */\nexport function mapErrorsToSource(errors: ExtractionError[], map: unknown): ExtractionError[] {\n if (!map || errors.length === 0) {\n return errors;\n }\n\n let tracer: TraceMap;\n\n try {\n tracer = new TraceMap(map as never);\n } catch {\n return errors;\n }\n\n return errors.map((error) => {\n const position = originalPositionFor(tracer, { line: error.line, column: error.column });\n\n if (position.line === null) {\n return error;\n }\n\n return { ...error, line: position.line, column: position.column ?? error.column };\n });\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AstroIntegrationLogger } from 'astro';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport { ALL_EXTENSIONS, extractKeysFromFile } from './extract.js';\nimport { KeyStore } from './key-store.js';\nimport { mapErrorsToSource } from './source-map.js';\n\nconst GLOB_PATTERN = `src/**/*.{${ALL_EXTENSIONS.map((e) => e.slice(1)).join(',')}}`;\n\n/**\n * Compile .astro file to JS using Astro's compiler.\n * Returns the compiled code and its source map, or null if compilation fails.\n */\nasync function compileAstro(code: string, filename: string): Promise<{ code: string; map: string } | null> {\n try {\n const { transform } = await import('@astrojs/compiler');\n const result = await transform(code, { filename, sourcemap: 'external' });\n\n return { code: result.code, map: result.map };\n } catch {\n return null;\n }\n}\n\nexport type ScanOptions = {\n projectRoot: string;\n logger: AstroIntegrationLogger;\n consistency: ConsistencyCheckLevel;\n};\n\n/**\n * Eagerly scan all source files for t() calls.\n * Used in dev mode to extract all keys upfront, instead of waiting for\n * Vite's lazy transform hook to process files on-demand.\n */\nexport async function scan(options: ScanOptions): Promise<KeyStore> {\n const { projectRoot, logger, consistency } = options;\n const store = new KeyStore(logger, consistency);\n\n const files: string[] = [];\n const entries = fs.promises.glob(GLOB_PATTERN, {\n cwd: projectRoot,\n exclude: (name) => name === 'node_modules',\n });\n\n for await (const entry of entries) {\n files.push(path.resolve(projectRoot, entry));\n }\n\n const results = await Promise.all(\n files.map(async (file) => {\n let code = await fs.promises.readFile(file, 'utf8');\n\n // quick check: skip files without i18n translate import\n if (!code.includes('@astroscope/i18n/translate')) {\n return { file, keys: null, errors: [] };\n }\n\n // compile .astro files first, keeping the map to report authored positions\n let astroMap: string | undefined;\n\n if (file.endsWith('.astro')) {\n const compiled = await compileAstro(code, file);\n\n if (!compiled) {\n return { file, keys: null, errors: [] };\n }\n\n code = compiled.code;\n astroMap = compiled.map;\n }\n\n const result = await extractKeysFromFile({\n filename: file,\n code,\n stripFallbacks: false,\n });\n\n return {\n file,\n keys: result.keys.length > 0 ? result.keys : null,\n errors: mapErrorsToSource(result.errors, astroMap),\n };\n }),\n );\n\n for (const { file, keys, errors } of results) {\n if (keys) {\n store.addFileKeys(file, keys);\n }\n\n store.addFileErrors(file, errors);\n }\n\n return store;\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { AstroIntegrationLogger } from 'astro';\nimport MagicString from 'magic-string';\nimport type { Plugin } from 'vite';\nimport type { ConsistencyCheckLevel } from '../integration/types.js';\nimport { chunkIdToName } from '../shared/url.js';\nimport { ALL_EXTENSIONS, extractKeysFromFile } from './extract.js';\nimport { KeyStore } from './key-store.js';\nimport { getGlobalState, getManifest } from './manifest.js';\nimport { scan } from './scan.js';\nimport { mapErrorsToSource } from './source-map.js';\nimport type { ExtractionError } from './types.js';\n\nconst VIRTUAL_MODULE_ID = 'virtual:@astroscope/i18n/manifest';\nconst isVirtualModuleId = (id: string) => id === VIRTUAL_MODULE_ID || id.startsWith(`${VIRTUAL_MODULE_ID}?`);\n\nconst RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\nconst isResolvedVirtualModuleId = (id: string) =>\n id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\\0${VIRTUAL_MODULE_ID}?`);\n\nconst MANIFEST_FILE_NAME = 'i18n-manifest.json';\n\nexport type I18nVitePluginOptions = {\n logger: AstroIntegrationLogger;\n consistency: ConsistencyCheckLevel;\n};\n\n/**\n * Describe a non-extractable t() call for a developer.\n */\nfunction formatExtractionError(error: ExtractionError, projectRoot: string): string {\n const location = `${path.relative(projectRoot, error.file)}:${error.line}:${error.column + 1}`;\n\n return `${location} — t('${error.key}') ${error.reason}`;\n}\n\n/**\n * Non-extractable meta is fatal for a production build. The fallback argument is\n * stripped from the bundle, so a key whose fallback could not be read at build\n * time has nothing to fall back to and renders its own key string to end users.\n */\nfunction formatExtractionErrors(errors: ExtractionError[], projectRoot: string): string {\n const list = errors.map((e) => ` - ${formatExtractionError(e, projectRoot)}`).join('\\n');\n\n return (\n `i18n: ${errors.length} t() call(s) have a fallback that cannot be read at build time.\\n\\n` +\n `${list}\\n\\n` +\n `Production builds strip the fallback argument, so these keys would render as their own key string. ` +\n `Fallbacks must be static string literals — pass runtime values as MF2 variables instead:\\n` +\n ' t(\\'some.key\\', \\'Hello {$name}\\', { name })'\n );\n}\n\n/**\n * Vite plugin for i18n extraction, chunk mapping, and loader injection\n *\n * This plugin:\n * 1. Extracts all t() calls using Babel AST parsing\n * 2. Strips fallback arguments in production builds\n * 3. Maps translation keys to output chunks\n * 4. Generates a manifest for syncing with translation providers\n * 5. Injects translation loader into chunks that use t()\n */\nexport function i18nVitePlugin(options: I18nVitePluginOptions): Plugin {\n const { logger, consistency } = options;\n\n const store = new KeyStore(logger, consistency);\n\n // sync store's computed keys to global state (for dev mode live access)\n const syncGlobalState = () => {\n const state = getGlobalState();\n state.extractedKeys = store.extractedKeys;\n state.version++;\n };\n\n let isBuild = false;\n let projectRoot = '';\n\n return {\n name: '@astroscope/i18n/extract',\n enforce: 'pre',\n\n configResolved(config) {\n const state = getGlobalState();\n state.projectRoot = config.root;\n\n isBuild = config.command === 'build';\n projectRoot = config.root;\n },\n\n async configureServer(server) {\n // astro's build runs an internal dev server; its configureServer must not trigger the eager scan\n if (isBuild || server.config.isProduction) {\n return;\n }\n\n const result = await scan({ projectRoot, logger, consistency });\n\n store.merge(result);\n\n syncGlobalState();\n\n logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);\n\n // surface here what the build would later reject\n for (const error of store.extractionErrors) {\n logger.warn(formatExtractionError(error, projectRoot));\n }\n },\n\n resolveId(id) {\n if (isVirtualModuleId(id)) {\n return `\\0${id}`;\n }\n },\n\n load(id) {\n if (!isResolvedVirtualModuleId(id)) {\n return;\n }\n\n if (isBuild) {\n // in build mode, load manifest from JSON file at runtime\n return `\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst manifestJson = JSON.parse(readFileSync(join(__dirname, '${MANIFEST_FILE_NAME}'), 'utf-8'));\n\nexport const manifest = manifestJson;\nexport function getManifest() { return manifestJson; }\n`;\n }\n\n // in dev mode, import the getManifest function from the plugin\n // this provides live access to the extraction state as files are transformed\n return `\nimport { getManifest as _getManifest } from '@astroscope/i18n/extraction';\nexport const manifest = { keys: [], chunks: {}, scripts: [] };\nexport function getManifest() { return _getManifest(); }\n`;\n },\n\n async transform(code, filename) {\n if (ALL_EXTENSIONS.every((ext) => !filename.endsWith(ext))) {\n return null;\n }\n\n if (filename.includes('node_modules')) {\n return null;\n }\n\n // quick check: skip files without i18n translate import\n if (!code.includes('@astroscope/i18n/translate')) {\n return null;\n }\n\n const result = await extractKeysFromFile({\n filename,\n code,\n stripFallbacks: isBuild,\n });\n\n // only pay for the sourcemap lookup when there is something to report\n // eslint-disable-next-line unicorn/no-this-outside-of-class -- `this` is the vite plugin context\n const errors = result.errors.length > 0 ? mapErrorsToSource(result.errors, this.getCombinedSourcemap()) : [];\n\n store.addFileKeys(filename, result.keys);\n store.addFileErrors(filename, errors);\n\n // dev keeps the fallback argument, so a non-extractable one still renders\n // correctly at runtime — warn here and let the build be the hard gate\n if (!isBuild) {\n for (const error of errors) {\n logger.warn(formatExtractionError(error, projectRoot));\n }\n }\n\n syncGlobalState();\n\n // serialize babel's map to a string: vite accepts string source maps and\n // its structural ExistingRawSourceMap type (vite 8) rejects babel's map shape\n if (isBuild && result.code) {\n return result.map ? { code: result.code, map: JSON.stringify(result.map) } : { code: result.code };\n }\n\n return null; // dev mode: keep fallbacks\n },\n\n generateBundle(_, bundle) {\n const state = getGlobalState();\n\n // reset for each build (server + client run separately)\n state.chunkManifest = {};\n state.scriptChunks = [];\n\n // first pass: collect keys, determine which chunks have i18n, and build direct imports map\n const chunksWithI18n = new Set<string>();\n const chunkNameToFileName = new Map<string, string>();\n const directImports = new Map<string, string[]>(); // chunk → direct imports incl. dynamic (chunks only)\n const scriptEntries: string[] = [];\n\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (!(chunk.type === 'chunk' && chunk.moduleIds)) {\n continue;\n }\n\n const keys = new Set<string>();\n let hasI18nModule = false;\n\n // collect keys from all (sub)modules in the chunk\n for (const moduleId of chunk.moduleIds) {\n const moduleKeys = store.fileToKeys.get(moduleId);\n\n moduleKeys?.forEach((k) => keys.add(k));\n\n if (store.filesWithI18n.has(moduleId)) {\n hasI18nModule = true;\n }\n }\n\n const chunkName = chunkIdToName(fileName.replace(/\\.js$/, ''));\n\n if (keys.size > 0) {\n state.chunkManifest[chunkName] = [...keys];\n }\n\n if (hasI18nModule) {\n chunksWithI18n.add(chunkName);\n }\n\n chunkNameToFileName.set(chunkName, fileName);\n\n const imports: string[] = [];\n\n for (const importedFile of [...chunk.imports, ...chunk.dynamicImports]) {\n const baseName = importedFile.replace(/^.*\\//, '').replace(/\\.js$/, '');\n\n imports.push(chunkIdToName(baseName));\n }\n\n directImports.set(chunkName, imports);\n\n // astro `<script>` entries are not islands, so no island carries their\n // hashes — collect them so the middleware can bootstrap their chunks\n if ((chunk.isEntry || chunk.isDynamicEntry) && chunk.facadeModuleId?.includes('astro&type=script')) {\n scriptEntries.push(chunkName);\n }\n }\n\n // i18n chunks reachable from script entries (the entry itself included)\n const scriptChunks = new Set<string>();\n\n const walkScripts = (chunkName: string, visited: Set<string>): void => {\n if (visited.has(chunkName)) return;\n\n visited.add(chunkName);\n\n if (Object.hasOwn(state.chunkManifest, chunkName)) {\n scriptChunks.add(chunkName);\n }\n\n const imports = directImports.get(chunkName) ?? [];\n\n for (const imported of imports) {\n walkScripts(imported, visited);\n }\n };\n\n const visited = new Set<string>();\n\n for (const entry of scriptEntries) {\n walkScripts(entry, visited);\n }\n\n state.scriptChunks = [...scriptChunks];\n\n // second pass: inject loaders (prefetching is handled by the islands emitter)\n for (const [fileName, chunk] of Object.entries(bundle)) {\n if (chunk.type !== 'chunk') continue;\n\n const chunkName = chunkIdToName(fileName.replace(/\\.js$/, ''));\n\n if (!chunksWithI18n.has(chunkName)) continue;\n\n // the loader awaits the chunk's own translation chunk before the module body runs\n const loaderCode =\n `if(typeof window!=='undefined'&&window.__i18n__){` +\n `const _=window.__i18n__,h=_.hashes[${JSON.stringify(chunkName)}];` +\n `if(h)await import(\\`/_i18n/\\${_.locale}/${chunkName}.\\${h}.js\\`);` +\n `}`;\n\n const s = new MagicString(chunk.code);\n\n s.prepend(loaderCode);\n chunk.code = s.toString();\n\n if (chunk.map) {\n chunk.map = s.generateMap({ hires: true }) as typeof chunk.map;\n }\n }\n\n logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${store.uniqueKeyCount} keys`);\n\n // a fallback that could not be read is a broken string in production\n const extractionErrors = store.extractionErrors;\n\n if (extractionErrors.length > 0) {\n throw new Error(formatExtractionErrors(extractionErrors, projectRoot));\n }\n\n if (consistency === 'error' && store.hasErrors) {\n throw new Error('i18n: build failed due to translation key inconsistencies');\n }\n },\n\n writeBundle(outputOptions) {\n // only emit from client build (has full chunk mapping, runs after server build)\n // write to server directory so it's not publicly accessible (same location as the virtual module)\n // eslint-disable-next-line unicorn/no-this-outside-of-class -- `this` is the vite plugin context\n if (!(this.environment.name === 'client' && outputOptions.dir)) {\n return;\n }\n\n const chunksDir = path.resolve(outputOptions.dir, '..', 'server', 'chunks');\n const manifestPath = path.join(chunksDir, MANIFEST_FILE_NAME);\n\n if (fs.existsSync(chunksDir)) {\n fs.writeFileSync(manifestPath, JSON.stringify(getManifest()));\n } else {\n logger.error(`server chunks directory not found, cannot write manifest to ${manifestPath}`);\n }\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/i18n",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
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",
@@ -62,23 +62,24 @@
62
62
  "homepage": "https://github.com/entwico/astroscope/tree/main/packages/i18n#readme",
63
63
  "dependencies": {
64
64
  "@astrojs/compiler": "^4.0.0",
65
- "@babel/core": "^8.0.1",
66
- "@babel/preset-typescript": "^8.0.1",
65
+ "@babel/core": "^8.0.7",
66
+ "@babel/preset-typescript": "^8.0.7",
67
67
  "@jridgewell/trace-mapping": "^0.3.31",
68
- "magic-string": "^1.2.3",
68
+ "magic-string": "^1.4.3",
69
69
  "messageformat": "^4.0.0"
70
70
  },
71
71
  "devDependencies": {
72
- "@astroscope/node": "^3.1.0",
73
- "@babel/types": "^8.0.4",
72
+ "@astroscope/node": "^4.0.0",
73
+ "@babel/types": "^8.0.6",
74
74
  "@types/babel__core": "^7.20.5",
75
- "astro": "^7.3.2",
75
+ "astro": "^7.3.7",
76
76
  "tsdown": "^0.23.0",
77
77
  "typescript": "^6.0.3",
78
- "vite": "^8.2.2"
78
+ "vite": "^8.3.4",
79
+ "vitest": "^5.0.3"
79
80
  },
80
81
  "peerDependencies": {
81
- "@astroscope/node": "^3.1.0",
82
+ "@astroscope/node": "^4.0.0",
82
83
  "astro": "^7.0.0",
83
84
  "vite": "^8.0.0"
84
85
  },