astro-intl 2.0.1 → 2.0.3
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 +701 -701
- package/package.json +99 -99
- package/dist/react.d.ts +0 -5
- package/dist/react.js +0 -42
package/README.md
CHANGED
|
@@ -1,701 +1,701 @@
|
|
|
1
|
-
# astro-intl
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## ✨
|
|
6
|
-
|
|
7
|
-
- 🔒 **Type-safe**:
|
|
8
|
-
- 🎯 **API
|
|
9
|
-
- ⚛️ **
|
|
10
|
-
- 🧡 **
|
|
11
|
-
- 🎨 **Markup
|
|
12
|
-
- 📁 **Namespaces**:
|
|
13
|
-
- 🌐 **
|
|
14
|
-
- 🛡️ **Concurrency-safe**:
|
|
15
|
-
- 🌍 **Multi-runtime**: Compatible
|
|
16
|
-
- ⚙️ **
|
|
17
|
-
- 🗺️ **
|
|
18
|
-
- 🔄 **
|
|
19
|
-
- 🔗 **
|
|
20
|
-
- 📦 **Sub-path imports**: `astro-intl/react`, `astro-intl/svelte`, `astro-intl/routing`, `astro-intl/middleware`
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
### Breaking changes
|
|
25
|
-
|
|
26
|
-
1. **`getTranslationsReact`
|
|
27
|
-
|
|
28
|
-
```diff
|
|
29
|
-
- import { getTranslationsReact } from "astro-intl";
|
|
30
|
-
+ import { getTranslations } from "astro-intl/react";
|
|
31
|
-
|
|
32
|
-
- const t = getTranslationsReact();
|
|
33
|
-
+ const t = getTranslations();
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
2. **Sub-path imports
|
|
37
|
-
- React: `astro-intl/react`
|
|
38
|
-
- Svelte: `astro-intl/svelte`
|
|
39
|
-
|
|
40
|
-
3.
|
|
41
|
-
|
|
42
|
-
###
|
|
43
|
-
|
|
44
|
-
- **
|
|
45
|
-
- **`createGetTranslations` factory**
|
|
46
|
-
- **`parseRichSegments()`**
|
|
47
|
-
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
###
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
npx astro add astro-intl
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- ✅
|
|
61
|
-
- ✅
|
|
62
|
-
- ✅
|
|
63
|
-
|
|
64
|
-
###
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm install astro-intl
|
|
70
|
-
# o
|
|
71
|
-
pnpm add astro-intl
|
|
72
|
-
# o
|
|
73
|
-
yarn add astro-intl
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```js
|
|
79
|
-
import { defineConfig } from "astro/config";
|
|
80
|
-
import astroIntl from "astro-intl";
|
|
81
|
-
|
|
82
|
-
export default defineConfig({
|
|
83
|
-
integrations: [
|
|
84
|
-
astroIntl({
|
|
85
|
-
defaultLocale: "en", //
|
|
86
|
-
}),
|
|
87
|
-
],
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## 🎯
|
|
92
|
-
|
|
93
|
-
###
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
// src/i18n/es.json
|
|
99
|
-
{
|
|
100
|
-
"welcome": "Bienvenido",
|
|
101
|
-
"nav": {
|
|
102
|
-
"home": "Inicio",
|
|
103
|
-
"about": "Acerca de"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/i18n/en.json
|
|
108
|
-
{
|
|
109
|
-
"welcome": "Welcome",
|
|
110
|
-
"nav": {
|
|
111
|
-
"home": "Home",
|
|
112
|
-
"about": "About"
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// src/i18n/index.ts
|
|
117
|
-
import es from './es.json';
|
|
118
|
-
import en from './en.json';
|
|
119
|
-
|
|
120
|
-
export const ui = { es, en };
|
|
121
|
-
export type Messages = typeof es;
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
###
|
|
125
|
-
|
|
126
|
-
```astro
|
|
127
|
-
---
|
|
128
|
-
import { setRequestLocale, getTranslations } from 'astro-intl';
|
|
129
|
-
import { ui } from '../i18n';
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
await setRequestLocale(Astro.url, async (locale) => ({
|
|
133
|
-
locale,
|
|
134
|
-
messages: ui[locale as keyof typeof ui]
|
|
135
|
-
}));
|
|
136
|
-
|
|
137
|
-
//
|
|
138
|
-
const t = getTranslations();
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
<h1>{t('welcome')}</h1>
|
|
142
|
-
<nav>
|
|
143
|
-
<a href="/">{t('nav.home')}</a>
|
|
144
|
-
<a href="/about">{t('nav.about')}</a>
|
|
145
|
-
</nav>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
###
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
// src/i18n/en.json
|
|
154
|
-
{
|
|
155
|
-
"greeting": "Hello, {name}!",
|
|
156
|
-
"info": "You have {count} items"
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
```astro
|
|
161
|
-
---
|
|
162
|
-
const t = getTranslations();
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
<p>{t('greeting', { name: 'John' })}</p> <!-- "Hello, John!" -->
|
|
166
|
-
<p>{t('info', { count: 5 })}</p> <!-- "You have 5 items" -->
|
|
167
|
-
<p>{t('greeting')}</p> <!-- "Hello, {name}!" (
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
###
|
|
173
|
-
|
|
174
|
-
```astro
|
|
175
|
-
---
|
|
176
|
-
// src/i18n/es.json
|
|
177
|
-
// { "terms": "Acepto los <link>términos y condiciones</link>" }
|
|
178
|
-
|
|
179
|
-
const t = getTranslations();
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
<p set:html={t.markup('terms', {
|
|
183
|
-
link: (chunks) => `<a href="/terms">${chunks}</a>`
|
|
184
|
-
})} />
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Markup
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
```astro
|
|
192
|
-
---
|
|
193
|
-
// src/i18n/en.json
|
|
194
|
-
// { "welcome": "Hello {name}, click <link>here</link> to continue" }
|
|
195
|
-
|
|
196
|
-
const t = getTranslations();
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
<p set:html={t.markup('welcome', {
|
|
200
|
-
values: { name: 'John' },
|
|
201
|
-
tags: {
|
|
202
|
-
link: (chunks) => `<a href="/home">${chunks}</a>`
|
|
203
|
-
}
|
|
204
|
-
})} />
|
|
205
|
-
<!-- "Hello John, click <a href="/home">here</a> to continue" -->
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
###
|
|
209
|
-
|
|
210
|
-
> **v2**:
|
|
211
|
-
|
|
212
|
-
```tsx
|
|
213
|
-
import { getTranslations } from "astro-intl/react";
|
|
214
|
-
|
|
215
|
-
export function MyComponent() {
|
|
216
|
-
const t = getTranslations();
|
|
217
|
-
|
|
218
|
-
return (
|
|
219
|
-
<div>
|
|
220
|
-
<h1>{t("welcome")}</h1>
|
|
221
|
-
<nav>
|
|
222
|
-
<a href="/">{t("nav.home")}</a>
|
|
223
|
-
</nav>
|
|
224
|
-
</div>
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
####
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
import { createGetTranslations } from "astro-intl/react";
|
|
235
|
-
import { ui } from "../i18n";
|
|
236
|
-
|
|
237
|
-
const getT = createGetTranslations(ui, "en");
|
|
238
|
-
|
|
239
|
-
export function MyComponent({ lang }: { lang: string }) {
|
|
240
|
-
const t = getT(lang, "nav");
|
|
241
|
-
return <a href="/">{t("home")}</a>;
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
###
|
|
246
|
-
|
|
247
|
-
```tsx
|
|
248
|
-
import { getTranslations } from "astro-intl/react";
|
|
249
|
-
|
|
250
|
-
export function MyComponent() {
|
|
251
|
-
const t = getTranslations();
|
|
252
|
-
|
|
253
|
-
// src/i18n/es.json
|
|
254
|
-
// { "terms": "Acepto los <link>términos y condiciones</link>" }
|
|
255
|
-
|
|
256
|
-
return (
|
|
257
|
-
<p>
|
|
258
|
-
{t.rich("terms", {
|
|
259
|
-
link: (chunks) => <a href="/terms">{chunks}</a>,
|
|
260
|
-
})}
|
|
261
|
-
</p>
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
###
|
|
267
|
-
|
|
268
|
-
> **v2**:
|
|
269
|
-
|
|
270
|
-
```svelte
|
|
271
|
-
<script>
|
|
272
|
-
import { getTranslations } from 'astro-intl/svelte';
|
|
273
|
-
|
|
274
|
-
const t = getTranslations();
|
|
275
|
-
</script>
|
|
276
|
-
|
|
277
|
-
<h1>{t('welcome')}</h1>
|
|
278
|
-
<nav>
|
|
279
|
-
<a href="/">{t('nav.home')}</a>
|
|
280
|
-
</nav>
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
#### Rich text
|
|
284
|
-
|
|
285
|
-
`t.rich()`
|
|
286
|
-
|
|
287
|
-
```svelte
|
|
288
|
-
<script>
|
|
289
|
-
import { getTranslations, renderRichText } from 'astro-intl/svelte';
|
|
290
|
-
|
|
291
|
-
// { "terms": "Acepto los <link>términos y condiciones</link>" }
|
|
292
|
-
const t = getTranslations();
|
|
293
|
-
const segments = t.rich('terms', ['link']);
|
|
294
|
-
|
|
295
|
-
const html = renderRichText(segments, {
|
|
296
|
-
tags: { link: 'a' }, //
|
|
297
|
-
});
|
|
298
|
-
</script>
|
|
299
|
-
|
|
300
|
-
<p>{@html html}</p>
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
```svelte
|
|
306
|
-
<script>
|
|
307
|
-
import { getTranslations, renderRichText } from 'astro-intl/svelte';
|
|
308
|
-
|
|
309
|
-
const t = getTranslations();
|
|
310
|
-
const segments = t.rich('terms', ['link']);
|
|
311
|
-
|
|
312
|
-
const html = renderRichText(segments, {
|
|
313
|
-
components: {
|
|
314
|
-
link: (chunks) => `<a href="/terms" class="underline">${chunks}</a>`,
|
|
315
|
-
},
|
|
316
|
-
});
|
|
317
|
-
</script>
|
|
318
|
-
|
|
319
|
-
<p>{@html html}</p>
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
####
|
|
323
|
-
|
|
324
|
-
```svelte
|
|
325
|
-
<script>
|
|
326
|
-
import { createGetTranslations } from 'astro-intl/svelte';
|
|
327
|
-
import { ui } from '../i18n';
|
|
328
|
-
|
|
329
|
-
const getT = createGetTranslations(ui, 'en');
|
|
330
|
-
|
|
331
|
-
export let lang;
|
|
332
|
-
const t = getT(lang, 'nav');
|
|
333
|
-
</script>
|
|
334
|
-
|
|
335
|
-
<a href="/">{t('home')}</a>
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Type-safety
|
|
339
|
-
|
|
340
|
-
```astro
|
|
341
|
-
---
|
|
342
|
-
import { setRequestLocale, getTranslations } from 'astro-intl';
|
|
343
|
-
import { ui, type Messages } from '../i18n';
|
|
344
|
-
|
|
345
|
-
await setRequestLocale(Astro.url, async (locale) => ({
|
|
346
|
-
locale,
|
|
347
|
-
messages: ui[locale as keyof typeof ui]
|
|
348
|
-
}));
|
|
349
|
-
|
|
350
|
-
//
|
|
351
|
-
const t = getTranslations<Messages>();
|
|
352
|
-
|
|
353
|
-
// TypeScript
|
|
354
|
-
// t('nav.home') ✓
|
|
355
|
-
// t('nav.invalid') ✗
|
|
356
|
-
---
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
###
|
|
360
|
-
|
|
361
|
-
```astro
|
|
362
|
-
---
|
|
363
|
-
//
|
|
364
|
-
const t = getTranslations<Messages>('nav');
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
<nav>
|
|
368
|
-
<a href="/">{t('home')}</a> <!--
|
|
369
|
-
<a href="/about">{t('about')}</a>
|
|
370
|
-
</nav>
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
##
|
|
374
|
-
|
|
375
|
-
###
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
```ts
|
|
380
|
-
// src/i18n/routing.ts
|
|
381
|
-
export const routing = {
|
|
382
|
-
locales: ["en", "es"],
|
|
383
|
-
defaultLocale: "en",
|
|
384
|
-
routes: {
|
|
385
|
-
home: { en: "/", es: "/" },
|
|
386
|
-
about: { en: "/about", es: "/sobre-nosotros" },
|
|
387
|
-
blog: { en: "/blog/[slug]", es: "/blog/[slug]" },
|
|
388
|
-
shop: { en: "/shop/[category]/[id]", es: "/tienda/[category]/[id]" },
|
|
389
|
-
},
|
|
390
|
-
} as const;
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
###
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
```ts
|
|
398
|
-
// src/middleware.ts
|
|
399
|
-
import "@/i18n/request";
|
|
400
|
-
import { createIntlMiddleware } from "astro-intl/middleware";
|
|
401
|
-
import { routing } from "@/i18n/routing";
|
|
402
|
-
|
|
403
|
-
export const onRequest = createIntlMiddleware(routing);
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
###
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
```js
|
|
413
|
-
// astro.config.mjs
|
|
414
|
-
import { defineConfig } from "astro/config";
|
|
415
|
-
import astroIntl from "astro-intl";
|
|
416
|
-
|
|
417
|
-
export default defineConfig({
|
|
418
|
-
integrations: [
|
|
419
|
-
astroIntl({
|
|
420
|
-
defaultLocale: "en",
|
|
421
|
-
locales: ["en", "es"],
|
|
422
|
-
routes: {
|
|
423
|
-
about: { en: "/about", es: "/sobre-nosotros" },
|
|
424
|
-
},
|
|
425
|
-
}),
|
|
426
|
-
],
|
|
427
|
-
});
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
```astro
|
|
433
|
-
---
|
|
434
|
-
// src/pages/[lang]/sobre-nosotros.astro
|
|
435
|
-
export { default } from "./about.astro";
|
|
436
|
-
export { getStaticPaths } from "./about.astro";
|
|
437
|
-
---
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
###
|
|
441
|
-
|
|
442
|
-
```astro
|
|
443
|
-
---
|
|
444
|
-
import { path } from "astro-intl/routing";
|
|
445
|
-
---
|
|
446
|
-
|
|
447
|
-
<a href={path("about")}>About</a>
|
|
448
|
-
<!-- locale "en" → /en/about -->
|
|
449
|
-
<!-- locale "es" → /es/sobre-nosotros -->
|
|
450
|
-
|
|
451
|
-
<a href={path("shop", { locale: "es", params: { category: "ropa", id: "42" } })}>
|
|
452
|
-
|
|
453
|
-
</a>
|
|
454
|
-
<!-- → /es/tienda/ropa/42 -->
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
###
|
|
458
|
-
|
|
459
|
-
```astro
|
|
460
|
-
---
|
|
461
|
-
import { switchLocalePath } from "astro-intl/routing";
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
<a href={switchLocalePath(Astro.url.pathname, "en")}>English</a>
|
|
465
|
-
<a href={switchLocalePath(Astro.url.pathname, "es")}>Español</a>
|
|
466
|
-
<!--
|
|
467
|
-
<!--
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
##
|
|
471
|
-
|
|
472
|
-
### `astroIntl(options?)`
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
**
|
|
477
|
-
|
|
478
|
-
- `defaultLocale?: string` -
|
|
479
|
-
- `enabled?: boolean` -
|
|
480
|
-
- `messages?: MessagesConfig` -
|
|
481
|
-
- `locales?: string[]` -
|
|
482
|
-
- `routes?: RoutesMap` -
|
|
483
|
-
|
|
484
|
-
### `setRequestLocale(url, getConfig?)`
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
**
|
|
489
|
-
|
|
490
|
-
- `url: URL` -
|
|
491
|
-
- `getConfig?: (locale: string) => RequestConfig | Promise<RequestConfig>` -
|
|
492
|
-
|
|
493
|
-
**
|
|
494
|
-
|
|
495
|
-
```ts
|
|
496
|
-
await setRequestLocale(Astro.url, async (locale) => ({
|
|
497
|
-
locale,
|
|
498
|
-
messages: ui[locale],
|
|
499
|
-
}));
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
### `runWithLocale(url, fn, getConfig?)`
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
**
|
|
507
|
-
|
|
508
|
-
- `url: URL` -
|
|
509
|
-
- `fn: () => R | Promise<R>` -
|
|
510
|
-
- `getConfig?: GetRequestConfigFn` -
|
|
511
|
-
|
|
512
|
-
**
|
|
513
|
-
|
|
514
|
-
```ts
|
|
515
|
-
// src/middleware.ts
|
|
516
|
-
import { runWithLocale } from "astro-intl";
|
|
517
|
-
|
|
518
|
-
export const onRequest = async (context, next) => {
|
|
519
|
-
return runWithLocale(
|
|
520
|
-
context.url,
|
|
521
|
-
() => next(),
|
|
522
|
-
(locale) => ({
|
|
523
|
-
locale,
|
|
524
|
-
messages: ui[locale],
|
|
525
|
-
})
|
|
526
|
-
);
|
|
527
|
-
};
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### `getTranslations<T>(namespace?)`
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
**
|
|
535
|
-
|
|
536
|
-
- `namespace?: string` -
|
|
537
|
-
|
|
538
|
-
**
|
|
539
|
-
|
|
540
|
-
#### `t(key, values?)`
|
|
541
|
-
|
|
542
|
-
- `key: string` -
|
|
543
|
-
- `values?: Record<string, Primitive>` -
|
|
544
|
-
|
|
545
|
-
#### `t.markup(key, options)`
|
|
546
|
-
|
|
547
|
-
- `key: string` -
|
|
548
|
-
- `options` -
|
|
549
|
-
- `Record<string, (chunks: string) => string>` -
|
|
550
|
-
- `{ values?: Record<string, Primitive>, tags: Record<string, (chunks: string) => string> }` - Tags
|
|
551
|
-
|
|
552
|
-
### `getTranslations()` — `astro-intl/react`
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
**
|
|
557
|
-
|
|
558
|
-
### `createGetTranslations(ui, defaultLocale)` — `astro-intl/react`
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
**
|
|
563
|
-
|
|
564
|
-
- `ui: Record<string, Record<string, unknown>>` -
|
|
565
|
-
- `defaultLocale: string` -
|
|
566
|
-
|
|
567
|
-
**
|
|
568
|
-
|
|
569
|
-
### `getTranslations()` — `astro-intl/svelte`
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
**
|
|
574
|
-
|
|
575
|
-
### `createGetTranslations(ui, defaultLocale)` — `astro-intl/svelte`
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
### `renderRichText(segments, options?)` — `astro-intl/svelte`
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
**
|
|
584
|
-
|
|
585
|
-
- `segments: RichSegment[]` -
|
|
586
|
-
- `options.tags?: Record<string, string>` -
|
|
587
|
-
- `options.components?: Record<string, (chunks: string) => string>` -
|
|
588
|
-
|
|
589
|
-
**
|
|
590
|
-
|
|
591
|
-
### `getLocale()`
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
**
|
|
596
|
-
|
|
597
|
-
### `createIntlMiddleware(options)`
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
**
|
|
602
|
-
|
|
603
|
-
- `locales: string[]` -
|
|
604
|
-
- `defaultLocale?: string` -
|
|
605
|
-
- `routes?: RoutesMap` -
|
|
606
|
-
|
|
607
|
-
### `path(routeKey, options?)`
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
**
|
|
612
|
-
|
|
613
|
-
- `routeKey: string` -
|
|
614
|
-
- `options?.locale` -
|
|
615
|
-
- `options?.params` - `Record<string, string>`
|
|
616
|
-
- `options?.encode` -
|
|
617
|
-
|
|
618
|
-
**
|
|
619
|
-
|
|
620
|
-
### `switchLocalePath(currentPath, nextLocale)`
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
**
|
|
625
|
-
|
|
626
|
-
- `currentPath: string | URL` -
|
|
627
|
-
- `nextLocale: string` -
|
|
628
|
-
|
|
629
|
-
**
|
|
630
|
-
|
|
631
|
-
---
|
|
632
|
-
|
|
633
|
-
## 🚀
|
|
634
|
-
|
|
635
|
-
###
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
```bash
|
|
640
|
-
npm run build
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
###
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
```bash
|
|
650
|
-
npm run dev
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
###
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
```bash
|
|
658
|
-
pnpm install
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
## 📦
|
|
664
|
-
|
|
665
|
-
```text
|
|
666
|
-
packages/integration/
|
|
667
|
-
├── src/
|
|
668
|
-
│ ├── adapters/
|
|
669
|
-
│ │ ├── react.ts # Adapter
|
|
670
|
-
│ │ └── svelte.ts # Adapter
|
|
671
|
-
│ ├── core.ts # Barrel — re-
|
|
672
|
-
│ ├── framework-base.ts # parseRichSegments() — base
|
|
673
|
-
│ ├── sanitize.ts #
|
|
674
|
-
│ ├── interpolation.ts #
|
|
675
|
-
│ ├── store.ts #
|
|
676
|
-
│ ├── translations.ts # getTranslations
|
|
677
|
-
│ ├── routing.ts # path(), switchLocalePath() —
|
|
678
|
-
│ ├── middleware.ts # createIntlMiddleware()
|
|
679
|
-
│ ├── index.ts #
|
|
680
|
-
│ └── types/
|
|
681
|
-
│ └── index.ts #
|
|
682
|
-
├── dist/ #
|
|
683
|
-
│ ├── *.js # JavaScript
|
|
684
|
-
│ └── *.d.ts #
|
|
685
|
-
├── package.json
|
|
686
|
-
└── tsconfig.json
|
|
687
|
-
```
|
|
688
|
-
|
|
689
|
-
## 🔧
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
- `module: "Node16"`
|
|
694
|
-
- `declaration: true`
|
|
695
|
-
- Imports
|
|
696
|
-
|
|
697
|
-
## 📝
|
|
698
|
-
|
|
699
|
-
1. **
|
|
700
|
-
2. **
|
|
701
|
-
3.
|
|
1
|
+
# astro-intl
|
|
2
|
+
|
|
3
|
+
Simple and type-safe internationalization system for Astro.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 🔒 **Type-safe**: Autocompletion and validation of translation keys with TypeScript
|
|
8
|
+
- 🎯 **Simple API**: Inspired by next-intl, easy to use
|
|
9
|
+
- ⚛️ **React support**: Dedicated adapter with `t.rich()` for rich text with React components. Import from `astro-intl/react`
|
|
10
|
+
- 🧡 **Svelte support**: Dedicated adapter with `t.rich()` that returns segments and `RichText` component. Import from `astro-intl/svelte`
|
|
11
|
+
- 🎨 **Markup in translations**: Insert HTML in strings with `t.markup()`
|
|
12
|
+
- 📁 **Namespaces**: Organize translations by sections
|
|
13
|
+
- 🌐 **Automatic locale detection**: Extracts the language from the URL
|
|
14
|
+
- 🛡️ **Concurrency-safe**: Uses `AsyncLocalStorage` in SSR to isolate concurrent requests
|
|
15
|
+
- 🌍 **Multi-runtime**: Compatible with Node.js, Cloudflare Workers and Deno
|
|
16
|
+
- ⚙️ **Configurable default locale**: Define your default locale from options
|
|
17
|
+
- 🗺️ **Localized routing**: Define translated URLs per locale (`/es/sobre-nosotros` instead of `/es/about`)
|
|
18
|
+
- 🔄 **Automatic rewrites**: Middleware rewrites translated URLs to canonical filesystem routes
|
|
19
|
+
- 🔗 **URL generation**: `path()` and `switchLocalePath()` to build and transform localized URLs
|
|
20
|
+
- 📦 **Sub-path imports**: `astro-intl/react`, `astro-intl/svelte`, `astro-intl/routing`, `astro-intl/middleware`
|
|
21
|
+
|
|
22
|
+
## 🔄 Migration from v1 to v2
|
|
23
|
+
|
|
24
|
+
### Breaking changes
|
|
25
|
+
|
|
26
|
+
1. **`getTranslationsReact` is no longer exported from `astro-intl`**. Use `getTranslations` from `astro-intl/react`:
|
|
27
|
+
|
|
28
|
+
```diff
|
|
29
|
+
- import { getTranslationsReact } from "astro-intl";
|
|
30
|
+
+ import { getTranslations } from "astro-intl/react";
|
|
31
|
+
|
|
32
|
+
- const t = getTranslationsReact();
|
|
33
|
+
+ const t = getTranslations();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. **Sub-path imports required for framework adapters**:
|
|
37
|
+
- React: `astro-intl/react`
|
|
38
|
+
- Svelte: `astro-intl/svelte`
|
|
39
|
+
|
|
40
|
+
3. Base Astro functions (`getTranslations`, `setRequestLocale`, `getLocale`, etc.) continue to be exported from `astro-intl` without changes.
|
|
41
|
+
|
|
42
|
+
### New features
|
|
43
|
+
|
|
44
|
+
- **Svelte adapter** with `t.rich()` and `renderRichText()`
|
|
45
|
+
- **`createGetTranslations` factory** in both adapters (React and Svelte) for standalone use without global store
|
|
46
|
+
- **`parseRichSegments()`** shared framework-agnostic base
|
|
47
|
+
|
|
48
|
+
## 📦 Installation
|
|
49
|
+
|
|
50
|
+
### Automatic installation (Recommended)
|
|
51
|
+
|
|
52
|
+
Use the Astro CLI to install and configure automatically:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx astro add astro-intl
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This command:
|
|
59
|
+
|
|
60
|
+
- ✅ Installs the package
|
|
61
|
+
- ✅ Adds the integration to your `astro.config.mjs`
|
|
62
|
+
- ✅ Configures necessary dependencies
|
|
63
|
+
|
|
64
|
+
### Manual installation
|
|
65
|
+
|
|
66
|
+
If you prefer to install manually:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm install astro-intl
|
|
70
|
+
# o
|
|
71
|
+
pnpm add astro-intl
|
|
72
|
+
# o
|
|
73
|
+
yarn add astro-intl
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then add the integration in your `astro.config.mjs`:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import { defineConfig } from "astro/config";
|
|
80
|
+
import astroIntl from "astro-intl";
|
|
81
|
+
|
|
82
|
+
export default defineConfig({
|
|
83
|
+
integrations: [
|
|
84
|
+
astroIntl({
|
|
85
|
+
defaultLocale: "en", // optional, defaults to "en"
|
|
86
|
+
}),
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 🎯 Usage
|
|
92
|
+
|
|
93
|
+
### Translation file structure
|
|
94
|
+
|
|
95
|
+
First, create your translation files:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
// src/i18n/es.json
|
|
99
|
+
{
|
|
100
|
+
"welcome": "Bienvenido",
|
|
101
|
+
"nav": {
|
|
102
|
+
"home": "Inicio",
|
|
103
|
+
"about": "Acerca de"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/i18n/en.json
|
|
108
|
+
{
|
|
109
|
+
"welcome": "Welcome",
|
|
110
|
+
"nav": {
|
|
111
|
+
"home": "Home",
|
|
112
|
+
"about": "About"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/i18n/index.ts
|
|
117
|
+
import es from './es.json';
|
|
118
|
+
import en from './en.json';
|
|
119
|
+
|
|
120
|
+
export const ui = { es, en };
|
|
121
|
+
export type Messages = typeof es;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### In Astro components
|
|
125
|
+
|
|
126
|
+
```astro
|
|
127
|
+
---
|
|
128
|
+
import { setRequestLocale, getTranslations } from 'astro-intl';
|
|
129
|
+
import { ui } from '../i18n';
|
|
130
|
+
|
|
131
|
+
// Configure the locale for this request
|
|
132
|
+
await setRequestLocale(Astro.url, async (locale) => ({
|
|
133
|
+
locale,
|
|
134
|
+
messages: ui[locale as keyof typeof ui]
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
// Get translation function
|
|
138
|
+
const t = getTranslations();
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
<h1>{t('welcome')}</h1>
|
|
142
|
+
<nav>
|
|
143
|
+
<a href="/">{t('nav.home')}</a>
|
|
144
|
+
<a href="/about">{t('nav.about')}</a>
|
|
145
|
+
</nav>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Variable interpolation
|
|
149
|
+
|
|
150
|
+
Use `{varName}` in your translation strings and pass an object of values:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
// src/i18n/en.json
|
|
154
|
+
{
|
|
155
|
+
"greeting": "Hello, {name}!",
|
|
156
|
+
"info": "You have {count} items"
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```astro
|
|
161
|
+
---
|
|
162
|
+
const t = getTranslations();
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
<p>{t('greeting', { name: 'John' })}</p> <!-- "Hello, John!" -->
|
|
166
|
+
<p>{t('info', { count: 5 })}</p> <!-- "You have 5 items" -->
|
|
167
|
+
<p>{t('greeting')}</p> <!-- "Hello, {name}!" (without values, placeholder remains) -->
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Accepted values are `string | number | boolean`. If a variable is not passed or is `null`/`undefined`, the placeholder `{varName}` remains unchanged.
|
|
171
|
+
|
|
172
|
+
### Translations with markup (HTML in strings)
|
|
173
|
+
|
|
174
|
+
```astro
|
|
175
|
+
---
|
|
176
|
+
// src/i18n/es.json
|
|
177
|
+
// { "terms": "Acepto los <link>términos y condiciones</link>" }
|
|
178
|
+
|
|
179
|
+
const t = getTranslations();
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
<p set:html={t.markup('terms', {
|
|
183
|
+
link: (chunks) => `<a href="/terms">${chunks}</a>`
|
|
184
|
+
})} />
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Markup with interpolation
|
|
188
|
+
|
|
189
|
+
You can combine variables and tag interpolation using the `{ values, tags }` format:
|
|
190
|
+
|
|
191
|
+
```astro
|
|
192
|
+
---
|
|
193
|
+
// src/i18n/en.json
|
|
194
|
+
// { "welcome": "Hello {name}, click <link>here</link> to continue" }
|
|
195
|
+
|
|
196
|
+
const t = getTranslations();
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
<p set:html={t.markup('welcome', {
|
|
200
|
+
values: { name: 'John' },
|
|
201
|
+
tags: {
|
|
202
|
+
link: (chunks) => `<a href="/home">${chunks}</a>`
|
|
203
|
+
}
|
|
204
|
+
})} />
|
|
205
|
+
<!-- "Hello John, click <a href="/home">here</a> to continue" -->
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### In React components
|
|
209
|
+
|
|
210
|
+
> **v2**: Import from `astro-intl/react` instead of `astro-intl`.
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import { getTranslations } from "astro-intl/react";
|
|
214
|
+
|
|
215
|
+
export function MyComponent() {
|
|
216
|
+
const t = getTranslations();
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<div>
|
|
220
|
+
<h1>{t("welcome")}</h1>
|
|
221
|
+
<nav>
|
|
222
|
+
<a href="/">{t("nav.home")}</a>
|
|
223
|
+
</nav>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Standalone factory (without store)
|
|
230
|
+
|
|
231
|
+
If you prefer to pass messages directly without depending on the global store:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { createGetTranslations } from "astro-intl/react";
|
|
235
|
+
import { ui } from "../i18n";
|
|
236
|
+
|
|
237
|
+
const getT = createGetTranslations(ui, "en");
|
|
238
|
+
|
|
239
|
+
export function MyComponent({ lang }: { lang: string }) {
|
|
240
|
+
const t = getT(lang, "nav");
|
|
241
|
+
return <a href="/">{t("home")}</a>;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Translations with React components (rich text)
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import { getTranslations } from "astro-intl/react";
|
|
249
|
+
|
|
250
|
+
export function MyComponent() {
|
|
251
|
+
const t = getTranslations();
|
|
252
|
+
|
|
253
|
+
// src/i18n/es.json
|
|
254
|
+
// { "terms": "Acepto los <link>términos y condiciones</link>" }
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<p>
|
|
258
|
+
{t.rich("terms", {
|
|
259
|
+
link: (chunks) => <a href="/terms">{chunks}</a>,
|
|
260
|
+
})}
|
|
261
|
+
</p>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### In Svelte components
|
|
267
|
+
|
|
268
|
+
> **v2**: New adapter. Import from `astro-intl/svelte`.
|
|
269
|
+
|
|
270
|
+
```svelte
|
|
271
|
+
<script>
|
|
272
|
+
import { getTranslations } from 'astro-intl/svelte';
|
|
273
|
+
|
|
274
|
+
const t = getTranslations();
|
|
275
|
+
</script>
|
|
276
|
+
|
|
277
|
+
<h1>{t('welcome')}</h1>
|
|
278
|
+
<nav>
|
|
279
|
+
<a href="/">{t('nav.home')}</a>
|
|
280
|
+
</nav>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
#### Rich text in Svelte
|
|
284
|
+
|
|
285
|
+
`t.rich()` returns an array of `RichSegment[]` that you can render with `renderRichText()`:
|
|
286
|
+
|
|
287
|
+
```svelte
|
|
288
|
+
<script>
|
|
289
|
+
import { getTranslations, renderRichText } from 'astro-intl/svelte';
|
|
290
|
+
|
|
291
|
+
// { "terms": "Acepto los <link>términos y condiciones</link>" }
|
|
292
|
+
const t = getTranslations();
|
|
293
|
+
const segments = t.rich('terms', ['link']);
|
|
294
|
+
|
|
295
|
+
const html = renderRichText(segments, {
|
|
296
|
+
tags: { link: 'a' }, // renders as <a>...</a>
|
|
297
|
+
});
|
|
298
|
+
</script>
|
|
299
|
+
|
|
300
|
+
<p>{@html html}</p>
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
You can also use custom functions with `components`:
|
|
304
|
+
|
|
305
|
+
```svelte
|
|
306
|
+
<script>
|
|
307
|
+
import { getTranslations, renderRichText } from 'astro-intl/svelte';
|
|
308
|
+
|
|
309
|
+
const t = getTranslations();
|
|
310
|
+
const segments = t.rich('terms', ['link']);
|
|
311
|
+
|
|
312
|
+
const html = renderRichText(segments, {
|
|
313
|
+
components: {
|
|
314
|
+
link: (chunks) => `<a href="/terms" class="underline">${chunks}</a>`,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
<p>{@html html}</p>
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Standalone factory in Svelte (without store)
|
|
323
|
+
|
|
324
|
+
```svelte
|
|
325
|
+
<script>
|
|
326
|
+
import { createGetTranslations } from 'astro-intl/svelte';
|
|
327
|
+
import { ui } from '../i18n';
|
|
328
|
+
|
|
329
|
+
const getT = createGetTranslations(ui, 'en');
|
|
330
|
+
|
|
331
|
+
export let lang;
|
|
332
|
+
const t = getT(lang, 'nav');
|
|
333
|
+
</script>
|
|
334
|
+
|
|
335
|
+
<a href="/">{t('home')}</a>
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Type-safety with TypeScript
|
|
339
|
+
|
|
340
|
+
```astro
|
|
341
|
+
---
|
|
342
|
+
import { setRequestLocale, getTranslations } from 'astro-intl';
|
|
343
|
+
import { ui, type Messages } from '../i18n';
|
|
344
|
+
|
|
345
|
+
await setRequestLocale(Astro.url, async (locale) => ({
|
|
346
|
+
locale,
|
|
347
|
+
messages: ui[locale as keyof typeof ui]
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
// Strong typing with autocompletion
|
|
351
|
+
const t = getTranslations<Messages>();
|
|
352
|
+
|
|
353
|
+
// TypeScript will autocomplete valid paths:
|
|
354
|
+
// t('nav.home') ✓
|
|
355
|
+
// t('nav.invalid') ✗ TypeScript error
|
|
356
|
+
---
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Using namespaces
|
|
360
|
+
|
|
361
|
+
```astro
|
|
362
|
+
---
|
|
363
|
+
// Get only a specific namespace
|
|
364
|
+
const t = getTranslations<Messages>('nav');
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
<nav>
|
|
368
|
+
<a href="/">{t('home')}</a> <!-- Instead of t('nav.home') -->
|
|
369
|
+
<a href="/about">{t('about')}</a>
|
|
370
|
+
</nav>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## 🗺️ Localized Routing
|
|
374
|
+
|
|
375
|
+
### Define translated routes
|
|
376
|
+
|
|
377
|
+
Create a route map with translated URLs per locale:
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
// src/i18n/routing.ts
|
|
381
|
+
export const routing = {
|
|
382
|
+
locales: ["en", "es"],
|
|
383
|
+
defaultLocale: "en",
|
|
384
|
+
routes: {
|
|
385
|
+
home: { en: "/", es: "/" },
|
|
386
|
+
about: { en: "/about", es: "/sobre-nosotros" },
|
|
387
|
+
blog: { en: "/blog/[slug]", es: "/blog/[slug]" },
|
|
388
|
+
shop: { en: "/shop/[category]/[id]", es: "/tienda/[category]/[id]" },
|
|
389
|
+
},
|
|
390
|
+
} as const;
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### With Middleware (recommended)
|
|
394
|
+
|
|
395
|
+
Pass the routes to the middleware. It automatically rewrites translated URLs to canonical filesystem routes:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
// src/middleware.ts
|
|
399
|
+
import "@/i18n/request";
|
|
400
|
+
import { createIntlMiddleware } from "astro-intl/middleware";
|
|
401
|
+
import { routing } from "@/i18n/routing";
|
|
402
|
+
|
|
403
|
+
export const onRequest = createIntlMiddleware(routing);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
When a user visits `/es/sobre-nosotros`, the middleware rewrites it to `/es/about` — which maps to your `[lang]/about.astro` file. No duplicate pages.
|
|
407
|
+
|
|
408
|
+
### Without Middleware
|
|
409
|
+
|
|
410
|
+
Configure routes via integration options:
|
|
411
|
+
|
|
412
|
+
```js
|
|
413
|
+
// astro.config.mjs
|
|
414
|
+
import { defineConfig } from "astro/config";
|
|
415
|
+
import astroIntl from "astro-intl";
|
|
416
|
+
|
|
417
|
+
export default defineConfig({
|
|
418
|
+
integrations: [
|
|
419
|
+
astroIntl({
|
|
420
|
+
defaultLocale: "en",
|
|
421
|
+
locales: ["en", "es"],
|
|
422
|
+
routes: {
|
|
423
|
+
about: { en: "/about", es: "/sobre-nosotros" },
|
|
424
|
+
},
|
|
425
|
+
}),
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Without middleware there are no automatic rewrites. Create lightweight wrappers for each translated route:
|
|
431
|
+
|
|
432
|
+
```astro
|
|
433
|
+
---
|
|
434
|
+
// src/pages/[lang]/sobre-nosotros.astro
|
|
435
|
+
export { default } from "./about.astro";
|
|
436
|
+
export { getStaticPaths } from "./about.astro";
|
|
437
|
+
---
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Generate URLs with `path()`
|
|
441
|
+
|
|
442
|
+
```astro
|
|
443
|
+
---
|
|
444
|
+
import { path } from "astro-intl/routing";
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
<a href={path("about")}>About</a>
|
|
448
|
+
<!-- locale "en" → /en/about -->
|
|
449
|
+
<!-- locale "es" → /es/sobre-nosotros -->
|
|
450
|
+
|
|
451
|
+
<a href={path("shop", { locale: "es", params: { category: "ropa", id: "42" } })}>
|
|
452
|
+
View product
|
|
453
|
+
</a>
|
|
454
|
+
<!-- → /es/tienda/ropa/42 -->
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Switch locale with `switchLocalePath()`
|
|
458
|
+
|
|
459
|
+
```astro
|
|
460
|
+
---
|
|
461
|
+
import { switchLocalePath } from "astro-intl/routing";
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
<a href={switchLocalePath(Astro.url.pathname, "en")}>English</a>
|
|
465
|
+
<a href={switchLocalePath(Astro.url.pathname, "es")}>Español</a>
|
|
466
|
+
<!-- On /en/about → /es/sobre-nosotros -->
|
|
467
|
+
<!-- On /es/tienda/ropa/42 → /en/shop/ropa/42 -->
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## 📚 API Reference
|
|
471
|
+
|
|
472
|
+
### `astroIntl(options?)`
|
|
473
|
+
|
|
474
|
+
Configures the integration in `astro.config.mjs`.
|
|
475
|
+
|
|
476
|
+
**Options:**
|
|
477
|
+
|
|
478
|
+
- `defaultLocale?: string` - Default locale when the URL has no language prefix (default: `"en"`)
|
|
479
|
+
- `enabled?: boolean` - Enable/disable the integration (default: `true`)
|
|
480
|
+
- `messages?: MessagesConfig` - Static or dynamic translation messages
|
|
481
|
+
- `locales?: string[]` - List of supported locales
|
|
482
|
+
- `routes?: RoutesMap` - Map of translated routes per locale
|
|
483
|
+
|
|
484
|
+
### `setRequestLocale(url, getConfig?)`
|
|
485
|
+
|
|
486
|
+
Configures the locale for the current request.
|
|
487
|
+
|
|
488
|
+
**Parameters:**
|
|
489
|
+
|
|
490
|
+
- `url: URL` - The Astro URL object (`Astro.url`)
|
|
491
|
+
- `getConfig?: (locale: string) => RequestConfig | Promise<RequestConfig>` - Function that returns the configuration
|
|
492
|
+
|
|
493
|
+
**Example:**
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
await setRequestLocale(Astro.url, async (locale) => ({
|
|
497
|
+
locale,
|
|
498
|
+
messages: ui[locale],
|
|
499
|
+
}));
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### `runWithLocale(url, fn, getConfig?)`
|
|
503
|
+
|
|
504
|
+
Executes a function within a request-isolated context. Uses `AsyncLocalStorage` when available (Node.js) to avoid race conditions in SSR with concurrent requests.
|
|
505
|
+
|
|
506
|
+
**Parameters:**
|
|
507
|
+
|
|
508
|
+
- `url: URL` - The Astro URL object (`Astro.url`)
|
|
509
|
+
- `fn: () => R | Promise<R>` - Function to execute within the isolated context
|
|
510
|
+
- `getConfig?: GetRequestConfigFn` - Optional configuration function
|
|
511
|
+
|
|
512
|
+
**Example in middleware:**
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
// src/middleware.ts
|
|
516
|
+
import { runWithLocale } from "astro-intl";
|
|
517
|
+
|
|
518
|
+
export const onRequest = async (context, next) => {
|
|
519
|
+
return runWithLocale(
|
|
520
|
+
context.url,
|
|
521
|
+
() => next(),
|
|
522
|
+
(locale) => ({
|
|
523
|
+
locale,
|
|
524
|
+
messages: ui[locale],
|
|
525
|
+
})
|
|
526
|
+
);
|
|
527
|
+
};
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### `getTranslations<T>(namespace?)`
|
|
531
|
+
|
|
532
|
+
Gets the translation function for Astro components.
|
|
533
|
+
|
|
534
|
+
**Parameters:**
|
|
535
|
+
|
|
536
|
+
- `namespace?: string` - Optional namespace to get only a subset of translations
|
|
537
|
+
|
|
538
|
+
**Returns:** Function `t(key, values?)` with method `t.markup(key, tags | { values?, tags })`
|
|
539
|
+
|
|
540
|
+
#### `t(key, values?)`
|
|
541
|
+
|
|
542
|
+
- `key: string` - Translation key (supports dot notation)
|
|
543
|
+
- `values?: Record<string, Primitive>` - Values for `{varName}` interpolation (optional)
|
|
544
|
+
|
|
545
|
+
#### `t.markup(key, options)`
|
|
546
|
+
|
|
547
|
+
- `key: string` - Translation key
|
|
548
|
+
- `options` - Can be:
|
|
549
|
+
- `Record<string, (chunks: string) => string>` - Tags only (backward compatible)
|
|
550
|
+
- `{ values?: Record<string, Primitive>, tags: Record<string, (chunks: string) => string> }` - Tags with interpolation
|
|
551
|
+
|
|
552
|
+
### `getTranslations()` — `astro-intl/react`
|
|
553
|
+
|
|
554
|
+
Gets the translation function for React components (uses the global store).
|
|
555
|
+
|
|
556
|
+
**Returns:** Function `t(key)` with method `t.rich(key, tags)` that returns `ReactNode[]`
|
|
557
|
+
|
|
558
|
+
### `createGetTranslations(ui, defaultLocale)` — `astro-intl/react`
|
|
559
|
+
|
|
560
|
+
Standalone factory that doesn't depend on the global store. Useful for passing messages directly.
|
|
561
|
+
|
|
562
|
+
**Parameters:**
|
|
563
|
+
|
|
564
|
+
- `ui: Record<string, Record<string, unknown>>` - Object with all messages per locale
|
|
565
|
+
- `defaultLocale: string` - Default locale
|
|
566
|
+
|
|
567
|
+
**Returns:** `(lang, namespace) => t` — function that returns `t(key)` with `t.rich(key, tags)`
|
|
568
|
+
|
|
569
|
+
### `getTranslations()` — `astro-intl/svelte`
|
|
570
|
+
|
|
571
|
+
Gets the translation function for Svelte components (uses the global store).
|
|
572
|
+
|
|
573
|
+
**Returns:** Function `t(key)` with method `t.rich(key, tagNames?)` that returns `RichSegment[]`
|
|
574
|
+
|
|
575
|
+
### `createGetTranslations(ui, defaultLocale)` — `astro-intl/svelte`
|
|
576
|
+
|
|
577
|
+
Standalone factory for Svelte. Same signature as React but `t.rich()` returns `RichSegment[]`.
|
|
578
|
+
|
|
579
|
+
### `renderRichText(segments, options?)` — `astro-intl/svelte`
|
|
580
|
+
|
|
581
|
+
Resolves an array of `RichSegment[]` into an HTML string.
|
|
582
|
+
|
|
583
|
+
**Parameters:**
|
|
584
|
+
|
|
585
|
+
- `segments: RichSegment[]` - Segments returned by `t.rich()`
|
|
586
|
+
- `options.tags?: Record<string, string>` - Maps tag name to HTML element (e.g., `{ link: 'a' }`)
|
|
587
|
+
- `options.components?: Record<string, (chunks: string) => string>` - Custom functions per tag
|
|
588
|
+
|
|
589
|
+
**Returns:** `string` - HTML ready to render with `{@html}`
|
|
590
|
+
|
|
591
|
+
### `getLocale()`
|
|
592
|
+
|
|
593
|
+
Gets the currently configured locale.
|
|
594
|
+
|
|
595
|
+
**Returns:** `string` - The locale code (e.g., `'es'`, `'en'`)
|
|
596
|
+
|
|
597
|
+
### `createIntlMiddleware(options)`
|
|
598
|
+
|
|
599
|
+
Creates an Astro middleware that automatically calls `setRequestLocale` on each request. Import from `astro-intl/middleware`.
|
|
600
|
+
|
|
601
|
+
**Options:**
|
|
602
|
+
|
|
603
|
+
- `locales: string[]` - List of supported locales
|
|
604
|
+
- `defaultLocale?: string` - Default locale (default: `"en"`)
|
|
605
|
+
- `routes?: RoutesMap` - Map of translated routes. When provided, the middleware rewrites translated URLs to their canonical filesystem routes
|
|
606
|
+
|
|
607
|
+
### `path(routeKey, options?)`
|
|
608
|
+
|
|
609
|
+
Generates a localized URL for a named route. Import from `astro-intl/routing`.
|
|
610
|
+
|
|
611
|
+
**Parameters:**
|
|
612
|
+
|
|
613
|
+
- `routeKey: string` - Route name (key from the `routes` map)
|
|
614
|
+
- `options?.locale` - Target locale (default: current locale)
|
|
615
|
+
- `options?.params` - `Record<string, string>` to substitute `[param]` in the template
|
|
616
|
+
- `options?.encode` - Encode params with `encodeURIComponent` (default: `true`)
|
|
617
|
+
|
|
618
|
+
**Returns:** `string` - Localized URL (e.g., `"/es/sobre-nosotros"`)
|
|
619
|
+
|
|
620
|
+
### `switchLocalePath(currentPath, nextLocale)`
|
|
621
|
+
|
|
622
|
+
Converts the current URL to its equivalent in another locale. Import from `astro-intl/routing`.
|
|
623
|
+
|
|
624
|
+
**Parameters:**
|
|
625
|
+
|
|
626
|
+
- `currentPath: string | URL` - Current path (pathname, URL string or URL object)
|
|
627
|
+
- `nextLocale: string` - Target locale
|
|
628
|
+
|
|
629
|
+
**Returns:** `string` - Equivalent URL in the new locale. Preserves query strings and hashes. If no template matches, falls back to swapping the locale prefix.
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## 🚀 Development (for contributors)
|
|
634
|
+
|
|
635
|
+
### Build the package
|
|
636
|
+
|
|
637
|
+
Before using the package in the playground or any project, you must build it:
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
npm run build
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
This will generate the JavaScript files and type declarations (`.d.ts`) in the `dist/` folder.
|
|
644
|
+
|
|
645
|
+
### Development mode
|
|
646
|
+
|
|
647
|
+
To automatically compile when you make changes:
|
|
648
|
+
|
|
649
|
+
```bash
|
|
650
|
+
npm run dev
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### After building
|
|
654
|
+
|
|
655
|
+
If you're working in a monorepo with pnpm workspaces, after building run:
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
pnpm install
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
This will update the symbolic links and types will be available in projects that use the package.
|
|
662
|
+
|
|
663
|
+
## 📦 Package Structure
|
|
664
|
+
|
|
665
|
+
```text
|
|
666
|
+
packages/integration/
|
|
667
|
+
├── src/
|
|
668
|
+
│ ├── adapters/
|
|
669
|
+
│ │ ├── react.ts # React Adapter — getTranslations, createGetTranslations, t.rich() → ReactNode[]
|
|
670
|
+
│ │ └── svelte.ts # Svelte Adapter — getTranslations, createGetTranslations, t.rich() → RichSegment[], renderRichText()
|
|
671
|
+
│ ├── core.ts # Barrel — re-exports everything from modules
|
|
672
|
+
│ ├── framework-base.ts # parseRichSegments() — framework-agnostic base shared by React and Svelte
|
|
673
|
+
│ ├── sanitize.ts # Locale validation, HTML sanitization, regex escape
|
|
674
|
+
│ ├── interpolation.ts # {variable} interpolation, nested value access
|
|
675
|
+
│ ├── store.ts # Per-request state (AsyncLocalStorage + fallback)
|
|
676
|
+
│ ├── translations.ts # getTranslations for Astro components
|
|
677
|
+
│ ├── routing.ts # path(), switchLocalePath() — localized URL generation
|
|
678
|
+
│ ├── middleware.ts # createIntlMiddleware() with translated route rewrites
|
|
679
|
+
│ ├── index.ts # Public entry point + Astro integration
|
|
680
|
+
│ └── types/
|
|
681
|
+
│ └── index.ts # TypeScript types (includes RoutesMap)
|
|
682
|
+
├── dist/ # Compiled files (generated)
|
|
683
|
+
│ ├── *.js # Compiled JavaScript
|
|
684
|
+
│ └── *.d.ts # Type declarations
|
|
685
|
+
├── package.json
|
|
686
|
+
└── tsconfig.json
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
## 🔧 TypeScript Configuration
|
|
690
|
+
|
|
691
|
+
The package uses:
|
|
692
|
+
|
|
693
|
+
- `module: "Node16"` for full ESM support
|
|
694
|
+
- `declaration: true` to generate `.d.ts` files
|
|
695
|
+
- Imports with `.js` extension for ESM compatibility
|
|
696
|
+
|
|
697
|
+
## 📝 Important Notes
|
|
698
|
+
|
|
699
|
+
1. **Always build before testing**: Changes in `src/` are not reflected until you run `npm run build`
|
|
700
|
+
2. **dist/ files in .gitignore**: Compiled files are not uploaded to git, they are generated on each installation
|
|
701
|
+
3. **.js extensions in imports**: Although the source code is TypeScript, imports must use `.js` for Node16/ESM compatibility
|