@i18n-micro/astro 1.3.7 → 1.3.8

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.
@@ -1,4 +1,4 @@
1
- import { BaseI18n, TranslationStorage } from '@i18n-micro/core';
1
+ import { BaseI18n, DateTimeFormatsConfig, NumberFormatsConfig, TranslationStorage } from '@i18n-micro/core';
2
2
  import { PluralFunc, Translations } from '@i18n-micro/types';
3
3
  export interface AstroI18nOptions {
4
4
  locale: string;
@@ -7,6 +7,8 @@ export interface AstroI18nOptions {
7
7
  plural?: PluralFunc;
8
8
  missingWarn?: boolean;
9
9
  missingHandler?: (locale: string, key: string, routeName: string) => void;
10
+ numberFormats?: NumberFormatsConfig;
11
+ datetimeFormats?: DateTimeFormatsConfig;
10
12
  _storage?: TranslationStorage;
11
13
  }
12
14
  export declare class AstroI18n extends BaseI18n {
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("@i18n-micro/core"),w=require("node:fs"),T=require("node:path"),C=require("@i18n-micro/utils/parse-path"),W=require("@i18n-micro/utils/accept-language");class j extends R.BaseI18n{constructor(e){const a=e._storage||{translations:new Map};if(super({storage:a,plural:e.plural,missingWarn:e.missingWarn,missingHandler:e.missingHandler}),this.initialMessages={},this.storage=a,this._locale=e.locale,this._fallbackLocale=e.fallbackLocale||e.locale,this._currentRoute="index",e.messages){this.initialMessages={...e.messages};for(const[r,i]of Object.entries(e.messages))this.helper.loadTranslations(r,i)}}cloneStorage(e){const a=new Map;for(const[r,i]of e.translations)a.set(r,{...i});return{translations:a}}clone(e){const a=this.cloneStorage(this.storage);return new j({locale:e||this._locale,fallbackLocale:this._fallbackLocale,plural:this.pluralFunc,missingWarn:this.missingWarn,missingHandler:this.missingHandler,_storage:a})}get locale(){return this._locale}set locale(e){this._locale=e}get fallbackLocale(){return this._fallbackLocale}set fallbackLocale(e){this._fallbackLocale=e}setRoute(e){this._currentRoute=e}getLocale(){return this._locale}getFallbackLocale(){return this._fallbackLocale}getRoute(){return this._currentRoute}getRouteTranslations(e,a){const r=`${e}:${a}`;return this.storage.translations.get(r)??null}addTranslations(e,a,r=!0){super.loadTranslationsCore(e,a,r)}addRouteTranslations(e,a,r,i=!0){super.loadRouteTranslationsCore(e,a,r,i)}mergeTranslations(e,a,r){this.helper.mergeTranslation(e,a,r,!0)}clearCache(){const e={...this.initialMessages};if(super.clearCache(),Object.keys(e).length>0)for(const[a,r]of Object.entries(e))this.helper.loadTranslations(a,r)}}let B=null;function N(){return B}function U(t){const{locale:e,fallbackLocale:a,translationDir:r,routingStrategy:i}=t;return B=i||null,{name:"@i18n-micro/astro",hooks:{"astro:config:setup":c=>{const{updateConfig:u}=c,o="virtual:i18n-micro/config",l=`\0${o}`,s={defaultLocale:e,fallbackLocale:a||e,locales:t.locales||[],localeCodes:(t.locales||[]).map(n=>n.code),translationDir:r||null,autoDetect:t.autoDetect??!0,redirectToDefault:t.redirectToDefault??!1,localeCookie:t.localeCookie===null?null:t.localeCookie||"i18n-locale",missingWarn:t.missingWarn??!1};u({vite:{plugins:[{name:"vite-plugin-i18n-micro-config",resolveId(n){if(n===o)return l},load(n){if(n===l)return`export const config = ${JSON.stringify(s)}`}}]}})},"astro:config:done":c=>{const{injectTypes:u}=c;u({filename:"i18n-micro-env.d.ts",content:`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const T=require("@i18n-micro/core"),E=require("node:async_hooks"),w=require("node:fs"),R=require("node:path"),B=require("@i18n-micro/utils/parse-path"),z=require("@i18n-micro/utils/accept-language");class j extends T.BaseI18n{constructor(e){const o=e._storage||{translations:new Map};if(super({storage:o,plural:e.plural,missingWarn:e.missingWarn,missingHandler:e.missingHandler,numberFormats:e.numberFormats,datetimeFormats:e.datetimeFormats}),this.initialMessages={},this.storage=o,this._locale=e.locale,this._fallbackLocale=e.fallbackLocale||e.locale,this._currentRoute="index",e.messages){this.initialMessages={...e.messages};for(const[a,i]of Object.entries(e.messages))this.helper.loadTranslations(a,i)}}cloneStorage(e){const o=new Map;for(const[a,i]of e.translations)o.set(a,{...i});return{translations:o}}clone(e){const o=this.cloneStorage(this.storage);return new j({locale:e||this._locale,fallbackLocale:this._fallbackLocale,plural:this.pluralFunc,missingWarn:this.missingWarn,missingHandler:this.missingHandler,numberFormats:this.formatter.getNumberFormats(),datetimeFormats:this.formatter.getDateTimeFormats(),_storage:o})}get locale(){return this._locale}set locale(e){this._locale=e}get fallbackLocale(){return this._fallbackLocale}set fallbackLocale(e){this._fallbackLocale=e}setRoute(e){this._currentRoute=e}getLocale(){return this._locale}getFallbackLocale(){return this._fallbackLocale}getRoute(){return this._currentRoute}getRouteTranslations(e,o){const a=`${e}:${o}`;return this.storage.translations.get(a)??null}addTranslations(e,o,a=!0){super.loadTranslationsCore(e,o,a)}addRouteTranslations(e,o,a,i=!0){super.loadRouteTranslationsCore(e,o,a,i)}mergeTranslations(e,o,a){this.helper.mergeTranslation(e,o,a,!0)}clearCache(){const e={...this.initialMessages};if(super.clearCache(),Object.keys(e).length>0)for(const[o,a]of Object.entries(e))this.helper.loadTranslations(o,a)}}let N=null;const q=new E.AsyncLocalStorage;function D(){const t=q.getStore();return t!==void 0?t:N}function A(t){N=t}function M(t,e){return q.run(t,e)}function x(t){const{locale:e,fallbackLocale:o,translationDir:a,routingStrategy:i}=t;return A(i||null),{name:"@i18n-micro/astro",hooks:{"astro:config:setup":c=>{const{updateConfig:u}=c,s="virtual:i18n-micro/config",r=`\0${s}`,l={defaultLocale:e,fallbackLocale:o||e,locales:t.locales||[],localeCodes:(t.locales||[]).map(n=>n.code),translationDir:a||null,autoDetect:t.autoDetect??!0,redirectToDefault:t.redirectToDefault??!1,localeCookie:t.localeCookie===null?null:t.localeCookie||"i18n-locale",missingWarn:t.missingWarn??!1};u({vite:{plugins:[{name:"vite-plugin-i18n-micro-config",resolveId(n){if(n===s)return r},load(n){if(n===r)return`export const config = ${JSON.stringify(l)}`}}]}})},"astro:config:done":c=>{const{injectTypes:u}=c;u({filename:"i18n-micro-env.d.ts",content:`
2
2
  /// <reference types="@i18n-micro/astro/env" />
3
3
 
4
4
  declare module 'virtual:i18n-micro/config' {
@@ -14,4 +14,4 @@
14
14
  missingWarn: boolean | null;
15
15
  }
16
16
  }
17
- `})}}}}function H(t){return new j(t)}function A(t,e){if(w.existsSync(t))for(const a of w.readdirSync(t)){const r=T.join(t,a);if(w.statSync(r).isDirectory()){A(r,e);continue}a.endsWith(".json")&&e(r)}}function M(t){const{translationDir:e,rootDir:a=process.cwd(),disablePageLocales:r=!1}=t,i=T.resolve(a,e);if(!w.existsSync(i))return console.warn(`[i18n] Translation directory not found: ${i}`),{root:{},routes:{}};const c={root:{},routes:{}};return A(i,u=>{const o=T.relative(i,u).split(T.sep).join("/");try{const l=w.readFileSync(u,"utf-8"),s=JSON.parse(l);C.storeLoadedTranslationFile(c,o,s,r)}catch(l){console.error(`[i18n] Failed to load translation file: ${u}`,l)}}),c}function E(t,e){const{root:a,routes:r}=M(e);for(const[i,c]of Object.entries(a))t.addTranslations(i,c,!1);for(const[i,c]of Object.entries(r))for(const[u,o]of Object.entries(c))t.addRouteTranslations(u,i,C.mergeRouteTranslationsWithRoot(a[u],o),!1)}function z(t){const{i18n:e,defaultLocale:a,locales:r,localeObjects:i,autoDetect:c=!0,redirectToDefault:u=!1,routingStrategy:o}=t,l=o||N();return async(s,n)=>{if(s.locals.locale&&s.locals.i18n)return n();const f=s.url,d=f.pathname;if(!l){const L=e.clone(a),y=d==="/"||d===""?"index":d.split("/").filter(Boolean).join("-");return L.setRoute(y),s.locals.i18n=L,s.locals.locale=a,s.locals.defaultLocale=a,s.locals.locales=i||r.map(S=>({code:S})),s.locals.currentUrl=f,n()}const m={...l,getCurrentPath:()=>d,getRoute:()=>({fullPath:f.pathname+f.search,query:Object.fromEntries(f.searchParams)})},p=d.split("/").filter(Boolean)[0],b=p!==void 0&&r.includes(p);let P;b&&p?P=p:m.getLocaleFromPath?P=m.getLocaleFromPath(d,a,r):P=a;const k=e.clone(P),g=m.getRouteName?m.getRouteName(d,r):"index";return k.setRoute(g),s.locals.i18n=k,s.locals.locale=P,s.locals.defaultLocale=a,s.locals.locales=i||r.map(L=>({code:L})),s.locals.currentUrl=f,s.locals.routingStrategy=m,n()}}function x(t,e,a,r,i,c="i18n-locale"){const u=N();let o=r;if(u?.getLocaleFromPath)o=u.getLocaleFromPath(t,r,i);else{const s=t.split("/").filter(Boolean)[0];s&&i.includes(s)&&(o=s)}if(c!==null&&o===r&&e.get(c)){const l=e.get(c)?.value;l&&i.includes(l)&&(o=l)}if(o===r)try{const l=W.detectLocaleFromAcceptLanguage(a.get("accept-language")??void 0,i);l&&(o=l)}catch{}return o}function V(t,e,a){const r=t.map(n=>n.code),i=(n,f=[])=>{const d=n.replace(/^\//,"").replace(/\/$/,"");if(!d)return"index";const m=d.split("/").filter(Boolean),h=m[0];return h&&f.includes(h)&&m.shift(),m.length===0?"index":m.join("-")},c=(n,f="en",d=[])=>{const h=n.split("/").filter(Boolean)[0];return h&&d.includes(h)?h:f},u=(n,f,d=[],m)=>{const h=n.split("/").filter(Boolean),p=h[0];return p&&d.includes(p)&&h.shift(),(f!==m||m===void 0)&&h.unshift(f),`/${h.join("/")}`},o=(n,f,d=[],m)=>{const p=(n.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),b=p[0];return b&&d.includes(b)&&p.shift(),(f!==m||m===void 0)&&p.unshift(f),`/${p.join("/")}`};return{getCurrentPath:()=>a?a().pathname:typeof window<"u"?window.location.pathname:"/",getRouteName:i,getLocaleFromPath:c,switchLocalePath:u,localizePath:o,removeLocaleFromPath:(n,f=[])=>{const d=n.split("/").filter(Boolean),m=d[0];return m&&f.includes(m)&&d.shift(),`/${d.join("/")}`},resolvePath:(n,f)=>{const d=typeof n=="string"?n:n.path||"/";return o(d,f,r,e)},getRoute:()=>{if(a){const n=a();return{fullPath:n.pathname+n.search,query:Object.fromEntries(n.searchParams)}}if(typeof window<"u"){const n=new URL(window.location.href);return{fullPath:n.pathname+n.search,query:Object.fromEntries(n.searchParams)}}return{fullPath:"/",query:{}}},push:n=>{typeof window<"u"&&(window.location.href=n.path)},replace:n=>{typeof window<"u"&&window.location.replace(n.path)}}}function D(t){const e=t.og?.trim();if(e)return e;const a=t.iso?.trim();if(!a)return null;const r=a.indexOf("_");if(r>0){const c=a.slice(0,r),u=a.slice(r+1);if(c.length>=2&&u.length===2)return`${c.toLowerCase()}_${u.toUpperCase()}`}const i=a.indexOf("-");if(i>0){const c=a.slice(0,i),u=a.slice(i+1);if(c.length>=2&&u.length===2&&/^[A-Za-z]{2}$/.test(u))return`${c.toLowerCase()}_${u.toUpperCase()}`}return null}function F(t,e={}){if(D(t)!==null||e.missingWarn===!1||process.env.NODE_ENV==="production")return;const a=e.tag??"og:locale",r=String(t.code??"unknown"),i=t.iso?`, iso: '${t.iso}'`:"";console.warn(`[i18n] Cannot derive ${a} for locale '${r}'${i}. Set locale.og (e.g. 'en_US') or use iso with a 2-letter region (e.g. 'en-US').`)}function I(t){const e=t.locals.i18n;if(!e)throw new Error("i18n instance not found. Make sure i18n middleware is configured.");return e}function $(t){return t.locals.locale||"en"}function v(t){return t.locals.defaultLocale||"en"}function O(t){return t.locals.locales||[]}function q(t){return t.locals.routingStrategy||null}function J(t){const e=I(t),a=$(t),r=v(t),i=O(t),c=i.map(o=>o.code),u=q(t);return{locale:a,defaultLocale:r,locales:i,t:(o,l,s,n)=>e.t(o,l,s,n),ts:(o,l,s,n)=>e.ts(o,l,s,n),tc:(o,l,s)=>e.tc(o,l,s),tn:(o,l)=>e.tn(o,l),td:(o,l)=>e.td(o,l),tdr:(o,l)=>e.tdr(o,l),has:(o,l)=>e.has(o,l),getRoute:()=>e.getRoute(),getRouteName:o=>{const l=o||t.url.pathname;if(u?.getRouteName)return u.getRouteName(l,c);const s=l.replace(/^\//,"").replace(/\/$/,"");if(!s)return"index";const n=s.split("/").filter(Boolean),f=n[0];return f&&c.includes(f)&&n.shift(),n.length===0?"index":n.join("-")},getLocaleFromPath:o=>{const l=o||t.url.pathname;if(u?.getLocaleFromPath)return u.getLocaleFromPath(l,r,c);const n=l.split("/").filter(Boolean)[0];return n&&c.includes(n)?n:r},switchLocalePath:o=>{if(u?.switchLocalePath)return u.switchLocalePath(t.url.pathname,o,c,r);const l=t.url.pathname.split("/").filter(Boolean),s=l[0];return s&&c.includes(s)&&l.shift(),o!==r&&l.unshift(o),`/${l.join("/")}`},localizePath:(o,l)=>{if(u?.localizePath)return u.localizePath(o,l||a,c,r);const n=(o.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),f=n[0];return f&&c.includes(f)&&n.shift(),l&&l!==r&&n.unshift(l),`/${n.join("/")}`},getI18n:()=>e,getBasePath:o=>{const n=(o||t.url).pathname.split("/").filter(Boolean),f=n[0];return f&&c.includes(f)&&n.shift(),n.length>0?`/${n.join("/")}`:"/"},addTranslations:(o,l,s=!0)=>{e.addTranslations(o,l,s)},addRouteTranslations:(o,l,s,n=!0)=>{e.addRouteTranslations(o,l,s,n)},mergeTranslations:(o,l,s)=>{e.mergeTranslations(o,l,s)},clearCache:()=>{e.clearCache()}}}function G(t,e={}){const{baseUrl:a="/",addDirAttribute:r=!0,addSeoAttributes:i=!0}=e,c=$(t),u=v(t),l=O(t).filter(g=>!g.disabled),s=l.filter(g=>g.seo!==!1),n=l.find(g=>g.code===c);if(!n)return{htmlAttrs:{},link:[],meta:[]};const f=n.iso||c,d=D(n),m=n.dir||"auto",h={htmlAttrs:{lang:f,...r?{dir:m}:{}},link:[],meta:[]};if(!i)return h;const p=`${a}${t.url.pathname}`;h.link.push({rel:"canonical",href:p});const b=q(t),P=l.map(g=>g.code);for(const g of s){let L=t.url.pathname;if(b?.switchLocalePath)L=b.switchLocalePath(t.url.pathname,g.code,P,u);else{const S=t.url.pathname.split("/").filter(Boolean),_=S[0];_&&P.includes(_)&&S.shift(),g.code!==u&&S.unshift(g.code),L=`/${S.join("/")}`}const y=`${a}${L}`;h.link.push({rel:"alternate",href:y,hreflang:g.code}),g.iso&&g.iso!==g.code&&h.link.push({rel:"alternate",href:y,hreflang:g.iso})}if(l.find(g=>g.code===u)?.seo!==!1){let g=t.url.pathname;if(b?.switchLocalePath)g=b.switchLocalePath(t.url.pathname,u,P,u);else{const L=t.url.pathname.split("/").filter(Boolean),y=L[0];y&&P.includes(y)&&L.shift(),g=`/${L.join("/")}`}h.link.push({rel:"alternate",href:`${a}${g}`,hreflang:"x-default"})}d?h.meta.push({property:"og:locale",content:d}):F(n,{tag:"og:locale"}),h.meta.push({property:"og:url",content:p});for(const g of s){if(g.code===c)continue;const L=D(g);if(!L){F(g,{tag:"og:locale:alternate"});continue}h.meta.push({property:"og:locale:alternate",content:L})}return h}function K(t,e,a){const r=e.split(".");let i=t;for(let u=0;u<r.length-1;u++){const o=r[u];i[o]||(i[o]={}),i=i[o]}const c=r[r.length-1];c!==void 0&&(i[c]=a)}function Z(t,e){const a=I(t),r=$(t),i=v(t),c=a.getRoute(),u={};if(e&&e.length>0){const o={};for(const l of e){const s=a.t(l,void 0,void 0,c);s!=null&&s!==l&&K(o,l,s)}Object.keys(o).length>0&&(u[c]=o)}else{const o=a.getRouteTranslations(r,c);o&&(u[c]=o)}return{locale:r,fallbackLocale:i,currentRoute:c,translations:u}}Object.defineProperty(exports,"FormatService",{enumerable:!0,get:()=>R.FormatService});Object.defineProperty(exports,"defaultPlural",{enumerable:!0,get:()=>R.defaultPlural});Object.defineProperty(exports,"interpolate",{enumerable:!0,get:()=>R.interpolate});exports.AstroI18n=j;exports.createAstroRouterAdapter=V;exports.createI18n=H;exports.createI18nMiddleware=z;exports.detectLocale=x;exports.getDefaultLocale=v;exports.getI18n=I;exports.getI18nProps=Z;exports.getLocale=$;exports.getLocales=O;exports.i18nIntegration=U;exports.loadTranslationsFromDir=M;exports.loadTranslationsIntoI18n=E;exports.useI18n=J;exports.useLocaleHead=G;
17
+ `})}}}}function G(t){return new j(t)}function W(t,e){if(w.existsSync(t))for(const o of w.readdirSync(t)){const a=R.join(t,o);if(w.statSync(a).isDirectory()){W(a,e);continue}o.endsWith(".json")&&e(a)}}function U(t){const{translationDir:e,rootDir:o=process.cwd(),disablePageLocales:a=!1}=t,i=R.resolve(o,e);if(!w.existsSync(i))return console.warn(`[i18n] Translation directory not found: ${i}`),{root:{},routes:{}};const c={root:{},routes:{}};return W(i,u=>{const s=R.relative(i,u).split(R.sep).join("/");try{const r=w.readFileSync(u,"utf-8"),l=JSON.parse(r);B.storeLoadedTranslationFile(c,s,l,a)}catch(r){console.error(`[i18n] Failed to load translation file: ${u}`,r)}}),c}function V(t,e){const{root:o,routes:a}=U(e);for(const[i,c]of Object.entries(o))t.addTranslations(i,c,!1);for(const[i,c]of Object.entries(a))for(const[u,s]of Object.entries(c))t.addRouteTranslations(u,i,B.mergeRouteTranslationsWithRoot(o[u],s),!1)}function J(t){const{i18n:e,defaultLocale:o,locales:a,localeObjects:i,autoDetect:c=!0,redirectToDefault:u=!1,routingStrategy:s}=t,r=s||D();return async(l,n)=>{if(l.locals.locale&&l.locals.i18n)return n();const f=l.url,d=f.pathname;if(!r){const b=e.clone(o),y=d==="/"||d===""?"index":d.split("/").filter(Boolean).join("-");return b.setRoute(y),l.locals.i18n=b,l.locals.locale=o,l.locals.defaultLocale=o,l.locals.locales=i||a.map(S=>({code:S})),l.locals.currentUrl=f,n()}const h={...r,getCurrentPath:()=>d,getRoute:()=>({fullPath:f.pathname+f.search,query:Object.fromEntries(f.searchParams)})},p=d.split("/").filter(Boolean)[0],P=p!==void 0&&a.includes(p);let L;P&&p?L=p:h.getLocaleFromPath?L=h.getLocaleFromPath(d,o,a):L=o;const k=e.clone(L),g=h.getRouteName?h.getRouteName(d,a):"index";return k.setRoute(g),l.locals.i18n=k,l.locals.locale=L,l.locals.defaultLocale=o,l.locals.locales=i||a.map(b=>({code:b})),l.locals.currentUrl=f,l.locals.routingStrategy=h,M(h,()=>n())}}function K(t,e,o,a,i,c="i18n-locale",u){const s=u??D();let r=a;if(s?.getLocaleFromPath)r=s.getLocaleFromPath(t,a,i);else{const n=t.split("/").filter(Boolean)[0];n&&i.includes(n)&&(r=n)}if(c!==null&&r===a&&e.get(c)){const l=e.get(c)?.value;l&&i.includes(l)&&(r=l)}if(r===a)try{const l=z.detectLocaleFromAcceptLanguage(o.get("accept-language")??void 0,i);l&&(r=l)}catch{}return r}function Z(t,e,o){const a=t.map(n=>n.code),i=(n,f=[])=>{const d=n.replace(/^\//,"").replace(/\/$/,"");if(!d)return"index";const h=d.split("/").filter(Boolean),m=h[0];return m&&f.includes(m)&&h.shift(),h.length===0?"index":h.join("-")},c=(n,f="en",d=[])=>{const m=n.split("/").filter(Boolean)[0];return m&&d.includes(m)?m:f},u=(n,f,d=[],h)=>{const m=n.split("/").filter(Boolean),p=m[0];return p&&d.includes(p)&&m.shift(),(f!==h||h===void 0)&&m.unshift(f),`/${m.join("/")}`},s=(n,f,d=[],h)=>{const p=(n.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),P=p[0];return P&&d.includes(P)&&p.shift(),(f!==h||h===void 0)&&p.unshift(f),`/${p.join("/")}`};return{getCurrentPath:()=>o?o().pathname:typeof window<"u"?window.location.pathname:"/",getRouteName:i,getLocaleFromPath:c,switchLocalePath:u,localizePath:s,removeLocaleFromPath:(n,f=[])=>{const d=n.split("/").filter(Boolean),h=d[0];return h&&f.includes(h)&&d.shift(),`/${d.join("/")}`},resolvePath:(n,f)=>{const d=typeof n=="string"?n:n.path||"/";return s(d,f,a,e)},getRoute:()=>{if(o){const n=o();return{fullPath:n.pathname+n.search,query:Object.fromEntries(n.searchParams)}}if(typeof window<"u"){const n=new URL(window.location.href);return{fullPath:n.pathname+n.search,query:Object.fromEntries(n.searchParams)}}return{fullPath:"/",query:{}}},push:n=>{typeof window<"u"&&(window.location.href=n.path)},replace:n=>{typeof window<"u"&&window.location.replace(n.path)}}}function F(t){const e=t.og?.trim();if(e)return e;const o=t.iso?.trim();if(!o)return null;const a=o.indexOf("_");if(a>0){const c=o.slice(0,a),u=o.slice(a+1);if(c.length>=2&&u.length===2)return`${c.toLowerCase()}_${u.toUpperCase()}`}const i=o.indexOf("-");if(i>0){const c=o.slice(0,i),u=o.slice(i+1);if(c.length>=2&&u.length===2&&/^[A-Za-z]{2}$/.test(u))return`${c.toLowerCase()}_${u.toUpperCase()}`}return null}function C(t,e={}){if(F(t)!==null||e.missingWarn===!1||process.env.NODE_ENV==="production")return;const o=e.tag??"og:locale",a=String(t.code??"unknown"),i=t.iso?`, iso: '${t.iso}'`:"";console.warn(`[i18n] Cannot derive ${o} for locale '${a}'${i}. Set locale.og (e.g. 'en_US') or use iso with a 2-letter region (e.g. 'en-US').`)}function I(t){const e=t.locals.i18n;if(!e)throw new Error("i18n instance not found. Make sure i18n middleware is configured.");return e}function v(t){return t.locals.locale||"en"}function $(t){return t.locals.defaultLocale||"en"}function _(t){return t.locals.locales||[]}function H(t){return t.locals.routingStrategy||null}function Q(t){const e=I(t),o=v(t),a=$(t),i=_(t),c=i.map(s=>s.code),u=H(t);return{locale:o,defaultLocale:a,locales:i,t:(s,r,l,n)=>e.t(s,r,l,n),ts:(s,r,l,n)=>e.ts(s,r,l,n),tc:(s,r,l)=>e.tc(s,r,l),tn:e.tn.bind(e),td:e.td.bind(e),tdr:e.tdr.bind(e),has:(s,r)=>e.has(s,r),getRoute:()=>e.getRoute(),getRouteName:s=>{const r=s||t.url.pathname;if(u?.getRouteName)return u.getRouteName(r,c);const l=r.replace(/^\//,"").replace(/\/$/,"");if(!l)return"index";const n=l.split("/").filter(Boolean),f=n[0];return f&&c.includes(f)&&n.shift(),n.length===0?"index":n.join("-")},getLocaleFromPath:s=>{const r=s||t.url.pathname;if(u?.getLocaleFromPath)return u.getLocaleFromPath(r,a,c);const n=r.split("/").filter(Boolean)[0];return n&&c.includes(n)?n:a},switchLocalePath:s=>{if(u?.switchLocalePath)return u.switchLocalePath(t.url.pathname,s,c,a);const r=t.url.pathname.split("/").filter(Boolean),l=r[0];return l&&c.includes(l)&&r.shift(),s!==a&&r.unshift(s),`/${r.join("/")}`},localizePath:(s,r)=>{if(u?.localizePath)return u.localizePath(s,r||o,c,a);const n=(s.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),f=n[0];return f&&c.includes(f)&&n.shift(),r&&r!==a&&n.unshift(r),`/${n.join("/")}`},getI18n:()=>e,getBasePath:s=>{const n=(s||t.url).pathname.split("/").filter(Boolean),f=n[0];return f&&c.includes(f)&&n.shift(),n.length>0?`/${n.join("/")}`:"/"},addTranslations:(s,r,l=!0)=>{e.addTranslations(s,r,l)},addRouteTranslations:(s,r,l,n=!0)=>{e.addRouteTranslations(s,r,l,n)},mergeTranslations:(s,r,l)=>{e.mergeTranslations(s,r,l)},clearCache:()=>{e.clearCache()}}}function X(t,e={}){const{baseUrl:o="/",addDirAttribute:a=!0,addSeoAttributes:i=!0}=e,c=v(t),u=$(t),r=_(t).filter(g=>!g.disabled),l=r.filter(g=>g.seo!==!1),n=r.find(g=>g.code===c);if(!n)return{htmlAttrs:{},link:[],meta:[]};const f=n.iso||c,d=F(n),h=n.dir||"auto",m={htmlAttrs:{lang:f,...a?{dir:h}:{}},link:[],meta:[]};if(!i)return m;const p=`${o}${t.url.pathname}`;m.link.push({rel:"canonical",href:p});const P=H(t),L=r.map(g=>g.code);for(const g of l){let b=t.url.pathname;if(P?.switchLocalePath)b=P.switchLocalePath(t.url.pathname,g.code,L,u);else{const S=t.url.pathname.split("/").filter(Boolean),O=S[0];O&&L.includes(O)&&S.shift(),g.code!==u&&S.unshift(g.code),b=`/${S.join("/")}`}const y=`${o}${b}`;m.link.push({rel:"alternate",href:y,hreflang:g.code}),g.iso&&g.iso!==g.code&&m.link.push({rel:"alternate",href:y,hreflang:g.iso})}if(r.find(g=>g.code===u)?.seo!==!1){let g=t.url.pathname;if(P?.switchLocalePath)g=P.switchLocalePath(t.url.pathname,u,L,u);else{const b=t.url.pathname.split("/").filter(Boolean),y=b[0];y&&L.includes(y)&&b.shift(),g=`/${b.join("/")}`}m.link.push({rel:"alternate",href:`${o}${g}`,hreflang:"x-default"})}d?m.meta.push({property:"og:locale",content:d}):C(n,{tag:"og:locale"}),m.meta.push({property:"og:url",content:p});for(const g of l){if(g.code===c)continue;const b=F(g);if(!b){C(g,{tag:"og:locale:alternate"});continue}m.meta.push({property:"og:locale:alternate",content:b})}return m}function Y(t,e,o){const a=e.split(".");let i=t;for(let u=0;u<a.length-1;u++){const s=a[u];i[s]||(i[s]={}),i=i[s]}const c=a[a.length-1];c!==void 0&&(i[c]=o)}function ee(t,e){const o=I(t),a=v(t),i=$(t),c=o.getRoute(),u={};if(e&&e.length>0){const s={};for(const r of e){const l=o.t(r,void 0,void 0,c);l!=null&&l!==r&&Y(s,r,l)}Object.keys(s).length>0&&(u[c]=s)}else{const s=o.getRouteTranslations(a,c);s&&(u[c]=s)}return{locale:a,fallbackLocale:i,currentRoute:c,translations:u}}Object.defineProperty(exports,"FormatService",{enumerable:!0,get:()=>T.FormatService});Object.defineProperty(exports,"defaultPlural",{enumerable:!0,get:()=>T.defaultPlural});Object.defineProperty(exports,"interpolate",{enumerable:!0,get:()=>T.interpolate});exports.AstroI18n=j;exports.createAstroRouterAdapter=Z;exports.createI18n=G;exports.createI18nMiddleware=J;exports.detectLocale=K;exports.getDefaultLocale=$;exports.getGlobalRoutingStrategy=D;exports.getI18n=I;exports.getI18nProps=ee;exports.getLocale=v;exports.getLocales=_;exports.i18nIntegration=x;exports.loadTranslationsFromDir=U;exports.loadTranslationsIntoI18n=V;exports.runWithRoutingStrategy=M;exports.setGlobalRoutingStrategy=A;exports.useI18n=Q;exports.useLocaleHead=X;
package/dist/index.d.cts CHANGED
@@ -3,6 +3,7 @@ export type { CleanTranslation, Getter, Locale, LocaleCode, Params, PluralFunc,
3
3
  export { AstroI18n, type AstroI18nOptions } from './composer';
4
4
  export type { I18nIntegrationOptions } from './integration';
5
5
  export { createI18n, i18nIntegration } from './integration';
6
+ export { getGlobalRoutingStrategy, runWithRoutingStrategy, setGlobalRoutingStrategy } from './routing-context';
6
7
  export type { LoadedTranslations, LoadTranslationsOptions } from './load-translations';
7
8
  export { loadTranslationsFromDir, loadTranslationsIntoI18n } from './load-translations';
8
9
  export type { I18nMiddlewareOptions } from './middleware';
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type { CleanTranslation, Getter, Locale, LocaleCode, Params, PluralFunc,
3
3
  export { AstroI18n, type AstroI18nOptions } from './composer';
4
4
  export type { I18nIntegrationOptions } from './integration';
5
5
  export { createI18n, i18nIntegration } from './integration';
6
+ export { getGlobalRoutingStrategy, runWithRoutingStrategy, setGlobalRoutingStrategy } from './routing-context';
6
7
  export type { LoadedTranslations, LoadTranslationsOptions } from './load-translations';
7
8
  export { loadTranslationsFromDir, loadTranslationsIntoI18n } from './load-translations';
8
9
  export type { I18nMiddlewareOptions } from './middleware';
package/dist/index.mjs CHANGED
@@ -1,40 +1,45 @@
1
- import { BaseI18n as N } from "@i18n-micro/core";
2
- import { FormatService as fe, defaultPlural as ge, interpolate as he } from "@i18n-micro/core";
3
- import { existsSync as D, readdirSync as M, statSync as W, readFileSync as A } from "node:fs";
4
- import { resolve as U, join as q, relative as E, sep as H } from "node:path";
5
- import { mergeRouteTranslationsWithRoot as x, storeLoadedTranslationFile as z } from "@i18n-micro/utils/parse-path";
6
- import { detectLocaleFromAcceptLanguage as V } from "@i18n-micro/utils/accept-language";
7
- class T extends N {
1
+ import { BaseI18n as W } from "@i18n-micro/core";
2
+ import { FormatService as pe, defaultPlural as Le, interpolate as Pe } from "@i18n-micro/core";
3
+ import { AsyncLocalStorage as A } from "node:async_hooks";
4
+ import { existsSync as F, readdirSync as M, statSync as U, readFileSync as q } from "node:fs";
5
+ import { resolve as E, join as H, relative as x, sep as z } from "node:path";
6
+ import { mergeRouteTranslationsWithRoot as V, storeLoadedTranslationFile as G } from "@i18n-micro/utils/parse-path";
7
+ import { detectLocaleFromAcceptLanguage as J } from "@i18n-micro/utils/accept-language";
8
+ class T extends W {
8
9
  constructor(e) {
9
- const a = e._storage || {
10
+ const o = e._storage || {
10
11
  translations: /* @__PURE__ */ new Map()
11
12
  };
12
13
  if (super({
13
- storage: a,
14
+ storage: o,
14
15
  plural: e.plural,
15
16
  missingWarn: e.missingWarn,
16
- missingHandler: e.missingHandler
17
- }), this.initialMessages = {}, this.storage = a, this._locale = e.locale, this._fallbackLocale = e.fallbackLocale || e.locale, this._currentRoute = "index", e.messages) {
17
+ missingHandler: e.missingHandler,
18
+ numberFormats: e.numberFormats,
19
+ datetimeFormats: e.datetimeFormats
20
+ }), this.initialMessages = {}, this.storage = o, this._locale = e.locale, this._fallbackLocale = e.fallbackLocale || e.locale, this._currentRoute = "index", e.messages) {
18
21
  this.initialMessages = { ...e.messages };
19
- for (const [r, i] of Object.entries(e.messages))
20
- this.helper.loadTranslations(r, i);
22
+ for (const [a, i] of Object.entries(e.messages))
23
+ this.helper.loadTranslations(a, i);
21
24
  }
22
25
  }
23
26
  cloneStorage(e) {
24
- const a = /* @__PURE__ */ new Map();
25
- for (const [r, i] of e.translations)
26
- a.set(r, { ...i });
27
- return { translations: a };
27
+ const o = /* @__PURE__ */ new Map();
28
+ for (const [a, i] of e.translations)
29
+ o.set(a, { ...i });
30
+ return { translations: o };
28
31
  }
29
32
  clone(e) {
30
- const a = this.cloneStorage(this.storage);
33
+ const o = this.cloneStorage(this.storage);
31
34
  return new T({
32
35
  locale: e || this._locale,
33
36
  fallbackLocale: this._fallbackLocale,
34
37
  plural: this.pluralFunc,
35
38
  missingWarn: this.missingWarn,
36
39
  missingHandler: this.missingHandler,
37
- _storage: a
40
+ numberFormats: this.formatter.getNumberFormats(),
41
+ datetimeFormats: this.formatter.getDateTimeFormats(),
42
+ _storage: o
38
43
  });
39
44
  }
40
45
  get locale() {
@@ -61,43 +66,51 @@ class T extends N {
61
66
  getRoute() {
62
67
  return this._currentRoute;
63
68
  }
64
- getRouteTranslations(e, a) {
65
- const r = `${e}:${a}`;
66
- return this.storage.translations.get(r) ?? null;
69
+ getRouteTranslations(e, o) {
70
+ const a = `${e}:${o}`;
71
+ return this.storage.translations.get(a) ?? null;
67
72
  }
68
- addTranslations(e, a, r = !0) {
69
- super.loadTranslationsCore(e, a, r);
73
+ addTranslations(e, o, a = !0) {
74
+ super.loadTranslationsCore(e, o, a);
70
75
  }
71
- addRouteTranslations(e, a, r, i = !0) {
72
- super.loadRouteTranslationsCore(e, a, r, i);
76
+ addRouteTranslations(e, o, a, i = !0) {
77
+ super.loadRouteTranslationsCore(e, o, a, i);
73
78
  }
74
- mergeTranslations(e, a, r) {
75
- this.helper.mergeTranslation(e, a, r, !0);
79
+ mergeTranslations(e, o, a) {
80
+ this.helper.mergeTranslation(e, o, a, !0);
76
81
  }
77
82
  clearCache() {
78
83
  const e = { ...this.initialMessages };
79
84
  if (super.clearCache(), Object.keys(e).length > 0)
80
- for (const [a, r] of Object.entries(e))
81
- this.helper.loadTranslations(a, r);
85
+ for (const [o, a] of Object.entries(e))
86
+ this.helper.loadTranslations(o, a);
82
87
  }
83
88
  }
84
- let _ = null;
85
- function F() {
86
- return _;
89
+ let D = null;
90
+ const _ = new A();
91
+ function O() {
92
+ const t = _.getStore();
93
+ return t !== void 0 ? t : D;
87
94
  }
88
- function ee(t) {
89
- const { locale: e, fallbackLocale: a, translationDir: r, routingStrategy: i } = t;
90
- return _ = i || null, {
95
+ function K(t) {
96
+ D = t;
97
+ }
98
+ function Z(t, e) {
99
+ return _.run(t, e);
100
+ }
101
+ function le(t) {
102
+ const { locale: e, fallbackLocale: o, translationDir: a, routingStrategy: i } = t;
103
+ return K(i || null), {
91
104
  name: "@i18n-micro/astro",
92
105
  hooks: {
93
106
  // 1. Vite setup (virtual module) happens here
94
107
  "astro:config:setup": (c) => {
95
- const { updateConfig: u } = c, o = "virtual:i18n-micro/config", l = `\0${o}`, s = {
108
+ const { updateConfig: u } = c, s = "virtual:i18n-micro/config", l = `\0${s}`, r = {
96
109
  defaultLocale: e,
97
- fallbackLocale: a || e,
110
+ fallbackLocale: o || e,
98
111
  locales: t.locales || [],
99
112
  localeCodes: (t.locales || []).map((n) => n.code),
100
- translationDir: r || null,
113
+ translationDir: a || null,
101
114
  autoDetect: t.autoDetect ?? !0,
102
115
  redirectToDefault: t.redirectToDefault ?? !1,
103
116
  localeCookie: t.localeCookie === null ? null : t.localeCookie || "i18n-locale",
@@ -109,12 +122,12 @@ function ee(t) {
109
122
  {
110
123
  name: "vite-plugin-i18n-micro-config",
111
124
  resolveId(n) {
112
- if (n === o)
125
+ if (n === s)
113
126
  return l;
114
127
  },
115
128
  load(n) {
116
129
  if (n === l)
117
- return `export const config = ${JSON.stringify(s)}`;
130
+ return `export const config = ${JSON.stringify(r)}`;
118
131
  }
119
132
  }
120
133
  ]
@@ -148,131 +161,131 @@ function ee(t) {
148
161
  }
149
162
  };
150
163
  }
151
- function te(t) {
164
+ function re(t) {
152
165
  return new T(t);
153
166
  }
154
- function O(t, e) {
155
- if (D(t))
156
- for (const a of M(t)) {
157
- const r = q(t, a);
158
- if (W(r).isDirectory()) {
159
- O(r, e);
167
+ function C(t, e) {
168
+ if (F(t))
169
+ for (const o of M(t)) {
170
+ const a = H(t, o);
171
+ if (U(a).isDirectory()) {
172
+ C(a, e);
160
173
  continue;
161
174
  }
162
- a.endsWith(".json") && e(r);
175
+ o.endsWith(".json") && e(a);
163
176
  }
164
177
  }
165
- function J(t) {
166
- const { translationDir: e, rootDir: a = process.cwd(), disablePageLocales: r = !1 } = t, i = U(a, e);
167
- if (!D(i))
178
+ function Q(t) {
179
+ const { translationDir: e, rootDir: o = process.cwd(), disablePageLocales: a = !1 } = t, i = E(o, e);
180
+ if (!F(i))
168
181
  return console.warn(`[i18n] Translation directory not found: ${i}`), { root: {}, routes: {} };
169
182
  const c = { root: {}, routes: {} };
170
- return O(i, (u) => {
171
- const o = E(i, u).split(H).join("/");
183
+ return C(i, (u) => {
184
+ const s = x(i, u).split(z).join("/");
172
185
  try {
173
- const l = A(u, "utf-8"), s = JSON.parse(l);
174
- z(c, o, s, r);
186
+ const l = q(u, "utf-8"), r = JSON.parse(l);
187
+ G(c, s, r, a);
175
188
  } catch (l) {
176
189
  console.error(`[i18n] Failed to load translation file: ${u}`, l);
177
190
  }
178
191
  }), c;
179
192
  }
180
- function ne(t, e) {
181
- const { root: a, routes: r } = J(e);
182
- for (const [i, c] of Object.entries(a))
193
+ function se(t, e) {
194
+ const { root: o, routes: a } = Q(e);
195
+ for (const [i, c] of Object.entries(o))
183
196
  t.addTranslations(i, c, !1);
184
- for (const [i, c] of Object.entries(r))
185
- for (const [u, o] of Object.entries(c))
186
- t.addRouteTranslations(u, i, x(a[u], o), !1);
197
+ for (const [i, c] of Object.entries(a))
198
+ for (const [u, s] of Object.entries(c))
199
+ t.addRouteTranslations(u, i, V(o[u], s), !1);
187
200
  }
188
- function oe(t) {
201
+ function ie(t) {
189
202
  const {
190
203
  i18n: e,
191
204
  // This is the global singleton with cache
192
- defaultLocale: a,
193
- locales: r,
205
+ defaultLocale: o,
206
+ locales: a,
194
207
  localeObjects: i,
195
208
  autoDetect: c = !0,
196
209
  redirectToDefault: u = !1,
197
- routingStrategy: o
198
- } = t, l = o || F();
199
- return async (s, n) => {
200
- if (s.locals.locale && s.locals.i18n)
210
+ routingStrategy: s
211
+ } = t, l = s || O();
212
+ return async (r, n) => {
213
+ if (r.locals.locale && r.locals.i18n)
201
214
  return n();
202
- const f = s.url, h = f.pathname;
215
+ const f = r.url, h = f.pathname;
203
216
  if (!l) {
204
- const L = e.clone(a), S = h === "/" || h === "" ? "index" : h.split("/").filter(Boolean).join("-");
205
- return L.setRoute(S), s.locals.i18n = L, s.locals.locale = a, s.locals.defaultLocale = a, s.locals.locales = i || r.map((y) => ({ code: y })), s.locals.currentUrl = f, n();
217
+ const L = e.clone(o), S = h === "/" || h === "" ? "index" : h.split("/").filter(Boolean).join("-");
218
+ return L.setRoute(S), r.locals.i18n = L, r.locals.locale = o, r.locals.defaultLocale = o, r.locals.locales = i || a.map((y) => ({ code: y })), r.locals.currentUrl = f, n();
206
219
  }
207
- const m = {
220
+ const d = {
208
221
  ...l,
209
222
  getCurrentPath: () => h,
210
223
  getRoute: () => ({
211
224
  fullPath: f.pathname + f.search,
212
225
  query: Object.fromEntries(f.searchParams)
213
226
  })
214
- }, p = h.split("/").filter(Boolean)[0], b = p !== void 0 && r.includes(p);
227
+ }, p = h.split("/").filter(Boolean)[0], b = p !== void 0 && a.includes(p);
215
228
  let P;
216
- b && p ? P = p : m.getLocaleFromPath ? P = m.getLocaleFromPath(h, a, r) : P = a;
217
- const w = e.clone(P), g = m.getRouteName ? m.getRouteName(h, r) : "index";
218
- return w.setRoute(g), s.locals.i18n = w, s.locals.locale = P, s.locals.defaultLocale = a, s.locals.locales = i || r.map((L) => ({ code: L })), s.locals.currentUrl = f, s.locals.routingStrategy = m, n();
229
+ b && p ? P = p : d.getLocaleFromPath ? P = d.getLocaleFromPath(h, o, a) : P = o;
230
+ const w = e.clone(P), g = d.getRouteName ? d.getRouteName(h, a) : "index";
231
+ return w.setRoute(g), r.locals.i18n = w, r.locals.locale = P, r.locals.defaultLocale = o, r.locals.locales = i || a.map((L) => ({ code: L })), r.locals.currentUrl = f, r.locals.routingStrategy = d, Z(d, () => n());
219
232
  };
220
233
  }
221
- function ae(t, e, a, r, i, c = "i18n-locale") {
222
- const u = F();
223
- let o = r;
224
- if (u?.getLocaleFromPath)
225
- o = u.getLocaleFromPath(t, r, i);
234
+ function ce(t, e, o, a, i, c = "i18n-locale", u) {
235
+ const s = u ?? O();
236
+ let l = a;
237
+ if (s?.getLocaleFromPath)
238
+ l = s.getLocaleFromPath(t, a, i);
226
239
  else {
227
- const s = t.split("/").filter(Boolean)[0];
228
- s && i.includes(s) && (o = s);
240
+ const n = t.split("/").filter(Boolean)[0];
241
+ n && i.includes(n) && (l = n);
229
242
  }
230
- if (c !== null && o === r && e.get(c)) {
231
- const l = e.get(c)?.value;
232
- l && i.includes(l) && (o = l);
243
+ if (c !== null && l === a && e.get(c)) {
244
+ const r = e.get(c)?.value;
245
+ r && i.includes(r) && (l = r);
233
246
  }
234
- if (o === r)
247
+ if (l === a)
235
248
  try {
236
- const l = V(a.get("accept-language") ?? void 0, i);
237
- l && (o = l);
249
+ const r = J(o.get("accept-language") ?? void 0, i);
250
+ r && (l = r);
238
251
  } catch {
239
252
  }
240
- return o;
253
+ return l;
241
254
  }
242
- function le(t, e, a) {
243
- const r = t.map((n) => n.code), i = (n, f = []) => {
255
+ function ue(t, e, o) {
256
+ const a = t.map((n) => n.code), i = (n, f = []) => {
244
257
  const h = n.replace(/^\//, "").replace(/\/$/, "");
245
258
  if (!h)
246
259
  return "index";
247
- const m = h.split("/").filter(Boolean), d = m[0];
248
- return d && f.includes(d) && m.shift(), m.length === 0 ? "index" : m.join("-");
260
+ const d = h.split("/").filter(Boolean), m = d[0];
261
+ return m && f.includes(m) && d.shift(), d.length === 0 ? "index" : d.join("-");
249
262
  }, c = (n, f = "en", h = []) => {
250
- const d = n.split("/").filter(Boolean)[0];
251
- return d && h.includes(d) ? d : f;
252
- }, u = (n, f, h = [], m) => {
253
- const d = n.split("/").filter(Boolean), p = d[0];
254
- return p && h.includes(p) && d.shift(), (f !== m || m === void 0) && d.unshift(f), `/${d.join("/")}`;
255
- }, o = (n, f, h = [], m) => {
263
+ const m = n.split("/").filter(Boolean)[0];
264
+ return m && h.includes(m) ? m : f;
265
+ }, u = (n, f, h = [], d) => {
266
+ const m = n.split("/").filter(Boolean), p = m[0];
267
+ return p && h.includes(p) && m.shift(), (f !== d || d === void 0) && m.unshift(f), `/${m.join("/")}`;
268
+ }, s = (n, f, h = [], d) => {
256
269
  const p = (n.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), b = p[0];
257
- return b && h.includes(b) && p.shift(), (f !== m || m === void 0) && p.unshift(f), `/${p.join("/")}`;
270
+ return b && h.includes(b) && p.shift(), (f !== d || d === void 0) && p.unshift(f), `/${p.join("/")}`;
258
271
  };
259
272
  return {
260
- getCurrentPath: () => a ? a().pathname : typeof window < "u" ? window.location.pathname : "/",
273
+ getCurrentPath: () => o ? o().pathname : typeof window < "u" ? window.location.pathname : "/",
261
274
  getRouteName: i,
262
275
  getLocaleFromPath: c,
263
276
  switchLocalePath: u,
264
- localizePath: o,
277
+ localizePath: s,
265
278
  removeLocaleFromPath: (n, f = []) => {
266
- const h = n.split("/").filter(Boolean), m = h[0];
267
- return m && f.includes(m) && h.shift(), `/${h.join("/")}`;
279
+ const h = n.split("/").filter(Boolean), d = h[0];
280
+ return d && f.includes(d) && h.shift(), `/${h.join("/")}`;
268
281
  },
269
282
  resolvePath: (n, f) => {
270
283
  const h = typeof n == "string" ? n : n.path || "/";
271
- return o(h, f, r, e);
284
+ return s(h, f, a, e);
272
285
  },
273
286
  getRoute: () => {
274
- if (a) {
275
- const n = a();
287
+ if (o) {
288
+ const n = o();
276
289
  return {
277
290
  fullPath: n.pathname + n.search,
278
291
  query: Object.fromEntries(n.searchParams)
@@ -302,17 +315,17 @@ function le(t, e, a) {
302
315
  function R(t) {
303
316
  const e = t.og?.trim();
304
317
  if (e) return e;
305
- const a = t.iso?.trim();
306
- if (!a) return null;
307
- const r = a.indexOf("_");
308
- if (r > 0) {
309
- const c = a.slice(0, r), u = a.slice(r + 1);
318
+ const o = t.iso?.trim();
319
+ if (!o) return null;
320
+ const a = o.indexOf("_");
321
+ if (a > 0) {
322
+ const c = o.slice(0, a), u = o.slice(a + 1);
310
323
  if (c.length >= 2 && u.length === 2)
311
324
  return `${c.toLowerCase()}_${u.toUpperCase()}`;
312
325
  }
313
- const i = a.indexOf("-");
326
+ const i = o.indexOf("-");
314
327
  if (i > 0) {
315
- const c = a.slice(0, i), u = a.slice(i + 1);
328
+ const c = o.slice(0, i), u = o.slice(i + 1);
316
329
  if (c.length >= 2 && u.length === 2 && /^[A-Za-z]{2}$/.test(u))
317
330
  return `${c.toLowerCase()}_${u.toUpperCase()}`;
318
331
  }
@@ -320,12 +333,12 @@ function R(t) {
320
333
  }
321
334
  function k(t, e = {}) {
322
335
  if (R(t) !== null || e.missingWarn === !1 || process.env.NODE_ENV === "production") return;
323
- const a = e.tag ?? "og:locale", r = String(t.code ?? "unknown"), i = t.iso ? `, iso: '${t.iso}'` : "";
336
+ const o = e.tag ?? "og:locale", a = String(t.code ?? "unknown"), i = t.iso ? `, iso: '${t.iso}'` : "";
324
337
  console.warn(
325
- `[i18n] Cannot derive ${a} for locale '${r}'${i}. Set locale.og (e.g. 'en_US') or use iso with a 2-letter region (e.g. 'en-US').`
338
+ `[i18n] Cannot derive ${o} for locale '${a}'${i}. Set locale.og (e.g. 'en_US') or use iso with a 2-letter region (e.g. 'en-US').`
326
339
  );
327
340
  }
328
- function C(t) {
341
+ function B(t) {
329
342
  const e = t.locals.i18n;
330
343
  if (!e)
331
344
  throw new Error("i18n instance not found. Make sure i18n middleware is configured.");
@@ -337,101 +350,101 @@ function $(t) {
337
350
  function v(t) {
338
351
  return t.locals.defaultLocale || "en";
339
352
  }
340
- function B(t) {
353
+ function I(t) {
341
354
  return t.locals.locales || [];
342
355
  }
343
- function I(t) {
356
+ function N(t) {
344
357
  return t.locals.routingStrategy || null;
345
358
  }
346
- function re(t) {
347
- const e = C(t), a = $(t), r = v(t), i = B(t), c = i.map((o) => o.code), u = I(t);
359
+ function fe(t) {
360
+ const e = B(t), o = $(t), a = v(t), i = I(t), c = i.map((s) => s.code), u = N(t);
348
361
  return {
349
362
  // Current locale
350
- locale: a,
351
- defaultLocale: r,
363
+ locale: o,
364
+ defaultLocale: a,
352
365
  locales: i,
353
366
  // Translation methods
354
- t: (o, l, s, n) => e.t(o, l, s, n),
355
- ts: (o, l, s, n) => e.ts(o, l, s, n),
356
- tc: (o, l, s) => e.tc(o, l, s),
357
- tn: (o, l) => e.tn(o, l),
358
- td: (o, l) => e.td(o, l),
359
- tdr: (o, l) => e.tdr(o, l),
360
- has: (o, l) => e.has(o, l),
367
+ t: (s, l, r, n) => e.t(s, l, r, n),
368
+ ts: (s, l, r, n) => e.ts(s, l, r, n),
369
+ tc: (s, l, r) => e.tc(s, l, r),
370
+ tn: e.tn.bind(e),
371
+ td: e.td.bind(e),
372
+ tdr: e.tdr.bind(e),
373
+ has: (s, l) => e.has(s, l),
361
374
  // Route management
362
375
  getRoute: () => e.getRoute(),
363
- getRouteName: (o) => {
364
- const l = o || t.url.pathname;
376
+ getRouteName: (s) => {
377
+ const l = s || t.url.pathname;
365
378
  if (u?.getRouteName)
366
379
  return u.getRouteName(l, c);
367
- const s = l.replace(/^\//, "").replace(/\/$/, "");
368
- if (!s) return "index";
369
- const n = s.split("/").filter(Boolean), f = n[0];
380
+ const r = l.replace(/^\//, "").replace(/\/$/, "");
381
+ if (!r) return "index";
382
+ const n = r.split("/").filter(Boolean), f = n[0];
370
383
  return f && c.includes(f) && n.shift(), n.length === 0 ? "index" : n.join("-");
371
384
  },
372
- getLocaleFromPath: (o) => {
373
- const l = o || t.url.pathname;
385
+ getLocaleFromPath: (s) => {
386
+ const l = s || t.url.pathname;
374
387
  if (u?.getLocaleFromPath)
375
- return u.getLocaleFromPath(l, r, c);
388
+ return u.getLocaleFromPath(l, a, c);
376
389
  const n = l.split("/").filter(Boolean)[0];
377
- return n && c.includes(n) ? n : r;
390
+ return n && c.includes(n) ? n : a;
378
391
  },
379
392
  // Path utilities
380
- switchLocalePath: (o) => {
393
+ switchLocalePath: (s) => {
381
394
  if (u?.switchLocalePath)
382
- return u.switchLocalePath(t.url.pathname, o, c, r);
383
- const l = t.url.pathname.split("/").filter(Boolean), s = l[0];
384
- return s && c.includes(s) && l.shift(), o !== r && l.unshift(o), `/${l.join("/")}`;
395
+ return u.switchLocalePath(t.url.pathname, s, c, a);
396
+ const l = t.url.pathname.split("/").filter(Boolean), r = l[0];
397
+ return r && c.includes(r) && l.shift(), s !== a && l.unshift(s), `/${l.join("/")}`;
385
398
  },
386
- localizePath: (o, l) => {
399
+ localizePath: (s, l) => {
387
400
  if (u?.localizePath)
388
- return u.localizePath(o, l || a, c, r);
389
- const n = (o.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), f = n[0];
390
- return f && c.includes(f) && n.shift(), l && l !== r && n.unshift(l), `/${n.join("/")}`;
401
+ return u.localizePath(s, l || o, c, a);
402
+ const n = (s.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), f = n[0];
403
+ return f && c.includes(f) && n.shift(), l && l !== a && n.unshift(l), `/${n.join("/")}`;
391
404
  },
392
405
  // Get i18n instance
393
406
  getI18n: () => e,
394
407
  // Get base path without locale (for rewrite)
395
- getBasePath: (o) => {
396
- const n = (o || t.url).pathname.split("/").filter(Boolean), f = n[0];
408
+ getBasePath: (s) => {
409
+ const n = (s || t.url).pathname.split("/").filter(Boolean), f = n[0];
397
410
  return f && c.includes(f) && n.shift(), n.length > 0 ? `/${n.join("/")}` : "/";
398
411
  },
399
412
  // Translation management
400
- addTranslations: (o, l, s = !0) => {
401
- e.addTranslations(o, l, s);
413
+ addTranslations: (s, l, r = !0) => {
414
+ e.addTranslations(s, l, r);
402
415
  },
403
- addRouteTranslations: (o, l, s, n = !0) => {
404
- e.addRouteTranslations(o, l, s, n);
416
+ addRouteTranslations: (s, l, r, n = !0) => {
417
+ e.addRouteTranslations(s, l, r, n);
405
418
  },
406
- mergeTranslations: (o, l, s) => {
407
- e.mergeTranslations(o, l, s);
419
+ mergeTranslations: (s, l, r) => {
420
+ e.mergeTranslations(s, l, r);
408
421
  },
409
422
  clearCache: () => {
410
423
  e.clearCache();
411
424
  }
412
425
  };
413
426
  }
414
- function se(t, e = {}) {
415
- const { baseUrl: a = "/", addDirAttribute: r = !0, addSeoAttributes: i = !0 } = e, c = $(t), u = v(t), l = B(t).filter((g) => !g.disabled), s = l.filter((g) => g.seo !== !1), n = l.find((g) => g.code === c);
427
+ function ge(t, e = {}) {
428
+ const { baseUrl: o = "/", addDirAttribute: a = !0, addSeoAttributes: i = !0 } = e, c = $(t), u = v(t), l = I(t).filter((g) => !g.disabled), r = l.filter((g) => g.seo !== !1), n = l.find((g) => g.code === c);
416
429
  if (!n)
417
430
  return { htmlAttrs: {}, link: [], meta: [] };
418
- const f = n.iso || c, h = R(n), m = n.dir || "auto", d = {
431
+ const f = n.iso || c, h = R(n), d = n.dir || "auto", m = {
419
432
  htmlAttrs: {
420
433
  lang: f,
421
- ...r ? { dir: m } : {}
434
+ ...a ? { dir: d } : {}
422
435
  },
423
436
  link: [],
424
437
  meta: []
425
438
  };
426
439
  if (!i)
427
- return d;
428
- const p = `${a}${t.url.pathname}`;
429
- d.link.push({
440
+ return m;
441
+ const p = `${o}${t.url.pathname}`;
442
+ m.link.push({
430
443
  rel: "canonical",
431
444
  href: p
432
445
  });
433
- const b = I(t), P = l.map((g) => g.code);
434
- for (const g of s) {
446
+ const b = N(t), P = l.map((g) => g.code);
447
+ for (const g of r) {
435
448
  let L = t.url.pathname;
436
449
  if (b?.switchLocalePath)
437
450
  L = b.switchLocalePath(t.url.pathname, g.code, P, u);
@@ -439,12 +452,12 @@ function se(t, e = {}) {
439
452
  const y = t.url.pathname.split("/").filter(Boolean), j = y[0];
440
453
  j && P.includes(j) && y.shift(), g.code !== u && y.unshift(g.code), L = `/${y.join("/")}`;
441
454
  }
442
- const S = `${a}${L}`;
443
- d.link.push({
455
+ const S = `${o}${L}`;
456
+ m.link.push({
444
457
  rel: "alternate",
445
458
  href: S,
446
459
  hreflang: g.code
447
- }), g.iso && g.iso !== g.code && d.link.push({
460
+ }), g.iso && g.iso !== g.code && m.link.push({
448
461
  rel: "alternate",
449
462
  href: S,
450
463
  hreflang: g.iso
@@ -458,58 +471,58 @@ function se(t, e = {}) {
458
471
  const L = t.url.pathname.split("/").filter(Boolean), S = L[0];
459
472
  S && P.includes(S) && L.shift(), g = `/${L.join("/")}`;
460
473
  }
461
- d.link.push({
474
+ m.link.push({
462
475
  rel: "alternate",
463
- href: `${a}${g}`,
476
+ href: `${o}${g}`,
464
477
  hreflang: "x-default"
465
478
  });
466
479
  }
467
- h ? d.meta.push({
480
+ h ? m.meta.push({
468
481
  property: "og:locale",
469
482
  content: h
470
- }) : k(n, { tag: "og:locale" }), d.meta.push({
483
+ }) : k(n, { tag: "og:locale" }), m.meta.push({
471
484
  property: "og:url",
472
485
  content: p
473
486
  });
474
- for (const g of s) {
487
+ for (const g of r) {
475
488
  if (g.code === c) continue;
476
489
  const L = R(g);
477
490
  if (!L) {
478
491
  k(g, { tag: "og:locale:alternate" });
479
492
  continue;
480
493
  }
481
- d.meta.push({
494
+ m.meta.push({
482
495
  property: "og:locale:alternate",
483
496
  content: L
484
497
  });
485
498
  }
486
- return d;
499
+ return m;
487
500
  }
488
- function G(t, e, a) {
489
- const r = e.split(".");
501
+ function X(t, e, o) {
502
+ const a = e.split(".");
490
503
  let i = t;
491
- for (let u = 0; u < r.length - 1; u++) {
492
- const o = r[u];
493
- i[o] || (i[o] = {}), i = i[o];
504
+ for (let u = 0; u < a.length - 1; u++) {
505
+ const s = a[u];
506
+ i[s] || (i[s] = {}), i = i[s];
494
507
  }
495
- const c = r[r.length - 1];
496
- c !== void 0 && (i[c] = a);
508
+ const c = a[a.length - 1];
509
+ c !== void 0 && (i[c] = o);
497
510
  }
498
- function ie(t, e) {
499
- const a = C(t), r = $(t), i = v(t), c = a.getRoute(), u = {};
511
+ function he(t, e) {
512
+ const o = B(t), a = $(t), i = v(t), c = o.getRoute(), u = {};
500
513
  if (e && e.length > 0) {
501
- const o = {};
514
+ const s = {};
502
515
  for (const l of e) {
503
- const s = a.t(l, void 0, void 0, c);
504
- s != null && s !== l && G(o, l, s);
516
+ const r = o.t(l, void 0, void 0, c);
517
+ r != null && r !== l && X(s, l, r);
505
518
  }
506
- Object.keys(o).length > 0 && (u[c] = o);
519
+ Object.keys(s).length > 0 && (u[c] = s);
507
520
  } else {
508
- const o = a.getRouteTranslations(r, c);
509
- o && (u[c] = o);
521
+ const s = o.getRouteTranslations(a, c);
522
+ s && (u[c] = s);
510
523
  }
511
524
  return {
512
- locale: r,
525
+ locale: a,
513
526
  fallbackLocale: i,
514
527
  currentRoute: c,
515
528
  translations: u
@@ -517,21 +530,24 @@ function ie(t, e) {
517
530
  }
518
531
  export {
519
532
  T as AstroI18n,
520
- fe as FormatService,
521
- le as createAstroRouterAdapter,
522
- te as createI18n,
523
- oe as createI18nMiddleware,
524
- ge as defaultPlural,
525
- ae as detectLocale,
533
+ pe as FormatService,
534
+ ue as createAstroRouterAdapter,
535
+ re as createI18n,
536
+ ie as createI18nMiddleware,
537
+ Le as defaultPlural,
538
+ ce as detectLocale,
526
539
  v as getDefaultLocale,
527
- C as getI18n,
528
- ie as getI18nProps,
540
+ O as getGlobalRoutingStrategy,
541
+ B as getI18n,
542
+ he as getI18nProps,
529
543
  $ as getLocale,
530
- B as getLocales,
531
- ee as i18nIntegration,
532
- he as interpolate,
533
- J as loadTranslationsFromDir,
534
- ne as loadTranslationsIntoI18n,
535
- re as useI18n,
536
- se as useLocaleHead
544
+ I as getLocales,
545
+ le as i18nIntegration,
546
+ Pe as interpolate,
547
+ Q as loadTranslationsFromDir,
548
+ se as loadTranslationsIntoI18n,
549
+ Z as runWithRoutingStrategy,
550
+ K as setGlobalRoutingStrategy,
551
+ fe as useI18n,
552
+ ge as useLocaleHead
537
553
  };
@@ -16,8 +16,7 @@ export interface I18nIntegrationOptions extends Omit<ModuleOptions, 'plural'> {
16
16
  translationDir?: string;
17
17
  routingStrategy?: I18nRoutingStrategy;
18
18
  }
19
- export declare function getGlobalRoutingStrategy(): I18nRoutingStrategy | null;
20
- export declare function setGlobalRoutingStrategy(strategy: I18nRoutingStrategy | null): void;
19
+ export { getGlobalRoutingStrategy, runWithRoutingStrategy, setGlobalRoutingStrategy } from './routing-context';
21
20
  /**
22
21
  * Astro Integration for i18n-micro
23
22
  */
@@ -22,4 +22,4 @@ export declare function detectLocale(pathname: string, cookies: {
22
22
  get: (name: string) => {
23
23
  value: string;
24
24
  } | undefined;
25
- }, headers: Headers, defaultLocale: string, locales: string[], localeCookie?: string | null): string;
25
+ }, headers: Headers, defaultLocale: string, locales: string[], localeCookie?: string | null, routingStrategy?: I18nRoutingStrategy | null): string;
@@ -0,0 +1,4 @@
1
+ import { I18nRoutingStrategy } from './router/types';
2
+ export declare function getGlobalRoutingStrategy(): I18nRoutingStrategy | null;
3
+ export declare function setGlobalRoutingStrategy(strategy: I18nRoutingStrategy | null): void;
4
+ export declare function runWithRoutingStrategy<T>(strategy: I18nRoutingStrategy | null, fn: () => T): T;
package/dist/utils.d.ts CHANGED
@@ -28,8 +28,16 @@ export declare function useI18n(astro: AstroGlobal): {
28
28
  t: (key: TranslationKey, params?: Params, defaultValue?: string | null, routeName?: string) => CleanTranslation;
29
29
  ts: (key: TranslationKey, params?: Params, defaultValue?: string, routeName?: string) => string;
30
30
  tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
31
- tn: (value: number, options?: Intl.NumberFormatOptions) => string;
32
- td: (value: Date | number | string, options?: Intl.DateTimeFormatOptions) => string;
31
+ tn: {
32
+ (value: number, options?: Intl.NumberFormatOptions): string;
33
+ (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
34
+ (value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
35
+ };
36
+ td: {
37
+ (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
38
+ (value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
39
+ (value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
40
+ };
33
41
  tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
34
42
  has: (key: TranslationKey, routeName?: string) => boolean;
35
43
  getRoute: () => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-micro/astro",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "Astro integration for i18n-micro — translations, routing, and islands.",
5
5
  "keywords": [
6
6
  "astro",
@@ -77,27 +77,24 @@
77
77
  "access": "public"
78
78
  },
79
79
  "dependencies": {
80
- "@i18n-micro/core": "1.3.2",
81
- "@i18n-micro/types": "1.2.5",
82
- "@i18n-micro/utils": "1.0.2",
83
- "@i18n-micro/node": "1.2.3"
80
+ "@i18n-micro/types": "1.2.7",
81
+ "@i18n-micro/core": "1.3.8",
82
+ "@i18n-micro/utils": "1.0.8",
83
+ "@i18n-micro/node": "1.2.4"
84
84
  },
85
85
  "devDependencies": {
86
- "@types/jest": "^29.5.14",
87
- "astro": "^5.16.5",
88
- "jest": "^29.7.0",
86
+ "astro": "^5.0.0 || ^6.0.0 || ^7.0.0",
89
87
  "preact": "^10.0.0",
90
88
  "publint": "^0.3.17",
91
89
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
92
90
  "svelte": "^4.0.0 || ^5.0.0",
93
- "ts-jest": "^29.4.6",
94
- "vite": "^7.3.1",
91
+ "vite": "^7.3.6",
95
92
  "vite-plugin-dts": "^4.5.4",
96
- "vitest": "^3.2.4",
93
+ "vitest": "^4.1.10",
97
94
  "vue": "^3.5.25"
98
95
  },
99
96
  "peerDependencies": {
100
- "astro": "^5.16.5",
97
+ "astro": "^5.0.0 || ^6.0.0 || ^7.0.0",
101
98
  "preact": "^10.0.0",
102
99
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
103
100
  "svelte": "^4.0.0 || ^5.0.0",
@@ -124,7 +121,7 @@
124
121
  "build": "vite build",
125
122
  "check:package": "publint",
126
123
  "dev": "cd playground && astro dev",
127
- "test": "jest",
124
+ "test": "vitest run",
128
125
  "test:dist": "vitest run --config vitest.dist.config.ts",
129
126
  "typecheck": "tsc --noEmit"
130
127
  }
package/src/composer.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BaseI18n, type TranslationStorage } from '@i18n-micro/core'
1
+ import { BaseI18n, type DateTimeFormatsConfig, type NumberFormatsConfig, type TranslationStorage } from '@i18n-micro/core'
2
2
  import type { PluralFunc, Translations } from '@i18n-micro/types'
3
3
 
4
4
  export interface AstroI18nOptions {
@@ -8,6 +8,8 @@ export interface AstroI18nOptions {
8
8
  plural?: PluralFunc
9
9
  missingWarn?: boolean
10
10
  missingHandler?: (locale: string, key: string, routeName: string) => void
11
+ numberFormats?: NumberFormatsConfig
12
+ datetimeFormats?: DateTimeFormatsConfig
11
13
  _storage?: TranslationStorage
12
14
  }
13
15
 
@@ -30,6 +32,8 @@ export class AstroI18n extends BaseI18n {
30
32
  plural: options.plural,
31
33
  missingWarn: options.missingWarn,
32
34
  missingHandler: options.missingHandler,
35
+ numberFormats: options.numberFormats,
36
+ datetimeFormats: options.datetimeFormats,
33
37
  })
34
38
 
35
39
  this.storage = storage
@@ -62,6 +66,8 @@ export class AstroI18n extends BaseI18n {
62
66
  plural: this.pluralFunc,
63
67
  missingWarn: this.missingWarn,
64
68
  missingHandler: this.missingHandler,
69
+ numberFormats: this.formatter.getNumberFormats(),
70
+ datetimeFormats: this.formatter.getDateTimeFormats(),
65
71
  _storage: isolatedStorage,
66
72
  })
67
73
  }
package/src/index.ts CHANGED
@@ -8,8 +8,8 @@ export type { CleanTranslation, Getter, Locale, LocaleCode, Params, PluralFunc,
8
8
  export { AstroI18n, type AstroI18nOptions } from './composer'
9
9
  // Export integration options type
10
10
  export type { I18nIntegrationOptions } from './integration'
11
- // Main exports
12
11
  export { createI18n, i18nIntegration } from './integration'
12
+ export { getGlobalRoutingStrategy, runWithRoutingStrategy, setGlobalRoutingStrategy } from './routing-context'
13
13
  export type { LoadedTranslations, LoadTranslationsOptions } from './load-translations'
14
14
  // Export translation loading utilities
15
15
  export { loadTranslationsFromDir, loadTranslationsIntoI18n } from './load-translations'
@@ -1,6 +1,7 @@
1
1
  import type { Locale, ModuleOptions, PluralFunc } from '@i18n-micro/types'
2
2
  import type { AstroIntegration, HookParameters } from 'astro'
3
3
  import { AstroI18n, type AstroI18nOptions } from './composer'
4
+ import { setGlobalRoutingStrategy } from './routing-context'
4
5
  import type { I18nRoutingStrategy } from './router/types'
5
6
 
6
7
  export interface I18nIntegrationOptions extends Omit<ModuleOptions, 'plural'> {
@@ -18,15 +19,7 @@ export interface I18nIntegrationOptions extends Omit<ModuleOptions, 'plural'> {
18
19
  routingStrategy?: I18nRoutingStrategy
19
20
  }
20
21
 
21
- let globalRoutingStrategy: I18nRoutingStrategy | null = null
22
-
23
- export function getGlobalRoutingStrategy(): I18nRoutingStrategy | null {
24
- return globalRoutingStrategy
25
- }
26
-
27
- export function setGlobalRoutingStrategy(strategy: I18nRoutingStrategy | null): void {
28
- globalRoutingStrategy = strategy
29
- }
22
+ export { getGlobalRoutingStrategy, runWithRoutingStrategy, setGlobalRoutingStrategy } from './routing-context'
30
23
 
31
24
  /**
32
25
  * Astro Integration for i18n-micro
@@ -34,7 +27,7 @@ export function setGlobalRoutingStrategy(strategy: I18nRoutingStrategy | null):
34
27
  export function i18nIntegration(options: I18nIntegrationOptions): AstroIntegration {
35
28
  const { locale: defaultLocale, fallbackLocale, translationDir, routingStrategy } = options
36
29
 
37
- globalRoutingStrategy = routingStrategy || null
30
+ setGlobalRoutingStrategy(routingStrategy || null)
38
31
 
39
32
  return {
40
33
  name: '@i18n-micro/astro',
package/src/middleware.ts CHANGED
@@ -2,7 +2,7 @@ import type { Locale } from '@i18n-micro/types'
2
2
  import { detectLocaleFromAcceptLanguage } from '@i18n-micro/utils/accept-language'
3
3
  import type { MiddlewareHandler } from 'astro'
4
4
  import type { AstroI18n } from './composer'
5
- import { getGlobalRoutingStrategy } from './integration'
5
+ import { getGlobalRoutingStrategy, runWithRoutingStrategy } from './routing-context'
6
6
  import type { I18nRoutingStrategy } from './router/types'
7
7
  // Import shim to ensure App.Locals is available
8
8
  import './env.d'
@@ -106,7 +106,7 @@ export function createI18nMiddleware(options: I18nMiddlewareOptions): Middleware
106
106
  context.locals.currentUrl = url
107
107
  context.locals.routingStrategy = contextStrategy
108
108
 
109
- return next()
109
+ return runWithRoutingStrategy(contextStrategy, () => next())
110
110
  }
111
111
  }
112
112
 
@@ -120,9 +120,9 @@ export function detectLocale(
120
120
  defaultLocale: string,
121
121
  locales: string[],
122
122
  localeCookie: string | null = 'i18n-locale',
123
+ routingStrategy?: I18nRoutingStrategy | null,
123
124
  ): string {
124
- // Get routing strategy
125
- const strategy = getGlobalRoutingStrategy()
125
+ const strategy = routingStrategy ?? getGlobalRoutingStrategy()
126
126
 
127
127
  // 1. Try path
128
128
  let locale = defaultLocale
@@ -0,0 +1,23 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks'
2
+ import type { I18nRoutingStrategy } from './router/types'
3
+
4
+ /** Integration-time default strategy (immutable after setup). */
5
+ let installedRoutingStrategy: I18nRoutingStrategy | null = null
6
+
7
+ /** Per-request strategy override for concurrent SSR. */
8
+ const requestRoutingStrategy = new AsyncLocalStorage<I18nRoutingStrategy | null>()
9
+
10
+ export function getGlobalRoutingStrategy(): I18nRoutingStrategy | null {
11
+ const store = requestRoutingStrategy.getStore()
12
+ // Distinguish an explicit per-request override (including `null`, i.e. routing
13
+ // disabled for this request) from "no request context" (`undefined`).
14
+ return store !== undefined ? store : installedRoutingStrategy
15
+ }
16
+
17
+ export function setGlobalRoutingStrategy(strategy: I18nRoutingStrategy | null): void {
18
+ installedRoutingStrategy = strategy
19
+ }
20
+
21
+ export function runWithRoutingStrategy<T>(strategy: I18nRoutingStrategy | null, fn: () => T): T {
22
+ return requestRoutingStrategy.run(strategy, fn)
23
+ }
package/src/utils.ts CHANGED
@@ -73,15 +73,9 @@ export function useI18n(astro: AstroGlobal) {
73
73
  tc: (key: TranslationKey, count: number | Params, defaultValue?: string): string => {
74
74
  return i18n.tc(key, count, defaultValue)
75
75
  },
76
- tn: (value: number, options?: Intl.NumberFormatOptions): string => {
77
- return i18n.tn(value, options)
78
- },
79
- td: (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
80
- return i18n.td(value, options)
81
- },
82
- tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
83
- return i18n.tdr(value, options)
84
- },
76
+ tn: i18n.tn.bind(i18n),
77
+ td: i18n.td.bind(i18n),
78
+ tdr: i18n.tdr.bind(i18n),
85
79
  has: (key: TranslationKey, routeName?: string): boolean => {
86
80
  return i18n.has(key, routeName)
87
81
  },