@i18n-micro/astro 1.3.3 → 1.3.5

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.
@@ -0,0 +1,6 @@
1
+ export type { I18nState } from './core';
2
+ export { hasTranslation, translate } from './core';
3
+ export { I18nProvider as I18nProviderPreact, useAstroI18n as useAstroI18nPreact } from './preact';
4
+ export { I18nProvider, useAstroI18n as useAstroI18nReact } from './react';
5
+ export { createI18nStore, useAstroI18n as useAstroI18nSvelte } from './svelte';
6
+ export { provideI18n, useAstroI18n as useAstroI18nVue } from './vue';
File without changes
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"),j=require("node:fs"),D=require("node:path");class T extends R.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[a,i]of Object.entries(e.messages))this.helper.loadTranslations(a,i)}}cloneStorage(e){const l=new Map;for(const[a,i]of e.translations)l.set(a,{...i});return{translations:l}}clone(e){const l=this.cloneStorage(this.storage);return new T({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 a=`${e}:${l}`;return this.storage.translations.get(a)??null}addTranslations(e,l,a=!0){super.loadTranslationsCore(e,l,a)}addRouteTranslations(e,l,a,i=!0){super.loadRouteTranslationsCore(e,l,a,i)}mergeTranslations(e,l,a){this.helper.mergeTranslation(e,l,a,!0)}clearCache(){const e={...this.initialMessages};if(super.clearCache(),Object.keys(e).length>0)for(const[l,a]of Object.entries(e))this.helper.loadTranslations(l,a)}}let F=null;function B(){return F}function C(t){const{locale:e,fallbackLocale:l,translationDir:a,routingStrategy:i}=t;return F=i||null,{name:"@i18n-micro/astro",hooks:{"astro:config:setup":c=>{const{updateConfig:u}=c,n="virtual:i18n-micro/config",s=`\0${n}`,r={defaultLocale:e,fallbackLocale:l||e,locales:t.locales||[],localeCodes:(t.locales||[]).map(o=>o.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(o){if(o===n)return s},load(o){if(o===s)return`export const config = ${JSON.stringify(r)}`}}]}})},"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 w=require("@i18n-micro/core"),j=require("node:fs"),T=require("node:path"),D=require("@i18n-micro/utils/parse-path"),M=require("@i18n-micro/utils/accept-language");class v extends w.BaseI18n{constructor(e){const o=e._storage||{translations:new Map};if(super({storage:o,plural:e.plural,missingWarn:e.missingWarn,missingHandler:e.missingHandler}),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[n,s]of Object.entries(e.messages))this.helper.loadTranslations(n,s)}}cloneStorage(e){const o=new Map;for(const[n,s]of e.translations)o.set(n,{...s});return{translations:o}}clone(e){const o=this.cloneStorage(this.storage);return new v({locale:e||this._locale,fallbackLocale:this._fallbackLocale,plural:this.pluralFunc,missingWarn:this.missingWarn,missingHandler:this.missingHandler,_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 n=`${e}:${o}`;return this.storage.translations.get(n)??null}addTranslations(e,o,n=!0){super.loadTranslationsCore(e,o,n)}addRouteTranslations(e,o,n,s=!0){super.loadRouteTranslationsCore(e,o,n,s)}mergeTranslations(e,o,n){this.helper.mergeTranslation(e,o,n,!0)}clearCache(){const e={...this.initialMessages};if(super.clearCache(),Object.keys(e).length>0)for(const[o,n]of Object.entries(e))this.helper.loadTranslations(o,n)}}let B=null;function O(){return B}function q(t){const{locale:e,fallbackLocale:o,translationDir:n,routingStrategy:s}=t;return B=s||null,{name:"@i18n-micro/astro",hooks:{"astro:config:setup":c=>{const{updateConfig:u}=c,a="virtual:i18n-micro/config",r=`\0${a}`,i={defaultLocale:e,fallbackLocale:o||e,locales:t.locales||[],localeCodes:(t.locales||[]).map(l=>l.code),translationDir:n||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(l){if(l===a)return r},load(l){if(l===r)return`export const config = ${JSON.stringify(i)}`}}]}})},"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 N(t){return new T(t)}function O(t){const{translationDir:e,rootDir:l=process.cwd(),disablePageLocales:a=!1}=t,i=D.resolve(l,e);if(!j.existsSync(i))return console.warn(`[i18n] Translation directory not found: ${i}`),{root:{},routes:{}};const c={},u={},n=(s,r="")=>{if(!j.existsSync(s))return;const o=j.readdirSync(s);for(const f of o){const g=D.join(s,f);if(j.statSync(g).isDirectory())f==="pages"&&!a?n(g,""):r||a?n(g,r):n(g,f);else if(f.endsWith(".json")){const p=f.replace(".json","");try{const m=j.readFileSync(g,"utf-8"),P=JSON.parse(m);r&&!a?(u[r]||(u[r]={}),u[r][p]=P):c[p]=P}catch(m){console.error(`[i18n] Failed to load translation file: ${g}`,m)}}}};return n(i),{root:c,routes:u}}function q(t,e){const{root:l,routes:a}=O(e);for(const[i,c]of Object.entries(l))t.addTranslations(i,c,!1);for(const[i,c]of Object.entries(a))for(const[u,n]of Object.entries(c)){const s=l[u]||{};t.addRouteTranslations(u,i,{...s,...n},!1)}}function M(t){const{i18n:e,defaultLocale:l,locales:a,localeObjects:i,autoDetect:c=!0,redirectToDefault:u=!1,routingStrategy:n}=t,s=n||B();return async(r,o)=>{if(r.locals.locale&&r.locals.i18n)return o();const f=r.url,g=f.pathname;if(!s){const L=e.clone(l),S=g==="/"||g===""?"index":g.split("/").filter(Boolean).join("-");return L.setRoute(S),r.locals.i18n=L,r.locals.locale=l,r.locals.defaultLocale=l,r.locals.locales=i||a.map(w=>({code:w})),r.locals.currentUrl=f,o()}const h={...s,getCurrentPath:()=>g,getRoute:()=>({fullPath:f.pathname+f.search,query:Object.fromEntries(f.searchParams)})},m=g.split("/").filter(Boolean)[0],P=m!==void 0&&a.includes(m);let y;P&&m?y=m:h.getLocaleFromPath?y=h.getLocaleFromPath(g,l,a):y=l;const d=e.clone(y),b=h.getRouteName?h.getRouteName(g,a):"index";return d.setRoute(b),r.locals.i18n=d,r.locals.locale=y,r.locals.defaultLocale=l,r.locals.locales=i||a.map(L=>({code:L})),r.locals.currentUrl=f,r.locals.routingStrategy=h,o()}}function A(t){const e=[],l=t.split(",");for(const a of l){const[i,c="1.0"]=a.trim().split(";q=");if(Number.parseFloat(c)>0&&i){const n=i.split("-")[0]?.toLowerCase();n&&(e.push(n),i!==n&&e.push(i.toLowerCase()))}}return e}function W(t,e,l,a,i,c="i18n-locale"){const u=B();let n=a;if(u?.getLocaleFromPath)n=u.getLocaleFromPath(t,a,i);else{const r=t.split("/").filter(Boolean)[0];r&&i.includes(r)&&(n=r)}if(c!==null&&n===a&&e.get(c)){const s=e.get(c)?.value;s&&i.includes(s)&&(n=s)}if(n===a)try{const s=l.get("accept-language");if(s){const r=A(s);for(const o of r)if(i.includes(o)){n=o;break}}}catch{}return n}function z(t,e,l){const a=t.map(o=>o.code),i=(o,f=[])=>{const g=o.replace(/^\//,"").replace(/\/$/,"");if(!g)return"index";const h=g.split("/").filter(Boolean),p=h[0];return p&&f.includes(p)&&h.shift(),h.length===0?"index":h.join("-")},c=(o,f="en",g=[])=>{const p=o.split("/").filter(Boolean)[0];return p&&g.includes(p)?p:f},u=(o,f,g=[],h)=>{const p=o.split("/").filter(Boolean),m=p[0];return m&&g.includes(m)&&p.shift(),(f!==h||h===void 0)&&p.unshift(f),`/${p.join("/")}`},n=(o,f,g=[],h)=>{const m=(o.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),P=m[0];return P&&g.includes(P)&&m.shift(),(f!==h||h===void 0)&&m.unshift(f),`/${m.join("/")}`};return{getCurrentPath:()=>l?l().pathname:typeof window<"u"?window.location.pathname:"/",getRouteName:i,getLocaleFromPath:c,switchLocalePath:u,localizePath:n,removeLocaleFromPath:(o,f=[])=>{const g=o.split("/").filter(Boolean),h=g[0];return h&&f.includes(h)&&g.shift(),`/${g.join("/")}`},resolvePath:(o,f)=>{const g=typeof o=="string"?o:o.path||"/";return n(g,f,a,e)},getRoute:()=>{if(l){const o=l();return{fullPath:o.pathname+o.search,query:Object.fromEntries(o.searchParams)}}if(typeof window<"u"){const o=new URL(window.location.href);return{fullPath:o.pathname+o.search,query:Object.fromEntries(o.searchParams)}}return{fullPath:"/",query:{}}},push:o=>{typeof window<"u"&&(window.location.href=o.path)},replace:o=>{typeof window<"u"&&window.location.replace(o.path)}}}function H(t,e=[]){const l=t.replace(/^\//,"").replace(/\/$/,"");if(!l)return"index";const a=l.split("/").filter(Boolean),i=a[0];return i&&e.includes(i)&&a.shift(),a.length===0?"index":a.join("-")}function U(t,e="en",l=[]){const i=t.split("/").filter(Boolean)[0];return i&&l.includes(i)?i:e}function E(t,e,l=[],a){const i=t.split("/").filter(Boolean),c=i[0];return c&&l.includes(c)&&i.shift(),(e!==a||a===void 0)&&i.unshift(e),`/${i.join("/")}`}function J(t,e,l=[],a){const c=(t.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),u=c[0];return u&&l.includes(u)&&c.shift(),(e!==a||a===void 0)&&c.unshift(e),`/${c.join("/")}`}function V(t,e=[]){const l=t.split("/").filter(Boolean),a=l[0];return a&&e.includes(a)&&l.shift(),`/${l.join("/")}`}function k(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 I(t){return t.locals.locales||[]}function _(t){return t.locals.routingStrategy||null}function G(t){const e=k(t),l=v(t),a=$(t),i=I(t),c=i.map(n=>n.code),u=_(t);return{locale:l,defaultLocale:a,locales:i,t:(n,s,r,o)=>e.t(n,s,r,o),ts:(n,s,r,o)=>e.ts(n,s,r,o),tc:(n,s,r)=>e.tc(n,s,r),tn:(n,s)=>e.tn(n,s),td:(n,s)=>e.td(n,s),tdr:(n,s)=>e.tdr(n,s),has:(n,s)=>e.has(n,s),getRoute:()=>e.getRoute(),getRouteName:n=>{const s=n||t.url.pathname;if(u?.getRouteName)return u.getRouteName(s,c);const r=s.replace(/^\//,"").replace(/\/$/,"");if(!r)return"index";const o=r.split("/").filter(Boolean),f=o[0];return f&&c.includes(f)&&o.shift(),o.length===0?"index":o.join("-")},getLocaleFromPath:n=>{const s=n||t.url.pathname;if(u?.getLocaleFromPath)return u.getLocaleFromPath(s,a,c);const o=s.split("/").filter(Boolean)[0];return o&&c.includes(o)?o:a},switchLocalePath:n=>{if(u?.switchLocalePath)return u.switchLocalePath(t.url.pathname,n,c,a);const s=t.url.pathname.split("/").filter(Boolean),r=s[0];return r&&c.includes(r)&&s.shift(),n!==a&&s.unshift(n),`/${s.join("/")}`},localizePath:(n,s)=>{if(u?.localizePath)return u.localizePath(n,s||l,c,a);const o=(n.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),f=o[0];return f&&c.includes(f)&&o.shift(),s&&s!==a&&o.unshift(s),`/${o.join("/")}`},getI18n:()=>e,getBasePath:n=>{const o=(n||t.url).pathname.split("/").filter(Boolean),f=o[0];return f&&c.includes(f)&&o.shift(),o.length>0?`/${o.join("/")}`:"/"},addTranslations:(n,s,r=!0)=>{e.addTranslations(n,s,r)},addRouteTranslations:(n,s,r,o=!0)=>{e.addRouteTranslations(n,s,r,o)},mergeTranslations:(n,s,r)=>{e.mergeTranslations(n,s,r)},clearCache:()=>{e.clearCache()}}}function K(t,e={}){const{baseUrl:l="/",addDirAttribute:a=!0,addSeoAttributes:i=!0}=e,c=v(t),u=$(t),s=I(t).filter(d=>!d.disabled),r=s.filter(d=>d.seo!==!1),o=s.find(d=>d.code===c);if(!o)return{htmlAttrs:{},link:[],meta:[]};const f=o.iso||c,g=o.dir||"auto",h={htmlAttrs:{lang:f,...a?{dir:g}:{}},link:[],meta:[]};if(!i)return h;const p=`${l}${t.url.pathname}`;h.link.push({rel:"canonical",href:p});const m=_(t),P=s.map(d=>d.code);for(const d of r){let b=t.url.pathname;if(m?.switchLocalePath)b=m.switchLocalePath(t.url.pathname,d.code,P,u);else{const S=t.url.pathname.split("/").filter(Boolean),w=S[0];w&&P.includes(w)&&S.shift(),d.code!==u&&S.unshift(d.code),b=`/${S.join("/")}`}const L=`${l}${b}`;h.link.push({rel:"alternate",href:L,hreflang:d.code}),d.iso&&d.iso!==d.code&&h.link.push({rel:"alternate",href:L,hreflang:d.iso})}if(s.find(d=>d.code===u)?.seo!==!1){let d=t.url.pathname;if(m?.switchLocalePath)d=m.switchLocalePath(t.url.pathname,u,P,u);else{const b=t.url.pathname.split("/").filter(Boolean),L=b[0];L&&P.includes(L)&&b.shift(),d=`/${b.join("/")}`}h.link.push({rel:"alternate",href:`${l}${d}`,hreflang:"x-default"})}h.meta.push({property:"og:locale",content:f}),h.meta.push({property:"og:url",content:p});for(const d of r)d.code!==c&&h.meta.push({property:"og:locale:alternate",content:d.og||d.iso||d.code});return h}function Q(t,e,l){const a=e.split(".");let i=t;for(let u=0;u<a.length-1;u++){const n=a[u];i[n]||(i[n]={}),i=i[n]}const c=a[a.length-1];c!==void 0&&(i[c]=l)}function X(t,e){const l=k(t),a=v(t),i=$(t),c=l.getRoute(),u={};if(e&&e.length>0){const n={};for(const s of e){const r=l.t(s,void 0,void 0,c);r!=null&&r!==s&&Q(n,s,r)}Object.keys(n).length>0&&(u[c]=n)}else{const n=l.getRouteTranslations(a,c);n&&(u[c]=n)}return{locale:a,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=T;exports.createAstroRouterAdapter=z;exports.createI18n=N;exports.createI18nMiddleware=M;exports.detectLocale=W;exports.getDefaultLocale=$;exports.getI18n=k;exports.getI18nProps=X;exports.getLocale=v;exports.getLocaleFromPath=U;exports.getLocales=I;exports.getRouteName=H;exports.i18nIntegration=C;exports.loadTranslationsFromDir=O;exports.loadTranslationsIntoI18n=q;exports.localizePath=J;exports.removeLocaleFromPath=V;exports.switchLocalePath=E;exports.useI18n=G;exports.useLocaleHead=K;
17
+ `})}}}}function A(t){return new v(t)}function _(t,e){if(j.existsSync(t))for(const o of j.readdirSync(t)){const n=T.join(t,o);if(j.statSync(n).isDirectory()){_(n,e);continue}o.endsWith(".json")&&e(n)}}function N(t){const{translationDir:e,rootDir:o=process.cwd(),disablePageLocales:n=!1}=t,s=T.resolve(o,e);if(!j.existsSync(s))return console.warn(`[i18n] Translation directory not found: ${s}`),{root:{},routes:{}};const c={root:{},routes:{}};return _(s,u=>{const a=T.relative(s,u).split(T.sep).join("/");try{const r=j.readFileSync(u,"utf-8"),i=JSON.parse(r);D.storeLoadedTranslationFile(c,a,i,n)}catch(r){console.error(`[i18n] Failed to load translation file: ${u}`,r)}}),c}function W(t,e){const{root:o,routes:n}=N(e);for(const[s,c]of Object.entries(o))t.addTranslations(s,c,!1);for(const[s,c]of Object.entries(n))for(const[u,a]of Object.entries(c))t.addRouteTranslations(u,s,D.mergeRouteTranslationsWithRoot(o[u],a),!1)}function H(t){const{i18n:e,defaultLocale:o,locales:n,localeObjects:s,autoDetect:c=!0,redirectToDefault:u=!1,routingStrategy:a}=t,r=a||O();return async(i,l)=>{if(i.locals.locale&&i.locals.i18n)return l();const f=i.url,d=f.pathname;if(!r){const P=e.clone(o),S=d==="/"||d===""?"index":d.split("/").filter(Boolean).join("-");return P.setRoute(S),i.locals.i18n=P,i.locals.locale=o,i.locals.defaultLocale=o,i.locals.locales=s||n.map(R=>({code:R})),i.locals.currentUrl=f,l()}const g={...r,getCurrentPath:()=>d,getRoute:()=>({fullPath:f.pathname+f.search,query:Object.fromEntries(f.searchParams)})},m=d.split("/").filter(Boolean)[0],L=m!==void 0&&n.includes(m);let y;L&&m?y=m:g.getLocaleFromPath?y=g.getLocaleFromPath(d,o,n):y=o;const h=e.clone(y),b=g.getRouteName?g.getRouteName(d,n):"index";return h.setRoute(b),i.locals.i18n=h,i.locals.locale=y,i.locals.defaultLocale=o,i.locals.locales=s||n.map(P=>({code:P})),i.locals.currentUrl=f,i.locals.routingStrategy=g,l()}}function z(t,e,o,n,s,c="i18n-locale"){const u=O();let a=n;if(u?.getLocaleFromPath)a=u.getLocaleFromPath(t,n,s);else{const i=t.split("/").filter(Boolean)[0];i&&s.includes(i)&&(a=i)}if(c!==null&&a===n&&e.get(c)){const r=e.get(c)?.value;r&&s.includes(r)&&(a=r)}if(a===n)try{const r=M.detectLocaleFromAcceptLanguage(o.get("accept-language")??void 0,s);r&&(a=r)}catch{}return a}function U(t,e,o){const n=t.map(l=>l.code),s=(l,f=[])=>{const d=l.replace(/^\//,"").replace(/\/$/,"");if(!d)return"index";const g=d.split("/").filter(Boolean),p=g[0];return p&&f.includes(p)&&g.shift(),g.length===0?"index":g.join("-")},c=(l,f="en",d=[])=>{const p=l.split("/").filter(Boolean)[0];return p&&d.includes(p)?p:f},u=(l,f,d=[],g)=>{const p=l.split("/").filter(Boolean),m=p[0];return m&&d.includes(m)&&p.shift(),(f!==g||g===void 0)&&p.unshift(f),`/${p.join("/")}`},a=(l,f,d=[],g)=>{const m=(l.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),L=m[0];return L&&d.includes(L)&&m.shift(),(f!==g||g===void 0)&&m.unshift(f),`/${m.join("/")}`};return{getCurrentPath:()=>o?o().pathname:typeof window<"u"?window.location.pathname:"/",getRouteName:s,getLocaleFromPath:c,switchLocalePath:u,localizePath:a,removeLocaleFromPath:(l,f=[])=>{const d=l.split("/").filter(Boolean),g=d[0];return g&&f.includes(g)&&d.shift(),`/${d.join("/")}`},resolvePath:(l,f)=>{const d=typeof l=="string"?l:l.path||"/";return a(d,f,n,e)},getRoute:()=>{if(o){const l=o();return{fullPath:l.pathname+l.search,query:Object.fromEntries(l.searchParams)}}if(typeof window<"u"){const l=new URL(window.location.href);return{fullPath:l.pathname+l.search,query:Object.fromEntries(l.searchParams)}}return{fullPath:"/",query:{}}},push:l=>{typeof window<"u"&&(window.location.href=l.path)},replace:l=>{typeof window<"u"&&window.location.replace(l.path)}}}function x(t,e=[]){const o=t.replace(/^\//,"").replace(/\/$/,"");if(!o)return"index";const n=o.split("/").filter(Boolean),s=n[0];return s&&e.includes(s)&&n.shift(),n.length===0?"index":n.join("-")}function E(t,e="en",o=[]){const s=t.split("/").filter(Boolean)[0];return s&&o.includes(s)?s:e}function J(t,e,o=[],n){const s=t.split("/").filter(Boolean),c=s[0];return c&&o.includes(c)&&s.shift(),(e!==n||n===void 0)&&s.unshift(e),`/${s.join("/")}`}function V(t,e,o=[],n){const c=(t.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),u=c[0];return u&&o.includes(u)&&c.shift(),(e!==n||n===void 0)&&c.unshift(e),`/${c.join("/")}`}function G(t,e=[]){const o=t.split("/").filter(Boolean),n=o[0];return n&&e.includes(n)&&o.shift(),`/${o.join("/")}`}function F(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 k(t){return t.locals.defaultLocale||"en"}function I(t){return t.locals.locales||[]}function C(t){return t.locals.routingStrategy||null}function K(t){const e=F(t),o=$(t),n=k(t),s=I(t),c=s.map(a=>a.code),u=C(t);return{locale:o,defaultLocale:n,locales:s,t:(a,r,i,l)=>e.t(a,r,i,l),ts:(a,r,i,l)=>e.ts(a,r,i,l),tc:(a,r,i)=>e.tc(a,r,i),tn:(a,r)=>e.tn(a,r),td:(a,r)=>e.td(a,r),tdr:(a,r)=>e.tdr(a,r),has:(a,r)=>e.has(a,r),getRoute:()=>e.getRoute(),getRouteName:a=>{const r=a||t.url.pathname;if(u?.getRouteName)return u.getRouteName(r,c);const i=r.replace(/^\//,"").replace(/\/$/,"");if(!i)return"index";const l=i.split("/").filter(Boolean),f=l[0];return f&&c.includes(f)&&l.shift(),l.length===0?"index":l.join("-")},getLocaleFromPath:a=>{const r=a||t.url.pathname;if(u?.getLocaleFromPath)return u.getLocaleFromPath(r,n,c);const l=r.split("/").filter(Boolean)[0];return l&&c.includes(l)?l:n},switchLocalePath:a=>{if(u?.switchLocalePath)return u.switchLocalePath(t.url.pathname,a,c,n);const r=t.url.pathname.split("/").filter(Boolean),i=r[0];return i&&c.includes(i)&&r.shift(),a!==n&&r.unshift(a),`/${r.join("/")}`},localizePath:(a,r)=>{if(u?.localizePath)return u.localizePath(a,r||o,c,n);const l=(a.replace(/^\//,"").replace(/\/$/,"")||"").split("/").filter(Boolean),f=l[0];return f&&c.includes(f)&&l.shift(),r&&r!==n&&l.unshift(r),`/${l.join("/")}`},getI18n:()=>e,getBasePath:a=>{const l=(a||t.url).pathname.split("/").filter(Boolean),f=l[0];return f&&c.includes(f)&&l.shift(),l.length>0?`/${l.join("/")}`:"/"},addTranslations:(a,r,i=!0)=>{e.addTranslations(a,r,i)},addRouteTranslations:(a,r,i,l=!0)=>{e.addRouteTranslations(a,r,i,l)},mergeTranslations:(a,r,i)=>{e.mergeTranslations(a,r,i)},clearCache:()=>{e.clearCache()}}}function Q(t,e={}){const{baseUrl:o="/",addDirAttribute:n=!0,addSeoAttributes:s=!0}=e,c=$(t),u=k(t),r=I(t).filter(h=>!h.disabled),i=r.filter(h=>h.seo!==!1),l=r.find(h=>h.code===c);if(!l)return{htmlAttrs:{},link:[],meta:[]};const f=l.iso||c,d=l.dir||"auto",g={htmlAttrs:{lang:f,...n?{dir:d}:{}},link:[],meta:[]};if(!s)return g;const p=`${o}${t.url.pathname}`;g.link.push({rel:"canonical",href:p});const m=C(t),L=r.map(h=>h.code);for(const h of i){let b=t.url.pathname;if(m?.switchLocalePath)b=m.switchLocalePath(t.url.pathname,h.code,L,u);else{const S=t.url.pathname.split("/").filter(Boolean),R=S[0];R&&L.includes(R)&&S.shift(),h.code!==u&&S.unshift(h.code),b=`/${S.join("/")}`}const P=`${o}${b}`;g.link.push({rel:"alternate",href:P,hreflang:h.code}),h.iso&&h.iso!==h.code&&g.link.push({rel:"alternate",href:P,hreflang:h.iso})}if(r.find(h=>h.code===u)?.seo!==!1){let h=t.url.pathname;if(m?.switchLocalePath)h=m.switchLocalePath(t.url.pathname,u,L,u);else{const b=t.url.pathname.split("/").filter(Boolean),P=b[0];P&&L.includes(P)&&b.shift(),h=`/${b.join("/")}`}g.link.push({rel:"alternate",href:`${o}${h}`,hreflang:"x-default"})}g.meta.push({property:"og:locale",content:f}),g.meta.push({property:"og:url",content:p});for(const h of i)h.code!==c&&g.meta.push({property:"og:locale:alternate",content:h.og||h.iso||h.code});return g}function X(t,e,o){const n=e.split(".");let s=t;for(let u=0;u<n.length-1;u++){const a=n[u];s[a]||(s[a]={}),s=s[a]}const c=n[n.length-1];c!==void 0&&(s[c]=o)}function Y(t,e){const o=F(t),n=$(t),s=k(t),c=o.getRoute(),u={};if(e&&e.length>0){const a={};for(const r of e){const i=o.t(r,void 0,void 0,c);i!=null&&i!==r&&X(a,r,i)}Object.keys(a).length>0&&(u[c]=a)}else{const a=o.getRouteTranslations(n,c);a&&(u[c]=a)}return{locale:n,fallbackLocale:s,currentRoute:c,translations:u}}Object.defineProperty(exports,"FormatService",{enumerable:!0,get:()=>w.FormatService});Object.defineProperty(exports,"defaultPlural",{enumerable:!0,get:()=>w.defaultPlural});Object.defineProperty(exports,"interpolate",{enumerable:!0,get:()=>w.interpolate});exports.AstroI18n=v;exports.createAstroRouterAdapter=U;exports.createI18n=A;exports.createI18nMiddleware=H;exports.detectLocale=z;exports.getDefaultLocale=k;exports.getI18n=F;exports.getI18nProps=Y;exports.getLocale=$;exports.getLocaleFromPath=E;exports.getLocales=I;exports.getRouteName=x;exports.i18nIntegration=q;exports.loadTranslationsFromDir=N;exports.loadTranslationsIntoI18n=W;exports.localizePath=V;exports.removeLocaleFromPath=G;exports.switchLocalePath=J;exports.useI18n=K;exports.useLocaleHead=Q;
@@ -0,0 +1,14 @@
1
+ export { defaultPlural, FormatService, interpolate } from '@i18n-micro/core';
2
+ export type { CleanTranslation, Getter, Locale, LocaleCode, Params, PluralFunc, Translations, } from '@i18n-micro/types';
3
+ export { AstroI18n, type AstroI18nOptions } from './composer';
4
+ export type { I18nIntegrationOptions } from './integration';
5
+ export { createI18n, i18nIntegration } from './integration';
6
+ export type { LoadedTranslations, LoadTranslationsOptions } from './load-translations';
7
+ export { loadTranslationsFromDir, loadTranslationsIntoI18n, } from './load-translations';
8
+ export type { I18nMiddlewareOptions } from './middleware';
9
+ export { createI18nMiddleware, detectLocale } from './middleware';
10
+ export { createAstroRouterAdapter } from './router/adapter';
11
+ export type { I18nRoutingStrategy } from './router/types';
12
+ export { getLocaleFromPath, getRouteName, localizePath, removeLocaleFromPath, switchLocalePath, } from './routing';
13
+ export type { I18nClientProps, LocaleHeadOptions, LocaleHeadResult } from './utils';
14
+ export { getDefaultLocale, getI18n, getI18nProps, getLocale, getLocales, useI18n, useLocaleHead, } from './utils';
package/dist/index.mjs CHANGED
@@ -1,38 +1,40 @@
1
- import { BaseI18n as I } from "@i18n-micro/core";
2
- import { FormatService as le, defaultPlural as re, interpolate as ie } from "@i18n-micro/core";
3
- import { existsSync as $, readdirSync as O, statSync as _, readFileSync as C } from "node:fs";
4
- import { resolve as N, join as q } from "node:path";
5
- class j extends I {
1
+ import { BaseI18n as O } from "@i18n-micro/core";
2
+ import { FormatService as he, defaultPlural as de, interpolate as me } from "@i18n-micro/core";
3
+ import { existsSync as v, 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 x } from "@i18n-micro/utils/parse-path";
6
+ import { detectLocaleFromAcceptLanguage as z } from "@i18n-micro/utils/accept-language";
7
+ class j extends O {
6
8
  constructor(e) {
7
- const s = e._storage || {
9
+ const o = e._storage || {
8
10
  translations: /* @__PURE__ */ new Map()
9
11
  };
10
12
  if (super({
11
- storage: s,
13
+ storage: o,
12
14
  plural: e.plural,
13
15
  missingWarn: e.missingWarn,
14
16
  missingHandler: e.missingHandler
15
- }), this.initialMessages = {}, this.storage = s, this._locale = e.locale, this._fallbackLocale = e.fallbackLocale || e.locale, this._currentRoute = "index", e.messages) {
17
+ }), this.initialMessages = {}, this.storage = o, this._locale = e.locale, this._fallbackLocale = e.fallbackLocale || e.locale, this._currentRoute = "index", e.messages) {
16
18
  this.initialMessages = { ...e.messages };
17
- for (const [a, i] of Object.entries(e.messages))
18
- this.helper.loadTranslations(a, i);
19
+ for (const [n, r] of Object.entries(e.messages))
20
+ this.helper.loadTranslations(n, r);
19
21
  }
20
22
  }
21
23
  cloneStorage(e) {
22
- const s = /* @__PURE__ */ new Map();
23
- for (const [a, i] of e.translations)
24
- s.set(a, { ...i });
25
- return { translations: s };
24
+ const o = /* @__PURE__ */ new Map();
25
+ for (const [n, r] of e.translations)
26
+ o.set(n, { ...r });
27
+ return { translations: o };
26
28
  }
27
29
  clone(e) {
28
- const s = this.cloneStorage(this.storage);
30
+ const o = this.cloneStorage(this.storage);
29
31
  return new j({
30
32
  locale: e || this._locale,
31
33
  fallbackLocale: this._fallbackLocale,
32
34
  plural: this.pluralFunc,
33
35
  missingWarn: this.missingWarn,
34
36
  missingHandler: this.missingHandler,
35
- _storage: s
37
+ _storage: o
36
38
  });
37
39
  }
38
40
  get locale() {
@@ -59,43 +61,43 @@ class j extends I {
59
61
  getRoute() {
60
62
  return this._currentRoute;
61
63
  }
62
- getRouteTranslations(e, s) {
63
- const a = `${e}:${s}`;
64
- return this.storage.translations.get(a) ?? null;
64
+ getRouteTranslations(e, o) {
65
+ const n = `${e}:${o}`;
66
+ return this.storage.translations.get(n) ?? null;
65
67
  }
66
- addTranslations(e, s, a = !0) {
67
- super.loadTranslationsCore(e, s, a);
68
+ addTranslations(e, o, n = !0) {
69
+ super.loadTranslationsCore(e, o, n);
68
70
  }
69
- addRouteTranslations(e, s, a, i = !0) {
70
- super.loadRouteTranslationsCore(e, s, a, i);
71
+ addRouteTranslations(e, o, n, r = !0) {
72
+ super.loadRouteTranslationsCore(e, o, n, r);
71
73
  }
72
- mergeTranslations(e, s, a) {
73
- this.helper.mergeTranslation(e, s, a, !0);
74
+ mergeTranslations(e, o, n) {
75
+ this.helper.mergeTranslation(e, o, n, !0);
74
76
  }
75
77
  clearCache() {
76
78
  const e = { ...this.initialMessages };
77
79
  if (super.clearCache(), Object.keys(e).length > 0)
78
- for (const [s, a] of Object.entries(e))
79
- this.helper.loadTranslations(s, a);
80
+ for (const [o, n] of Object.entries(e))
81
+ this.helper.loadTranslations(o, n);
80
82
  }
81
83
  }
82
- let v = null;
84
+ let $ = null;
83
85
  function k() {
84
- return v;
86
+ return $;
85
87
  }
86
- function E(t) {
87
- const { locale: e, fallbackLocale: s, translationDir: a, routingStrategy: i } = t;
88
- return v = i || null, {
88
+ function X(t) {
89
+ const { locale: e, fallbackLocale: o, translationDir: n, routingStrategy: r } = t;
90
+ return $ = r || null, {
89
91
  name: "@i18n-micro/astro",
90
92
  hooks: {
91
93
  // 1. Vite setup (virtual module) happens here
92
94
  "astro:config:setup": (c) => {
93
- const { updateConfig: u } = c, n = "virtual:i18n-micro/config", l = `\0${n}`, r = {
95
+ const { updateConfig: u } = c, a = "virtual:i18n-micro/config", s = `\0${a}`, i = {
94
96
  defaultLocale: e,
95
- fallbackLocale: s || e,
97
+ fallbackLocale: o || e,
96
98
  locales: t.locales || [],
97
- localeCodes: (t.locales || []).map((o) => o.code),
98
- translationDir: a || null,
99
+ localeCodes: (t.locales || []).map((l) => l.code),
100
+ translationDir: n || null,
99
101
  autoDetect: t.autoDetect ?? !0,
100
102
  redirectToDefault: t.redirectToDefault ?? !1,
101
103
  localeCookie: t.localeCookie === null ? null : t.localeCookie || "i18n-locale",
@@ -106,13 +108,13 @@ function E(t) {
106
108
  plugins: [
107
109
  {
108
110
  name: "vite-plugin-i18n-micro-config",
109
- resolveId(o) {
110
- if (o === n)
111
- return l;
111
+ resolveId(l) {
112
+ if (l === a)
113
+ return s;
112
114
  },
113
- load(o) {
114
- if (o === l)
115
- return `export const config = ${JSON.stringify(r)}`;
115
+ load(l) {
116
+ if (l === s)
117
+ return `export const config = ${JSON.stringify(i)}`;
116
118
  }
117
119
  }
118
120
  ]
@@ -146,159 +148,141 @@ function E(t) {
146
148
  }
147
149
  };
148
150
  }
149
- function J(t) {
151
+ function Y(t) {
150
152
  return new j(t);
151
153
  }
152
- function M(t) {
153
- const { translationDir: e, rootDir: s = process.cwd(), disablePageLocales: a = !1 } = t, i = N(s, e);
154
- if (!$(i))
155
- return console.warn(`[i18n] Translation directory not found: ${i}`), { root: {}, routes: {} };
156
- const c = {}, u = {}, n = (l, r = "") => {
157
- if (!$(l)) return;
158
- const o = O(l);
159
- for (const f of o) {
160
- const g = q(l, f);
161
- if (_(g).isDirectory())
162
- f === "pages" && !a ? n(g, "") : r || a ? n(g, r) : n(g, f);
163
- else if (f.endsWith(".json")) {
164
- const p = f.replace(".json", "");
165
- try {
166
- const m = C(g, "utf-8"), L = JSON.parse(m);
167
- r && !a ? (u[r] || (u[r] = {}), u[r][p] = L) : c[p] = L;
168
- } catch (m) {
169
- console.error(`[i18n] Failed to load translation file: ${g}`, m);
170
- }
154
+ function D(t, e) {
155
+ if (v(t))
156
+ for (const o of _(t)) {
157
+ const n = W(t, o);
158
+ if (C(n).isDirectory()) {
159
+ D(n, e);
160
+ continue;
171
161
  }
162
+ o.endsWith(".json") && e(n);
172
163
  }
173
- };
174
- return n(i), { root: c, routes: u };
175
164
  }
176
- function V(t, e) {
177
- const { root: s, routes: a } = M(e);
178
- for (const [i, c] of Object.entries(s))
179
- t.addTranslations(i, c, !1);
180
- for (const [i, c] of Object.entries(a))
181
- for (const [u, n] of Object.entries(c)) {
182
- const l = s[u] || {};
183
- t.addRouteTranslations(u, i, { ...l, ...n }, !1);
165
+ function U(t) {
166
+ const { translationDir: e, rootDir: o = process.cwd(), disablePageLocales: n = !1 } = t, r = M(o, e);
167
+ if (!v(r))
168
+ return console.warn(`[i18n] Translation directory not found: ${r}`), { root: {}, routes: {} };
169
+ const c = { root: {}, routes: {} };
170
+ return D(r, (u) => {
171
+ const a = A(r, u).split(q).join("/");
172
+ try {
173
+ const s = N(u, "utf-8"), i = JSON.parse(s);
174
+ x(c, a, i, n);
175
+ } catch (s) {
176
+ console.error(`[i18n] Failed to load translation file: ${u}`, s);
184
177
  }
178
+ }), c;
185
179
  }
186
- function G(t) {
180
+ function Z(t, e) {
181
+ const { root: o, routes: n } = U(e);
182
+ for (const [r, c] of Object.entries(o))
183
+ t.addTranslations(r, c, !1);
184
+ for (const [r, c] of Object.entries(n))
185
+ for (const [u, a] of Object.entries(c))
186
+ t.addRouteTranslations(u, r, H(o[u], a), !1);
187
+ }
188
+ function ee(t) {
187
189
  const {
188
190
  i18n: e,
189
191
  // This is the global singleton with cache
190
- defaultLocale: s,
191
- locales: a,
192
- localeObjects: i,
192
+ defaultLocale: o,
193
+ locales: n,
194
+ localeObjects: r,
193
195
  autoDetect: c = !0,
194
196
  redirectToDefault: u = !1,
195
- routingStrategy: n
196
- } = t, l = n || k();
197
- return async (r, o) => {
198
- if (r.locals.locale && r.locals.i18n)
199
- return o();
200
- const f = r.url, g = f.pathname;
201
- if (!l) {
202
- const P = e.clone(s), S = g === "/" || g === "" ? "index" : g.split("/").filter(Boolean).join("-");
203
- return P.setRoute(S), r.locals.i18n = P, r.locals.locale = s, r.locals.defaultLocale = s, r.locals.locales = i || a.map((w) => ({ code: w })), r.locals.currentUrl = f, o();
197
+ routingStrategy: a
198
+ } = t, s = a || k();
199
+ return async (i, l) => {
200
+ if (i.locals.locale && i.locals.i18n)
201
+ return l();
202
+ const f = i.url, d = f.pathname;
203
+ if (!s) {
204
+ const P = e.clone(o), y = d === "/" || d === "" ? "index" : d.split("/").filter(Boolean).join("-");
205
+ return P.setRoute(y), i.locals.i18n = P, i.locals.locale = o, i.locals.defaultLocale = o, i.locals.locales = r || n.map((R) => ({ code: R })), i.locals.currentUrl = f, l();
204
206
  }
205
- const h = {
206
- ...l,
207
- getCurrentPath: () => g,
207
+ const g = {
208
+ ...s,
209
+ getCurrentPath: () => d,
208
210
  getRoute: () => ({
209
211
  fullPath: f.pathname + f.search,
210
212
  query: Object.fromEntries(f.searchParams)
211
213
  })
212
- }, m = g.split("/").filter(Boolean)[0], L = m !== void 0 && a.includes(m);
213
- let y;
214
- L && m ? y = m : h.getLocaleFromPath ? y = h.getLocaleFromPath(g, s, a) : y = s;
215
- const d = e.clone(y), b = h.getRouteName ? h.getRouteName(g, a) : "index";
216
- return d.setRoute(b), r.locals.i18n = d, r.locals.locale = y, r.locals.defaultLocale = s, r.locals.locales = i || a.map((P) => ({ code: P })), r.locals.currentUrl = f, r.locals.routingStrategy = h, o();
214
+ }, m = d.split("/").filter(Boolean)[0], L = m !== void 0 && n.includes(m);
215
+ let b;
216
+ L && m ? b = m : g.getLocaleFromPath ? b = g.getLocaleFromPath(d, o, n) : b = o;
217
+ const h = e.clone(b), S = g.getRouteName ? g.getRouteName(d, n) : "index";
218
+ return h.setRoute(S), i.locals.i18n = h, i.locals.locale = b, i.locals.defaultLocale = o, i.locals.locales = r || n.map((P) => ({ code: P })), i.locals.currentUrl = f, i.locals.routingStrategy = g, l();
217
219
  };
218
220
  }
219
- function W(t) {
220
- const e = [], s = t.split(",");
221
- for (const a of s) {
222
- const [i, c = "1.0"] = a.trim().split(";q=");
223
- if (Number.parseFloat(c) > 0 && i) {
224
- const n = i.split("-")[0]?.toLowerCase();
225
- n && (e.push(n), i !== n && e.push(i.toLowerCase()));
226
- }
227
- }
228
- return e;
229
- }
230
- function K(t, e, s, a, i, c = "i18n-locale") {
221
+ function te(t, e, o, n, r, c = "i18n-locale") {
231
222
  const u = k();
232
- let n = a;
223
+ let a = n;
233
224
  if (u?.getLocaleFromPath)
234
- n = u.getLocaleFromPath(t, a, i);
225
+ a = u.getLocaleFromPath(t, n, r);
235
226
  else {
236
- const r = t.split("/").filter(Boolean)[0];
237
- r && i.includes(r) && (n = r);
227
+ const i = t.split("/").filter(Boolean)[0];
228
+ i && r.includes(i) && (a = i);
238
229
  }
239
- if (c !== null && n === a && e.get(c)) {
240
- const l = e.get(c)?.value;
241
- l && i.includes(l) && (n = l);
230
+ if (c !== null && a === n && e.get(c)) {
231
+ const s = e.get(c)?.value;
232
+ s && r.includes(s) && (a = s);
242
233
  }
243
- if (n === a)
234
+ if (a === n)
244
235
  try {
245
- const l = s.get("accept-language");
246
- if (l) {
247
- const r = W(l);
248
- for (const o of r)
249
- if (i.includes(o)) {
250
- n = o;
251
- break;
252
- }
253
- }
236
+ const s = z(o.get("accept-language") ?? void 0, r);
237
+ s && (a = s);
254
238
  } catch {
255
239
  }
256
- return n;
240
+ return a;
257
241
  }
258
- function x(t, e, s) {
259
- const a = t.map((o) => o.code), i = (o, f = []) => {
260
- const g = o.replace(/^\//, "").replace(/\/$/, "");
261
- if (!g)
242
+ function ne(t, e, o) {
243
+ const n = t.map((l) => l.code), r = (l, f = []) => {
244
+ const d = l.replace(/^\//, "").replace(/\/$/, "");
245
+ if (!d)
262
246
  return "index";
263
- const h = g.split("/").filter(Boolean), p = h[0];
264
- return p && f.includes(p) && h.shift(), h.length === 0 ? "index" : h.join("-");
265
- }, c = (o, f = "en", g = []) => {
266
- const p = o.split("/").filter(Boolean)[0];
267
- return p && g.includes(p) ? p : f;
268
- }, u = (o, f, g = [], h) => {
269
- const p = o.split("/").filter(Boolean), m = p[0];
270
- return m && g.includes(m) && p.shift(), (f !== h || h === void 0) && p.unshift(f), `/${p.join("/")}`;
271
- }, n = (o, f, g = [], h) => {
272
- const m = (o.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), L = m[0];
273
- return L && g.includes(L) && m.shift(), (f !== h || h === void 0) && m.unshift(f), `/${m.join("/")}`;
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 = (l, f = "en", d = []) => {
250
+ const p = l.split("/").filter(Boolean)[0];
251
+ return p && d.includes(p) ? p : f;
252
+ }, u = (l, f, d = [], g) => {
253
+ const p = l.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
+ }, a = (l, f, d = [], g) => {
256
+ const m = (l.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), L = m[0];
257
+ return L && d.includes(L) && m.shift(), (f !== g || g === void 0) && m.unshift(f), `/${m.join("/")}`;
274
258
  };
275
259
  return {
276
- getCurrentPath: () => s ? s().pathname : typeof window < "u" ? window.location.pathname : "/",
277
- getRouteName: i,
260
+ getCurrentPath: () => o ? o().pathname : typeof window < "u" ? window.location.pathname : "/",
261
+ getRouteName: r,
278
262
  getLocaleFromPath: c,
279
263
  switchLocalePath: u,
280
- localizePath: n,
281
- removeLocaleFromPath: (o, f = []) => {
282
- const g = o.split("/").filter(Boolean), h = g[0];
283
- return h && f.includes(h) && g.shift(), `/${g.join("/")}`;
264
+ localizePath: a,
265
+ removeLocaleFromPath: (l, f = []) => {
266
+ const d = l.split("/").filter(Boolean), g = d[0];
267
+ return g && f.includes(g) && d.shift(), `/${d.join("/")}`;
284
268
  },
285
- resolvePath: (o, f) => {
286
- const g = typeof o == "string" ? o : o.path || "/";
287
- return n(g, f, a, e);
269
+ resolvePath: (l, f) => {
270
+ const d = typeof l == "string" ? l : l.path || "/";
271
+ return a(d, f, n, e);
288
272
  },
289
273
  getRoute: () => {
290
- if (s) {
291
- const o = s();
274
+ if (o) {
275
+ const l = o();
292
276
  return {
293
- fullPath: o.pathname + o.search,
294
- query: Object.fromEntries(o.searchParams)
277
+ fullPath: l.pathname + l.search,
278
+ query: Object.fromEntries(l.searchParams)
295
279
  };
296
280
  }
297
281
  if (typeof window < "u") {
298
- const o = new URL(window.location.href);
282
+ const l = new URL(window.location.href);
299
283
  return {
300
- fullPath: o.pathname + o.search,
301
- query: Object.fromEntries(o.searchParams)
284
+ fullPath: l.pathname + l.search,
285
+ query: Object.fromEntries(l.searchParams)
302
286
  };
303
287
  }
304
288
  return {
@@ -307,242 +291,242 @@ function x(t, e, s) {
307
291
  };
308
292
  },
309
293
  // Optional: client-side navigation for islands
310
- push: (o) => {
311
- typeof window < "u" && (window.location.href = o.path);
294
+ push: (l) => {
295
+ typeof window < "u" && (window.location.href = l.path);
312
296
  },
313
- replace: (o) => {
314
- typeof window < "u" && window.location.replace(o.path);
297
+ replace: (l) => {
298
+ typeof window < "u" && window.location.replace(l.path);
315
299
  }
316
300
  };
317
301
  }
318
- function Q(t, e = []) {
319
- const s = t.replace(/^\//, "").replace(/\/$/, "");
320
- if (!s)
302
+ function oe(t, e = []) {
303
+ const o = t.replace(/^\//, "").replace(/\/$/, "");
304
+ if (!o)
321
305
  return "index";
322
- const a = s.split("/").filter(Boolean), i = a[0];
323
- return i && e.includes(i) && a.shift(), a.length === 0 ? "index" : a.join("-");
306
+ const n = o.split("/").filter(Boolean), r = n[0];
307
+ return r && e.includes(r) && n.shift(), n.length === 0 ? "index" : n.join("-");
324
308
  }
325
- function X(t, e = "en", s = []) {
326
- const i = t.split("/").filter(Boolean)[0];
327
- return i && s.includes(i) ? i : e;
309
+ function ae(t, e = "en", o = []) {
310
+ const r = t.split("/").filter(Boolean)[0];
311
+ return r && o.includes(r) ? r : e;
328
312
  }
329
- function Y(t, e, s = [], a) {
330
- const i = t.split("/").filter(Boolean), c = i[0];
331
- return c && s.includes(c) && i.shift(), (e !== a || a === void 0) && i.unshift(e), `/${i.join("/")}`;
313
+ function le(t, e, o = [], n) {
314
+ const r = t.split("/").filter(Boolean), c = r[0];
315
+ return c && o.includes(c) && r.shift(), (e !== n || n === void 0) && r.unshift(e), `/${r.join("/")}`;
332
316
  }
333
- function Z(t, e, s = [], a) {
317
+ function se(t, e, o = [], n) {
334
318
  const c = (t.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), u = c[0];
335
- return u && s.includes(u) && c.shift(), (e !== a || a === void 0) && c.unshift(e), `/${c.join("/")}`;
319
+ return u && o.includes(u) && c.shift(), (e !== n || n === void 0) && c.unshift(e), `/${c.join("/")}`;
336
320
  }
337
- function ee(t, e = []) {
338
- const s = t.split("/").filter(Boolean), a = s[0];
339
- return a && e.includes(a) && s.shift(), `/${s.join("/")}`;
321
+ function re(t, e = []) {
322
+ const o = t.split("/").filter(Boolean), n = o[0];
323
+ return n && e.includes(n) && o.shift(), `/${o.join("/")}`;
340
324
  }
341
- function D(t) {
325
+ function B(t) {
342
326
  const e = t.locals.i18n;
343
327
  if (!e)
344
328
  throw new Error("i18n instance not found. Make sure i18n middleware is configured.");
345
329
  return e;
346
330
  }
347
- function R(t) {
331
+ function w(t) {
348
332
  return t.locals.locale || "en";
349
333
  }
350
334
  function T(t) {
351
335
  return t.locals.defaultLocale || "en";
352
336
  }
353
- function B(t) {
337
+ function F(t) {
354
338
  return t.locals.locales || [];
355
339
  }
356
- function F(t) {
340
+ function I(t) {
357
341
  return t.locals.routingStrategy || null;
358
342
  }
359
- function te(t) {
360
- const e = D(t), s = R(t), a = T(t), i = B(t), c = i.map((n) => n.code), u = F(t);
343
+ function ie(t) {
344
+ const e = B(t), o = w(t), n = T(t), r = F(t), c = r.map((a) => a.code), u = I(t);
361
345
  return {
362
346
  // Current locale
363
- locale: s,
364
- defaultLocale: a,
365
- locales: i,
347
+ locale: o,
348
+ defaultLocale: n,
349
+ locales: r,
366
350
  // Translation methods
367
- t: (n, l, r, o) => e.t(n, l, r, o),
368
- ts: (n, l, r, o) => e.ts(n, l, r, o),
369
- tc: (n, l, r) => e.tc(n, l, r),
370
- tn: (n, l) => e.tn(n, l),
371
- td: (n, l) => e.td(n, l),
372
- tdr: (n, l) => e.tdr(n, l),
373
- has: (n, l) => e.has(n, l),
351
+ t: (a, s, i, l) => e.t(a, s, i, l),
352
+ ts: (a, s, i, l) => e.ts(a, s, i, l),
353
+ tc: (a, s, i) => e.tc(a, s, i),
354
+ tn: (a, s) => e.tn(a, s),
355
+ td: (a, s) => e.td(a, s),
356
+ tdr: (a, s) => e.tdr(a, s),
357
+ has: (a, s) => e.has(a, s),
374
358
  // Route management
375
359
  getRoute: () => e.getRoute(),
376
- getRouteName: (n) => {
377
- const l = n || t.url.pathname;
360
+ getRouteName: (a) => {
361
+ const s = a || t.url.pathname;
378
362
  if (u?.getRouteName)
379
- return u.getRouteName(l, c);
380
- const r = l.replace(/^\//, "").replace(/\/$/, "");
381
- if (!r) return "index";
382
- const o = r.split("/").filter(Boolean), f = o[0];
383
- return f && c.includes(f) && o.shift(), o.length === 0 ? "index" : o.join("-");
363
+ return u.getRouteName(s, c);
364
+ const i = s.replace(/^\//, "").replace(/\/$/, "");
365
+ if (!i) return "index";
366
+ const l = i.split("/").filter(Boolean), f = l[0];
367
+ return f && c.includes(f) && l.shift(), l.length === 0 ? "index" : l.join("-");
384
368
  },
385
- getLocaleFromPath: (n) => {
386
- const l = n || t.url.pathname;
369
+ getLocaleFromPath: (a) => {
370
+ const s = a || t.url.pathname;
387
371
  if (u?.getLocaleFromPath)
388
- return u.getLocaleFromPath(l, a, c);
389
- const o = l.split("/").filter(Boolean)[0];
390
- return o && c.includes(o) ? o : a;
372
+ return u.getLocaleFromPath(s, n, c);
373
+ const l = s.split("/").filter(Boolean)[0];
374
+ return l && c.includes(l) ? l : n;
391
375
  },
392
376
  // Path utilities
393
- switchLocalePath: (n) => {
377
+ switchLocalePath: (a) => {
394
378
  if (u?.switchLocalePath)
395
- return u.switchLocalePath(t.url.pathname, n, c, a);
396
- const l = t.url.pathname.split("/").filter(Boolean), r = l[0];
397
- return r && c.includes(r) && l.shift(), n !== a && l.unshift(n), `/${l.join("/")}`;
379
+ return u.switchLocalePath(t.url.pathname, a, c, n);
380
+ const s = t.url.pathname.split("/").filter(Boolean), i = s[0];
381
+ return i && c.includes(i) && s.shift(), a !== n && s.unshift(a), `/${s.join("/")}`;
398
382
  },
399
- localizePath: (n, l) => {
383
+ localizePath: (a, s) => {
400
384
  if (u?.localizePath)
401
- return u.localizePath(n, l || s, c, a);
402
- const o = (n.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), f = o[0];
403
- return f && c.includes(f) && o.shift(), l && l !== a && o.unshift(l), `/${o.join("/")}`;
385
+ return u.localizePath(a, s || o, c, n);
386
+ const l = (a.replace(/^\//, "").replace(/\/$/, "") || "").split("/").filter(Boolean), f = l[0];
387
+ return f && c.includes(f) && l.shift(), s && s !== n && l.unshift(s), `/${l.join("/")}`;
404
388
  },
405
389
  // Get i18n instance
406
390
  getI18n: () => e,
407
391
  // Get base path without locale (for rewrite)
408
- getBasePath: (n) => {
409
- const o = (n || t.url).pathname.split("/").filter(Boolean), f = o[0];
410
- return f && c.includes(f) && o.shift(), o.length > 0 ? `/${o.join("/")}` : "/";
392
+ getBasePath: (a) => {
393
+ const l = (a || t.url).pathname.split("/").filter(Boolean), f = l[0];
394
+ return f && c.includes(f) && l.shift(), l.length > 0 ? `/${l.join("/")}` : "/";
411
395
  },
412
396
  // Translation management
413
- addTranslations: (n, l, r = !0) => {
414
- e.addTranslations(n, l, r);
397
+ addTranslations: (a, s, i = !0) => {
398
+ e.addTranslations(a, s, i);
415
399
  },
416
- addRouteTranslations: (n, l, r, o = !0) => {
417
- e.addRouteTranslations(n, l, r, o);
400
+ addRouteTranslations: (a, s, i, l = !0) => {
401
+ e.addRouteTranslations(a, s, i, l);
418
402
  },
419
- mergeTranslations: (n, l, r) => {
420
- e.mergeTranslations(n, l, r);
403
+ mergeTranslations: (a, s, i) => {
404
+ e.mergeTranslations(a, s, i);
421
405
  },
422
406
  clearCache: () => {
423
407
  e.clearCache();
424
408
  }
425
409
  };
426
410
  }
427
- function ne(t, e = {}) {
428
- const { baseUrl: s = "/", addDirAttribute: a = !0, addSeoAttributes: i = !0 } = e, c = R(t), u = T(t), l = B(t).filter((d) => !d.disabled), r = l.filter((d) => d.seo !== !1), o = l.find((d) => d.code === c);
429
- if (!o)
411
+ function ce(t, e = {}) {
412
+ const { baseUrl: o = "/", addDirAttribute: n = !0, addSeoAttributes: r = !0 } = e, c = w(t), u = T(t), s = F(t).filter((h) => !h.disabled), i = s.filter((h) => h.seo !== !1), l = s.find((h) => h.code === c);
413
+ if (!l)
430
414
  return { htmlAttrs: {}, link: [], meta: [] };
431
- const f = o.iso || c, g = o.dir || "auto", h = {
415
+ const f = l.iso || c, d = l.dir || "auto", g = {
432
416
  htmlAttrs: {
433
417
  lang: f,
434
- ...a ? { dir: g } : {}
418
+ ...n ? { dir: d } : {}
435
419
  },
436
420
  link: [],
437
421
  meta: []
438
422
  };
439
- if (!i)
440
- return h;
441
- const p = `${s}${t.url.pathname}`;
442
- h.link.push({
423
+ if (!r)
424
+ return g;
425
+ const p = `${o}${t.url.pathname}`;
426
+ g.link.push({
443
427
  rel: "canonical",
444
428
  href: p
445
429
  });
446
- const m = F(t), L = l.map((d) => d.code);
447
- for (const d of r) {
448
- let b = t.url.pathname;
430
+ const m = I(t), L = s.map((h) => h.code);
431
+ for (const h of i) {
432
+ let S = t.url.pathname;
449
433
  if (m?.switchLocalePath)
450
- b = m.switchLocalePath(t.url.pathname, d.code, L, u);
434
+ S = m.switchLocalePath(t.url.pathname, h.code, L, u);
451
435
  else {
452
- const S = t.url.pathname.split("/").filter(Boolean), w = S[0];
453
- w && L.includes(w) && S.shift(), d.code !== u && S.unshift(d.code), b = `/${S.join("/")}`;
436
+ const y = t.url.pathname.split("/").filter(Boolean), R = y[0];
437
+ R && L.includes(R) && y.shift(), h.code !== u && y.unshift(h.code), S = `/${y.join("/")}`;
454
438
  }
455
- const P = `${s}${b}`;
456
- h.link.push({
439
+ const P = `${o}${S}`;
440
+ g.link.push({
457
441
  rel: "alternate",
458
442
  href: P,
459
- hreflang: d.code
460
- }), d.iso && d.iso !== d.code && h.link.push({
443
+ hreflang: h.code
444
+ }), h.iso && h.iso !== h.code && g.link.push({
461
445
  rel: "alternate",
462
446
  href: P,
463
- hreflang: d.iso
447
+ hreflang: h.iso
464
448
  });
465
449
  }
466
- if (l.find((d) => d.code === u)?.seo !== !1) {
467
- let d = t.url.pathname;
450
+ if (s.find((h) => h.code === u)?.seo !== !1) {
451
+ let h = t.url.pathname;
468
452
  if (m?.switchLocalePath)
469
- d = m.switchLocalePath(t.url.pathname, u, L, u);
453
+ h = m.switchLocalePath(t.url.pathname, u, L, u);
470
454
  else {
471
- const b = t.url.pathname.split("/").filter(Boolean), P = b[0];
472
- P && L.includes(P) && b.shift(), d = `/${b.join("/")}`;
455
+ const S = t.url.pathname.split("/").filter(Boolean), P = S[0];
456
+ P && L.includes(P) && S.shift(), h = `/${S.join("/")}`;
473
457
  }
474
- h.link.push({
458
+ g.link.push({
475
459
  rel: "alternate",
476
- href: `${s}${d}`,
460
+ href: `${o}${h}`,
477
461
  hreflang: "x-default"
478
462
  });
479
463
  }
480
- h.meta.push({
464
+ g.meta.push({
481
465
  property: "og:locale",
482
466
  content: f
483
- }), h.meta.push({
467
+ }), g.meta.push({
484
468
  property: "og:url",
485
469
  content: p
486
470
  });
487
- for (const d of r)
488
- d.code !== c && h.meta.push({
471
+ for (const h of i)
472
+ h.code !== c && g.meta.push({
489
473
  property: "og:locale:alternate",
490
- content: d.og || d.iso || d.code
474
+ content: h.og || h.iso || h.code
491
475
  });
492
- return h;
476
+ return g;
493
477
  }
494
- function A(t, e, s) {
495
- const a = e.split(".");
496
- let i = t;
497
- for (let u = 0; u < a.length - 1; u++) {
498
- const n = a[u];
499
- i[n] || (i[n] = {}), i = i[n];
478
+ function E(t, e, o) {
479
+ const n = e.split(".");
480
+ let r = t;
481
+ for (let u = 0; u < n.length - 1; u++) {
482
+ const a = n[u];
483
+ r[a] || (r[a] = {}), r = r[a];
500
484
  }
501
- const c = a[a.length - 1];
502
- c !== void 0 && (i[c] = s);
485
+ const c = n[n.length - 1];
486
+ c !== void 0 && (r[c] = o);
503
487
  }
504
- function oe(t, e) {
505
- const s = D(t), a = R(t), i = T(t), c = s.getRoute(), u = {};
488
+ function ue(t, e) {
489
+ const o = B(t), n = w(t), r = T(t), c = o.getRoute(), u = {};
506
490
  if (e && e.length > 0) {
507
- const n = {};
508
- for (const l of e) {
509
- const r = s.t(l, void 0, void 0, c);
510
- r != null && r !== l && A(n, l, r);
491
+ const a = {};
492
+ for (const s of e) {
493
+ const i = o.t(s, void 0, void 0, c);
494
+ i != null && i !== s && E(a, s, i);
511
495
  }
512
- Object.keys(n).length > 0 && (u[c] = n);
496
+ Object.keys(a).length > 0 && (u[c] = a);
513
497
  } else {
514
- const n = s.getRouteTranslations(a, c);
515
- n && (u[c] = n);
498
+ const a = o.getRouteTranslations(n, c);
499
+ a && (u[c] = a);
516
500
  }
517
501
  return {
518
- locale: a,
519
- fallbackLocale: i,
502
+ locale: n,
503
+ fallbackLocale: r,
520
504
  currentRoute: c,
521
505
  translations: u
522
506
  };
523
507
  }
524
508
  export {
525
509
  j as AstroI18n,
526
- le as FormatService,
527
- x as createAstroRouterAdapter,
528
- J as createI18n,
529
- G as createI18nMiddleware,
530
- re as defaultPlural,
531
- K as detectLocale,
510
+ he as FormatService,
511
+ ne as createAstroRouterAdapter,
512
+ Y as createI18n,
513
+ ee as createI18nMiddleware,
514
+ de as defaultPlural,
515
+ te as detectLocale,
532
516
  T as getDefaultLocale,
533
- D as getI18n,
534
- oe as getI18nProps,
535
- R as getLocale,
536
- X as getLocaleFromPath,
537
- B as getLocales,
538
- Q as getRouteName,
539
- E as i18nIntegration,
540
- ie as interpolate,
541
- M as loadTranslationsFromDir,
542
- V as loadTranslationsIntoI18n,
543
- Z as localizePath,
544
- ee as removeLocaleFromPath,
545
- Y as switchLocalePath,
546
- te as useI18n,
547
- ne as useLocaleHead
517
+ B as getI18n,
518
+ ue as getI18nProps,
519
+ w as getLocale,
520
+ ae as getLocaleFromPath,
521
+ F as getLocales,
522
+ oe as getRouteName,
523
+ X as i18nIntegration,
524
+ me as interpolate,
525
+ U as loadTranslationsFromDir,
526
+ Z as loadTranslationsIntoI18n,
527
+ se as localizePath,
528
+ re as removeLocaleFromPath,
529
+ le as switchLocalePath,
530
+ ie as useI18n,
531
+ ce as useLocaleHead
548
532
  };
@@ -1,4 +1,5 @@
1
1
  import { Translations } from '@i18n-micro/types';
2
+ import { TranslationFileBuckets } from '@i18n-micro/utils/parse-path';
2
3
  /**
3
4
  * WARNING: Node.js-only functions
4
5
  *
@@ -20,9 +21,7 @@ export interface LoadTranslationsOptions {
20
21
  rootDir?: string;
21
22
  disablePageLocales?: boolean;
22
23
  }
23
- export interface LoadedTranslations {
24
- root: Record<string, Translations>;
25
- routes: Record<string, Record<string, Translations>>;
24
+ export interface LoadedTranslations extends TranslationFileBuckets<Translations> {
26
25
  }
27
26
  /**
28
27
  * Load all translations from a directory
package/package.json CHANGED
@@ -1,15 +1,35 @@
1
1
  {
2
2
  "name": "@i18n-micro/astro",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
+ "description": "Astro integration for i18n-micro — translations, routing, and islands.",
4
5
  "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/s00d/nuxt-i18n-micro.git",
11
+ "directory": "packages/astro"
12
+ },
13
+ "homepage": "https://github.com/s00d/nuxt-i18n-micro/tree/main/packages/astro#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/s00d/nuxt-i18n-micro/issues"
16
+ },
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
5
20
  "main": "./dist/index.cjs",
6
- "module": "./dist/index.mjs",
7
21
  "types": "./dist/index.d.ts",
8
22
  "exports": {
9
23
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.cjs"
24
+ "import": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.mjs"
27
+ },
28
+ "require": {
29
+ "types": "./dist/index.d.cts",
30
+ "default": "./dist/index.cjs"
31
+ },
32
+ "default": "./dist/index.mjs"
13
33
  },
14
34
  "./env": {
15
35
  "types": "./dist/env.d.ts"
@@ -41,19 +61,29 @@
41
61
  "./client/svelte": {
42
62
  "types": "./dist/client/svelte.d.ts",
43
63
  "import": "./dist/client/svelte.js"
44
- }
64
+ },
65
+ "./package.json": "./package.json"
45
66
  },
46
67
  "files": [
47
68
  "dist",
48
- "src"
69
+ "src",
70
+ "README.md",
71
+ "LICENSE"
49
72
  ],
50
73
  "publishConfig": {
51
74
  "access": "public"
52
75
  },
76
+ "keywords": [
77
+ "astro",
78
+ "i18n",
79
+ "internationalization",
80
+ "translations"
81
+ ],
53
82
  "dependencies": {
54
- "@i18n-micro/core": "1.3.0",
55
- "@i18n-micro/types": "1.2.1",
56
- "@i18n-micro/node": "1.2.0"
83
+ "@i18n-micro/types": "1.2.3",
84
+ "@i18n-micro/core": "1.3.1",
85
+ "@i18n-micro/utils": "1.0.0",
86
+ "@i18n-micro/node": "1.2.2"
57
87
  },
58
88
  "peerDependencies": {
59
89
  "astro": "^5.16.5",
@@ -86,12 +116,16 @@
86
116
  "svelte": "^4.0.0 || ^5.0.0",
87
117
  "vue": "^3.5.25",
88
118
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
89
- "preact": "^10.0.0"
119
+ "preact": "^10.0.0",
120
+ "publint": "^0.3.17",
121
+ "vitest": "^3.2.4"
90
122
  },
91
123
  "scripts": {
92
124
  "build": "vite build",
125
+ "check:package": "publint",
93
126
  "dev": "cd playground && astro dev",
94
127
  "test": "jest",
128
+ "test:dist": "vitest run --config vitest.dist.config.ts",
95
129
  "typecheck": "tsc --noEmit"
96
130
  }
97
131
  }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
2
- import { join, resolve } from 'node:path'
2
+ import { join, relative, resolve, sep } from 'node:path'
3
3
  import type { Translations } from '@i18n-micro/types'
4
+ import { mergeRouteTranslationsWithRoot, storeLoadedTranslationFile, type TranslationFileBuckets } from '@i18n-micro/utils/parse-path'
4
5
 
5
6
  /**
6
7
  * WARNING: Node.js-only functions
@@ -25,9 +26,24 @@ export interface LoadTranslationsOptions {
25
26
  disablePageLocales?: boolean
26
27
  }
27
28
 
28
- export interface LoadedTranslations {
29
- root: Record<string, Translations>
30
- routes: Record<string, Record<string, Translations>>
29
+ export interface LoadedTranslations extends TranslationFileBuckets<Translations> {}
30
+
31
+ function walkTranslationFiles(dir: string, onFile: (fullPath: string) => void): void {
32
+ if (!existsSync(dir)) return
33
+
34
+ for (const entry of readdirSync(dir)) {
35
+ const fullPath = join(dir, entry)
36
+ const stat = statSync(fullPath)
37
+
38
+ if (stat.isDirectory()) {
39
+ walkTranslationFiles(fullPath, onFile)
40
+ continue
41
+ }
42
+
43
+ if (entry.endsWith('.json')) {
44
+ onFile(fullPath)
45
+ }
46
+ }
31
47
  }
32
48
 
33
49
  /**
@@ -46,56 +62,21 @@ export function loadTranslationsFromDir(options: LoadTranslationsOptions): Loade
46
62
  return { root: {}, routes: {} }
47
63
  }
48
64
 
49
- const root: Record<string, Translations> = {}
50
- const routes: Record<string, Record<string, Translations>> = {}
51
-
52
- /**
53
- * Recursively load translation files
54
- */
55
- const loadFiles = (dir: string, routePrefix = ''): void => {
56
- if (!existsSync(dir)) return
57
-
58
- const entries = readdirSync(dir)
59
- for (const entry of entries) {
60
- const fullPath = join(dir, entry)
61
- const stat = statSync(fullPath)
62
-
63
- if (stat.isDirectory()) {
64
- // If it's a 'pages' directory and page locales are enabled, treat as route-specific
65
- if (entry === 'pages' && !disablePageLocales) {
66
- loadFiles(fullPath, '')
67
- } else if (routePrefix || disablePageLocales) {
68
- loadFiles(fullPath, routePrefix)
69
- } else {
70
- // This is a route directory (e.g., pages/home/)
71
- loadFiles(fullPath, entry)
72
- }
73
- } else if (entry.endsWith('.json')) {
74
- const locale = entry.replace('.json', '')
75
- try {
76
- const content = readFileSync(fullPath, 'utf-8')
77
- const translations = JSON.parse(content) as Translations
78
-
79
- if (routePrefix && !disablePageLocales) {
80
- // Route-specific translation
81
- if (!routes[routePrefix]) {
82
- routes[routePrefix] = {}
83
- }
84
- routes[routePrefix][locale] = translations
85
- } else {
86
- // Root-level translation
87
- root[locale] = translations
88
- }
89
- } catch (error) {
90
- console.error(`[i18n] Failed to load translation file: ${fullPath}`, error)
91
- }
92
- }
93
- }
94
- }
65
+ const buckets: LoadedTranslations = { root: {}, routes: {} }
95
66
 
96
- loadFiles(fullTranslationDir)
67
+ walkTranslationFiles(fullTranslationDir, (fullPath) => {
68
+ const relativePath = relative(fullTranslationDir, fullPath).split(sep).join('/')
69
+
70
+ try {
71
+ const content = readFileSync(fullPath, 'utf-8')
72
+ const translations = JSON.parse(content) as Translations
73
+ storeLoadedTranslationFile(buckets, relativePath, translations, disablePageLocales)
74
+ } catch (error) {
75
+ console.error(`[i18n] Failed to load translation file: ${fullPath}`, error)
76
+ }
77
+ })
97
78
 
98
- return { root, routes }
79
+ return buckets
99
80
  }
100
81
 
101
82
  /**
@@ -113,16 +94,13 @@ export function loadTranslationsIntoI18n(
113
94
  ): void {
114
95
  const { root, routes } = loadTranslationsFromDir(options)
115
96
 
116
- // Load root-level translations as index
117
97
  for (const [locale, translations] of Object.entries(root)) {
118
98
  i18n.addTranslations(locale, translations, false)
119
99
  }
120
100
 
121
- // Load route-specific translations (with root baked in)
122
101
  for (const [routeName, routeTranslations] of Object.entries(routes)) {
123
102
  for (const [locale, translations] of Object.entries(routeTranslations)) {
124
- const base = root[locale] || {}
125
- i18n.addRouteTranslations(locale, routeName, { ...base, ...translations }, false)
103
+ i18n.addRouteTranslations(locale, routeName, mergeRouteTranslationsWithRoot(root[locale], translations), false)
126
104
  }
127
105
  }
128
106
  }
package/src/middleware.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Locale } from '@i18n-micro/types'
2
+ import { detectLocaleFromAcceptLanguage } from '@i18n-micro/utils/accept-language'
2
3
  import type { MiddlewareHandler } from 'astro'
3
4
  import type { AstroI18n } from './composer'
4
5
  import { getGlobalRoutingStrategy } from './integration'
@@ -109,32 +110,6 @@ export function createI18nMiddleware(options: I18nMiddlewareOptions): Middleware
109
110
  }
110
111
  }
111
112
 
112
- /**
113
- * Parse Accept-Language header
114
- */
115
- function parseAcceptLanguage(acceptLanguage: string): string[] {
116
- const languages: string[] = []
117
- const parts = acceptLanguage.split(',')
118
-
119
- for (const part of parts) {
120
- const [lang, q = '1.0'] = part.trim().split(';q=')
121
- const quality = Number.parseFloat(q)
122
- if (quality > 0 && lang) {
123
- // Extract base language (e.g., 'en-US' -> 'en')
124
- const baseLang = lang.split('-')[0]?.toLowerCase()
125
- if (baseLang) {
126
- languages.push(baseLang)
127
- // Also add full locale if different
128
- if (lang !== baseLang) {
129
- languages.push(lang.toLowerCase())
130
- }
131
- }
132
- }
133
- }
134
-
135
- return languages
136
- }
137
-
138
113
  /**
139
114
  * Detect locale from request
140
115
  */
@@ -173,15 +148,9 @@ export function detectLocale(
173
148
  // 3. Try Accept-Language
174
149
  if (locale === defaultLocale) {
175
150
  try {
176
- const acceptLanguage = headers.get('accept-language')
177
- if (acceptLanguage) {
178
- const preferredLocales = parseAcceptLanguage(acceptLanguage)
179
- for (const preferred of preferredLocales) {
180
- if (locales.includes(preferred)) {
181
- locale = preferred
182
- break
183
- }
184
- }
151
+ const detectedFromHeader = detectLocaleFromAcceptLanguage(headers.get('accept-language') ?? undefined, locales)
152
+ if (detectedFromHeader) {
153
+ locale = detectedFromHeader
185
154
  }
186
155
  } catch {
187
156
  // Headers may be unavailable in static mode