@astroscope/i18n 0.5.0 → 0.6.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.
Files changed (80) hide show
  1. package/dist/babel-plugin-C8O9spUS.js +120 -0
  2. package/dist/babel-plugin-C8O9spUS.js.map +1 -0
  3. package/dist/client/directives/idle.d.ts +4 -9
  4. package/dist/client/directives/idle.d.ts.map +1 -0
  5. package/dist/client/directives/idle.js +13 -21
  6. package/dist/client/directives/idle.js.map +1 -0
  7. package/dist/client/directives/load.d.ts +4 -9
  8. package/dist/client/directives/load.d.ts.map +1 -0
  9. package/dist/client/directives/load.js +9 -14
  10. package/dist/client/directives/load.js.map +1 -0
  11. package/dist/client/directives/media.d.ts +4 -9
  12. package/dist/client/directives/media.d.ts.map +1 -0
  13. package/dist/client/directives/media.js +16 -24
  14. package/dist/client/directives/media.js.map +1 -0
  15. package/dist/client/directives/only.d.ts +4 -11
  16. package/dist/client/directives/only.d.ts.map +1 -0
  17. package/dist/client/directives/only.js +9 -14
  18. package/dist/client/directives/only.js.map +1 -0
  19. package/dist/client/directives/utils.d.ts +3 -4
  20. package/dist/client/directives/utils.d.ts.map +1 -0
  21. package/dist/client/directives/utils.js +32 -7
  22. package/dist/client/directives/utils.js.map +1 -0
  23. package/dist/client/directives/visible.d.ts +4 -9
  24. package/dist/client/directives/visible.d.ts.map +1 -0
  25. package/dist/client/directives/visible.js +22 -29
  26. package/dist/client/directives/visible.js.map +1 -0
  27. package/dist/client/translate.d.ts +5 -4
  28. package/dist/client/translate.d.ts.map +1 -0
  29. package/dist/client/translate.js +68 -41
  30. package/dist/client/translate.js.map +1 -0
  31. package/dist/compiler-DjYjJc0V.js +45 -0
  32. package/dist/compiler-DjYjJc0V.js.map +1 -0
  33. package/dist/context-CNd4O0Pf.js +247 -0
  34. package/dist/context-CNd4O0Pf.js.map +1 -0
  35. package/dist/extraction/index.d.ts +12 -17
  36. package/dist/extraction/index.d.ts.map +1 -0
  37. package/dist/extraction/index.js +3 -11
  38. package/dist/i18n-DzmH9JmE.d.ts +79 -0
  39. package/dist/i18n-DzmH9JmE.d.ts.map +1 -0
  40. package/dist/index.d.ts +37 -33
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +163 -148
  43. package/dist/index.js.map +1 -0
  44. package/dist/internal.d.ts +6 -6
  45. package/dist/internal.d.ts.map +1 -0
  46. package/dist/internal.js +2 -10
  47. package/dist/manifest-D883_BIS.js +43 -0
  48. package/dist/manifest-D883_BIS.js.map +1 -0
  49. package/dist/rich-CHisXE-A.js +87 -0
  50. package/dist/rich-CHisXE-A.js.map +1 -0
  51. package/dist/{rich-CQUhdwBc.d.ts → rich-CqNMP3tk.d.ts} +4 -2
  52. package/dist/rich-CqNMP3tk.d.ts.map +1 -0
  53. package/dist/server/translate.d.ts +5 -3
  54. package/dist/server/translate.d.ts.map +1 -0
  55. package/dist/server/translate.js +75 -64
  56. package/dist/server/translate.js.map +1 -0
  57. package/dist/state-DeudhdeT.js +20 -0
  58. package/dist/state-DeudhdeT.js.map +1 -0
  59. package/dist/{types-ChMT6Vbd.d.ts → types-BfyoJsoB.d.ts} +11 -10
  60. package/dist/types-BfyoJsoB.d.ts.map +1 -0
  61. package/dist/types-Bo4jMK8g.d.ts +20 -0
  62. package/dist/types-Bo4jMK8g.d.ts.map +1 -0
  63. package/dist/{types-DNRFj1XA.d.ts → types-C98CcjtN.d.ts} +13 -11
  64. package/dist/types-C98CcjtN.d.ts.map +1 -0
  65. package/dist/url-B8334XSD.js +36 -0
  66. package/dist/url-B8334XSD.js.map +1 -0
  67. package/dist/vite-plugin-DVUlcvEH.js +421 -0
  68. package/dist/vite-plugin-DVUlcvEH.js.map +1 -0
  69. package/package.json +13 -12
  70. package/dist/babel-plugin-GOB6FMZ2.js +0 -119
  71. package/dist/chunk-33JVAFGQ.js +0 -44
  72. package/dist/chunk-4A5BKHFN.js +0 -16
  73. package/dist/chunk-7MSGHEST.js +0 -31
  74. package/dist/chunk-FYKC3DAZ.js +0 -36
  75. package/dist/chunk-LXW5WYBS.js +0 -298
  76. package/dist/chunk-PACQPCSW.js +0 -479
  77. package/dist/chunk-VGPVG5JP.js +0 -73
  78. package/dist/i18n-B--L7OQk.d.ts +0 -76
  79. package/dist/types-B_8vAmvv.d.ts +0 -18
  80. package/dist/types-BeDFfFIL.d.ts +0 -7
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { AstroIntegration, APIContext, MiddlewareHandler } from 'astro';
2
- import { I as I18nOptions } from './types-B_8vAmvv.js';
3
- export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-B--L7OQk.js';
4
- import { ExcludePattern } from '@astroscope/excludes';
5
- export { R as RawTranslations, T as TranslationMeta } from './types-DNRFj1XA.js';
6
- export { a as ExtractedKey, E as ExtractionManifest } from './types-ChMT6Vbd.js';
1
+ import { a as TranslationMeta, r as RawTranslations } from "./types-C98CcjtN.js";
2
+ import { n as ExtractedKey, r as ExtractionManifest } from "./types-BfyoJsoB.js";
3
+ import { n as I18nOptions } from "./types-Bo4jMK8g.js";
4
+ import { n as i18n, r as FallbackBehavior, t as I18nConfig } from "./i18n-DzmH9JmE.js";
5
+ import { ExcludePattern } from "@astroscope/excludes";
6
+ import { APIContext, AstroIntegration, MiddlewareHandler } from "astro";
7
7
 
8
+ //#region src/integration/integration.d.ts
8
9
  /**
9
10
  * Astro integration for i18n with automatic per-chunk translation loading.
10
11
  *
@@ -13,31 +14,32 @@ export { a as ExtractedKey, E as ExtractionManifest } from './types-ChMT6Vbd.js'
13
14
  * - Vite plugin for translation key extraction
14
15
  */
15
16
  declare function i18nIntegration(options?: I18nOptions): AstroIntegration;
16
-
17
+ //#endregion
18
+ //#region src/server/middleware.d.ts
17
19
  type I18nMiddlewareOptions = {
18
- locale: (ctx: APIContext) => string;
19
- /**
20
- * Patterns to exclude from locale context setup.
21
- * Defaults to RECOMMENDED_EXCLUDES if not provided.
22
- *
23
- * @example
24
- * ```ts
25
- * import { RECOMMENDED_EXCLUDES } from '@astroscope/excludes';
26
- *
27
- * // extend defaults
28
- * createI18nMiddleware({
29
- * locale: (ctx) => ctx.locals.session?.locale ?? 'en',
30
- * exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
31
- * })
32
- *
33
- * // disable excludes entirely
34
- * createI18nMiddleware({
35
- * locale: (ctx) => ctx.locals.session?.locale ?? 'en',
36
- * exclude: [],
37
- * })
38
- * ```
39
- */
40
- exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;
20
+ locale: (ctx: APIContext) => string;
21
+ /**
22
+ * Patterns to exclude from locale context setup.
23
+ * Defaults to RECOMMENDED_EXCLUDES if not provided.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { RECOMMENDED_EXCLUDES } from '@astroscope/excludes';
28
+ *
29
+ * // extend defaults
30
+ * createI18nMiddleware({
31
+ * locale: (ctx) => ctx.locals.session?.locale ?? 'en',
32
+ * exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
33
+ * })
34
+ *
35
+ * // disable excludes entirely
36
+ * createI18nMiddleware({
37
+ * locale: (ctx) => ctx.locals.session?.locale ?? 'en',
38
+ * exclude: [],
39
+ * })
40
+ * ```
41
+ */
42
+ exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;
41
43
  };
42
44
  /**
43
45
  * Create the i18n chunk middleware that serves translation chunks at `/_i18n/` endpoints.
@@ -79,11 +81,13 @@ declare function createI18nChunkMiddleware(): MiddlewareHandler;
79
81
  * ```
80
82
  */
81
83
  declare function createI18nMiddleware(options: I18nMiddlewareOptions): MiddlewareHandler;
82
-
84
+ //#endregion
85
+ //#region src/server/locale.d.ts
83
86
  /**
84
87
  * Detect locale from Accept-Language header.
85
88
  * Returns best match from configured locales, or undefined if none match.
86
89
  */
87
90
  declare function detectLocale(request: Request): string | undefined;
88
-
89
- export { type I18nMiddlewareOptions, createI18nChunkMiddleware, createI18nMiddleware, i18nIntegration as default, detectLocale };
91
+ //#endregion
92
+ export { type ExtractedKey, type ExtractionManifest, type FallbackBehavior, type I18nConfig, type I18nMiddlewareOptions, type RawTranslations, type TranslationMeta, createI18nChunkMiddleware, createI18nMiddleware, i18nIntegration as default, detectLocale, i18n };
93
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"mappings":";;;;;;;;;;;;;;AAE8C;iBAWtB,eAAA,CAAgB,OAAA,GAAS,WAAA,GAAmB,gBAAgB;;;KCPxE,qBAAA;EACV,MAAA,GAAS,GAAA,EAAK,UAAA;;;;;ADL8B;;;;;;;;AAWsC;;;;ACPpF;;;;;EAuBE,OAAA,GAAU,cAAA,OAAqB,OAAA,EAAS,UAAA;AAAA;;;;;;;;;;AAAU;AAwBpD;;;;AAA8D;AA0F9D;;;;iBA1FgB,yBAAA,IAA6B,iBAAiB;;;;AA0FyB;;;;ACzIvF;;;;AAA6C;;;;;;;;iBDyI7B,oBAAA,CAAqB,OAAA,EAAS,qBAAA,GAAwB,iBAAiB;;;;;;;iBCzIvE,YAAA,CAAa,OAAgB,EAAP,OAAO"}
package/dist/index.js CHANGED
@@ -1,160 +1,175 @@
1
- import {
2
- i18nVitePlugin
3
- } from "./chunk-PACQPCSW.js";
4
- import {
5
- i18n,
6
- runWithContext
7
- } from "./chunk-LXW5WYBS.js";
8
- import "./chunk-33JVAFGQ.js";
9
- import "./chunk-FYKC3DAZ.js";
10
- import "./chunk-4A5BKHFN.js";
11
-
12
- // src/integration/integration.ts
13
- var CLIENT_DIRECTIVES = ["load-x", "idle-x", "visible-x", "media-x", "only-x"];
1
+ import { t as i18nVitePlugin } from "./vite-plugin-DVUlcvEH.js";
2
+ import { n as runWithContext, r as i18n } from "./context-CNd4O0Pf.js";
3
+ import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/excludes";
4
+ //#region src/integration/integration.ts
5
+ const CLIENT_DIRECTIVES = [
6
+ "load-x",
7
+ "idle-x",
8
+ "visible-x",
9
+ "media-x",
10
+ "only-x"
11
+ ];
12
+ /**
13
+ * Astro integration for i18n with automatic per-chunk translation loading.
14
+ *
15
+ * Registers:
16
+ * - Client directives (`client:load-x`, `client:idle-x`, `client:visible-x`, `client:media-x`, `client:only-x`)
17
+ * - Vite plugin for translation key extraction
18
+ */
14
19
  function i18nIntegration(options = {}) {
15
- const { consistency = "warn" } = options;
16
- return {
17
- name: "@astroscope/i18n",
18
- hooks: {
19
- "astro:config:setup": ({ addClientDirective, updateConfig, logger }) => {
20
- for (const directive of CLIENT_DIRECTIVES) {
21
- const base = directive.replace("-x", "");
22
- addClientDirective({
23
- name: directive,
24
- entrypoint: `@astroscope/i18n/client-directives/${base}.js`
25
- });
26
- }
27
- updateConfig({
28
- vite: {
29
- plugins: [i18nVitePlugin({ logger, consistency })]
30
- }
31
- });
32
- },
33
- "astro:config:done": ({ injectTypes }) => {
34
- const typeMap = {
35
- load: "boolean",
36
- idle: "boolean",
37
- visible: "boolean | string",
38
- media: "string",
39
- only: "string"
40
- };
41
- const attributes = CLIENT_DIRECTIVES.map((d) => {
42
- const base = d.replace("-x", "");
43
- return ` 'client:${d}'?: ${typeMap[base]};`;
44
- }).join("\n");
45
- injectTypes({
46
- filename: "client-directives.d.ts",
47
- content: `// generated by @astroscope/i18n
20
+ const { consistency = "error" } = options;
21
+ return {
22
+ name: "@astroscope/i18n",
23
+ hooks: {
24
+ "astro:config:setup": ({ addClientDirective, updateConfig, logger }) => {
25
+ for (const directive of CLIENT_DIRECTIVES) addClientDirective({
26
+ name: directive,
27
+ entrypoint: `@astroscope/i18n/client-directives/${directive.replace("-x", "")}.js`
28
+ });
29
+ updateConfig({ vite: { plugins: [i18nVitePlugin({
30
+ logger,
31
+ consistency
32
+ })] } });
33
+ },
34
+ "astro:config:done": ({ injectTypes }) => {
35
+ const typeMap = {
36
+ load: "boolean",
37
+ idle: "boolean",
38
+ visible: "boolean | string",
39
+ media: "string",
40
+ only: "string"
41
+ };
42
+ injectTypes({
43
+ filename: "client-directives.d.ts",
44
+ content: `// generated by @astroscope/i18n
48
45
  declare namespace astroHTML.JSX {
49
46
  interface AstroBuiltinAttributes {
50
- ${attributes}
47
+ ${CLIENT_DIRECTIVES.map((d) => {
48
+ return ` 'client:${d}'?: ${typeMap[d.replace("-x", "")]};`;
49
+ }).join("\n")}
51
50
  }
52
51
  }`
53
- });
54
- }
55
- }
56
- };
52
+ });
53
+ }
54
+ }
55
+ };
57
56
  }
58
-
59
- // src/server/middleware.ts
60
- import { RECOMMENDED_EXCLUDES, shouldExclude } from "@astroscope/excludes";
61
- var I18N_ENDPOINT_PREFIX = "/_i18n/";
57
+ //#endregion
58
+ //#region src/server/middleware.ts
59
+ const I18N_ENDPOINT_PREFIX = "/_i18n/";
60
+ /**
61
+ * Create the i18n chunk middleware that serves translation chunks at `/_i18n/` endpoints.
62
+ *
63
+ * Place this early in your middleware sequence (before session/auth)
64
+ * to avoid unnecessary overhead for static translation requests.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * // src/middleware.ts
69
+ * import { sequence } from 'astro:middleware';
70
+ * import { createI18nChunkMiddleware, createI18nMiddleware } from '@astroscope/i18n';
71
+ *
72
+ * export const onRequest = sequence(
73
+ * createI18nChunkMiddleware(), // early: serves /_i18n/ chunks
74
+ * sessionMiddleware,
75
+ * createI18nMiddleware({ locale: (ctx) => ... }), // after session
76
+ * );
77
+ * ```
78
+ */
62
79
  function createI18nChunkMiddleware() {
63
- return (ctx, next) => {
64
- const { pathname } = ctx.url;
65
- if (!pathname.startsWith(I18N_ENDPOINT_PREFIX)) {
66
- return next();
67
- }
68
- if (!i18n.isConfigured()) {
69
- console.warn(`[@astroscope/i18n] not configured, passing through: ${pathname}`);
70
- return next();
71
- }
72
- const path = pathname.slice(I18N_ENDPOINT_PREFIX.length);
73
- const slashIdx = path.indexOf("/");
74
- if (slashIdx === -1) {
75
- return next();
76
- }
77
- const locale = path.slice(0, slashIdx);
78
- const config = i18n.getConfig();
79
- if (!config.locales.includes(locale)) {
80
- return new Response("/* unknown locale */", {
81
- status: 404,
82
- headers: { "Content-Type": "application/javascript" }
83
- });
84
- }
85
- const rest = path.slice(slashIdx + 1);
86
- if (!rest.endsWith(".js")) {
87
- return next();
88
- }
89
- const withoutJs = rest.slice(0, -3);
90
- const lastDotIdx = withoutJs.lastIndexOf(".");
91
- if (lastDotIdx === -1) {
92
- return next();
93
- }
94
- const chunkName = withoutJs.slice(0, lastDotIdx);
95
- const body = i18n.getChunkBody(locale, chunkName);
96
- if (!body) {
97
- return new Response(`/* chunk not found: ${chunkName} */`, {
98
- status: 404,
99
- headers: { "Content-Type": "application/javascript" }
100
- });
101
- }
102
- return new Response(body, {
103
- status: 200,
104
- headers: {
105
- "Content-Type": "text/javascript; charset=utf-8",
106
- "Content-Length": String(body.byteLength),
107
- "Cache-Control": "public, max-age=31536000, immutable"
108
- }
109
- });
110
- };
80
+ return (ctx, next) => {
81
+ const { pathname } = ctx.url;
82
+ if (!pathname.startsWith(I18N_ENDPOINT_PREFIX)) return next();
83
+ if (!i18n.isConfigured()) {
84
+ console.warn(`[@astroscope/i18n] not configured, passing through: ${pathname}`);
85
+ return next();
86
+ }
87
+ const path = pathname.slice(7);
88
+ const slashIdx = path.indexOf("/");
89
+ if (slashIdx === -1) return next();
90
+ const locale = path.slice(0, slashIdx);
91
+ if (!i18n.getConfig().locales.includes(locale)) return new Response("/* unknown locale */", {
92
+ status: 404,
93
+ headers: { "Content-Type": "application/javascript" }
94
+ });
95
+ const rest = path.slice(slashIdx + 1);
96
+ if (!rest.endsWith(".js")) return next();
97
+ const withoutJs = rest.slice(0, -3);
98
+ const lastDotIdx = withoutJs.lastIndexOf(".");
99
+ if (lastDotIdx === -1) return next();
100
+ const chunkName = withoutJs.slice(0, lastDotIdx);
101
+ const body = i18n.getChunkBody(locale, chunkName);
102
+ if (!body) return new Response(`/* chunk not found: ${chunkName} */`, {
103
+ status: 404,
104
+ headers: { "Content-Type": "application/javascript" }
105
+ });
106
+ return new Response(body, {
107
+ status: 200,
108
+ headers: {
109
+ "Content-Type": "text/javascript; charset=utf-8",
110
+ "Content-Length": String(body.byteLength),
111
+ "Cache-Control": "public, max-age=31536000, immutable"
112
+ }
113
+ });
114
+ };
111
115
  }
116
+ /**
117
+ * Create the i18n locale middleware.
118
+ *
119
+ * Sets up the request context with locale and translations for use by
120
+ * `t()` and `<I18nScript />`. Place this after session middleware if
121
+ * your locale detection depends on session/cookies.
122
+ *
123
+ * @example
124
+ * ```typescript
125
+ * import { createI18nMiddleware, detectLocale, i18n } from '@astroscope/i18n';
126
+ *
127
+ * export const i18nMiddleware = createI18nMiddleware({
128
+ * locale: (ctx) =>
129
+ * ctx.cookies.get('locale')?.value ??
130
+ * detectLocale(ctx.request) ??
131
+ * i18n.getConfig().defaultLocale,
132
+ * });
133
+ * ```
134
+ */
112
135
  function createI18nMiddleware(options) {
113
- return (ctx, next) => {
114
- if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {
115
- return next();
116
- }
117
- if (!i18n.isConfigured()) {
118
- console.warn(`[@astroscope/i18n] not configured, passing through: ${ctx.url.pathname}`);
119
- return next();
120
- }
121
- const locale = options.locale(ctx);
122
- const context = {
123
- locale,
124
- translations: i18n.getCompiledTranslations(locale),
125
- rawTranslations: i18n.getTranslations(locale),
126
- fallback: i18n.getConfig().fallback
127
- };
128
- return runWithContext(context, () => next());
129
- };
136
+ return (ctx, next) => {
137
+ if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) return next();
138
+ if (!i18n.isConfigured()) {
139
+ console.warn(`[@astroscope/i18n] not configured, passing through: ${ctx.url.pathname}`);
140
+ return next();
141
+ }
142
+ const locale = options.locale(ctx);
143
+ return runWithContext({
144
+ locale,
145
+ translations: i18n.getCompiledTranslations(locale),
146
+ rawTranslations: i18n.getTranslations(locale),
147
+ fallback: i18n.getConfig().fallback
148
+ }, () => next());
149
+ };
130
150
  }
131
-
132
- // src/server/locale.ts
151
+ //#endregion
152
+ //#region src/server/locale.ts
153
+ /**
154
+ * Detect locale from Accept-Language header.
155
+ * Returns best match from configured locales, or undefined if none match.
156
+ */
133
157
  function detectLocale(request) {
134
- const config = i18n.getConfig();
135
- if (!config) return void 0;
136
- const { locales } = config;
137
- const acceptLanguage = request.headers.get("accept-language");
138
- if (!acceptLanguage) return void 0;
139
- const preferred = acceptLanguage.split(",").map((part) => {
140
- const [lang, q = "q=1"] = part.trim().split(";");
141
- const baseLang = lang?.split("-")[0]?.toLowerCase();
142
- return {
143
- lang: baseLang,
144
- q: parseFloat(q.replace("q=", ""))
145
- };
146
- }).filter((p) => !!p.lang).sort((a, b) => b.q - a.q);
147
- for (const { lang } of preferred) {
148
- if (locales.includes(lang)) {
149
- return lang;
150
- }
151
- }
152
- return void 0;
158
+ const config = i18n.getConfig();
159
+ if (!config) return void 0;
160
+ const { locales } = config;
161
+ const acceptLanguage = request.headers.get("accept-language");
162
+ if (!acceptLanguage) return void 0;
163
+ const preferred = acceptLanguage.split(",").map((part) => {
164
+ const [lang, q = "q=1"] = part.trim().split(";");
165
+ return {
166
+ lang: lang?.split("-")[0]?.toLowerCase(),
167
+ q: parseFloat(q.replace("q=", ""))
168
+ };
169
+ }).filter((p) => !!p.lang).sort((a, b) => b.q - a.q);
170
+ for (const { lang } of preferred) if (locales.includes(lang)) return lang;
153
171
  }
154
- export {
155
- createI18nChunkMiddleware,
156
- createI18nMiddleware,
157
- i18nIntegration as default,
158
- detectLocale,
159
- i18n
160
- };
172
+ //#endregion
173
+ export { createI18nChunkMiddleware, createI18nMiddleware, i18nIntegration as default, detectLocale, i18n };
174
+
175
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"sourcesContent":["import type { AstroIntegration } from 'astro';\nimport { i18nVitePlugin } from '../extraction/vite-plugin.js';\nimport type { I18nOptions } from './types.js';\n\nconst CLIENT_DIRECTIVES = ['load-x', 'idle-x', 'visible-x', 'media-x', 'only-x'] as const;\n\n/**\n * Astro integration for i18n with automatic per-chunk translation loading.\n *\n * Registers:\n * - Client directives (`client:load-x`, `client:idle-x`, `client:visible-x`, `client:media-x`, `client:only-x`)\n * - Vite plugin for translation key extraction\n */\nexport default function i18nIntegration(options: I18nOptions = {}): AstroIntegration {\n const { consistency = 'error' } = options;\n\n return {\n name: '@astroscope/i18n',\n hooks: {\n 'astro:config:setup': ({ addClientDirective, updateConfig, logger }) => {\n for (const directive of CLIENT_DIRECTIVES) {\n const base = directive.replace('-x', '');\n addClientDirective({\n name: directive,\n entrypoint: `@astroscope/i18n/client-directives/${base}.js`,\n });\n }\n\n updateConfig({\n vite: {\n plugins: [i18nVitePlugin({ logger, consistency })],\n },\n });\n },\n 'astro:config:done': ({ injectTypes }) => {\n // inject client directive types for LSP support\n const typeMap: Record<string, string> = {\n load: 'boolean',\n idle: 'boolean',\n visible: 'boolean | string',\n media: 'string',\n only: 'string',\n };\n\n const attributes = CLIENT_DIRECTIVES.map((d) => {\n const base = d.replace('-x', '');\n return ` 'client:${d}'?: ${typeMap[base]};`;\n }).join('\\n');\n\n injectTypes({\n filename: 'client-directives.d.ts',\n content: `// generated by @astroscope/i18n\ndeclare namespace astroHTML.JSX {\n interface AstroBuiltinAttributes {\n${attributes}\n }\n}`,\n });\n },\n },\n };\n}\n","import { type ExcludePattern, RECOMMENDED_EXCLUDES, shouldExclude } from '@astroscope/excludes';\nimport type { APIContext, MiddlewareHandler } from 'astro';\nimport { runWithContext } from './context.js';\nimport { i18n } from './i18n.js';\nimport type { I18nContext } from './types.js';\n\nexport type I18nMiddlewareOptions = {\n locale: (ctx: APIContext) => string;\n /**\n * Patterns to exclude from locale context setup.\n * Defaults to RECOMMENDED_EXCLUDES if not provided.\n *\n * @example\n * ```ts\n * import { RECOMMENDED_EXCLUDES } from '@astroscope/excludes';\n *\n * // extend defaults\n * createI18nMiddleware({\n * locale: (ctx) => ctx.locals.session?.locale ?? 'en',\n * exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],\n * })\n *\n * // disable excludes entirely\n * createI18nMiddleware({\n * locale: (ctx) => ctx.locals.session?.locale ?? 'en',\n * exclude: [],\n * })\n * ```\n */\n exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;\n};\n\nconst I18N_ENDPOINT_PREFIX = '/_i18n/';\n\n/**\n * Create the i18n chunk middleware that serves translation chunks at `/_i18n/` endpoints.\n *\n * Place this early in your middleware sequence (before session/auth)\n * to avoid unnecessary overhead for static translation requests.\n *\n * @example\n * ```typescript\n * // src/middleware.ts\n * import { sequence } from 'astro:middleware';\n * import { createI18nChunkMiddleware, createI18nMiddleware } from '@astroscope/i18n';\n *\n * export const onRequest = sequence(\n * createI18nChunkMiddleware(), // early: serves /_i18n/ chunks\n * sessionMiddleware,\n * createI18nMiddleware({ locale: (ctx) => ... }), // after session\n * );\n * ```\n */\nexport function createI18nChunkMiddleware(): MiddlewareHandler {\n return (ctx, next) => {\n const { pathname } = ctx.url;\n\n if (!pathname.startsWith(I18N_ENDPOINT_PREFIX)) {\n return next();\n }\n\n if (!i18n.isConfigured()) {\n console.warn(`[@astroscope/i18n] not configured, passing through: ${pathname}`);\n\n return next();\n }\n\n const path = pathname.slice(I18N_ENDPOINT_PREFIX.length);\n\n // attempt to parse as efficient as possible\n // expected path format: {locale}/{chunkName}.{hash}.js\n // avoiding regex or split for performance\n const slashIdx = path.indexOf('/');\n\n if (slashIdx === -1) {\n return next();\n }\n\n const locale = path.slice(0, slashIdx);\n\n // validate locale against configured locales to prevent arbitrary locale injection\n const config = i18n.getConfig();\n\n if (!config.locales.includes(locale)) {\n return new Response('/* unknown locale */', {\n status: 404,\n headers: { 'Content-Type': 'application/javascript' },\n });\n }\n\n const rest = path.slice(slashIdx + 1);\n\n if (!rest.endsWith('.js')) {\n return next();\n }\n\n const withoutJs = rest.slice(0, -3);\n const lastDotIdx = withoutJs.lastIndexOf('.');\n\n if (lastDotIdx === -1) {\n return next();\n }\n\n const chunkName = withoutJs.slice(0, lastDotIdx);\n const body = i18n.getChunkBody(locale, chunkName);\n\n if (!body) {\n return new Response(`/* chunk not found: ${chunkName} */`, {\n status: 404,\n headers: { 'Content-Type': 'application/javascript' },\n });\n }\n\n return new Response(body as BodyInit, {\n status: 200,\n headers: {\n 'Content-Type': 'text/javascript; charset=utf-8',\n 'Content-Length': String(body.byteLength),\n 'Cache-Control': 'public, max-age=31536000, immutable',\n },\n });\n };\n}\n\n/**\n * Create the i18n locale middleware.\n *\n * Sets up the request context with locale and translations for use by\n * `t()` and `<I18nScript />`. Place this after session middleware if\n * your locale detection depends on session/cookies.\n *\n * @example\n * ```typescript\n * import { createI18nMiddleware, detectLocale, i18n } from '@astroscope/i18n';\n *\n * export const i18nMiddleware = createI18nMiddleware({\n * locale: (ctx) =>\n * ctx.cookies.get('locale')?.value ??\n * detectLocale(ctx.request) ??\n * i18n.getConfig().defaultLocale,\n * });\n * ```\n */\nexport function createI18nMiddleware(options: I18nMiddlewareOptions): MiddlewareHandler {\n return (ctx, next) => {\n if (shouldExclude(ctx, options.exclude ?? RECOMMENDED_EXCLUDES)) {\n return next();\n }\n\n if (!i18n.isConfigured()) {\n console.warn(`[@astroscope/i18n] not configured, passing through: ${ctx.url.pathname}`);\n\n return next();\n }\n\n const locale = options.locale(ctx);\n\n const context: I18nContext = {\n locale,\n translations: i18n.getCompiledTranslations(locale),\n rawTranslations: i18n.getTranslations(locale),\n fallback: i18n.getConfig().fallback,\n };\n\n return runWithContext(context, () => next());\n };\n}\n","import { i18n } from './i18n.js';\n\n/**\n * Detect locale from Accept-Language header.\n * Returns best match from configured locales, or undefined if none match.\n */\nexport function detectLocale(request: Request): string | undefined {\n const config = i18n.getConfig();\n\n if (!config) return undefined;\n\n const { locales } = config;\n const acceptLanguage = request.headers.get('accept-language');\n\n if (!acceptLanguage) return undefined;\n\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q = 'q=1'] = part.trim().split(';');\n const baseLang = lang?.split('-')[0]?.toLowerCase();\n\n return {\n lang: baseLang,\n q: parseFloat(q.replace('q=', '')),\n };\n })\n .filter((p): p is { lang: string; q: number } => !!p.lang)\n .sort((a, b) => b.q - a.q);\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) {\n return lang;\n }\n }\n\n return undefined;\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
- import { a as I18nContext } from './i18n-B--L7OQk.js';
2
- export { i as i18n } from './i18n-B--L7OQk.js';
3
- export { I as I18nClientState } from './types-DNRFj1XA.js';
4
- import './types-ChMT6Vbd.js';
1
+ import { n as I18nClientState } from "./types-C98CcjtN.js";
2
+ import { i as I18nContext, n as i18n } from "./i18n-DzmH9JmE.js";
5
3
 
4
+ //#region src/server/context.d.ts
6
5
  /**
7
6
  * Get the current i18n context
8
7
  */
9
8
  declare function getContext(): I18nContext | null;
10
-
11
- export { getContext };
9
+ //#endregion
10
+ export { type I18nClientState, getContext, i18n };
11
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","names":[],"sources":["../src/server/context.ts"],"mappings":";;;;;;;iBAegB,UAAA,IAAc,WAAW"}
package/dist/internal.js CHANGED
@@ -1,10 +1,2 @@
1
- import {
2
- getContext,
3
- i18n
4
- } from "./chunk-LXW5WYBS.js";
5
- import "./chunk-33JVAFGQ.js";
6
- import "./chunk-FYKC3DAZ.js";
7
- export {
8
- getContext,
9
- i18n
10
- };
1
+ import { r as i18n, t as getContext } from "./context-CNd4O0Pf.js";
2
+ export { getContext, i18n };
@@ -0,0 +1,43 @@
1
+ import path from "node:path";
2
+ //#region src/extraction/manifest.ts
3
+ /**
4
+ * Manifest state management for i18n extraction
5
+ *
6
+ * Separated from vite-plugin to avoid bundling Babel at runtime
7
+ */
8
+ const I18N_MANIFEST_GLOBAL_KEY = "__astroscope_i18n_manifest__";
9
+ function getGlobalState() {
10
+ const g = globalThis;
11
+ if (!g[I18N_MANIFEST_GLOBAL_KEY]) g[I18N_MANIFEST_GLOBAL_KEY] = {
12
+ extractedKeys: [],
13
+ chunkManifest: {},
14
+ importsManifest: {},
15
+ projectRoot: "",
16
+ version: 0
17
+ };
18
+ return g[I18N_MANIFEST_GLOBAL_KEY];
19
+ }
20
+ /**
21
+ * Get the current extraction manifest (for dev mode live access).
22
+ * This bypasses Vite's module caching to return fresh data.
23
+ */
24
+ function getManifest() {
25
+ const state = getGlobalState();
26
+ return {
27
+ keys: state.extractedKeys.map((key) => ({
28
+ ...key,
29
+ files: key.files.map((fileLocation) => {
30
+ const [filePath, line] = fileLocation.split(":");
31
+ if (!filePath) return fileLocation;
32
+ const relativePath = state.projectRoot ? path.relative(state.projectRoot, filePath) : filePath;
33
+ return line ? `${relativePath}:${line}` : relativePath;
34
+ })
35
+ })),
36
+ chunks: state.chunkManifest,
37
+ imports: state.importsManifest
38
+ };
39
+ }
40
+ //#endregion
41
+ export { getManifest as n, getGlobalState as t };
42
+
43
+ //# sourceMappingURL=manifest-D883_BIS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-D883_BIS.js","names":[],"sources":["../src/extraction/manifest.ts"],"sourcesContent":["/**\n * Manifest state management for i18n extraction\n *\n * Separated from vite-plugin to avoid bundling Babel at runtime\n */\n\nimport path from 'node:path';\nimport type { ChunkManifest, ExtractedKey, ExtractionManifest, ImportsManifest } from './types.js';\n\n// global state for dev mode live access via globalThis\n// this allows the virtual module to access current extraction data\n// even after initial import (since dev mode transforms are on-demand)\nconst I18N_MANIFEST_GLOBAL_KEY = '__astroscope_i18n_manifest__';\n\nexport type GlobalI18nState = {\n // actual manifest data\n extractedKeys: ExtractedKey[];\n chunkManifest: ChunkManifest;\n importsManifest: ImportsManifest;\n\n // project root for relative paths\n projectRoot: string;\n\n // incremented on each sync to signal cache invalidation\n version: number;\n};\n\nexport function getGlobalState(): GlobalI18nState {\n const g = globalThis as Record<string, unknown>;\n\n // in dev mode, initialize global state if not present\n if (!g[I18N_MANIFEST_GLOBAL_KEY]) {\n g[I18N_MANIFEST_GLOBAL_KEY] = {\n extractedKeys: [],\n chunkManifest: {},\n importsManifest: {},\n projectRoot: '',\n version: 0,\n };\n }\n\n return g[I18N_MANIFEST_GLOBAL_KEY] as GlobalI18nState;\n}\n\n/**\n * Get the current extraction manifest (for dev mode live access).\n * This bypasses Vite's module caching to return fresh data.\n */\nexport function getManifest(): ExtractionManifest {\n const state = getGlobalState();\n\n const keys = state.extractedKeys.map((key) => ({\n ...key,\n // make file paths relative to project root for better readability\n // and avoiding leaking absolute paths from dev or ci environments\n files: key.files.map((fileLocation) => {\n const [filePath, line] = fileLocation.split(':');\n\n if (!filePath) return fileLocation;\n\n const relativePath = state.projectRoot ? path.relative(state.projectRoot, filePath) : filePath;\n\n return line ? `${relativePath}:${line}` : relativePath;\n }),\n }));\n\n return { keys, chunks: state.chunkManifest, imports: state.importsManifest };\n}\n"],"mappings":";;;;;;;AAYA,MAAM,2BAA2B;AAejC,SAAgB,iBAAkC;CAChD,MAAM,IAAI;CAGV,IAAI,CAAC,EAAE,2BACL,EAAE,4BAA4B;EAC5B,eAAe,CAAC;EAChB,eAAe,CAAC;EAChB,iBAAiB,CAAC;EAClB,aAAa;EACb,SAAS;CACX;CAGF,OAAO,EAAE;AACX;;;;;AAMA,SAAgB,cAAkC;CAChD,MAAM,QAAQ,eAAe;CAiB7B,OAAO;EAAE,MAfI,MAAM,cAAc,KAAK,SAAS;GAC7C,GAAG;GAGH,OAAO,IAAI,MAAM,KAAK,iBAAiB;IACrC,MAAM,CAAC,UAAU,QAAQ,aAAa,MAAM,GAAG;IAE/C,IAAI,CAAC,UAAU,OAAO;IAEtB,MAAM,eAAe,MAAM,cAAc,KAAK,SAAS,MAAM,aAAa,QAAQ,IAAI;IAEtF,OAAO,OAAO,GAAG,aAAa,GAAG,SAAS;GAC5C,CAAC;EACH,EAEY;EAAG,QAAQ,MAAM;EAAe,SAAS,MAAM;CAAgB;AAC7E"}
@@ -0,0 +1,87 @@
1
+ //#region src/shared/meta.ts
2
+ /**
3
+ * Normalize meta argument to TranslationMeta object
4
+ */
5
+ function normalizeMeta(meta) {
6
+ return typeof meta === "string" ? { fallback: meta } : meta;
7
+ }
8
+ //#endregion
9
+ //#region src/shared/rich.ts
10
+ const REACT_ELEMENT_TYPE = Symbol.for("react.element");
11
+ const REACT_TRANSITIONAL_ELEMENT_TYPE = Symbol.for("react.transitional.element");
12
+ /**
13
+ * Duck-type check for React elements without importing React.
14
+ * Uses Symbol.for() which returns the same symbol React uses internally.
15
+ */
16
+ function isReactElement(value) {
17
+ if (typeof value !== "object" || value === null || !("$$typeof" in value)) return false;
18
+ const type = value.$$typeof;
19
+ return type === REACT_ELEMENT_TYPE || type === REACT_TRANSITIONAL_ELEMENT_TYPE;
20
+ }
21
+ /**
22
+ * Add a key to a React element if it doesn't have one.
23
+ * Creates a shallow copy since React elements are frozen.
24
+ */
25
+ function addKeyIfNeeded(element, key) {
26
+ if (isReactElement(element) && element.key === null) return {
27
+ ...element,
28
+ key
29
+ };
30
+ return element;
31
+ }
32
+ function isMarkupPart(part) {
33
+ return part.type === "markup";
34
+ }
35
+ /**
36
+ * Convert MF2 formatToParts() output to a tree of nodes.
37
+ * Works with any JSX runtime (Astro, React, etc.) - the callbacks
38
+ * return whatever JSX elements the active runtime compiles to.
39
+ *
40
+ * @example
41
+ * MF2 message: "Read our {#link}Terms{/link}"
42
+ * Parts: [
43
+ * { type: 'text', value: 'Read our ' },
44
+ * { type: 'markup', kind: 'open', name: 'link' },
45
+ * { type: 'text', value: 'Terms' },
46
+ * { type: 'markup', kind: 'close', name: 'link' },
47
+ * ]
48
+ * Result: ['Read our ', <a href="/tos">Terms</a>]
49
+ */
50
+ function partsToNodes(parts, components) {
51
+ const result = [];
52
+ const stack = [];
53
+ let keyIndex = 0;
54
+ 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);
73
+ else if (part.type === "bidiIsolation") {} else if ("value" in part && part.value !== void 0 && part.value !== null) {
74
+ const stringValue = String(part.value);
75
+ target.push(stringValue);
76
+ }
77
+ }
78
+ while (stack.length > 0) {
79
+ const frame = stack.pop();
80
+ (stack.length > 0 ? stack[stack.length - 1].children : result).push(...frame.children);
81
+ }
82
+ return result;
83
+ }
84
+ //#endregion
85
+ export { normalizeMeta as n, partsToNodes as t };
86
+
87
+ //# sourceMappingURL=rich-CHisXE-A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rich-CHisXE-A.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\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\n for (const part of parts) {\n const target = stack.length > 0 ? stack[stack.length - 1]!.children : result;\n\n if (isMarkupPart(part)) {\n if (part.kind === 'open') {\n stack.push({ name: part.name, children: [] });\n } else if (part.kind === 'close') {\n const frame = stack.pop();\n\n if (!frame) {\n // mismatched close tag, ignore\n continue;\n }\n\n const component = components[frame.name];\n const parent = stack.length > 0 ? stack[stack.length - 1]!.children : result;\n\n if (component) {\n parent.push(addKeyIfNeeded(component(frame.children), `rich-${keyIndex++}`));\n } else {\n // no component for this tag, just flatten children\n parent.push(...frame.children);\n }\n } else if (part.kind === 'standalone') {\n // self-closing: {#icon/}\n const component = components[part.name];\n\n if (component) {\n target.push(addKeyIfNeeded(component([]), `rich-${keyIndex++}`));\n }\n }\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 isolates - skip them (they're control characters for text direction)\n // could optionally include them for proper BiDi handling\n } else {\n // handle other part types (number, string, fallback, unknown, etc.)\n // they all have a value property we can stringify\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[stack.length - 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;;;;;;;;;;;;;;;;AAiBA,SAAgB,aAAgB,OAAe,YAA+C;CAC5F,MAAM,SAAyB,CAAC;CAChC,MAAM,QAAyB,CAAC;CAChC,IAAI,WAAW;CAEf,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,EAAE,CAAE,WAAW;EAEtE,IAAI,aAAa,IAAI;OACf,KAAK,SAAS,QAChB,MAAM,KAAK;IAAE,MAAM,KAAK;IAAM,UAAU,CAAC;GAAE,CAAC;QACvC,IAAI,KAAK,SAAS,SAAS;IAChC,MAAM,QAAQ,MAAM,IAAI;IAExB,IAAI,CAAC,OAEH;IAGF,MAAM,YAAY,WAAW,MAAM;IACnC,MAAM,SAAS,MAAM,SAAS,IAAI,MAAM,MAAM,SAAS,EAAE,CAAE,WAAW;IAEtE,IAAI,WACF,OAAO,KAAK,eAAe,UAAU,MAAM,QAAQ,GAAG,QAAQ,YAAY,CAAC;SAG3E,OAAO,KAAK,GAAG,MAAM,QAAQ;GAEjC,OAAO,IAAI,KAAK,SAAS,cAAc;IAErC,MAAM,YAAY,WAAW,KAAK;IAElC,IAAI,WACF,OAAO,KAAK,eAAe,UAAU,CAAC,CAAC,GAAG,QAAQ,YAAY,CAAC;GAEnE;SACK,IAAI,KAAK,SAAS,QAEvB,OAAO,KAAK,KAAK,KAAe;OAC3B,IAAI,KAAK,SAAS,iBAAiB,CAG1C,OAGE,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,MAAM,SAAS,EAAE,CAAE,WAAW,OAAA,CAE/D,KAAK,GAAG,MAAM,QAAQ;CAC/B;CAEA,OAAO;AACT"}