@i18n-micro/astro 1.3.25 → 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.
Files changed (75) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -64
  3. package/dist/auto-middleware.d.ts +1 -0
  4. package/dist/client/core.d.cts +86 -0
  5. package/dist/client/core.d.ts +71 -8
  6. package/dist/client/index.d.cts +2 -6
  7. package/dist/client/index.d.ts +2 -6
  8. package/dist/client/index.js +1 -6
  9. package/dist/client/preact.d.ts +12 -23
  10. package/dist/client/preact.js +13 -37
  11. package/dist/client/react.d.ts +14 -21
  12. package/dist/client/react.js +12 -37
  13. package/dist/client/svelte.d.ts +14 -14
  14. package/dist/client/svelte.js +17 -47
  15. package/dist/client/vue.d.ts +15 -15
  16. package/dist/client/vue.js +37 -2
  17. package/dist/composer.d.cts +16 -0
  18. package/dist/composer.d.ts +10 -30
  19. package/dist/core-CgrgdmGy.cjs +1 -0
  20. package/dist/core-DTAcn2yF.js +72 -0
  21. package/dist/env.d.ts +8 -2
  22. package/dist/index.cjs +11 -3
  23. package/dist/index.d.cts +10 -13
  24. package/dist/index.d.ts +10 -13
  25. package/dist/index.mjs +171 -482
  26. package/dist/integration.d.cts +26 -0
  27. package/dist/integration.d.ts +14 -15
  28. package/dist/middleware-BT3OA5YL.cjs +1 -0
  29. package/dist/middleware-Ch8E0iV_.js +257 -0
  30. package/dist/middleware.d.cts +34 -0
  31. package/dist/middleware.d.ts +17 -8
  32. package/dist/middleware.js +23 -0
  33. package/dist/node.d.ts +26 -0
  34. package/dist/node.js +22 -0
  35. package/dist/request.d.ts +69 -0
  36. package/dist/router/adapter.d.cts +9 -0
  37. package/dist/router/adapter.d.ts +5 -4
  38. package/dist/router/types.d.cts +66 -0
  39. package/dist/router/types.d.ts +3 -2
  40. package/dist/utils.d.cts +112 -0
  41. package/dist/utils.d.ts +39 -35
  42. package/dist/virtual.d.ts +21 -0
  43. package/package.json +28 -15
  44. package/src/auto-middleware.ts +26 -0
  45. package/src/client/core.ts +124 -59
  46. package/src/client/index.ts +2 -11
  47. package/src/client/preact.tsx +12 -127
  48. package/src/client/react.tsx +11 -123
  49. package/src/client/svelte.ts +6 -69
  50. package/src/client/vue.ts +22 -77
  51. package/src/components/i18n-link.astro +2 -59
  52. package/src/components/i18n-switcher.astro +46 -149
  53. package/src/components/i18n-t.astro +6 -54
  54. package/src/composer.ts +25 -115
  55. package/src/env.d.ts +7 -0
  56. package/src/index.ts +1 -6
  57. package/src/integration.ts +113 -52
  58. package/src/middleware.ts +174 -120
  59. package/src/node.ts +51 -0
  60. package/src/request.ts +177 -0
  61. package/src/router/adapter.ts +47 -149
  62. package/src/router/types.ts +3 -2
  63. package/src/utils.ts +149 -323
  64. package/src/virtual.d.ts +21 -0
  65. package/dist/components/index.d.cts +0 -0
  66. package/dist/components/index.d.ts +0 -0
  67. package/dist/core-DwkpOV-H.cjs +0 -1
  68. package/dist/core-VSnkGRWR.js +0 -28
  69. package/dist/load-translations.d.ts +0 -43
  70. package/dist/routing-context.d.ts +0 -4
  71. package/dist/vue-BNiEwZr2.cjs +0 -5
  72. package/dist/vue-CWHjIOiS.js +0 -1724
  73. package/src/components/index.ts +0 -11
  74. package/src/load-translations.ts +0 -106
  75. package/src/routing-context.ts +0 -23
@@ -0,0 +1,26 @@
1
+ import { Locale } from '@i18n-micro/types';
2
+ import { AstroIntegration } from 'astro';
3
+ import { AstroI18n, AstroI18nOptions } from './composer.cjs';
4
+ export interface I18nIntegrationOptions {
5
+ locale: string;
6
+ fallbackLocale?: string;
7
+ locales?: Locale[];
8
+ missingWarn?: boolean;
9
+ localeCookie?: string | null;
10
+ autoDetect?: boolean;
11
+ /** `/` also redirects when nothing prefers another locale: see `createI18nMiddleware`. */
12
+ redirectToDefault?: boolean;
13
+ /** Bundled into the server at build time, relative to the project root. */
14
+ translationDir?: string;
15
+ /** Page files (`pages/about/de.json`) count as root translations. */
16
+ disablePageLocales?: boolean;
17
+ /**
18
+ * `false`: no middleware from the integration. Write `src/middleware.ts` with `createI18nMiddleware`
19
+ * for what cannot cross from the Astro config to the server bundle: `plural`, `missingHandler`, a strategy.
20
+ */
21
+ middleware?: boolean;
22
+ }
23
+ /** Astro integration for i18n-micro: publishes the config and dictionaries, and installs the middleware. */
24
+ export declare function i18nIntegration(options: I18nIntegrationOptions): AstroIntegration;
25
+ /** Create an i18n instance (for a `src/middleware.ts` of your own). */
26
+ export declare function createI18n(options: AstroI18nOptions): AstroI18n;
@@ -1,27 +1,26 @@
1
- import { Locale, ModuleOptions, PluralFunc } from '@i18n-micro/types';
1
+ import { Locale } from '@i18n-micro/types';
2
2
  import { AstroIntegration } from 'astro';
3
- import { AstroI18n, AstroI18nOptions } from './composer';
4
- import { I18nRoutingStrategy } from './router/types';
5
- export interface I18nIntegrationOptions extends Omit<ModuleOptions, 'plural'> {
3
+ import { AstroI18n, AstroI18nOptions } from './composer.js';
4
+ export interface I18nIntegrationOptions {
6
5
  locale: string;
7
6
  fallbackLocale?: string;
8
7
  locales?: Locale[];
9
- messages?: Record<string, Record<string, unknown>>;
10
- plural?: PluralFunc;
11
8
  missingWarn?: boolean;
12
- missingHandler?: (locale: string, key: string, routeName: string) => void;
13
9
  localeCookie?: string | null;
14
10
  autoDetect?: boolean;
11
+ /** `/` also redirects when nothing prefers another locale: see `createI18nMiddleware`. */
15
12
  redirectToDefault?: boolean;
13
+ /** Bundled into the server at build time, relative to the project root. */
16
14
  translationDir?: string;
17
- routingStrategy?: I18nRoutingStrategy;
15
+ /** Page files (`pages/about/de.json`) count as root translations. */
16
+ disablePageLocales?: boolean;
17
+ /**
18
+ * `false`: no middleware from the integration. Write `src/middleware.ts` with `createI18nMiddleware`
19
+ * for what cannot cross from the Astro config to the server bundle: `plural`, `missingHandler`, a strategy.
20
+ */
21
+ middleware?: boolean;
18
22
  }
19
- export { getGlobalRoutingStrategy, runWithRoutingStrategy, setGlobalRoutingStrategy } from './routing-context';
20
- /**
21
- * Astro Integration for i18n-micro
22
- */
23
+ /** Astro integration for i18n-micro: publishes the config and dictionaries, and installs the middleware. */
23
24
  export declare function i18nIntegration(options: I18nIntegrationOptions): AstroIntegration;
24
- /**
25
- * Create i18n instance (for manual setup)
26
- */
25
+ /** Create an i18n instance (for a `src/middleware.ts` of your own). */
27
26
  export declare function createI18n(options: AstroI18nOptions): AstroI18n;
@@ -0,0 +1 @@
1
+ let e=require("@i18n-micro/core"),t=require("@i18n-micro/runtime"),n=require("@i18n-micro/utils/active-locales"),r=require("@i18n-micro/utils/app-path"),i=require("@i18n-micro/utils/cookie"),a=require("@i18n-micro/utils/resolve-locale");var o=class extends t.I18n{constructor(e){super(e);let t=e.parent;this.initialMessages=t?.initialMessages??{...e.messages},this.initialRouteMessages=t?.initialRouteMessages??Object.fromEntries(Object.entries(e.routeMessages??{}).map(([e,t])=>[e,{...t}]))}keepMessages(e,t){this.initialMessages={...this.initialMessages,...e};let n={...this.initialRouteMessages};for(let[e,r]of Object.entries(t))n[e]={...n[e],...r};this.initialRouteMessages=n}getLocaleIso(){return this.localeIso}getRouteTranslations(e,t){return this.helper.getCache(e,t)??null}mergeTranslations(e,t,n){this.addRouteTranslations(e,t,n)}clearCache(){super.clearCache(),this.loadMessages(this.initialMessages,this.initialRouteMessages)}},s=e=>(0,r.trimTrailingSlashes)(e??``);function c(e,t){return t?e===`/`?t:`${t}${e}`:e}function l(e,t){try{return e.request.headers.get(t)}catch{return null}}function u(e){let t=e.isPrerendered?null:l(e,`referer`);if(!t)return null;try{return new URL(t)}catch{return null}}function d(e,t){let n=l(e,`sec-fetch-site`);return n?n===`same-origin`:t.host===e.url.host}function f(e){let t=u(e);return t&&new URL(t.pathname+t.search,e.url)}var p=e=>e?.startsWith(`/_server-islands/`)===!0||e?.startsWith(`/_actions/`)===!0;function m(e,t){let{routePattern:n,params:r}=e;if(!n)return;let i=/^\/\[([^\].]+)\]/.exec(n),a=i?r?.[i[1]]:void 0;if(!a)return n;let o=n.slice(i[0].length);return t.includes(a)?`/${a}${o}`:i[1]===`locale`?o||`/`:n}function h(e,{defaultLocale:t,locales:n,strategy:r}){let i=(0,a.localeFromPath)(e,n);if(i)return i;let o=r.getLocaleFromPath?.(e,t,n)??null;return o!==t&&o!==null&&n.includes(o)?o:null}function g(e,t){let{strategy:n,locales:i,defaultLocale:a,base:o}=t,s=p(e.routePattern),c=s&&f(e)||e.url,l=(0,r.withoutAppBaseURL)(c.pathname,o),u=h(l,t),d=s?void 0:m(e,i);return{url:c,partOfPage:s,path:l,urlLocale:u,locale:u??a,route:n.getRouteName(l,i,d)}}function _(e,t,n){let i=u(e);return!i||!d(e,i)?!1:(h((0,r.withoutAppBaseURL)(i.pathname,n.base),n)??n.defaultLocale)!==t.locale}function v(e,t){let n=e.request.method??`GET`;return!t.partOfPage&&!e.isPrerendered&&(n===`GET`||n===`HEAD`)}function y(e,t){return!t.ok||!t.headers.get(`content-type`)?.startsWith(`text/html`)?!1:!`${l(e,`sec-purpose`)} ${l(e,`purpose`)}`.includes(`prefetch`)}var b=/^[a-z][a-z\d+.-]*:/i;function x(t,n,i){if(t===``||t[0]===`#`||t[0]===`?`)return t;let a=(0,e.externalLinkHref)(t)??(b.test(t)?t:void 0);if(a)return a;let{strategy:o,codes:s,defaultLocale:l,base:u,from:d=`/`}=i,f=(0,e.pathEnd)(t),p=f===-1?t:t.slice(0,f),m=(0,e.withoutDots)(p[0]===`/`?(0,r.withoutAppBaseURL)(p,u):`${(0,r.trimTrailingSlashes)(d)}/${p}`),h=o.localizePath(m,n,s,l);return`${c(h+(p.length>1&&p.endsWith(`/`)&&!h.endsWith(`/`)?`/`:``),u)}${f===-1?``:t.slice(f)}`}var S=/\[(?:\.\.\.)?([^\]]+)\]/g;function C(t,n){let r=(0,e.pathSegments)((0,e.urlPath)(t));return r[0]!==void 0&&n.includes(r[0])&&r.shift(),r}var w=t=>t.slice((0,e.urlPath)(t).length);function T(e,t=[]){let n=C(e,t);if(n.length===0)return`index`;let r=n.join(`-`);return r.includes(`[`)?r.replace(S,`$1`):r}var E=(e,t=[])=>`/${C(e,t).join(`/`)}${w(e)}`;function D(e,t,n=[],r){let i=C(e,n);return t!==r&&i.unshift(t),`/${i.join(`/`)}${w(e)}`}function O(e,t,n){let i=e.map(e=>e.code),o=()=>n?.()??(typeof window<`u`?new URL(window.location.href):null);return{getCurrentPath:()=>o()?.pathname??`/`,getRouteName:(e,t=[],n)=>T(n??e,t),getLocaleFromPath:(e,t=`en`,n=[])=>(0,a.localeFromPath)(`/${(0,r.trimLeadingSlashes)(e)}`,n)??t,switchLocalePath:D,localizePath:D,removeLocaleFromPath:E,resolvePath:(e,n)=>D(typeof e==`string`?e:e.path||`/`,n,i,t),getRoute:()=>{let e=o();return e?{fullPath:e.pathname+e.search,query:Object.fromEntries(e.searchParams)}:{fullPath:`/`,query:{}}},push:e=>{typeof window<`u`&&(window.location.href=e.path)},replace:e=>{typeof window<`u`&&window.location.replace(e.path)}}}var k=`i18n-locale`;function A(e,t){if(t===null)return null;try{return e.get(t)?.value??null}catch{return null}}function j(e,t,n){let r=O(t,n);if(!e)return r;let i=e,a={};for(let[t,n]of Object.entries(r)){let r=i[t];a[t]=typeof r==`function`?r.bind(e):n}return a}function M(e,t,{cookies:n,headers:r,localeCookie:i,autoDetect:o}){return(0,a.resolveLocale)({defaultLocale:t.defaultLocale,validLocales:t.locales,autoDetectLanguage:o,url:h(e,t),cookie:o?A(n,i):null,acceptLanguage:o?l({request:{headers:r}},`accept-language`):null})}function N(e,t,n,r,i,a=k,o,s=!0){return M(e,{defaultLocale:r,locales:i,strategy:j(o,i.map(e=>({code:e})),r)},{cookies:t,headers:n,localeCookie:a,autoDetect:s})}var P=!1;function F(){P||(P=!0,console.warn("[i18n-micro] i18nIntegration already installs the middleware: pass `middleware: false` to it to use your own createI18nMiddleware."))}function I(e){let{i18n:t,defaultLocale:r,localeObjects:a,autoDetect:o=!0}=e,l=e.localeCookie===void 0?k:e.localeCookie,u=a??e.locales.map(e=>({code:e})),d=s(e.base),f={defaultLocale:r,base:d,locales:(0,n.getEnabledLocaleCodes)(u).filter(t=>e.locales.includes(t)),strategy:j(e.routingStrategy,u,r)},p={path:d||`/`,maxAge:i.DEFAULT_COOKIE_MAX_AGE,sameSite:`lax`},m=l===null?`Accept-Language`:`Cookie, Accept-Language`,h=async(n,r)=>{if(n.locals.locale&&n.locals.i18n)return n.locals.i18nHandler&&n.locals.i18nHandler!==h&&F(),r();let i=g(n,f),{path:a,url:s,locale:b,partOfPage:x}=i,S=v(n,i),C=S&&_(n,i,f),w=S&&a===`/`,T=o&&w&&!C?M(a,f,{cookies:n.cookies,headers:n.request.headers,localeCookie:l,autoDetect:!0}):b,E=[x||C?`Referer`:``,o&&w?m:``].filter(Boolean).join(`, `)||null,D=T!==b||w&&e.redirectToDefault?R(T,f):a;if(D!==a)return z(n.redirect(c(D,d)+s.search,302),E);Object.assign(n.locals,L(t,i,f,u,h));let O=await r();return C&&l!==null&&y(n,O)&&n.cookies.set(l,b,p),z(O,E)};return h}function L(e,t,n,r,i){let{url:a,path:o,locale:s,route:c}=t,l=e.fork(s);l.setRoute(c);let u=Object.create(n.strategy);return u.getCurrentPath=()=>o,u.getRoute=()=>({fullPath:o+a.search,query:Object.fromEntries(a.searchParams)}),{i18n:l,locale:s,defaultLocale:n.defaultLocale,locales:r,currentUrl:a,routingStrategy:u,i18nBase:n.base,i18nHandler:i}}function R(e,{strategy:t,defaultLocale:n}){let r=t.resolvePath(`/`,e);return(typeof r==`string`?r:r.path)||(e===n?`/`:`/${e}`)}function z(e,t){if(t===null)return e;try{e.headers.append(`Vary`,t)}catch{}return e}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return x}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return O}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return I}});
@@ -0,0 +1,257 @@
1
+ import { externalLinkHref as e, pathEnd as t, pathSegments as n, urlPath as r, withoutDots as i } from "@i18n-micro/core";
2
+ import { I18n as a } from "@i18n-micro/runtime";
3
+ import { getEnabledLocaleCodes as o } from "@i18n-micro/utils/active-locales";
4
+ import { trimLeadingSlashes as s, trimTrailingSlashes as c, withoutAppBaseURL as l } from "@i18n-micro/utils/app-path";
5
+ import { DEFAULT_COOKIE_MAX_AGE as u } from "@i18n-micro/utils/cookie";
6
+ import { localeFromPath as d, resolveLocale as f } from "@i18n-micro/utils/resolve-locale";
7
+ // src/composer.ts
8
+ var p = class extends a {
9
+ constructor(e) {
10
+ super(e);
11
+ let t = e.parent;
12
+ this.initialMessages = t?.initialMessages ?? { ...e.messages }, this.initialRouteMessages = t?.initialRouteMessages ?? Object.fromEntries(Object.entries(e.routeMessages ?? {}).map(([e, t]) => [e, { ...t }]));
13
+ }
14
+ keepMessages(e, t) {
15
+ this.initialMessages = {
16
+ ...this.initialMessages,
17
+ ...e
18
+ };
19
+ let n = { ...this.initialRouteMessages };
20
+ for (let [e, r] of Object.entries(t)) n[e] = {
21
+ ...n[e],
22
+ ...r
23
+ };
24
+ this.initialRouteMessages = n;
25
+ }
26
+ getLocaleIso() {
27
+ return this.localeIso;
28
+ }
29
+ getRouteTranslations(e, t) {
30
+ return this.helper.getCache(e, t) ?? null;
31
+ }
32
+ mergeTranslations(e, t, n) {
33
+ this.addRouteTranslations(e, t, n);
34
+ }
35
+ clearCache() {
36
+ super.clearCache(), this.loadMessages(this.initialMessages, this.initialRouteMessages);
37
+ }
38
+ }, m = (e) => c(e ?? "");
39
+ function h(e, t) {
40
+ return t ? e === "/" ? t : `${t}${e}` : e;
41
+ }
42
+ function g(e, t) {
43
+ try {
44
+ return e.request.headers.get(t);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function _(e) {
50
+ let t = e.isPrerendered ? null : g(e, "referer");
51
+ if (!t) return null;
52
+ try {
53
+ return new URL(t);
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ function v(e, t) {
59
+ let n = g(e, "sec-fetch-site");
60
+ return n ? n === "same-origin" : t.host === e.url.host;
61
+ }
62
+ function y(e) {
63
+ let t = _(e);
64
+ return t && new URL(t.pathname + t.search, e.url);
65
+ }
66
+ var b = (e) => e?.startsWith("/_server-islands/") === !0 || e?.startsWith("/_actions/") === !0;
67
+ function x(e, t) {
68
+ let { routePattern: n, params: r } = e;
69
+ if (!n) return;
70
+ let i = /^\/\[([^\].]+)\]/.exec(n), a = i ? r?.[i[1]] : void 0;
71
+ if (!a) return n;
72
+ let o = n.slice(i[0].length);
73
+ return t.includes(a) ? `/${a}${o}` : i[1] === "locale" ? o || "/" : n;
74
+ }
75
+ function S(e, { defaultLocale: t, locales: n, strategy: r }) {
76
+ let i = d(e, n);
77
+ if (i) return i;
78
+ let a = r.getLocaleFromPath?.(e, t, n) ?? null;
79
+ return a !== t && a !== null && n.includes(a) ? a : null;
80
+ }
81
+ function C(e, t) {
82
+ let { strategy: n, locales: r, defaultLocale: i, base: a } = t, o = b(e.routePattern), s = o && y(e) || e.url, c = l(s.pathname, a), u = S(c, t), d = o ? void 0 : x(e, r);
83
+ return {
84
+ url: s,
85
+ partOfPage: o,
86
+ path: c,
87
+ urlLocale: u,
88
+ locale: u ?? i,
89
+ route: n.getRouteName(c, r, d)
90
+ };
91
+ }
92
+ function w(e, t, n) {
93
+ let r = _(e);
94
+ return !r || !v(e, r) ? !1 : (S(l(r.pathname, n.base), n) ?? n.defaultLocale) !== t.locale;
95
+ }
96
+ function T(e, t) {
97
+ let n = e.request.method ?? "GET";
98
+ return !t.partOfPage && !e.isPrerendered && (n === "GET" || n === "HEAD");
99
+ }
100
+ function E(e, t) {
101
+ return !t.ok || !t.headers.get("content-type")?.startsWith("text/html") ? !1 : !`${g(e, "sec-purpose")} ${g(e, "purpose")}`.includes("prefetch");
102
+ }
103
+ var D = /^[a-z][a-z\d+.-]*:/i;
104
+ function O(n, r, a) {
105
+ if (n === "" || n[0] === "#" || n[0] === "?") return n;
106
+ let o = e(n) ?? (D.test(n) ? n : void 0);
107
+ if (o) return o;
108
+ let { strategy: s, codes: u, defaultLocale: d, base: f, from: p = "/" } = a, m = t(n), g = m === -1 ? n : n.slice(0, m), _ = i(g[0] === "/" ? l(g, f) : `${c(p)}/${g}`), v = s.localizePath(_, r, u, d);
109
+ return `${h(v + (g.length > 1 && g.endsWith("/") && !v.endsWith("/") ? "/" : ""), f)}${m === -1 ? "" : n.slice(m)}`;
110
+ }
111
+ // src/router/adapter.ts
112
+ var k = /\[(?:\.\.\.)?([^\]]+)\]/g;
113
+ function A(e, t) {
114
+ let i = n(r(e));
115
+ return i[0] !== void 0 && t.includes(i[0]) && i.shift(), i;
116
+ }
117
+ var j = (e) => e.slice(r(e).length);
118
+ function M(e, t = []) {
119
+ let n = A(e, t);
120
+ if (n.length === 0) return "index";
121
+ let r = n.join("-");
122
+ return r.includes("[") ? r.replace(k, "$1") : r;
123
+ }
124
+ var N = (e, t = []) => `/${A(e, t).join("/")}${j(e)}`;
125
+ function P(e, t, n = [], r) {
126
+ let i = A(e, n);
127
+ return t !== r && i.unshift(t), `/${i.join("/")}${j(e)}`;
128
+ }
129
+ function F(e, t, n) {
130
+ let r = e.map((e) => e.code), i = () => n?.() ?? (typeof window < "u" ? new URL(window.location.href) : null);
131
+ return {
132
+ getCurrentPath: () => i()?.pathname ?? "/",
133
+ getRouteName: (e, t = [], n) => M(n ?? e, t),
134
+ getLocaleFromPath: (e, t = "en", n = []) => d(`/${s(e)}`, n) ?? t,
135
+ switchLocalePath: P,
136
+ localizePath: P,
137
+ removeLocaleFromPath: N,
138
+ resolvePath: (e, n) => P(typeof e == "string" ? e : e.path || "/", n, r, t),
139
+ getRoute: () => {
140
+ let e = i();
141
+ return e ? {
142
+ fullPath: e.pathname + e.search,
143
+ query: Object.fromEntries(e.searchParams)
144
+ } : {
145
+ fullPath: "/",
146
+ query: {}
147
+ };
148
+ },
149
+ push: (e) => {
150
+ typeof window < "u" && (window.location.href = e.path);
151
+ },
152
+ replace: (e) => {
153
+ typeof window < "u" && window.location.replace(e.path);
154
+ }
155
+ };
156
+ }
157
+ // src/middleware.ts
158
+ var I = "i18n-locale";
159
+ function L(e, t) {
160
+ if (t === null) return null;
161
+ try {
162
+ return e.get(t)?.value ?? null;
163
+ } catch {
164
+ return null;
165
+ }
166
+ }
167
+ function R(e, t, n) {
168
+ let r = F(t, n);
169
+ if (!e) return r;
170
+ let i = e, a = {};
171
+ for (let [t, n] of Object.entries(r)) {
172
+ let r = i[t];
173
+ a[t] = typeof r == "function" ? r.bind(e) : n;
174
+ }
175
+ return a;
176
+ }
177
+ function z(e, t, { cookies: n, headers: r, localeCookie: i, autoDetect: a }) {
178
+ return f({
179
+ defaultLocale: t.defaultLocale,
180
+ validLocales: t.locales,
181
+ autoDetectLanguage: a,
182
+ url: S(e, t),
183
+ cookie: a ? L(n, i) : null,
184
+ acceptLanguage: a ? g({ request: { headers: r } }, "accept-language") : null
185
+ });
186
+ }
187
+ function B(e, t, n, r, i, a = I, o, s = !0) {
188
+ return z(e, {
189
+ defaultLocale: r,
190
+ locales: i,
191
+ strategy: R(o, i.map((e) => ({ code: e })), r)
192
+ }, {
193
+ cookies: t,
194
+ headers: n,
195
+ localeCookie: a,
196
+ autoDetect: s
197
+ });
198
+ }
199
+ var V = !1;
200
+ function H() {
201
+ V || (V = !0, console.warn("[i18n-micro] i18nIntegration already installs the middleware: pass `middleware: false` to it to use your own createI18nMiddleware."));
202
+ }
203
+ function U(e) {
204
+ let { i18n: t, defaultLocale: n, localeObjects: r, autoDetect: i = !0 } = e, a = e.localeCookie === void 0 ? I : e.localeCookie, s = r ?? e.locales.map((e) => ({ code: e })), c = m(e.base), l = {
205
+ defaultLocale: n,
206
+ base: c,
207
+ locales: o(s).filter((t) => e.locales.includes(t)),
208
+ strategy: R(e.routingStrategy, s, n)
209
+ }, d = {
210
+ path: c || "/",
211
+ maxAge: u,
212
+ sameSite: "lax"
213
+ }, f = a === null ? "Accept-Language" : "Cookie, Accept-Language", p = async (n, r) => {
214
+ if (n.locals.locale && n.locals.i18n) return n.locals.i18nHandler && n.locals.i18nHandler !== p && H(), r();
215
+ let o = C(n, l), { path: u, url: m, locale: g, partOfPage: _ } = o, v = T(n, o), y = v && w(n, o, l), b = v && u === "/", x = i && b && !y ? z(u, l, {
216
+ cookies: n.cookies,
217
+ headers: n.request.headers,
218
+ localeCookie: a,
219
+ autoDetect: !0
220
+ }) : g, S = [_ || y ? "Referer" : "", i && b ? f : ""].filter(Boolean).join(", ") || null, D = x !== g || b && e.redirectToDefault ? G(x, l) : u;
221
+ if (D !== u) return K(n.redirect(h(D, c) + m.search, 302), S);
222
+ Object.assign(n.locals, W(t, o, l, s, p));
223
+ let O = await r();
224
+ return y && a !== null && E(n, O) && n.cookies.set(a, g, d), K(O, S);
225
+ };
226
+ return p;
227
+ }
228
+ function W(e, t, n, r, i) {
229
+ let { url: a, path: o, locale: s, route: c } = t, l = e.fork(s);
230
+ l.setRoute(c);
231
+ let u = Object.create(n.strategy);
232
+ return u.getCurrentPath = () => o, u.getRoute = () => ({
233
+ fullPath: o + a.search,
234
+ query: Object.fromEntries(a.searchParams)
235
+ }), {
236
+ i18n: l,
237
+ locale: s,
238
+ defaultLocale: n.defaultLocale,
239
+ locales: r,
240
+ currentUrl: a,
241
+ routingStrategy: u,
242
+ i18nBase: n.base,
243
+ i18nHandler: i
244
+ };
245
+ }
246
+ function G(e, { strategy: t, defaultLocale: n }) {
247
+ let r = t.resolvePath("/", e);
248
+ return (typeof r == "string" ? r : r.path) || (e === n ? "/" : `/${e}`);
249
+ }
250
+ function K(e, t) {
251
+ if (t === null) return e;
252
+ try {
253
+ e.headers.append("Vary", t);
254
+ } catch {}
255
+ return e;
256
+ }
257
+ export { h as a, O as i, B as n, p as o, F as r, U as t };
@@ -0,0 +1,34 @@
1
+ import { Locale } from '@i18n-micro/types';
2
+ import { MiddlewareHandler } from 'astro';
3
+ import { AstroI18n } from './composer.cjs';
4
+ import { I18nRoutingStrategy } from './router/types.cjs';
5
+ export interface I18nMiddlewareOptions {
6
+ i18n: AstroI18n;
7
+ defaultLocale: string;
8
+ locales: string[];
9
+ localeObjects?: Locale[];
10
+ /** `/` redirects to the locale the cookie / Accept-Language prefer (default `true`); pages keep the URL's locale. */
11
+ autoDetect?: boolean;
12
+ /**
13
+ * `/` also redirects when nothing prefers another locale: to the home `routingStrategy` resolves
14
+ * for the default locale (default `false`). Without one `/` stays: it is the default locale's page.
15
+ */
16
+ redirectToDefault?: boolean;
17
+ routingStrategy?: I18nRoutingStrategy;
18
+ /** Cookie holding the visitor's locale (default `'i18n-locale'`); `null` disables it. */
19
+ localeCookie?: string | null;
20
+ /** Astro's `base`, `config.base` of the virtual config. */
21
+ base?: string;
22
+ }
23
+ export declare const DEFAULT_ASTRO_LOCALE_COOKIE = "i18n-locale";
24
+ /**
25
+ * Locale a visitor prefers: URL prefix → cookie → Accept-Language → default, the one precedence of
26
+ * `resolveLocale`. An explicit prefix always wins; `autoDetect: false` leaves only the URL.
27
+ */
28
+ export declare function detectLocale(pathname: string, cookies: {
29
+ get: (name: string) => {
30
+ value: string;
31
+ } | undefined;
32
+ }, headers: Headers, defaultLocale: string, locales: string[], localeCookie?: string | null, routingStrategy?: I18nRoutingStrategy | null, autoDetect?: boolean): string;
33
+ /** Create Astro middleware for i18n locale detection */
34
+ export declare function createI18nMiddleware(options: I18nMiddlewareOptions): MiddlewareHandler;
@@ -1,25 +1,34 @@
1
1
  import { Locale } from '@i18n-micro/types';
2
2
  import { MiddlewareHandler } from 'astro';
3
- import { AstroI18n } from './composer';
4
- import { I18nRoutingStrategy } from './router/types';
3
+ import { AstroI18n } from './composer.js';
4
+ import { I18nRoutingStrategy } from './router/types.js';
5
5
  export interface I18nMiddlewareOptions {
6
6
  i18n: AstroI18n;
7
7
  defaultLocale: string;
8
8
  locales: string[];
9
9
  localeObjects?: Locale[];
10
+ /** `/` redirects to the locale the cookie / Accept-Language prefer (default `true`); pages keep the URL's locale. */
10
11
  autoDetect?: boolean;
12
+ /**
13
+ * `/` also redirects when nothing prefers another locale: to the home `routingStrategy` resolves
14
+ * for the default locale (default `false`). Without one `/` stays: it is the default locale's page.
15
+ */
11
16
  redirectToDefault?: boolean;
12
17
  routingStrategy?: I18nRoutingStrategy;
18
+ /** Cookie holding the visitor's locale (default `'i18n-locale'`); `null` disables it. */
19
+ localeCookie?: string | null;
20
+ /** Astro's `base`, `config.base` of the virtual config. */
21
+ base?: string;
13
22
  }
23
+ export declare const DEFAULT_ASTRO_LOCALE_COOKIE = "i18n-locale";
14
24
  /**
15
- * Create Astro middleware for i18n locale detection
16
- */
17
- export declare function createI18nMiddleware(options: I18nMiddlewareOptions): MiddlewareHandler;
18
- /**
19
- * Detect locale from request
25
+ * Locale a visitor prefers: URL prefix → cookie → Accept-Language → default, the one precedence of
26
+ * `resolveLocale`. An explicit prefix always wins; `autoDetect: false` leaves only the URL.
20
27
  */
21
28
  export declare function detectLocale(pathname: string, cookies: {
22
29
  get: (name: string) => {
23
30
  value: string;
24
31
  } | undefined;
25
- }, headers: Headers, defaultLocale: string, locales: string[], localeCookie?: string | null, routingStrategy?: I18nRoutingStrategy | null): string;
32
+ }, headers: Headers, defaultLocale: string, locales: string[], localeCookie?: string | null, routingStrategy?: I18nRoutingStrategy | null, autoDetect?: boolean): string;
33
+ /** Create Astro middleware for i18n locale detection */
34
+ export declare function createI18nMiddleware(options: I18nMiddlewareOptions): MiddlewareHandler;
@@ -0,0 +1,23 @@
1
+ import { o as e, t } from "./middleware-Ch8E0iV_.js";
2
+ import { localeIsoMap as n } from "@i18n-micro/core";
3
+ import { config as r } from "virtual:i18n-micro/config";
4
+ import { messages as i } from "virtual:i18n-micro/messages";
5
+ // src/auto-middleware.ts
6
+ var a = new e({
7
+ locale: r.defaultLocale,
8
+ fallbackLocale: r.fallbackLocale,
9
+ missingWarn: r.missingWarn,
10
+ localeIso: n(r.locales),
11
+ messages: i.root,
12
+ routeMessages: i.routes
13
+ }), o = t({
14
+ i18n: a,
15
+ defaultLocale: r.defaultLocale,
16
+ locales: r.localeCodes,
17
+ localeObjects: r.locales,
18
+ autoDetect: r.autoDetect,
19
+ redirectToDefault: r.redirectToDefault,
20
+ localeCookie: r.localeCookie,
21
+ base: r.base
22
+ });
23
+ export { o as onRequest };
package/dist/node.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { Translations } from '@i18n-micro/types';
2
+ import { TranslationFileBuckets } from '@i18n-micro/utils/parse-path';
3
+ export interface LoadTranslationsOptions {
4
+ translationDir: string;
5
+ rootDir?: string;
6
+ disablePageLocales?: boolean;
7
+ }
8
+ export interface LoadedTranslations extends TranslationFileBuckets<Translations> {
9
+ }
10
+ /** Load all translations from a directory: flat (`en.json`) and nested (`pages/home/en.json`). */
11
+ export declare function loadTranslationsFromDir(options: LoadTranslationsOptions): LoadedTranslations;
12
+ /** The directory's dictionaries and the files they came from, for the integration's build. */
13
+ export declare function readTranslationDir(options: {
14
+ translationDir: string;
15
+ rootDir: URL;
16
+ disablePageLocales?: boolean;
17
+ }): {
18
+ messages: TranslationFileBuckets<Translations>;
19
+ files: string[];
20
+ };
21
+ /** Load a directory into an instance, replacing the chunks it finds. */
22
+ export declare function loadTranslationsIntoI18n(i18n: {
23
+ addTranslations: (locale: string, translations: Translations, merge?: boolean) => void;
24
+ addRouteTranslations: (locale: string, routeName: string, translations: Translations, merge?: boolean) => void;
25
+ keepMessages?: (messages: Record<string, Translations>, routeMessages: Record<string, Record<string, Translations>>) => void;
26
+ }, options: LoadTranslationsOptions): void;
package/dist/node.js ADDED
@@ -0,0 +1,22 @@
1
+ import { resolve as e } from "node:path";
2
+ import { fileURLToPath as t } from "node:url";
3
+ import { listTranslationFiles as n, loadTranslationDir as r, loadTranslationFiles as i } from "@i18n-micro/utils/load-dir";
4
+ // src/node.ts
5
+ function a(t) {
6
+ let { translationDir: n, rootDir: i = process.cwd(), disablePageLocales: a = !1 } = t;
7
+ return r(e(i, n), a);
8
+ }
9
+ function o(r) {
10
+ let a = e(t(r.rootDir), r.translationDir), o = n(a);
11
+ return {
12
+ messages: i(o, r.disablePageLocales),
13
+ files: [a, ...o.map((e) => e.absolutePath)]
14
+ };
15
+ }
16
+ function s(e, t) {
17
+ let { root: n, routes: r } = a(t);
18
+ for (let [t, r] of Object.entries(n)) e.addTranslations(t, r, !1);
19
+ for (let [t, n] of Object.entries(r)) for (let [r, i] of Object.entries(n)) e.addRouteTranslations(r, t, i, !1);
20
+ e.keepMessages?.(n, r);
21
+ }
22
+ export { a as loadTranslationsFromDir, s as loadTranslationsIntoI18n, o as readTranslationDir };
@@ -0,0 +1,69 @@
1
+ import { I18nRoutingStrategy } from './router/types.js';
2
+ /**
3
+ * The one request model. Everything below works on the page's path without Astro's `base`:
4
+ * the middleware strips it once, every URL that goes out gets it back from {@link withBase}.
5
+ */
6
+ export interface I18nRouting {
7
+ defaultLocale: string;
8
+ /** Codes of the locales in service: a disabled one is neither a URL's locale nor a redirect target. */
9
+ locales: string[];
10
+ /** `''` or `/docs`, no trailing slash. */
11
+ base: string;
12
+ strategy: I18nRoutingStrategy;
13
+ }
14
+ /** The parts of an Astro context a request is read from: also a mock in tests. */
15
+ export interface RequestContext {
16
+ url: URL;
17
+ params?: Record<string, string | undefined>;
18
+ routePattern?: string;
19
+ isPrerendered?: boolean;
20
+ request: {
21
+ headers: Headers;
22
+ method?: string;
23
+ };
24
+ }
25
+ export interface I18nRequest {
26
+ /** The page this request is for: another URL than `context.url` for a server island or action. */
27
+ url: URL;
28
+ /** A server island or action: it belongs to the page in `url`, and is no page view itself. */
29
+ partOfPage: boolean;
30
+ /** `url.pathname` without `base`. */
31
+ path: string;
32
+ /** The locale `url` names, `null` for an unprefixed URL. */
33
+ urlLocale: string | null;
34
+ /** The URL's locale, else the default one: an unprefixed URL is the default locale's page. */
35
+ locale: string;
36
+ route: string;
37
+ }
38
+ export declare const normalizeBase: (base: string | undefined) => string;
39
+ /** `path`, which has no `base`, as a URL of the site. */
40
+ export declare function withBase(path: string, base: string): string;
41
+ /** A request header, or null where there is none (static prerendering). */
42
+ export declare function header(context: Pick<RequestContext, 'request'>, name: string): string | null;
43
+ /**
44
+ * Locale a path names: its prefix, or what the strategy reads from it when that is not the default
45
+ * (a strategy's `getLocaleFromPath` answers the default for a URL without a locale).
46
+ */
47
+ export declare function localeOfPath(path: string, { defaultLocale, locales, strategy }: Omit<I18nRouting, 'base'>): string | null;
48
+ export declare function resolveRequest(context: RequestContext, routing: I18nRouting): I18nRequest;
49
+ /** Whether the visitor comes from a page of this site in another locale. */
50
+ export declare function isSwitch(context: RequestContext, request: I18nRequest, routing: I18nRouting): boolean;
51
+ /** A document may be asked for by this request: an island, an action or a form post is no landing. */
52
+ export declare function asksForPage(context: RequestContext, request: I18nRequest): boolean;
53
+ /**
54
+ * The visitor did see this response as a page: no endpoint, image, 404 or redirect, and not a
55
+ * prefetch of a link they may never follow.
56
+ * ponytail: a `fetch` of an HTML fragment from a page of another locale still counts; `<ClientRouter>`
57
+ * navigates by `fetch` too, and the two look the same from here.
58
+ */
59
+ export declare function isPageView(context: RequestContext, response: Response): boolean;
60
+ export interface HrefContext extends Pick<I18nRouting, 'base' | 'defaultLocale' | 'strategy'> {
61
+ codes: string[];
62
+ /** What a relative `href` resolves against: the page's path. */
63
+ from?: string;
64
+ }
65
+ /**
66
+ * `href` as a link to `locale`. What leaves the app, and a bare `#hash` or `?query`, stays as
67
+ * written; a route is localized and keeps its query, hash and trailing slash.
68
+ */
69
+ export declare function localizeHref(href: string, locale: string, context: HrefContext): string;
@@ -0,0 +1,9 @@
1
+ import { Locale } from '@i18n-micro/types';
2
+ import { I18nRoutingStrategy } from './types.cjs';
3
+ /** `/en/blog/post` → `blog-post`, `/blog/[slug]` → `blog-slug`, `/` → `index`. */
4
+ export declare function routeNameFromPath(path: string, locales?: readonly string[]): string;
5
+ /**
6
+ * Prefix-except-default routing over plain paths. Paths here never carry Astro's `base`: the
7
+ * middleware strips it from what comes in and adds it to what goes out.
8
+ */
9
+ export declare function createAstroRouterAdapter(locales: Locale[], defaultLocale: string, getCurrentUrl?: () => URL): I18nRoutingStrategy;
@@ -1,8 +1,9 @@
1
1
  import { Locale } from '@i18n-micro/types';
2
- import { I18nRoutingStrategy } from './types';
2
+ import { I18nRoutingStrategy } from './types.js';
3
+ /** `/en/blog/post` → `blog-post`, `/blog/[slug]` → `blog-slug`, `/` → `index`. */
4
+ export declare function routeNameFromPath(path: string, locales?: readonly string[]): string;
3
5
  /**
4
- * Factory for Astro router adapter
5
- * Implements routing utilities for Astro file-based routing
6
- * Uses standard Astro APIs: Astro.url, context.url
6
+ * Prefix-except-default routing over plain paths. Paths here never carry Astro's `base`: the
7
+ * middleware strips it from what comes in and adds it to what goes out.
7
8
  */
8
9
  export declare function createAstroRouterAdapter(locales: Locale[], defaultLocale: string, getCurrentUrl?: () => URL): I18nRoutingStrategy;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Routing strategy interface for i18n in Astro
3
+ * Implement this interface to integrate with any routing logic
4
+ * Matches Vue/Solid/React package interface for consistency
5
+ * Adapted for Astro SSR context (push/replace are optional)
6
+ */
7
+ export interface I18nRoutingStrategy {
8
+ /**
9
+ * Returns current path (without locale prefix if needed, or full path)
10
+ * Used for determining active classes in links
11
+ */
12
+ getCurrentPath: () => string;
13
+ /**
14
+ * Generate path for specific locale
15
+ */
16
+ resolvePath?: (to: string | {
17
+ path?: string;
18
+ }, locale: string) => string | {
19
+ path?: string;
20
+ };
21
+ /**
22
+ * Get route name from path (e.g., /en/about -> about)
23
+ * Used in middleware to set route name. `routePattern` is the page's pattern (`/blog/[slug]`), with
24
+ * a `[locale]` param already replaced by its value: use it to give every URL of a page one name.
25
+ */
26
+ getRouteName?: (path: string, locales: string[], routePattern?: string) => string;
27
+ /**
28
+ * Get locale from path
29
+ * Checks if first segment is a locale code
30
+ */
31
+ getLocaleFromPath?: (path: string, defaultLocale: string, locales: string[]) => string;
32
+ /**
33
+ * Switch locale in path
34
+ * Replaces or adds locale prefix to path
35
+ */
36
+ switchLocalePath?: (path: string, newLocale: string, locales: string[], defaultLocale?: string) => string;
37
+ /**
38
+ * Localize path with locale prefix
39
+ */
40
+ localizePath?: (path: string, locale: string, locales: string[], defaultLocale?: string) => string;
41
+ /**
42
+ * Remove locale from path
43
+ */
44
+ removeLocaleFromPath?: (path: string, locales: string[]) => string;
45
+ /**
46
+ * (Optional) Function to navigate to another route/locale
47
+ * Not used in SSR, but can be used in client-side islands
48
+ */
49
+ push?: (target: {
50
+ path: string;
51
+ }) => void;
52
+ /**
53
+ * (Optional) Function to replace current route
54
+ * Not used in SSR, but can be used in client-side islands
55
+ */
56
+ replace?: (target: {
57
+ path: string;
58
+ }) => void;
59
+ /**
60
+ * (Optional) Get current route object for SEO/Meta tags
61
+ */
62
+ getRoute?: () => {
63
+ fullPath: string;
64
+ query: Record<string, unknown>;
65
+ };
66
+ }