@i18n-micro/astro 1.3.6 → 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 j=require("@i18n-micro/core"),T=require("node:fs"),R=require("node:path"),F=require("@i18n-micro/utils/parse-path"),N=require("@i18n-micro/utils/accept-language");class v extends j.BaseI18n{constructor(e){const l=e._storage||{translations:new Map};if(super({storage:l,plural:e.plural,missingWarn:e.missingWarn,missingHandler:e.missingHandler}),this.initialMessages={},this.storage=l,this._locale=e.locale,this._fallbackLocale=e.fallbackLocale||e.locale,this._currentRoute="index",e.messages){this.initialMessages={...e.messages};for(const[r,c]of Object.entries(e.messages))this.helper.loadTranslations(r,c)}}cloneStorage(e){const l=new Map;for(const[r,c]of e.translations)l.set(r,{...c});return{translations:l}}clone(e){const l=this.cloneStorage(this.storage);return new v({locale:e||this._locale,fallbackLocale:this._fallbackLocale,plural:this.pluralFunc,missingWarn:this.missingWarn,missingHandler:this.missingHandler,_storage:l})}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,l){const r=`${e}:${l}`;return this.storage.translations.get(r)??null}addTranslations(e,l,r=!0){super.loadTranslationsCore(e,l,r)}addRouteTranslations(e,l,r,c=!0){super.loadRouteTranslationsCore(e,l,r,c)}mergeTranslations(e,l,r){this.helper.mergeTranslation(e,l,r,!0)}clearCache(){const e={...this.initialMessages};if(super.clearCache(),Object.keys(e).length>0)for(const[l,r]of Object.entries(e))this.helper.loadTranslations(l,r)}}let O=null;function _(){return O}function q(a){const{locale:e,fallbackLocale:l,translationDir:r,routingStrategy:c}=a;return O=c||null,{name:"@i18n-micro/astro",hooks:{"astro:config:setup":i=>{const{updateConfig:u}=i,t="virtual:i18n-micro/config",o=`\0${t}`,s={defaultLocale:e,fallbackLocale:l||e,locales:a.locales||[],localeCodes:(a.locales||[]).map(n=>n.code),translationDir:r||null,autoDetect:a.autoDetect??!0,redirectToDefault:a.redirectToDefault??!1,localeCookie:a.localeCookie===null?null:a.localeCookie||"i18n-locale",missingWarn:a.missingWarn??!1};u({vite:{plugins:[{name:"vite-plugin-i18n-micro-config",resolveId(n){if(n===t)return o},load(n){if(n===o)return`export const config = ${JSON.stringify(s)}`}}]}})},"astro:config:done":i=>{const{injectTypes:u}=i;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 A(a){return new v(a)}function B(a,e){if(T.existsSync(a))for(const l of T.readdirSync(a)){const r=R.join(a,l);if(T.statSync(r).isDirectory()){B(r,e);continue}l.endsWith(".json")&&e(r)}}function C(a){const{translationDir:e,rootDir:l=process.cwd(),disablePageLocales:r=!1}=a,c=R.resolve(l,e);if(!T.existsSync(c))return console.warn(`[i18n] Translation directory not found: ${c}`),{root:{},routes:{}};const i={root:{},routes:{}};return B(c,u=>{const t=R.relative(c,u).split(R.sep).join("/");try{const o=T.readFileSync(u,"utf-8"),s=JSON.parse(o);F.storeLoadedTranslationFile(i,t,s,r)}catch(o){console.error(`[i18n] Failed to load translation file: ${u}`,o)}}),i}function W(a,e){const{root:l,routes:r}=C(e);for(const[c,i]of Object.entries(l))a.addTranslations(c,i,!1);for(const[c,i]of Object.entries(r))for(const[u,t]of Object.entries(i))a.addRouteTranslations(u,c,F.mergeRouteTranslationsWithRoot(l[u],t),!1)}function H(a){const{i18n:e,defaultLocale:l,locales:r,localeObjects:c,autoDetect:i=!0,redirectToDefault:u=!1,routingStrategy:t}=a,o=t||_();return async(s,n)=>{if(s.locals.locale&&s.locals.i18n)return n();const f=s.url,h=f.pathname;if(!o){const P=e.clone(l),S=h==="/"||h===""?"index":h.split("/").filter(Boolean).join("-");return P.setRoute(S),s.locals.i18n=P,s.locals.locale=l,s.locals.defaultLocale=l,s.locals.locales=c||r.map(w=>({code:w})),s.locals.currentUrl=f,n()}const g={...o,getCurrentPath:()=>h,getRoute:()=>({fullPath:f.pathname+f.search,query:Object.fromEntries(f.searchParams)})},m=h.split("/").filter(Boolean)[0],L=m!==void 0&&r.includes(m);let y;L&&m?y=m:g.getLocaleFromPath?y=g.getLocaleFromPath(h,l,r):y=l;const d=e.clone(y),b=g.getRouteName?g.getRouteName(h,r):"index";return d.setRoute(b),s.locals.i18n=d,s.locals.locale=y,s.locals.defaultLocale=l,s.locals.locales=c||r.map(P=>({code:P})),s.locals.currentUrl=f,s.locals.routingStrategy=g,n()}}function U(a,e,l,r,c,i="i18n-locale"){const u=_();let t=r;if(u?.getLocaleFromPath)t=u.getLocaleFromPath(a,r,c);else{const s=a.split("/").filter(Boolean)[0];s&&c.includes(s)&&(t=s)}if(i!==null&&t===r&&e.get(i)){const o=e.get(i)?.value;o&&c.includes(o)&&(t=o)}if(t===r)try{const o=N.detectLocaleFromAcceptLanguage(l.get("accept-language")??void 0,c);o&&(t=o)}catch{}return t}function z(a,e,l){const r=a.map(n=>n.code),c=(n,f=[])=>{const h=n.replace(/^\//,"").replace(/\/$/,"");if(!h)return"index";const g=h.split("/").filter(Boolean),p=g[0];return p&&f.includes(p)&&g.shift(),g.length===0?"index":g.join("-")},i=(n,f="en",h=[])=>{const p=n.split("/").filter(Boolean)[0];return p&&h.includes(p)?p:f},u=(n,f,h=[],g)=>{const p=n.split("/").filter(Boolean),m=p[0];return m&&h.includes(m)&&p.shift(),(f!==g||g===void 0)&&p.unshift(f),`/${p.join("/")}`},t=(n,f,h=[],g)=>{const m=(n.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),L=m[0];return L&&h.includes(L)&&m.shift(),(f!==g||g===void 0)&&m.unshift(f),`/${m.join("/")}`};return{getCurrentPath:()=>l?l().pathname:typeof window<"u"?window.location.pathname:"/",getRouteName:c,getLocaleFromPath:i,switchLocalePath:u,localizePath:t,removeLocaleFromPath:(n,f=[])=>{const h=n.split("/").filter(Boolean),g=h[0];return g&&f.includes(g)&&h.shift(),`/${h.join("/")}`},resolvePath:(n,f)=>{const h=typeof n=="string"?n:n.path||"/";return t(h,f,r,e)},getRoute:()=>{if(l){const n=l();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(a){const e=a.locals.i18n;if(!e)throw new Error("i18n instance not found. Make sure i18n middleware is configured.");return e}function k(a){return a.locals.locale||"en"}function I(a){return a.locals.defaultLocale||"en"}function $(a){return a.locals.locales||[]}function M(a){return a.locals.routingStrategy||null}function E(a){const e=D(a),l=k(a),r=I(a),c=$(a),i=c.map(t=>t.code),u=M(a);return{locale:l,defaultLocale:r,locales:c,t:(t,o,s,n)=>e.t(t,o,s,n),ts:(t,o,s,n)=>e.ts(t,o,s,n),tc:(t,o,s)=>e.tc(t,o,s),tn:(t,o)=>e.tn(t,o),td:(t,o)=>e.td(t,o),tdr:(t,o)=>e.tdr(t,o),has:(t,o)=>e.has(t,o),getRoute:()=>e.getRoute(),getRouteName:t=>{const o=t||a.url.pathname;if(u?.getRouteName)return u.getRouteName(o,i);const s=o.replace(/^\//,"").replace(/\/$/,"");if(!s)return"index";const n=s.split("/").filter(Boolean),f=n[0];return f&&i.includes(f)&&n.shift(),n.length===0?"index":n.join("-")},getLocaleFromPath:t=>{const o=t||a.url.pathname;if(u?.getLocaleFromPath)return u.getLocaleFromPath(o,r,i);const n=o.split("/").filter(Boolean)[0];return n&&i.includes(n)?n:r},switchLocalePath:t=>{if(u?.switchLocalePath)return u.switchLocalePath(a.url.pathname,t,i,r);const o=a.url.pathname.split("/").filter(Boolean),s=o[0];return s&&i.includes(s)&&o.shift(),t!==r&&o.unshift(t),`/${o.join("/")}`},localizePath:(t,o)=>{if(u?.localizePath)return u.localizePath(t,o||l,i,r);const n=(t.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),f=n[0];return f&&i.includes(f)&&n.shift(),o&&o!==r&&n.unshift(o),`/${n.join("/")}`},getI18n:()=>e,getBasePath:t=>{const n=(t||a.url).pathname.split("/").filter(Boolean),f=n[0];return f&&i.includes(f)&&n.shift(),n.length>0?`/${n.join("/")}`:"/"},addTranslations:(t,o,s=!0)=>{e.addTranslations(t,o,s)},addRouteTranslations:(t,o,s,n=!0)=>{e.addRouteTranslations(t,o,s,n)},mergeTranslations:(t,o,s)=>{e.mergeTranslations(t,o,s)},clearCache:()=>{e.clearCache()}}}function x(a,e={}){const{baseUrl:l="/",addDirAttribute:r=!0,addSeoAttributes:c=!0}=e,i=k(a),u=I(a),o=$(a).filter(d=>!d.disabled),s=o.filter(d=>d.seo!==!1),n=o.find(d=>d.code===i);if(!n)return{htmlAttrs:{},link:[],meta:[]};const f=n.iso||i,h=n.dir||"auto",g={htmlAttrs:{lang:f,...r?{dir:h}:{}},link:[],meta:[]};if(!c)return g;const p=`${l}${a.url.pathname}`;g.link.push({rel:"canonical",href:p});const m=M(a),L=o.map(d=>d.code);for(const d of s){let b=a.url.pathname;if(m?.switchLocalePath)b=m.switchLocalePath(a.url.pathname,d.code,L,u);else{const S=a.url.pathname.split("/").filter(Boolean),w=S[0];w&&L.includes(w)&&S.shift(),d.code!==u&&S.unshift(d.code),b=`/${S.join("/")}`}const P=`${l}${b}`;g.link.push({rel:"alternate",href:P,hreflang:d.code}),d.iso&&d.iso!==d.code&&g.link.push({rel:"alternate",href:P,hreflang:d.iso})}if(o.find(d=>d.code===u)?.seo!==!1){let d=a.url.pathname;if(m?.switchLocalePath)d=m.switchLocalePath(a.url.pathname,u,L,u);else{const b=a.url.pathname.split("/").filter(Boolean),P=b[0];P&&L.includes(P)&&b.shift(),d=`/${b.join("/")}`}g.link.push({rel:"alternate",href:`${l}${d}`,hreflang:"x-default"})}g.meta.push({property:"og:locale",content:f}),g.meta.push({property:"og:url",content:p});for(const d of s)d.code!==i&&g.meta.push({property:"og:locale:alternate",content:d.og||d.iso||d.code});return g}function J(a,e,l){const r=e.split(".");let c=a;for(let u=0;u<r.length-1;u++){const t=r[u];c[t]||(c[t]={}),c=c[t]}const i=r[r.length-1];i!==void 0&&(c[i]=l)}function V(a,e){const l=D(a),r=k(a),c=I(a),i=l.getRoute(),u={};if(e&&e.length>0){const t={};for(const o of e){const s=l.t(o,void 0,void 0,i);s!=null&&s!==o&&J(t,o,s)}Object.keys(t).length>0&&(u[i]=t)}else{const t=l.getRouteTranslations(r,i);t&&(u[i]=t)}return{locale:r,fallbackLocale:c,currentRoute:i,translations:u}}Object.defineProperty(exports,"FormatService",{enumerable:!0,get:()=>j.FormatService});Object.defineProperty(exports,"defaultPlural",{enumerable:!0,get:()=>j.defaultPlural});Object.defineProperty(exports,"interpolate",{enumerable:!0,get:()=>j.interpolate});exports.AstroI18n=v;exports.createAstroRouterAdapter=z;exports.createI18n=A;exports.createI18nMiddleware=H;exports.detectLocale=U;exports.getDefaultLocale=I;exports.getI18n=D;exports.getI18nProps=V;exports.getLocale=k;exports.getLocales=$;exports.i18nIntegration=q;exports.loadTranslationsFromDir=C;exports.loadTranslationsIntoI18n=W;exports.useI18n=E;exports.useLocaleHead=x;
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 O } from "@i18n-micro/core";
2
- import { FormatService as ie, defaultPlural as ce, interpolate as ue } from "@i18n-micro/core";
3
- import { existsSync as k, readdirSync as _, statSync as C, readFileSync as N } from "node:fs";
4
- import { resolve as M, join as W, relative as A, sep as q } from "node:path";
5
- import { mergeRouteTranslationsWithRoot as H, storeLoadedTranslationFile as U } from "@i18n-micro/utils/parse-path";
6
- import { detectLocaleFromAcceptLanguage as z } from "@i18n-micro/utils/accept-language";
7
- class R extends O {
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 l = e._storage || {
10
+ const o = e._storage || {
10
11
  translations: /* @__PURE__ */ new Map()
11
12
  };
12
13
  if (super({
13
- storage: l,
14
+ storage: o,
14
15
  plural: e.plural,
15
16
  missingWarn: e.missingWarn,
16
- missingHandler: e.missingHandler
17
- }), this.initialMessages = {}, this.storage = l, 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 l = /* @__PURE__ */ new Map();
25
- for (const [r, i] of e.translations)
26
- l.set(r, { ...i });
27
- return { translations: l };
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 l = this.cloneStorage(this.storage);
31
- return new R({
33
+ const o = this.cloneStorage(this.storage);
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: l
40
+ numberFormats: this.formatter.getNumberFormats(),
41
+ datetimeFormats: this.formatter.getDateTimeFormats(),
42
+ _storage: o
38
43
  });
39
44
  }
40
45
  get locale() {
@@ -61,47 +66,55 @@ class R extends O {
61
66
  getRoute() {
62
67
  return this._currentRoute;
63
68
  }
64
- getRouteTranslations(e, l) {
65
- const r = `${e}:${l}`;
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, l, r = !0) {
69
- super.loadTranslationsCore(e, l, r);
73
+ addTranslations(e, o, a = !0) {
74
+ super.loadTranslationsCore(e, o, a);
70
75
  }
71
- addRouteTranslations(e, l, r, i = !0) {
72
- super.loadRouteTranslationsCore(e, l, r, i);
76
+ addRouteTranslations(e, o, a, i = !0) {
77
+ super.loadRouteTranslationsCore(e, o, a, i);
73
78
  }
74
- mergeTranslations(e, l, r) {
75
- this.helper.mergeTranslation(e, l, 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 [l, r] of Object.entries(e))
81
- this.helper.loadTranslations(l, r);
85
+ for (const [o, a] of Object.entries(e))
86
+ this.helper.loadTranslations(o, a);
82
87
  }
83
88
  }
84
- let v = null;
85
- function $() {
86
- return v;
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 X(o) {
89
- const { locale: e, fallbackLocale: l, translationDir: r, routingStrategy: i } = o;
90
- return v = 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, t = "virtual:i18n-micro/config", a = `\0${t}`, s = {
108
+ const { updateConfig: u } = c, s = "virtual:i18n-micro/config", l = `\0${s}`, r = {
96
109
  defaultLocale: e,
97
- fallbackLocale: l || e,
98
- locales: o.locales || [],
99
- localeCodes: (o.locales || []).map((n) => n.code),
100
- translationDir: r || null,
101
- autoDetect: o.autoDetect ?? !0,
102
- redirectToDefault: o.redirectToDefault ?? !1,
103
- localeCookie: o.localeCookie === null ? null : o.localeCookie || "i18n-locale",
104
- missingWarn: o.missingWarn ?? !1
110
+ fallbackLocale: o || e,
111
+ locales: t.locales || [],
112
+ localeCodes: (t.locales || []).map((n) => n.code),
113
+ translationDir: a || null,
114
+ autoDetect: t.autoDetect ?? !0,
115
+ redirectToDefault: t.redirectToDefault ?? !1,
116
+ localeCookie: t.localeCookie === null ? null : t.localeCookie || "i18n-locale",
117
+ missingWarn: t.missingWarn ?? !1
105
118
  };
106
119
  u({
107
120
  vite: {
@@ -109,12 +122,12 @@ function X(o) {
109
122
  {
110
123
  name: "vite-plugin-i18n-micro-config",
111
124
  resolveId(n) {
112
- if (n === t)
113
- return a;
125
+ if (n === s)
126
+ return l;
114
127
  },
115
128
  load(n) {
116
- if (n === a)
117
- return `export const config = ${JSON.stringify(s)}`;
129
+ if (n === l)
130
+ return `export const config = ${JSON.stringify(r)}`;
118
131
  }
119
132
  }
120
133
  ]
@@ -148,131 +161,131 @@ function X(o) {
148
161
  }
149
162
  };
150
163
  }
151
- function Y(o) {
152
- return new R(o);
164
+ function re(t) {
165
+ return new T(t);
153
166
  }
154
- function D(o, e) {
155
- if (k(o))
156
- for (const l of _(o)) {
157
- const r = W(o, l);
158
- if (C(r).isDirectory()) {
159
- D(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
- l.endsWith(".json") && e(r);
175
+ o.endsWith(".json") && e(a);
163
176
  }
164
177
  }
165
- function E(o) {
166
- const { translationDir: e, rootDir: l = process.cwd(), disablePageLocales: r = !1 } = o, i = M(l, e);
167
- if (!k(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 D(i, (u) => {
171
- const t = A(i, u).split(q).join("/");
183
+ return C(i, (u) => {
184
+ const s = x(i, u).split(z).join("/");
172
185
  try {
173
- const a = N(u, "utf-8"), s = JSON.parse(a);
174
- U(c, t, s, r);
175
- } catch (a) {
176
- console.error(`[i18n] Failed to load translation file: ${u}`, a);
186
+ const l = q(u, "utf-8"), r = JSON.parse(l);
187
+ G(c, s, r, a);
188
+ } catch (l) {
189
+ console.error(`[i18n] Failed to load translation file: ${u}`, l);
177
190
  }
178
191
  }), c;
179
192
  }
180
- function Z(o, e) {
181
- const { root: l, routes: r } = E(e);
182
- for (const [i, c] of Object.entries(l))
183
- o.addTranslations(i, c, !1);
184
- for (const [i, c] of Object.entries(r))
185
- for (const [u, t] of Object.entries(c))
186
- o.addRouteTranslations(u, i, H(l[u], t), !1);
193
+ function se(t, e) {
194
+ const { root: o, routes: a } = Q(e);
195
+ for (const [i, c] of Object.entries(o))
196
+ t.addTranslations(i, c, !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 ee(o) {
201
+ function ie(t) {
189
202
  const {
190
203
  i18n: e,
191
204
  // This is the global singleton with cache
192
- defaultLocale: l,
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: t
198
- } = o, a = t || $();
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, d = f.pathname;
203
- if (!a) {
204
- const L = e.clone(l), S = d === "/" || d === "" ? "index" : d.split("/").filter(Boolean).join("-");
205
- return L.setRoute(S), s.locals.i18n = L, s.locals.locale = l, s.locals.defaultLocale = l, s.locals.locales = i || r.map((w) => ({ code: w })), s.locals.currentUrl = f, n();
215
+ const f = r.url, h = f.pathname;
216
+ if (!l) {
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 g = {
208
- ...a,
209
- getCurrentPath: () => d,
220
+ const d = {
221
+ ...l,
222
+ getCurrentPath: () => h,
210
223
  getRoute: () => ({
211
224
  fullPath: f.pathname + f.search,
212
225
  query: Object.fromEntries(f.searchParams)
213
226
  })
214
- }, m = d.split("/").filter(Boolean)[0], P = m !== void 0 && r.includes(m);
215
- let y;
216
- P && m ? y = m : g.getLocaleFromPath ? y = g.getLocaleFromPath(d, l, r) : y = l;
217
- const h = e.clone(y), b = g.getRouteName ? g.getRouteName(d, r) : "index";
218
- return h.setRoute(b), s.locals.i18n = h, s.locals.locale = y, s.locals.defaultLocale = l, s.locals.locales = i || r.map((L) => ({ code: L })), s.locals.currentUrl = f, s.locals.routingStrategy = g, n();
227
+ }, p = h.split("/").filter(Boolean)[0], b = p !== void 0 && a.includes(p);
228
+ let P;
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 te(o, e, l, r, i, c = "i18n-locale") {
222
- const u = $();
223
- let t = r;
224
- if (u?.getLocaleFromPath)
225
- t = u.getLocaleFromPath(o, 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 = o.split("/").filter(Boolean)[0];
228
- s && i.includes(s) && (t = s);
240
+ const n = t.split("/").filter(Boolean)[0];
241
+ n && i.includes(n) && (l = n);
229
242
  }
230
- if (c !== null && t === r && e.get(c)) {
231
- const a = e.get(c)?.value;
232
- a && i.includes(a) && (t = a);
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 (t === r)
247
+ if (l === a)
235
248
  try {
236
- const a = z(l.get("accept-language") ?? void 0, i);
237
- a && (t = a);
249
+ const r = J(o.get("accept-language") ?? void 0, i);
250
+ r && (l = r);
238
251
  } catch {
239
252
  }
240
- return t;
253
+ return l;
241
254
  }
242
- function ne(o, e, l) {
243
- const r = o.map((n) => n.code), i = (n, f = []) => {
244
- const d = n.replace(/^\//, "").replace(/\/$/, "");
245
- if (!d)
255
+ function ue(t, e, o) {
256
+ const a = t.map((n) => n.code), i = (n, f = []) => {
257
+ const h = n.replace(/^\//, "").replace(/\/$/, "");
258
+ if (!h)
246
259
  return "index";
247
- const g = d.split("/").filter(Boolean), p = g[0];
248
- return p && f.includes(p) && g.shift(), g.length === 0 ? "index" : g.join("-");
249
- }, c = (n, f = "en", d = []) => {
250
- const p = n.split("/").filter(Boolean)[0];
251
- return p && d.includes(p) ? p : f;
252
- }, u = (n, f, d = [], g) => {
253
- const p = n.split("/").filter(Boolean), m = p[0];
254
- return m && d.includes(m) && p.shift(), (f !== g || g === void 0) && p.unshift(f), `/${p.join("/")}`;
255
- }, t = (n, f, d = [], g) => {
256
- const m = (n.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), P = m[0];
257
- return P && d.includes(P) && m.shift(), (f !== g || g === void 0) && m.unshift(f), `/${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("-");
262
+ }, c = (n, f = "en", h = []) => {
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) => {
269
+ const p = (n.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), b = p[0];
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: () => l ? l().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: t,
277
+ localizePath: s,
265
278
  removeLocaleFromPath: (n, f = []) => {
266
- const d = n.split("/").filter(Boolean), g = d[0];
267
- return g && f.includes(g) && d.shift(), `/${d.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
- const d = typeof n == "string" ? n : n.path || "/";
271
- return t(d, f, r, e);
283
+ const h = typeof n == "string" ? n : n.path || "/";
284
+ return s(h, f, a, e);
272
285
  },
273
286
  getRoute: () => {
274
- if (l) {
275
- const n = l();
287
+ if (o) {
288
+ const n = o();
276
289
  return {
277
290
  fullPath: n.pathname + n.search,
278
291
  query: Object.fromEntries(n.searchParams)
@@ -299,206 +312,242 @@ function ne(o, e, l) {
299
312
  }
300
313
  };
301
314
  }
302
- function F(o) {
303
- const e = o.locals.i18n;
315
+ function R(t) {
316
+ const e = t.og?.trim();
317
+ if (e) return e;
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);
323
+ if (c.length >= 2 && u.length === 2)
324
+ return `${c.toLowerCase()}_${u.toUpperCase()}`;
325
+ }
326
+ const i = o.indexOf("-");
327
+ if (i > 0) {
328
+ const c = o.slice(0, i), u = o.slice(i + 1);
329
+ if (c.length >= 2 && u.length === 2 && /^[A-Za-z]{2}$/.test(u))
330
+ return `${c.toLowerCase()}_${u.toUpperCase()}`;
331
+ }
332
+ return null;
333
+ }
334
+ function k(t, e = {}) {
335
+ if (R(t) !== null || e.missingWarn === !1 || process.env.NODE_ENV === "production") return;
336
+ const o = e.tag ?? "og:locale", a = String(t.code ?? "unknown"), i = t.iso ? `, iso: '${t.iso}'` : "";
337
+ console.warn(
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').`
339
+ );
340
+ }
341
+ function B(t) {
342
+ const e = t.locals.i18n;
304
343
  if (!e)
305
344
  throw new Error("i18n instance not found. Make sure i18n middleware is configured.");
306
345
  return e;
307
346
  }
308
- function T(o) {
309
- return o.locals.locale || "en";
347
+ function $(t) {
348
+ return t.locals.locale || "en";
310
349
  }
311
- function j(o) {
312
- return o.locals.defaultLocale || "en";
350
+ function v(t) {
351
+ return t.locals.defaultLocale || "en";
313
352
  }
314
- function B(o) {
315
- return o.locals.locales || [];
353
+ function I(t) {
354
+ return t.locals.locales || [];
316
355
  }
317
- function I(o) {
318
- return o.locals.routingStrategy || null;
356
+ function N(t) {
357
+ return t.locals.routingStrategy || null;
319
358
  }
320
- function oe(o) {
321
- const e = F(o), l = T(o), r = j(o), i = B(o), c = i.map((t) => t.code), u = I(o);
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);
322
361
  return {
323
362
  // Current locale
324
- locale: l,
325
- defaultLocale: r,
363
+ locale: o,
364
+ defaultLocale: a,
326
365
  locales: i,
327
366
  // Translation methods
328
- t: (t, a, s, n) => e.t(t, a, s, n),
329
- ts: (t, a, s, n) => e.ts(t, a, s, n),
330
- tc: (t, a, s) => e.tc(t, a, s),
331
- tn: (t, a) => e.tn(t, a),
332
- td: (t, a) => e.td(t, a),
333
- tdr: (t, a) => e.tdr(t, a),
334
- has: (t, a) => e.has(t, a),
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),
335
374
  // Route management
336
375
  getRoute: () => e.getRoute(),
337
- getRouteName: (t) => {
338
- const a = t || o.url.pathname;
376
+ getRouteName: (s) => {
377
+ const l = s || t.url.pathname;
339
378
  if (u?.getRouteName)
340
- return u.getRouteName(a, c);
341
- const s = a.replace(/^\//, "").replace(/\/$/, "");
342
- if (!s) return "index";
343
- const n = s.split("/").filter(Boolean), f = n[0];
379
+ return u.getRouteName(l, c);
380
+ const r = l.replace(/^\//, "").replace(/\/$/, "");
381
+ if (!r) return "index";
382
+ const n = r.split("/").filter(Boolean), f = n[0];
344
383
  return f && c.includes(f) && n.shift(), n.length === 0 ? "index" : n.join("-");
345
384
  },
346
- getLocaleFromPath: (t) => {
347
- const a = t || o.url.pathname;
385
+ getLocaleFromPath: (s) => {
386
+ const l = s || t.url.pathname;
348
387
  if (u?.getLocaleFromPath)
349
- return u.getLocaleFromPath(a, r, c);
350
- const n = a.split("/").filter(Boolean)[0];
351
- return n && c.includes(n) ? n : r;
388
+ return u.getLocaleFromPath(l, a, c);
389
+ const n = l.split("/").filter(Boolean)[0];
390
+ return n && c.includes(n) ? n : a;
352
391
  },
353
392
  // Path utilities
354
- switchLocalePath: (t) => {
393
+ switchLocalePath: (s) => {
355
394
  if (u?.switchLocalePath)
356
- return u.switchLocalePath(o.url.pathname, t, c, r);
357
- const a = o.url.pathname.split("/").filter(Boolean), s = a[0];
358
- return s && c.includes(s) && a.shift(), t !== r && a.unshift(t), `/${a.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("/")}`;
359
398
  },
360
- localizePath: (t, a) => {
399
+ localizePath: (s, l) => {
361
400
  if (u?.localizePath)
362
- return u.localizePath(t, a || l, c, r);
363
- const n = (t.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), f = n[0];
364
- return f && c.includes(f) && n.shift(), a && a !== r && n.unshift(a), `/${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("/")}`;
365
404
  },
366
405
  // Get i18n instance
367
406
  getI18n: () => e,
368
407
  // Get base path without locale (for rewrite)
369
- getBasePath: (t) => {
370
- const n = (t || o.url).pathname.split("/").filter(Boolean), f = n[0];
408
+ getBasePath: (s) => {
409
+ const n = (s || t.url).pathname.split("/").filter(Boolean), f = n[0];
371
410
  return f && c.includes(f) && n.shift(), n.length > 0 ? `/${n.join("/")}` : "/";
372
411
  },
373
412
  // Translation management
374
- addTranslations: (t, a, s = !0) => {
375
- e.addTranslations(t, a, s);
413
+ addTranslations: (s, l, r = !0) => {
414
+ e.addTranslations(s, l, r);
376
415
  },
377
- addRouteTranslations: (t, a, s, n = !0) => {
378
- e.addRouteTranslations(t, a, s, n);
416
+ addRouteTranslations: (s, l, r, n = !0) => {
417
+ e.addRouteTranslations(s, l, r, n);
379
418
  },
380
- mergeTranslations: (t, a, s) => {
381
- e.mergeTranslations(t, a, s);
419
+ mergeTranslations: (s, l, r) => {
420
+ e.mergeTranslations(s, l, r);
382
421
  },
383
422
  clearCache: () => {
384
423
  e.clearCache();
385
424
  }
386
425
  };
387
426
  }
388
- function ae(o, e = {}) {
389
- const { baseUrl: l = "/", addDirAttribute: r = !0, addSeoAttributes: i = !0 } = e, c = T(o), u = j(o), a = B(o).filter((h) => !h.disabled), s = a.filter((h) => h.seo !== !1), n = a.find((h) => h.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);
390
429
  if (!n)
391
430
  return { htmlAttrs: {}, link: [], meta: [] };
392
- const f = n.iso || c, d = n.dir || "auto", g = {
431
+ const f = n.iso || c, h = R(n), d = n.dir || "auto", m = {
393
432
  htmlAttrs: {
394
433
  lang: f,
395
- ...r ? { dir: d } : {}
434
+ ...a ? { dir: d } : {}
396
435
  },
397
436
  link: [],
398
437
  meta: []
399
438
  };
400
439
  if (!i)
401
- return g;
402
- const p = `${l}${o.url.pathname}`;
403
- g.link.push({
440
+ return m;
441
+ const p = `${o}${t.url.pathname}`;
442
+ m.link.push({
404
443
  rel: "canonical",
405
444
  href: p
406
445
  });
407
- const m = I(o), P = a.map((h) => h.code);
408
- for (const h of s) {
409
- let b = o.url.pathname;
410
- if (m?.switchLocalePath)
411
- b = m.switchLocalePath(o.url.pathname, h.code, P, u);
446
+ const b = N(t), P = l.map((g) => g.code);
447
+ for (const g of r) {
448
+ let L = t.url.pathname;
449
+ if (b?.switchLocalePath)
450
+ L = b.switchLocalePath(t.url.pathname, g.code, P, u);
412
451
  else {
413
- const S = o.url.pathname.split("/").filter(Boolean), w = S[0];
414
- w && P.includes(w) && S.shift(), h.code !== u && S.unshift(h.code), b = `/${S.join("/")}`;
452
+ const y = t.url.pathname.split("/").filter(Boolean), j = y[0];
453
+ j && P.includes(j) && y.shift(), g.code !== u && y.unshift(g.code), L = `/${y.join("/")}`;
415
454
  }
416
- const L = `${l}${b}`;
417
- g.link.push({
455
+ const S = `${o}${L}`;
456
+ m.link.push({
418
457
  rel: "alternate",
419
- href: L,
420
- hreflang: h.code
421
- }), h.iso && h.iso !== h.code && g.link.push({
458
+ href: S,
459
+ hreflang: g.code
460
+ }), g.iso && g.iso !== g.code && m.link.push({
422
461
  rel: "alternate",
423
- href: L,
424
- hreflang: h.iso
462
+ href: S,
463
+ hreflang: g.iso
425
464
  });
426
465
  }
427
- if (a.find((h) => h.code === u)?.seo !== !1) {
428
- let h = o.url.pathname;
429
- if (m?.switchLocalePath)
430
- h = m.switchLocalePath(o.url.pathname, u, P, u);
466
+ if (l.find((g) => g.code === u)?.seo !== !1) {
467
+ let g = t.url.pathname;
468
+ if (b?.switchLocalePath)
469
+ g = b.switchLocalePath(t.url.pathname, u, P, u);
431
470
  else {
432
- const b = o.url.pathname.split("/").filter(Boolean), L = b[0];
433
- L && P.includes(L) && b.shift(), h = `/${b.join("/")}`;
471
+ const L = t.url.pathname.split("/").filter(Boolean), S = L[0];
472
+ S && P.includes(S) && L.shift(), g = `/${L.join("/")}`;
434
473
  }
435
- g.link.push({
474
+ m.link.push({
436
475
  rel: "alternate",
437
- href: `${l}${h}`,
476
+ href: `${o}${g}`,
438
477
  hreflang: "x-default"
439
478
  });
440
479
  }
441
- g.meta.push({
480
+ h ? m.meta.push({
442
481
  property: "og:locale",
443
- content: f
444
- }), g.meta.push({
482
+ content: h
483
+ }) : k(n, { tag: "og:locale" }), m.meta.push({
445
484
  property: "og:url",
446
485
  content: p
447
486
  });
448
- for (const h of s)
449
- h.code !== c && g.meta.push({
487
+ for (const g of r) {
488
+ if (g.code === c) continue;
489
+ const L = R(g);
490
+ if (!L) {
491
+ k(g, { tag: "og:locale:alternate" });
492
+ continue;
493
+ }
494
+ m.meta.push({
450
495
  property: "og:locale:alternate",
451
- content: h.og || h.iso || h.code
496
+ content: L
452
497
  });
453
- return g;
498
+ }
499
+ return m;
454
500
  }
455
- function x(o, e, l) {
456
- const r = e.split(".");
457
- let i = o;
458
- for (let u = 0; u < r.length - 1; u++) {
459
- const t = r[u];
460
- i[t] || (i[t] = {}), i = i[t];
501
+ function X(t, e, o) {
502
+ const a = e.split(".");
503
+ let i = t;
504
+ for (let u = 0; u < a.length - 1; u++) {
505
+ const s = a[u];
506
+ i[s] || (i[s] = {}), i = i[s];
461
507
  }
462
- const c = r[r.length - 1];
463
- c !== void 0 && (i[c] = l);
508
+ const c = a[a.length - 1];
509
+ c !== void 0 && (i[c] = o);
464
510
  }
465
- function le(o, e) {
466
- const l = F(o), r = T(o), i = j(o), c = l.getRoute(), u = {};
511
+ function he(t, e) {
512
+ const o = B(t), a = $(t), i = v(t), c = o.getRoute(), u = {};
467
513
  if (e && e.length > 0) {
468
- const t = {};
469
- for (const a of e) {
470
- const s = l.t(a, void 0, void 0, c);
471
- s != null && s !== a && x(t, a, s);
514
+ const s = {};
515
+ for (const l of e) {
516
+ const r = o.t(l, void 0, void 0, c);
517
+ r != null && r !== l && X(s, l, r);
472
518
  }
473
- Object.keys(t).length > 0 && (u[c] = t);
519
+ Object.keys(s).length > 0 && (u[c] = s);
474
520
  } else {
475
- const t = l.getRouteTranslations(r, c);
476
- t && (u[c] = t);
521
+ const s = o.getRouteTranslations(a, c);
522
+ s && (u[c] = s);
477
523
  }
478
524
  return {
479
- locale: r,
525
+ locale: a,
480
526
  fallbackLocale: i,
481
527
  currentRoute: c,
482
528
  translations: u
483
529
  };
484
530
  }
485
531
  export {
486
- R as AstroI18n,
487
- ie as FormatService,
488
- ne as createAstroRouterAdapter,
489
- Y as createI18n,
490
- ee as createI18nMiddleware,
491
- ce as defaultPlural,
492
- te as detectLocale,
493
- j as getDefaultLocale,
494
- F as getI18n,
495
- le as getI18nProps,
496
- T as getLocale,
497
- B as getLocales,
498
- X as i18nIntegration,
499
- ue as interpolate,
500
- E as loadTranslationsFromDir,
501
- Z as loadTranslationsIntoI18n,
502
- oe as useI18n,
503
- ae as useLocaleHead
532
+ T as AstroI18n,
533
+ pe as FormatService,
534
+ ue as createAstroRouterAdapter,
535
+ re as createI18n,
536
+ ie as createI18nMiddleware,
537
+ Le as defaultPlural,
538
+ ce as detectLocale,
539
+ v as getDefaultLocale,
540
+ O as getGlobalRoutingStrategy,
541
+ B as getI18n,
542
+ he as getI18nProps,
543
+ $ as getLocale,
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
504
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.6",
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/utils": "1.0.1",
81
- "@i18n-micro/core": "1.3.2",
82
- "@i18n-micro/node": "1.2.3",
83
- "@i18n-micro/types": "1.2.4"
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
@@ -1,4 +1,5 @@
1
1
  import type { CleanTranslation, Locale, Params, TranslationKey, Translations } from '@i18n-micro/types'
2
+ import { resolveOgLocale, warnUnresolvedOgLocale } from '@i18n-micro/utils/resolve-og-locale'
2
3
  import type { AstroGlobal } from 'astro'
3
4
  import type { AstroI18n } from './composer'
4
5
  import type { I18nRoutingStrategy } from './router/types'
@@ -72,15 +73,9 @@ export function useI18n(astro: AstroGlobal) {
72
73
  tc: (key: TranslationKey, count: number | Params, defaultValue?: string): string => {
73
74
  return i18n.tc(key, count, defaultValue)
74
75
  },
75
- tn: (value: number, options?: Intl.NumberFormatOptions): string => {
76
- return i18n.tn(value, options)
77
- },
78
- td: (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
79
- return i18n.td(value, options)
80
- },
81
- tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
82
- return i18n.tdr(value, options)
83
- },
76
+ tn: i18n.tn.bind(i18n),
77
+ td: i18n.td.bind(i18n),
78
+ tdr: i18n.tdr.bind(i18n),
84
79
  has: (key: TranslationKey, routeName?: string): boolean => {
85
80
  return i18n.has(key, routeName)
86
81
  },
@@ -225,6 +220,7 @@ export function useLocaleHead(astro: AstroGlobal, options: LocaleHeadOptions = {
225
220
  }
226
221
 
227
222
  const currentIso = currentLocaleObj.iso || locale
223
+ const currentOg = resolveOgLocale(currentLocaleObj)
228
224
  const currentDir = currentLocaleObj.dir || 'auto'
229
225
 
230
226
  const result: LocaleHeadResult = {
@@ -309,10 +305,14 @@ export function useLocaleHead(astro: AstroGlobal, options: LocaleHeadOptions = {
309
305
  }
310
306
 
311
307
  // Open Graph locale
312
- result.meta.push({
313
- property: 'og:locale',
314
- content: currentIso,
315
- })
308
+ if (currentOg) {
309
+ result.meta.push({
310
+ property: 'og:locale',
311
+ content: currentOg,
312
+ })
313
+ } else {
314
+ warnUnresolvedOgLocale(currentLocaleObj, { tag: 'og:locale' })
315
+ }
316
316
 
317
317
  result.meta.push({
318
318
  property: 'og:url',
@@ -322,9 +322,14 @@ export function useLocaleHead(astro: AstroGlobal, options: LocaleHeadOptions = {
322
322
  // Alternate OG locales
323
323
  for (const loc of localesForSeo) {
324
324
  if (loc.code === locale) continue
325
+ const ogAlt = resolveOgLocale(loc)
326
+ if (!ogAlt) {
327
+ warnUnresolvedOgLocale(loc, { tag: 'og:locale:alternate' })
328
+ continue
329
+ }
325
330
  result.meta.push({
326
331
  property: 'og:locale:alternate',
327
- content: loc.og || loc.iso || loc.code,
332
+ content: ogAlt,
328
333
  })
329
334
  }
330
335