@astroscope/i18n 1.1.2 → 2.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 +28 -72
- package/dist/babel-plugin-CWafmw5z.js.map +1 -1
- package/dist/client/translate.d.ts +1 -1
- package/dist/client/translate.d.ts.map +1 -1
- package/dist/client/translate.js +19 -2
- package/dist/client/translate.js.map +1 -1
- package/dist/{context-BEQ6qqX5.js → context-CYqyZJyH.js} +14 -66
- package/dist/context-CYqyZJyH.js.map +1 -0
- package/dist/extraction/index.d.ts +1 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +2 -2
- package/dist/{i18n-Dzh54-at.d.ts → i18n-Bp8TFmd3.d.ts} +7 -8
- package/dist/i18n-Bp8TFmd3.d.ts.map +1 -0
- package/dist/index.d.ts +14 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -152
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +1 -1
- package/dist/{manifest-BEm6GezL.js → manifest-Cma68XM-.js} +3 -3
- package/dist/manifest-Cma68XM-.js.map +1 -0
- package/dist/middleware-7SvnYV--.js +220 -0
- package/dist/middleware-7SvnYV--.js.map +1 -0
- package/dist/rich-CHisXE-A.js.map +1 -1
- package/dist/server/chunk-middleware-entrypoint.d.ts +5 -0
- package/dist/server/chunk-middleware-entrypoint.d.ts.map +1 -0
- package/dist/server/chunk-middleware-entrypoint.js +7 -0
- package/dist/server/chunk-middleware-entrypoint.js.map +1 -0
- package/dist/server/translate.d.ts +1 -1
- package/dist/server/translate.d.ts.map +1 -1
- package/dist/server/translate.js +5 -2
- package/dist/server/translate.js.map +1 -1
- package/dist/{types-C98CcjtN.d.ts → types-DU8QHch4.d.ts} +4 -3
- package/dist/types-DU8QHch4.d.ts.map +1 -0
- package/dist/{types-CkqZWnXH.d.ts → types-f-SNJdvf.d.ts} +8 -8
- package/dist/types-f-SNJdvf.d.ts.map +1 -0
- package/dist/{url-B8334XSD.js → url-CifeaDzl.js} +2 -12
- package/dist/{url-B8334XSD.js.map → url-CifeaDzl.js.map} +1 -1
- package/dist/{vite-plugin-BbdVKdt8.js → vite-plugin-C-jEpd8t.js} +18 -22
- package/dist/vite-plugin-C-jEpd8t.js.map +1 -0
- package/package.json +14 -35
- package/dist/client/directives/idle.d.ts +0 -6
- package/dist/client/directives/idle.d.ts.map +0 -1
- package/dist/client/directives/idle.js +0 -14
- package/dist/client/directives/idle.js.map +0 -1
- package/dist/client/directives/load.d.ts +0 -6
- package/dist/client/directives/load.d.ts.map +0 -1
- package/dist/client/directives/load.js +0 -10
- package/dist/client/directives/load.js.map +0 -1
- package/dist/client/directives/media.d.ts +0 -6
- package/dist/client/directives/media.d.ts.map +0 -1
- package/dist/client/directives/media.js +0 -17
- package/dist/client/directives/media.js.map +0 -1
- package/dist/client/directives/only.d.ts +0 -6
- package/dist/client/directives/only.d.ts.map +0 -1
- package/dist/client/directives/only.js +0 -10
- package/dist/client/directives/only.js.map +0 -1
- package/dist/client/directives/utils.d.ts +0 -10
- package/dist/client/directives/utils.d.ts.map +0 -1
- package/dist/client/directives/utils.js +0 -32
- package/dist/client/directives/utils.js.map +0 -1
- package/dist/client/directives/visible.d.ts +0 -6
- package/dist/client/directives/visible.d.ts.map +0 -1
- package/dist/client/directives/visible.js +0 -23
- package/dist/client/directives/visible.js.map +0 -1
- package/dist/context-BEQ6qqX5.js.map +0 -1
- package/dist/i18n-Dzh54-at.d.ts.map +0 -1
- package/dist/manifest-BEm6GezL.js.map +0 -1
- package/dist/state-DeudhdeT.js +0 -20
- package/dist/state-DeudhdeT.js.map +0 -1
- package/dist/types-C98CcjtN.d.ts.map +0 -1
- package/dist/types-CkqZWnXH.d.ts.map +0 -1
- package/dist/vite-plugin-BbdVKdt8.js.map +0 -1
- package/src/astro/I18nScript.astro +0 -16
- package/src/astro/index.ts +0 -2
package/README.md
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# @astroscope/i18n
|
|
2
2
|
|
|
3
|
-
> **Note:** This package is in active development. APIs may change between versions.
|
|
4
|
-
|
|
5
3
|
i18n for Astro + React islands — automatic tree-shaking, parallel loading, any translation source.
|
|
6
4
|
|
|
7
5
|
## Why this library?
|
|
@@ -10,21 +8,18 @@ i18n for Astro + React islands — automatic tree-shaking, parallel loading, any
|
|
|
10
8
|
|
|
11
9
|
**Automatic tree-shaking** — Only the translations each component actually uses are delivered to the browser. No manual chunk splitting or configuration.
|
|
12
10
|
|
|
13
|
-
**Parallel loading** —
|
|
11
|
+
**Parallel loading** — Translation chunks preload alongside component code with plain `client:*` directives (via `@astroscope/node`), so hydration doesn't wait on a separate translation fetch.
|
|
14
12
|
|
|
15
13
|
**Unified API** — The same `t()` function works in Astro templates and React islands.
|
|
16
14
|
|
|
17
15
|
**Any translation source** — Fetch translations from any provider: JSON files, database, headless CMS, TMS, or custom API. All are chunked and optimized the same way.
|
|
18
16
|
|
|
19
|
-
**Production optimized** — Fallback strings are stripped from production bundles via Babel and kept available through the manifest.
|
|
20
|
-
|
|
21
17
|
## Features
|
|
22
18
|
|
|
23
19
|
- **Per-chunk translation loading** — each island gets only its translations
|
|
24
20
|
- **Unicode MessageFormat 2** (MF2) support via `messageformat` v4
|
|
25
21
|
- **Built-in formatters** — `:number`, `:integer`, `:percent`, `:currency`, `:date`, `:time`, `:datetime`, `:unit`
|
|
26
|
-
- **
|
|
27
|
-
- **Manifest fallbacks** — missing translations automatically use extracted fallbacks
|
|
22
|
+
- **Fallback strings in code** — used during development, stripped from production bundles, available to translators through the manifest
|
|
28
23
|
- **Full TypeScript support**
|
|
29
24
|
- **Tiny client runtime** — ~8KB gzipped for translations
|
|
30
25
|
|
|
@@ -102,56 +97,32 @@ export async function onStartup() {
|
|
|
102
97
|
|
|
103
98
|
```ts
|
|
104
99
|
// src/middleware.ts
|
|
105
|
-
import {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
ctx.cookies.get('locale')?.value ??
|
|
113
|
-
i18n.getConfig().defaultLocale,
|
|
114
|
-
}),
|
|
115
|
-
);
|
|
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
|
+
});
|
|
116
107
|
```
|
|
117
108
|
|
|
109
|
+
The middleware serving `/_i18n/` translation chunks is injected automatically by the integration.
|
|
110
|
+
|
|
118
111
|
By default, `RECOMMENDED_EXCLUDES` (static assets like `/_astro/`) are excluded from locale context setup. To customize:
|
|
119
112
|
|
|
120
113
|
```ts
|
|
121
|
-
import {
|
|
122
|
-
import { createI18nChunkMiddleware, createI18nMiddleware, i18n } from '@astroscope/i18n';
|
|
114
|
+
import { createI18nMiddleware, i18n } from '@astroscope/i18n';
|
|
123
115
|
import { RECOMMENDED_EXCLUDES } from '@astroscope/node/excludes';
|
|
124
116
|
|
|
125
|
-
export const onRequest =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
exclude: [...RECOMMENDED_EXCLUDES, { exact: '/health' }],
|
|
132
|
-
}),
|
|
133
|
-
);
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### 4. Add `<I18nScript />` to your layout
|
|
137
|
-
|
|
138
|
-
Inject translations into the page for hydrated components:
|
|
139
|
-
|
|
140
|
-
```astro
|
|
141
|
-
---
|
|
142
|
-
import { I18nScript } from '@astroscope/i18n/astro';
|
|
143
|
-
---
|
|
144
|
-
<html>
|
|
145
|
-
<head>
|
|
146
|
-
<I18nScript />
|
|
147
|
-
</head>
|
|
148
|
-
<body>
|
|
149
|
-
<slot />
|
|
150
|
-
</body>
|
|
151
|
-
</html>
|
|
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
|
+
});
|
|
152
123
|
```
|
|
153
124
|
|
|
154
|
-
###
|
|
125
|
+
### 4. Use `t()` in your components
|
|
155
126
|
|
|
156
127
|
```astro
|
|
157
128
|
---
|
|
@@ -177,21 +148,7 @@ export function CheckoutSummary() {
|
|
|
177
148
|
|
|
178
149
|
> **Note:** Variables use `{$name}` syntax (with `$` prefix) per MessageFormat 2 specification.
|
|
179
150
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
**The problem:** With standard `client:*` directives, the translation chunk loads *after* the component module. This delays hydration while translations are fetched sequentially.
|
|
183
|
-
|
|
184
|
-
**The solution:** Use `client:*-x` directives to load translations in parallel with the component code:
|
|
185
|
-
|
|
186
|
-
```astro
|
|
187
|
-
---
|
|
188
|
-
import Cart from '../components/Cart';
|
|
189
|
-
---
|
|
190
|
-
<!-- translations load alongside component code -->
|
|
191
|
-
<Cart client:load-x />
|
|
192
|
-
<Cart client:visible-x />
|
|
193
|
-
<Cart client:idle-x />
|
|
194
|
-
```
|
|
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`, no layout script or special directives needed.
|
|
195
152
|
|
|
196
153
|
## API
|
|
197
154
|
|
|
@@ -331,18 +288,16 @@ export function App() {
|
|
|
331
288
|
|
|
332
289
|
## How it works
|
|
333
290
|
|
|
334
|
-
1. **Build time** —
|
|
291
|
+
1. **Build time** — all `t()` calls are extracted, mapped to chunks, and fallbacks are stripped from production bundles
|
|
335
292
|
2. **Manifest** — Extracted keys with fallbacks are written to `i18n-manifest.json`
|
|
336
293
|
3. **SSR** — Middleware provides translations to `t()`, merging manifest fallbacks for missing keys
|
|
337
|
-
4. **Client** —
|
|
294
|
+
4. **Client** — each island preloads only the translation chunks its code needs, in parallel with the component code
|
|
338
295
|
|
|
339
296
|
The same `import { t } from '@astroscope/i18n/translate'` works everywhere — bundler picks the correct implementation via conditional exports (`browser` vs `default`).
|
|
340
297
|
|
|
341
298
|
### Client bundle
|
|
342
299
|
|
|
343
|
-
Translation chunks
|
|
344
|
-
|
|
345
|
-
> **Future:** Once browsers ship native `Intl.MessageFormat`, this 8KB runtime will be replaced by the built-in API with zero bundle cost.
|
|
300
|
+
Translation chunks ship as raw MessageFormat 2 strings and are compiled in the browser on first use — the whole client runtime is ~8KB gzipped.
|
|
346
301
|
|
|
347
302
|
## MessageFormat 2 Syntax
|
|
348
303
|
|
|
@@ -424,18 +379,19 @@ For `:currency` and `:unit`, the required option (`currency` or `unit`) can be:
|
|
|
424
379
|
|
|
425
380
|
## ESLint Plugin
|
|
426
381
|
|
|
427
|
-
Use [`@astroscope/eslint-plugin
|
|
382
|
+
Use the `i18n` config set from [`@astroscope/eslint-plugin`](../eslint-plugin) to enforce correct `t()` usage and catch build-time extraction issues:
|
|
428
383
|
|
|
429
384
|
```bash
|
|
430
|
-
npm install -D @astroscope/eslint-plugin
|
|
385
|
+
npm install -D @astroscope/eslint-plugin
|
|
431
386
|
```
|
|
432
387
|
|
|
433
388
|
```js
|
|
434
389
|
// eslint.config.js
|
|
435
|
-
import
|
|
390
|
+
import astroscope from '@astroscope/eslint-plugin';
|
|
436
391
|
|
|
437
392
|
export default [
|
|
438
|
-
|
|
393
|
+
...astroscope.configs.recommended,
|
|
394
|
+
...astroscope.configs.i18n,
|
|
439
395
|
];
|
|
440
396
|
```
|
|
441
397
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-CWafmw5z.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 meta.fallback = value;\n } else {\n report('the fallback is not a static string');\n }\n } else if (name === 'description') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value !== null) {\n meta.description = value;\n } else {\n report('the description is not a static string');\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)) {\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\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 // check if callee is 't' identifier\n // we assume that it was not aliased / reassigned\n if (!path.get('callee').isIdentifier({ name: 't' })) return;\n\n const args = path.node.arguments;\n const keyArg = args[0];\n\n
|
|
1
|
+
{"version":3,"file":"babel-plugin-CWafmw5z.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 meta.fallback = value;\n } else {\n report('the fallback is not a static string');\n }\n } else if (name === 'description') {\n const value = getStringValue(t, prop.value as Node);\n\n if (value !== null) {\n meta.description = value;\n } else {\n report('the description is not a static string');\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)) {\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\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 // check if callee is 't' identifier\n // we assume that it was not aliased / reassigned\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) {\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}\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-DU8QHch4.js";
|
|
3
3
|
//#region src/client/translate.d.ts
|
|
4
4
|
declare function getLocale(): string;
|
|
5
5
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/client/translate.ts"],"mappings":";;;iBAQgB;;;;;;;;;;;;;;cAiBH,GAAG;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"translate.d.ts","names":[],"sources":["../../src/client/translate.ts"],"mappings":";;;iBAQgB;;;;;;;;;;;;;;cAiBH,GAAG;;;;;;;;;;;;;;;;;;;;iBAyDA,KAAK,aACnB,aACA,OAAO,sCACP,aAAa,eAAe,gBAC5B,SAAS,gDACE"}
|
package/dist/client/translate.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { r as formatMessageToParts, t as compileMessage } from "../compiler-CkOVGWU5.js";
|
|
2
2
|
import { n as normalizeMeta, t as partsToNodes } from "../rich-CHisXE-A.js";
|
|
3
|
-
|
|
3
|
+
//#region src/client/state.ts
|
|
4
|
+
let warnedMissingState = false;
|
|
5
|
+
/**
|
|
6
|
+
* Read the client i18n state injected by the i18n middleware and islands emitter.
|
|
7
|
+
*
|
|
8
|
+
* Returns undefined and logs an actionable error (once) when the state was not
|
|
9
|
+
* injected — without it window.__i18n__ is undefined and translations cannot load.
|
|
10
|
+
*/
|
|
11
|
+
function getI18nState() {
|
|
12
|
+
const state = window.__i18n__;
|
|
13
|
+
if (!state && !warnedMissingState) {
|
|
14
|
+
warnedMissingState = true;
|
|
15
|
+
console.error("[@astroscope/i18n] window.__i18n__ is not defined — translations will not work on the client. Make sure createI18nMiddleware() runs for this page (it injects the state) and, for per-island hashes, that the site uses the @astroscope/node adapter.");
|
|
16
|
+
}
|
|
17
|
+
return state;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
4
20
|
//#region src/client/translate.ts
|
|
5
21
|
const cache = /* @__PURE__ */ new Map();
|
|
6
22
|
function getLocale() {
|
|
@@ -65,7 +81,8 @@ function rich(key, meta, components, values) {
|
|
|
65
81
|
const normalizedMeta = meta ? normalizeMeta(meta) : { fallback: "" };
|
|
66
82
|
const state = getI18nState();
|
|
67
83
|
const raw = state?.translations[key] || normalizedMeta.fallback || key;
|
|
68
|
-
|
|
84
|
+
const parts = formatMessageToParts(state?.locale ?? "", raw, values);
|
|
85
|
+
return partsToNodes(parts, components ?? {});
|
|
69
86
|
}
|
|
70
87
|
//#endregion
|
|
71
88
|
export { getLocale, rich, t };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translate.js","names":[],"sources":["../../src/client/translate.ts"],"sourcesContent":["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
|
|
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 createI18nMiddleware() runs for this page (it injects the state) and, for per-island ' +\n 'hashes, that the site uses the @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 (ICU MessageFormat)\n *\n * @example\n * ```tsx\n * rich('tos', 'Read our {#link}Terms{/link}', {\n * link: (children) => <a href=\"/tos\">{children}</a>\n * })\n * // Returns: ['Read our ', <a href=\"/tos\">Terms</a>]\n * ```\n */\nexport function rich<T = unknown>(\n key: string,\n meta?: TranslationMeta | string | undefined,\n components?: RichComponents<T> | undefined,\n values?: Record<string, unknown> | undefined,\n): (string | T)[] {\n const normalizedMeta: TranslationMeta = meta ? normalizeMeta(meta) : { fallback: '' };\n\n const 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,uPAGF;CACF;CAEA,OAAO;AACT;;;ACnBA,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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as getGlobalState } from "./manifest-
|
|
1
|
+
import { t as getGlobalState } from "./manifest-Cma68XM-.js";
|
|
2
2
|
import { n as compileTranslations } from "./compiler-CkOVGWU5.js";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -26,33 +26,6 @@ function computeAllChunkHashes(translations, manifest) {
|
|
|
26
26
|
return hashes;
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region src/server/utils.ts
|
|
30
|
-
const LOWERCASE_A = 97;
|
|
31
|
-
/**
|
|
32
|
-
* Convert index to lowercase letter: 0→'a', 1→'b', ..., 25→'z'
|
|
33
|
-
*/
|
|
34
|
-
function indexToLetter(index) {
|
|
35
|
-
return String.fromCharCode(LOWERCASE_A + index);
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Generate short variable name using bijective base-26: 0→'a', 25→'z', 26→'aa', 27→'ab', ...
|
|
39
|
-
*/
|
|
40
|
-
function generateBB26(index) {
|
|
41
|
-
let name = "";
|
|
42
|
-
let remaining = index;
|
|
43
|
-
do {
|
|
44
|
-
name = indexToLetter(remaining % 26) + name;
|
|
45
|
-
remaining = Math.floor(remaining / 26) - 1;
|
|
46
|
-
} while (remaining >= 0);
|
|
47
|
-
return name;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Generate a JS-safe variable name for the inline i18n script.
|
|
51
|
-
*/
|
|
52
|
-
function generateVarName(index) {
|
|
53
|
-
return `_${generateBB26(index)}`;
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
29
|
//#region src/server/i18n.ts
|
|
57
30
|
var I18nSingleton = class {
|
|
58
31
|
config = null;
|
|
@@ -60,7 +33,6 @@ var I18nSingleton = class {
|
|
|
60
33
|
mergedCache = /* @__PURE__ */ new Map();
|
|
61
34
|
compiledCache = /* @__PURE__ */ new Map();
|
|
62
35
|
hashCache = /* @__PURE__ */ new Map();
|
|
63
|
-
scriptCache = /* @__PURE__ */ new Map();
|
|
64
36
|
chunkCache = /* @__PURE__ */ new Map();
|
|
65
37
|
manifestGetter = null;
|
|
66
38
|
manifestVersion = 0;
|
|
@@ -98,7 +70,6 @@ var I18nSingleton = class {
|
|
|
98
70
|
this.mergedCache.delete(locale);
|
|
99
71
|
this.compiledCache.delete(locale);
|
|
100
72
|
this.hashCache.delete(locale);
|
|
101
|
-
this.scriptCache.delete(locale);
|
|
102
73
|
for (const key of this.chunkCache.keys()) if (key.startsWith(`${locale}:`)) this.chunkCache.delete(key);
|
|
103
74
|
const { chunks } = this.getManifest();
|
|
104
75
|
if (Object.keys(chunks).length > 0) this.hashCache.set(locale, computeAllChunkHashes(raw, chunks));
|
|
@@ -111,7 +82,7 @@ var I18nSingleton = class {
|
|
|
111
82
|
const manifest = this.manifestGetter?.() ?? {
|
|
112
83
|
keys: [],
|
|
113
84
|
chunks: {},
|
|
114
|
-
|
|
85
|
+
scripts: []
|
|
115
86
|
};
|
|
116
87
|
if (manifest.keys.length === 0) return raw;
|
|
117
88
|
const withFallbacks = { ...raw };
|
|
@@ -163,40 +134,19 @@ var I18nSingleton = class {
|
|
|
163
134
|
return body;
|
|
164
135
|
}
|
|
165
136
|
/**
|
|
166
|
-
*
|
|
167
|
-
*
|
|
137
|
+
* Hashes for chunks reachable from astro `<script>` entries. Script modules are
|
|
138
|
+
* not islands, so the islands emitter never covers them — the middleware
|
|
139
|
+
* bootstraps these few into the client state on every html response.
|
|
168
140
|
*/
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
if (cached) return cached;
|
|
172
|
-
const script = this.createClientScript(locale);
|
|
173
|
-
this.scriptCache.set(locale, script);
|
|
174
|
-
return script;
|
|
175
|
-
}
|
|
176
|
-
createClientScript(locale) {
|
|
177
|
-
const { chunks, imports } = this.getManifest();
|
|
178
|
-
if (!(Object.keys(chunks).length > 0)) {
|
|
179
|
-
const raw = this.getTranslations(locale);
|
|
180
|
-
return `window.__i18n__=${JSON.stringify({
|
|
181
|
-
locale,
|
|
182
|
-
hashes: {},
|
|
183
|
-
imports: {},
|
|
184
|
-
translations: raw
|
|
185
|
-
})};`;
|
|
186
|
-
}
|
|
141
|
+
getScriptChunkHashes(locale) {
|
|
142
|
+
const { scripts } = this.getManifest();
|
|
187
143
|
const hashes = this.getHashes(locale);
|
|
188
|
-
const
|
|
189
|
-
for (const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const hashesObj = Object.entries(hashes).map(([chunk, hash]) => `[${chunkToVar.get(chunk)}]:${JSON.stringify(hash)}`).join(",");
|
|
195
|
-
const importsObj = Object.entries(imports).filter(([chunk]) => chunkToVar.has(chunk)).map(([chunk, deps]) => {
|
|
196
|
-
const validDeps = deps.filter((d) => chunkToVar.has(d));
|
|
197
|
-
return validDeps.length > 0 ? `[${chunkToVar.get(chunk)}]:[${validDeps.map((d) => chunkToVar.get(d)).join(",")}]` : null;
|
|
198
|
-
}).filter(Boolean).join(",");
|
|
199
|
-
return `(()=>{var ${varDecls};window.__i18n__={locale:${JSON.stringify(locale)},hashes:{${hashesObj}},imports:{${importsObj}},translations:{}};})();`;
|
|
144
|
+
const result = {};
|
|
145
|
+
for (const chunk of scripts) {
|
|
146
|
+
const hash = hashes[chunk];
|
|
147
|
+
if (hash) result[chunk] = hash;
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
200
150
|
}
|
|
201
151
|
/**
|
|
202
152
|
* Clear translations for specified locale(s).
|
|
@@ -209,7 +159,6 @@ var I18nSingleton = class {
|
|
|
209
159
|
this.mergedCache.delete(locale);
|
|
210
160
|
this.compiledCache.delete(locale);
|
|
211
161
|
this.hashCache.delete(locale);
|
|
212
|
-
this.scriptCache.delete(locale);
|
|
213
162
|
for (const key of this.chunkCache.keys()) if (key.startsWith(`${locale}:`)) this.chunkCache.delete(key);
|
|
214
163
|
}
|
|
215
164
|
}
|
|
@@ -222,7 +171,6 @@ var I18nSingleton = class {
|
|
|
222
171
|
this.manifestVersion = version;
|
|
223
172
|
this.mergedCache.clear();
|
|
224
173
|
this.compiledCache.clear();
|
|
225
|
-
this.scriptCache.clear();
|
|
226
174
|
this.chunkCache.clear();
|
|
227
175
|
}
|
|
228
176
|
resolveLocales(locales) {
|
|
@@ -250,4 +198,4 @@ function getContext() {
|
|
|
250
198
|
//#endregion
|
|
251
199
|
export { runWithContext as n, i18n as r, getContext as t };
|
|
252
200
|
|
|
253
|
-
//# sourceMappingURL=context-
|
|
201
|
+
//# sourceMappingURL=context-CYqyZJyH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-CYqyZJyH.js","names":[],"sources":["../src/extraction/hash.ts","../src/server/i18n.ts","../src/server/context.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').substring(0, 8);\n}\n\n/**\n * Compute hash for chunk translations\n */\nexport function computeChunkHash(translations: RawTranslations, keys: string[]): string {\n const relevantTranslations: RawTranslations = {};\n\n for (const key of [...keys].sort()) {\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 { 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\nexport type I18nConfig = {\n locales: string[];\n defaultLocale?: string | undefined;\n fallback?: FallbackBehavior | undefined;\n};\n\ntype NormalizedConfig = {\n locales: string[];\n defaultLocale: string;\n fallback: FallbackBehavior;\n};\n\nclass I18nSingleton {\n // normalized user config with defaults applied\n private config: NormalizedConfig | null = null;\n\n // locale -> raw translation strings (before ICU 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 ICU MessageFormat 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 // 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 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 };\n\n // resolved lazily so importing the singleton needs no virtual module\n if (!this.manifestGetter) {\n const m = await import('virtual:@astroscope/i18n/manifest');\n this.manifestGetter = m.getManifest;\n }\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.mergedCache.delete(locale); // invalidate merged cache\n this.compiledCache.delete(locale); // invalidate compiled cache\n this.hashCache.delete(locale); // invalidate hash cache\n\n // invalidate chunk cache entries for this locale\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 (!(key in withFallbacks) && 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 if (translations[key]) {\n chunkTranslations[key] = translations[key];\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.mergedCache.delete(locale);\n this.compiledCache.delete(locale);\n this.hashCache.delete(locale);\n\n // clear chunk cache entries for this locale\n for (const key of this.chunkCache.keys()) {\n if (key.startsWith(`${locale}:`)) {\n this.chunkCache.delete(key);\n }\n }\n }\n }\n\n /**\n * invalidate derived caches when the extraction manifest has changed (dev mode HMR)\n */\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\nexport const i18n = new I18nSingleton();\n","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":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ExtractedKey, r as ExtractionManifest, t as ChunkManifest } from "../types-
|
|
1
|
+
import { n as ExtractedKey, r as ExtractionManifest, t as ChunkManifest } from "../types-f-SNJdvf.js";
|
|
2
2
|
import { t as ConsistencyCheckLevel } from "../types-Bo4jMK8g.js";
|
|
3
3
|
import { AstroIntegrationLogger } from "astro";
|
|
4
4
|
import { Plugin } from "vite";
|
|
@@ -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":";;;;;;;;;iBA+CgB,eAAe;;;KCvBnB;EACV,QAAQ;EACR,aAAa;;;;;;;;;;;;iBAuCC,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-C-jEpd8t.js";
|
|
2
|
+
import { n as getManifest } from "../manifest-Cma68XM-.js";
|
|
3
3
|
export { getManifest, i18nVitePlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
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-DU8QHch4.js";
|
|
2
|
+
import { r as ExtractionManifest } from "./types-f-SNJdvf.js";
|
|
3
3
|
//#region src/server/types.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Fallback behavior when translation is missing
|
|
@@ -32,7 +32,6 @@ declare class I18nSingleton {
|
|
|
32
32
|
private mergedCache;
|
|
33
33
|
private compiledCache;
|
|
34
34
|
private hashCache;
|
|
35
|
-
private scriptCache;
|
|
36
35
|
private chunkCache;
|
|
37
36
|
private manifestGetter;
|
|
38
37
|
private manifestVersion;
|
|
@@ -56,11 +55,11 @@ declare class I18nSingleton {
|
|
|
56
55
|
*/
|
|
57
56
|
getChunkBody(locale: string, chunkName: string): Uint8Array | undefined;
|
|
58
57
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
58
|
+
* Hashes for chunks reachable from astro `<script>` entries. Script modules are
|
|
59
|
+
* not islands, so the islands emitter never covers them — the middleware
|
|
60
|
+
* bootstraps these few into the client state on every html response.
|
|
61
61
|
*/
|
|
62
|
-
|
|
63
|
-
private createClientScript;
|
|
62
|
+
getScriptChunkHashes(locale: string): Record<string, string>;
|
|
64
63
|
/**
|
|
65
64
|
* Clear translations for specified locale(s).
|
|
66
65
|
* @param locales - Locale(s) to clear, or all if not specified
|
|
@@ -75,4 +74,4 @@ declare class I18nSingleton {
|
|
|
75
74
|
declare const i18n: I18nSingleton;
|
|
76
75
|
//#endregion
|
|
77
76
|
export { I18nContext as i, i18n as n, FallbackBehavior as r, I18nConfig as t };
|
|
78
|
-
//# sourceMappingURL=i18n-
|
|
77
|
+
//# sourceMappingURL=i18n-Bp8TFmd3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-Bp8TFmd3.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;;;;KCVA;EACV;EACA;EACA,WAAW;;KAGR;EACH;EACA;EACA,UAAU;;cAGN;UAEI;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;UAGA;EAEF,UAAU,QAAQ,aAAa;EA8CrC;EAIA,aAAa;EAQb,gBAAgB,gBAAgB,KAAK;EAqBrC,gBAAgB,iBAAiB;EA8BjC,wBAAwB,iBAAiB;;;;;;EAmBzC,eAAe;EAQf,UAAU,iBAAiB;;;;;;EAS3B,aAAa,gBAAgB,oBAAoB;;;;;;EA0CjD,qBAAqB,iBAAiB;;;;;EAoBtC,MAAM;;;;UAqBE;UAaA;;cAaG,MAAI"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { a as TranslationMeta, r as RawTranslations } from "./types-
|
|
2
|
-
import { n as ExtractedKey, r as ExtractionManifest } from "./types-
|
|
1
|
+
import { a as TranslationMeta, r as RawTranslations } from "./types-DU8QHch4.js";
|
|
2
|
+
import { n as ExtractedKey, r as ExtractionManifest } from "./types-f-SNJdvf.js";
|
|
3
3
|
import { n as I18nOptions } from "./types-Bo4jMK8g.js";
|
|
4
|
-
import { n as i18n, r as FallbackBehavior, t as I18nConfig } from "./i18n-
|
|
4
|
+
import { n as i18n, r as FallbackBehavior, t as I18nConfig } from "./i18n-Bp8TFmd3.js";
|
|
5
5
|
import { ExcludePattern } from "@astroscope/node/excludes";
|
|
6
6
|
import { APIContext, AstroIntegration, MiddlewareHandler } from "astro";
|
|
7
7
|
//#region src/integration/integration.d.ts
|
|
8
8
|
/**
|
|
9
9
|
* Astro integration for i18n with automatic per-chunk translation loading.
|
|
10
10
|
*
|
|
11
|
-
* Registers
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Registers the vite plugin for translation key extraction, chunk mapping and
|
|
12
|
+
* loader injection, and injects the chunk middleware serving `/_i18n/`
|
|
13
|
+
* translation chunks. Client state delivery and translation preloading happen at
|
|
14
|
+
* runtime: the i18n middleware bootstraps `window.__i18n__`, and the islands
|
|
15
|
+
* emitter (via `@astroscope/node`) slices hashes and preloads translation chunks
|
|
16
|
+
* per island — plain `client:*` directives, no forks.
|
|
14
17
|
*/
|
|
15
18
|
declare function i18nIntegration(options?: I18nOptions): AstroIntegration;
|
|
16
19
|
//#endregion
|
|
@@ -40,32 +43,13 @@ type I18nMiddlewareOptions = {
|
|
|
40
43
|
*/
|
|
41
44
|
exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;
|
|
42
45
|
};
|
|
43
|
-
/**
|
|
44
|
-
* Create the i18n chunk middleware that serves translation chunks at `/_i18n/` endpoints.
|
|
45
|
-
*
|
|
46
|
-
* Place this early in your middleware sequence (before session/auth)
|
|
47
|
-
* to avoid unnecessary overhead for static translation requests.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```typescript
|
|
51
|
-
* // src/middleware.ts
|
|
52
|
-
* import { sequence } from 'astro:middleware';
|
|
53
|
-
* import { createI18nChunkMiddleware, createI18nMiddleware } from '@astroscope/i18n';
|
|
54
|
-
*
|
|
55
|
-
* export const onRequest = sequence(
|
|
56
|
-
* createI18nChunkMiddleware(), // early: serves /_i18n/ chunks
|
|
57
|
-
* sessionMiddleware,
|
|
58
|
-
* createI18nMiddleware({ locale: (ctx) => ... }), // after session
|
|
59
|
-
* );
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
declare function createI18nChunkMiddleware(): MiddlewareHandler;
|
|
63
46
|
/**
|
|
64
47
|
* Create the i18n locale middleware.
|
|
65
48
|
*
|
|
66
|
-
* Sets up the request context with locale and translations for
|
|
67
|
-
*
|
|
68
|
-
*
|
|
49
|
+
* Sets up the request context with locale and translations for `t()`, injects
|
|
50
|
+
* the client i18n state into html responses, and records the locale for the
|
|
51
|
+
* islands emitter. Place this after session middleware if your locale detection
|
|
52
|
+
* depends on session/cookies.
|
|
69
53
|
*
|
|
70
54
|
* @example
|
|
71
55
|
* ```typescript
|
|
@@ -88,5 +72,5 @@ declare function createI18nMiddleware(options: I18nMiddlewareOptions): Middlewar
|
|
|
88
72
|
*/
|
|
89
73
|
declare function detectLocale(request: Request): string | undefined;
|
|
90
74
|
//#endregion
|
|
91
|
-
export { type ExtractedKey, type ExtractionManifest, type FallbackBehavior, type I18nConfig, type I18nMiddlewareOptions, type RawTranslations, type TranslationMeta,
|
|
75
|
+
export { type ExtractedKey, type ExtractionManifest, type FallbackBehavior, type I18nConfig, type I18nMiddlewareOptions, type RawTranslations, type TranslationMeta, createI18nMiddleware, i18nIntegration as default, detectLocale, i18n };
|
|
92
76
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/integration/integration.ts","../src/server/middleware.ts","../src/server/locale.ts"],"mappings":";;;;;;;;;;;;;;;;;iBAcwB,gBAAgB,UAAS,cAAmB;;;KCNxD;EACV,SAAS,KAAK;;;;;;;;;;;;;;;;;;;;;;EAsBd,UAAU,qBAAqB,SAAS;;;;;;;;;;;;;;;;;;;;;;iBA2H1B,qBAAqB,SAAS,wBAAwB;;;;;;;iBCpJtD,aAAa,SAAS"}
|