astro-intl 1.1.0 → 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 CHANGED
@@ -6,15 +6,46 @@ Sistema de internacionalización simple y type-safe para Astro, inspirado en nex
6
6
 
7
7
  - 🔒 **Type-safe**: Autocompletado y validación de claves de traducción con TypeScript
8
8
  - 🎯 **API simple**: Inspirada en next-intl, fácil de usar
9
- - ⚛️ **Soporte React**: Funciones específicas para componentes React con `t.rich()`
9
+ - ⚛️ **Soporte React**: Adapter dedicado con `t.rich()` para rich text con componentes React. Importa desde `astro-intl/react`
10
+ - 🧡 **Soporte Svelte**: Adapter dedicado con `t.rich()` que retorna segmentos y componente `RichText`. Importa desde `astro-intl/svelte`
10
11
  - 🎨 **Markup en traducciones**: Inserta HTML en strings con `t.markup()`
11
12
  - 📁 **Namespaces**: Organiza traducciones por secciones
12
13
  - 🌐 **Detección automática de locale**: Extrae el idioma desde la URL
13
14
  - 🛡️ **Concurrency-safe**: Usa `AsyncLocalStorage` en SSR para aislar requests concurrentes
14
15
  - 🌍 **Multi-runtime**: Compatible con Node.js, Cloudflare Workers y Deno
15
16
  - ⚙️ **Default locale configurable**: Define tu locale por defecto desde las opciones
17
+ - 🗺️ **Routing localizado**: Define URLs traducidas por locale (`/es/sobre-nosotros` en vez de `/es/about`)
18
+ - 🔄 **Rewrites automáticos**: El middleware reescribe URLs traducidas a rutas canónicas del filesystem
19
+ - 🔗 **Generación de URLs**: `path()` y `switchLocalePath()` para construir y transformar URLs localizadas
20
+ - 📦 **Sub-path imports**: `astro-intl/react`, `astro-intl/svelte`, `astro-intl/routing`, `astro-intl/middleware`
16
21
 
17
- ## 📦 Instalación
22
+ ## Migración desde v1 a v2
23
+
24
+ ### Breaking changes
25
+
26
+ 1. **`getTranslationsReact` ya no se exporta desde `astro-intl`**. Usa `getTranslations` desde `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 obligatorios para adapters de framework**:
37
+ - React: `astro-intl/react`
38
+ - Svelte: `astro-intl/svelte`
39
+
40
+ 3. Las funciones base de Astro (`getTranslations`, `setRequestLocale`, `getLocale`, etc.) siguen exportándose desde `astro-intl` sin cambios.
41
+
42
+ ### Nuevas funcionalidades
43
+
44
+ - **Adapter Svelte** con `t.rich()` y `renderRichText()`
45
+ - **`createGetTranslations` factory** en ambos adapters (React y Svelte) para uso standalone sin store global
46
+ - **`parseRichSegments()`** base agnóstica compartida
47
+
48
+ ## �📦 Instalación
18
49
 
19
50
  ### Instalación automática (Recomendado)
20
51
 
@@ -176,11 +207,13 @@ const t = getTranslations();
176
207
 
177
208
  ### En componentes React
178
209
 
210
+ > **v2**: Importa desde `astro-intl/react` en lugar de `astro-intl`.
211
+
179
212
  ```tsx
180
- import { getTranslationsReact } from "astro-intl";
213
+ import { getTranslations } from "astro-intl/react";
181
214
 
182
215
  export function MyComponent() {
183
- const t = getTranslationsReact();
216
+ const t = getTranslations();
184
217
 
185
218
  return (
186
219
  <div>
@@ -193,13 +226,29 @@ export function MyComponent() {
193
226
  }
194
227
  ```
195
228
 
229
+ #### Factory standalone (sin store)
230
+
231
+ Si prefieres pasar los mensajes directamente sin depender del store global:
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
+
196
245
  ### Traducciones con componentes React (rich text)
197
246
 
198
247
  ```tsx
199
- import { getTranslationsReact } from "astro-intl";
248
+ import { getTranslations } from "astro-intl/react";
200
249
 
201
250
  export function MyComponent() {
202
- const t = getTranslationsReact();
251
+ const t = getTranslations();
203
252
 
204
253
  // src/i18n/es.json
205
254
  // { "terms": "Acepto los <link>términos y condiciones</link>" }
@@ -214,6 +263,78 @@ export function MyComponent() {
214
263
  }
215
264
  ```
216
265
 
266
+ ### En componentes Svelte
267
+
268
+ > **v2**: Nuevo adapter. Importa desde `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 en Svelte
284
+
285
+ `t.rich()` retorna un array de `RichSegment[]` que puedes renderizar con `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' }, // renderiza como <a>...</a>
297
+ });
298
+ </script>
299
+
300
+ <p>{@html html}</p>
301
+ ```
302
+
303
+ También puedes usar funciones personalizadas con `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
+ #### Factory standalone en Svelte (sin 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
+
217
338
  ### Type-safety con TypeScript
218
339
 
219
340
  ```astro
@@ -249,7 +370,104 @@ const t = getTranslations<Messages>('nav');
249
370
  </nav>
250
371
  ```
251
372
 
252
- ## 📚 API Reference
373
+ ## �️ Routing Localizado
374
+
375
+ ### Definir rutas traducidas
376
+
377
+ Crea un mapa de rutas con URLs traducidas por 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
+ ### Con Middleware (recomendado)
394
+
395
+ Pasa las rutas al middleware. Este reescribe automáticamente URLs traducidas a las rutas canónicas del filesystem:
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
+ Cuando un usuario visita `/es/sobre-nosotros`, el middleware lo reescribe a `/es/about` — que mapea a tu archivo `[lang]/about.astro`. Sin páginas duplicadas.
407
+
408
+ ### Sin Middleware
409
+
410
+ Configura las rutas via las opciones de la integración:
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
+ Sin middleware no hay rewrites automáticos. Crea wrappers ligeros para cada ruta traducida:
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
+ ### Generar URLs con `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
+ Ver producto
453
+ </a>
454
+ <!-- → /es/tienda/ropa/42 -->
455
+ ```
456
+
457
+ ### Cambiar locale con `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
+ <!-- En /en/about → /es/sobre-nosotros -->
467
+ <!-- En /es/tienda/ropa/42 → /en/shop/ropa/42 -->
468
+ ```
469
+
470
+ ## � API Reference
253
471
 
254
472
  ### `astroIntl(options?)`
255
473
 
@@ -260,6 +478,8 @@ Configura la integración en `astro.config.mjs`.
260
478
  - `defaultLocale?: string` - Locale por defecto cuando la URL no tiene prefijo de idioma (default: `"en"`)
261
479
  - `enabled?: boolean` - Habilitar/deshabilitar la integración (default: `true`)
262
480
  - `messages?: MessagesConfig` - Mensajes de traducción estáticos o dinámicos
481
+ - `locales?: string[]` - Lista de locales soportados
482
+ - `routes?: RoutesMap` - Mapa de rutas traducidas por locale
263
483
 
264
484
  ### `setRequestLocale(url, getConfig?)`
265
485
 
@@ -329,15 +549,44 @@ Obtiene la función de traducción para componentes Astro.
329
549
  - `Record<string, (chunks: string) => string>` - Solo tags (backward compatible)
330
550
  - `{ values?: Record<string, Primitive>, tags: Record<string, (chunks: string) => string> }` - Tags con interpolación
331
551
 
332
- ### `getTranslationsReact<T>(namespace?)`
552
+ ### `getTranslations()` — `astro-intl/react`
553
+
554
+ Obtiene la función de traducción para componentes React (usa el store global).
555
+
556
+ **Retorna:** Función `t(key)` con método `t.rich(key, tags)` que retorna `ReactNode[]`
557
+
558
+ ### `createGetTranslations(ui, defaultLocale)` — `astro-intl/react`
559
+
560
+ Factory standalone que no depende del store global. Útil para pasar mensajes directamente.
561
+
562
+ **Parámetros:**
563
+
564
+ - `ui: Record<string, Record<string, unknown>>` - Objeto con todos los mensajes por locale
565
+ - `defaultLocale: string` - Locale por defecto
333
566
 
334
- Obtiene la función de traducción para componentes React.
567
+ **Retorna:** `(lang, namespace) => t` — función que retorna `t(key)` con `t.rich(key, tags)`
568
+
569
+ ### `getTranslations()` — `astro-intl/svelte`
570
+
571
+ Obtiene la función de traducción para componentes Svelte (usa el store global).
572
+
573
+ **Retorna:** Función `t(key)` con método `t.rich(key, tagNames?)` que retorna `RichSegment[]`
574
+
575
+ ### `createGetTranslations(ui, defaultLocale)` — `astro-intl/svelte`
576
+
577
+ Factory standalone para Svelte. Misma firma que el de React pero `t.rich()` retorna `RichSegment[]`.
578
+
579
+ ### `renderRichText(segments, options?)` — `astro-intl/svelte`
580
+
581
+ Resuelve un array de `RichSegment[]` en un string HTML.
335
582
 
336
583
  **Parámetros:**
337
584
 
338
- - `namespace?: string` - Namespace opcional
585
+ - `segments: RichSegment[]` - Segmentos retornados por `t.rich()`
586
+ - `options.tags?: Record<string, string>` - Mapea nombre de tag a elemento HTML (ej: `{ link: 'a' }`)
587
+ - `options.components?: Record<string, (chunks: string) => string>` - Funciones personalizadas por tag
339
588
 
340
- **Retorna:** Función `t(key)` con método `t.rich(key, tags)`
589
+ **Retorna:** `string` - HTML listo para renderizar con `{@html}`
341
590
 
342
591
  ### `getLocale()`
343
592
 
@@ -345,6 +594,40 @@ Obtiene el locale actual configurado.
345
594
 
346
595
  **Retorna:** `string` - El código del locale (ej: `'es'`, `'en'`)
347
596
 
597
+ ### `createIntlMiddleware(options)`
598
+
599
+ Crea un middleware de Astro que llama automáticamente a `setRequestLocale` en cada request. Importar desde `astro-intl/middleware`.
600
+
601
+ **Opciones:**
602
+
603
+ - `locales: string[]` - Lista de locales soportados
604
+ - `defaultLocale?: string` - Locale por defecto (default: `"en"`)
605
+ - `routes?: RoutesMap` - Mapa de rutas traducidas. Cuando se proporciona, el middleware reescribe URLs traducidas a sus rutas canónicas del filesystem
606
+
607
+ ### `path(routeKey, options?)`
608
+
609
+ Genera una URL localizada para una ruta nombrada. Importar desde `astro-intl/routing`.
610
+
611
+ **Parámetros:**
612
+
613
+ - `routeKey: string` - Nombre de la ruta (clave del mapa de `routes`)
614
+ - `options?.locale` - Locale destino (default: locale actual)
615
+ - `options?.params` - `Record<string, string>` para sustituir `[param]` en el template
616
+ - `options?.encode` - Codificar params con `encodeURIComponent` (default: `true`)
617
+
618
+ **Retorna:** `string` - URL localizada (ej: `"/es/sobre-nosotros"`)
619
+
620
+ ### `switchLocalePath(currentPath, nextLocale)`
621
+
622
+ Convierte la URL actual a su equivalente en otro locale. Importar desde `astro-intl/routing`.
623
+
624
+ **Parámetros:**
625
+
626
+ - `currentPath: string | URL` - Ruta actual (pathname, URL string o URL object)
627
+ - `nextLocale: string` - Locale destino
628
+
629
+ **Retorna:** `string` - URL equivalente en el nuevo locale. Preserva query strings y hashes. Si no matchea ningún template, hace fallback a intercambiar el prefijo del locale.
630
+
348
631
  ---
349
632
 
350
633
  ## 🚀 Desarrollo (para contribuidores)
@@ -382,15 +665,20 @@ Esto actualizará los enlaces simbólicos y los tipos estarán disponibles en lo
382
665
  ```text
383
666
  packages/integration/
384
667
  ├── src/
668
+ │ ├── adapters/
669
+ │ │ ├── react.ts # Adapter React — getTranslations, createGetTranslations, t.rich() → ReactNode[]
670
+ │ │ └── svelte.ts # Adapter Svelte — getTranslations, createGetTranslations, t.rich() → RichSegment[], renderRichText()
385
671
  │ ├── core.ts # Barrel — re-exporta todo desde los módulos
672
+ │ ├── framework-base.ts # parseRichSegments() — base agnóstica compartida por React y Svelte
386
673
  │ ├── sanitize.ts # Validación de locale, sanitización HTML, escape regex
387
674
  │ ├── interpolation.ts # Interpolación {variables}, acceso a valores anidados
388
675
  │ ├── store.ts # Estado por request (AsyncLocalStorage + fallback)
389
- │ ├── translations.ts # getTranslations y getTranslationsReact
390
- │ ├── react.ts # Factory de t.rich() para React
676
+ │ ├── translations.ts # getTranslations para componentes Astro
677
+ │ ├── routing.ts # path(), switchLocalePath() generación de URLs localizadas
678
+ │ ├── middleware.ts # createIntlMiddleware() con rewrites de rutas traducidas
391
679
  │ ├── index.ts # Entry point público + integración de Astro
392
680
  │ └── types/
393
- │ └── index.ts # Tipos TypeScript
681
+ │ └── index.ts # Tipos TypeScript (incluye RoutesMap)
394
682
  ├── dist/ # Archivos compilados (generados)
395
683
  │ ├── *.js # JavaScript compilado
396
684
  │ └── *.d.ts # Declaraciones de tipos
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseRichSegments } from "../framework-base.js";
3
+ describe("framework-base.ts", () => {
4
+ describe("parseRichSegments", () => {
5
+ it("should return single text segment for plain text", () => {
6
+ const result = parseRichSegments("Hello world", []);
7
+ expect(result).toEqual([{ type: "text", value: "Hello world" }]);
8
+ });
9
+ it("should return empty array for empty string", () => {
10
+ const result = parseRichSegments("", []);
11
+ expect(result).toEqual([]);
12
+ });
13
+ it("should parse a single tag", () => {
14
+ const result = parseRichSegments("Click <link>here</link> to continue", ["link"]);
15
+ expect(result).toEqual([
16
+ { type: "text", value: "Click " },
17
+ { type: "tag", tag: "link", chunks: "here" },
18
+ { type: "text", value: " to continue" },
19
+ ]);
20
+ });
21
+ it("should parse multiple tags", () => {
22
+ const result = parseRichSegments("Text with <bold>bold</bold> and <italic>italic</italic>", ["bold", "italic"]);
23
+ expect(result).toEqual([
24
+ { type: "text", value: "Text with " },
25
+ { type: "tag", tag: "bold", chunks: "bold" },
26
+ { type: "text", value: " and " },
27
+ { type: "tag", tag: "italic", chunks: "italic" },
28
+ ]);
29
+ });
30
+ it("should handle adjacent tags", () => {
31
+ const result = parseRichSegments("<tag1>First</tag1><tag2>Second</tag2>", [
32
+ "tag1",
33
+ "tag2",
34
+ ]);
35
+ expect(result).toEqual([
36
+ { type: "tag", tag: "tag1", chunks: "First" },
37
+ { type: "tag", tag: "tag2", chunks: "Second" },
38
+ ]);
39
+ });
40
+ it("should handle empty chunks", () => {
41
+ const result = parseRichSegments("Text <tag></tag> more text", ["tag"]);
42
+ expect(result).toEqual([
43
+ { type: "text", value: "Text " },
44
+ { type: "tag", tag: "tag", chunks: "" },
45
+ { type: "text", value: " more text" },
46
+ ]);
47
+ });
48
+ it("should return text segment when no matching tags found", () => {
49
+ const result = parseRichSegments("Hello <unknown>world</unknown>", ["link"]);
50
+ expect(result).toEqual([
51
+ { type: "text", value: "Hello <unknown>world</unknown>" },
52
+ ]);
53
+ });
54
+ it("should handle tag names with special regex characters", () => {
55
+ const result = parseRichSegments("Click <link.ext>here</link.ext> now", ["link.ext"]);
56
+ expect(result).toEqual([
57
+ { type: "text", value: "Click " },
58
+ { type: "tag", tag: "link.ext", chunks: "here" },
59
+ { type: "text", value: " now" },
60
+ ]);
61
+ });
62
+ it("should treat text as plain when tagNames is empty", () => {
63
+ const result = parseRichSegments("Click <link>here</link>", []);
64
+ expect(result).toEqual([{ type: "text", value: "Click <link>here</link>" }]);
65
+ });
66
+ });
67
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach } from "vitest";
2
- import { setRequestLocale, getLocale, getTranslations, getTranslationsReact, __resetRequestConfig, } from "../index.js";
2
+ import { setRequestLocale, getLocale, getTranslations, __resetRequestConfig } from "../index.js";
3
+ import { getTranslations as getTranslationsReact } from "../adapters/react.js";
3
4
  describe("Integration Tests", () => {
4
5
  beforeEach(() => {
5
6
  __resetRequestConfig();
@@ -127,7 +128,6 @@ describe("Integration Tests", () => {
127
128
  it("should throw descriptive error when accessing translations before setup", () => {
128
129
  expect(() => getLocale()).toThrow("[astro-intl] No request config found. Did you call setRequestLocale()?");
129
130
  expect(() => getTranslations()).toThrow("[astro-intl] No request config found. Did you call setRequestLocale()?");
130
- expect(() => getTranslationsReact()).toThrow("[astro-intl] No request config found. Did you call setRequestLocale()?");
131
131
  });
132
132
  it("should handle missing translations gracefully", async () => {
133
133
  const url = new URL("https://example.com/en/page");
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { createGetTranslationsReact } from "../react.js";
2
+ import { createGetTranslations } from "../adapters/react.js";
3
3
  describe("react.ts", () => {
4
4
  const ui = {
5
5
  en: {
@@ -31,36 +31,36 @@ describe("react.ts", () => {
31
31
  },
32
32
  },
33
33
  };
34
- describe("createGetTranslationsReact", () => {
34
+ describe("createGetTranslations", () => {
35
35
  it("should create translation function for default locale", () => {
36
- const getT = createGetTranslationsReact(ui, "en");
36
+ const getT = createGetTranslations(ui, "en");
37
37
  const t = getT("en", "common");
38
38
  expect(t("greeting")).toBe("Hello");
39
39
  expect(t("farewell")).toBe("Goodbye");
40
40
  });
41
41
  it("should create translation function for non-default locale", () => {
42
- const getT = createGetTranslationsReact(ui, "en");
42
+ const getT = createGetTranslations(ui, "en");
43
43
  const t = getT("es", "common");
44
44
  expect(t("greeting")).toBe("Hola");
45
45
  expect(t("farewell")).toBe("Adiós");
46
46
  });
47
47
  it("should handle nested keys", () => {
48
- const getT = createGetTranslationsReact(ui, "en");
48
+ const getT = createGetTranslations(ui, "en");
49
49
  const t = getT("en", "common");
50
50
  expect(t("nested.deep")).toBe("Deep value");
51
51
  });
52
52
  it("should return key when translation not found", () => {
53
- const getT = createGetTranslationsReact(ui, "en");
53
+ const getT = createGetTranslations(ui, "en");
54
54
  const t = getT("en", "common");
55
55
  expect(t("nonexistent")).toBe("nonexistent");
56
56
  });
57
57
  it("should fallback to default locale when locale not found", () => {
58
- const getT = createGetTranslationsReact(ui, "en");
58
+ const getT = createGetTranslations(ui, "en");
59
59
  const t = getT("fr", "common");
60
60
  expect(t("greeting")).toBe("Hello");
61
61
  });
62
62
  it("should work with different namespaces", () => {
63
- const getT = createGetTranslationsReact(ui, "en");
63
+ const getT = createGetTranslations(ui, "en");
64
64
  const tCommon = getT("en", "common");
65
65
  const tHome = getT("en", "home");
66
66
  expect(tCommon("greeting")).toBe("Hello");
@@ -69,7 +69,7 @@ describe("react.ts", () => {
69
69
  });
70
70
  describe("t.rich", () => {
71
71
  it("should interpolate React components", () => {
72
- const getT = createGetTranslationsReact(ui, "en");
72
+ const getT = createGetTranslations(ui, "en");
73
73
  const t = getT("en", "home");
74
74
  const result = t.rich("withTags", {
75
75
  link: (chunks) => `<a>${chunks}</a>`,
@@ -77,7 +77,7 @@ describe("react.ts", () => {
77
77
  expect(result).toEqual(["Click ", "<a>here</a>", " to continue"]);
78
78
  });
79
79
  it("should handle multiple tags", () => {
80
- const getT = createGetTranslationsReact(ui, "en");
80
+ const getT = createGetTranslations(ui, "en");
81
81
  const t = getT("en", "home");
82
82
  const result = t.rich("multipleTags", {
83
83
  bold: (chunks) => `<strong>${chunks}</strong>`,
@@ -86,13 +86,13 @@ describe("react.ts", () => {
86
86
  expect(result).toEqual(["Text with ", "<strong>bold</strong>", " and ", "<em>italic</em>"]);
87
87
  });
88
88
  it("should handle text without tags", () => {
89
- const getT = createGetTranslationsReact(ui, "en");
89
+ const getT = createGetTranslations(ui, "en");
90
90
  const t = getT("en", "common");
91
91
  const result = t.rich("greeting", {});
92
92
  expect(result).toEqual(["Hello"]);
93
93
  });
94
94
  it("should work with Spanish locale", () => {
95
- const getT = createGetTranslationsReact(ui, "en");
95
+ const getT = createGetTranslations(ui, "en");
96
96
  const t = getT("es", "home");
97
97
  const result = t.rich("withTags", {
98
98
  link: (chunks) => `<a>${chunks}</a>`,
@@ -107,13 +107,21 @@ describe("react.ts", () => {
107
107
  },
108
108
  },
109
109
  };
110
- const getT = createGetTranslationsReact(customUi, "en");
110
+ const getT = createGetTranslations(customUi, "en");
111
111
  const t = getT("en", "test");
112
112
  const result = t.rich("nested", {
113
- outer: (chunks) => `[${chunks}]`,
114
- inner: (chunks) => `{${chunks}}`,
113
+ outer: (chunks) => ({ wrapper: "outer", children: chunks }),
114
+ inner: (chunks) => ({ wrapper: "inner", children: chunks }),
115
115
  });
116
- expect(result).toEqual(["Start ", "[outer {inner} outer]", " end"]);
116
+ // outer receives a ReactNode[] because inner was processed recursively
117
+ expect(result).toEqual([
118
+ "Start ",
119
+ {
120
+ wrapper: "outer",
121
+ children: ["outer ", { wrapper: "inner", children: "inner" }, " outer"],
122
+ },
123
+ " end",
124
+ ]);
117
125
  });
118
126
  it("should handle adjacent tags", () => {
119
127
  const customUi = {
@@ -123,7 +131,7 @@ describe("react.ts", () => {
123
131
  },
124
132
  },
125
133
  };
126
- const getT = createGetTranslationsReact(customUi, "en");
134
+ const getT = createGetTranslations(customUi, "en");
127
135
  const t = getT("en", "test");
128
136
  const result = t.rich("adjacent", {
129
137
  tag1: (chunks) => `[${chunks}]`,
@@ -139,7 +147,7 @@ describe("react.ts", () => {
139
147
  },
140
148
  },
141
149
  };
142
- const getT = createGetTranslationsReact(customUi, "en");
150
+ const getT = createGetTranslations(customUi, "en");
143
151
  const t = getT("en", "test");
144
152
  const result = t.rich("empty", {
145
153
  tag: (chunks) => `[${chunks}]`,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,180 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createGetTranslations, renderRichText } from "../adapters/svelte.js";
3
+ describe("svelte adapter", () => {
4
+ const ui = {
5
+ en: {
6
+ common: {
7
+ greeting: "Hello",
8
+ farewell: "Goodbye",
9
+ nested: {
10
+ deep: "Deep value",
11
+ },
12
+ },
13
+ home: {
14
+ title: "Welcome Home",
15
+ withTags: "Click <link>here</link> to continue",
16
+ multipleTags: "Text with <bold>bold</bold> and <italic>italic</italic>",
17
+ },
18
+ },
19
+ es: {
20
+ common: {
21
+ greeting: "Hola",
22
+ farewell: "Adiós",
23
+ nested: {
24
+ deep: "Valor profundo",
25
+ },
26
+ },
27
+ home: {
28
+ title: "Bienvenido a Casa",
29
+ withTags: "Haz clic <link>aquí</link> para continuar",
30
+ multipleTags: "Texto con <bold>negrita</bold> y <italic>cursiva</italic>",
31
+ },
32
+ },
33
+ };
34
+ describe("createGetTranslations", () => {
35
+ it("should create translation function for default locale", () => {
36
+ const getT = createGetTranslations(ui, "en");
37
+ const t = getT("en", "common");
38
+ expect(t("greeting")).toBe("Hello");
39
+ expect(t("farewell")).toBe("Goodbye");
40
+ });
41
+ it("should create translation function for non-default locale", () => {
42
+ const getT = createGetTranslations(ui, "en");
43
+ const t = getT("es", "common");
44
+ expect(t("greeting")).toBe("Hola");
45
+ expect(t("farewell")).toBe("Adiós");
46
+ });
47
+ it("should handle nested keys", () => {
48
+ const getT = createGetTranslations(ui, "en");
49
+ const t = getT("en", "common");
50
+ expect(t("nested.deep")).toBe("Deep value");
51
+ });
52
+ it("should return key when translation not found", () => {
53
+ const getT = createGetTranslations(ui, "en");
54
+ const t = getT("en", "common");
55
+ expect(t("nonexistent")).toBe("nonexistent");
56
+ });
57
+ it("should fallback to default locale when locale not found", () => {
58
+ const getT = createGetTranslations(ui, "en");
59
+ const t = getT("fr", "common");
60
+ expect(t("greeting")).toBe("Hello");
61
+ });
62
+ });
63
+ describe("t.rich", () => {
64
+ it("should return segments for a single tag", () => {
65
+ const getT = createGetTranslations(ui, "en");
66
+ const t = getT("en", "home");
67
+ const result = t.rich("withTags", ["link"]);
68
+ expect(result).toEqual([
69
+ { type: "text", value: "Click " },
70
+ { type: "tag", tag: "link", chunks: "here" },
71
+ { type: "text", value: " to continue" },
72
+ ]);
73
+ });
74
+ it("should return segments for multiple tags", () => {
75
+ const getT = createGetTranslations(ui, "en");
76
+ const t = getT("en", "home");
77
+ const result = t.rich("multipleTags", ["bold", "italic"]);
78
+ expect(result).toEqual([
79
+ { type: "text", value: "Text with " },
80
+ { type: "tag", tag: "bold", chunks: "bold" },
81
+ { type: "text", value: " and " },
82
+ { type: "tag", tag: "italic", chunks: "italic" },
83
+ ]);
84
+ });
85
+ it("should return text segment when no tagNames provided", () => {
86
+ const getT = createGetTranslations(ui, "en");
87
+ const t = getT("en", "common");
88
+ const result = t.rich("greeting");
89
+ expect(result).toEqual([{ type: "text", value: "Hello" }]);
90
+ });
91
+ it("should work with Spanish locale", () => {
92
+ const getT = createGetTranslations(ui, "en");
93
+ const t = getT("es", "home");
94
+ const result = t.rich("withTags", ["link"]);
95
+ expect(result).toEqual([
96
+ { type: "text", value: "Haz clic " },
97
+ { type: "tag", tag: "link", chunks: "aquí" },
98
+ { type: "text", value: " para continuar" },
99
+ ]);
100
+ });
101
+ it("should handle adjacent tags", () => {
102
+ const customUi = {
103
+ en: {
104
+ test: {
105
+ adjacent: "<tag1>First</tag1><tag2>Second</tag2>",
106
+ },
107
+ },
108
+ };
109
+ const getT = createGetTranslations(customUi, "en");
110
+ const t = getT("en", "test");
111
+ const result = t.rich("adjacent", ["tag1", "tag2"]);
112
+ expect(result).toEqual([
113
+ { type: "tag", tag: "tag1", chunks: "First" },
114
+ { type: "tag", tag: "tag2", chunks: "Second" },
115
+ ]);
116
+ });
117
+ it("should handle empty chunks", () => {
118
+ const customUi = {
119
+ en: {
120
+ test: {
121
+ empty: "Text <tag></tag> more text",
122
+ },
123
+ },
124
+ };
125
+ const getT = createGetTranslations(customUi, "en");
126
+ const t = getT("en", "test");
127
+ const result = t.rich("empty", ["tag"]);
128
+ expect(result).toEqual([
129
+ { type: "text", value: "Text " },
130
+ { type: "tag", tag: "tag", chunks: "" },
131
+ { type: "text", value: " more text" },
132
+ ]);
133
+ });
134
+ });
135
+ describe("renderRichText", () => {
136
+ it("should resolve tags to native HTML elements", () => {
137
+ const getT = createGetTranslations(ui, "en");
138
+ const t = getT("en", "home");
139
+ const segments = t.rich("multipleTags", ["bold", "italic"]);
140
+ const html = renderRichText(segments, {
141
+ tags: { bold: "strong", italic: "em" },
142
+ });
143
+ expect(html).toBe("Text with <strong>bold</strong> and <em>italic</em>");
144
+ });
145
+ it("should resolve components as callback functions", () => {
146
+ const getT = createGetTranslations(ui, "en");
147
+ const t = getT("en", "home");
148
+ const segments = t.rich("withTags", ["link"]);
149
+ const html = renderRichText(segments, {
150
+ components: {
151
+ link: (chunks) => `<a href="/target">${chunks}</a>`,
152
+ },
153
+ });
154
+ expect(html).toBe('Click <a href="/target">here</a> to continue');
155
+ });
156
+ it("should mix tags and components", () => {
157
+ const getT = createGetTranslations(ui, "en");
158
+ const t = getT("en", "home");
159
+ const segments = t.rich("multipleTags", ["bold", "italic"]);
160
+ const html = renderRichText(segments, {
161
+ tags: { bold: "strong" },
162
+ components: {
163
+ italic: (chunks) => `<span class="italic">${chunks}</span>`,
164
+ },
165
+ });
166
+ expect(html).toBe('Text with <strong>bold</strong> and <span class="italic">italic</span>');
167
+ });
168
+ it("should fallback to plain chunks when no resolver found", () => {
169
+ const getT = createGetTranslations(ui, "en");
170
+ const t = getT("en", "home");
171
+ const segments = t.rich("withTags", ["link"]);
172
+ const html = renderRichText(segments);
173
+ expect(html).toBe("Click here to continue");
174
+ });
175
+ it("should handle empty segments", () => {
176
+ const html = renderRichText([]);
177
+ expect(html).toBe("");
178
+ });
179
+ });
180
+ });
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ import { type DotPaths } from "../interpolation.js";
3
+ export declare function createGetTranslations<UI extends Record<string, Record<string, unknown>>, DefaultLocale extends keyof UI>(ui: UI, defaultLocale: DefaultLocale): <N extends keyof UI[DefaultLocale]>(lang: string | undefined, namespace: N) => ((key: DotPaths<UI[DefaultLocale][N]>) => string) & {
4
+ rich: (key: DotPaths<UI[DefaultLocale][N]>, tags: Record<string, (chunks: ReactNode) => ReactNode>) => ReactNode[];
5
+ };
6
+ export declare function getTranslations<T extends Record<string, unknown> = Record<string, unknown>>(namespace?: string): ((key: DotPaths<T>) => string) & {
7
+ rich: (key: DotPaths<T>, tags: Record<string, (chunks: ReactNode) => ReactNode>) => ReactNode[];
8
+ };
@@ -0,0 +1,45 @@
1
+ import { getNestedValue } from "../interpolation.js";
2
+ import { parseRichSegments } from "../framework-base.js";
3
+ import { getMessages, getLocale } from "../store.js";
4
+ // ─── createGetTranslations (standalone, no store dependency) ────────
5
+ export function createGetTranslations(ui, defaultLocale) {
6
+ return function getTranslations(lang, namespace) {
7
+ const resolvedLang = lang && lang in ui ? lang : defaultLocale;
8
+ const messages = ui[resolvedLang][namespace];
9
+ function t(key) {
10
+ const value = getNestedValue(messages, key);
11
+ return typeof value === "string" ? value : key;
12
+ }
13
+ const rich = function (key, tags) {
14
+ const str = t(key);
15
+ const tagNames = Object.keys(tags);
16
+ function processString(input) {
17
+ const segments = parseRichSegments(input, tagNames);
18
+ return segments.map((seg) => {
19
+ if (seg.type === "text")
20
+ return seg.value;
21
+ // Recursively process nested tags within chunks
22
+ const innerSegments = parseRichSegments(seg.chunks, tagNames);
23
+ const hasNestedTags = innerSegments.some((s) => s.type === "tag");
24
+ const resolvedChunks = hasNestedTags ? processString(seg.chunks) : seg.chunks;
25
+ const tagFn = tags[seg.tag];
26
+ if (typeof tagFn !== "function") {
27
+ console.warn(`[astro-intl] Unregistered rich tag: <${seg.tag}>. Content will render without transformation.`);
28
+ return resolvedChunks;
29
+ }
30
+ return tagFn(resolvedChunks);
31
+ });
32
+ }
33
+ return processString(str);
34
+ };
35
+ Object.assign(t, { rich });
36
+ return t;
37
+ };
38
+ }
39
+ // ─── getTranslations (store-backed, for use in Astro islands) ───────
40
+ export function getTranslations(namespace) {
41
+ const messages = getMessages(namespace);
42
+ const locale = getLocale();
43
+ const ui = { [locale]: { default: messages } };
44
+ return createGetTranslations(ui, locale)(locale, "default");
45
+ }
@@ -0,0 +1,14 @@
1
+ import { type DotPaths } from "../interpolation.js";
2
+ import { type RichSegment } from "../framework-base.js";
3
+ export type { RichSegment } from "../framework-base.js";
4
+ export declare function createGetTranslations<UI extends Record<string, Record<string, unknown>>, DefaultLocale extends keyof UI>(ui: UI, defaultLocale: DefaultLocale): <N extends keyof UI[DefaultLocale]>(lang: string | undefined, namespace: N) => ((key: DotPaths<UI[DefaultLocale][N]>) => string) & {
5
+ rich: (key: DotPaths<UI[DefaultLocale][N]>, tagNames?: string[]) => RichSegment[];
6
+ };
7
+ export type RichTextOptions = {
8
+ tags?: Record<string, string>;
9
+ components?: Record<string, (chunks: string) => string>;
10
+ };
11
+ export declare function renderRichText(segments: RichSegment[], options?: RichTextOptions): string;
12
+ export declare function getTranslations<T extends Record<string, unknown> = Record<string, unknown>>(namespace?: string): ((key: DotPaths<T>) => string) & {
13
+ rich: (key: DotPaths<T>, tagNames?: string[]) => RichSegment[];
14
+ };
@@ -0,0 +1,44 @@
1
+ import { getNestedValue } from "../interpolation.js";
2
+ import { parseRichSegments } from "../framework-base.js";
3
+ import { getMessages, getLocale } from "../store.js";
4
+ // ─── createGetTranslations (standalone, no store dependency) ────────
5
+ export function createGetTranslations(ui, defaultLocale) {
6
+ return function getTranslations(lang, namespace) {
7
+ const resolvedLang = lang && lang in ui ? lang : defaultLocale;
8
+ const messages = ui[resolvedLang][namespace];
9
+ function t(key) {
10
+ const value = getNestedValue(messages, key);
11
+ return typeof value === "string" ? value : key;
12
+ }
13
+ const rich = function (key, tagNames) {
14
+ const str = t(key);
15
+ return parseRichSegments(str, tagNames ?? []);
16
+ };
17
+ Object.assign(t, { rich });
18
+ return t;
19
+ };
20
+ }
21
+ export function renderRichText(segments, options = {}) {
22
+ const { tags = {}, components = {} } = options;
23
+ return segments
24
+ .map((seg) => {
25
+ if (seg.type === "text")
26
+ return seg.value;
27
+ const componentFn = components[seg.tag];
28
+ if (typeof componentFn === "function")
29
+ return componentFn(seg.chunks);
30
+ const htmlTag = tags[seg.tag];
31
+ if (typeof htmlTag === "string")
32
+ return `<${htmlTag}>${seg.chunks}</${htmlTag}>`;
33
+ console.warn(`[astro-intl] Unregistered rich tag: <${seg.tag}>. Content will render without transformation.`);
34
+ return seg.chunks;
35
+ })
36
+ .join("");
37
+ }
38
+ // ─── getTranslations (store-backed, for use in Astro islands) ───────
39
+ export function getTranslations(namespace) {
40
+ const messages = getMessages(namespace);
41
+ const locale = getLocale();
42
+ const ui = { [locale]: { default: messages } };
43
+ return createGetTranslations(ui, locale)(locale, "default");
44
+ }
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { setRequestLocale, runWithLocale, getLocale, getLocales, isValidLocale, getMessages, getDefaultLocale, defineRequestConfig, __setConfigMessages, __resetRequestConfig, __setIntlConfig, } from "./store.js";
2
- export { getTranslations, getTranslationsReact } from "./translations.js";
2
+ export { getTranslations } from "./translations.js";
3
3
  export { getNestedValue, type DotPaths } from "./interpolation.js";
4
4
  export { sanitizeLocale, sanitizeHtml, escapeRegExp } from "./sanitize.js";
5
5
  export { path, switchLocalePath, templateToRegex } from "./routing.js";
package/dist/core.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // ─── Store (request lifecycle) ──────────────────────────────────────
2
2
  export { setRequestLocale, runWithLocale, getLocale, getLocales, isValidLocale, getMessages, getDefaultLocale, defineRequestConfig, __setConfigMessages, __resetRequestConfig, __setIntlConfig, } from "./store.js";
3
3
  // ─── Translations ───────────────────────────────────────────────────
4
- export { getTranslations, getTranslationsReact } from "./translations.js";
4
+ export { getTranslations } from "./translations.js";
5
5
  // ─── Interpolation utilities ────────────────────────────────────────
6
6
  export { getNestedValue } from "./interpolation.js";
7
7
  // ─── Sanitisation utilities ─────────────────────────────────────────
@@ -0,0 +1,11 @@
1
+ export type RichSegmentText = {
2
+ type: "text";
3
+ value: string;
4
+ };
5
+ export type RichSegmentTag = {
6
+ type: "tag";
7
+ tag: string;
8
+ chunks: string;
9
+ };
10
+ export type RichSegment = RichSegmentText | RichSegmentTag;
11
+ export declare function parseRichSegments(str: string, tagNames: string[]): RichSegment[];
@@ -0,0 +1,24 @@
1
+ import { escapeRegExp } from "./sanitize.js";
2
+ // ─── Parse rich text into framework-agnostic segments ───────────────
3
+ export function parseRichSegments(str, tagNames) {
4
+ if (tagNames.length === 0) {
5
+ return str.length > 0 ? [{ type: "text", value: str }] : [];
6
+ }
7
+ const escaped = tagNames.map(escapeRegExp);
8
+ const regex = new RegExp(`<(${escaped.join("|")})>(.*?)<\\/(\\1)>`, "g");
9
+ const result = [];
10
+ let lastIndex = 0;
11
+ let match;
12
+ while ((match = regex.exec(str)) !== null) {
13
+ if (match.index > lastIndex) {
14
+ result.push({ type: "text", value: str.slice(lastIndex, match.index) });
15
+ }
16
+ const [, tag, chunks] = match;
17
+ result.push({ type: "tag", tag, chunks });
18
+ lastIndex = match.index + match[0].length;
19
+ }
20
+ if (lastIndex < str.length) {
21
+ result.push({ type: "text", value: str.slice(lastIndex) });
22
+ }
23
+ return result;
24
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { AstroIntegration } from "astro";
2
2
  import type { MessagesConfig, RoutesMap } from "./types/index.js";
3
- import { setRequestLocale as _setRequestLocale, runWithLocale as _runWithLocale, getLocale as _getLocale, getLocales as _getLocales, isValidLocale as _isValidLocale, getMessages as _getMessages, getTranslations as _getTranslations, getTranslationsReact as _getTranslationsReact, defineRequestConfig as _defineRequestConfig, __resetRequestConfig as _resetRequestConfig, path as _path, switchLocalePath as _switchLocalePath } from "./core.js";
3
+ import { setRequestLocale as _setRequestLocale, runWithLocale as _runWithLocale, getLocale as _getLocale, getLocales as _getLocales, isValidLocale as _isValidLocale, getMessages as _getMessages, getTranslations as _getTranslations, defineRequestConfig as _defineRequestConfig, __resetRequestConfig as _resetRequestConfig, path as _path, switchLocalePath as _switchLocalePath } from "./core.js";
4
4
  export type AstroIntlOptions = {
5
5
  enabled?: boolean;
6
6
  defaultLocale?: string;
@@ -16,7 +16,6 @@ export declare const getLocales: typeof _getLocales;
16
16
  export declare const isValidLocale: typeof _isValidLocale;
17
17
  export declare const getMessages: typeof _getMessages;
18
18
  export declare const getTranslations: typeof _getTranslations;
19
- export declare const getTranslationsReact: typeof _getTranslationsReact;
20
19
  export declare const defineRequestConfig: typeof _defineRequestConfig;
21
20
  export declare const __resetRequestConfig: typeof _resetRequestConfig;
22
21
  export declare const path: typeof _path;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { setRequestLocale as _setRequestLocale, runWithLocale as _runWithLocale, getLocale as _getLocale, getLocales as _getLocales, isValidLocale as _isValidLocale, getMessages as _getMessages, getTranslations as _getTranslations, getTranslationsReact as _getTranslationsReact, defineRequestConfig as _defineRequestConfig, __resetRequestConfig as _resetRequestConfig, __setConfigMessages, __setIntlConfig, path as _path, switchLocalePath as _switchLocalePath, } from "./core.js";
1
+ import { setRequestLocale as _setRequestLocale, runWithLocale as _runWithLocale, getLocale as _getLocale, getLocales as _getLocales, isValidLocale as _isValidLocale, getMessages as _getMessages, getTranslations as _getTranslations, defineRequestConfig as _defineRequestConfig, __resetRequestConfig as _resetRequestConfig, __setConfigMessages, __setIntlConfig, path as _path, switchLocalePath as _switchLocalePath, } from "./core.js";
2
2
  export default function astroIntl(options = {}) {
3
3
  const { enabled = true, defaultLocale, locales, messages, routes } = options;
4
4
  if (defaultLocale || locales || routes) {
@@ -38,7 +38,6 @@ export const getLocales = _getLocales;
38
38
  export const isValidLocale = _isValidLocale;
39
39
  export const getMessages = _getMessages;
40
40
  export const getTranslations = _getTranslations;
41
- export const getTranslationsReact = _getTranslationsReact;
42
41
  export const defineRequestConfig = _defineRequestConfig;
43
42
  export const __resetRequestConfig = _resetRequestConfig;
44
43
  export const path = _path;
@@ -6,6 +6,3 @@ export declare function getTranslations<T extends Record<string, unknown> = Reco
6
6
  tags: Record<string, (chunks: string) => string>;
7
7
  }) => string;
8
8
  };
9
- export declare function getTranslationsReact<T extends Record<string, unknown> = Record<string, unknown>>(namespace?: string): ((key: DotPaths<T>) => string) & {
10
- rich: (key: DotPaths<T>, tags: Record<string, (chunks: string) => import("react").ReactNode>) => import("react").ReactNode[];
11
- };
@@ -1,7 +1,6 @@
1
- import { getMessages, getLocale } from "./store.js";
1
+ import { getMessages } from "./store.js";
2
2
  import { getNestedValue, interpolateValues } from "./interpolation.js";
3
3
  import { escapeRegExp, sanitizeHtml } from "./sanitize.js";
4
- import { createGetTranslationsReact } from "./react.js";
5
4
  // ─── getTranslations (Astro / plain HTML) ───────────────────────────
6
5
  export function getTranslations(namespace) {
7
6
  const messages = getMessages(namespace);
@@ -32,10 +31,3 @@ export function getTranslations(namespace) {
32
31
  Object.assign(t, { markup });
33
32
  return t;
34
33
  }
35
- // ─── getTranslationsReact (React islands) ───────────────────────────
36
- export function getTranslationsReact(namespace) {
37
- const messages = getMessages(namespace);
38
- const locale = getLocale();
39
- const ui = { [locale]: { default: messages } };
40
- return createGetTranslationsReact(ui, locale)(locale, "default");
41
- }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "astro-intl",
3
- "version": "1.1.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
- "description": "Sistema de internacionalización simple y type-safe para Astro, inspirado en next-intl",
5
+ "description": "Sistema de internacionalización simple y type-safe para Astro.",
6
6
  "keywords": [
7
7
  "astro",
8
8
  "astro-integration",
@@ -41,6 +41,18 @@
41
41
  "./middleware": {
42
42
  "types": "./dist/middleware.d.ts",
43
43
  "import": "./dist/middleware.js"
44
+ },
45
+ "./react": {
46
+ "types": "./dist/adapters/react.d.ts",
47
+ "import": "./dist/adapters/react.js"
48
+ },
49
+ "./svelte": {
50
+ "types": "./dist/adapters/svelte.d.ts",
51
+ "import": "./dist/adapters/svelte.js"
52
+ },
53
+ "./routing": {
54
+ "types": "./dist/routing.d.ts",
55
+ "import": "./dist/routing.js"
44
56
  }
45
57
  },
46
58
  "peerDependencies": {
@@ -49,6 +61,9 @@
49
61
  "peerDependenciesMeta": {
50
62
  "react": {
51
63
  "optional": true
64
+ },
65
+ "svelte": {
66
+ "optional": true
52
67
  }
53
68
  },
54
69
  "repository": {
@@ -76,6 +91,7 @@
76
91
  "react-dom": "^19.2.4",
77
92
  "typescript": "^5.9.3",
78
93
  "typescript-eslint": "^8.19.1",
94
+ "svelte": "^5.0.0",
79
95
  "vitest": "^4.0.18"
80
96
  }
81
97
  }