@astroscope/i18n 0.2.4 → 0.3.1
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 +158 -16
- package/dist/chunk-33JVAFGQ.js +44 -0
- package/dist/{chunk-ZKXMIGSF.js → chunk-MSJHXRJB.js} +1 -1
- package/dist/chunk-VGPVG5JP.js +73 -0
- package/dist/{chunk-3VF2YMJG.js → chunk-Z3LDPCYD.js} +112 -8
- package/dist/client/translate.d.ts +41 -0
- package/dist/client/{t.js → translate.js} +14 -5
- package/dist/extraction/index.d.ts +2 -0
- package/dist/extraction/index.js +1 -1
- package/dist/{i18n-CbSjmzlu.d.ts → i18n-DaLnNvss.d.ts} +1 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +7 -5
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -2
- package/dist/rich-CQUhdwBc.d.ts +15 -0
- package/dist/server/translate.d.ts +38 -0
- package/dist/server/{t.js → translate.js} +17 -6
- package/dist/types-B_8vAmvv.d.ts +18 -0
- package/package.json +13 -11
- package/dist/chunk-EMM6LRO2.js +0 -8
- package/dist/chunk-KNDYKTHE.js +0 -31
- package/dist/client/t.d.ts +0 -20
- package/dist/server/t.d.ts +0 -17
- /package/src/{components → astro}/I18nScript.astro +0 -0
- /package/src/{components → astro}/index.ts +0 -0
package/README.md
CHANGED
|
@@ -21,11 +21,12 @@ i18n for Astro + React islands — automatic tree-shaking, parallel loading, any
|
|
|
21
21
|
## Features
|
|
22
22
|
|
|
23
23
|
- **Per-chunk translation loading** — each island gets only its translations
|
|
24
|
-
- **
|
|
24
|
+
- **Unicode MessageFormat 2** (MF2) support via `messageformat` v4
|
|
25
|
+
- **Built-in formatters** — `:number`, `:integer`, `:percent`, `:currency`, `:date`, `:time`, `:datetime`, `:unit`
|
|
25
26
|
- **Babel-based extraction** — robust AST parsing, source maps, production stripping
|
|
26
27
|
- **Manifest fallbacks** — missing translations automatically use extracted fallbacks
|
|
27
28
|
- **Full TypeScript support**
|
|
28
|
-
- **Tiny client runtime** —
|
|
29
|
+
- **Tiny client runtime** — ~8KB gzipped for translations
|
|
29
30
|
|
|
30
31
|
## Installation
|
|
31
32
|
|
|
@@ -51,6 +52,20 @@ export default defineConfig({
|
|
|
51
52
|
});
|
|
52
53
|
```
|
|
53
54
|
|
|
55
|
+
#### Consistency checking
|
|
56
|
+
|
|
57
|
+
The integration checks for translation key consistency — when the same key is used in multiple files with different fallbacks, variables, or descriptions.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
i18n({
|
|
61
|
+
consistency: 'warn', // 'warn' (default) | 'error' | 'off'
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- **`'warn'`** — Log a warning but continue (default)
|
|
66
|
+
- **`'error'`** — Fail the build
|
|
67
|
+
- **`'off'`** — Disable consistency checking
|
|
68
|
+
|
|
54
69
|
### 2. Configure i18n in your boot file
|
|
55
70
|
|
|
56
71
|
**VERY IMPORTANT:** `i18n.configure` must be awaited during boot before handling any requests!
|
|
@@ -123,7 +138,7 @@ Inject translations into the page for hydrated components:
|
|
|
123
138
|
|
|
124
139
|
```astro
|
|
125
140
|
---
|
|
126
|
-
import { I18nScript } from '@astroscope/i18n/
|
|
141
|
+
import { I18nScript } from '@astroscope/i18n/astro';
|
|
127
142
|
---
|
|
128
143
|
<html>
|
|
129
144
|
<head>
|
|
@@ -140,25 +155,27 @@ import { I18nScript } from '@astroscope/i18n/components';
|
|
|
140
155
|
```astro
|
|
141
156
|
---
|
|
142
157
|
// In .astro files
|
|
143
|
-
import { t } from '@astroscope/i18n/
|
|
158
|
+
import { t } from '@astroscope/i18n/translate';
|
|
144
159
|
---
|
|
145
160
|
<h1>{t('checkout.title', 'Order Summary')}</h1>
|
|
146
161
|
```
|
|
147
162
|
|
|
148
163
|
```tsx
|
|
149
164
|
// In React components
|
|
150
|
-
import { t } from '@astroscope/i18n/
|
|
165
|
+
import { t } from '@astroscope/i18n/translate';
|
|
151
166
|
|
|
152
167
|
export function CheckoutSummary() {
|
|
153
168
|
return (
|
|
154
169
|
<div>
|
|
155
170
|
<h1>{t('checkout.title', 'Order Summary')}</h1>
|
|
156
|
-
<p>{t('checkout.tax', 'Includes {tax} VAT', { tax: '19%' })}</p>
|
|
171
|
+
<p>{t('checkout.tax', 'Includes {$tax} VAT', { tax: '19%' })}</p>
|
|
157
172
|
</div>
|
|
158
173
|
);
|
|
159
174
|
}
|
|
160
175
|
```
|
|
161
176
|
|
|
177
|
+
> **Note:** Variables use `{$name}` syntax (with `$` prefix) per MessageFormat 2 specification.
|
|
178
|
+
|
|
162
179
|
### 6. Use i18n-aware client directives
|
|
163
180
|
|
|
164
181
|
**The problem:** With standard `client:*` directives, the translation chunk loads *after* the component module. This delays hydration while translations are fetched sequentially.
|
|
@@ -179,28 +196,90 @@ import Cart from '../components/Cart';
|
|
|
179
196
|
|
|
180
197
|
### `t(key, fallback, values?)`
|
|
181
198
|
|
|
182
|
-
Translate a key with optional interpolation values.
|
|
199
|
+
Translate a key with optional interpolation values. The fallback is used when a translation is missing and also serves as an example for translators. Uses [MessageFormat 2](https://github.com/unicode-org/message-format-wg) syntax.
|
|
183
200
|
|
|
184
201
|
```ts
|
|
185
|
-
// simple
|
|
202
|
+
// simple text
|
|
186
203
|
t('checkout.title', 'Order Summary')
|
|
187
204
|
|
|
188
|
-
// with variables
|
|
189
|
-
t('checkout.tax', 'Includes {tax} VAT', { tax: '19%' })
|
|
205
|
+
// with variables (note the $ prefix)
|
|
206
|
+
t('checkout.tax', 'Includes {$tax} VAT', { tax: '19%' })
|
|
207
|
+
|
|
208
|
+
// with pluralization (MF2 syntax)
|
|
209
|
+
t('cart.items', `.input {$count :number}
|
|
210
|
+
.match $count
|
|
211
|
+
one {{{$count} item}}
|
|
212
|
+
* {{{$count} items}}`, { count: 5 })
|
|
213
|
+
|
|
214
|
+
// with number formatting
|
|
215
|
+
t('stats.value', '{$value :number minimumFractionDigits=2}', { value: 1234.5 })
|
|
216
|
+
|
|
217
|
+
// with percentage
|
|
218
|
+
t('stats.ratio', '{$value :percent}', { value: 0.856 })
|
|
219
|
+
|
|
220
|
+
// with date/time formatting
|
|
221
|
+
t('event.date', '{$date :date style=long}', { date: new Date() })
|
|
222
|
+
t('event.time', '{$time :time style=short}', { time: new Date() })
|
|
190
223
|
|
|
191
|
-
// with
|
|
192
|
-
t('
|
|
224
|
+
// with currency (translator controls currency)
|
|
225
|
+
t('product.price', '{$price :currency currency=EUR}', { price: 99.99 })
|
|
226
|
+
|
|
227
|
+
// with currency (code controls currency via wrapped value)
|
|
228
|
+
t('product.price', '{$price :currency}', {
|
|
229
|
+
price: { valueOf: () => 99.99, options: { currency: 'EUR' } }
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// with units
|
|
233
|
+
t('distance', '{$value :unit unit=kilometer}', { value: 42 })
|
|
193
234
|
|
|
194
235
|
// with metadata object (for extraction tooling)
|
|
195
236
|
t('cart.total', {
|
|
196
|
-
|
|
237
|
+
example: 'Total: {$amount}',
|
|
197
238
|
description: 'Cart total price',
|
|
198
239
|
variables: {
|
|
199
|
-
amount: {
|
|
240
|
+
amount: { example: '$0.00', description: 'Formatted price' }
|
|
200
241
|
}
|
|
201
242
|
}, { amount: '$49.99' })
|
|
202
243
|
```
|
|
203
244
|
|
|
245
|
+
### `rich(key, fallback, components, values?)`
|
|
246
|
+
|
|
247
|
+
Translate with embedded components using MF2 markup syntax. Returns an array of strings and JSX elements that can be rendered directly.
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { rich } from '@astroscope/i18n/translate';
|
|
251
|
+
|
|
252
|
+
// basic link
|
|
253
|
+
rich('tos', 'Read our {#link}Terms of Service{/link}', {
|
|
254
|
+
link: (children) => <a href="/tos">{children}</a>
|
|
255
|
+
})
|
|
256
|
+
// Returns: ['Read our ', <a href="/tos">Terms of Service</a>]
|
|
257
|
+
|
|
258
|
+
// multiple tags
|
|
259
|
+
rich('legal', 'Read our {#tos}Terms{/tos} and {#privacy}Privacy Policy{/privacy}', {
|
|
260
|
+
tos: (children) => <a href="/tos">{children}</a>,
|
|
261
|
+
privacy: (children) => <a href="/privacy">{children}</a>,
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// with variables
|
|
265
|
+
rich('greeting', 'Hello {$name}, check your {#inbox}messages{/inbox}', {
|
|
266
|
+
inbox: (children) => <a href="/inbox">{children}</a>,
|
|
267
|
+
}, { name: 'Alice' })
|
|
268
|
+
|
|
269
|
+
// nested tags
|
|
270
|
+
rich('highlight', 'This is {#bold}very {#em}important{/em}{/bold}', {
|
|
271
|
+
bold: (children) => <strong>{children}</strong>,
|
|
272
|
+
em: (children) => <em>{children}</em>,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// standalone (self-closing) tags
|
|
276
|
+
rich('install', 'Click {#icon/} to install', {
|
|
277
|
+
icon: () => <DownloadIcon />,
|
|
278
|
+
})
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The same code works in both Astro templates and React islands — `rich()` is JSX-runtime agnostic.
|
|
282
|
+
|
|
204
283
|
### `i18n` singleton
|
|
205
284
|
|
|
206
285
|
```ts
|
|
@@ -256,11 +335,74 @@ export function App() {
|
|
|
256
335
|
3. **SSR** — Middleware provides translations to `t()`, merging manifest fallbacks for missing keys
|
|
257
336
|
4. **Client** — Custom directives load only the translations needed by each chunk
|
|
258
337
|
|
|
259
|
-
The same `import { t } from '@astroscope/i18n/
|
|
338
|
+
The same `import { t } from '@astroscope/i18n/translate'` works everywhere — bundler picks the correct implementation via conditional exports (`browser` vs `default`).
|
|
260
339
|
|
|
261
340
|
### Client bundle
|
|
262
341
|
|
|
263
|
-
Translation chunks are served as raw
|
|
342
|
+
Translation chunks are served as raw MessageFormat 2 strings and compiled on the browser on first use. This keeps chunk sizes minimal — the `messageformat` runtime is ~8KB gzipped. Compiled messages are cached for subsequent renders.
|
|
343
|
+
|
|
344
|
+
> **Future:** Once browsers ship native `Intl.MessageFormat`, this 8KB runtime will be replaced by the built-in API with zero bundle cost.
|
|
345
|
+
|
|
346
|
+
## MessageFormat 2 Syntax
|
|
347
|
+
|
|
348
|
+
This library uses [Unicode MessageFormat 2](https://github.com/unicode-org/message-format-wg) (MF2), the modern standard for internationalization.
|
|
349
|
+
|
|
350
|
+
### Basic syntax
|
|
351
|
+
|
|
352
|
+
```
|
|
353
|
+
Simple text
|
|
354
|
+
Hello {$name}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Pluralization
|
|
358
|
+
|
|
359
|
+
```
|
|
360
|
+
.input {$count :number}
|
|
361
|
+
.match $count
|
|
362
|
+
one {{{$count} item}}
|
|
363
|
+
* {{{$count} items}}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Selection (gender, etc.)
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
.input {$gender :string}
|
|
370
|
+
.match $gender
|
|
371
|
+
male {{He liked it}}
|
|
372
|
+
female {{She liked it}}
|
|
373
|
+
* {{They liked it}}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Built-in formatters
|
|
377
|
+
|
|
378
|
+
| Formatter | Description | Example |
|
|
379
|
+
|-----------|-------------|---------|
|
|
380
|
+
| `:number` | Locale-aware number | `{$n :number}` → "1,234.56" |
|
|
381
|
+
| `:integer` | Integer (no decimals) | `{$n :integer}` → "1,235" |
|
|
382
|
+
| `:percent` | Percentage | `{$n :percent}` → "85.6%" |
|
|
383
|
+
| `:currency` | Currency | `{$n :currency currency=EUR}` → "€99.99" |
|
|
384
|
+
| `:date` | Date | `{$d :date style=long}` → "January 26, 2026" |
|
|
385
|
+
| `:time` | Time | `{$d :time style=short}` → "3:45 PM" |
|
|
386
|
+
| `:datetime` | Date + time | `{$d :datetime dateStyle=medium timeStyle=short}` |
|
|
387
|
+
| `:unit` | Units | `{$n :unit unit=kilometer}` → "42 km" |
|
|
388
|
+
|
|
389
|
+
### Currency and unit options
|
|
390
|
+
|
|
391
|
+
For `:currency` and `:unit`, the required option (`currency` or `unit`) can be:
|
|
392
|
+
|
|
393
|
+
1. **Hardcoded in translation** (translator controls):
|
|
394
|
+
```
|
|
395
|
+
{$price :currency currency=EUR}
|
|
396
|
+
```
|
|
397
|
+
Code passes plain number: `{ price: 99.99 }`
|
|
398
|
+
|
|
399
|
+
2. **Provided by code** (for dynamic currency/unit):
|
|
400
|
+
```
|
|
401
|
+
{$price :currency}
|
|
402
|
+
```
|
|
403
|
+
Code passes wrapped value: `{ price: { valueOf: () => 99.99, options: { currency: 'EUR' } } }`
|
|
404
|
+
|
|
405
|
+
> **Note:** If both translation and code specify the option, **translation wins**.
|
|
264
406
|
|
|
265
407
|
## Configuration
|
|
266
408
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/shared/compiler.ts
|
|
2
|
+
import { MessageFormat } from "messageformat";
|
|
3
|
+
import { DraftFunctions } from "messageformat/functions";
|
|
4
|
+
var mfOptions = { functions: DraftFunctions };
|
|
5
|
+
var cache = /* @__PURE__ */ new Map();
|
|
6
|
+
function getOrCreateMessageFormat(locale, message) {
|
|
7
|
+
const cacheKey = `${locale}:${message}`;
|
|
8
|
+
const cached = cache.get(cacheKey);
|
|
9
|
+
if (cached) {
|
|
10
|
+
return cached;
|
|
11
|
+
}
|
|
12
|
+
const mf = new MessageFormat(locale, message, mfOptions);
|
|
13
|
+
cache.set(cacheKey, mf);
|
|
14
|
+
return mf;
|
|
15
|
+
}
|
|
16
|
+
function compileMessage(locale, message) {
|
|
17
|
+
try {
|
|
18
|
+
const mf = getOrCreateMessageFormat(locale, message);
|
|
19
|
+
return (values) => mf.format(values);
|
|
20
|
+
} catch {
|
|
21
|
+
return () => message;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function formatMessageToParts(locale, message, values) {
|
|
25
|
+
try {
|
|
26
|
+
const mf = getOrCreateMessageFormat(locale, message);
|
|
27
|
+
return mf.formatToParts(values);
|
|
28
|
+
} catch {
|
|
29
|
+
return [{ type: "literal", value: message }];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function compileTranslations(locale, raw) {
|
|
33
|
+
const compiled = {};
|
|
34
|
+
for (const [key, message] of Object.entries(raw)) {
|
|
35
|
+
compiled[key] = compileMessage(locale, message);
|
|
36
|
+
}
|
|
37
|
+
return compiled;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
compileMessage,
|
|
42
|
+
formatMessageToParts,
|
|
43
|
+
compileTranslations
|
|
44
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// src/shared/meta.ts
|
|
2
|
+
function normalizeMeta(meta) {
|
|
3
|
+
return typeof meta === "string" ? { fallback: meta } : meta;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/shared/rich.ts
|
|
7
|
+
var REACT_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("react.element");
|
|
8
|
+
var REACT_TRANSITIONAL_ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("react.transitional.element");
|
|
9
|
+
function isReactElement(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null || !("$$typeof" in value)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const type = value.$$typeof;
|
|
14
|
+
return type === REACT_ELEMENT_TYPE || type === REACT_TRANSITIONAL_ELEMENT_TYPE;
|
|
15
|
+
}
|
|
16
|
+
function addKeyIfNeeded(element, key) {
|
|
17
|
+
if (isReactElement(element) && element.key === null) {
|
|
18
|
+
return { ...element, key };
|
|
19
|
+
}
|
|
20
|
+
return element;
|
|
21
|
+
}
|
|
22
|
+
function isMarkupPart(part) {
|
|
23
|
+
return part.type === "markup";
|
|
24
|
+
}
|
|
25
|
+
function partsToNodes(parts, components) {
|
|
26
|
+
const result = [];
|
|
27
|
+
const stack = [];
|
|
28
|
+
let keyIndex = 0;
|
|
29
|
+
for (const part of parts) {
|
|
30
|
+
const target = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
31
|
+
if (isMarkupPart(part)) {
|
|
32
|
+
if (part.kind === "open") {
|
|
33
|
+
stack.push({ name: part.name, children: [] });
|
|
34
|
+
} else if (part.kind === "close") {
|
|
35
|
+
const frame = stack.pop();
|
|
36
|
+
if (!frame) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const component = components[frame.name];
|
|
40
|
+
const parent = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
41
|
+
if (component) {
|
|
42
|
+
parent.push(addKeyIfNeeded(component(frame.children), `rich-${keyIndex++}`));
|
|
43
|
+
} else {
|
|
44
|
+
parent.push(...frame.children);
|
|
45
|
+
}
|
|
46
|
+
} else if (part.kind === "standalone") {
|
|
47
|
+
const component = components[part.name];
|
|
48
|
+
if (component) {
|
|
49
|
+
target.push(addKeyIfNeeded(component([]), `rich-${keyIndex++}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} else if (part.type === "text") {
|
|
53
|
+
target.push(part.value);
|
|
54
|
+
} else if (part.type === "bidiIsolation") {
|
|
55
|
+
} else {
|
|
56
|
+
if ("value" in part && part.value !== void 0 && part.value !== null) {
|
|
57
|
+
const stringValue = String(part.value);
|
|
58
|
+
target.push(stringValue);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
while (stack.length > 0) {
|
|
63
|
+
const frame = stack.pop();
|
|
64
|
+
const parent = stack.length > 0 ? stack[stack.length - 1].children : result;
|
|
65
|
+
parent.push(...frame.children);
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
normalizeMeta,
|
|
72
|
+
partsToNodes
|
|
73
|
+
};
|
|
@@ -85,11 +85,16 @@ async function extractKeysFromFile(options) {
|
|
|
85
85
|
|
|
86
86
|
// src/extraction/key-store.ts
|
|
87
87
|
var KeyStore = class {
|
|
88
|
-
constructor(logger) {
|
|
88
|
+
constructor(logger, consistency = "warn") {
|
|
89
89
|
this.logger = logger;
|
|
90
|
+
this.consistency = consistency;
|
|
90
91
|
}
|
|
91
92
|
/** All key occurrences (may have duplicates) */
|
|
92
93
|
occurrences = [];
|
|
94
|
+
/** Tracks reported inconsistencies to avoid duplicate warnings */
|
|
95
|
+
reportedInconsistencies = /* @__PURE__ */ new Set();
|
|
96
|
+
/** Whether an error-level inconsistency was found (for build failure) */
|
|
97
|
+
hasInconsistencyError = false;
|
|
93
98
|
fileToKeys = /* @__PURE__ */ new Map();
|
|
94
99
|
filesWithI18n = /* @__PURE__ */ new Set();
|
|
95
100
|
/**
|
|
@@ -119,11 +124,103 @@ var KeyStore = class {
|
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
this.filesWithI18n.add(filename);
|
|
127
|
+
if (this.consistency !== "off") {
|
|
128
|
+
this.checkConsistency(keys);
|
|
129
|
+
}
|
|
122
130
|
if (keys.length > 0) {
|
|
123
131
|
this.occurrences.push(...keys);
|
|
124
132
|
}
|
|
125
133
|
this.fileToKeys.set(filename, newKeyStrings);
|
|
126
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Check new keys for inconsistencies with existing occurrences.
|
|
137
|
+
*/
|
|
138
|
+
checkConsistency(newKeys) {
|
|
139
|
+
const existingByKey = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const occ of this.occurrences) {
|
|
141
|
+
if (!existingByKey.has(occ.key)) {
|
|
142
|
+
existingByKey.set(occ.key, occ);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const newKey of newKeys) {
|
|
146
|
+
const existing = existingByKey.get(newKey.key);
|
|
147
|
+
if (!existing) continue;
|
|
148
|
+
const inconsistencies = this.findInconsistencies(newKey, existing);
|
|
149
|
+
for (const inc of inconsistencies) {
|
|
150
|
+
this.reportInconsistency(inc);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Compare two occurrences and find any metadata inconsistencies.
|
|
156
|
+
*/
|
|
157
|
+
findInconsistencies(a, b) {
|
|
158
|
+
const result = [];
|
|
159
|
+
const locA = `${a.file}:${a.line}`;
|
|
160
|
+
const locB = `${b.file}:${b.line}`;
|
|
161
|
+
if (a.meta.fallback !== b.meta.fallback) {
|
|
162
|
+
result.push({
|
|
163
|
+
key: a.key,
|
|
164
|
+
field: "fallback",
|
|
165
|
+
locations: [locA, locB],
|
|
166
|
+
values: [a.meta.fallback, b.meta.fallback]
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (a.meta.description !== b.meta.description) {
|
|
170
|
+
result.push({
|
|
171
|
+
key: a.key,
|
|
172
|
+
field: "description",
|
|
173
|
+
locations: [locA, locB],
|
|
174
|
+
values: [a.meta.description ?? "(none)", b.meta.description ?? "(none)"]
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const varsA = this.serializeVariables(a.meta);
|
|
178
|
+
const varsB = this.serializeVariables(b.meta);
|
|
179
|
+
if (varsA !== varsB) {
|
|
180
|
+
result.push({
|
|
181
|
+
key: a.key,
|
|
182
|
+
field: "variables",
|
|
183
|
+
locations: [locA, locB],
|
|
184
|
+
values: [varsA || "(none)", varsB || "(none)"]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Serialize variables for comparison.
|
|
191
|
+
*/
|
|
192
|
+
serializeVariables(meta) {
|
|
193
|
+
if (!meta.variables) return "";
|
|
194
|
+
const sorted = {};
|
|
195
|
+
for (const key of Object.keys(meta.variables).sort()) {
|
|
196
|
+
sorted[key] = meta.variables[key];
|
|
197
|
+
}
|
|
198
|
+
return JSON.stringify(sorted);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Report an inconsistency via logger.
|
|
202
|
+
*/
|
|
203
|
+
reportInconsistency(inc) {
|
|
204
|
+
const incKey = `${inc.key}:${inc.field}`;
|
|
205
|
+
if (this.reportedInconsistencies.has(incKey)) return;
|
|
206
|
+
this.reportedInconsistencies.add(incKey);
|
|
207
|
+
const message = `inconsistent ${inc.field} for key "${inc.key}"
|
|
208
|
+
${inc.locations[0]}: ${JSON.stringify(inc.values[0])}
|
|
209
|
+
${inc.locations[1]}: ${JSON.stringify(inc.values[1])}`;
|
|
210
|
+
if (this.consistency === "error") {
|
|
211
|
+
this.logger.error(message);
|
|
212
|
+
this.hasInconsistencyError = true;
|
|
213
|
+
} else {
|
|
214
|
+
this.logger.warn(message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Check if any error-level inconsistencies were found.
|
|
219
|
+
* Call this at the end of build to determine if it should fail.
|
|
220
|
+
*/
|
|
221
|
+
get hasErrors() {
|
|
222
|
+
return this.hasInconsistencyError;
|
|
223
|
+
}
|
|
127
224
|
/**
|
|
128
225
|
* Get deduplicated keys with all file locations merged.
|
|
129
226
|
* Last occurrence's meta wins for each key.
|
|
@@ -164,6 +261,9 @@ var KeyStore = class {
|
|
|
164
261
|
for (const file of other.filesWithI18n) {
|
|
165
262
|
this.filesWithI18n.add(file);
|
|
166
263
|
}
|
|
264
|
+
if (this.consistency !== "off") {
|
|
265
|
+
this.checkConsistency(other.occurrences);
|
|
266
|
+
}
|
|
167
267
|
this.occurrences.push(...other.occurrences);
|
|
168
268
|
}
|
|
169
269
|
/**
|
|
@@ -187,8 +287,9 @@ async function compileAstro(code, filename) {
|
|
|
187
287
|
return null;
|
|
188
288
|
}
|
|
189
289
|
}
|
|
190
|
-
async function scan(
|
|
191
|
-
const
|
|
290
|
+
async function scan(options) {
|
|
291
|
+
const { projectRoot, logger, consistency } = options;
|
|
292
|
+
const store = new KeyStore(logger, consistency);
|
|
192
293
|
const files = await glob(GLOB_PATTERN, {
|
|
193
294
|
cwd: projectRoot,
|
|
194
295
|
absolute: true,
|
|
@@ -197,7 +298,7 @@ async function scan(projectRoot, logger) {
|
|
|
197
298
|
const results = await Promise.all(
|
|
198
299
|
files.map(async (file) => {
|
|
199
300
|
let code = await fs.promises.readFile(file, "utf-8");
|
|
200
|
-
if (!code.includes("@astroscope/i18n/
|
|
301
|
+
if (!code.includes("@astroscope/i18n/translate")) {
|
|
201
302
|
return { file, keys: null };
|
|
202
303
|
}
|
|
203
304
|
if (file.endsWith(".astro")) {
|
|
@@ -231,8 +332,8 @@ var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
|
|
|
231
332
|
var isResolvedVirtualModuleId = (id) => id === RESOLVED_VIRTUAL_MODULE_ID || id.startsWith(`\0${VIRTUAL_MODULE_ID}?`);
|
|
232
333
|
var MANIFEST_FILE_NAME = "i18n-manifest.json";
|
|
233
334
|
function i18nVitePlugin(options) {
|
|
234
|
-
const { logger } = options;
|
|
235
|
-
const store = new KeyStore(logger);
|
|
335
|
+
const { logger, consistency } = options;
|
|
336
|
+
const store = new KeyStore(logger, consistency);
|
|
236
337
|
const syncGlobalState = () => {
|
|
237
338
|
const state = getGlobalState();
|
|
238
339
|
state.extractedKeys = store.extractedKeys;
|
|
@@ -254,7 +355,7 @@ function i18nVitePlugin(options) {
|
|
|
254
355
|
if (server.config.isProduction || isBuild) {
|
|
255
356
|
return;
|
|
256
357
|
}
|
|
257
|
-
const result = await scan(projectRoot, logger);
|
|
358
|
+
const result = await scan({ projectRoot, logger, consistency });
|
|
258
359
|
store.merge(result);
|
|
259
360
|
syncGlobalState();
|
|
260
361
|
logger.info(`dev mode: scanned ${result.fileToKeys.size} files, found ${result.uniqueKeyCount} keys`);
|
|
@@ -293,7 +394,7 @@ export function getManifest() { return _getManifest(); }
|
|
|
293
394
|
if (filename.includes("node_modules")) {
|
|
294
395
|
return null;
|
|
295
396
|
}
|
|
296
|
-
if (!code.includes("@astroscope/i18n/
|
|
397
|
+
if (!code.includes("@astroscope/i18n/translate")) {
|
|
297
398
|
return null;
|
|
298
399
|
}
|
|
299
400
|
const result = await extractKeysFromFile({
|
|
@@ -379,6 +480,9 @@ export function getManifest() { return _getManifest(); }
|
|
|
379
480
|
}
|
|
380
481
|
}
|
|
381
482
|
logger.info(`manifest: ${Object.keys(state.chunkManifest).length} chunks, ${store.uniqueKeyCount} keys`);
|
|
483
|
+
if (consistency === "error" && store.hasErrors) {
|
|
484
|
+
throw new Error("i18n: build failed due to translation key inconsistencies");
|
|
485
|
+
}
|
|
382
486
|
},
|
|
383
487
|
writeBundle(outputOptions) {
|
|
384
488
|
if (!isSSR && outputOptions.dir) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { R as RichComponents } from '../rich-CQUhdwBc.js';
|
|
2
|
+
import { a as TranslateFunction, T as TranslationMeta } from '../types-DNRFj1XA.js';
|
|
3
|
+
import '../types-BeDFfFIL.js';
|
|
4
|
+
|
|
5
|
+
declare function getLocale(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Client-side translate function
|
|
8
|
+
*
|
|
9
|
+
* Reads from window.__i18n__.translations (injected per-chunk).
|
|
10
|
+
* In production, translations are always loaded before component code runs
|
|
11
|
+
* via the chunk loader injected by the Vite plugin.
|
|
12
|
+
*
|
|
13
|
+
* Call patterns:
|
|
14
|
+
* - Development: t('key', 'fallback') or t('key', 'fallback', values)
|
|
15
|
+
* - Production: t('key') or t('key', undefined, values)
|
|
16
|
+
*
|
|
17
|
+
* Falls back to meta.fallback (dev) or key (prod) when translation is missing.
|
|
18
|
+
*/
|
|
19
|
+
declare const t: TranslateFunction;
|
|
20
|
+
/**
|
|
21
|
+
* Client-side rich text translation function
|
|
22
|
+
*
|
|
23
|
+
* Parses MF2 markup syntax and applies component callbacks to wrap content.
|
|
24
|
+
* Works with any JSX runtime (React, Preact, etc.).
|
|
25
|
+
*
|
|
26
|
+
* @param key - Translation key
|
|
27
|
+
* @param meta - Fallback string or meta object with fallback (used as message template)
|
|
28
|
+
* @param components - Map of tag names to component callbacks
|
|
29
|
+
* @param values - Runtime interpolation values (ICU MessageFormat)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* rich('tos', 'Read our {#link}Terms{/link}', {
|
|
34
|
+
* link: (children) => <a href="/tos">{children}</a>
|
|
35
|
+
* })
|
|
36
|
+
* // Returns: ['Read our ', <a href="/tos">Terms</a>]
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function rich<T = unknown>(key: string, meta?: TranslationMeta | string | undefined, components?: RichComponents<T> | undefined, values?: Record<string, unknown> | undefined): (string | T)[];
|
|
40
|
+
|
|
41
|
+
export { getLocale, rich, t };
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
-
normalizeMeta
|
|
3
|
-
|
|
2
|
+
normalizeMeta,
|
|
3
|
+
partsToNodes
|
|
4
|
+
} from "../chunk-VGPVG5JP.js";
|
|
4
5
|
import {
|
|
5
|
-
compileMessage
|
|
6
|
-
|
|
6
|
+
compileMessage,
|
|
7
|
+
formatMessageToParts
|
|
8
|
+
} from "../chunk-33JVAFGQ.js";
|
|
7
9
|
|
|
8
|
-
// src/client/
|
|
10
|
+
// src/client/translate.ts
|
|
9
11
|
var cache = /* @__PURE__ */ new Map();
|
|
10
12
|
function getLocale() {
|
|
11
13
|
return window.__i18n__.locale;
|
|
@@ -31,7 +33,14 @@ var t = ((key, meta, values) => {
|
|
|
31
33
|
}
|
|
32
34
|
return compiled(values);
|
|
33
35
|
});
|
|
36
|
+
function rich(key, meta, components, values) {
|
|
37
|
+
const normalizedMeta = meta ? normalizeMeta(meta) : { fallback: "" };
|
|
38
|
+
const raw = window.__i18n__.translations[key] ?? normalizedMeta.fallback ?? key;
|
|
39
|
+
const parts = formatMessageToParts(window.__i18n__.locale, raw, values);
|
|
40
|
+
return partsToNodes(parts, components ?? {});
|
|
41
|
+
}
|
|
34
42
|
export {
|
|
35
43
|
getLocale,
|
|
44
|
+
rich,
|
|
36
45
|
t
|
|
37
46
|
};
|
|
@@ -2,6 +2,7 @@ import { a as ExtractionManifest } from '../types-moTgocy7.js';
|
|
|
2
2
|
export { C as ChunkManifest, E as ExtractedKey } from '../types-moTgocy7.js';
|
|
3
3
|
import { AstroIntegrationLogger } from 'astro';
|
|
4
4
|
import { Plugin } from 'vite';
|
|
5
|
+
import { C as ConsistencyCheckLevel } from '../types-B_8vAmvv.js';
|
|
5
6
|
import '../types-DNRFj1XA.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -18,6 +19,7 @@ declare function getManifest(): ExtractionManifest;
|
|
|
18
19
|
|
|
19
20
|
type I18nVitePluginOptions = {
|
|
20
21
|
logger: AstroIntegrationLogger;
|
|
22
|
+
consistency: ConsistencyCheckLevel;
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* Vite plugin for i18n extraction, chunk mapping, and loader injection
|
package/dist/extraction/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { AstroIntegration, MiddlewareHandler, APIContext } from 'astro';
|
|
2
|
-
|
|
2
|
+
import { I as I18nOptions } from './types-B_8vAmvv.js';
|
|
3
|
+
export { F as FallbackBehavior, I as I18nConfig, i as i18n } from './i18n-DaLnNvss.js';
|
|
3
4
|
import { ExcludePattern } from '@astroscope/excludes';
|
|
4
5
|
export { R as RawTranslations, T as TranslationMeta } from './types-DNRFj1XA.js';
|
|
5
6
|
export { E as ExtractedKey, a as ExtractionManifest } from './types-moTgocy7.js';
|
|
6
7
|
|
|
7
|
-
interface I18nOptions {
|
|
8
|
-
}
|
|
9
|
-
|
|
10
8
|
/**
|
|
11
9
|
* Astro integration for i18n with automatic per-chunk translation loading.
|
|
12
10
|
*
|
|
@@ -14,7 +12,7 @@ interface I18nOptions {
|
|
|
14
12
|
* - Client directives (`client:load-x`, `client:idle-x`, `client:visible-x`, `client:media-x`, `client:only-x`)
|
|
15
13
|
* - Vite plugin for translation key extraction
|
|
16
14
|
*/
|
|
17
|
-
declare function i18nIntegration(
|
|
15
|
+
declare function i18nIntegration(options?: I18nOptions): AstroIntegration;
|
|
18
16
|
|
|
19
17
|
type I18nMiddlewareOptions = {
|
|
20
18
|
locale: (ctx: APIContext) => string;
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
i18nVitePlugin
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Z3LDPCYD.js";
|
|
4
4
|
import {
|
|
5
5
|
i18n,
|
|
6
6
|
runWithContext
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-MSJHXRJB.js";
|
|
8
|
+
import "./chunk-33JVAFGQ.js";
|
|
9
9
|
import "./chunk-4A5BKHFN.js";
|
|
10
10
|
|
|
11
11
|
// src/integration/integration.ts
|
|
12
12
|
var CLIENT_DIRECTIVES = ["load-x", "idle-x", "visible-x", "media-x", "only-x"];
|
|
13
|
-
function i18nIntegration(
|
|
13
|
+
function i18nIntegration(options = {}) {
|
|
14
|
+
const { consistency = "warn" } = options;
|
|
14
15
|
return {
|
|
15
16
|
name: "@astroscope/i18n",
|
|
16
17
|
hooks: {
|
|
@@ -24,7 +25,7 @@ function i18nIntegration(_options = {}) {
|
|
|
24
25
|
}
|
|
25
26
|
updateConfig({
|
|
26
27
|
vite: {
|
|
27
|
-
plugins: [i18nVitePlugin({ logger })]
|
|
28
|
+
plugins: [i18nVitePlugin({ logger, consistency })]
|
|
28
29
|
}
|
|
29
30
|
});
|
|
30
31
|
},
|
|
@@ -112,6 +113,7 @@ function createI18nMiddleware(options) {
|
|
|
112
113
|
const context = {
|
|
113
114
|
locale,
|
|
114
115
|
translations: i18n.getCompiledTranslations(locale),
|
|
116
|
+
rawTranslations: i18n.getTranslations(locale),
|
|
115
117
|
fallback: i18n.getConfig().fallback
|
|
116
118
|
};
|
|
117
119
|
return runWithContext(context, () => next());
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as I18nContext } from './i18n-
|
|
2
|
-
export { i as i18n } from './i18n-
|
|
1
|
+
import { a as I18nContext } from './i18n-DaLnNvss.js';
|
|
2
|
+
export { i as i18n } from './i18n-DaLnNvss.js';
|
|
3
3
|
export { I as I18nClientState } from './types-DNRFj1XA.js';
|
|
4
4
|
import './types-moTgocy7.js';
|
|
5
5
|
|
package/dist/internal.js
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component callbacks for rich text rendering.
|
|
3
|
+
* Each key maps to a function that wraps children in a component.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* const components: RichComponents = {
|
|
8
|
+
* link: (children) => <a href="/tos">{children}</a>,
|
|
9
|
+
* bold: (children) => <strong>{children}</strong>,
|
|
10
|
+
* };
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
type RichComponents<T = unknown> = Record<string, (children: (string | T)[]) => T>;
|
|
14
|
+
|
|
15
|
+
export type { RichComponents as R };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { R as RichComponents } from '../rich-CQUhdwBc.js';
|
|
2
|
+
import { a as TranslateFunction, T as TranslationMeta } from '../types-DNRFj1XA.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the current locale from request context.
|
|
6
|
+
* Returns defaultLocale if called outside a request (e.g. during build).
|
|
7
|
+
*/
|
|
8
|
+
declare function getLocale(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Server-side translate function
|
|
11
|
+
*
|
|
12
|
+
* Call patterns:
|
|
13
|
+
* - Development: t('key', 'fallback') or t('key', 'fallback', values)
|
|
14
|
+
* - Production: t('key') or t('key', undefined, values)
|
|
15
|
+
*/
|
|
16
|
+
declare const t: TranslateFunction;
|
|
17
|
+
/**
|
|
18
|
+
* Server-side rich text translation function
|
|
19
|
+
*
|
|
20
|
+
* Parses MF2 markup syntax and applies component callbacks to wrap content.
|
|
21
|
+
* Works with any JSX runtime (Astro, React during SSR, etc.).
|
|
22
|
+
*
|
|
23
|
+
* @param key - Translation key
|
|
24
|
+
* @param meta - Fallback string or meta object with fallback (used as message template)
|
|
25
|
+
* @param components - Map of tag names to component callbacks
|
|
26
|
+
* @param values - Runtime interpolation values (ICU MessageFormat)
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* rich('tos', 'Read our {#link}Terms{/link}', {
|
|
31
|
+
* link: (children) => <a href="/tos">{children}</a>
|
|
32
|
+
* })
|
|
33
|
+
* // Returns: ['Read our ', <a href="/tos">Terms</a>]
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function rich<T = unknown>(key: string, meta?: TranslationMeta | string | undefined, components?: RichComponents<T> | undefined, values?: Record<string, unknown> | undefined): (string | T)[];
|
|
37
|
+
|
|
38
|
+
export { getLocale, rich, t };
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getContext,
|
|
3
3
|
i18n
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-MSJHXRJB.js";
|
|
5
5
|
import {
|
|
6
|
-
normalizeMeta
|
|
7
|
-
|
|
6
|
+
normalizeMeta,
|
|
7
|
+
partsToNodes
|
|
8
|
+
} from "../chunk-VGPVG5JP.js";
|
|
8
9
|
import {
|
|
9
|
-
compileMessage
|
|
10
|
-
|
|
10
|
+
compileMessage,
|
|
11
|
+
formatMessageToParts
|
|
12
|
+
} from "../chunk-33JVAFGQ.js";
|
|
11
13
|
|
|
12
|
-
// src/server/
|
|
14
|
+
// src/server/translate.ts
|
|
13
15
|
var fallbackCache = /* @__PURE__ */ new Map();
|
|
14
16
|
function applyFallback(key, meta, fallback) {
|
|
15
17
|
if (typeof fallback === "function") {
|
|
@@ -53,7 +55,16 @@ var t = ((key, meta, values) => {
|
|
|
53
55
|
}
|
|
54
56
|
return compiled(values);
|
|
55
57
|
});
|
|
58
|
+
function rich(key, meta, components, values) {
|
|
59
|
+
const normalizedMeta = meta ? normalizeMeta(meta) : { fallback: "" };
|
|
60
|
+
const ctx = getContext();
|
|
61
|
+
const locale = ctx?.locale ?? i18n.getConfig().defaultLocale;
|
|
62
|
+
const raw = ctx?.rawTranslations[key] ?? normalizedMeta.fallback ?? key;
|
|
63
|
+
const parts = formatMessageToParts(locale, raw, values);
|
|
64
|
+
return partsToNodes(parts, components ?? {});
|
|
65
|
+
}
|
|
56
66
|
export {
|
|
57
67
|
getLocale,
|
|
68
|
+
rich,
|
|
58
69
|
t
|
|
59
70
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consistency check level for translation keys.
|
|
3
|
+
* Controls behavior when the same key has different fallbacks/variables across files.
|
|
4
|
+
*
|
|
5
|
+
* - 'warn' (default): Log a warning but continue
|
|
6
|
+
* - 'error': Fail the build
|
|
7
|
+
* - 'off': Disable consistency checking
|
|
8
|
+
*/
|
|
9
|
+
type ConsistencyCheckLevel = 'off' | 'warn' | 'error';
|
|
10
|
+
interface I18nOptions {
|
|
11
|
+
/**
|
|
12
|
+
* How to handle inconsistent translation keys (same key with different fallbacks/variables).
|
|
13
|
+
* @default 'warn'
|
|
14
|
+
*/
|
|
15
|
+
consistency?: ConsistencyCheckLevel | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type { ConsistencyCheckLevel as C, I18nOptions as I };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/i18n",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "i18n for Astro + React islands — automatic tree-shaking, parallel loading, any translation source",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "i18n for Astro + React islands — MessageFormat 2, automatic tree-shaking, parallel loading, any translation source",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
},
|
|
13
|
-
"./
|
|
14
|
-
"types": "./dist/client/
|
|
15
|
-
"browser": "./dist/client/
|
|
16
|
-
"default": "./dist/server/
|
|
13
|
+
"./translate": {
|
|
14
|
+
"types": "./dist/client/translate.d.ts",
|
|
15
|
+
"browser": "./dist/client/translate.js",
|
|
16
|
+
"default": "./dist/server/translate.js"
|
|
17
17
|
},
|
|
18
18
|
"./client-directives/load.js": {
|
|
19
19
|
"types": "./dist/client/directives/load.d.ts",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"types": "./dist/client/directives/only.d.ts",
|
|
36
36
|
"import": "./dist/client/directives/only.js"
|
|
37
37
|
},
|
|
38
|
-
"./
|
|
39
|
-
"import": "./src/
|
|
38
|
+
"./astro": {
|
|
39
|
+
"import": "./src/astro/index.ts"
|
|
40
40
|
},
|
|
41
41
|
"./internal": {
|
|
42
42
|
"types": "./dist/internal.d.ts",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
"dist",
|
|
52
|
-
"src/
|
|
52
|
+
"src/astro"
|
|
53
53
|
],
|
|
54
54
|
"sideEffects": [
|
|
55
55
|
"./dist/client/directives/*.js",
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
"i18n",
|
|
70
70
|
"internationalization",
|
|
71
71
|
"translations",
|
|
72
|
+
"messageformat",
|
|
73
|
+
"mf2",
|
|
72
74
|
"react",
|
|
73
75
|
"islands",
|
|
74
76
|
"ssr"
|
|
@@ -80,7 +82,7 @@
|
|
|
80
82
|
},
|
|
81
83
|
"homepage": "https://github.com/smnbbrv/astroscope/tree/main/packages/i18n#readme",
|
|
82
84
|
"scripts": {
|
|
83
|
-
"build": "tsup src/index.ts src/internal.ts src/extraction/index.ts src/server/
|
|
85
|
+
"build": "tsup src/index.ts src/internal.ts src/extraction/index.ts src/server/translate.ts src/client/translate.ts src/client/directives/load.ts src/client/directives/idle.ts src/client/directives/visible.ts src/client/directives/media.ts src/client/directives/only.ts src/client/directives/utils.ts --format esm --dts --external astro --external astro/client/load.js --external astro/client/idle.js --external astro/client/visible.js --external astro/client/media.js --external astro/client/only.js --external virtual:@astroscope/i18n/manifest --external @babel/core",
|
|
84
86
|
"typecheck": "tsc --noEmit",
|
|
85
87
|
"lint": "eslint 'src/**/*.ts'",
|
|
86
88
|
"lint:fix": "eslint 'src/**/*.ts' --fix"
|
|
@@ -89,7 +91,7 @@
|
|
|
89
91
|
"@astrojs/compiler": "^2.12.2",
|
|
90
92
|
"@babel/core": "^7.28.6",
|
|
91
93
|
"@babel/preset-typescript": "^7.28.5",
|
|
92
|
-
"
|
|
94
|
+
"messageformat": "^4.0.0",
|
|
93
95
|
"glob": "^11.0.0",
|
|
94
96
|
"magic-string": "^0.30.21"
|
|
95
97
|
},
|
package/dist/chunk-EMM6LRO2.js
DELETED
package/dist/chunk-KNDYKTHE.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// src/shared/compiler.ts
|
|
2
|
-
import MessageFormat from "@messageformat/core";
|
|
3
|
-
var cache = /* @__PURE__ */ new Map();
|
|
4
|
-
function getMessageFormat(locale) {
|
|
5
|
-
let mf = cache.get(locale);
|
|
6
|
-
if (!mf) {
|
|
7
|
-
mf = new MessageFormat(locale);
|
|
8
|
-
cache.set(locale, mf);
|
|
9
|
-
}
|
|
10
|
-
return mf;
|
|
11
|
-
}
|
|
12
|
-
function compileMessage(locale, message) {
|
|
13
|
-
const mf = getMessageFormat(locale);
|
|
14
|
-
try {
|
|
15
|
-
return mf.compile(message);
|
|
16
|
-
} catch {
|
|
17
|
-
return () => message;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function compileTranslations(locale, raw) {
|
|
21
|
-
const compiled = {};
|
|
22
|
-
for (const [key, message] of Object.entries(raw)) {
|
|
23
|
-
compiled[key] = compileMessage(locale, message);
|
|
24
|
-
}
|
|
25
|
-
return compiled;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export {
|
|
29
|
-
compileMessage,
|
|
30
|
-
compileTranslations
|
|
31
|
-
};
|
package/dist/client/t.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { a as TranslateFunction } from '../types-DNRFj1XA.js';
|
|
2
|
-
import '../types-BeDFfFIL.js';
|
|
3
|
-
|
|
4
|
-
declare function getLocale(): string;
|
|
5
|
-
/**
|
|
6
|
-
* Client-side translate function
|
|
7
|
-
*
|
|
8
|
-
* Reads from window.__i18n__.translations (injected per-chunk).
|
|
9
|
-
* In production, translations are always loaded before component code runs
|
|
10
|
-
* via the chunk loader injected by the Vite plugin.
|
|
11
|
-
*
|
|
12
|
-
* Call patterns:
|
|
13
|
-
* - Development: t('key', 'fallback') or t('key', 'fallback', values)
|
|
14
|
-
* - Production: t('key') or t('key', undefined, values)
|
|
15
|
-
*
|
|
16
|
-
* Falls back to meta.fallback (dev) or key (prod) when translation is missing.
|
|
17
|
-
*/
|
|
18
|
-
declare const t: TranslateFunction;
|
|
19
|
-
|
|
20
|
-
export { getLocale, t };
|
package/dist/server/t.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { a as TranslateFunction } from '../types-DNRFj1XA.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get the current locale from request context.
|
|
5
|
-
* Returns defaultLocale if called outside a request (e.g. during build).
|
|
6
|
-
*/
|
|
7
|
-
declare function getLocale(): string;
|
|
8
|
-
/**
|
|
9
|
-
* Server-side translate function
|
|
10
|
-
*
|
|
11
|
-
* Call patterns:
|
|
12
|
-
* - Development: t('key', 'fallback') or t('key', 'fallback', values)
|
|
13
|
-
* - Production: t('key') or t('key', undefined, values)
|
|
14
|
-
*/
|
|
15
|
-
declare const t: TranslateFunction;
|
|
16
|
-
|
|
17
|
-
export { getLocale, t };
|
|
File without changes
|
|
File without changes
|