@fluenti/solid 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/context.ts +5 -4
- package/src/server.ts +8 -0
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAE/I,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,CAAC,CAAA;AAwB5F,uDAAuD;AACvD,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,iDAAiD;IACjD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,+BAA+B;IAC/B,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,iCAAiC;IACjC,aAAa,CAAC,EAAE,mBAAmB,CAAA;CACpC;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,MAAM,IAAI,MAAM,CAAA;IAChB,wEAAwE;IACxE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,mEAAmE;IACnE,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;IAC3E,6CAA6C;IAC7C,CAAC,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAAA;IAC7D,iEAAiE;IACjE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACtD,wDAAwD;IACxD,UAAU,IAAI,MAAM,EAAE,CAAA;IACtB,iDAAiD;IACjD,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/C,mDAAmD;IACnD,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxC,gEAAgE;IAChE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;IACjE,uDAAuD;IACvD,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC5B,qDAAqD;IACrD,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IACpC,iEAAiE;IACjE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAE/I,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,MAAM,KACX,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,CAAC,CAAA;AAwB5F,uDAAuD;AACvD,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,iDAAiD;IACjD,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,qDAAqD;IACrD,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,+BAA+B;IAC/B,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,iCAAiC;IACjC,aAAa,CAAC,EAAE,mBAAmB,CAAA;CACpC;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,MAAM,IAAI,MAAM,CAAA;IAChB,wEAAwE;IACxE,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,mEAAmE;IACnE,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;IAC3E,6CAA6C;IAC7C,CAAC,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAAA;IAC7D,iEAAiE;IACjE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACtD,wDAAwD;IACxD,UAAU,IAAI,MAAM,EAAE,CAAA;IACtB,iDAAiD;IACjD,CAAC,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC/C,mDAAmD;IACnD,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxC,gEAAgE;IAChE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAA;IACjE,uDAAuD;IACvD,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IAC5B,qDAAqD;IACrD,aAAa,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IACpC,iEAAiE;IACjE,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW,CAsNhF;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW,CAezE;AAED,mDAAmD;AACnD,wBAAgB,oBAAoB,IAAI,WAAW,GAAG,SAAS,CAE9D;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAE3D;AAED,gEAAgE;AAChE,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`solid-js`),t=require(`@fluenti/core`),n=require(`solid-js/web`);var r=Symbol.for(`fluenti.runtime.solid`);function i(){let e=globalThis[r];return typeof e==`object`&&e?e:null}function a(e){return typeof e==`object`&&e&&`default`in e?e.default:e}function o(n){let[r,o]=(0,e.createSignal)(n.locale),[s,c]=(0,e.createSignal)(!1),l=new Set([n.locale]),[u,d]=(0,e.createSignal)(new Set(l)),f={...n.messages},p=n,m=p.lazyLocaleLoading??n.splitting??!1;function h(e,n,r){let i=f[n];if(!i)return;let a=i[e];if(a!==void 0)return typeof a==`function`?a(r):typeof a==`string`&&r?(0,t.interpolate)(a,r,n):String(a)}function g(e,t,r){let i=[t],a=new Set(i);n.fallbackLocale&&!a.has(n.fallbackLocale)&&(i.push(n.fallbackLocale),a.add(n.fallbackLocale));let o=p.fallbackChain?.[t]??p.fallbackChain?.[`*`];if(o)for(let e of o)a.has(e)||(i.push(e),a.add(e));for(let t of i){let n=h(e,t,r);if(n!==void 0)return n}}function _(e,t){if(!n.missing)return;let r=n.missing(t,e);if(r!==void 0)return r}function v(e,n,r){let i=g(e,n,r);if(i!==void 0)return i;let a=_(e,n);return a===void 0?e.includes(`{`)?(0,t.interpolate)(e,r,n):e:a}function y(e,...n){if(Array.isArray(e)&&`raw`in e)return y((0,t.buildICUMessage)(e,n),Object.fromEntries(n.map((e,t)=>[String(t),e])));let i=e,a=n[0],o=r();if(typeof i==`object`&&i){let e=(0,t.resolveDescriptorId)(i);if(e){let t=g(e,o,a);if(t!==void 0)return t;let n=_(e,o);if(n!==void 0)return n}return i.message===void 0?e??``:(0,t.interpolate)(i.message,a,o)}return v(i,o,a)}return{locale:r,setLocale:async e=>{if(!m||!p.chunkLoader){o(e);return}let t=i();if(l.has(e)){t?.__switchLocale&&await t.__switchLocale(e),o(e);return}c(!0);try{let n=a(await p.chunkLoader(e));f[e]={...f[e],...n},l.add(e),d(new Set(l)),t?.__switchLocale&&await t.__switchLocale(e),o(e)}finally{c(!1)}},t:y,loadMessages:(e,t)=>{f[e]={...f[e],...t},l.add(e),d(new Set(l))},getLocales:()=>Object.keys(f),d:(e,n)=>(0,t.formatDate)(e,r(),n,p.dateFormats),n:(e,n)=>(0,t.formatNumber)(e,r(),n,p.numberFormats),format:(e,n)=>(0,t.interpolate)(e,n,r()),isLoading:s,loadedLocales:u,preloadLocale:e=>{if(!m||l.has(e)||!p.chunkLoader)return;let t=i();p.chunkLoader(e).then(async n=>{let r=a(n);f[e]={...f[e],...r},l.add(e),d(new Set(l)),t?.__preloadLocale&&await t.__preloadLocale(e)}).catch(()
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`solid-js`),t=require(`@fluenti/core`),n=require(`solid-js/web`);var r=Symbol.for(`fluenti.runtime.solid`);function i(){let e=globalThis[r];return typeof e==`object`&&e?e:null}function a(e){return typeof e==`object`&&e&&`default`in e?e.default:e}function o(n){let[r,o]=(0,e.createSignal)(n.locale),[s,c]=(0,e.createSignal)(!1),l=new Set([n.locale]),[u,d]=(0,e.createSignal)(new Set(l)),f={...n.messages},p=n,m=p.lazyLocaleLoading??n.splitting??!1;function h(e,n,r){let i=f[n];if(!i)return;let a=i[e];if(a!==void 0)return typeof a==`function`?a(r):typeof a==`string`&&r?(0,t.interpolate)(a,r,n):String(a)}function g(e,t,r){let i=[t],a=new Set(i);n.fallbackLocale&&!a.has(n.fallbackLocale)&&(i.push(n.fallbackLocale),a.add(n.fallbackLocale));let o=p.fallbackChain?.[t]??p.fallbackChain?.[`*`];if(o)for(let e of o)a.has(e)||(i.push(e),a.add(e));for(let t of i){let n=h(e,t,r);if(n!==void 0)return n}}function _(e,t){if(!n.missing)return;let r=n.missing(t,e);if(r!==void 0)return r}function v(e,n,r){let i=g(e,n,r);if(i!==void 0)return i;let a=_(e,n);return a===void 0?e.includes(`{`)?(0,t.interpolate)(e,r,n):e:a}function y(e,...n){if(Array.isArray(e)&&`raw`in e)return y((0,t.buildICUMessage)(e,n),Object.fromEntries(n.map((e,t)=>[String(t),e])));let i=e,a=n[0],o=r();if(typeof i==`object`&&i){let e=(0,t.resolveDescriptorId)(i);if(e){let t=g(e,o,a);if(t!==void 0)return t;let n=_(e,o);if(n!==void 0)return n}return i.message===void 0?e??``:(0,t.interpolate)(i.message,a,o)}return v(i,o,a)}return{locale:r,setLocale:async e=>{if(!m||!p.chunkLoader){o(e);return}let t=i();if(l.has(e)){t?.__switchLocale&&await t.__switchLocale(e),o(e);return}c(!0);try{let n=a(await p.chunkLoader(e));f[e]={...f[e],...n},l.add(e),d(new Set(l)),t?.__switchLocale&&await t.__switchLocale(e),o(e)}finally{c(!1)}},t:y,loadMessages:(e,t)=>{f[e]={...f[e],...t},l.add(e),d(new Set(l))},getLocales:()=>Object.keys(f),d:(e,n)=>(0,t.formatDate)(e,r(),n,p.dateFormats),n:(e,n)=>(0,t.formatNumber)(e,r(),n,p.numberFormats),format:(e,n)=>(0,t.interpolate)(e,n,r()),isLoading:s,loadedLocales:u,preloadLocale:e=>{if(!m||l.has(e)||!p.chunkLoader)return;let t=i();p.chunkLoader(e).then(async n=>{let r=a(n);f[e]={...f[e],...r},l.add(e),d(new Set(l)),t?.__preloadLocale&&await t.__preloadLocale(e)}).catch(t=>{console.warn(`[fluenti] preload failed:`,e,t)})}}}var s;function c(t){let n=(0,e.createRoot)(()=>o(t));return typeof window<`u`?s=n:console.warn(`[fluenti] createI18n() detected SSR environment. Use <I18nProvider> for per-request isolation in SSR.`),n}function l(){return s}var u=(0,e.createContext)(),d=e=>{let t=o(e);return(0,n.createComponent)(u.Provider,{value:t,get children(){return e.children}})};function f(){let t=(0,e.useContext)(u);if(t)return t;let n=l();if(n)return n;throw Error(`useI18n requires either createI18n() to be called at startup, or the component to be inside an <I18nProvider>.`)}var p=((...e)=>{throw Error("[fluenti] `t` imported from '@fluenti/solid' is a compile-time API. Use it only with the Fluenti build transform inside a component or custom hook. For runtime lookups, use useI18n().t(...).")});function m(e){return typeof Node<`u`&&e instanceof Node}function h(e){return typeof e==`function`&&!e.length?e():e}function g(e,t){return t===0?e:e.replace(/<(\d+)(\/?>)/g,(e,n,r)=>`<${Number(n)+t}${r}`).replace(/<\/(\d+)>/g,(e,n)=>`</${Number(n)+t}>`)}function _(e){let t=[],n=``;function r(e){let i=h(e);if(i==null||typeof i==`boolean`)return;if(Array.isArray(i)){for(let e of i)r(e);return}if(typeof i==`string`||typeof i==`number`){n+=String(i);return}if(!m(i))return;if(i.nodeType===Node.TEXT_NODE){n+=i.textContent??``;return}if(i.nodeType===Node.DOCUMENT_FRAGMENT_NODE){r(Array.from(i.childNodes));return}let a=t.length,o=_(Array.from(i.childNodes));t.push(i.cloneNode(!1)),t.push(...o.components),n+=`<${a}>${g(o.message,a+1)}</${a}>`}return r(e),{message:n,components:t}}function v(e,t){let n=h(t);if(!(n==null||typeof n==`boolean`)){if(Array.isArray(n)){for(let t of n)v(e,t);return}if(typeof n==`string`||typeof n==`number`){e.appendChild(document.createTextNode(String(n)));return}m(n)&&e.appendChild(n)}}function y(e,t){let n=/<(\d+)>([\s\S]*?)<\/\1>/g,r=[],i=0,a;for(n.lastIndex=0,a=n.exec(e);a!==null;){a.index>i&&r.push(e.slice(i,a.index));let o=t[Number(a[1])],s=y(a[2],t);if(o){let e=o.cloneNode(!1);v(e,s),r.push(e)}else r.push(a[2]);i=n.lastIndex,a=n.exec(e)}return i<e.length&&r.push(e.slice(i)),r.length<=1?r[0]??``:r}function b(e,t){let n={},r=[];for(let i of e){let e=t[i];if(e===void 0)continue;let a=_(e);n[i]=g(a.message,r.length),r.push(...a.components)}for(let[i,a]of Object.entries(t)){if(e.includes(i)||a===void 0)continue;let t=_(a);n[i]=g(t.message,r.length),r.push(...t.components)}return{messages:n,components:r}}function x(e){return`{value, select, ${Object.entries(e).map(([e,t])=>`${e} {${t}}`).join(` `)}}`}function S(e){let t={},n={},r=0;for(let[i,a]of Object.entries(e)){if(i===`other`){t.other=a;continue}let e=/^[A-Za-z0-9_]+$/.test(i)?i:`case_${r++}`;t[e]=a,n[i]=e}return t.other===void 0&&(t.other=``),{forms:t,valueMap:n}}function C(e){let t=[],n=0;for(;n<e.length;){let r=e.indexOf(`<`,n);if(r===-1){t.push({type:`text`,value:e.slice(n)});break}r>n&&t.push({type:`text`,value:e.slice(n,r)});let i=e.slice(r).match(/^<(\w+)\s*\/>/);if(i){t.push({type:`tag`,name:i[1],children:[]}),n=r+i[0].length;continue}let a=e.slice(r).match(/^<(\w+)>/);if(!a){t.push({type:`text`,value:`<`}),n=r+1;continue}let o=a[1],s=r+a[0].length,c=w(e,o,s);if(c===-1){t.push({type:`text`,value:e.slice(r,s)}),n=s;continue}let l=e.slice(s,c),u=`</${o}>`;t.push({type:`tag`,name:o,children:C(l)}),n=c+u.length}return t}function w(e,t,n){let r=`<${t}>`,i=`</${t}>`,a=1,o=n;for(;o<e.length&&a>0;){let t=e.indexOf(r,o),n=e.indexOf(i,o);if(n===-1)return-1;if(t!==-1&&t<n)a++,o=t+r.length;else{if(a--,a===0)return n;o=n+i.length}}return-1}function T(e,t){let r=e.map(e=>{if(e.type===`text`)return e.value;let r=t[e.name];return r?(0,n.createComponent)(n.Dynamic,{component:r,children:e.children.length>0?T(e.children,t):void 0}):T(e.children,t)});return r.length===1?r[0]:r}var E=t=>{let{t:r}=f(),i=(0,e.children)(()=>t.children),a=(0,e.createMemo)(()=>{let e=t.__message??t.message;return typeof e==`function`?e():e}),o=(0,e.createMemo)(()=>t.__components??t.components);return(()=>{let e=a(),s=o();if(e!==void 0&&s)return T(C(r({...t.id===void 0?{}:{id:t.id},message:e,...t.context===void 0?{}:{context:t.context},...t.comment===void 0?{}:{comment:t.comment}})),s);let c=i.toArray();if(c.length===0)return null;let l=_(c),u=r({...t.id===void 0?{}:{id:t.id},message:l.message,...t.context===void 0?{}:{context:t.context},...t.comment===void 0?{}:{comment:t.comment}}),d=l.components.length>0?y(u,l.components):u;return Array.isArray(d)&&d.length>1?(0,n.createComponent)(n.Dynamic,{get component(){return t.tag??`span`},children:d}):d})},D=[`zero`,`one`,`two`,`few`,`many`,`other`];function O(e,t){let n=[];for(let t of D){let r=e[t];if(r!==void 0){let e=t===`zero`?`=0`:t;n.push(`${e} {${r}}`)}}return`{count, plural, ${t?`offset:${t} `:``}${n.join(` `)}}`}var k=e=>{let{t:r}=f();function i(e){return typeof e==`function`?e():e}return(()=>{let a={};for(let t of D){let n=i(e[t]);n!==void 0&&(a[t]=n)}let{messages:o,components:s}=b(D,a),c=O({...o.zero!==void 0&&{zero:o.zero},...o.one!==void 0&&{one:o.one},...o.two!==void 0&&{two:o.two},...o.few!==void 0&&{few:o.few},...o.many!==void 0&&{many:o.many},other:o.other??``},e.offset),l=r({id:e.id??(e.context===void 0?c:(0,t.hashMessage)(c,e.context)),message:c,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{count:e.value});return(0,n.createComponent)(n.Dynamic,{get component(){return e.tag??`span`},get children(){return(0,n.memo)(()=>s.length>0)()?y(l,s):l}})})},A=e=>{let{t:r}=f(),i=()=>e.tag??`span`,a=()=>{let n=e.options===void 0?{...Object.fromEntries(Object.entries(e).filter(([e])=>![`value`,`id`,`context`,`comment`,`options`,`other`,`tag`].includes(e))),other:e.other}:{...e.options,other:e.other},i=[...Object.keys(n).filter(e=>e!==`other`),`other`],{messages:a,components:o}=b(i,n),s=S(Object.fromEntries([...i].map(e=>[e,a[e]??``]))),c=r({id:e.id??(e.context===void 0?x(s.forms):(0,t.hashMessage)(x(s.forms),e.context)),message:x(s.forms),...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{value:s.valueMap[e.value]??`other`});return o.length>0?y(c,o):c};return(0,n.createComponent)(n.Dynamic,{get component(){return i()},get children(){return a()}})};function j(e){let{d:t}=f();return(0,n.memo)(()=>t(e.value,e.style))}function M(e){let{n:t}=f();return(0,n.memo)(()=>t(e.value,e.style))}exports.DateTime=j,exports.I18nCtx=u,exports.I18nProvider=d,exports.NumberFormat=M,exports.Plural=k,exports.Select=A,exports.Trans=E,exports.createI18n=c,exports.createI18nContext=o,Object.defineProperty(exports,`msg`,{enumerable:!0,get:function(){return t.msg}}),exports.t=p,exports.useI18n=f;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["createContext","ParentComponent","createI18nContext","I18nConfig","I18nContext","I18nCtx","I18nProvider","props","ctx","_$createComponent","Provider","value","children","JSX","isNodeLike","value","Node","resolveValue","length","offsetIndices","message","offset","replace","_match","index","suffix","Number","extractMessage","components","visit","node","resolved","undefined","Array","isArray","child","String","nodeType","TEXT_NODE","textContent","DOCUMENT_FRAGMENT_NODE","from","childNodes","idx","inner","push","Element","cloneNode","appendChild","parent","entry","document","createTextNode","reconstruct","translated","tagRe","result","lastIndex","match","RegExpExecArray","exec","slice","template","clone","serializeRichForms","keys","T","forms","Partial","Record","messages","key","extracted","Object","entries","includes","buildICUSelectMessage","map","text","join","normalizeSelectForms","valueMap","normalized","safeKey","test","Dynamic","children","resolveChildren","createMemo","Component","JSX","useI18n","extractMessage","extractDomMessage","reconstruct","reconstructDomMessage","RichComponent","Element","TransProps","id","context","comment","tag","message","components","Record","__message","__components","TextToken","type","value","TagToken","name","Token","parseTokens","input","tokens","pos","length","openIdx","indexOf","push","slice","selfCloseMatch","match","openMatch","tagName","contentStart","innerEnd","findClosingTag","innerContent","closingTag","startPos","openTag","closeTag","depth","nextOpen","nextClose","renderTokens","elements","map","token","Comp","undefined","_$createComponent","component","Trans","props","t","resolvedChildren","raw","msg","comps","translated","toArray","extracted","result","Array","isArray","Dynamic","Component","JSX","hashMessage","useI18n","reconstruct","serializeRichForms","PLURAL_CATEGORIES","const","PluralCategory","buildICUPluralMessage","forms","Partial","Record","other","offset","parts","cat","text","undefined","key","push","offsetPrefix","join","PluralProps","value","id","context","comment","zero","Element","one","two","few","many","tag","Plural","props","t","resolveProp","val","resolvedValues","resolved","messages","components","icuMessage","translated","message","count","_$createComponent","component","children","_$memo","length","Component","JSX","Dynamic","hashMessage","useI18n","buildICUSelectMessage","normalizeSelectForms","reconstruct","serializeRichForms","SelectProps","value","id","context","comment","other","Element","options","Record","tag","key","SelectComp","props","t","resolvedTag","content","forms","undefined","Object","fromEntries","entries","filter","includes","orderedKeys","keys","const","messages","components","normalized","map","translated","message","valueMap","length","_$createComponent","component","children","useI18n","DateTimeProps","value","Date","style","DateTime","props","d","_$memo","useI18n","NumberProps","value","style","NumberFormat","props","n","_$memo"],"sources":["../src/context.ts","../src/provider.tsx","../src/use-i18n.ts","../src/compile-time-t.ts","../src/rich-dom.tsx","../src/trans.tsx","../src/plural.tsx","../src/select.tsx","../src/components/DateTime.tsx","../src/components/NumberFormat.tsx"],"sourcesContent":["import { createSignal, createRoot, type Accessor } from 'solid-js'\nimport { formatDate, formatNumber, interpolate as coreInterpolate, buildICUMessage, resolveDescriptorId } from '@fluenti/core'\nimport type { FluentConfig, Locale, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions } from '@fluenti/core'\n\n/** Chunk loader for lazy locale loading */\nexport type ChunkLoader = (\n locale: string,\n) => Promise<Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> }>\n\ninterface SplitRuntimeModule {\n __switchLocale?: (locale: string) => Promise<void>\n __preloadLocale?: (locale: string) => Promise<void>\n}\n\nconst SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.solid')\n\nfunction getSplitRuntimeModule(): SplitRuntimeModule | null {\n const runtime = (globalThis as Record<PropertyKey, unknown>)[SPLIT_RUNTIME_KEY]\n return typeof runtime === 'object' && runtime !== null\n ? runtime as SplitRuntimeModule\n : null\n}\n\nfunction resolveChunkMessages(\n loaded: Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> },\n): Record<string, CompiledMessage> {\n return typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded as { default: Record<string, CompiledMessage> }).default\n : loaded\n}\n\n/** Extended config with lazy locale loading support */\nexport interface I18nConfig extends FluentConfig {\n /** Async chunk loader for lazy locale loading */\n chunkLoader?: ChunkLoader\n /** Enable lazy locale loading through chunkLoader */\n lazyLocaleLoading?: boolean\n /** Locale-specific fallback chains */\n fallbackChain?: Record<string, Locale[]>\n /** Named date format styles */\n dateFormats?: DateFormatOptions\n /** Named number format styles */\n numberFormats?: NumberFormatOptions\n}\n\n/** Reactive i18n context holding locale signal and translation utilities */\nexport interface I18nContext {\n /** Reactive accessor for the current locale */\n locale(): Locale\n /** Set the active locale (async when lazy locale loading is enabled) */\n setLocale(locale: Locale): Promise<void>\n /** Translate a message by id with optional interpolation values */\n t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n /** Tagged template form: t`Hello ${name}` */\n t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n /** Merge additional messages into a locale catalog at runtime */\n loadMessages(locale: Locale, messages: Messages): void\n /** Return all locale codes that have loaded messages */\n getLocales(): Locale[]\n /** Format a date value for the current locale */\n d(value: Date | number, style?: string): string\n /** Format a number value for the current locale */\n n(value: number, style?: string): string\n /** Format an ICU message string directly (no catalog lookup) */\n format(message: string, values?: Record<string, unknown>): string\n /** Whether a locale chunk is currently being loaded */\n isLoading: Accessor<boolean>\n /** Set of locales whose messages have been loaded */\n loadedLocales: Accessor<Set<string>>\n /** Preload a locale in the background without switching to it */\n preloadLocale(locale: string): void\n}\n\n/**\n * Create a reactive i18n context backed by Solid signals.\n *\n * The returned `t()` reads the internal `locale()` signal, so any\n * Solid computation that calls `t()` will re-run when the locale changes.\n */\nexport function createI18nContext(config: FluentConfig | I18nConfig): I18nContext {\n const [locale, setLocaleSignal] = createSignal<Locale>(config.locale)\n const [isLoading, setIsLoading] = createSignal(false)\n const loadedLocalesSet = new Set<string>([config.locale])\n const [loadedLocales, setLoadedLocales] = createSignal(new Set(loadedLocalesSet))\n const messages: Record<string, Messages> = { ...config.messages }\n const i18nConfig = config as I18nConfig\n const lazyLocaleLoading = i18nConfig.lazyLocaleLoading\n ?? (config as I18nConfig & { splitting?: boolean }).splitting\n ?? false\n\n function lookupCatalog(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const catalog = messages[loc]\n if (!catalog) {\n return undefined\n }\n\n const msg = catalog[id]\n if (msg === undefined) {\n return undefined\n }\n\n if (typeof msg === 'function') {\n return msg(values)\n }\n\n if (typeof msg === 'string' && values) {\n return coreInterpolate(msg, values, loc)\n }\n\n return String(msg)\n }\n\n function lookupWithFallbacks(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const localesToTry: Locale[] = [loc]\n const seen = new Set(localesToTry)\n\n if (config.fallbackLocale && !seen.has(config.fallbackLocale)) {\n localesToTry.push(config.fallbackLocale)\n seen.add(config.fallbackLocale)\n }\n\n const chainLocales = i18nConfig.fallbackChain?.[loc] ?? i18nConfig.fallbackChain?.['*']\n if (chainLocales) {\n for (const chainLocale of chainLocales) {\n if (!seen.has(chainLocale)) {\n localesToTry.push(chainLocale)\n seen.add(chainLocale)\n }\n }\n }\n\n for (const targetLocale of localesToTry) {\n const result = lookupCatalog(id, targetLocale, values)\n if (result !== undefined) {\n return result\n }\n }\n\n return undefined\n }\n\n function resolveMissing(\n id: string,\n loc: Locale,\n ): string | undefined {\n if (!config.missing) {\n return undefined\n }\n\n const result = config.missing(loc, id)\n if (result !== undefined) {\n return result\n }\n return undefined\n }\n\n function resolveMessage(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string {\n const catalogResult = lookupWithFallbacks(id, loc, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(id, loc)\n if (missingResult !== undefined) {\n return missingResult\n }\n\n if (id.includes('{')) {\n return coreInterpolate(id, values, loc)\n }\n\n return id\n }\n\n function t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n function t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n function t(idOrStrings: string | MessageDescriptor | TemplateStringsArray, ...rest: unknown[]): string {\n // Tagged template form: t`Hello ${name}`\n if (Array.isArray(idOrStrings) && 'raw' in idOrStrings) {\n const strings = idOrStrings as TemplateStringsArray\n const icu = buildICUMessage(strings, rest)\n const values = Object.fromEntries(rest.map((v, i) => [String(i), v]))\n return t(icu, values)\n }\n\n const id = idOrStrings as string | MessageDescriptor\n const values = rest[0] as Record<string, unknown> | undefined\n const currentLocale = locale() // reactive dependency\n if (typeof id === 'object' && id !== null) {\n const messageId = resolveDescriptorId(id)\n if (messageId) {\n const catalogResult = lookupWithFallbacks(messageId, currentLocale, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(messageId, currentLocale)\n if (missingResult !== undefined) {\n return missingResult\n }\n }\n\n if (id.message !== undefined) {\n return coreInterpolate(id.message, values, currentLocale)\n }\n\n return messageId ?? ''\n }\n\n return resolveMessage(id, currentLocale, values)\n }\n\n const loadMessages = (loc: Locale, msgs: Messages): void => {\n messages[loc] = { ...messages[loc], ...msgs }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n }\n\n const setLocale = async (newLocale: Locale): Promise<void> => {\n if (!lazyLocaleLoading || !i18nConfig.chunkLoader) {\n setLocaleSignal(newLocale)\n return\n }\n\n const splitRuntime = getSplitRuntimeModule()\n\n if (loadedLocalesSet.has(newLocale)) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n return\n }\n\n setIsLoading(true)\n try {\n const loaded = resolveChunkMessages(await i18nConfig.chunkLoader(newLocale))\n messages[newLocale] = { ...messages[newLocale], ...loaded }\n loadedLocalesSet.add(newLocale)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n } finally {\n setIsLoading(false)\n }\n }\n\n const preloadLocale = (loc: string): void => {\n if (!lazyLocaleLoading || loadedLocalesSet.has(loc) || !i18nConfig.chunkLoader) return\n const splitRuntime = getSplitRuntimeModule()\n i18nConfig.chunkLoader(loc).then(async (loaded) => {\n const resolved = resolveChunkMessages(loaded)\n messages[loc] = { ...messages[loc], ...resolved }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n }).catch(() => {\n // Silent failure for preload\n })\n }\n\n const getLocales = (): Locale[] => Object.keys(messages)\n\n const d = (value: Date | number, style?: string): string =>\n formatDate(value, locale(), style, i18nConfig.dateFormats)\n\n const n = (value: number, style?: string): string =>\n formatNumber(value, locale(), style, i18nConfig.numberFormats)\n\n const format = (message: string, values?: Record<string, unknown>): string => {\n return coreInterpolate(message, values, locale())\n }\n\n return { locale, setLocale, t, loadMessages, getLocales, d, n, format, isLoading, loadedLocales, preloadLocale }\n}\n\n// ─── Module-level singleton ─────────────────────────────────────────────────\n\nlet globalCtx: I18nContext | undefined\n\n/**\n * Initialize the global i18n singleton.\n *\n * Call once at app startup (e.g. in your entry file) before any `useI18n()`.\n * Signals are created inside a `createRoot` so they outlive any component scope.\n *\n * Returns the context for convenience, but `useI18n()` will also find it.\n */\nexport function createI18n(config: FluentConfig | I18nConfig): I18nContext {\n const ctx = createRoot(() => createI18nContext(config))\n\n // Only set global singleton in browser (client-side).\n // In SSR, each request should use <I18nProvider> for per-request isolation.\n if (typeof window !== 'undefined') {\n globalCtx = ctx\n } else {\n console.warn(\n '[fluenti] createI18n() detected SSR environment. ' +\n 'Use <I18nProvider> for per-request isolation in SSR.',\n )\n // Still set globalCtx as fallback, but document the risk\n globalCtx = ctx\n }\n\n return ctx\n}\n\n/** @internal — used by useI18n and I18nProvider */\nexport function getGlobalI18nContext(): I18nContext | undefined {\n return globalCtx\n}\n\n/** @internal — used by I18nProvider to set context without createRoot wrapper */\nexport function setGlobalI18nContext(ctx: I18nContext): void {\n globalCtx = ctx\n}\n\n/** @internal — reset the global singleton (for testing only) */\nexport function resetGlobalI18nContext(): void {\n globalCtx = undefined\n}\n","import { createContext } from 'solid-js'\nimport type { ParentComponent } from 'solid-js'\nimport { createI18nContext } from './context'\nimport type { I18nConfig, I18nContext } from './context'\n\n/** Solid context object for i18n — used internally by useI18n() */\nexport const I18nCtx = createContext<I18nContext>()\n\n/**\n * Provide i18n context to the component tree.\n *\n */\nexport const I18nProvider: ParentComponent<I18nConfig> = (props) => {\n const ctx = createI18nContext(props)\n return <I18nCtx.Provider value={ctx}>{props.children}</I18nCtx.Provider>\n}\n","import { useContext } from 'solid-js'\nimport { I18nCtx } from './provider'\nimport { getGlobalI18nContext } from './context'\nimport type { I18nContext } from './context'\n\n/**\n * Access the i18n context.\n *\n * Resolution order:\n * 1. Nearest `<I18nProvider>` in the component tree\n * 2. Module-level singleton created by `createI18n()`\n *\n * Throws if neither is available.\n */\nexport function useI18n(): I18nContext {\n const ctx = useContext(I18nCtx)\n if (ctx) {\n return ctx\n }\n\n const global = getGlobalI18nContext()\n if (global) {\n return global\n }\n\n throw new Error(\n 'useI18n requires either createI18n() to be called at startup, ' +\n 'or the component to be inside an <I18nProvider>.',\n )\n}\n","import type { CompileTimeT } from '@fluenti/core'\n\nexport const t: CompileTimeT = ((..._args: unknown[]) => {\n throw new Error(\n \"[fluenti] `t` imported from '@fluenti/solid' is a compile-time API. \" +\n 'Use it only with the Fluenti build transform inside a component or custom hook. ' +\n 'For runtime lookups, use useI18n().t(...).',\n )\n}) as CompileTimeT\n","import type { JSX } from 'solid-js'\n\nfunction isNodeLike(value: unknown): value is Node {\n return typeof Node !== 'undefined' && value instanceof Node\n}\n\nfunction resolveValue(value: unknown): unknown {\n if (typeof value === 'function' && !(value as { length?: number }).length) {\n return (value as () => unknown)()\n }\n return value\n}\n\nexport function offsetIndices(message: string, offset: number): string {\n if (offset === 0) return message\n return message\n .replace(/<(\\d+)(\\/?>)/g, (_match, index: string, suffix: string) => `<${Number(index) + offset}${suffix}`)\n .replace(/<\\/(\\d+)>/g, (_match, index: string) => `</${Number(index) + offset}>`)\n}\n\nexport function extractMessage(value: unknown): {\n message: string\n components: Node[]\n} {\n const components: Node[] = []\n let message = ''\n\n function visit(node: unknown): void {\n const resolved = resolveValue(node)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const child of resolved) visit(child)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n message += String(resolved)\n return\n }\n if (!isNodeLike(resolved)) return\n if (resolved.nodeType === Node.TEXT_NODE) {\n message += resolved.textContent ?? ''\n return\n }\n if (resolved.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n visit(Array.from(resolved.childNodes))\n return\n }\n\n const idx = components.length\n const inner = extractMessage(Array.from(resolved.childNodes))\n components.push((resolved as Element).cloneNode(false))\n components.push(...inner.components)\n message += `<${idx}>${offsetIndices(inner.message, idx + 1)}</${idx}>`\n }\n\n visit(value)\n return { message, components }\n}\n\nfunction appendChild(parent: Node, child: unknown): void {\n const resolved = resolveValue(child)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const entry of resolved) appendChild(parent, entry)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n parent.appendChild(document.createTextNode(String(resolved)))\n return\n }\n if (isNodeLike(resolved)) {\n parent.appendChild(resolved)\n }\n}\n\nexport function reconstruct(\n translated: string,\n components: Node[],\n): JSX.Element {\n const tagRe = /<(\\d+)>([\\s\\S]*?)<\\/\\1>/g\n const result: unknown[] = []\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n tagRe.lastIndex = 0\n match = tagRe.exec(translated)\n while (match !== null) {\n if (match.index > lastIndex) {\n result.push(translated.slice(lastIndex, match.index))\n }\n\n const idx = Number(match[1])\n const template = components[idx]\n const inner = reconstruct(match[2]!, components)\n if (template) {\n const clone = template.cloneNode(false)\n appendChild(clone, inner)\n result.push(clone)\n } else {\n result.push(match[2]!)\n }\n\n lastIndex = tagRe.lastIndex\n match = tagRe.exec(translated)\n }\n\n if (lastIndex < translated.length) {\n result.push(translated.slice(lastIndex))\n }\n\n return (result.length <= 1 ? result[0] ?? '' : result) as JSX.Element\n}\n\nexport function serializeRichForms<T extends string>(\n keys: readonly T[],\n forms: Partial<Record<T, unknown>> & Record<string, unknown>,\n): {\n messages: Record<string, string>\n components: Node[]\n} {\n const messages: Record<string, string> = {}\n const components: Node[] = []\n\n for (const key of keys) {\n const value = forms[key]\n if (value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n for (const [key, value] of Object.entries(forms)) {\n if (keys.includes(key as T) || value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n return { messages, components }\n}\n\nexport function buildICUSelectMessage(forms: Record<string, string>): string {\n return `{value, select, ${Object.entries(forms).map(([key, text]) => `${key} {${text}}`).join(' ')}}`\n}\n\nexport function normalizeSelectForms(forms: Record<string, string>): {\n forms: Record<string, string>\n valueMap: Record<string, string>\n} {\n const normalized: Record<string, string> = {}\n const valueMap: Record<string, string> = {}\n let index = 0\n\n for (const [key, text] of Object.entries(forms)) {\n if (key === 'other') {\n normalized['other'] = text\n continue\n }\n\n const safeKey = /^[A-Za-z0-9_]+$/.test(key) ? key : `case_${index++}`\n normalized[safeKey] = text\n valueMap[key] = safeKey\n }\n\n if (normalized['other'] === undefined) {\n normalized['other'] = ''\n }\n\n return { forms: normalized, valueMap }\n}\n","import { Dynamic } from 'solid-js/web'\nimport { children as resolveChildren, createMemo } from 'solid-js'\nimport type { Component, JSX } from 'solid-js'\nimport { useI18n } from './use-i18n'\nimport { extractMessage as extractDomMessage, reconstruct as reconstructDomMessage } from './rich-dom'\n\n/** A Solid component that accepts children */\nexport type RichComponent = Component<{ children?: JSX.Element }>\n\n/** Props for the `<Trans>` component */\nexport interface TransProps {\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Wrapper element tag name (default: `'span'`) — used in children-only mode */\n tag?: string\n /** Children — the content to translate (legacy API) */\n children?: JSX.Element\n /** Translated message string with XML-like tags (e.g. `<bold>text</bold>`) */\n message?: string\n /** Map of tag names to Solid components */\n components?: Record<string, RichComponent>\n /** @internal Pre-computed message from build plugin */\n __message?: string\n /** @internal Pre-computed component map from build plugin */\n __components?: Record<string, RichComponent>\n}\n\n/**\n * A token from parsing the message string.\n * Either a plain text segment or a tag with inner content.\n */\ninterface TextToken {\n readonly type: 'text'\n readonly value: string\n}\n\ninterface TagToken {\n readonly type: 'tag'\n readonly name: string\n readonly children: readonly Token[]\n}\n\ntype Token = TextToken | TagToken\n\n/**\n * Parse a message string containing XML-like tags into a token tree.\n *\n * Supports:\n * - Named tags: `<bold>content</bold>`\n * - Self-closing tags: `<br/>`\n * - Nested tags: `<bold>hello <italic>world</italic></bold>`\n */\nfunction parseTokens(input: string): readonly Token[] {\n const tokens: Token[] = []\n let pos = 0\n\n while (pos < input.length) {\n const openIdx = input.indexOf('<', pos)\n\n if (openIdx === -1) {\n // No more tags — rest is plain text\n tokens.push({ type: 'text', value: input.slice(pos) })\n break\n }\n\n // Push any text before this tag\n if (openIdx > pos) {\n tokens.push({ type: 'text', value: input.slice(pos, openIdx) })\n }\n\n // Check for self-closing tag: <tagName/>\n const selfCloseMatch = input.slice(openIdx).match(/^<(\\w+)\\s*\\/>/)\n if (selfCloseMatch) {\n tokens.push({ type: 'tag', name: selfCloseMatch[1]!, children: [] })\n pos = openIdx + selfCloseMatch[0].length\n continue\n }\n\n // Check for opening tag: <tagName>\n const openMatch = input.slice(openIdx).match(/^<(\\w+)>/)\n if (!openMatch) {\n // Not a valid tag — treat '<' as text\n tokens.push({ type: 'text', value: '<' })\n pos = openIdx + 1\n continue\n }\n\n const tagName = openMatch[1]!\n const contentStart = openIdx + openMatch[0].length\n\n // Find the matching closing tag, respecting nesting\n const innerEnd = findClosingTag(input, tagName, contentStart)\n if (innerEnd === -1) {\n // No closing tag found — treat as plain text\n tokens.push({ type: 'text', value: input.slice(openIdx, contentStart) })\n pos = contentStart\n continue\n }\n\n const innerContent = input.slice(contentStart, innerEnd)\n const closingTag = `</${tagName}>`\n tokens.push({\n type: 'tag',\n name: tagName,\n children: parseTokens(innerContent),\n })\n pos = innerEnd + closingTag.length\n }\n\n return tokens\n}\n\n/**\n * Find the position of the matching closing tag, accounting for nesting\n * of the same tag name.\n *\n * Returns the index of the start of the closing tag, or -1 if not found.\n */\nfunction findClosingTag(input: string, tagName: string, startPos: number): number {\n const openTag = `<${tagName}>`\n const closeTag = `</${tagName}>`\n let depth = 1\n let pos = startPos\n\n while (pos < input.length && depth > 0) {\n const nextOpen = input.indexOf(openTag, pos)\n const nextClose = input.indexOf(closeTag, pos)\n\n if (nextClose === -1) return -1\n\n if (nextOpen !== -1 && nextOpen < nextClose) {\n depth++\n pos = nextOpen + openTag.length\n } else {\n depth--\n if (depth === 0) return nextClose\n pos = nextClose + closeTag.length\n }\n }\n\n return -1\n}\n\n/**\n * Render a token tree into Solid JSX elements using the components map.\n */\nfunction renderTokens(\n tokens: readonly Token[],\n components: Record<string, RichComponent>,\n): JSX.Element {\n const elements = tokens.map((token): JSX.Element => {\n if (token.type === 'text') {\n return token.value as unknown as JSX.Element\n }\n\n const Comp = components[token.name]\n if (!Comp) {\n // Unknown component — render inner content as plain text\n return renderTokens(token.children, components)\n }\n\n const innerContent = token.children.length > 0\n ? renderTokens(token.children, components)\n : undefined\n\n return (<Dynamic component={Comp}>{innerContent}</Dynamic>) as JSX.Element\n })\n\n if (elements.length === 1) return elements[0]!\n return (<>{elements}</>) as JSX.Element\n}\n\n/**\n * Render translated content with inline components.\n *\n * Supports two APIs:\n *\n * 1. **message + components** (recommended for rich text):\n * ```tsx\n * <Trans\n * message={t`Welcome to <bold>Fluenti</bold>!`}\n * components={{ bold: (props) => <strong>{props.children}</strong> }}\n * />\n * ```\n *\n * 2. **children** (legacy / simple passthrough):\n * ```tsx\n * <Trans>Click <a href=\"/next\">here</a> to continue</Trans>\n * ```\n */\nexport const Trans: Component<TransProps> = (props) => {\n const { t } = useI18n()\n const resolvedChildren = resolveChildren(() => props.children)\n // message + components API (including build-time __message/__components)\n // Note: the vite-plugin tagged-template transform wraps Solid expressions in\n // createMemo(), so props.message may be a memo accessor (function) instead of\n // a string. We unwrap it here to handle both cases.\n const message = createMemo(() => {\n const raw = props.__message ?? props.message\n return typeof raw === 'function' ? (raw as () => string)() : raw\n })\n const components = createMemo(() => props.__components ?? props.components)\n\n return (() => {\n const msg = message()\n const comps = components()\n\n if (msg !== undefined && comps) {\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: msg,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const tokens = parseTokens(translated)\n return renderTokens(tokens, comps)\n }\n\n // Fallback: children-only API with runtime extraction/reconstruction\n const children = resolvedChildren.toArray()\n if (children.length === 0) return null\n const extracted = extractDomMessage(children)\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: extracted.message,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const result = extracted.components.length > 0\n ? reconstructDomMessage(translated, extracted.components)\n : translated\n\n if (Array.isArray(result) && result.length > 1) {\n return (<Dynamic component={props.tag ?? 'span'}>{result}</Dynamic>) as JSX.Element\n }\n\n return result as JSX.Element\n }) as unknown as JSX.Element\n}\n","import { Dynamic } from 'solid-js/web'\nimport type { Component, JSX } from 'solid-js'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { reconstruct, serializeRichForms } from './rich-dom'\n\n/** Plural category names in a stable order for ICU message building. */\nconst PLURAL_CATEGORIES = ['zero', 'one', 'two', 'few', 'many', 'other'] as const\n\ntype PluralCategory = (typeof PLURAL_CATEGORIES)[number]\n\n/**\n * Build an ICU plural message string from individual category props.\n *\n * Given `{ zero: \"No items\", one: \"# item\", other: \"# items\" }`,\n * produces `\"{count, plural, =0 {No items} one {# item} other {# items}}\"`.\n *\n * @internal\n */\nfunction buildICUPluralMessage(\n forms: Partial<Record<PluralCategory, string>> & { other: string },\n offset?: number,\n): string {\n const parts: string[] = []\n for (const cat of PLURAL_CATEGORIES) {\n const text = forms[cat]\n if (text !== undefined) {\n // Map the `zero` prop to ICU `=0` exact match. In ICU MessageFormat,\n // `zero` is a CLDR plural category that only activates in languages\n // with a grammatical zero form (e.g. Arabic). The `=0` exact match\n // works universally for the common \"show this when count is 0\" intent.\n const key = cat === 'zero' ? '=0' : cat\n parts.push(`${key} {${text}}`)\n }\n }\n const offsetPrefix = offset ? `offset:${offset} ` : ''\n return `{count, plural, ${offsetPrefix}${parts.join(' ')}}`\n}\n\n/** Props for the `<Plural>` component */\nexport interface PluralProps {\n /** The numeric value to pluralise */\n value: number\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Offset from value before selecting form */\n offset?: number\n /** Message for the \"zero\" plural category */\n zero?: string | JSX.Element\n /** Message for the \"one\" plural category */\n one?: string | JSX.Element\n /** Message for the \"two\" plural category */\n two?: string | JSX.Element\n /** Message for the \"few\" plural category */\n few?: string | JSX.Element\n /** Message for the \"many\" plural category */\n many?: string | JSX.Element\n /** Fallback message when no category-specific prop matches */\n other: string | JSX.Element\n /** Wrapper element tag name (default: `'span'`) */\n tag?: string\n}\n\n/**\n * `<Plural>` component — shorthand for ICU plural patterns.\n *\n * Plural form props (`zero`, `one`, `two`, `few`, `many`, `other`) are treated\n * as source-language messages. The component builds an ICU plural message,\n * looks it up via `t()` in the catalog, and interpolates the translated result.\n *\n * When no catalog translation exists, the component falls back to interpolating\n * the source-language ICU message directly via core's `interpolate`.\n *\n * Rich text is supported via JSX element props:\n * ```tsx\n * <Plural\n * value={count()}\n * zero={<>No <strong>items</strong> left</>}\n * one={<><em>1</em> item remaining</>}\n * other={<><strong>{count()}</strong> items remaining</>}\n * />\n * ```\n *\n * String props still work (backward compatible):\n * ```tsx\n * <Plural value={count()} zero=\"No items\" one=\"# item\" other=\"# items\" />\n * ```\n */\nexport const Plural: Component<PluralProps> = (props) => {\n const { t } = useI18n()\n\n /** Resolve a category prop value — handles string, accessor function, and JSX */\n function resolveProp(val: string | JSX.Element | undefined): string | JSX.Element | undefined {\n if (typeof val === 'function') return (val as () => string | JSX.Element)()\n return val\n }\n\n return (() => {\n // Resolve all category values (handles Solid accessors from createMemo)\n const resolvedValues: Partial<Record<PluralCategory, string | JSX.Element>> = {}\n for (const cat of PLURAL_CATEGORIES) {\n const resolved = resolveProp(props[cat])\n if (resolved !== undefined) {\n resolvedValues[cat] = resolved\n }\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, resolvedValues)\n const icuMessage = buildICUPluralMessage(\n {\n ...(messages['zero'] !== undefined && { zero: messages['zero'] }),\n ...(messages['one'] !== undefined && { one: messages['one'] }),\n ...(messages['two'] !== undefined && { two: messages['two'] }),\n ...(messages['few'] !== undefined && { few: messages['few'] }),\n ...(messages['many'] !== undefined && { many: messages['many'] }),\n other: messages['other'] ?? '',\n },\n props.offset,\n )\n\n const translated = t(\n {\n id: props.id ?? (props.context === undefined ? icuMessage : hashMessage(icuMessage, props.context)),\n message: icuMessage,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { count: props.value },\n )\n\n return (<Dynamic component={props.tag ?? 'span'}>{components.length > 0 ? reconstruct(translated, components) : translated}</Dynamic>) as JSX.Element\n }) as unknown as JSX.Element\n}\n","import type { Component, JSX } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { buildICUSelectMessage, normalizeSelectForms, reconstruct, serializeRichForms } from './rich-dom'\n\n/** Props for the `<Select>` component */\nexport interface SelectProps {\n /** The value to match against prop keys */\n value: string\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Fallback message when no key matches */\n other: string | JSX.Element\n /**\n * Named options map. Keys are match values, values are display strings or JSX.\n * Takes precedence over dynamic attrs when both are provided.\n *\n * @example `{ male: 'He', female: 'She' }`\n */\n options?: Record<string, string | JSX.Element>\n /** Wrapper element tag name (default: `span`) */\n tag?: string\n /** Additional key/message pairs for matching (attrs fallback) */\n [key: string]: unknown\n}\n\n/**\n * Render a message selected by matching `value` against prop keys.\n *\n * Options can be provided via the type-safe `options` prop (recommended)\n * or as direct attrs (convenience). When both are present, `options` takes\n * precedence.\n *\n * Rich text is supported via JSX element values in the `options` prop or\n * as direct JSX element props:\n * ```tsx\n * <Select\n * value={gender()}\n * options={{\n * male: <><strong>He</strong> liked this</>,\n * female: <><strong>She</strong> liked this</>,\n * }}\n * other={<><em>They</em> liked this</>}\n * />\n * ```\n *\n * Falls back to the `other` prop when no key matches.\n */\nexport const SelectComp: Component<SelectProps> = (props) => {\n const { t } = useI18n()\n\n const resolvedTag = () => props.tag ?? 'span'\n\n const content = () => {\n const forms: Record<string, unknown> = props.options !== undefined\n ? { ...props.options, other: props.other }\n : {\n ...Object.fromEntries(\n Object.entries(props).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other', 'tag'].includes(key)),\n ),\n other: props.other,\n }\n\n const orderedKeys = [...Object.keys(forms).filter(key => key !== 'other'), 'other'] as const\n const { messages, components } = serializeRichForms(orderedKeys, forms)\n const normalized = normalizeSelectForms(\n Object.fromEntries([...orderedKeys].map((key) => [key, messages[key] ?? ''])),\n )\n const translated = t(\n {\n id: props.id ?? (props.context === undefined\n ? buildICUSelectMessage(normalized.forms)\n : hashMessage(buildICUSelectMessage(normalized.forms), props.context)),\n message: buildICUSelectMessage(normalized.forms),\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { value: normalized.valueMap[props.value] ?? 'other' },\n )\n\n return components.length > 0 ? reconstruct(translated, components) : translated\n }\n\n return (<Dynamic component={resolvedTag()}>{content()}</Dynamic>) as JSX.Element\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface DateTimeProps {\n /** Date value to format */\n value: Date | number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<DateTime>` — date formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport function DateTime(props: DateTimeProps) {\n const { d } = useI18n()\n return <>{d(props.value, props.style)}</>\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<NumberFormat>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <NumberFormat value={1234.56} style=\"currency\" />\n * ```\n */\nexport function NumberFormat(props: NumberProps) {\n const { n } = useI18n()\n return <>{n(props.value, props.style)}</>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["createContext","ParentComponent","createI18nContext","I18nConfig","I18nContext","I18nCtx","I18nProvider","props","ctx","_$createComponent","Provider","value","children","JSX","isNodeLike","value","Node","resolveValue","length","offsetIndices","message","offset","replace","_match","index","suffix","Number","extractMessage","components","visit","node","resolved","undefined","Array","isArray","child","String","nodeType","TEXT_NODE","textContent","DOCUMENT_FRAGMENT_NODE","from","childNodes","idx","inner","push","Element","cloneNode","appendChild","parent","entry","document","createTextNode","reconstruct","translated","tagRe","result","lastIndex","match","RegExpExecArray","exec","slice","template","clone","serializeRichForms","keys","T","forms","Partial","Record","messages","key","extracted","Object","entries","includes","buildICUSelectMessage","map","text","join","normalizeSelectForms","valueMap","normalized","safeKey","test","Dynamic","children","resolveChildren","createMemo","Component","JSX","useI18n","extractMessage","extractDomMessage","reconstruct","reconstructDomMessage","RichComponent","Element","TransProps","id","context","comment","tag","message","components","Record","__message","__components","TextToken","type","value","TagToken","name","Token","parseTokens","input","tokens","pos","length","openIdx","indexOf","push","slice","selfCloseMatch","match","openMatch","tagName","contentStart","innerEnd","findClosingTag","innerContent","closingTag","startPos","openTag","closeTag","depth","nextOpen","nextClose","renderTokens","elements","map","token","Comp","undefined","_$createComponent","component","Trans","props","t","resolvedChildren","raw","msg","comps","translated","toArray","extracted","result","Array","isArray","Dynamic","Component","JSX","hashMessage","useI18n","reconstruct","serializeRichForms","PLURAL_CATEGORIES","const","PluralCategory","buildICUPluralMessage","forms","Partial","Record","other","offset","parts","cat","text","undefined","key","push","offsetPrefix","join","PluralProps","value","id","context","comment","zero","Element","one","two","few","many","tag","Plural","props","t","resolveProp","val","resolvedValues","resolved","messages","components","icuMessage","translated","message","count","_$createComponent","component","children","_$memo","length","Component","JSX","Dynamic","hashMessage","useI18n","buildICUSelectMessage","normalizeSelectForms","reconstruct","serializeRichForms","SelectProps","value","id","context","comment","other","Element","options","Record","tag","key","SelectComp","props","t","resolvedTag","content","forms","undefined","Object","fromEntries","entries","filter","includes","orderedKeys","keys","const","messages","components","normalized","map","translated","message","valueMap","length","_$createComponent","component","children","useI18n","DateTimeProps","value","Date","style","DateTime","props","d","_$memo","useI18n","NumberProps","value","style","NumberFormat","props","n","_$memo"],"sources":["../src/context.ts","../src/provider.tsx","../src/use-i18n.ts","../src/compile-time-t.ts","../src/rich-dom.tsx","../src/trans.tsx","../src/plural.tsx","../src/select.tsx","../src/components/DateTime.tsx","../src/components/NumberFormat.tsx"],"sourcesContent":["import { createSignal, createRoot, type Accessor } from 'solid-js'\nimport { formatDate, formatNumber, interpolate as coreInterpolate, buildICUMessage, resolveDescriptorId } from '@fluenti/core'\nimport type { FluentConfig, Locale, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions } from '@fluenti/core'\n\n/** Chunk loader for lazy locale loading */\nexport type ChunkLoader = (\n locale: string,\n) => Promise<Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> }>\n\ninterface SplitRuntimeModule {\n __switchLocale?: (locale: string) => Promise<void>\n __preloadLocale?: (locale: string) => Promise<void>\n}\n\nconst SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.solid')\n\nfunction getSplitRuntimeModule(): SplitRuntimeModule | null {\n const runtime = (globalThis as Record<PropertyKey, unknown>)[SPLIT_RUNTIME_KEY]\n return typeof runtime === 'object' && runtime !== null\n ? runtime as SplitRuntimeModule\n : null\n}\n\nfunction resolveChunkMessages(\n loaded: Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> },\n): Record<string, CompiledMessage> {\n return typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded as { default: Record<string, CompiledMessage> }).default\n : loaded\n}\n\n/** Extended config with lazy locale loading support */\nexport interface I18nConfig extends FluentConfig {\n /** Async chunk loader for lazy locale loading */\n chunkLoader?: ChunkLoader\n /** Enable lazy locale loading through chunkLoader */\n lazyLocaleLoading?: boolean\n /** Locale-specific fallback chains */\n fallbackChain?: Record<string, Locale[]>\n /** Named date format styles */\n dateFormats?: DateFormatOptions\n /** Named number format styles */\n numberFormats?: NumberFormatOptions\n}\n\n/** Reactive i18n context holding locale signal and translation utilities */\nexport interface I18nContext {\n /** Reactive accessor for the current locale */\n locale(): Locale\n /** Set the active locale (async when lazy locale loading is enabled) */\n setLocale(locale: Locale): Promise<void>\n /** Translate a message by id with optional interpolation values */\n t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n /** Tagged template form: t`Hello ${name}` */\n t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n /** Merge additional messages into a locale catalog at runtime */\n loadMessages(locale: Locale, messages: Messages): void\n /** Return all locale codes that have loaded messages */\n getLocales(): Locale[]\n /** Format a date value for the current locale */\n d(value: Date | number, style?: string): string\n /** Format a number value for the current locale */\n n(value: number, style?: string): string\n /** Format an ICU message string directly (no catalog lookup) */\n format(message: string, values?: Record<string, unknown>): string\n /** Whether a locale chunk is currently being loaded */\n isLoading: Accessor<boolean>\n /** Set of locales whose messages have been loaded */\n loadedLocales: Accessor<Set<string>>\n /** Preload a locale in the background without switching to it */\n preloadLocale(locale: string): void\n}\n\n/**\n * Create a reactive i18n context backed by Solid signals.\n *\n * The returned `t()` reads the internal `locale()` signal, so any\n * Solid computation that calls `t()` will re-run when the locale changes.\n */\nexport function createI18nContext(config: FluentConfig | I18nConfig): I18nContext {\n const [locale, setLocaleSignal] = createSignal<Locale>(config.locale)\n const [isLoading, setIsLoading] = createSignal(false)\n const loadedLocalesSet = new Set<string>([config.locale])\n const [loadedLocales, setLoadedLocales] = createSignal(new Set(loadedLocalesSet))\n const messages: Record<string, Messages> = { ...config.messages }\n const i18nConfig = config as I18nConfig\n const lazyLocaleLoading = i18nConfig.lazyLocaleLoading\n ?? (config as I18nConfig & { splitting?: boolean }).splitting\n ?? false\n\n function lookupCatalog(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const catalog = messages[loc]\n if (!catalog) {\n return undefined\n }\n\n const msg = catalog[id]\n if (msg === undefined) {\n return undefined\n }\n\n if (typeof msg === 'function') {\n return msg(values)\n }\n\n if (typeof msg === 'string' && values) {\n return coreInterpolate(msg, values, loc)\n }\n\n return String(msg)\n }\n\n function lookupWithFallbacks(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const localesToTry: Locale[] = [loc]\n const seen = new Set(localesToTry)\n\n if (config.fallbackLocale && !seen.has(config.fallbackLocale)) {\n localesToTry.push(config.fallbackLocale)\n seen.add(config.fallbackLocale)\n }\n\n const chainLocales = i18nConfig.fallbackChain?.[loc] ?? i18nConfig.fallbackChain?.['*']\n if (chainLocales) {\n for (const chainLocale of chainLocales) {\n if (!seen.has(chainLocale)) {\n localesToTry.push(chainLocale)\n seen.add(chainLocale)\n }\n }\n }\n\n for (const targetLocale of localesToTry) {\n const result = lookupCatalog(id, targetLocale, values)\n if (result !== undefined) {\n return result\n }\n }\n\n return undefined\n }\n\n function resolveMissing(\n id: string,\n loc: Locale,\n ): string | undefined {\n if (!config.missing) {\n return undefined\n }\n\n const result = config.missing(loc, id)\n if (result !== undefined) {\n return result\n }\n return undefined\n }\n\n function resolveMessage(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string {\n const catalogResult = lookupWithFallbacks(id, loc, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(id, loc)\n if (missingResult !== undefined) {\n return missingResult\n }\n\n if (id.includes('{')) {\n return coreInterpolate(id, values, loc)\n }\n\n return id\n }\n\n function t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n function t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n function t(idOrStrings: string | MessageDescriptor | TemplateStringsArray, ...rest: unknown[]): string {\n // Tagged template form: t`Hello ${name}`\n if (Array.isArray(idOrStrings) && 'raw' in idOrStrings) {\n const strings = idOrStrings as TemplateStringsArray\n const icu = buildICUMessage(strings, rest)\n const values = Object.fromEntries(rest.map((v, i) => [String(i), v]))\n return t(icu, values)\n }\n\n const id = idOrStrings as string | MessageDescriptor\n const values = rest[0] as Record<string, unknown> | undefined\n const currentLocale = locale() // reactive dependency\n if (typeof id === 'object' && id !== null) {\n const messageId = resolveDescriptorId(id)\n if (messageId) {\n const catalogResult = lookupWithFallbacks(messageId, currentLocale, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(messageId, currentLocale)\n if (missingResult !== undefined) {\n return missingResult\n }\n }\n\n if (id.message !== undefined) {\n return coreInterpolate(id.message, values, currentLocale)\n }\n\n return messageId ?? ''\n }\n\n return resolveMessage(id, currentLocale, values)\n }\n\n const loadMessages = (loc: Locale, msgs: Messages): void => {\n // Intentional mutation: messages record is locally scoped to this context closure\n messages[loc] = { ...messages[loc], ...msgs }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n }\n\n const setLocale = async (newLocale: Locale): Promise<void> => {\n if (!lazyLocaleLoading || !i18nConfig.chunkLoader) {\n setLocaleSignal(newLocale)\n return\n }\n\n const splitRuntime = getSplitRuntimeModule()\n\n if (loadedLocalesSet.has(newLocale)) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n return\n }\n\n setIsLoading(true)\n try {\n const loaded = resolveChunkMessages(await i18nConfig.chunkLoader(newLocale))\n // Intentional mutation: messages record is locally scoped to this context closure\n messages[newLocale] = { ...messages[newLocale], ...loaded }\n loadedLocalesSet.add(newLocale)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n } finally {\n setIsLoading(false)\n }\n }\n\n const preloadLocale = (loc: string): void => {\n if (!lazyLocaleLoading || loadedLocalesSet.has(loc) || !i18nConfig.chunkLoader) return\n const splitRuntime = getSplitRuntimeModule()\n i18nConfig.chunkLoader(loc).then(async (loaded) => {\n const resolved = resolveChunkMessages(loaded)\n // Intentional mutation: messages record is locally scoped to this context closure\n messages[loc] = { ...messages[loc], ...resolved }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n }).catch((e: unknown) => {\n console.warn('[fluenti] preload failed:', loc, e)\n })\n }\n\n const getLocales = (): Locale[] => Object.keys(messages)\n\n const d = (value: Date | number, style?: string): string =>\n formatDate(value, locale(), style, i18nConfig.dateFormats)\n\n const n = (value: number, style?: string): string =>\n formatNumber(value, locale(), style, i18nConfig.numberFormats)\n\n const format = (message: string, values?: Record<string, unknown>): string => {\n return coreInterpolate(message, values, locale())\n }\n\n return { locale, setLocale, t, loadMessages, getLocales, d, n, format, isLoading, loadedLocales, preloadLocale }\n}\n\n// ─── Module-level singleton ─────────────────────────────────────────────────\n\nlet globalCtx: I18nContext | undefined\n\n/**\n * Initialize the global i18n singleton.\n *\n * Call once at app startup (e.g. in your entry file) before any `useI18n()`.\n * Signals are created inside a `createRoot` so they outlive any component scope.\n *\n * Returns the context for convenience, but `useI18n()` will also find it.\n */\nexport function createI18n(config: FluentConfig | I18nConfig): I18nContext {\n const ctx = createRoot(() => createI18nContext(config))\n\n // Only set global singleton in browser (client-side).\n // In SSR, each request should use <I18nProvider> for per-request isolation.\n if (typeof window !== 'undefined') {\n globalCtx = ctx\n } else {\n console.warn(\n '[fluenti] createI18n() detected SSR environment. ' +\n 'Use <I18nProvider> for per-request isolation in SSR.',\n )\n }\n\n return ctx\n}\n\n/** @internal — used by useI18n and I18nProvider */\nexport function getGlobalI18nContext(): I18nContext | undefined {\n return globalCtx\n}\n\n/** @internal — used by I18nProvider to set context without createRoot wrapper */\nexport function setGlobalI18nContext(ctx: I18nContext): void {\n globalCtx = ctx\n}\n\n/** @internal — reset the global singleton (for testing only) */\nexport function resetGlobalI18nContext(): void {\n globalCtx = undefined\n}\n","import { createContext } from 'solid-js'\nimport type { ParentComponent } from 'solid-js'\nimport { createI18nContext } from './context'\nimport type { I18nConfig, I18nContext } from './context'\n\n/** Solid context object for i18n — used internally by useI18n() */\nexport const I18nCtx = createContext<I18nContext>()\n\n/**\n * Provide i18n context to the component tree.\n *\n */\nexport const I18nProvider: ParentComponent<I18nConfig> = (props) => {\n const ctx = createI18nContext(props)\n return <I18nCtx.Provider value={ctx}>{props.children}</I18nCtx.Provider>\n}\n","import { useContext } from 'solid-js'\nimport { I18nCtx } from './provider'\nimport { getGlobalI18nContext } from './context'\nimport type { I18nContext } from './context'\n\n/**\n * Access the i18n context.\n *\n * Resolution order:\n * 1. Nearest `<I18nProvider>` in the component tree\n * 2. Module-level singleton created by `createI18n()`\n *\n * Throws if neither is available.\n */\nexport function useI18n(): I18nContext {\n const ctx = useContext(I18nCtx)\n if (ctx) {\n return ctx\n }\n\n const global = getGlobalI18nContext()\n if (global) {\n return global\n }\n\n throw new Error(\n 'useI18n requires either createI18n() to be called at startup, ' +\n 'or the component to be inside an <I18nProvider>.',\n )\n}\n","import type { CompileTimeT } from '@fluenti/core'\n\nexport const t: CompileTimeT = ((..._args: unknown[]) => {\n throw new Error(\n \"[fluenti] `t` imported from '@fluenti/solid' is a compile-time API. \" +\n 'Use it only with the Fluenti build transform inside a component or custom hook. ' +\n 'For runtime lookups, use useI18n().t(...).',\n )\n}) as CompileTimeT\n","import type { JSX } from 'solid-js'\n\nfunction isNodeLike(value: unknown): value is Node {\n return typeof Node !== 'undefined' && value instanceof Node\n}\n\nfunction resolveValue(value: unknown): unknown {\n if (typeof value === 'function' && !(value as { length?: number }).length) {\n return (value as () => unknown)()\n }\n return value\n}\n\nexport function offsetIndices(message: string, offset: number): string {\n if (offset === 0) return message\n return message\n .replace(/<(\\d+)(\\/?>)/g, (_match, index: string, suffix: string) => `<${Number(index) + offset}${suffix}`)\n .replace(/<\\/(\\d+)>/g, (_match, index: string) => `</${Number(index) + offset}>`)\n}\n\nexport function extractMessage(value: unknown): {\n message: string\n components: Node[]\n} {\n const components: Node[] = []\n let message = ''\n\n function visit(node: unknown): void {\n const resolved = resolveValue(node)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const child of resolved) visit(child)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n message += String(resolved)\n return\n }\n if (!isNodeLike(resolved)) return\n if (resolved.nodeType === Node.TEXT_NODE) {\n message += resolved.textContent ?? ''\n return\n }\n if (resolved.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n visit(Array.from(resolved.childNodes))\n return\n }\n\n const idx = components.length\n const inner = extractMessage(Array.from(resolved.childNodes))\n components.push((resolved as Element).cloneNode(false))\n components.push(...inner.components)\n message += `<${idx}>${offsetIndices(inner.message, idx + 1)}</${idx}>`\n }\n\n visit(value)\n return { message, components }\n}\n\nfunction appendChild(parent: Node, child: unknown): void {\n const resolved = resolveValue(child)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const entry of resolved) appendChild(parent, entry)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n parent.appendChild(document.createTextNode(String(resolved)))\n return\n }\n if (isNodeLike(resolved)) {\n parent.appendChild(resolved)\n }\n}\n\nexport function reconstruct(\n translated: string,\n components: Node[],\n): JSX.Element {\n const tagRe = /<(\\d+)>([\\s\\S]*?)<\\/\\1>/g\n const result: unknown[] = []\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n tagRe.lastIndex = 0\n match = tagRe.exec(translated)\n while (match !== null) {\n if (match.index > lastIndex) {\n result.push(translated.slice(lastIndex, match.index))\n }\n\n const idx = Number(match[1])\n const template = components[idx]\n const inner = reconstruct(match[2]!, components)\n if (template) {\n const clone = template.cloneNode(false)\n appendChild(clone, inner)\n result.push(clone)\n } else {\n result.push(match[2]!)\n }\n\n lastIndex = tagRe.lastIndex\n match = tagRe.exec(translated)\n }\n\n if (lastIndex < translated.length) {\n result.push(translated.slice(lastIndex))\n }\n\n return (result.length <= 1 ? result[0] ?? '' : result) as JSX.Element\n}\n\nexport function serializeRichForms<T extends string>(\n keys: readonly T[],\n forms: Partial<Record<T, unknown>> & Record<string, unknown>,\n): {\n messages: Record<string, string>\n components: Node[]\n} {\n const messages: Record<string, string> = {}\n const components: Node[] = []\n\n for (const key of keys) {\n const value = forms[key]\n if (value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n for (const [key, value] of Object.entries(forms)) {\n if (keys.includes(key as T) || value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n return { messages, components }\n}\n\nexport function buildICUSelectMessage(forms: Record<string, string>): string {\n return `{value, select, ${Object.entries(forms).map(([key, text]) => `${key} {${text}}`).join(' ')}}`\n}\n\nexport function normalizeSelectForms(forms: Record<string, string>): {\n forms: Record<string, string>\n valueMap: Record<string, string>\n} {\n const normalized: Record<string, string> = {}\n const valueMap: Record<string, string> = {}\n let index = 0\n\n for (const [key, text] of Object.entries(forms)) {\n if (key === 'other') {\n normalized['other'] = text\n continue\n }\n\n const safeKey = /^[A-Za-z0-9_]+$/.test(key) ? key : `case_${index++}`\n normalized[safeKey] = text\n valueMap[key] = safeKey\n }\n\n if (normalized['other'] === undefined) {\n normalized['other'] = ''\n }\n\n return { forms: normalized, valueMap }\n}\n","import { Dynamic } from 'solid-js/web'\nimport { children as resolveChildren, createMemo } from 'solid-js'\nimport type { Component, JSX } from 'solid-js'\nimport { useI18n } from './use-i18n'\nimport { extractMessage as extractDomMessage, reconstruct as reconstructDomMessage } from './rich-dom'\n\n/** A Solid component that accepts children */\nexport type RichComponent = Component<{ children?: JSX.Element }>\n\n/** Props for the `<Trans>` component */\nexport interface TransProps {\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Wrapper element tag name (default: `'span'`) — used in children-only mode */\n tag?: string\n /** Children — the content to translate (legacy API) */\n children?: JSX.Element\n /** Translated message string with XML-like tags (e.g. `<bold>text</bold>`) */\n message?: string\n /** Map of tag names to Solid components */\n components?: Record<string, RichComponent>\n /** @internal Pre-computed message from build plugin */\n __message?: string\n /** @internal Pre-computed component map from build plugin */\n __components?: Record<string, RichComponent>\n}\n\n/**\n * A token from parsing the message string.\n * Either a plain text segment or a tag with inner content.\n */\ninterface TextToken {\n readonly type: 'text'\n readonly value: string\n}\n\ninterface TagToken {\n readonly type: 'tag'\n readonly name: string\n readonly children: readonly Token[]\n}\n\ntype Token = TextToken | TagToken\n\n/**\n * Parse a message string containing XML-like tags into a token tree.\n *\n * Supports:\n * - Named tags: `<bold>content</bold>`\n * - Self-closing tags: `<br/>`\n * - Nested tags: `<bold>hello <italic>world</italic></bold>`\n */\nfunction parseTokens(input: string): readonly Token[] {\n const tokens: Token[] = []\n let pos = 0\n\n while (pos < input.length) {\n const openIdx = input.indexOf('<', pos)\n\n if (openIdx === -1) {\n // No more tags — rest is plain text\n tokens.push({ type: 'text', value: input.slice(pos) })\n break\n }\n\n // Push any text before this tag\n if (openIdx > pos) {\n tokens.push({ type: 'text', value: input.slice(pos, openIdx) })\n }\n\n // Check for self-closing tag: <tagName/>\n const selfCloseMatch = input.slice(openIdx).match(/^<(\\w+)\\s*\\/>/)\n if (selfCloseMatch) {\n tokens.push({ type: 'tag', name: selfCloseMatch[1]!, children: [] })\n pos = openIdx + selfCloseMatch[0].length\n continue\n }\n\n // Check for opening tag: <tagName>\n const openMatch = input.slice(openIdx).match(/^<(\\w+)>/)\n if (!openMatch) {\n // Not a valid tag — treat '<' as text\n tokens.push({ type: 'text', value: '<' })\n pos = openIdx + 1\n continue\n }\n\n const tagName = openMatch[1]!\n const contentStart = openIdx + openMatch[0].length\n\n // Find the matching closing tag, respecting nesting\n const innerEnd = findClosingTag(input, tagName, contentStart)\n if (innerEnd === -1) {\n // No closing tag found — treat as plain text\n tokens.push({ type: 'text', value: input.slice(openIdx, contentStart) })\n pos = contentStart\n continue\n }\n\n const innerContent = input.slice(contentStart, innerEnd)\n const closingTag = `</${tagName}>`\n tokens.push({\n type: 'tag',\n name: tagName,\n children: parseTokens(innerContent),\n })\n pos = innerEnd + closingTag.length\n }\n\n return tokens\n}\n\n/**\n * Find the position of the matching closing tag, accounting for nesting\n * of the same tag name.\n *\n * Returns the index of the start of the closing tag, or -1 if not found.\n */\nfunction findClosingTag(input: string, tagName: string, startPos: number): number {\n const openTag = `<${tagName}>`\n const closeTag = `</${tagName}>`\n let depth = 1\n let pos = startPos\n\n while (pos < input.length && depth > 0) {\n const nextOpen = input.indexOf(openTag, pos)\n const nextClose = input.indexOf(closeTag, pos)\n\n if (nextClose === -1) return -1\n\n if (nextOpen !== -1 && nextOpen < nextClose) {\n depth++\n pos = nextOpen + openTag.length\n } else {\n depth--\n if (depth === 0) return nextClose\n pos = nextClose + closeTag.length\n }\n }\n\n return -1\n}\n\n/**\n * Render a token tree into Solid JSX elements using the components map.\n */\nfunction renderTokens(\n tokens: readonly Token[],\n components: Record<string, RichComponent>,\n): JSX.Element {\n const elements = tokens.map((token): JSX.Element => {\n if (token.type === 'text') {\n return token.value as unknown as JSX.Element\n }\n\n const Comp = components[token.name]\n if (!Comp) {\n // Unknown component — render inner content as plain text\n return renderTokens(token.children, components)\n }\n\n const innerContent = token.children.length > 0\n ? renderTokens(token.children, components)\n : undefined\n\n return (<Dynamic component={Comp}>{innerContent}</Dynamic>) as JSX.Element\n })\n\n if (elements.length === 1) return elements[0]!\n return (<>{elements}</>) as JSX.Element\n}\n\n/**\n * Render translated content with inline components.\n *\n * Supports two APIs:\n *\n * 1. **message + components** (recommended for rich text):\n * ```tsx\n * <Trans\n * message={t`Welcome to <bold>Fluenti</bold>!`}\n * components={{ bold: (props) => <strong>{props.children}</strong> }}\n * />\n * ```\n *\n * 2. **children** (legacy / simple passthrough):\n * ```tsx\n * <Trans>Click <a href=\"/next\">here</a> to continue</Trans>\n * ```\n */\nexport const Trans: Component<TransProps> = (props) => {\n const { t } = useI18n()\n const resolvedChildren = resolveChildren(() => props.children)\n // message + components API (including build-time __message/__components)\n // Note: the vite-plugin tagged-template transform wraps Solid expressions in\n // createMemo(), so props.message may be a memo accessor (function) instead of\n // a string. We unwrap it here to handle both cases.\n const message = createMemo(() => {\n const raw = props.__message ?? props.message\n return typeof raw === 'function' ? (raw as () => string)() : raw\n })\n const components = createMemo(() => props.__components ?? props.components)\n\n return (() => {\n const msg = message()\n const comps = components()\n\n if (msg !== undefined && comps) {\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: msg,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const tokens = parseTokens(translated)\n return renderTokens(tokens, comps)\n }\n\n // Fallback: children-only API with runtime extraction/reconstruction\n const children = resolvedChildren.toArray()\n if (children.length === 0) return null\n const extracted = extractDomMessage(children)\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: extracted.message,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const result = extracted.components.length > 0\n ? reconstructDomMessage(translated, extracted.components)\n : translated\n\n if (Array.isArray(result) && result.length > 1) {\n return (<Dynamic component={props.tag ?? 'span'}>{result}</Dynamic>) as JSX.Element\n }\n\n return result as JSX.Element\n }) as unknown as JSX.Element\n}\n","import { Dynamic } from 'solid-js/web'\nimport type { Component, JSX } from 'solid-js'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { reconstruct, serializeRichForms } from './rich-dom'\n\n/** Plural category names in a stable order for ICU message building. */\nconst PLURAL_CATEGORIES = ['zero', 'one', 'two', 'few', 'many', 'other'] as const\n\ntype PluralCategory = (typeof PLURAL_CATEGORIES)[number]\n\n/**\n * Build an ICU plural message string from individual category props.\n *\n * Given `{ zero: \"No items\", one: \"# item\", other: \"# items\" }`,\n * produces `\"{count, plural, =0 {No items} one {# item} other {# items}}\"`.\n *\n * @internal\n */\nfunction buildICUPluralMessage(\n forms: Partial<Record<PluralCategory, string>> & { other: string },\n offset?: number,\n): string {\n const parts: string[] = []\n for (const cat of PLURAL_CATEGORIES) {\n const text = forms[cat]\n if (text !== undefined) {\n // Map the `zero` prop to ICU `=0` exact match. In ICU MessageFormat,\n // `zero` is a CLDR plural category that only activates in languages\n // with a grammatical zero form (e.g. Arabic). The `=0` exact match\n // works universally for the common \"show this when count is 0\" intent.\n const key = cat === 'zero' ? '=0' : cat\n parts.push(`${key} {${text}}`)\n }\n }\n const offsetPrefix = offset ? `offset:${offset} ` : ''\n return `{count, plural, ${offsetPrefix}${parts.join(' ')}}`\n}\n\n/** Props for the `<Plural>` component */\nexport interface PluralProps {\n /** The numeric value to pluralise */\n value: number\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Offset from value before selecting form */\n offset?: number\n /** Message for the \"zero\" plural category */\n zero?: string | JSX.Element\n /** Message for the \"one\" plural category */\n one?: string | JSX.Element\n /** Message for the \"two\" plural category */\n two?: string | JSX.Element\n /** Message for the \"few\" plural category */\n few?: string | JSX.Element\n /** Message for the \"many\" plural category */\n many?: string | JSX.Element\n /** Fallback message when no category-specific prop matches */\n other: string | JSX.Element\n /** Wrapper element tag name (default: `'span'`) */\n tag?: string\n}\n\n/**\n * `<Plural>` component — shorthand for ICU plural patterns.\n *\n * Plural form props (`zero`, `one`, `two`, `few`, `many`, `other`) are treated\n * as source-language messages. The component builds an ICU plural message,\n * looks it up via `t()` in the catalog, and interpolates the translated result.\n *\n * When no catalog translation exists, the component falls back to interpolating\n * the source-language ICU message directly via core's `interpolate`.\n *\n * Rich text is supported via JSX element props:\n * ```tsx\n * <Plural\n * value={count()}\n * zero={<>No <strong>items</strong> left</>}\n * one={<><em>1</em> item remaining</>}\n * other={<><strong>{count()}</strong> items remaining</>}\n * />\n * ```\n *\n * String props still work (backward compatible):\n * ```tsx\n * <Plural value={count()} zero=\"No items\" one=\"# item\" other=\"# items\" />\n * ```\n */\nexport const Plural: Component<PluralProps> = (props) => {\n const { t } = useI18n()\n\n /** Resolve a category prop value — handles string, accessor function, and JSX */\n function resolveProp(val: string | JSX.Element | undefined): string | JSX.Element | undefined {\n if (typeof val === 'function') return (val as () => string | JSX.Element)()\n return val\n }\n\n return (() => {\n // Resolve all category values (handles Solid accessors from createMemo)\n const resolvedValues: Partial<Record<PluralCategory, string | JSX.Element>> = {}\n for (const cat of PLURAL_CATEGORIES) {\n const resolved = resolveProp(props[cat])\n if (resolved !== undefined) {\n resolvedValues[cat] = resolved\n }\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, resolvedValues)\n const icuMessage = buildICUPluralMessage(\n {\n ...(messages['zero'] !== undefined && { zero: messages['zero'] }),\n ...(messages['one'] !== undefined && { one: messages['one'] }),\n ...(messages['two'] !== undefined && { two: messages['two'] }),\n ...(messages['few'] !== undefined && { few: messages['few'] }),\n ...(messages['many'] !== undefined && { many: messages['many'] }),\n other: messages['other'] ?? '',\n },\n props.offset,\n )\n\n const translated = t(\n {\n id: props.id ?? (props.context === undefined ? icuMessage : hashMessage(icuMessage, props.context)),\n message: icuMessage,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { count: props.value },\n )\n\n return (<Dynamic component={props.tag ?? 'span'}>{components.length > 0 ? reconstruct(translated, components) : translated}</Dynamic>) as JSX.Element\n }) as unknown as JSX.Element\n}\n","import type { Component, JSX } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { buildICUSelectMessage, normalizeSelectForms, reconstruct, serializeRichForms } from './rich-dom'\n\n/** Props for the `<Select>` component */\nexport interface SelectProps {\n /** The value to match against prop keys */\n value: string\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Fallback message when no key matches */\n other: string | JSX.Element\n /**\n * Named options map. Keys are match values, values are display strings or JSX.\n * Takes precedence over dynamic attrs when both are provided.\n *\n * @example `{ male: 'He', female: 'She' }`\n */\n options?: Record<string, string | JSX.Element>\n /** Wrapper element tag name (default: `span`) */\n tag?: string\n /** Additional key/message pairs for matching (attrs fallback) */\n [key: string]: unknown\n}\n\n/**\n * Render a message selected by matching `value` against prop keys.\n *\n * Options can be provided via the type-safe `options` prop (recommended)\n * or as direct attrs (convenience). When both are present, `options` takes\n * precedence.\n *\n * Rich text is supported via JSX element values in the `options` prop or\n * as direct JSX element props:\n * ```tsx\n * <Select\n * value={gender()}\n * options={{\n * male: <><strong>He</strong> liked this</>,\n * female: <><strong>She</strong> liked this</>,\n * }}\n * other={<><em>They</em> liked this</>}\n * />\n * ```\n *\n * Falls back to the `other` prop when no key matches.\n */\nexport const SelectComp: Component<SelectProps> = (props) => {\n const { t } = useI18n()\n\n const resolvedTag = () => props.tag ?? 'span'\n\n const content = () => {\n const forms: Record<string, unknown> = props.options !== undefined\n ? { ...props.options, other: props.other }\n : {\n ...Object.fromEntries(\n Object.entries(props).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other', 'tag'].includes(key)),\n ),\n other: props.other,\n }\n\n const orderedKeys = [...Object.keys(forms).filter(key => key !== 'other'), 'other'] as const\n const { messages, components } = serializeRichForms(orderedKeys, forms)\n const normalized = normalizeSelectForms(\n Object.fromEntries([...orderedKeys].map((key) => [key, messages[key] ?? ''])),\n )\n const translated = t(\n {\n id: props.id ?? (props.context === undefined\n ? buildICUSelectMessage(normalized.forms)\n : hashMessage(buildICUSelectMessage(normalized.forms), props.context)),\n message: buildICUSelectMessage(normalized.forms),\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { value: normalized.valueMap[props.value] ?? 'other' },\n )\n\n return components.length > 0 ? reconstruct(translated, components) : translated\n }\n\n return (<Dynamic component={resolvedTag()}>{content()}</Dynamic>) as JSX.Element\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface DateTimeProps {\n /** Date value to format */\n value: Date | number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<DateTime>` — date formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport function DateTime(props: DateTimeProps) {\n const { d } = useI18n()\n return <>{d(props.value, props.style)}</>\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<NumberFormat>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <NumberFormat value={1234.56} style=\"currency\" />\n * ```\n */\nexport function NumberFormat(props: NumberProps) {\n const { n } = useI18n()\n return <>{n(props.value, props.style)}</>\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -99,14 +99,16 @@ function y(e) {
|
|
|
99
99
|
...m[e],
|
|
100
100
|
...r
|
|
101
101
|
}, l.add(e), p(new Set(l)), t?.__preloadLocale && await t.__preloadLocale(e);
|
|
102
|
-
}).catch(() => {
|
|
102
|
+
}).catch((t) => {
|
|
103
|
+
console.warn("[fluenti] preload failed:", e, t);
|
|
104
|
+
});
|
|
103
105
|
}
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
var b;
|
|
107
109
|
function x(e) {
|
|
108
110
|
let t = r(() => y(e));
|
|
109
|
-
return typeof window < "u"
|
|
111
|
+
return typeof window < "u" ? b = t : console.warn("[fluenti] createI18n() detected SSR environment. Use <I18nProvider> for per-request isolation in SSR."), t;
|
|
110
112
|
}
|
|
111
113
|
function S() {
|
|
112
114
|
return b;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["createContext","ParentComponent","createI18nContext","I18nConfig","I18nContext","I18nCtx","I18nProvider","props","ctx","_$createComponent","Provider","value","children","JSX","isNodeLike","value","Node","resolveValue","length","offsetIndices","message","offset","replace","_match","index","suffix","Number","extractMessage","components","visit","node","resolved","undefined","Array","isArray","child","String","nodeType","TEXT_NODE","textContent","DOCUMENT_FRAGMENT_NODE","from","childNodes","idx","inner","push","Element","cloneNode","appendChild","parent","entry","document","createTextNode","reconstruct","translated","tagRe","result","lastIndex","match","RegExpExecArray","exec","slice","template","clone","serializeRichForms","keys","T","forms","Partial","Record","messages","key","extracted","Object","entries","includes","buildICUSelectMessage","map","text","join","normalizeSelectForms","valueMap","normalized","safeKey","test","Dynamic","children","resolveChildren","createMemo","Component","JSX","useI18n","extractMessage","extractDomMessage","reconstruct","reconstructDomMessage","RichComponent","Element","TransProps","id","context","comment","tag","message","components","Record","__message","__components","TextToken","type","value","TagToken","name","Token","parseTokens","input","tokens","pos","length","openIdx","indexOf","push","slice","selfCloseMatch","match","openMatch","tagName","contentStart","innerEnd","findClosingTag","innerContent","closingTag","startPos","openTag","closeTag","depth","nextOpen","nextClose","renderTokens","elements","map","token","Comp","undefined","_$createComponent","component","Trans","props","t","resolvedChildren","raw","msg","comps","translated","toArray","extracted","result","Array","isArray","Dynamic","Component","JSX","hashMessage","useI18n","reconstruct","serializeRichForms","PLURAL_CATEGORIES","const","PluralCategory","buildICUPluralMessage","forms","Partial","Record","other","offset","parts","cat","text","undefined","key","push","offsetPrefix","join","PluralProps","value","id","context","comment","zero","Element","one","two","few","many","tag","Plural","props","t","resolveProp","val","resolvedValues","resolved","messages","components","icuMessage","translated","message","count","_$createComponent","component","children","_$memo","length","Component","JSX","Dynamic","hashMessage","useI18n","buildICUSelectMessage","normalizeSelectForms","reconstruct","serializeRichForms","SelectProps","value","id","context","comment","other","Element","options","Record","tag","key","SelectComp","props","t","resolvedTag","content","forms","undefined","Object","fromEntries","entries","filter","includes","orderedKeys","keys","const","messages","components","normalized","map","translated","message","valueMap","length","_$createComponent","component","children","useI18n","DateTimeProps","value","Date","style","DateTime","props","d","_$memo","useI18n","NumberProps","value","style","NumberFormat","props","n","_$memo"],"sources":["../src/context.ts","../src/provider.tsx","../src/use-i18n.ts","../src/compile-time-t.ts","../src/rich-dom.tsx","../src/trans.tsx","../src/plural.tsx","../src/select.tsx","../src/components/DateTime.tsx","../src/components/NumberFormat.tsx"],"sourcesContent":["import { createSignal, createRoot, type Accessor } from 'solid-js'\nimport { formatDate, formatNumber, interpolate as coreInterpolate, buildICUMessage, resolveDescriptorId } from '@fluenti/core'\nimport type { FluentConfig, Locale, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions } from '@fluenti/core'\n\n/** Chunk loader for lazy locale loading */\nexport type ChunkLoader = (\n locale: string,\n) => Promise<Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> }>\n\ninterface SplitRuntimeModule {\n __switchLocale?: (locale: string) => Promise<void>\n __preloadLocale?: (locale: string) => Promise<void>\n}\n\nconst SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.solid')\n\nfunction getSplitRuntimeModule(): SplitRuntimeModule | null {\n const runtime = (globalThis as Record<PropertyKey, unknown>)[SPLIT_RUNTIME_KEY]\n return typeof runtime === 'object' && runtime !== null\n ? runtime as SplitRuntimeModule\n : null\n}\n\nfunction resolveChunkMessages(\n loaded: Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> },\n): Record<string, CompiledMessage> {\n return typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded as { default: Record<string, CompiledMessage> }).default\n : loaded\n}\n\n/** Extended config with lazy locale loading support */\nexport interface I18nConfig extends FluentConfig {\n /** Async chunk loader for lazy locale loading */\n chunkLoader?: ChunkLoader\n /** Enable lazy locale loading through chunkLoader */\n lazyLocaleLoading?: boolean\n /** Locale-specific fallback chains */\n fallbackChain?: Record<string, Locale[]>\n /** Named date format styles */\n dateFormats?: DateFormatOptions\n /** Named number format styles */\n numberFormats?: NumberFormatOptions\n}\n\n/** Reactive i18n context holding locale signal and translation utilities */\nexport interface I18nContext {\n /** Reactive accessor for the current locale */\n locale(): Locale\n /** Set the active locale (async when lazy locale loading is enabled) */\n setLocale(locale: Locale): Promise<void>\n /** Translate a message by id with optional interpolation values */\n t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n /** Tagged template form: t`Hello ${name}` */\n t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n /** Merge additional messages into a locale catalog at runtime */\n loadMessages(locale: Locale, messages: Messages): void\n /** Return all locale codes that have loaded messages */\n getLocales(): Locale[]\n /** Format a date value for the current locale */\n d(value: Date | number, style?: string): string\n /** Format a number value for the current locale */\n n(value: number, style?: string): string\n /** Format an ICU message string directly (no catalog lookup) */\n format(message: string, values?: Record<string, unknown>): string\n /** Whether a locale chunk is currently being loaded */\n isLoading: Accessor<boolean>\n /** Set of locales whose messages have been loaded */\n loadedLocales: Accessor<Set<string>>\n /** Preload a locale in the background without switching to it */\n preloadLocale(locale: string): void\n}\n\n/**\n * Create a reactive i18n context backed by Solid signals.\n *\n * The returned `t()` reads the internal `locale()` signal, so any\n * Solid computation that calls `t()` will re-run when the locale changes.\n */\nexport function createI18nContext(config: FluentConfig | I18nConfig): I18nContext {\n const [locale, setLocaleSignal] = createSignal<Locale>(config.locale)\n const [isLoading, setIsLoading] = createSignal(false)\n const loadedLocalesSet = new Set<string>([config.locale])\n const [loadedLocales, setLoadedLocales] = createSignal(new Set(loadedLocalesSet))\n const messages: Record<string, Messages> = { ...config.messages }\n const i18nConfig = config as I18nConfig\n const lazyLocaleLoading = i18nConfig.lazyLocaleLoading\n ?? (config as I18nConfig & { splitting?: boolean }).splitting\n ?? false\n\n function lookupCatalog(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const catalog = messages[loc]\n if (!catalog) {\n return undefined\n }\n\n const msg = catalog[id]\n if (msg === undefined) {\n return undefined\n }\n\n if (typeof msg === 'function') {\n return msg(values)\n }\n\n if (typeof msg === 'string' && values) {\n return coreInterpolate(msg, values, loc)\n }\n\n return String(msg)\n }\n\n function lookupWithFallbacks(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const localesToTry: Locale[] = [loc]\n const seen = new Set(localesToTry)\n\n if (config.fallbackLocale && !seen.has(config.fallbackLocale)) {\n localesToTry.push(config.fallbackLocale)\n seen.add(config.fallbackLocale)\n }\n\n const chainLocales = i18nConfig.fallbackChain?.[loc] ?? i18nConfig.fallbackChain?.['*']\n if (chainLocales) {\n for (const chainLocale of chainLocales) {\n if (!seen.has(chainLocale)) {\n localesToTry.push(chainLocale)\n seen.add(chainLocale)\n }\n }\n }\n\n for (const targetLocale of localesToTry) {\n const result = lookupCatalog(id, targetLocale, values)\n if (result !== undefined) {\n return result\n }\n }\n\n return undefined\n }\n\n function resolveMissing(\n id: string,\n loc: Locale,\n ): string | undefined {\n if (!config.missing) {\n return undefined\n }\n\n const result = config.missing(loc, id)\n if (result !== undefined) {\n return result\n }\n return undefined\n }\n\n function resolveMessage(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string {\n const catalogResult = lookupWithFallbacks(id, loc, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(id, loc)\n if (missingResult !== undefined) {\n return missingResult\n }\n\n if (id.includes('{')) {\n return coreInterpolate(id, values, loc)\n }\n\n return id\n }\n\n function t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n function t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n function t(idOrStrings: string | MessageDescriptor | TemplateStringsArray, ...rest: unknown[]): string {\n // Tagged template form: t`Hello ${name}`\n if (Array.isArray(idOrStrings) && 'raw' in idOrStrings) {\n const strings = idOrStrings as TemplateStringsArray\n const icu = buildICUMessage(strings, rest)\n const values = Object.fromEntries(rest.map((v, i) => [String(i), v]))\n return t(icu, values)\n }\n\n const id = idOrStrings as string | MessageDescriptor\n const values = rest[0] as Record<string, unknown> | undefined\n const currentLocale = locale() // reactive dependency\n if (typeof id === 'object' && id !== null) {\n const messageId = resolveDescriptorId(id)\n if (messageId) {\n const catalogResult = lookupWithFallbacks(messageId, currentLocale, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(messageId, currentLocale)\n if (missingResult !== undefined) {\n return missingResult\n }\n }\n\n if (id.message !== undefined) {\n return coreInterpolate(id.message, values, currentLocale)\n }\n\n return messageId ?? ''\n }\n\n return resolveMessage(id, currentLocale, values)\n }\n\n const loadMessages = (loc: Locale, msgs: Messages): void => {\n messages[loc] = { ...messages[loc], ...msgs }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n }\n\n const setLocale = async (newLocale: Locale): Promise<void> => {\n if (!lazyLocaleLoading || !i18nConfig.chunkLoader) {\n setLocaleSignal(newLocale)\n return\n }\n\n const splitRuntime = getSplitRuntimeModule()\n\n if (loadedLocalesSet.has(newLocale)) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n return\n }\n\n setIsLoading(true)\n try {\n const loaded = resolveChunkMessages(await i18nConfig.chunkLoader(newLocale))\n messages[newLocale] = { ...messages[newLocale], ...loaded }\n loadedLocalesSet.add(newLocale)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n } finally {\n setIsLoading(false)\n }\n }\n\n const preloadLocale = (loc: string): void => {\n if (!lazyLocaleLoading || loadedLocalesSet.has(loc) || !i18nConfig.chunkLoader) return\n const splitRuntime = getSplitRuntimeModule()\n i18nConfig.chunkLoader(loc).then(async (loaded) => {\n const resolved = resolveChunkMessages(loaded)\n messages[loc] = { ...messages[loc], ...resolved }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n }).catch(() => {\n // Silent failure for preload\n })\n }\n\n const getLocales = (): Locale[] => Object.keys(messages)\n\n const d = (value: Date | number, style?: string): string =>\n formatDate(value, locale(), style, i18nConfig.dateFormats)\n\n const n = (value: number, style?: string): string =>\n formatNumber(value, locale(), style, i18nConfig.numberFormats)\n\n const format = (message: string, values?: Record<string, unknown>): string => {\n return coreInterpolate(message, values, locale())\n }\n\n return { locale, setLocale, t, loadMessages, getLocales, d, n, format, isLoading, loadedLocales, preloadLocale }\n}\n\n// ─── Module-level singleton ─────────────────────────────────────────────────\n\nlet globalCtx: I18nContext | undefined\n\n/**\n * Initialize the global i18n singleton.\n *\n * Call once at app startup (e.g. in your entry file) before any `useI18n()`.\n * Signals are created inside a `createRoot` so they outlive any component scope.\n *\n * Returns the context for convenience, but `useI18n()` will also find it.\n */\nexport function createI18n(config: FluentConfig | I18nConfig): I18nContext {\n const ctx = createRoot(() => createI18nContext(config))\n\n // Only set global singleton in browser (client-side).\n // In SSR, each request should use <I18nProvider> for per-request isolation.\n if (typeof window !== 'undefined') {\n globalCtx = ctx\n } else {\n console.warn(\n '[fluenti] createI18n() detected SSR environment. ' +\n 'Use <I18nProvider> for per-request isolation in SSR.',\n )\n // Still set globalCtx as fallback, but document the risk\n globalCtx = ctx\n }\n\n return ctx\n}\n\n/** @internal — used by useI18n and I18nProvider */\nexport function getGlobalI18nContext(): I18nContext | undefined {\n return globalCtx\n}\n\n/** @internal — used by I18nProvider to set context without createRoot wrapper */\nexport function setGlobalI18nContext(ctx: I18nContext): void {\n globalCtx = ctx\n}\n\n/** @internal — reset the global singleton (for testing only) */\nexport function resetGlobalI18nContext(): void {\n globalCtx = undefined\n}\n","import { createContext } from 'solid-js'\nimport type { ParentComponent } from 'solid-js'\nimport { createI18nContext } from './context'\nimport type { I18nConfig, I18nContext } from './context'\n\n/** Solid context object for i18n — used internally by useI18n() */\nexport const I18nCtx = createContext<I18nContext>()\n\n/**\n * Provide i18n context to the component tree.\n *\n */\nexport const I18nProvider: ParentComponent<I18nConfig> = (props) => {\n const ctx = createI18nContext(props)\n return <I18nCtx.Provider value={ctx}>{props.children}</I18nCtx.Provider>\n}\n","import { useContext } from 'solid-js'\nimport { I18nCtx } from './provider'\nimport { getGlobalI18nContext } from './context'\nimport type { I18nContext } from './context'\n\n/**\n * Access the i18n context.\n *\n * Resolution order:\n * 1. Nearest `<I18nProvider>` in the component tree\n * 2. Module-level singleton created by `createI18n()`\n *\n * Throws if neither is available.\n */\nexport function useI18n(): I18nContext {\n const ctx = useContext(I18nCtx)\n if (ctx) {\n return ctx\n }\n\n const global = getGlobalI18nContext()\n if (global) {\n return global\n }\n\n throw new Error(\n 'useI18n requires either createI18n() to be called at startup, ' +\n 'or the component to be inside an <I18nProvider>.',\n )\n}\n","import type { CompileTimeT } from '@fluenti/core'\n\nexport const t: CompileTimeT = ((..._args: unknown[]) => {\n throw new Error(\n \"[fluenti] `t` imported from '@fluenti/solid' is a compile-time API. \" +\n 'Use it only with the Fluenti build transform inside a component or custom hook. ' +\n 'For runtime lookups, use useI18n().t(...).',\n )\n}) as CompileTimeT\n","import type { JSX } from 'solid-js'\n\nfunction isNodeLike(value: unknown): value is Node {\n return typeof Node !== 'undefined' && value instanceof Node\n}\n\nfunction resolveValue(value: unknown): unknown {\n if (typeof value === 'function' && !(value as { length?: number }).length) {\n return (value as () => unknown)()\n }\n return value\n}\n\nexport function offsetIndices(message: string, offset: number): string {\n if (offset === 0) return message\n return message\n .replace(/<(\\d+)(\\/?>)/g, (_match, index: string, suffix: string) => `<${Number(index) + offset}${suffix}`)\n .replace(/<\\/(\\d+)>/g, (_match, index: string) => `</${Number(index) + offset}>`)\n}\n\nexport function extractMessage(value: unknown): {\n message: string\n components: Node[]\n} {\n const components: Node[] = []\n let message = ''\n\n function visit(node: unknown): void {\n const resolved = resolveValue(node)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const child of resolved) visit(child)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n message += String(resolved)\n return\n }\n if (!isNodeLike(resolved)) return\n if (resolved.nodeType === Node.TEXT_NODE) {\n message += resolved.textContent ?? ''\n return\n }\n if (resolved.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n visit(Array.from(resolved.childNodes))\n return\n }\n\n const idx = components.length\n const inner = extractMessage(Array.from(resolved.childNodes))\n components.push((resolved as Element).cloneNode(false))\n components.push(...inner.components)\n message += `<${idx}>${offsetIndices(inner.message, idx + 1)}</${idx}>`\n }\n\n visit(value)\n return { message, components }\n}\n\nfunction appendChild(parent: Node, child: unknown): void {\n const resolved = resolveValue(child)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const entry of resolved) appendChild(parent, entry)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n parent.appendChild(document.createTextNode(String(resolved)))\n return\n }\n if (isNodeLike(resolved)) {\n parent.appendChild(resolved)\n }\n}\n\nexport function reconstruct(\n translated: string,\n components: Node[],\n): JSX.Element {\n const tagRe = /<(\\d+)>([\\s\\S]*?)<\\/\\1>/g\n const result: unknown[] = []\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n tagRe.lastIndex = 0\n match = tagRe.exec(translated)\n while (match !== null) {\n if (match.index > lastIndex) {\n result.push(translated.slice(lastIndex, match.index))\n }\n\n const idx = Number(match[1])\n const template = components[idx]\n const inner = reconstruct(match[2]!, components)\n if (template) {\n const clone = template.cloneNode(false)\n appendChild(clone, inner)\n result.push(clone)\n } else {\n result.push(match[2]!)\n }\n\n lastIndex = tagRe.lastIndex\n match = tagRe.exec(translated)\n }\n\n if (lastIndex < translated.length) {\n result.push(translated.slice(lastIndex))\n }\n\n return (result.length <= 1 ? result[0] ?? '' : result) as JSX.Element\n}\n\nexport function serializeRichForms<T extends string>(\n keys: readonly T[],\n forms: Partial<Record<T, unknown>> & Record<string, unknown>,\n): {\n messages: Record<string, string>\n components: Node[]\n} {\n const messages: Record<string, string> = {}\n const components: Node[] = []\n\n for (const key of keys) {\n const value = forms[key]\n if (value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n for (const [key, value] of Object.entries(forms)) {\n if (keys.includes(key as T) || value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n return { messages, components }\n}\n\nexport function buildICUSelectMessage(forms: Record<string, string>): string {\n return `{value, select, ${Object.entries(forms).map(([key, text]) => `${key} {${text}}`).join(' ')}}`\n}\n\nexport function normalizeSelectForms(forms: Record<string, string>): {\n forms: Record<string, string>\n valueMap: Record<string, string>\n} {\n const normalized: Record<string, string> = {}\n const valueMap: Record<string, string> = {}\n let index = 0\n\n for (const [key, text] of Object.entries(forms)) {\n if (key === 'other') {\n normalized['other'] = text\n continue\n }\n\n const safeKey = /^[A-Za-z0-9_]+$/.test(key) ? key : `case_${index++}`\n normalized[safeKey] = text\n valueMap[key] = safeKey\n }\n\n if (normalized['other'] === undefined) {\n normalized['other'] = ''\n }\n\n return { forms: normalized, valueMap }\n}\n","import { Dynamic } from 'solid-js/web'\nimport { children as resolveChildren, createMemo } from 'solid-js'\nimport type { Component, JSX } from 'solid-js'\nimport { useI18n } from './use-i18n'\nimport { extractMessage as extractDomMessage, reconstruct as reconstructDomMessage } from './rich-dom'\n\n/** A Solid component that accepts children */\nexport type RichComponent = Component<{ children?: JSX.Element }>\n\n/** Props for the `<Trans>` component */\nexport interface TransProps {\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Wrapper element tag name (default: `'span'`) — used in children-only mode */\n tag?: string\n /** Children — the content to translate (legacy API) */\n children?: JSX.Element\n /** Translated message string with XML-like tags (e.g. `<bold>text</bold>`) */\n message?: string\n /** Map of tag names to Solid components */\n components?: Record<string, RichComponent>\n /** @internal Pre-computed message from build plugin */\n __message?: string\n /** @internal Pre-computed component map from build plugin */\n __components?: Record<string, RichComponent>\n}\n\n/**\n * A token from parsing the message string.\n * Either a plain text segment or a tag with inner content.\n */\ninterface TextToken {\n readonly type: 'text'\n readonly value: string\n}\n\ninterface TagToken {\n readonly type: 'tag'\n readonly name: string\n readonly children: readonly Token[]\n}\n\ntype Token = TextToken | TagToken\n\n/**\n * Parse a message string containing XML-like tags into a token tree.\n *\n * Supports:\n * - Named tags: `<bold>content</bold>`\n * - Self-closing tags: `<br/>`\n * - Nested tags: `<bold>hello <italic>world</italic></bold>`\n */\nfunction parseTokens(input: string): readonly Token[] {\n const tokens: Token[] = []\n let pos = 0\n\n while (pos < input.length) {\n const openIdx = input.indexOf('<', pos)\n\n if (openIdx === -1) {\n // No more tags — rest is plain text\n tokens.push({ type: 'text', value: input.slice(pos) })\n break\n }\n\n // Push any text before this tag\n if (openIdx > pos) {\n tokens.push({ type: 'text', value: input.slice(pos, openIdx) })\n }\n\n // Check for self-closing tag: <tagName/>\n const selfCloseMatch = input.slice(openIdx).match(/^<(\\w+)\\s*\\/>/)\n if (selfCloseMatch) {\n tokens.push({ type: 'tag', name: selfCloseMatch[1]!, children: [] })\n pos = openIdx + selfCloseMatch[0].length\n continue\n }\n\n // Check for opening tag: <tagName>\n const openMatch = input.slice(openIdx).match(/^<(\\w+)>/)\n if (!openMatch) {\n // Not a valid tag — treat '<' as text\n tokens.push({ type: 'text', value: '<' })\n pos = openIdx + 1\n continue\n }\n\n const tagName = openMatch[1]!\n const contentStart = openIdx + openMatch[0].length\n\n // Find the matching closing tag, respecting nesting\n const innerEnd = findClosingTag(input, tagName, contentStart)\n if (innerEnd === -1) {\n // No closing tag found — treat as plain text\n tokens.push({ type: 'text', value: input.slice(openIdx, contentStart) })\n pos = contentStart\n continue\n }\n\n const innerContent = input.slice(contentStart, innerEnd)\n const closingTag = `</${tagName}>`\n tokens.push({\n type: 'tag',\n name: tagName,\n children: parseTokens(innerContent),\n })\n pos = innerEnd + closingTag.length\n }\n\n return tokens\n}\n\n/**\n * Find the position of the matching closing tag, accounting for nesting\n * of the same tag name.\n *\n * Returns the index of the start of the closing tag, or -1 if not found.\n */\nfunction findClosingTag(input: string, tagName: string, startPos: number): number {\n const openTag = `<${tagName}>`\n const closeTag = `</${tagName}>`\n let depth = 1\n let pos = startPos\n\n while (pos < input.length && depth > 0) {\n const nextOpen = input.indexOf(openTag, pos)\n const nextClose = input.indexOf(closeTag, pos)\n\n if (nextClose === -1) return -1\n\n if (nextOpen !== -1 && nextOpen < nextClose) {\n depth++\n pos = nextOpen + openTag.length\n } else {\n depth--\n if (depth === 0) return nextClose\n pos = nextClose + closeTag.length\n }\n }\n\n return -1\n}\n\n/**\n * Render a token tree into Solid JSX elements using the components map.\n */\nfunction renderTokens(\n tokens: readonly Token[],\n components: Record<string, RichComponent>,\n): JSX.Element {\n const elements = tokens.map((token): JSX.Element => {\n if (token.type === 'text') {\n return token.value as unknown as JSX.Element\n }\n\n const Comp = components[token.name]\n if (!Comp) {\n // Unknown component — render inner content as plain text\n return renderTokens(token.children, components)\n }\n\n const innerContent = token.children.length > 0\n ? renderTokens(token.children, components)\n : undefined\n\n return (<Dynamic component={Comp}>{innerContent}</Dynamic>) as JSX.Element\n })\n\n if (elements.length === 1) return elements[0]!\n return (<>{elements}</>) as JSX.Element\n}\n\n/**\n * Render translated content with inline components.\n *\n * Supports two APIs:\n *\n * 1. **message + components** (recommended for rich text):\n * ```tsx\n * <Trans\n * message={t`Welcome to <bold>Fluenti</bold>!`}\n * components={{ bold: (props) => <strong>{props.children}</strong> }}\n * />\n * ```\n *\n * 2. **children** (legacy / simple passthrough):\n * ```tsx\n * <Trans>Click <a href=\"/next\">here</a> to continue</Trans>\n * ```\n */\nexport const Trans: Component<TransProps> = (props) => {\n const { t } = useI18n()\n const resolvedChildren = resolveChildren(() => props.children)\n // message + components API (including build-time __message/__components)\n // Note: the vite-plugin tagged-template transform wraps Solid expressions in\n // createMemo(), so props.message may be a memo accessor (function) instead of\n // a string. We unwrap it here to handle both cases.\n const message = createMemo(() => {\n const raw = props.__message ?? props.message\n return typeof raw === 'function' ? (raw as () => string)() : raw\n })\n const components = createMemo(() => props.__components ?? props.components)\n\n return (() => {\n const msg = message()\n const comps = components()\n\n if (msg !== undefined && comps) {\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: msg,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const tokens = parseTokens(translated)\n return renderTokens(tokens, comps)\n }\n\n // Fallback: children-only API with runtime extraction/reconstruction\n const children = resolvedChildren.toArray()\n if (children.length === 0) return null\n const extracted = extractDomMessage(children)\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: extracted.message,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const result = extracted.components.length > 0\n ? reconstructDomMessage(translated, extracted.components)\n : translated\n\n if (Array.isArray(result) && result.length > 1) {\n return (<Dynamic component={props.tag ?? 'span'}>{result}</Dynamic>) as JSX.Element\n }\n\n return result as JSX.Element\n }) as unknown as JSX.Element\n}\n","import { Dynamic } from 'solid-js/web'\nimport type { Component, JSX } from 'solid-js'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { reconstruct, serializeRichForms } from './rich-dom'\n\n/** Plural category names in a stable order for ICU message building. */\nconst PLURAL_CATEGORIES = ['zero', 'one', 'two', 'few', 'many', 'other'] as const\n\ntype PluralCategory = (typeof PLURAL_CATEGORIES)[number]\n\n/**\n * Build an ICU plural message string from individual category props.\n *\n * Given `{ zero: \"No items\", one: \"# item\", other: \"# items\" }`,\n * produces `\"{count, plural, =0 {No items} one {# item} other {# items}}\"`.\n *\n * @internal\n */\nfunction buildICUPluralMessage(\n forms: Partial<Record<PluralCategory, string>> & { other: string },\n offset?: number,\n): string {\n const parts: string[] = []\n for (const cat of PLURAL_CATEGORIES) {\n const text = forms[cat]\n if (text !== undefined) {\n // Map the `zero` prop to ICU `=0` exact match. In ICU MessageFormat,\n // `zero` is a CLDR plural category that only activates in languages\n // with a grammatical zero form (e.g. Arabic). The `=0` exact match\n // works universally for the common \"show this when count is 0\" intent.\n const key = cat === 'zero' ? '=0' : cat\n parts.push(`${key} {${text}}`)\n }\n }\n const offsetPrefix = offset ? `offset:${offset} ` : ''\n return `{count, plural, ${offsetPrefix}${parts.join(' ')}}`\n}\n\n/** Props for the `<Plural>` component */\nexport interface PluralProps {\n /** The numeric value to pluralise */\n value: number\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Offset from value before selecting form */\n offset?: number\n /** Message for the \"zero\" plural category */\n zero?: string | JSX.Element\n /** Message for the \"one\" plural category */\n one?: string | JSX.Element\n /** Message for the \"two\" plural category */\n two?: string | JSX.Element\n /** Message for the \"few\" plural category */\n few?: string | JSX.Element\n /** Message for the \"many\" plural category */\n many?: string | JSX.Element\n /** Fallback message when no category-specific prop matches */\n other: string | JSX.Element\n /** Wrapper element tag name (default: `'span'`) */\n tag?: string\n}\n\n/**\n * `<Plural>` component — shorthand for ICU plural patterns.\n *\n * Plural form props (`zero`, `one`, `two`, `few`, `many`, `other`) are treated\n * as source-language messages. The component builds an ICU plural message,\n * looks it up via `t()` in the catalog, and interpolates the translated result.\n *\n * When no catalog translation exists, the component falls back to interpolating\n * the source-language ICU message directly via core's `interpolate`.\n *\n * Rich text is supported via JSX element props:\n * ```tsx\n * <Plural\n * value={count()}\n * zero={<>No <strong>items</strong> left</>}\n * one={<><em>1</em> item remaining</>}\n * other={<><strong>{count()}</strong> items remaining</>}\n * />\n * ```\n *\n * String props still work (backward compatible):\n * ```tsx\n * <Plural value={count()} zero=\"No items\" one=\"# item\" other=\"# items\" />\n * ```\n */\nexport const Plural: Component<PluralProps> = (props) => {\n const { t } = useI18n()\n\n /** Resolve a category prop value — handles string, accessor function, and JSX */\n function resolveProp(val: string | JSX.Element | undefined): string | JSX.Element | undefined {\n if (typeof val === 'function') return (val as () => string | JSX.Element)()\n return val\n }\n\n return (() => {\n // Resolve all category values (handles Solid accessors from createMemo)\n const resolvedValues: Partial<Record<PluralCategory, string | JSX.Element>> = {}\n for (const cat of PLURAL_CATEGORIES) {\n const resolved = resolveProp(props[cat])\n if (resolved !== undefined) {\n resolvedValues[cat] = resolved\n }\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, resolvedValues)\n const icuMessage = buildICUPluralMessage(\n {\n ...(messages['zero'] !== undefined && { zero: messages['zero'] }),\n ...(messages['one'] !== undefined && { one: messages['one'] }),\n ...(messages['two'] !== undefined && { two: messages['two'] }),\n ...(messages['few'] !== undefined && { few: messages['few'] }),\n ...(messages['many'] !== undefined && { many: messages['many'] }),\n other: messages['other'] ?? '',\n },\n props.offset,\n )\n\n const translated = t(\n {\n id: props.id ?? (props.context === undefined ? icuMessage : hashMessage(icuMessage, props.context)),\n message: icuMessage,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { count: props.value },\n )\n\n return (<Dynamic component={props.tag ?? 'span'}>{components.length > 0 ? reconstruct(translated, components) : translated}</Dynamic>) as JSX.Element\n }) as unknown as JSX.Element\n}\n","import type { Component, JSX } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { buildICUSelectMessage, normalizeSelectForms, reconstruct, serializeRichForms } from './rich-dom'\n\n/** Props for the `<Select>` component */\nexport interface SelectProps {\n /** The value to match against prop keys */\n value: string\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Fallback message when no key matches */\n other: string | JSX.Element\n /**\n * Named options map. Keys are match values, values are display strings or JSX.\n * Takes precedence over dynamic attrs when both are provided.\n *\n * @example `{ male: 'He', female: 'She' }`\n */\n options?: Record<string, string | JSX.Element>\n /** Wrapper element tag name (default: `span`) */\n tag?: string\n /** Additional key/message pairs for matching (attrs fallback) */\n [key: string]: unknown\n}\n\n/**\n * Render a message selected by matching `value` against prop keys.\n *\n * Options can be provided via the type-safe `options` prop (recommended)\n * or as direct attrs (convenience). When both are present, `options` takes\n * precedence.\n *\n * Rich text is supported via JSX element values in the `options` prop or\n * as direct JSX element props:\n * ```tsx\n * <Select\n * value={gender()}\n * options={{\n * male: <><strong>He</strong> liked this</>,\n * female: <><strong>She</strong> liked this</>,\n * }}\n * other={<><em>They</em> liked this</>}\n * />\n * ```\n *\n * Falls back to the `other` prop when no key matches.\n */\nexport const SelectComp: Component<SelectProps> = (props) => {\n const { t } = useI18n()\n\n const resolvedTag = () => props.tag ?? 'span'\n\n const content = () => {\n const forms: Record<string, unknown> = props.options !== undefined\n ? { ...props.options, other: props.other }\n : {\n ...Object.fromEntries(\n Object.entries(props).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other', 'tag'].includes(key)),\n ),\n other: props.other,\n }\n\n const orderedKeys = [...Object.keys(forms).filter(key => key !== 'other'), 'other'] as const\n const { messages, components } = serializeRichForms(orderedKeys, forms)\n const normalized = normalizeSelectForms(\n Object.fromEntries([...orderedKeys].map((key) => [key, messages[key] ?? ''])),\n )\n const translated = t(\n {\n id: props.id ?? (props.context === undefined\n ? buildICUSelectMessage(normalized.forms)\n : hashMessage(buildICUSelectMessage(normalized.forms), props.context)),\n message: buildICUSelectMessage(normalized.forms),\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { value: normalized.valueMap[props.value] ?? 'other' },\n )\n\n return components.length > 0 ? reconstruct(translated, components) : translated\n }\n\n return (<Dynamic component={resolvedTag()}>{content()}</Dynamic>) as JSX.Element\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface DateTimeProps {\n /** Date value to format */\n value: Date | number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<DateTime>` — date formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport function DateTime(props: DateTimeProps) {\n const { d } = useI18n()\n return <>{d(props.value, props.style)}</>\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<NumberFormat>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <NumberFormat value={1234.56} style=\"currency\" />\n * ```\n */\nexport function NumberFormat(props: NumberProps) {\n const { n } = useI18n()\n return <>{n(props.value, props.style)}</>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["createContext","ParentComponent","createI18nContext","I18nConfig","I18nContext","I18nCtx","I18nProvider","props","ctx","_$createComponent","Provider","value","children","JSX","isNodeLike","value","Node","resolveValue","length","offsetIndices","message","offset","replace","_match","index","suffix","Number","extractMessage","components","visit","node","resolved","undefined","Array","isArray","child","String","nodeType","TEXT_NODE","textContent","DOCUMENT_FRAGMENT_NODE","from","childNodes","idx","inner","push","Element","cloneNode","appendChild","parent","entry","document","createTextNode","reconstruct","translated","tagRe","result","lastIndex","match","RegExpExecArray","exec","slice","template","clone","serializeRichForms","keys","T","forms","Partial","Record","messages","key","extracted","Object","entries","includes","buildICUSelectMessage","map","text","join","normalizeSelectForms","valueMap","normalized","safeKey","test","Dynamic","children","resolveChildren","createMemo","Component","JSX","useI18n","extractMessage","extractDomMessage","reconstruct","reconstructDomMessage","RichComponent","Element","TransProps","id","context","comment","tag","message","components","Record","__message","__components","TextToken","type","value","TagToken","name","Token","parseTokens","input","tokens","pos","length","openIdx","indexOf","push","slice","selfCloseMatch","match","openMatch","tagName","contentStart","innerEnd","findClosingTag","innerContent","closingTag","startPos","openTag","closeTag","depth","nextOpen","nextClose","renderTokens","elements","map","token","Comp","undefined","_$createComponent","component","Trans","props","t","resolvedChildren","raw","msg","comps","translated","toArray","extracted","result","Array","isArray","Dynamic","Component","JSX","hashMessage","useI18n","reconstruct","serializeRichForms","PLURAL_CATEGORIES","const","PluralCategory","buildICUPluralMessage","forms","Partial","Record","other","offset","parts","cat","text","undefined","key","push","offsetPrefix","join","PluralProps","value","id","context","comment","zero","Element","one","two","few","many","tag","Plural","props","t","resolveProp","val","resolvedValues","resolved","messages","components","icuMessage","translated","message","count","_$createComponent","component","children","_$memo","length","Component","JSX","Dynamic","hashMessage","useI18n","buildICUSelectMessage","normalizeSelectForms","reconstruct","serializeRichForms","SelectProps","value","id","context","comment","other","Element","options","Record","tag","key","SelectComp","props","t","resolvedTag","content","forms","undefined","Object","fromEntries","entries","filter","includes","orderedKeys","keys","const","messages","components","normalized","map","translated","message","valueMap","length","_$createComponent","component","children","useI18n","DateTimeProps","value","Date","style","DateTime","props","d","_$memo","useI18n","NumberProps","value","style","NumberFormat","props","n","_$memo"],"sources":["../src/context.ts","../src/provider.tsx","../src/use-i18n.ts","../src/compile-time-t.ts","../src/rich-dom.tsx","../src/trans.tsx","../src/plural.tsx","../src/select.tsx","../src/components/DateTime.tsx","../src/components/NumberFormat.tsx"],"sourcesContent":["import { createSignal, createRoot, type Accessor } from 'solid-js'\nimport { formatDate, formatNumber, interpolate as coreInterpolate, buildICUMessage, resolveDescriptorId } from '@fluenti/core'\nimport type { FluentConfig, Locale, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions } from '@fluenti/core'\n\n/** Chunk loader for lazy locale loading */\nexport type ChunkLoader = (\n locale: string,\n) => Promise<Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> }>\n\ninterface SplitRuntimeModule {\n __switchLocale?: (locale: string) => Promise<void>\n __preloadLocale?: (locale: string) => Promise<void>\n}\n\nconst SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.solid')\n\nfunction getSplitRuntimeModule(): SplitRuntimeModule | null {\n const runtime = (globalThis as Record<PropertyKey, unknown>)[SPLIT_RUNTIME_KEY]\n return typeof runtime === 'object' && runtime !== null\n ? runtime as SplitRuntimeModule\n : null\n}\n\nfunction resolveChunkMessages(\n loaded: Record<string, CompiledMessage> | { default: Record<string, CompiledMessage> },\n): Record<string, CompiledMessage> {\n return typeof loaded === 'object' && loaded !== null && 'default' in loaded\n ? (loaded as { default: Record<string, CompiledMessage> }).default\n : loaded\n}\n\n/** Extended config with lazy locale loading support */\nexport interface I18nConfig extends FluentConfig {\n /** Async chunk loader for lazy locale loading */\n chunkLoader?: ChunkLoader\n /** Enable lazy locale loading through chunkLoader */\n lazyLocaleLoading?: boolean\n /** Locale-specific fallback chains */\n fallbackChain?: Record<string, Locale[]>\n /** Named date format styles */\n dateFormats?: DateFormatOptions\n /** Named number format styles */\n numberFormats?: NumberFormatOptions\n}\n\n/** Reactive i18n context holding locale signal and translation utilities */\nexport interface I18nContext {\n /** Reactive accessor for the current locale */\n locale(): Locale\n /** Set the active locale (async when lazy locale loading is enabled) */\n setLocale(locale: Locale): Promise<void>\n /** Translate a message by id with optional interpolation values */\n t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n /** Tagged template form: t`Hello ${name}` */\n t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n /** Merge additional messages into a locale catalog at runtime */\n loadMessages(locale: Locale, messages: Messages): void\n /** Return all locale codes that have loaded messages */\n getLocales(): Locale[]\n /** Format a date value for the current locale */\n d(value: Date | number, style?: string): string\n /** Format a number value for the current locale */\n n(value: number, style?: string): string\n /** Format an ICU message string directly (no catalog lookup) */\n format(message: string, values?: Record<string, unknown>): string\n /** Whether a locale chunk is currently being loaded */\n isLoading: Accessor<boolean>\n /** Set of locales whose messages have been loaded */\n loadedLocales: Accessor<Set<string>>\n /** Preload a locale in the background without switching to it */\n preloadLocale(locale: string): void\n}\n\n/**\n * Create a reactive i18n context backed by Solid signals.\n *\n * The returned `t()` reads the internal `locale()` signal, so any\n * Solid computation that calls `t()` will re-run when the locale changes.\n */\nexport function createI18nContext(config: FluentConfig | I18nConfig): I18nContext {\n const [locale, setLocaleSignal] = createSignal<Locale>(config.locale)\n const [isLoading, setIsLoading] = createSignal(false)\n const loadedLocalesSet = new Set<string>([config.locale])\n const [loadedLocales, setLoadedLocales] = createSignal(new Set(loadedLocalesSet))\n const messages: Record<string, Messages> = { ...config.messages }\n const i18nConfig = config as I18nConfig\n const lazyLocaleLoading = i18nConfig.lazyLocaleLoading\n ?? (config as I18nConfig & { splitting?: boolean }).splitting\n ?? false\n\n function lookupCatalog(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const catalog = messages[loc]\n if (!catalog) {\n return undefined\n }\n\n const msg = catalog[id]\n if (msg === undefined) {\n return undefined\n }\n\n if (typeof msg === 'function') {\n return msg(values)\n }\n\n if (typeof msg === 'string' && values) {\n return coreInterpolate(msg, values, loc)\n }\n\n return String(msg)\n }\n\n function lookupWithFallbacks(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string | undefined {\n const localesToTry: Locale[] = [loc]\n const seen = new Set(localesToTry)\n\n if (config.fallbackLocale && !seen.has(config.fallbackLocale)) {\n localesToTry.push(config.fallbackLocale)\n seen.add(config.fallbackLocale)\n }\n\n const chainLocales = i18nConfig.fallbackChain?.[loc] ?? i18nConfig.fallbackChain?.['*']\n if (chainLocales) {\n for (const chainLocale of chainLocales) {\n if (!seen.has(chainLocale)) {\n localesToTry.push(chainLocale)\n seen.add(chainLocale)\n }\n }\n }\n\n for (const targetLocale of localesToTry) {\n const result = lookupCatalog(id, targetLocale, values)\n if (result !== undefined) {\n return result\n }\n }\n\n return undefined\n }\n\n function resolveMissing(\n id: string,\n loc: Locale,\n ): string | undefined {\n if (!config.missing) {\n return undefined\n }\n\n const result = config.missing(loc, id)\n if (result !== undefined) {\n return result\n }\n return undefined\n }\n\n function resolveMessage(\n id: string,\n loc: Locale,\n values?: Record<string, unknown>,\n ): string {\n const catalogResult = lookupWithFallbacks(id, loc, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(id, loc)\n if (missingResult !== undefined) {\n return missingResult\n }\n\n if (id.includes('{')) {\n return coreInterpolate(id, values, loc)\n }\n\n return id\n }\n\n function t(strings: TemplateStringsArray, ...exprs: unknown[]): string\n function t(id: string | MessageDescriptor, values?: Record<string, unknown>): string\n function t(idOrStrings: string | MessageDescriptor | TemplateStringsArray, ...rest: unknown[]): string {\n // Tagged template form: t`Hello ${name}`\n if (Array.isArray(idOrStrings) && 'raw' in idOrStrings) {\n const strings = idOrStrings as TemplateStringsArray\n const icu = buildICUMessage(strings, rest)\n const values = Object.fromEntries(rest.map((v, i) => [String(i), v]))\n return t(icu, values)\n }\n\n const id = idOrStrings as string | MessageDescriptor\n const values = rest[0] as Record<string, unknown> | undefined\n const currentLocale = locale() // reactive dependency\n if (typeof id === 'object' && id !== null) {\n const messageId = resolveDescriptorId(id)\n if (messageId) {\n const catalogResult = lookupWithFallbacks(messageId, currentLocale, values)\n if (catalogResult !== undefined) {\n return catalogResult\n }\n\n const missingResult = resolveMissing(messageId, currentLocale)\n if (missingResult !== undefined) {\n return missingResult\n }\n }\n\n if (id.message !== undefined) {\n return coreInterpolate(id.message, values, currentLocale)\n }\n\n return messageId ?? ''\n }\n\n return resolveMessage(id, currentLocale, values)\n }\n\n const loadMessages = (loc: Locale, msgs: Messages): void => {\n // Intentional mutation: messages record is locally scoped to this context closure\n messages[loc] = { ...messages[loc], ...msgs }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n }\n\n const setLocale = async (newLocale: Locale): Promise<void> => {\n if (!lazyLocaleLoading || !i18nConfig.chunkLoader) {\n setLocaleSignal(newLocale)\n return\n }\n\n const splitRuntime = getSplitRuntimeModule()\n\n if (loadedLocalesSet.has(newLocale)) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n return\n }\n\n setIsLoading(true)\n try {\n const loaded = resolveChunkMessages(await i18nConfig.chunkLoader(newLocale))\n // Intentional mutation: messages record is locally scoped to this context closure\n messages[newLocale] = { ...messages[newLocale], ...loaded }\n loadedLocalesSet.add(newLocale)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setLocaleSignal(newLocale)\n } finally {\n setIsLoading(false)\n }\n }\n\n const preloadLocale = (loc: string): void => {\n if (!lazyLocaleLoading || loadedLocalesSet.has(loc) || !i18nConfig.chunkLoader) return\n const splitRuntime = getSplitRuntimeModule()\n i18nConfig.chunkLoader(loc).then(async (loaded) => {\n const resolved = resolveChunkMessages(loaded)\n // Intentional mutation: messages record is locally scoped to this context closure\n messages[loc] = { ...messages[loc], ...resolved }\n loadedLocalesSet.add(loc)\n setLoadedLocales(new Set(loadedLocalesSet))\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n }).catch((e: unknown) => {\n console.warn('[fluenti] preload failed:', loc, e)\n })\n }\n\n const getLocales = (): Locale[] => Object.keys(messages)\n\n const d = (value: Date | number, style?: string): string =>\n formatDate(value, locale(), style, i18nConfig.dateFormats)\n\n const n = (value: number, style?: string): string =>\n formatNumber(value, locale(), style, i18nConfig.numberFormats)\n\n const format = (message: string, values?: Record<string, unknown>): string => {\n return coreInterpolate(message, values, locale())\n }\n\n return { locale, setLocale, t, loadMessages, getLocales, d, n, format, isLoading, loadedLocales, preloadLocale }\n}\n\n// ─── Module-level singleton ─────────────────────────────────────────────────\n\nlet globalCtx: I18nContext | undefined\n\n/**\n * Initialize the global i18n singleton.\n *\n * Call once at app startup (e.g. in your entry file) before any `useI18n()`.\n * Signals are created inside a `createRoot` so they outlive any component scope.\n *\n * Returns the context for convenience, but `useI18n()` will also find it.\n */\nexport function createI18n(config: FluentConfig | I18nConfig): I18nContext {\n const ctx = createRoot(() => createI18nContext(config))\n\n // Only set global singleton in browser (client-side).\n // In SSR, each request should use <I18nProvider> for per-request isolation.\n if (typeof window !== 'undefined') {\n globalCtx = ctx\n } else {\n console.warn(\n '[fluenti] createI18n() detected SSR environment. ' +\n 'Use <I18nProvider> for per-request isolation in SSR.',\n )\n }\n\n return ctx\n}\n\n/** @internal — used by useI18n and I18nProvider */\nexport function getGlobalI18nContext(): I18nContext | undefined {\n return globalCtx\n}\n\n/** @internal — used by I18nProvider to set context without createRoot wrapper */\nexport function setGlobalI18nContext(ctx: I18nContext): void {\n globalCtx = ctx\n}\n\n/** @internal — reset the global singleton (for testing only) */\nexport function resetGlobalI18nContext(): void {\n globalCtx = undefined\n}\n","import { createContext } from 'solid-js'\nimport type { ParentComponent } from 'solid-js'\nimport { createI18nContext } from './context'\nimport type { I18nConfig, I18nContext } from './context'\n\n/** Solid context object for i18n — used internally by useI18n() */\nexport const I18nCtx = createContext<I18nContext>()\n\n/**\n * Provide i18n context to the component tree.\n *\n */\nexport const I18nProvider: ParentComponent<I18nConfig> = (props) => {\n const ctx = createI18nContext(props)\n return <I18nCtx.Provider value={ctx}>{props.children}</I18nCtx.Provider>\n}\n","import { useContext } from 'solid-js'\nimport { I18nCtx } from './provider'\nimport { getGlobalI18nContext } from './context'\nimport type { I18nContext } from './context'\n\n/**\n * Access the i18n context.\n *\n * Resolution order:\n * 1. Nearest `<I18nProvider>` in the component tree\n * 2. Module-level singleton created by `createI18n()`\n *\n * Throws if neither is available.\n */\nexport function useI18n(): I18nContext {\n const ctx = useContext(I18nCtx)\n if (ctx) {\n return ctx\n }\n\n const global = getGlobalI18nContext()\n if (global) {\n return global\n }\n\n throw new Error(\n 'useI18n requires either createI18n() to be called at startup, ' +\n 'or the component to be inside an <I18nProvider>.',\n )\n}\n","import type { CompileTimeT } from '@fluenti/core'\n\nexport const t: CompileTimeT = ((..._args: unknown[]) => {\n throw new Error(\n \"[fluenti] `t` imported from '@fluenti/solid' is a compile-time API. \" +\n 'Use it only with the Fluenti build transform inside a component or custom hook. ' +\n 'For runtime lookups, use useI18n().t(...).',\n )\n}) as CompileTimeT\n","import type { JSX } from 'solid-js'\n\nfunction isNodeLike(value: unknown): value is Node {\n return typeof Node !== 'undefined' && value instanceof Node\n}\n\nfunction resolveValue(value: unknown): unknown {\n if (typeof value === 'function' && !(value as { length?: number }).length) {\n return (value as () => unknown)()\n }\n return value\n}\n\nexport function offsetIndices(message: string, offset: number): string {\n if (offset === 0) return message\n return message\n .replace(/<(\\d+)(\\/?>)/g, (_match, index: string, suffix: string) => `<${Number(index) + offset}${suffix}`)\n .replace(/<\\/(\\d+)>/g, (_match, index: string) => `</${Number(index) + offset}>`)\n}\n\nexport function extractMessage(value: unknown): {\n message: string\n components: Node[]\n} {\n const components: Node[] = []\n let message = ''\n\n function visit(node: unknown): void {\n const resolved = resolveValue(node)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const child of resolved) visit(child)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n message += String(resolved)\n return\n }\n if (!isNodeLike(resolved)) return\n if (resolved.nodeType === Node.TEXT_NODE) {\n message += resolved.textContent ?? ''\n return\n }\n if (resolved.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {\n visit(Array.from(resolved.childNodes))\n return\n }\n\n const idx = components.length\n const inner = extractMessage(Array.from(resolved.childNodes))\n components.push((resolved as Element).cloneNode(false))\n components.push(...inner.components)\n message += `<${idx}>${offsetIndices(inner.message, idx + 1)}</${idx}>`\n }\n\n visit(value)\n return { message, components }\n}\n\nfunction appendChild(parent: Node, child: unknown): void {\n const resolved = resolveValue(child)\n if (resolved === null || resolved === undefined || typeof resolved === 'boolean') return\n if (Array.isArray(resolved)) {\n for (const entry of resolved) appendChild(parent, entry)\n return\n }\n if (typeof resolved === 'string' || typeof resolved === 'number') {\n parent.appendChild(document.createTextNode(String(resolved)))\n return\n }\n if (isNodeLike(resolved)) {\n parent.appendChild(resolved)\n }\n}\n\nexport function reconstruct(\n translated: string,\n components: Node[],\n): JSX.Element {\n const tagRe = /<(\\d+)>([\\s\\S]*?)<\\/\\1>/g\n const result: unknown[] = []\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n tagRe.lastIndex = 0\n match = tagRe.exec(translated)\n while (match !== null) {\n if (match.index > lastIndex) {\n result.push(translated.slice(lastIndex, match.index))\n }\n\n const idx = Number(match[1])\n const template = components[idx]\n const inner = reconstruct(match[2]!, components)\n if (template) {\n const clone = template.cloneNode(false)\n appendChild(clone, inner)\n result.push(clone)\n } else {\n result.push(match[2]!)\n }\n\n lastIndex = tagRe.lastIndex\n match = tagRe.exec(translated)\n }\n\n if (lastIndex < translated.length) {\n result.push(translated.slice(lastIndex))\n }\n\n return (result.length <= 1 ? result[0] ?? '' : result) as JSX.Element\n}\n\nexport function serializeRichForms<T extends string>(\n keys: readonly T[],\n forms: Partial<Record<T, unknown>> & Record<string, unknown>,\n): {\n messages: Record<string, string>\n components: Node[]\n} {\n const messages: Record<string, string> = {}\n const components: Node[] = []\n\n for (const key of keys) {\n const value = forms[key]\n if (value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n for (const [key, value] of Object.entries(forms)) {\n if (keys.includes(key as T) || value === undefined) continue\n const extracted = extractMessage(value)\n messages[key] = offsetIndices(extracted.message, components.length)\n components.push(...extracted.components)\n }\n\n return { messages, components }\n}\n\nexport function buildICUSelectMessage(forms: Record<string, string>): string {\n return `{value, select, ${Object.entries(forms).map(([key, text]) => `${key} {${text}}`).join(' ')}}`\n}\n\nexport function normalizeSelectForms(forms: Record<string, string>): {\n forms: Record<string, string>\n valueMap: Record<string, string>\n} {\n const normalized: Record<string, string> = {}\n const valueMap: Record<string, string> = {}\n let index = 0\n\n for (const [key, text] of Object.entries(forms)) {\n if (key === 'other') {\n normalized['other'] = text\n continue\n }\n\n const safeKey = /^[A-Za-z0-9_]+$/.test(key) ? key : `case_${index++}`\n normalized[safeKey] = text\n valueMap[key] = safeKey\n }\n\n if (normalized['other'] === undefined) {\n normalized['other'] = ''\n }\n\n return { forms: normalized, valueMap }\n}\n","import { Dynamic } from 'solid-js/web'\nimport { children as resolveChildren, createMemo } from 'solid-js'\nimport type { Component, JSX } from 'solid-js'\nimport { useI18n } from './use-i18n'\nimport { extractMessage as extractDomMessage, reconstruct as reconstructDomMessage } from './rich-dom'\n\n/** A Solid component that accepts children */\nexport type RichComponent = Component<{ children?: JSX.Element }>\n\n/** Props for the `<Trans>` component */\nexport interface TransProps {\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Wrapper element tag name (default: `'span'`) — used in children-only mode */\n tag?: string\n /** Children — the content to translate (legacy API) */\n children?: JSX.Element\n /** Translated message string with XML-like tags (e.g. `<bold>text</bold>`) */\n message?: string\n /** Map of tag names to Solid components */\n components?: Record<string, RichComponent>\n /** @internal Pre-computed message from build plugin */\n __message?: string\n /** @internal Pre-computed component map from build plugin */\n __components?: Record<string, RichComponent>\n}\n\n/**\n * A token from parsing the message string.\n * Either a plain text segment or a tag with inner content.\n */\ninterface TextToken {\n readonly type: 'text'\n readonly value: string\n}\n\ninterface TagToken {\n readonly type: 'tag'\n readonly name: string\n readonly children: readonly Token[]\n}\n\ntype Token = TextToken | TagToken\n\n/**\n * Parse a message string containing XML-like tags into a token tree.\n *\n * Supports:\n * - Named tags: `<bold>content</bold>`\n * - Self-closing tags: `<br/>`\n * - Nested tags: `<bold>hello <italic>world</italic></bold>`\n */\nfunction parseTokens(input: string): readonly Token[] {\n const tokens: Token[] = []\n let pos = 0\n\n while (pos < input.length) {\n const openIdx = input.indexOf('<', pos)\n\n if (openIdx === -1) {\n // No more tags — rest is plain text\n tokens.push({ type: 'text', value: input.slice(pos) })\n break\n }\n\n // Push any text before this tag\n if (openIdx > pos) {\n tokens.push({ type: 'text', value: input.slice(pos, openIdx) })\n }\n\n // Check for self-closing tag: <tagName/>\n const selfCloseMatch = input.slice(openIdx).match(/^<(\\w+)\\s*\\/>/)\n if (selfCloseMatch) {\n tokens.push({ type: 'tag', name: selfCloseMatch[1]!, children: [] })\n pos = openIdx + selfCloseMatch[0].length\n continue\n }\n\n // Check for opening tag: <tagName>\n const openMatch = input.slice(openIdx).match(/^<(\\w+)>/)\n if (!openMatch) {\n // Not a valid tag — treat '<' as text\n tokens.push({ type: 'text', value: '<' })\n pos = openIdx + 1\n continue\n }\n\n const tagName = openMatch[1]!\n const contentStart = openIdx + openMatch[0].length\n\n // Find the matching closing tag, respecting nesting\n const innerEnd = findClosingTag(input, tagName, contentStart)\n if (innerEnd === -1) {\n // No closing tag found — treat as plain text\n tokens.push({ type: 'text', value: input.slice(openIdx, contentStart) })\n pos = contentStart\n continue\n }\n\n const innerContent = input.slice(contentStart, innerEnd)\n const closingTag = `</${tagName}>`\n tokens.push({\n type: 'tag',\n name: tagName,\n children: parseTokens(innerContent),\n })\n pos = innerEnd + closingTag.length\n }\n\n return tokens\n}\n\n/**\n * Find the position of the matching closing tag, accounting for nesting\n * of the same tag name.\n *\n * Returns the index of the start of the closing tag, or -1 if not found.\n */\nfunction findClosingTag(input: string, tagName: string, startPos: number): number {\n const openTag = `<${tagName}>`\n const closeTag = `</${tagName}>`\n let depth = 1\n let pos = startPos\n\n while (pos < input.length && depth > 0) {\n const nextOpen = input.indexOf(openTag, pos)\n const nextClose = input.indexOf(closeTag, pos)\n\n if (nextClose === -1) return -1\n\n if (nextOpen !== -1 && nextOpen < nextClose) {\n depth++\n pos = nextOpen + openTag.length\n } else {\n depth--\n if (depth === 0) return nextClose\n pos = nextClose + closeTag.length\n }\n }\n\n return -1\n}\n\n/**\n * Render a token tree into Solid JSX elements using the components map.\n */\nfunction renderTokens(\n tokens: readonly Token[],\n components: Record<string, RichComponent>,\n): JSX.Element {\n const elements = tokens.map((token): JSX.Element => {\n if (token.type === 'text') {\n return token.value as unknown as JSX.Element\n }\n\n const Comp = components[token.name]\n if (!Comp) {\n // Unknown component — render inner content as plain text\n return renderTokens(token.children, components)\n }\n\n const innerContent = token.children.length > 0\n ? renderTokens(token.children, components)\n : undefined\n\n return (<Dynamic component={Comp}>{innerContent}</Dynamic>) as JSX.Element\n })\n\n if (elements.length === 1) return elements[0]!\n return (<>{elements}</>) as JSX.Element\n}\n\n/**\n * Render translated content with inline components.\n *\n * Supports two APIs:\n *\n * 1. **message + components** (recommended for rich text):\n * ```tsx\n * <Trans\n * message={t`Welcome to <bold>Fluenti</bold>!`}\n * components={{ bold: (props) => <strong>{props.children}</strong> }}\n * />\n * ```\n *\n * 2. **children** (legacy / simple passthrough):\n * ```tsx\n * <Trans>Click <a href=\"/next\">here</a> to continue</Trans>\n * ```\n */\nexport const Trans: Component<TransProps> = (props) => {\n const { t } = useI18n()\n const resolvedChildren = resolveChildren(() => props.children)\n // message + components API (including build-time __message/__components)\n // Note: the vite-plugin tagged-template transform wraps Solid expressions in\n // createMemo(), so props.message may be a memo accessor (function) instead of\n // a string. We unwrap it here to handle both cases.\n const message = createMemo(() => {\n const raw = props.__message ?? props.message\n return typeof raw === 'function' ? (raw as () => string)() : raw\n })\n const components = createMemo(() => props.__components ?? props.components)\n\n return (() => {\n const msg = message()\n const comps = components()\n\n if (msg !== undefined && comps) {\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: msg,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const tokens = parseTokens(translated)\n return renderTokens(tokens, comps)\n }\n\n // Fallback: children-only API with runtime extraction/reconstruction\n const children = resolvedChildren.toArray()\n if (children.length === 0) return null\n const extracted = extractDomMessage(children)\n const translated = t({\n ...(props.id !== undefined ? { id: props.id } : {}),\n message: extracted.message,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n })\n const result = extracted.components.length > 0\n ? reconstructDomMessage(translated, extracted.components)\n : translated\n\n if (Array.isArray(result) && result.length > 1) {\n return (<Dynamic component={props.tag ?? 'span'}>{result}</Dynamic>) as JSX.Element\n }\n\n return result as JSX.Element\n }) as unknown as JSX.Element\n}\n","import { Dynamic } from 'solid-js/web'\nimport type { Component, JSX } from 'solid-js'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { reconstruct, serializeRichForms } from './rich-dom'\n\n/** Plural category names in a stable order for ICU message building. */\nconst PLURAL_CATEGORIES = ['zero', 'one', 'two', 'few', 'many', 'other'] as const\n\ntype PluralCategory = (typeof PLURAL_CATEGORIES)[number]\n\n/**\n * Build an ICU plural message string from individual category props.\n *\n * Given `{ zero: \"No items\", one: \"# item\", other: \"# items\" }`,\n * produces `\"{count, plural, =0 {No items} one {# item} other {# items}}\"`.\n *\n * @internal\n */\nfunction buildICUPluralMessage(\n forms: Partial<Record<PluralCategory, string>> & { other: string },\n offset?: number,\n): string {\n const parts: string[] = []\n for (const cat of PLURAL_CATEGORIES) {\n const text = forms[cat]\n if (text !== undefined) {\n // Map the `zero` prop to ICU `=0` exact match. In ICU MessageFormat,\n // `zero` is a CLDR plural category that only activates in languages\n // with a grammatical zero form (e.g. Arabic). The `=0` exact match\n // works universally for the common \"show this when count is 0\" intent.\n const key = cat === 'zero' ? '=0' : cat\n parts.push(`${key} {${text}}`)\n }\n }\n const offsetPrefix = offset ? `offset:${offset} ` : ''\n return `{count, plural, ${offsetPrefix}${parts.join(' ')}}`\n}\n\n/** Props for the `<Plural>` component */\nexport interface PluralProps {\n /** The numeric value to pluralise */\n value: number\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Offset from value before selecting form */\n offset?: number\n /** Message for the \"zero\" plural category */\n zero?: string | JSX.Element\n /** Message for the \"one\" plural category */\n one?: string | JSX.Element\n /** Message for the \"two\" plural category */\n two?: string | JSX.Element\n /** Message for the \"few\" plural category */\n few?: string | JSX.Element\n /** Message for the \"many\" plural category */\n many?: string | JSX.Element\n /** Fallback message when no category-specific prop matches */\n other: string | JSX.Element\n /** Wrapper element tag name (default: `'span'`) */\n tag?: string\n}\n\n/**\n * `<Plural>` component — shorthand for ICU plural patterns.\n *\n * Plural form props (`zero`, `one`, `two`, `few`, `many`, `other`) are treated\n * as source-language messages. The component builds an ICU plural message,\n * looks it up via `t()` in the catalog, and interpolates the translated result.\n *\n * When no catalog translation exists, the component falls back to interpolating\n * the source-language ICU message directly via core's `interpolate`.\n *\n * Rich text is supported via JSX element props:\n * ```tsx\n * <Plural\n * value={count()}\n * zero={<>No <strong>items</strong> left</>}\n * one={<><em>1</em> item remaining</>}\n * other={<><strong>{count()}</strong> items remaining</>}\n * />\n * ```\n *\n * String props still work (backward compatible):\n * ```tsx\n * <Plural value={count()} zero=\"No items\" one=\"# item\" other=\"# items\" />\n * ```\n */\nexport const Plural: Component<PluralProps> = (props) => {\n const { t } = useI18n()\n\n /** Resolve a category prop value — handles string, accessor function, and JSX */\n function resolveProp(val: string | JSX.Element | undefined): string | JSX.Element | undefined {\n if (typeof val === 'function') return (val as () => string | JSX.Element)()\n return val\n }\n\n return (() => {\n // Resolve all category values (handles Solid accessors from createMemo)\n const resolvedValues: Partial<Record<PluralCategory, string | JSX.Element>> = {}\n for (const cat of PLURAL_CATEGORIES) {\n const resolved = resolveProp(props[cat])\n if (resolved !== undefined) {\n resolvedValues[cat] = resolved\n }\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, resolvedValues)\n const icuMessage = buildICUPluralMessage(\n {\n ...(messages['zero'] !== undefined && { zero: messages['zero'] }),\n ...(messages['one'] !== undefined && { one: messages['one'] }),\n ...(messages['two'] !== undefined && { two: messages['two'] }),\n ...(messages['few'] !== undefined && { few: messages['few'] }),\n ...(messages['many'] !== undefined && { many: messages['many'] }),\n other: messages['other'] ?? '',\n },\n props.offset,\n )\n\n const translated = t(\n {\n id: props.id ?? (props.context === undefined ? icuMessage : hashMessage(icuMessage, props.context)),\n message: icuMessage,\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { count: props.value },\n )\n\n return (<Dynamic component={props.tag ?? 'span'}>{components.length > 0 ? reconstruct(translated, components) : translated}</Dynamic>) as JSX.Element\n }) as unknown as JSX.Element\n}\n","import type { Component, JSX } from 'solid-js'\nimport { Dynamic } from 'solid-js/web'\nimport { hashMessage } from '@fluenti/core'\nimport { useI18n } from './use-i18n'\nimport { buildICUSelectMessage, normalizeSelectForms, reconstruct, serializeRichForms } from './rich-dom'\n\n/** Props for the `<Select>` component */\nexport interface SelectProps {\n /** The value to match against prop keys */\n value: string\n /** Override the auto-generated synthetic ICU message id */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Translator-facing note preserved in extraction catalogs */\n comment?: string\n /** Fallback message when no key matches */\n other: string | JSX.Element\n /**\n * Named options map. Keys are match values, values are display strings or JSX.\n * Takes precedence over dynamic attrs when both are provided.\n *\n * @example `{ male: 'He', female: 'She' }`\n */\n options?: Record<string, string | JSX.Element>\n /** Wrapper element tag name (default: `span`) */\n tag?: string\n /** Additional key/message pairs for matching (attrs fallback) */\n [key: string]: unknown\n}\n\n/**\n * Render a message selected by matching `value` against prop keys.\n *\n * Options can be provided via the type-safe `options` prop (recommended)\n * or as direct attrs (convenience). When both are present, `options` takes\n * precedence.\n *\n * Rich text is supported via JSX element values in the `options` prop or\n * as direct JSX element props:\n * ```tsx\n * <Select\n * value={gender()}\n * options={{\n * male: <><strong>He</strong> liked this</>,\n * female: <><strong>She</strong> liked this</>,\n * }}\n * other={<><em>They</em> liked this</>}\n * />\n * ```\n *\n * Falls back to the `other` prop when no key matches.\n */\nexport const SelectComp: Component<SelectProps> = (props) => {\n const { t } = useI18n()\n\n const resolvedTag = () => props.tag ?? 'span'\n\n const content = () => {\n const forms: Record<string, unknown> = props.options !== undefined\n ? { ...props.options, other: props.other }\n : {\n ...Object.fromEntries(\n Object.entries(props).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other', 'tag'].includes(key)),\n ),\n other: props.other,\n }\n\n const orderedKeys = [...Object.keys(forms).filter(key => key !== 'other'), 'other'] as const\n const { messages, components } = serializeRichForms(orderedKeys, forms)\n const normalized = normalizeSelectForms(\n Object.fromEntries([...orderedKeys].map((key) => [key, messages[key] ?? ''])),\n )\n const translated = t(\n {\n id: props.id ?? (props.context === undefined\n ? buildICUSelectMessage(normalized.forms)\n : hashMessage(buildICUSelectMessage(normalized.forms), props.context)),\n message: buildICUSelectMessage(normalized.forms),\n ...(props.context !== undefined ? { context: props.context } : {}),\n ...(props.comment !== undefined ? { comment: props.comment } : {}),\n },\n { value: normalized.valueMap[props.value] ?? 'other' },\n )\n\n return components.length > 0 ? reconstruct(translated, components) : translated\n }\n\n return (<Dynamic component={resolvedTag()}>{content()}</Dynamic>) as JSX.Element\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface DateTimeProps {\n /** Date value to format */\n value: Date | number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<DateTime>` — date formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport function DateTime(props: DateTimeProps) {\n const { d } = useI18n()\n return <>{d(props.value, props.style)}</>\n}\n","import { useI18n } from '../use-i18n'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<NumberFormat>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <NumberFormat value={1234.56} style=\"currency\" />\n * ```\n */\nexport function NumberFormat(props: NumberProps) {\n const { n } = useI18n()\n return <>{n(props.value, props.style)}</>\n}\n"],"mappings":";;;;AAcA,IAAM,IAAoB,OAAO,IAAI,wBAAwB;AAE7D,SAAS,IAAmD;CAC1D,IAAM,IAAW,WAA4C;AAC7D,QAAO,OAAO,KAAY,YAAY,IAClC,IACA;;AAGN,SAAS,EACP,GACiC;AACjC,QAAO,OAAO,KAAW,YAAY,KAAmB,aAAa,IAChE,EAAwD,UACzD;;AAmDN,SAAgB,EAAkB,GAAgD;CAChF,IAAM,CAAC,GAAQ,KAAmB,EAAqB,EAAO,OAAO,EAC/D,CAAC,GAAW,KAAgB,EAAa,GAAM,EAC/C,IAAmB,IAAI,IAAY,CAAC,EAAO,OAAO,CAAC,EACnD,CAAC,GAAe,KAAoB,EAAa,IAAI,IAAI,EAAiB,CAAC,EAC3E,IAAqC,EAAE,GAAG,EAAO,UAAU,EAC3D,IAAa,GACb,IAAoB,EAAW,qBAC/B,EAAgD,aACjD;CAEL,SAAS,EACP,GACA,GACA,GACoB;EACpB,IAAM,IAAU,EAAS;AACzB,MAAI,CAAC,EACH;EAGF,IAAM,IAAM,EAAQ;AAChB,YAAQ,KAAA,EAYZ,QARI,OAAO,KAAQ,aACV,EAAI,EAAO,GAGhB,OAAO,KAAQ,YAAY,IACtB,EAAgB,GAAK,GAAQ,EAAI,GAGnC,OAAO,EAAI;;CAGpB,SAAS,EACP,GACA,GACA,GACoB;EACpB,IAAM,IAAyB,CAAC,EAAI,EAC9B,IAAO,IAAI,IAAI,EAAa;AAElC,EAAI,EAAO,kBAAkB,CAAC,EAAK,IAAI,EAAO,eAAe,KAC3D,EAAa,KAAK,EAAO,eAAe,EACxC,EAAK,IAAI,EAAO,eAAe;EAGjC,IAAM,IAAe,EAAW,gBAAgB,MAAQ,EAAW,gBAAgB;AACnF,MAAI,QACG,IAAM,KAAe,EACxB,CAAK,EAAK,IAAI,EAAY,KACxB,EAAa,KAAK,EAAY,EAC9B,EAAK,IAAI,EAAY;AAK3B,OAAK,IAAM,KAAgB,GAAc;GACvC,IAAM,IAAS,EAAc,GAAI,GAAc,EAAO;AACtD,OAAI,MAAW,KAAA,EACb,QAAO;;;CAOb,SAAS,EACP,GACA,GACoB;AACpB,MAAI,CAAC,EAAO,QACV;EAGF,IAAM,IAAS,EAAO,QAAQ,GAAK,EAAG;AACtC,MAAI,MAAW,KAAA,EACb,QAAO;;CAKX,SAAS,EACP,GACA,GACA,GACQ;EACR,IAAM,IAAgB,EAAoB,GAAI,GAAK,EAAO;AAC1D,MAAI,MAAkB,KAAA,EACpB,QAAO;EAGT,IAAM,IAAgB,EAAe,GAAI,EAAI;AAS7C,SARI,MAAkB,KAAA,IAIlB,EAAG,SAAS,IAAI,GACX,EAAgB,GAAI,GAAQ,EAAI,GAGlC,IAPE;;CAYX,SAAS,EAAE,GAAgE,GAAG,GAAyB;AAErG,MAAI,MAAM,QAAQ,EAAY,IAAI,SAAS,EAIzC,QAAO,EAFK,EADI,GACqB,EAAK,EAC3B,OAAO,YAAY,EAAK,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAChD;EAGvB,IAAM,IAAK,GACL,IAAS,EAAK,IACd,IAAgB,GAAQ;AAC9B,MAAI,OAAO,KAAO,YAAY,GAAa;GACzC,IAAM,IAAY,EAAoB,EAAG;AACzC,OAAI,GAAW;IACb,IAAM,IAAgB,EAAoB,GAAW,GAAe,EAAO;AAC3E,QAAI,MAAkB,KAAA,EACpB,QAAO;IAGT,IAAM,IAAgB,EAAe,GAAW,EAAc;AAC9D,QAAI,MAAkB,KAAA,EACpB,QAAO;;AAQX,UAJI,EAAG,YAAY,KAAA,IAIZ,KAAa,KAHX,EAAgB,EAAG,SAAS,GAAQ,EAAc;;AAM7D,SAAO,EAAe,GAAI,GAAe,EAAO;;AAuElD,QAAO;EAAE;EAAQ,WA7DC,OAAO,MAAqC;AAC5D,OAAI,CAAC,KAAqB,CAAC,EAAW,aAAa;AACjD,MAAgB,EAAU;AAC1B;;GAGF,IAAM,IAAe,GAAuB;AAE5C,OAAI,EAAiB,IAAI,EAAU,EAAE;AAInC,IAHI,GAAc,kBAChB,MAAM,EAAa,eAAe,EAAU,EAE9C,EAAgB,EAAU;AAC1B;;AAGF,KAAa,GAAK;AAClB,OAAI;IACF,IAAM,IAAS,EAAqB,MAAM,EAAW,YAAY,EAAU,CAAC;AAQ5E,IANA,EAAS,KAAa;KAAE,GAAG,EAAS;KAAY,GAAG;KAAQ,EAC3D,EAAiB,IAAI,EAAU,EAC/B,EAAiB,IAAI,IAAI,EAAiB,CAAC,EACvC,GAAc,kBAChB,MAAM,EAAa,eAAe,EAAU,EAE9C,EAAgB,EAAU;aAClB;AACR,MAAa,GAAM;;;EAiCK;EAAG,eApET,GAAa,MAAyB;AAI1D,GAFA,EAAS,KAAO;IAAE,GAAG,EAAS;IAAM,GAAG;IAAM,EAC7C,EAAiB,IAAI,EAAI,EACzB,EAAiB,IAAI,IAAI,EAAiB,CAAC;;EAgEA,kBAZV,OAAO,KAAK,EAAS;EAYC,IAV9C,GAAsB,MAC/B,EAAW,GAAO,GAAQ,EAAE,GAAO,EAAW,YAAY;EASA,IAPjD,GAAe,MACxB,EAAa,GAAO,GAAQ,EAAE,GAAO,EAAW,cAAc;EAMD,SAJ/C,GAAiB,MACxB,EAAgB,GAAS,GAAQ,GAAQ,CAAC;EAGoB;EAAW;EAAe,gBA7B1E,MAAsB;AAC3C,OAAI,CAAC,KAAqB,EAAiB,IAAI,EAAI,IAAI,CAAC,EAAW,YAAa;GAChF,IAAM,IAAe,GAAuB;AAC5C,KAAW,YAAY,EAAI,CAAC,KAAK,OAAO,MAAW;IACjD,IAAM,IAAW,EAAqB,EAAO;AAK7C,IAHA,EAAS,KAAO;KAAE,GAAG,EAAS;KAAM,GAAG;KAAU,EACjD,EAAiB,IAAI,EAAI,EACzB,EAAiB,IAAI,IAAI,EAAiB,CAAC,EACvC,GAAc,mBAChB,MAAM,EAAa,gBAAgB,EAAI;KAEzC,CAAC,OAAO,MAAe;AACvB,YAAQ,KAAK,6BAA6B,GAAK,EAAE;KACjD;;EAe4G;;AAKlH,IAAI;AAUJ,SAAgB,EAAW,GAAgD;CACzE,IAAM,IAAM,QAAiB,EAAkB,EAAO,CAAC;AAavD,QATI,OAAO,SAAW,MACpB,IAAY,IAEZ,QAAQ,KACN,wGAED,EAGI;;AAIT,SAAgB,IAAgD;AAC9D,QAAO;;;;AChUT,IAAaK,IAAUL,GAA4B,EAMtCM,KAA6CC,MAAU;CAClE,IAAMC,IAAMN,EAAkBK,EAAM;AACpC,QAAAE,EAAQJ,EAAQK,UAAQ;EAACC,OAAOH;EAAG,IAAAI,WAAA;AAAA,UAAGL,EAAMK;;EAAQ,CAAA;;;;ACAtD,SAAgB,IAAuB;CACrC,IAAM,IAAM,EAAW,EAAQ;AAC/B,KAAI,EACF,QAAO;CAGT,IAAM,IAAS,GAAsB;AACrC,KAAI,EACF,QAAO;AAGT,OAAU,MACR,iHAED;;;;AC1BH,IAAa,MAAoB,GAAG,MAAqB;AACvD,OAAU,MACR,iMAGD;;;;ACLH,SAASE,EAAWC,GAA+B;AACjD,QAAO,OAAOC,OAAS,OAAeD,aAAiBC;;AAGzD,SAASC,EAAaF,GAAyB;AAI7C,QAHI,OAAOA,KAAU,cAAc,CAAEA,EAA8BG,SACzDH,GAAyB,GAE5BA;;AAGT,SAAgBI,EAAcC,GAAiBC,GAAwB;AAErE,QADIA,MAAW,IAAUD,IAClBA,EACJE,QAAQ,kBAAkBC,GAAQC,GAAeC,MAAmB,IAAIC,OAAOF,EAAM,GAAGH,IAASI,IAAS,CAC1GH,QAAQ,eAAeC,GAAQC,MAAkB,KAAKE,OAAOF,EAAM,GAAGH,EAAM,GAAI;;AAGrF,SAAgBM,EAAeZ,GAG7B;CACA,IAAMa,IAAqB,EAAE,EACzBR,IAAU;CAEd,SAASS,EAAMC,GAAqB;EAClC,IAAMC,IAAWd,EAAaa,EAAK;AACnC,MAAIC,KAAa,QAAkC,OAAOA,KAAa,UAAW;AAClF,MAAIE,MAAMC,QAAQH,EAAS,EAAE;AAC3B,QAAK,IAAMI,KAASJ,EAAUF,GAAMM,EAAM;AAC1C;;AAEF,MAAI,OAAOJ,KAAa,YAAY,OAAOA,KAAa,UAAU;AAChEX,QAAWgB,OAAOL,EAAS;AAC3B;;AAEF,MAAI,CAACjB,EAAWiB,EAAS,CAAE;AAC3B,MAAIA,EAASM,aAAarB,KAAKsB,WAAW;AACxClB,QAAWW,EAASQ,eAAe;AACnC;;AAEF,MAAIR,EAASM,aAAarB,KAAKwB,wBAAwB;AACrDX,KAAMI,MAAMQ,KAAKV,EAASW,WAAW,CAAC;AACtC;;EAGF,IAAMC,IAAMf,EAAWV,QACjB0B,IAAQjB,EAAeM,MAAMQ,KAAKV,EAASW,WAAW,CAAC;AAG7DtB,EAFAQ,EAAWiB,KAAMd,EAAqBgB,UAAU,GAAM,CAAC,EACvDnB,EAAWiB,KAAK,GAAGD,EAAMhB,WAAW,EACpCR,KAAW,IAAIuB,EAAG,GAAIxB,EAAcyB,EAAMxB,SAASuB,IAAM,EAAE,CAAA,IAAKA,EAAG;;AAIrE,QADAd,EAAMd,EAAM,EACL;EAAEK;EAASQ;EAAY;;AAGhC,SAASoB,EAAYC,GAAcd,GAAsB;CACvD,IAAMJ,IAAWd,EAAakB,EAAM;AAChCJ,YAAa,QAAkC,OAAOA,KAAa,YACvE;MAAIE,MAAMC,QAAQH,EAAS,EAAE;AAC3B,QAAK,IAAMmB,KAASnB,EAAUiB,GAAYC,GAAQC,EAAM;AACxD;;AAEF,MAAI,OAAOnB,KAAa,YAAY,OAAOA,KAAa,UAAU;AAChEkB,KAAOD,YAAYG,SAASC,eAAehB,OAAOL,EAAS,CAAC,CAAC;AAC7D;;AAEF,EAAIjB,EAAWiB,EAAS,IACtBkB,EAAOD,YAAYjB,EAAS;;;AAIhC,SAAgBsB,EACdC,GACA1B,GACa;CACb,IAAM2B,IAAQ,4BACRC,IAAoB,EAAE,EACxBC,IAAY,GACZC;AAIJ,MAFAH,EAAME,YAAY,GAClBC,IAAQH,EAAMK,KAAKN,EAAW,EACvBI,MAAU,OAAM;AACrB,EAAIA,EAAMlC,QAAQiC,KAChBD,EAAOX,KAAKS,EAAWO,MAAMJ,GAAWC,EAAMlC,MAAM,CAAC;EAIvD,IAAMsC,IAAWlC,EADLF,OAAOgC,EAAM,GAAG,GAEtBd,IAAQS,EAAYK,EAAM,IAAK9B,EAAW;AAChD,MAAIkC,GAAU;GACZ,IAAMC,IAAQD,EAASf,UAAU,GAAM;AAEvCS,GADAR,EAAYe,GAAOnB,EAAM,EACzBY,EAAOX,KAAKkB,EAAM;QAElBP,GAAOX,KAAKa,EAAM,GAAI;AAIxBA,EADAD,IAAYF,EAAME,WAClBC,IAAQH,EAAMK,KAAKN,EAAW;;AAOhC,QAJIG,IAAYH,EAAWpC,UACzBsC,EAAOX,KAAKS,EAAWO,MAAMJ,EAAU,CAAC,EAGlCD,EAAOtC,UAAU,IAAIsC,EAAO,MAAM,KAAKA;;AAGjD,SAAgBQ,EACdC,GACAE,GAIA;CACA,IAAMG,IAAmC,EAAE,EACrC1C,IAAqB,EAAE;AAE7B,MAAK,IAAM2C,KAAON,GAAM;EACtB,IAAMlD,IAAQoD,EAAMI;AACpB,MAAIxD,MAAUiB,KAAAA,EAAW;EACzB,IAAMwC,IAAY7C,EAAeZ,EAAM;AAEvCa,EADA0C,EAASC,KAAOpD,EAAcqD,EAAUpD,SAASQ,EAAWV,OAAO,EACnEU,EAAWiB,KAAK,GAAG2B,EAAU5C,WAAW;;AAG1C,MAAK,IAAM,CAAC2C,GAAKxD,MAAU0D,OAAOC,QAAQP,EAAM,EAAE;AAChD,MAAIF,EAAKU,SAASJ,EAAS,IAAIxD,MAAUiB,KAAAA,EAAW;EACpD,IAAMwC,IAAY7C,EAAeZ,EAAM;AAEvCa,EADA0C,EAASC,KAAOpD,EAAcqD,EAAUpD,SAASQ,EAAWV,OAAO,EACnEU,EAAWiB,KAAK,GAAG2B,EAAU5C,WAAW;;AAG1C,QAAO;EAAE0C;EAAU1C;EAAY;;AAGjC,SAAgBgD,EAAsBT,GAAuC;AAC3E,QAAO,mBAAmBM,OAAOC,QAAQP,EAAM,CAACU,KAAK,CAACN,GAAKO,OAAU,GAAGP,EAAG,IAAKO,EAAI,GAAI,CAACC,KAAK,IAAI,CAAA;;AAGpG,SAAgBC,EAAqBb,GAGnC;CACA,IAAMe,IAAqC,EAAE,EACvCD,IAAmC,EAAE,EACvCzD,IAAQ;AAEZ,MAAK,IAAM,CAAC+C,GAAKO,MAASL,OAAOC,QAAQP,EAAM,EAAE;AAC/C,MAAII,MAAQ,SAAS;AACnBW,KAAW,QAAWJ;AACtB;;EAGF,IAAMK,IAAU,kBAAkBC,KAAKb,EAAI,GAAGA,IAAM,QAAQ/C;AAE5DyD,EADAC,EAAWC,KAAWL,GACtBG,EAASV,KAAOY;;AAOlB,QAJID,EAAW,UAAalD,KAAAA,MAC1BkD,EAAW,QAAW,KAGjB;EAAEf,OAAOe;EAAYD;EAAU;;;;AChHxC,SAASiC,EAAYC,GAAiC;CACpD,IAAMC,IAAkB,EAAE,EACtBC,IAAM;AAEV,QAAOA,IAAMF,EAAMG,SAAQ;EACzB,IAAMC,IAAUJ,EAAMK,QAAQ,KAAKH,EAAI;AAEvC,MAAIE,MAAY,IAAI;AAElBH,KAAOK,KAAK;IAAEZ,MAAM;IAAQC,OAAOK,EAAMO,MAAML,EAAG;IAAG,CAAC;AACtD;;AAIF,EAAIE,IAAUF,KACZD,EAAOK,KAAK;GAAEZ,MAAM;GAAQC,OAAOK,EAAMO,MAAML,GAAKE,EAAO;GAAG,CAAC;EAIjE,IAAMI,IAAiBR,EAAMO,MAAMH,EAAQ,CAACK,MAAM,gBAAgB;AAClE,MAAID,GAAgB;AAElBN,GADAD,EAAOK,KAAK;IAAEZ,MAAM;IAAOG,MAAMW,EAAe;IAAKrC,UAAU,EAAA;IAAI,CAAC,EACpE+B,IAAME,IAAUI,EAAe,GAAGL;AAClC;;EAIF,IAAMO,IAAYV,EAAMO,MAAMH,EAAQ,CAACK,MAAM,WAAW;AACxD,MAAI,CAACC,GAAW;AAGdR,GADAD,EAAOK,KAAK;IAAEZ,MAAM;IAAQC,OAAO;IAAK,CAAC,EACzCO,IAAME,IAAU;AAChB;;EAGF,IAAMO,IAAUD,EAAU,IACpBE,IAAeR,IAAUM,EAAU,GAAGP,QAGtCU,IAAWC,EAAed,GAAOW,GAASC,EAAa;AAC7D,MAAIC,MAAa,IAAI;AAGnBX,GADAD,EAAOK,KAAK;IAAEZ,MAAM;IAAQC,OAAOK,EAAMO,MAAMH,GAASQ,EAAY;IAAG,CAAC,EACxEV,IAAMU;AACN;;EAGF,IAAMG,IAAef,EAAMO,MAAMK,GAAcC,EAAS,EAClDG,IAAa,KAAKL,EAAO;AAM/BT,EALAD,EAAOK,KAAK;GACVZ,MAAM;GACNG,MAAMc;GACNxC,UAAU4B,EAAYgB,EAAY;GACnC,CAAC,EACFb,IAAMW,IAAWG,EAAWb;;AAG9B,QAAOF;;AAST,SAASa,EAAed,GAAeW,GAAiBM,GAA0B;CAChF,IAAMC,IAAU,IAAIP,EAAO,IACrBQ,IAAW,KAAKR,EAAO,IACzBS,IAAQ,GACRlB,IAAMe;AAEV,QAAOf,IAAMF,EAAMG,UAAUiB,IAAQ,IAAG;EACtC,IAAMC,IAAWrB,EAAMK,QAAQa,GAAShB,EAAI,EACtCoB,IAAYtB,EAAMK,QAAQc,GAAUjB,EAAI;AAE9C,MAAIoB,MAAc,GAAI,QAAO;AAE7B,MAAID,MAAa,MAAMA,IAAWC,EAEhCpB,CADAkB,KACAlB,IAAMmB,IAAWH,EAAQf;OACpB;AAEL,OADAiB,KACIA,MAAU,EAAG,QAAOE;AACxBpB,OAAMoB,IAAYH,EAAShB;;;AAI/B,QAAO;;AAMT,SAASoB,EACPtB,GACAZ,GACa;CACb,IAAMmC,IAAWvB,EAAOwB,KAAKC,MAAuB;AAClD,MAAIA,EAAMhC,SAAS,OACjB,QAAOgC,EAAM/B;EAGf,IAAMgC,IAAOtC,EAAWqC,EAAM7B;AAU9B,SATK8B,IASEE,EAAE3D,GAAO;GAAC4D,WAAWH;GAAIxD,UAJXuD,EAAMvD,SAASgC,SAAS,IACzCoB,EAAaG,EAAMvD,UAAUkB,EAAW,GACxCuC,KAAAA;GAE2C,CAAA,GAPtCL,EAAaG,EAAMvD,UAAUkB,EAAW;GAQjD;AAGF,QADImC,EAASrB,WAAW,IAAUqB,EAAS,KAChCA;;AAqBb,IAAaO,KAAgCC,MAAU;CACrD,IAAM,EAAEC,SAAMzD,GAAS,EACjB0D,IAAmB9D,QAAsB4D,EAAM7D,SAAS,EAKxDiB,IAAUf,QAAiB;EAC/B,IAAM8D,IAAMH,EAAMzC,aAAayC,EAAM5C;AACrC,SAAO,OAAO+C,KAAQ,aAAcA,GAAsB,GAAGA;GAC7D,EACI9C,IAAahB,QAAiB2D,EAAMxC,gBAAgBwC,EAAM3C,WAAW;AAE3E,eAAc;EACZ,IAAM+C,IAAMhD,GAAS,EACfiD,IAAQhD,GAAY;AAE1B,MAAI+C,MAAQR,KAAAA,KAAaS,EAQvB,QAAOd,EADQxB,EANIkC,EAAE;GACnB,GAAID,EAAMhD,OAAO4C,KAAAA,IAA+B,EAAE,GAArB,EAAE5C,IAAIgD,EAAMhD,IAAI;GAC7CI,SAASgD;GACT,GAAIJ,EAAM/C,YAAY2C,KAAAA,IAAyC,EAAE,GAA/B,EAAE3C,SAAS+C,EAAM/C,SAAS;GAC5D,GAAI+C,EAAM9C,YAAY0C,KAAAA,IAAyC,EAAE,GAA/B,EAAE1C,SAAS8C,EAAM9C,SAAS;GAC7D,CAAC,CACoC,EACVmD,EAAM;EAIpC,IAAMlE,IAAW+D,EAAiBK,SAAS;AAC3C,MAAIpE,EAASgC,WAAW,EAAG,QAAO;EAClC,IAAMqC,IAAY9D,EAAkBP,EAAS,EACvCmE,IAAaL,EAAE;GACnB,GAAID,EAAMhD,OAAO4C,KAAAA,IAA+B,EAAE,GAArB,EAAE5C,IAAIgD,EAAMhD,IAAI;GAC7CI,SAASoD,EAAUpD;GACnB,GAAI4C,EAAM/C,YAAY2C,KAAAA,IAAyC,EAAE,GAA/B,EAAE3C,SAAS+C,EAAM/C,SAAS;GAC5D,GAAI+C,EAAM9C,YAAY0C,KAAAA,IAAyC,EAAE,GAA/B,EAAE1C,SAAS8C,EAAM9C,SAAS;GAC7D,CAAC,EACIuD,IAASD,EAAUnD,WAAWc,SAAS,IACzCvB,EAAsB0D,GAAYE,EAAUnD,WAAW,GACvDiD;AAMJ,SAJII,MAAMC,QAAQF,EAAO,IAAIA,EAAOtC,SAAS,IACpC0B,EAAE3D,GAAO;GAAA,IAAC4D,YAAS;AAAA,WAAEE,EAAM7C,OAAO;;GAAMhB,UAAGsE;GAAM,CAAA,GAGnDA;;GCzOLU,IAAoB;CAAC;CAAQ;CAAO;CAAO;CAAO;CAAQ;CAAQ;AAYxE,SAASG,EACPC,GACAI,GACQ;CACR,IAAMC,IAAkB,EAAE;AAC1B,MAAK,IAAMC,KAAOV,GAAmB;EACnC,IAAMW,IAAOP,EAAMM;AACnB,MAAIC,MAASC,KAAAA,GAAW;GAKtB,IAAMC,IAAMH,MAAQ,SAAS,OAAOA;AACpCD,KAAMK,KAAK,GAAGD,EAAG,IAAKF,EAAI,GAAI;;;AAIlC,QAAO,mBADcH,IAAS,UAAUA,EAAM,KAAM,KACXC,EAAMO,KAAK,IAAI,CAAA;;AAwD1D,IAAaa,KAAkCC,MAAU;CACvD,IAAM,EAAEC,MAAMlC,GAAS;CAGvB,SAASmC,EAAYC,GAAyE;AAE5F,SADI,OAAOA,KAAQ,aAAoBA,GAAoC,GACpEA;;AAGT,eAAc;EAEZ,IAAMC,IAAwE,EAAE;AAChF,OAAK,IAAMxB,KAAOV,GAAmB;GACnC,IAAMmC,IAAWH,EAAYF,EAAMpB,GAAK;AACxC,GAAIyB,MAAavB,KAAAA,MACfsB,EAAexB,KAAOyB;;EAG1B,IAAM,EAAEC,aAAUC,kBAAetC,EAAmBC,GAAmBkC,EAAe,EAChFI,IAAanC,EACjB;GACE,GAAIiC,EAAS,SAAYxB,KAAAA,KAAa,EAAEU,MAAMc,EAAS,MAAS;GAChE,GAAIA,EAAS,QAAWxB,KAAAA,KAAa,EAAEY,KAAKY,EAAS,KAAQ;GAC7D,GAAIA,EAAS,QAAWxB,KAAAA,KAAa,EAAEa,KAAKW,EAAS,KAAQ;GAC7D,GAAIA,EAAS,QAAWxB,KAAAA,KAAa,EAAEc,KAAKU,EAAS,KAAQ;GAC7D,GAAIA,EAAS,SAAYxB,KAAAA,KAAa,EAAEe,MAAMS,EAAS,MAAS;GAChE7B,OAAO6B,EAAS,SAAY;GAC7B,EACDN,EAAMtB,OACP,EAEK+B,IAAaR,EACjB;GACEZ,IAAIW,EAAMX,OAAOW,EAAMV,YAAYR,KAAAA,IAAY0B,IAAa1C,EAAY0C,GAAYR,EAAMV,QAAQ;GAClGoB,SAASF;GACT,GAAIR,EAAMV,YAAYR,KAAAA,IAAyC,EAAE,GAA/B,EAAEQ,SAASU,EAAMV,SAAS;GAC5D,GAAIU,EAAMT,YAAYT,KAAAA,IAAyC,EAAE,GAA/B,EAAES,SAASS,EAAMT,SAAS;GAC7D,EACD,EAAEoB,OAAOX,EAAMZ,OACjB,CAAC;AAED,SAAOwB,EAAEjD,GAAO;GAAA,IAACkD,YAAS;AAAA,WAAEb,EAAMF,OAAO;;GAAM,IAAAgB,WAAA;AAAA,WAAGC,QAAAR,EAAWS,SAAS,EAAC,EAAA,GAAGhD,EAAYyC,GAAYF,EAAW,GAAGE;;GAAU,CAAA;;GChFjH4B,KAAsCC,MAAU;CAC3D,IAAM,EAAEC,MAAMlB,GAAS,EAEjBmB,UAAoBF,EAAMH,OAAO,QAEjCM,UAAgB;EACpB,IAAMC,IAAiCJ,EAAML,YAAYU,KAAAA,IAErD;GACA,GAAGC,OAAOC,YACRD,OAAOE,QAAQR,EAAM,CAACS,QAAQ,CAACX,OAAS,CAAC;IAAC;IAAS;IAAM;IAAW;IAAW;IAAW;IAAS;IAAM,CAACY,SAASZ,EAAI,CACzH,CAAC;GACDL,OAAOO,EAAMP;GACd,GANC;GAAE,GAAGO,EAAML;GAASF,OAAOO,EAAMP;GAAO,EAQtCkB,IAAc,CAAC,GAAGL,OAAOM,KAAKR,EAAM,CAACK,QAAOX,MAAOA,MAAQ,QAAQ,EAAE,QAAQ,EAC7E,EAAEgB,aAAUC,kBAAe5B,EAAmBwB,GAAaP,EAAM,EACjEY,IAAa/B,EACjBqB,OAAOC,YAAY,CAAC,GAAGI,EAAY,CAACM,KAAKnB,MAAQ,CAACA,GAAKgB,EAAShB,MAAQ,GAAG,CAAC,CAC9E,CAAC,EACKoB,IAAajB,EACjB;GACEX,IAAIU,EAAMV,OAAOU,EAAMT,YAAYc,KAAAA,IAC/BrB,EAAsBgC,EAAWZ,MAAM,GACvCtB,EAAYE,EAAsBgC,EAAWZ,MAAM,EAAEJ,EAAMT,QAAQ;GACvE4B,SAASnC,EAAsBgC,EAAWZ,MAAM;GAChD,GAAIJ,EAAMT,YAAYc,KAAAA,IAAyC,EAAE,GAA/B,EAAEd,SAASS,EAAMT,SAAS;GAC5D,GAAIS,EAAMR,YAAYa,KAAAA,IAAyC,EAAE,GAA/B,EAAEb,SAASQ,EAAMR,SAAS;GAC7D,EACD,EAAEH,OAAO2B,EAAWI,SAASpB,EAAMX,UAAU,SAC/C,CAAC;AAED,SAAO0B,EAAWM,SAAS,IAAInC,EAAYgC,GAAYH,EAAW,GAAGG;;AAGvE,QAAOI,EAAEzC,GAAO;EAAA,IAAC0C,YAAS;AAAA,UAAErB,GAAa;;EAAA,IAAAsB,WAAA;AAAA,UAAGrB,GAAS;;EAAA,CAAA;;;;ACvEvD,SAAgB2B,EAASC,GAAsB;CAC7C,IAAM,EAAEC,SAAMP,GAAS;AACvB,QAAAQ,QAAUD,EAAED,EAAMJ,OAAOI,EAAMF,MAAM,CAAA;;;;ACFvC,SAAgBS,EAAaC,GAAoB;CAC/C,IAAM,EAAEC,SAAMN,GAAS;AACvB,QAAAO,QAAUD,EAAED,EAAMH,OAAOG,EAAMF,MAAM,CAAA"}
|
package/dist/server.d.ts
CHANGED
|
@@ -62,6 +62,13 @@ export interface ServerI18n {
|
|
|
62
62
|
* your `resolveLocale` callback, or call `setLocale()` in your
|
|
63
63
|
* entry-server middleware.
|
|
64
64
|
*
|
|
65
|
+
* **⚠️ SSR Concurrency Warning**: This function uses module-level state for locale
|
|
66
|
+
* and cached instance. In concurrent SSR environments (e.g. multiple simultaneous
|
|
67
|
+
* requests), this can cause cross-request locale leakage. For per-request isolation:
|
|
68
|
+
* - Use `getRequestEvent()` in SolidStart to scope locale per request
|
|
69
|
+
* - Or create a separate `createServerI18n()` per request context
|
|
70
|
+
* - Consider using AsyncLocalStorage for true per-request isolation (future)
|
|
71
|
+
*
|
|
65
72
|
* @example
|
|
66
73
|
* ```ts
|
|
67
74
|
* // lib/i18n.server.ts
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EAEtB,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACxG,YAAY,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAExD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG;QAAE,OAAO,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IAC3E,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9C,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,gCAAgC;IAChC,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,kCAAkC;IAClC,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;CAC7D;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAEnC;;;OAGG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,sBAAsB,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACpE;AAED
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EAEtB,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,mBAAmB,EACpB,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACxG,YAAY,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAExD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,GAAG;QAAE,OAAO,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IAC3E,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9C,wCAAwC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,gCAAgC;IAChC,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,kCAAkC;IAClC,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,2CAA2C;IAC3C,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;CAC7D;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IAEnC;;;OAGG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,sBAAsB,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAoErE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluenti/solid",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SolidJS compile-time i18n — Trans/Plural/Select components, I18nProvider, useI18n",
|
|
6
6
|
"homepage": "https://fluenti.dev",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"solid-js": "^1.8"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@fluenti/core": "0.1.
|
|
56
|
+
"@fluenti/core": "0.1.3"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@solidjs/testing-library": "^0.8",
|
package/src/context.ts
CHANGED
|
@@ -223,6 +223,7 @@ export function createI18nContext(config: FluentConfig | I18nConfig): I18nContex
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
const loadMessages = (loc: Locale, msgs: Messages): void => {
|
|
226
|
+
// Intentional mutation: messages record is locally scoped to this context closure
|
|
226
227
|
messages[loc] = { ...messages[loc], ...msgs }
|
|
227
228
|
loadedLocalesSet.add(loc)
|
|
228
229
|
setLoadedLocales(new Set(loadedLocalesSet))
|
|
@@ -247,6 +248,7 @@ export function createI18nContext(config: FluentConfig | I18nConfig): I18nContex
|
|
|
247
248
|
setIsLoading(true)
|
|
248
249
|
try {
|
|
249
250
|
const loaded = resolveChunkMessages(await i18nConfig.chunkLoader(newLocale))
|
|
251
|
+
// Intentional mutation: messages record is locally scoped to this context closure
|
|
250
252
|
messages[newLocale] = { ...messages[newLocale], ...loaded }
|
|
251
253
|
loadedLocalesSet.add(newLocale)
|
|
252
254
|
setLoadedLocales(new Set(loadedLocalesSet))
|
|
@@ -264,14 +266,15 @@ export function createI18nContext(config: FluentConfig | I18nConfig): I18nContex
|
|
|
264
266
|
const splitRuntime = getSplitRuntimeModule()
|
|
265
267
|
i18nConfig.chunkLoader(loc).then(async (loaded) => {
|
|
266
268
|
const resolved = resolveChunkMessages(loaded)
|
|
269
|
+
// Intentional mutation: messages record is locally scoped to this context closure
|
|
267
270
|
messages[loc] = { ...messages[loc], ...resolved }
|
|
268
271
|
loadedLocalesSet.add(loc)
|
|
269
272
|
setLoadedLocales(new Set(loadedLocalesSet))
|
|
270
273
|
if (splitRuntime?.__preloadLocale) {
|
|
271
274
|
await splitRuntime.__preloadLocale(loc)
|
|
272
275
|
}
|
|
273
|
-
}).catch(() => {
|
|
274
|
-
|
|
276
|
+
}).catch((e: unknown) => {
|
|
277
|
+
console.warn('[fluenti] preload failed:', loc, e)
|
|
275
278
|
})
|
|
276
279
|
}
|
|
277
280
|
|
|
@@ -314,8 +317,6 @@ export function createI18n(config: FluentConfig | I18nConfig): I18nContext {
|
|
|
314
317
|
'[fluenti] createI18n() detected SSR environment. ' +
|
|
315
318
|
'Use <I18nProvider> for per-request isolation in SSR.',
|
|
316
319
|
)
|
|
317
|
-
// Still set globalCtx as fallback, but document the risk
|
|
318
|
-
globalCtx = ctx
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
return ctx
|
package/src/server.ts
CHANGED
|
@@ -72,6 +72,13 @@ export interface ServerI18n {
|
|
|
72
72
|
* your `resolveLocale` callback, or call `setLocale()` in your
|
|
73
73
|
* entry-server middleware.
|
|
74
74
|
*
|
|
75
|
+
* **⚠️ SSR Concurrency Warning**: This function uses module-level state for locale
|
|
76
|
+
* and cached instance. In concurrent SSR environments (e.g. multiple simultaneous
|
|
77
|
+
* requests), this can cause cross-request locale leakage. For per-request isolation:
|
|
78
|
+
* - Use `getRequestEvent()` in SolidStart to scope locale per request
|
|
79
|
+
* - Or create a separate `createServerI18n()` per request context
|
|
80
|
+
* - Consider using AsyncLocalStorage for true per-request isolation (future)
|
|
81
|
+
*
|
|
75
82
|
* @example
|
|
76
83
|
* ```ts
|
|
77
84
|
* // lib/i18n.server.ts
|
|
@@ -90,6 +97,7 @@ export function createServerI18n(config: ServerI18nConfig): ServerI18n {
|
|
|
90
97
|
|
|
91
98
|
function setLocale(locale: string): void {
|
|
92
99
|
currentLocale = locale
|
|
100
|
+
cachedInstance = null
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
async function loadLocaleMessages(locale: string): Promise<Messages> {
|