@astroscope/i18n 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -35
- package/dist/{babel-plugin-CWafmw5z.js → babel-plugin-DJZS9L_W.js} +28 -30
- package/dist/babel-plugin-DJZS9L_W.js.map +1 -0
- package/dist/client/translate.d.ts +2 -2
- package/dist/client/translate.js +3 -3
- package/dist/client/translate.js.map +1 -1
- package/dist/context-BCUJxb8p.js +19 -0
- package/dist/context-BCUJxb8p.js.map +1 -0
- package/dist/extraction/index.d.ts +5 -2
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +2 -2
- package/dist/{context-CgqIoo00.js → i18n-C47eQi8U.js} +34 -44
- package/dist/i18n-C47eQi8U.js.map +1 -0
- package/dist/{i18n-Czkrfb_4.d.ts → i18n-C4nlkQTf.d.ts} +19 -9
- package/dist/i18n-C4nlkQTf.d.ts.map +1 -0
- package/dist/index.d.ts +14 -60
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -14
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -1
- package/dist/{manifest-Cma68XM-.js → manifest-X-UhW6tH.js} +2 -2
- package/dist/manifest-X-UhW6tH.js.map +1 -0
- package/dist/{middleware-OnRVbxvs.js → middleware-BSEtDRYy.js} +21 -23
- package/dist/middleware-BSEtDRYy.js.map +1 -0
- package/dist/{rich-CHisXE-A.js → rich-BDqjhv2h.js} +31 -20
- package/dist/rich-BDqjhv2h.js.map +1 -0
- package/dist/server/chunk-middleware-entrypoint.js +1 -1
- package/dist/server/locale-middleware-entrypoint.d.ts +4 -0
- package/dist/server/locale-middleware-entrypoint.d.ts.map +1 -0
- package/dist/server/locale-middleware-entrypoint.js +8 -0
- package/dist/server/locale-middleware-entrypoint.js.map +1 -0
- package/dist/server/translate.d.ts +2 -2
- package/dist/server/translate.d.ts.map +1 -1
- package/dist/server/translate.js +4 -3
- package/dist/server/translate.js.map +1 -1
- package/dist/{types-f-SNJdvf.d.ts → types-BC-tQ30r.d.ts} +2 -2
- package/dist/{types-f-SNJdvf.d.ts.map → types-BC-tQ30r.d.ts.map} +1 -1
- package/dist/{types-DU8QHch4.d.ts → types-BjTD5hUT.d.ts} +6 -4
- package/dist/{types-DU8QHch4.d.ts.map → types-BjTD5hUT.d.ts.map} +1 -1
- package/dist/{types-Bo4jMK8g.d.ts → types-LT4Zo0bM.d.ts} +7 -1
- package/dist/types-LT4Zo0bM.d.ts.map +1 -0
- package/dist/{vite-plugin-C-jEpd8t.js → vite-plugin-CZqtAqQM.js} +83 -73
- package/dist/vite-plugin-CZqtAqQM.js.map +1 -0
- package/package.json +14 -9
- package/dist/babel-plugin-CWafmw5z.js.map +0 -1
- package/dist/context-CgqIoo00.js.map +0 -1
- package/dist/i18n-Czkrfb_4.d.ts.map +0 -1
- package/dist/manifest-Cma68XM-.js.map +0 -1
- package/dist/middleware-OnRVbxvs.js.map +0 -1
- package/dist/rich-CHisXE-A.js.map +0 -1
- package/dist/types-Bo4jMK8g.d.ts.map +0 -1
- package/dist/vite-plugin-C-jEpd8t.js.map +0 -1
package/README.md
CHANGED
|
@@ -62,6 +62,22 @@ i18n({
|
|
|
62
62
|
- **`'warn'`** — Log a warning but continue
|
|
63
63
|
- **`'off'`** — Disable consistency checking
|
|
64
64
|
|
|
65
|
+
#### Middleware
|
|
66
|
+
|
|
67
|
+
The integration injects two middlewares: one serving the `/_i18n/` translation chunks ahead of your own middleware, and the locale middleware after it — it resolves the request's locale and provides the request context for `t()`. Running after your middleware means the locale resolver sees what a session middleware put on `locals`.
|
|
68
|
+
|
|
69
|
+
Both middlewares are injected in the order of your `integrations` array — keep `i18n()` before integrations whose middleware calls `t()` (e.g. `@astroscope/wormhole`).
|
|
70
|
+
|
|
71
|
+
By default, `RECOMMENDED_EXCLUDES` (static assets like `/_astro/`) are excluded from locale context setup. To customize:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { RECOMMENDED_EXCLUDES } from '@astroscope/node/excludes';
|
|
75
|
+
|
|
76
|
+
i18n({
|
|
77
|
+
exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
65
81
|
### 2. Configure i18n in your boot file
|
|
66
82
|
|
|
67
83
|
> **Note:** `i18n.configure` must be awaited during boot, before any requests are handled.
|
|
@@ -80,6 +96,9 @@ export async function onStartup() {
|
|
|
80
96
|
await i18n.configure({
|
|
81
97
|
locales: ['en', 'de'],
|
|
82
98
|
defaultLocale: 'en', // optional, defaults to first locale
|
|
99
|
+
// optional, per request: a cookie, the session on `locals`, the url, `detectLocale(request)`
|
|
100
|
+
// for Accept-Language — `undefined` falls back to defaultLocale
|
|
101
|
+
locale: (ctx) => ctx.cookies.get('locale')?.value,
|
|
83
102
|
});
|
|
84
103
|
|
|
85
104
|
// load translations for all locales
|
|
@@ -93,36 +112,7 @@ export async function onStartup() {
|
|
|
93
112
|
}
|
|
94
113
|
```
|
|
95
114
|
|
|
96
|
-
### 3.
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
// src/middleware.ts
|
|
100
|
-
import { createI18nMiddleware, i18n } from '@astroscope/i18n';
|
|
101
|
-
|
|
102
|
-
export const onRequest = createI18nMiddleware({
|
|
103
|
-
locale: (ctx) =>
|
|
104
|
-
ctx.cookies.get('locale')?.value ??
|
|
105
|
-
i18n.getConfig().defaultLocale,
|
|
106
|
-
});
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The middleware serving `/_i18n/` translation chunks is injected automatically by the integration.
|
|
110
|
-
|
|
111
|
-
By default, `RECOMMENDED_EXCLUDES` (static assets like `/_astro/`) are excluded from locale context setup. To customize:
|
|
112
|
-
|
|
113
|
-
```ts
|
|
114
|
-
import { createI18nMiddleware, i18n } from '@astroscope/i18n';
|
|
115
|
-
import { RECOMMENDED_EXCLUDES } from '@astroscope/node/excludes';
|
|
116
|
-
|
|
117
|
-
export const onRequest = createI18nMiddleware({
|
|
118
|
-
locale: (ctx) =>
|
|
119
|
-
ctx.cookies.get('locale')?.value ??
|
|
120
|
-
i18n.getConfig().defaultLocale,
|
|
121
|
-
exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### 4. Use `t()` in your components
|
|
115
|
+
### 3. Use `t()` in your components
|
|
126
116
|
|
|
127
117
|
```astro
|
|
128
118
|
---
|
|
@@ -148,7 +138,7 @@ export function CheckoutSummary() {
|
|
|
148
138
|
|
|
149
139
|
> **Note:** Variables use `{$name}` syntax (with `$` prefix) per MessageFormat 2 specification.
|
|
150
140
|
|
|
151
|
-
That's it — client delivery is automatic. Use plain `client:*` directives; translations are injected into the page and preloaded per island by `@astroscope/node
|
|
141
|
+
That's it — client delivery is automatic. Use plain `client:*` directives; translations are injected into the page and preloaded per island by `@astroscope/node`.
|
|
152
142
|
|
|
153
143
|
## API
|
|
154
144
|
|
|
@@ -192,10 +182,10 @@ t('distance', '{$value :unit unit=kilometer}', { value: 42 })
|
|
|
192
182
|
|
|
193
183
|
// with metadata object (for extraction tooling)
|
|
194
184
|
t('cart.total', {
|
|
195
|
-
|
|
185
|
+
fallback: 'Total: {$amount}',
|
|
196
186
|
description: 'Cart total price',
|
|
197
187
|
variables: {
|
|
198
|
-
amount: {
|
|
188
|
+
amount: { fallback: '$0.00', description: 'Formatted price' }
|
|
199
189
|
}
|
|
200
190
|
}, { amount: '$49.99' })
|
|
201
191
|
```
|
|
@@ -255,7 +245,7 @@ i18n.setTranslations('en', { 'key': 'value' });
|
|
|
255
245
|
// get raw translations (includes manifest fallbacks)
|
|
256
246
|
i18n.getTranslations('en');
|
|
257
247
|
|
|
258
|
-
// get compiled translations (
|
|
248
|
+
// get compiled translations (MessageFormat 2 functions)
|
|
259
249
|
i18n.getCompiledTranslations('en');
|
|
260
250
|
|
|
261
251
|
// get extraction manifest
|
|
@@ -290,7 +280,7 @@ export function App() {
|
|
|
290
280
|
|
|
291
281
|
1. **Build time** — all `t()` calls are extracted, mapped to chunks, and fallbacks are stripped from production bundles
|
|
292
282
|
2. **Manifest** — Extracted keys with fallbacks are written to `i18n-manifest.json`
|
|
293
|
-
3. **SSR** —
|
|
283
|
+
3. **SSR** — the locale middleware provides translations to `t()`, merging manifest fallbacks for missing keys
|
|
294
284
|
4. **Client** — each island preloads only the translation chunks its code needs, in parallel with the component code
|
|
295
285
|
|
|
296
286
|
The same `import { t } from '@astroscope/i18n/translate'` works everywhere — bundler picks the correct implementation via conditional exports (`browser` vs `default`).
|
|
@@ -378,6 +368,7 @@ For `:currency` and `:unit`, the required option (`currency` or `unit`) can be:
|
|
|
378
368
|
| `locales` | `string[]` | required | Supported locales |
|
|
379
369
|
| `defaultLocale` | `string` | first locale | Default/fallback locale |
|
|
380
370
|
| `fallback` | `FallbackBehavior` | `'fallback'` | Behavior when translation missing |
|
|
371
|
+
| `locale` | `(ctx: APIContext) => string \| undefined` | `defaultLocale` for every request | Per-request locale resolver, run by the locale middleware |
|
|
381
372
|
|
|
382
373
|
### FallbackBehavior
|
|
383
374
|
|
|
@@ -29,12 +29,12 @@ function extractMeta(t, node, report) {
|
|
|
29
29
|
const name = prop.key.name;
|
|
30
30
|
if (name === "fallback") {
|
|
31
31
|
const value = getStringValue(t, prop.value);
|
|
32
|
-
if (value
|
|
33
|
-
else
|
|
32
|
+
if (value === null) report("the fallback is not a static string");
|
|
33
|
+
else meta.fallback = value;
|
|
34
34
|
} else if (name === "description") {
|
|
35
35
|
const value = getStringValue(t, prop.value);
|
|
36
|
-
if (value
|
|
37
|
-
else
|
|
36
|
+
if (value === null) report("the description is not a static string");
|
|
37
|
+
else meta.description = value;
|
|
38
38
|
} else if (name === "variables" && t.isObjectExpression(prop.value)) meta.variables = extractVariables(t, prop.value, report);
|
|
39
39
|
}
|
|
40
40
|
return meta;
|
|
@@ -55,24 +55,23 @@ function extractVariables(t, obj, report) {
|
|
|
55
55
|
if (!t.isObjectProperty(prop)) continue;
|
|
56
56
|
if (!t.isIdentifier(prop.key)) continue;
|
|
57
57
|
const varName = prop.key.name;
|
|
58
|
-
if (t.isObjectExpression(prop.value))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
if (!t.isObjectProperty(varProp)) continue;
|
|
66
|
-
if (!t.isIdentifier(varProp.key)) continue;
|
|
67
|
-
const varPropName = varProp.key.name;
|
|
68
|
-
const value = getStringValue(t, varProp.value);
|
|
69
|
-
if (value !== null) {
|
|
70
|
-
if (varPropName === "fallback") varDef.fallback = value;
|
|
71
|
-
else if (varPropName === "description") varDef.description = value;
|
|
72
|
-
} else if (varPropName === "fallback" || varPropName === "description") report(`variable "${varName}.${varPropName}" is not a static string`);
|
|
58
|
+
if (!t.isObjectExpression(prop.value)) continue;
|
|
59
|
+
const varDef = {};
|
|
60
|
+
for (const varProp of prop.value.properties) {
|
|
61
|
+
if (t.isSpreadElement(varProp)) {
|
|
62
|
+
report(`variable "${varName}" contains a spread element`);
|
|
63
|
+
continue;
|
|
73
64
|
}
|
|
74
|
-
|
|
65
|
+
if (!t.isObjectProperty(varProp)) continue;
|
|
66
|
+
if (!t.isIdentifier(varProp.key)) continue;
|
|
67
|
+
const varPropName = varProp.key.name;
|
|
68
|
+
const value = getStringValue(t, varProp.value);
|
|
69
|
+
if (value !== null) {
|
|
70
|
+
if (varPropName === "fallback") varDef.fallback = value;
|
|
71
|
+
else if (varPropName === "description") varDef.description = value;
|
|
72
|
+
} else if (varPropName === "fallback" || varPropName === "description") report(`variable "${varName}.${varPropName}" is not a static string`);
|
|
75
73
|
}
|
|
74
|
+
result[varName] = varDef;
|
|
76
75
|
}
|
|
77
76
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
78
77
|
}
|
|
@@ -109,19 +108,18 @@ function i18nExtractPlugin({ types: t }) {
|
|
|
109
108
|
file,
|
|
110
109
|
line
|
|
111
110
|
});
|
|
112
|
-
if (state.opts.stripFallbacks
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
111
|
+
if (!state.opts.stripFallbacks || args.length < 2) return;
|
|
112
|
+
const firstArg = args[0];
|
|
113
|
+
if (args.length === 2) path.node.arguments = [firstArg];
|
|
114
|
+
else path.node.arguments = [
|
|
115
|
+
firstArg,
|
|
116
|
+
t.identifier("undefined"),
|
|
117
|
+
args[2]
|
|
118
|
+
];
|
|
121
119
|
} }
|
|
122
120
|
};
|
|
123
121
|
}
|
|
124
122
|
//#endregion
|
|
125
123
|
export { i18nExtractPlugin };
|
|
126
124
|
|
|
127
|
-
//# sourceMappingURL=babel-plugin-
|
|
125
|
+
//# sourceMappingURL=babel-plugin-DJZS9L_W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"babel-plugin-DJZS9L_W.js","names":[],"sources":["../src/extraction/babel-plugin.ts"],"sourcesContent":["import type { NodePath, PluginObject, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport type { Expression, Node, ObjectExpression } from '@babel/types';\nimport type { TranslationMeta, VariableDef } from '../shared/types.js';\nimport type { ExtractedKeyOccurrence, ExtractionError } from './types.js';\n\nexport type I18nExtractPluginOpts = {\n onKeyExtracted: (key: ExtractedKeyOccurrence) => void;\n onExtractionError: (error: ExtractionError) => void;\n stripFallbacks: boolean;\n};\n\ntype PluginState = PluginPass & { opts: I18nExtractPluginOpts };\n\ntype ReportFn = (reason: string) => void;\n\n/**\n * Extract string value from AST node\n */\nfunction getStringValue(t: typeof BabelTypes, node: Node): string | null {\n if (t.isStringLiteral(node)) {\n return node.value;\n }\n\n if (t.isTemplateLiteral(node) && node.quasis.length === 1 && node.expressions.length === 0) {\n return node.quasis[0]?.value.cooked ?? null;\n }\n\n return null;\n}\n\n/**\n * Extract TranslationMeta from the second argument of t()\n */\nfunction extractMeta(t: typeof BabelTypes, node: Expression, report: ReportFn): TranslationMeta {\n // string shorthand: t('key', 'fallback')\n const stringValue = getStringValue(t, node);\n\n if (stringValue !== null) {\n return { fallback: stringValue };\n }\n\n // dynamic template literal: t('key', `Hello ${name}`)\n if (t.isTemplateLiteral(node) && node.expressions.length > 0) {\n report('the fallback is a template literal with expressions');\n\n return { fallback: '' };\n }\n\n // object form: t('key', { fallback: '...', description: '...', variables: {...} })\n if (t.isObjectExpression(node)) {\n const meta: TranslationMeta = { fallback: '' };\n\n for (const prop of node.properties) {\n // spread elements cannot be extracted statically\n if (t.isSpreadElement(prop)) {\n report('the meta object contains a spread element');\n\n continue;\n }\n\n if (!t.isObjectProperty(prop)) continue;\n if (!t.isIdentifier(prop.key)) continue;\n\n const name = prop.key.name;\n\n if (name === 'fallback') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value === null) {\n report('the fallback is not a static string');\n } else {\n meta.fallback = value;\n }\n } else if (name === 'description') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value === null) {\n report('the description is not a static string');\n } else {\n meta.description = value;\n }\n } else if (name === 'variables' && t.isObjectExpression(prop.value)) {\n meta.variables = extractVariables(t, prop.value, report);\n }\n }\n\n return meta;\n }\n\n // dynamic meta (e.g. variable reference)\n report('the meta is neither a static string nor an object literal');\n\n return { fallback: '' };\n}\n\n/**\n * Extract variables definition from object expression\n */\nfunction extractVariables(\n t: typeof BabelTypes,\n obj: ObjectExpression,\n report: ReportFn,\n): Record<string, VariableDef> | undefined {\n const result: Record<string, VariableDef> = {};\n\n for (const prop of obj.properties) {\n if (t.isSpreadElement(prop)) {\n report('the variables object contains a spread element');\n continue;\n }\n\n if (!t.isObjectProperty(prop)) continue;\n if (!t.isIdentifier(prop.key)) continue;\n\n const varName = prop.key.name;\n\n if (!t.isObjectExpression(prop.value)) continue;\n\n const varDef: VariableDef = {};\n\n for (const varProp of prop.value.properties) {\n if (t.isSpreadElement(varProp)) {\n report(`variable \"${varName}\" contains a spread element`);\n continue;\n }\n\n if (!t.isObjectProperty(varProp)) continue;\n if (!t.isIdentifier(varProp.key)) continue;\n\n const varPropName = varProp.key.name;\n const value = getStringValue(t, varProp.value as Node);\n\n if (value !== null) {\n if (varPropName === 'fallback') {\n varDef.fallback = value;\n } else if (varPropName === 'description') {\n varDef.description = value;\n }\n } else if (varPropName === 'fallback' || varPropName === 'description') {\n report(`variable \"${varName}.${varPropName}\" is not a static string`);\n }\n }\n\n result[varName] = varDef;\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Babel plugin for i18n extraction and transformation\n *\n * - Extracts t() calls with key and metadata\n * - Strips fallback argument in production builds\n */\nexport function i18nExtractPlugin({ types: t }: { types: typeof BabelTypes }): PluginObject<PluginState> {\n return {\n name: '@astroscope/i18n/extract',\n visitor: {\n CallExpression(path: NodePath<BabelTypes.CallExpression>, state: PluginState) {\n // matched by name only — an aliased or reassigned `t` is invisible here\n if (!path.get('callee').isIdentifier({ name: 't' })) return;\n\n const args = path.node.arguments;\n const keyArg = args[0];\n\n if (!t.isExpression(keyArg)) return;\n\n const key = getStringValue(t, keyArg);\n\n if (key === null) return; // first arg must be string\n\n const file = state.filename ?? '';\n const line = path.node.loc?.start.line ?? 0;\n const column = path.node.loc?.start.column ?? 0;\n const report: ReportFn = (reason) => state.opts.onExtractionError({ key, reason, file, line, column });\n\n // second arg is always meta (fallback string or object)\n // may not exist if we're processing already-transformed code\n const meta = args.length >= 2 && t.isExpression(args[1]) ? extractMeta(t, args[1], report) : { fallback: '' };\n\n state.opts.onKeyExtracted({ key, meta, file, line });\n\n // strip fallback in production\n // t('key', 'fallback') → t('key')\n // t('key', 'fallback', values) → t('key', undefined, values)\n if (!state.opts.stripFallbacks || args.length < 2) return;\n\n const firstArg = args[0]!;\n\n if (args.length === 2) {\n path.node.arguments = [firstArg];\n } else {\n path.node.arguments = [firstArg, t.identifier('undefined'), args[2]!];\n }\n },\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as RichComponents } from "../rich-BWszYAzS.js";
|
|
2
|
-
import { a as TranslationMeta, i as TranslateFunction } from "../types-
|
|
2
|
+
import { a as TranslationMeta, i as TranslateFunction } from "../types-BjTD5hUT.js";
|
|
3
3
|
//#region src/client/translate.d.ts
|
|
4
4
|
export declare function getLocale(): string;
|
|
5
5
|
/**
|
|
@@ -25,7 +25,7 @@ export declare const t: TranslateFunction;
|
|
|
25
25
|
* @param key - Translation key
|
|
26
26
|
* @param meta - Fallback string or meta object with fallback (used as message template)
|
|
27
27
|
* @param components - Map of tag names to component callbacks
|
|
28
|
-
* @param values - Runtime interpolation values (
|
|
28
|
+
* @param values - Runtime interpolation values (MessageFormat 2)
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
31
|
* ```tsx
|
package/dist/client/translate.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as formatMessageToParts, t as compileMessage } from "../compiler-CkOVGWU5.js";
|
|
2
|
-
import { n as normalizeMeta, t as partsToNodes } from "../rich-
|
|
2
|
+
import { n as normalizeMeta, t as partsToNodes } from "../rich-BDqjhv2h.js";
|
|
3
3
|
//#region src/client/state.ts
|
|
4
4
|
let warnedMissingState = false;
|
|
5
5
|
/**
|
|
@@ -12,7 +12,7 @@ function getI18nState() {
|
|
|
12
12
|
const state = window.__i18n__;
|
|
13
13
|
if (!state && !warnedMissingState) {
|
|
14
14
|
warnedMissingState = true;
|
|
15
|
-
console.error("[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. Make sure
|
|
15
|
+
console.error("[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. Make sure the @astroscope/i18n integration is set up and the page is not in its `exclude` list (the locale middleware injects the state) and, for per-island hashes, that the site uses the @astroscope/node adapter.");
|
|
16
16
|
}
|
|
17
17
|
return state;
|
|
18
18
|
}
|
|
@@ -67,7 +67,7 @@ const t = ((key, meta, values) => {
|
|
|
67
67
|
* @param key - Translation key
|
|
68
68
|
* @param meta - Fallback string or meta object with fallback (used as message template)
|
|
69
69
|
* @param components - Map of tag names to component callbacks
|
|
70
|
-
* @param values - Runtime interpolation values (
|
|
70
|
+
* @param values - Runtime interpolation values (MessageFormat 2)
|
|
71
71
|
*
|
|
72
72
|
* @example
|
|
73
73
|
* ```tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","names":[],"sources":["../../src/client/state.ts","../../src/client/translate.ts"],"sourcesContent":["import type { I18nClientState } from '../shared/types.js';\nimport './types.js';\n\nlet warnedMissingState = false;\n\n/**\n * Read the client i18n state injected by the i18n middleware and islands emitter.\n *\n * Returns undefined and logs an actionable error (once) when the state was not\n * injected — without it window.__i18n__ is undefined and translations cannot load.\n */\nexport function getI18nState(): I18nClientState | undefined {\n const state = window.__i18n__;\n\n if (!state && !warnedMissingState) {\n warnedMissingState = true;\n\n console.error(\n '[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. ' +\n
|
|
1
|
+
{"version":3,"file":"translate.js","names":[],"sources":["../../src/client/state.ts","../../src/client/translate.ts"],"sourcesContent":["import type { I18nClientState } from '../shared/types.js';\nimport './types.js';\n\nlet warnedMissingState = false;\n\n/**\n * Read the client i18n state injected by the i18n middleware and islands emitter.\n *\n * Returns undefined and logs an actionable error (once) when the state was not\n * injected — without it window.__i18n__ is undefined and translations cannot load.\n */\nexport function getI18nState(): I18nClientState | undefined {\n const state = window.__i18n__;\n\n if (!state && !warnedMissingState) {\n warnedMissingState = true;\n\n console.error(\n '[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. ' +\n 'Make sure the @astroscope/i18n integration is set up and the page is not in its `exclude` list ' +\n '(the locale middleware injects the state) and, for per-island hashes, that the site uses the ' +\n '@astroscope/node adapter.',\n );\n }\n\n return state;\n}\n","import { 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 { getI18nState } from './state.js';\n\nconst cache = new Map<string, CompiledTranslation>();\n\nexport function getLocale(): string {\n return getI18nState()?.locale ?? '';\n}\n\n/**\n * Client-side translate function\n *\n * Reads from window.__i18n__.translations (injected per-chunk).\n * In production, translations are always loaded before component code runs\n * via the chunk loader injected by the Vite plugin.\n *\n * Call patterns:\n * - Development: t('key', 'fallback') or t('key', 'fallback', values)\n * - Production: t('key') or t('key', undefined, values)\n *\n * Falls back to meta.fallback (dev) or key (prod) when translation is missing.\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 state = getI18nState();\n const locale = state?.locale ?? '';\n\n const raw = state?.translations[key];\n\n if (!raw) {\n // translation missing: use fallback (dev) or key (prod)\n if (normalizedMeta.fallback) {\n let compiled = cache.get(`__fallback__${key}`);\n\n if (!compiled) {\n compiled = compileMessage(locale, normalizedMeta.fallback);\n cache.set(`__fallback__${key}`, compiled);\n }\n\n return compiled(values);\n }\n\n return key;\n }\n\n let compiled = cache.get(key);\n\n if (!compiled) {\n compiled = compileMessage(locale, raw);\n cache.set(key, compiled);\n }\n\n return compiled(values);\n}) as TranslateFunction;\n\n/**\n * Client-side rich text translation function\n *\n * Parses MF2 markup syntax and applies component callbacks to wrap content.\n * Works with any JSX runtime (React, Preact, 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 state = getI18nState();\n\n const raw = state?.translations[key] || normalizedMeta.fallback || key;\n\n const parts = formatMessageToParts(state?.locale ?? '', raw, values);\n\n return partsToNodes(parts, components ?? {});\n}\n"],"mappings":";;;AAGA,IAAI,qBAAqB;;;;;;;AAQzB,SAAgB,eAA4C;CAC1D,MAAM,QAAQ,OAAO;CAErB,IAAI,CAAC,SAAS,CAAC,oBAAoB;EACjC,qBAAqB;EAErB,QAAQ,MACN,qTAIF;CACF;CAEA,OAAO;AACT;;;ACpBA,MAAM,wBAAQ,IAAI,IAAiC;AAEnD,SAAgB,YAAoB;CAClC,OAAO,aAAa,CAAC,EAAE,UAAU;AACnC;;;;;;;;;;;;;;AAeA,MAAa,MACX,KACA,MACA,WACW;CACX,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,QAAQ,aAAa;CAC3B,MAAM,SAAS,OAAO,UAAU;CAEhC,MAAM,MAAM,OAAO,aAAa;CAEhC,IAAI,CAAC,KAAK;EAER,IAAI,eAAe,UAAU;GAC3B,IAAI,WAAW,MAAM,IAAI,eAAe,KAAK;GAE7C,IAAI,CAAC,UAAU;IACb,WAAW,eAAe,QAAQ,eAAe,QAAQ;IACzD,MAAM,IAAI,eAAe,OAAO,QAAQ;GAC1C;GAEA,OAAO,SAAS,MAAM;EACxB;EAEA,OAAO;CACT;CAEA,IAAI,WAAW,MAAM,IAAI,GAAG;CAE5B,IAAI,CAAC,UAAU;EACb,WAAW,eAAe,QAAQ,GAAG;EACrC,MAAM,IAAI,KAAK,QAAQ;CACzB;CAEA,OAAO,SAAS,MAAM;AACxB;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,KACd,KACA,MACA,YACA,QACgB;CAChB,MAAM,iBAAkC,OAAO,cAAc,IAAI,IAAI,EAAE,UAAU,GAAG;CAEpF,MAAM,QAAQ,aAAa;CAE3B,MAAM,MAAM,OAAO,aAAa,QAAQ,eAAe,YAAY;CAEnE,MAAM,QAAQ,qBAAqB,OAAO,UAAU,IAAI,KAAK,MAAM;CAEnE,OAAO,aAAa,OAAO,cAAc,CAAC,CAAC;AAC7C"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
//#region src/server/context.ts
|
|
3
|
+
const als = new AsyncLocalStorage();
|
|
4
|
+
/**
|
|
5
|
+
* Run a function with i18n context (called by middleware)
|
|
6
|
+
*/
|
|
7
|
+
function runWithContext(context, fn) {
|
|
8
|
+
return als.run(context, fn);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get the current i18n context
|
|
12
|
+
*/
|
|
13
|
+
function getContext() {
|
|
14
|
+
return als.getStore() ?? null;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { runWithContext as n, getContext as t };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=context-BCUJxb8p.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-BCUJxb8p.js","names":[],"sources":["../src/server/context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { I18nContext } from './types.js';\n\nconst als = new AsyncLocalStorage<I18nContext>();\n\n/**\n * Run a function with i18n context (called by middleware)\n */\nexport function runWithContext<T>(context: I18nContext, fn: () => T): T {\n return als.run(context, fn);\n}\n\n/**\n * Get the current i18n context\n */\nexport function getContext(): I18nContext | null {\n return als.getStore() ?? null;\n}\n"],"mappings":";;AAGA,MAAM,MAAM,IAAI,kBAA+B;;;;AAK/C,SAAgB,eAAkB,SAAsB,IAAgB;CACtE,OAAO,IAAI,IAAI,SAAS,EAAE;AAC5B;;;;AAKA,SAAgB,aAAiC;CAC/C,OAAO,IAAI,SAAS,KAAK;AAC3B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { n as ExtractedKey, r as ExtractionManifest, t as ChunkManifest } from "../types-
|
|
2
|
-
import { t as ConsistencyCheckLevel } from "../types-
|
|
1
|
+
import { n as ExtractedKey, r as ExtractionManifest, t as ChunkManifest } from "../types-BC-tQ30r.js";
|
|
2
|
+
import { t as ConsistencyCheckLevel } from "../types-LT4Zo0bM.js";
|
|
3
|
+
import { ExcludePattern } from "@astroscope/node/excludes";
|
|
3
4
|
import { AstroIntegrationLogger } from "astro";
|
|
4
5
|
import { Plugin } from "vite";
|
|
5
6
|
//#region src/extraction/manifest.d.ts
|
|
@@ -13,6 +14,8 @@ export declare function getManifest(): ExtractionManifest;
|
|
|
13
14
|
type I18nVitePluginOptions = {
|
|
14
15
|
logger: AstroIntegrationLogger;
|
|
15
16
|
consistency: ConsistencyCheckLevel;
|
|
17
|
+
/** the locale middleware's exclude patterns, null for the default */
|
|
18
|
+
exclude: ExcludePattern[] | null;
|
|
16
19
|
};
|
|
17
20
|
/**
|
|
18
21
|
* Vite plugin for i18n extraction, chunk mapping, and loader injection
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/extraction/manifest.ts","../../src/extraction/vite-plugin.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/extraction/manifest.ts","../../src/extraction/vite-plugin.ts"],"mappings":";;;;;;;;;;wBA4CgB,eAAe;;;KCjBnB;EACV,QAAQ;EACR,aAAa;;EAEb,SAAS;;;;;;;;;;;;wBAuCK,eAAe,SAAS,wBAAwB"}
|
package/dist/extraction/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as i18nVitePlugin } from "../vite-plugin-
|
|
2
|
-
import { n as getManifest } from "../manifest-
|
|
1
|
+
import { t as i18nVitePlugin } from "../vite-plugin-CZqtAqQM.js";
|
|
2
|
+
import { n as getManifest } from "../manifest-X-UhW6tH.js";
|
|
3
3
|
export { getManifest, i18nVitePlugin };
|
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
import { t as getGlobalState } from "./manifest-
|
|
1
|
+
import { t as getGlobalState } from "./manifest-X-UhW6tH.js";
|
|
2
2
|
import { n as compileTranslations } from "./compiler-CkOVGWU5.js";
|
|
3
3
|
import { createObservableGauge } from "@astroscope/node/telemetry";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
5
|
//#region src/extraction/hash.ts
|
|
7
6
|
/**
|
|
8
7
|
* Hash a string to a short hash (for cache busting)
|
|
9
8
|
*/
|
|
10
9
|
function hashString(str) {
|
|
11
|
-
return createHash("sha256").update(str).digest("hex").
|
|
10
|
+
return createHash("sha256").update(str).digest("hex").slice(0, 8);
|
|
11
|
+
}
|
|
12
|
+
function compareKeys(a, b) {
|
|
13
|
+
if (a === b) return 0;
|
|
14
|
+
return a < b ? -1 : 1;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Compute hash for chunk translations
|
|
15
18
|
*/
|
|
16
19
|
function computeChunkHash(translations, keys) {
|
|
17
20
|
const relevantTranslations = {};
|
|
18
|
-
|
|
21
|
+
const sortedKeys = keys.toSorted(compareKeys);
|
|
22
|
+
for (const key of sortedKeys) if (translations[key] !== void 0) relevantTranslations[key] = translations[key];
|
|
19
23
|
return hashString(JSON.stringify(relevantTranslations));
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
@@ -38,6 +42,20 @@ var I18nSingleton = class {
|
|
|
38
42
|
updatedAt = /* @__PURE__ */ new Map();
|
|
39
43
|
manifestGetter = null;
|
|
40
44
|
manifestVersion = 0;
|
|
45
|
+
/** Invalidate derived caches when the extraction manifest changed (dev HMR). */
|
|
46
|
+
invalidateIfManifestChanged() {
|
|
47
|
+
const { version } = getGlobalState();
|
|
48
|
+
if (version === this.manifestVersion) return;
|
|
49
|
+
this.manifestVersion = version;
|
|
50
|
+
this.mergedCache.clear();
|
|
51
|
+
this.compiledCache.clear();
|
|
52
|
+
this.chunkCache.clear();
|
|
53
|
+
}
|
|
54
|
+
resolveLocales(locales) {
|
|
55
|
+
if (!locales) return this.config?.locales ?? [];
|
|
56
|
+
if (typeof locales === "string") return [locales];
|
|
57
|
+
return locales;
|
|
58
|
+
}
|
|
41
59
|
async configure(config) {
|
|
42
60
|
if (!config.locales?.length) throw new Error("i18n.configure(): locales array is required and must not be empty");
|
|
43
61
|
for (const locale of config.locales) {
|
|
@@ -53,12 +71,12 @@ var I18nSingleton = class {
|
|
|
53
71
|
this.config = {
|
|
54
72
|
locales: config.locales,
|
|
55
73
|
defaultLocale: config.defaultLocale ?? config.locales[0],
|
|
56
|
-
fallback: config.fallback ?? "fallback"
|
|
74
|
+
fallback: config.fallback ?? "fallback",
|
|
75
|
+
locale: config.locale
|
|
57
76
|
};
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
77
|
+
if (this.manifestGetter) return;
|
|
78
|
+
const m = await import("virtual:@astroscope/i18n/manifest");
|
|
79
|
+
this.manifestGetter = m.getManifest;
|
|
62
80
|
}
|
|
63
81
|
isConfigured() {
|
|
64
82
|
return this.config !== null;
|
|
@@ -89,7 +107,7 @@ var I18nSingleton = class {
|
|
|
89
107
|
};
|
|
90
108
|
if (manifest.keys.length === 0) return raw;
|
|
91
109
|
const withFallbacks = { ...raw };
|
|
92
|
-
for (const { key, meta } of manifest.keys) if (!(key
|
|
110
|
+
for (const { key, meta } of manifest.keys) if (!Object.hasOwn(withFallbacks, key) && meta.fallback) withFallbacks[key] = meta.fallback;
|
|
93
111
|
this.mergedCache.set(locale, withFallbacks);
|
|
94
112
|
return withFallbacks;
|
|
95
113
|
}
|
|
@@ -125,7 +143,10 @@ var I18nSingleton = class {
|
|
|
125
143
|
if (cached) return cached;
|
|
126
144
|
const translations = this.getTranslations(locale);
|
|
127
145
|
const chunkTranslations = {};
|
|
128
|
-
for (const key of keys)
|
|
146
|
+
for (const key of keys) {
|
|
147
|
+
const translation = translations[key];
|
|
148
|
+
if (translation) chunkTranslations[key] = translation;
|
|
149
|
+
}
|
|
129
150
|
const js = `/* @astroscope/i18n chunk: ${chunkName} */
|
|
130
151
|
(function() {
|
|
131
152
|
var i = window.__i18n__;
|
|
@@ -173,22 +194,6 @@ var I18nSingleton = class {
|
|
|
173
194
|
seconds: (now - at) / 1e3
|
|
174
195
|
}));
|
|
175
196
|
}
|
|
176
|
-
/**
|
|
177
|
-
* invalidate derived caches when the extraction manifest has changed (dev mode HMR)
|
|
178
|
-
*/
|
|
179
|
-
invalidateIfManifestChanged() {
|
|
180
|
-
const { version } = getGlobalState();
|
|
181
|
-
if (version === this.manifestVersion) return;
|
|
182
|
-
this.manifestVersion = version;
|
|
183
|
-
this.mergedCache.clear();
|
|
184
|
-
this.compiledCache.clear();
|
|
185
|
-
this.chunkCache.clear();
|
|
186
|
-
}
|
|
187
|
-
resolveLocales(locales) {
|
|
188
|
-
if (!locales) return this.config?.locales ?? [];
|
|
189
|
-
if (typeof locales === "string") return [locales];
|
|
190
|
-
return locales;
|
|
191
|
-
}
|
|
192
197
|
};
|
|
193
198
|
const i18n = new I18nSingleton();
|
|
194
199
|
createObservableGauge("astro.i18n.translations.age", {
|
|
@@ -199,21 +204,6 @@ createObservableGauge("astro.i18n.translations.age", {
|
|
|
199
204
|
for (const { locale, seconds } of i18n.getTranslationsAge()) result.observe(seconds, { "astro.i18n.locale": locale });
|
|
200
205
|
});
|
|
201
206
|
//#endregion
|
|
202
|
-
|
|
203
|
-
const als = new AsyncLocalStorage();
|
|
204
|
-
/**
|
|
205
|
-
* Run a function with i18n context (called by middleware)
|
|
206
|
-
*/
|
|
207
|
-
function runWithContext(context, fn) {
|
|
208
|
-
return als.run(context, fn);
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Get the current i18n context
|
|
212
|
-
*/
|
|
213
|
-
function getContext() {
|
|
214
|
-
return als.getStore() ?? null;
|
|
215
|
-
}
|
|
216
|
-
//#endregion
|
|
217
|
-
export { runWithContext as n, i18n as r, getContext as t };
|
|
207
|
+
export { i18n as t };
|
|
218
208
|
|
|
219
|
-
//# sourceMappingURL=
|
|
209
|
+
//# sourceMappingURL=i18n-C47eQi8U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-C47eQi8U.js","names":[],"sources":["../src/extraction/hash.ts","../src/server/i18n.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { RawTranslations } from '../shared/types.js';\nimport type { ChunkManifest } from './types.js';\n\n/**\n * Hash a string to a short hash (for cache busting)\n */\nfunction hashString(str: string): string {\n return createHash('sha256').update(str).digest('hex').slice(0, 8);\n}\n\n// code-unit order (what the default sort does): the order feeds the hash, so it must not depend on icu locale data\nfunction compareKeys(a: string, b: string): number {\n if (a === b) return 0;\n\n return a < b ? -1 : 1;\n}\n\n/**\n * Compute hash for chunk translations\n */\nexport function computeChunkHash(translations: RawTranslations, keys: string[]): string {\n const relevantTranslations: RawTranslations = {};\n const sortedKeys = keys.toSorted(compareKeys);\n\n for (const key of sortedKeys) {\n if (translations[key] !== undefined) {\n relevantTranslations[key] = translations[key];\n }\n }\n\n return hashString(JSON.stringify(relevantTranslations));\n}\n\n/**\n * Compute hashes for all chunks in manifest\n */\nexport function computeAllChunkHashes(translations: RawTranslations, manifest: ChunkManifest): Record<string, string> {\n const hashes: Record<string, string> = {};\n\n for (const [chunkId, keys] of Object.entries(manifest)) {\n hashes[chunkId] = computeChunkHash(translations, keys);\n }\n\n return hashes;\n}\n","import { createObservableGauge } from '@astroscope/node/telemetry';\nimport type { APIContext } from 'astro';\nimport { computeAllChunkHashes } from '../extraction/hash.js';\nimport { getGlobalState } from '../extraction/manifest.js';\nimport type { ExtractionManifest } from '../extraction/types.js';\nimport { compileTranslations } from '../shared/compiler.js';\nimport type { CompiledTranslations, RawTranslations } from '../shared/types.js';\nimport type { FallbackBehavior } from './types.js';\n\n/**\n * Resolves a request's locale — from a cookie, the session on `locals`, the url or\n * `detectLocale(request)`. `undefined` falls back to the default locale.\n */\nexport type LocaleResolver = (ctx: APIContext) => string | undefined;\n\nexport type I18nConfig = {\n locales: string[];\n defaultLocale?: string | undefined;\n fallback?: FallbackBehavior | undefined;\n /**\n * Called by the locale middleware for every request it does not exclude. Without\n * a resolver every request renders in the default locale.\n */\n locale?: LocaleResolver | undefined;\n};\n\ntype NormalizedConfig = {\n locales: string[];\n defaultLocale: string;\n fallback: FallbackBehavior;\n locale: LocaleResolver | undefined;\n};\n\nclass I18nSingleton {\n private config: NormalizedConfig | null = null;\n\n // locale -> raw translation strings (before MessageFormat compilation)\n private rawCache = new Map<string, RawTranslations>();\n\n // locale -> raw translations merged with manifest fallbacks\n private mergedCache = new Map<string, RawTranslations>();\n\n // locale -> compiled MessageFormat 2 functions\n private compiledCache = new Map<string, CompiledTranslations>();\n\n // locale -> chunk name -> content hash (for cache busting)\n private hashCache = new Map<string, Record<string, string>>();\n\n // \"locale:chunkName\" -> encoded chunk response body\n private chunkCache = new Map<string, Uint8Array>();\n\n // locale -> epoch ms of the last setTranslations, for the age gauge\n private updatedAt = new Map<string, number>();\n\n // manifest getter, set on configure() via the virtual module (live data in dev mode)\n private manifestGetter: (() => ExtractionManifest) | null = null;\n\n // tracks the manifest version to detect when derived caches need invalidation\n private manifestVersion = 0;\n\n /** Invalidate derived caches when the extraction manifest changed (dev HMR). */\n private invalidateIfManifestChanged(): void {\n const { version } = getGlobalState();\n\n if (version === this.manifestVersion) {\n return;\n }\n\n this.manifestVersion = version;\n this.mergedCache.clear();\n this.compiledCache.clear();\n this.chunkCache.clear();\n }\n\n private resolveLocales(locales?: string | string[] | undefined): string[] {\n if (!locales) {\n return this.config?.locales ?? [];\n }\n\n if (typeof locales === 'string') {\n return [locales];\n }\n\n return locales;\n }\n\n async configure(config: I18nConfig): Promise<void> {\n if (!config.locales?.length) {\n throw new Error('i18n.configure(): locales array is required and must not be empty');\n }\n\n for (const locale of config.locales) {\n if (!locale) {\n throw new Error('i18n.configure(): locale cannot be empty');\n }\n\n if (locale !== locale.trim()) {\n throw new Error(`i18n.configure(): locale \"${locale}\" has leading or trailing whitespace`);\n }\n }\n\n if (new Set(config.locales).size !== config.locales.length) {\n throw new Error('i18n.configure(): locales array contains duplicates');\n }\n\n if (config.defaultLocale !== undefined) {\n if (!config.defaultLocale) {\n throw new Error('i18n.configure(): defaultLocale cannot be empty');\n }\n\n if (config.defaultLocale !== config.defaultLocale.trim()) {\n throw new Error(`i18n.configure(): defaultLocale \"${config.defaultLocale}\" has leading or trailing whitespace`);\n }\n\n if (!config.locales.includes(config.defaultLocale)) {\n throw new Error(`i18n.configure(): defaultLocale \"${config.defaultLocale}\" is not in locales array`);\n }\n }\n\n this.config = {\n locales: config.locales,\n defaultLocale: config.defaultLocale ?? config.locales[0]!,\n fallback: config.fallback ?? 'fallback',\n locale: config.locale,\n };\n\n // resolved lazily so importing the singleton needs no virtual module\n if (this.manifestGetter) {\n return;\n }\n\n const m = await import('virtual:@astroscope/i18n/manifest');\n\n this.manifestGetter = m.getManifest;\n }\n\n isConfigured(): boolean {\n return this.config !== null;\n }\n\n getConfig(): NormalizedConfig {\n if (!this.config) {\n throw new Error('i18n not configured. Call i18n.configure() first.');\n }\n\n return this.config;\n }\n\n setTranslations(locale: string, raw: RawTranslations): void {\n this.rawCache.set(locale, raw);\n this.updatedAt.set(locale, Date.now());\n this.mergedCache.delete(locale);\n this.compiledCache.delete(locale);\n this.hashCache.delete(locale);\n\n for (const key of this.chunkCache.keys()) {\n if (key.startsWith(`${locale}:`)) {\n this.chunkCache.delete(key);\n }\n }\n\n // recompute hashes if chunk manifest exists (production only)\n const { chunks } = this.getManifest();\n\n if (Object.keys(chunks).length > 0) {\n this.hashCache.set(locale, computeAllChunkHashes(raw, chunks));\n }\n }\n\n getTranslations(locale: string): RawTranslations {\n this.invalidateIfManifestChanged();\n\n const cached = this.mergedCache.get(locale);\n\n if (cached) {\n return cached;\n }\n\n const raw = this.rawCache.get(locale) ?? {};\n const manifest = this.manifestGetter?.() ?? { keys: [], chunks: {}, scripts: [] };\n\n if (manifest.keys.length === 0) {\n return raw;\n }\n\n // merge fallbacks for keys that don't have translations\n const withFallbacks: RawTranslations = { ...raw };\n\n for (const { key, meta } of manifest.keys) {\n if (!Object.hasOwn(withFallbacks, key) && meta.fallback) {\n withFallbacks[key] = meta.fallback;\n }\n }\n\n this.mergedCache.set(locale, withFallbacks);\n\n return withFallbacks;\n }\n\n getCompiledTranslations(locale: string): CompiledTranslations {\n const cached = this.compiledCache.get(locale);\n\n if (cached) {\n return cached;\n }\n\n const compiled = compileTranslations(locale, this.getTranslations(locale));\n\n this.compiledCache.set(locale, compiled);\n\n return compiled;\n }\n\n /**\n * Get the extraction manifest (keys + chunks).\n * - keys: translation keys extracted from codebase with metadata\n * - chunks: mapping of chunk names to their translation keys\n */\n getManifest(): ExtractionManifest {\n if (!this.manifestGetter) {\n throw new Error('i18n manifest not initialized. Call i18n.configure() first.');\n }\n\n return this.manifestGetter();\n }\n\n getHashes(locale: string): Record<string, string> {\n return this.hashCache.get(locale) ?? {};\n }\n\n /**\n * Get the encoded chunk response body for a locale/chunk combination.\n * Cached to avoid repeated JSON.stringify and encoding on each request.\n * Returns undefined if chunk not found in manifest.\n */\n getChunkBody(locale: string, chunkName: string): Uint8Array | undefined {\n const keys = this.getManifest().chunks[chunkName];\n\n if (!keys) {\n return undefined;\n }\n\n const cacheKey = `${locale}:${chunkName}`;\n const cached = this.chunkCache.get(cacheKey);\n\n if (cached) {\n return cached;\n }\n\n const translations = this.getTranslations(locale);\n const chunkTranslations: RawTranslations = {};\n\n for (const key of keys) {\n const translation = translations[key];\n\n if (translation) {\n chunkTranslations[key] = translation;\n }\n }\n\n const js = `/* @astroscope/i18n chunk: ${chunkName} */\n(function() {\n var i = window.__i18n__;\n if (i) Object.assign(i.translations, ${JSON.stringify(chunkTranslations)});\n})();\n`;\n\n const body = new TextEncoder().encode(js);\n\n this.chunkCache.set(cacheKey, body);\n\n return body;\n }\n\n /**\n * Hashes for chunks reachable from astro `<script>` entries. Script modules are\n * not islands, so the islands emitter never covers them — the middleware\n * bootstraps these few into the client state on every html response.\n */\n getScriptChunkHashes(locale: string): Record<string, string> {\n const { scripts } = this.getManifest();\n const hashes = this.getHashes(locale);\n const result: Record<string, string> = {};\n\n for (const chunk of scripts) {\n const hash = hashes[chunk];\n\n if (hash) {\n result[chunk] = hash;\n }\n }\n\n return result;\n }\n\n /**\n * Clear translations for specified locale(s).\n * @param locales - Locale(s) to clear, or all if not specified\n */\n clear(locales?: string | string[] | undefined): void {\n const targets = this.resolveLocales(locales);\n\n for (const locale of targets) {\n this.rawCache.delete(locale);\n this.updatedAt.delete(locale);\n this.mergedCache.delete(locale);\n this.compiledCache.delete(locale);\n this.hashCache.delete(locale);\n\n for (const key of this.chunkCache.keys()) {\n if (key.startsWith(`${locale}:`)) {\n this.chunkCache.delete(key);\n }\n }\n }\n }\n\n /** seconds since each locale's translations were last set */\n getTranslationsAge(now = Date.now()): { locale: string; seconds: number }[] {\n return [...this.updatedAt].map(([locale, at]) => ({ locale, seconds: (now - at) / 1000 }));\n }\n}\n\nexport const i18n = new I18nSingleton();\n\n// the one signal for a stalled refresh loop: an app that reloads translations\n// periodically shows a sawtooth here, a stuck one a line going up\ncreateObservableGauge(\n 'astro.i18n.translations.age',\n { scope: '@astroscope/i18n', description: 'Seconds since the translations of a locale were last set', unit: 's' },\n (result) => {\n for (const { locale, seconds } of i18n.getTranslationsAge()) {\n result.observe(seconds, { 'astro.i18n.locale': locale });\n }\n },\n);\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { a as TranslationMeta, r as RawTranslations, t as CompiledTranslations } from "./types-
|
|
2
|
-
import { r as ExtractionManifest } from "./types-
|
|
1
|
+
import { a as TranslationMeta, r as RawTranslations, t as CompiledTranslations } from "./types-BjTD5hUT.js";
|
|
2
|
+
import { r as ExtractionManifest } from "./types-BC-tQ30r.js";
|
|
3
|
+
import { APIContext } from "astro";
|
|
3
4
|
//#region src/server/types.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* Fallback behavior when translation is missing
|
|
@@ -16,15 +17,26 @@ type I18nContext = {
|
|
|
16
17
|
};
|
|
17
18
|
//#endregion
|
|
18
19
|
//#region src/server/i18n.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* Resolves a request's locale — from a cookie, the session on `locals`, the url or
|
|
22
|
+
* `detectLocale(request)`. `undefined` falls back to the default locale.
|
|
23
|
+
*/
|
|
24
|
+
type LocaleResolver = (ctx: APIContext) => string | undefined;
|
|
19
25
|
type I18nConfig = {
|
|
20
26
|
locales: string[];
|
|
21
27
|
defaultLocale?: string | undefined;
|
|
22
28
|
fallback?: FallbackBehavior | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Called by the locale middleware for every request it does not exclude. Without
|
|
31
|
+
* a resolver every request renders in the default locale.
|
|
32
|
+
*/
|
|
33
|
+
locale?: LocaleResolver | undefined;
|
|
23
34
|
};
|
|
24
35
|
type NormalizedConfig = {
|
|
25
36
|
locales: string[];
|
|
26
37
|
defaultLocale: string;
|
|
27
38
|
fallback: FallbackBehavior;
|
|
39
|
+
locale: LocaleResolver | undefined;
|
|
28
40
|
};
|
|
29
41
|
declare class I18nSingleton {
|
|
30
42
|
private config;
|
|
@@ -36,6 +48,9 @@ declare class I18nSingleton {
|
|
|
36
48
|
private updatedAt;
|
|
37
49
|
private manifestGetter;
|
|
38
50
|
private manifestVersion;
|
|
51
|
+
/** Invalidate derived caches when the extraction manifest changed (dev HMR). */
|
|
52
|
+
private invalidateIfManifestChanged;
|
|
53
|
+
private resolveLocales;
|
|
39
54
|
configure(config: I18nConfig): Promise<void>;
|
|
40
55
|
isConfigured(): boolean;
|
|
41
56
|
getConfig(): NormalizedConfig;
|
|
@@ -71,13 +86,8 @@ declare class I18nSingleton {
|
|
|
71
86
|
locale: string;
|
|
72
87
|
seconds: number;
|
|
73
88
|
}[];
|
|
74
|
-
/**
|
|
75
|
-
* invalidate derived caches when the extraction manifest has changed (dev mode HMR)
|
|
76
|
-
*/
|
|
77
|
-
private invalidateIfManifestChanged;
|
|
78
|
-
private resolveLocales;
|
|
79
89
|
}
|
|
80
90
|
declare const i18n: I18nSingleton;
|
|
81
91
|
//#endregion
|
|
82
|
-
export { I18nContext as i,
|
|
83
|
-
//# sourceMappingURL=i18n-
|
|
92
|
+
export { I18nContext as a, FallbackBehavior as i, LocaleResolver as n, i18n as r, I18nConfig as t };
|
|
93
|
+
//# sourceMappingURL=i18n-C4nlkQTf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-C4nlkQTf.d.ts","names":[],"sources":["../src/server/types.ts","../src/server/i18n.ts"],"mappings":";;;;;;;KAKY,oDACuB,aAAa,OAAO;;;;KAK3C;EACV;EAEA,cAAc;EAEd,iBAAiB;EACjB,UAAU;;;;;;;;KCJA,kBAAkB,KAAK;KAEvB;EACV;EACA;EACA,WAAW;;;;;EAKX,SAAS;;KAGN;EACH;EACA;EACA,UAAU;EACV,QAAQ;;cAGJ;UACI;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;;UAGA;UAaA;EAYF,UAAU,QAAQ,aAAa;EAkDrC;EAIA,aAAa;EAQb,gBAAgB,gBAAgB,KAAK;EAqBrC,gBAAgB,iBAAiB;EA8BjC,wBAAwB,iBAAiB;;;;;;EAmBzC,eAAe;EAQf,UAAU,iBAAiB;;;;;;EAS3B,aAAa,gBAAgB,oBAAoB;;;;;;EA4CjD,qBAAqB,iBAAiB;;;;;EAoBtC,MAAM;;EAmBN,mBAAmB;IAAqB;IAAgB;;;cAK7C,MAAI"}
|