@fluenti/react 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,7 @@ Fluenti compiles your translations at build time so your production bundle ships
29
29
 
30
30
  ```bash
31
31
  pnpm add @fluenti/core @fluenti/react
32
- pnpm add -D @fluenti/cli @fluenti/vite-plugin
32
+ pnpm add -D @fluenti/cli
33
33
  ```
34
34
 
35
35
  ### 2. Configure Vite
@@ -37,11 +37,11 @@ pnpm add -D @fluenti/cli @fluenti/vite-plugin
37
37
  ```ts
38
38
  // vite.config.ts
39
39
  import react from '@vitejs/plugin-react'
40
- import fluenti from '@fluenti/vite-plugin'
40
+ import fluentiReact from '@fluenti/react/vite-plugin'
41
41
 
42
42
  export default {
43
43
  plugins: [
44
- fluenti({ framework: 'react' }),
44
+ fluentiReact(),
45
45
  react(),
46
46
  ],
47
47
  }
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./icu-rich-XY1SdM5K.cjs`);let t=require(`react`),n=require(`@fluenti/core`),r=require(`react/jsx-runtime`);var i=(0,t.createContext)(null),a=Symbol.for(`fluenti.runtime.react`);function o(){let e=globalThis[a];return typeof e==`object`&&e?e:null}function s({locale:e,fallbackLocale:a,messages:s,loadMessages:c,fallbackChain:l,dateFormats:u,numberFormats:d,missing:f,children:p}){let[m,h]=(0,t.useState)(e),[g,_]=(0,t.useState)(!1),[v,y]=(0,t.useState)(s??{}),[b,x]=(0,t.useState)(s?Object.keys(s):[]),S=(0,t.useRef)(v);S.current=v;let C=(0,t.useRef)(0),w=(0,t.useMemo)(()=>{let e={locale:m,messages:v};return a!==void 0&&(e.fallbackLocale=a),l!==void 0&&(e.fallbackChain=l),u!==void 0&&(e.dateFormats=u),d!==void 0&&(e.numberFormats=d),f!==void 0&&(e.missing=f),(0,n.createFluent)(e)},[m,v,a,l,u,d,f]);(0,t.useEffect)(()=>{e!==m&&T(e)},[e]);let T=(0,t.useCallback)(async e=>{let t=++C.current;if(S.current[e]&&!c){h(e);return}let n=c?o():null;if(S.current[e]){n?.__switchLocale&&await n.__switchLocale(e),h(e);return}if(!c){console.warn(`[fluenti] No messages for locale "${e}" and no loadMessages function provided`);return}_(!0);try{let r=await c(e);if(t!==C.current)return;let i=typeof r==`object`&&r&&`default`in r?r.default:r;y(t=>({...t,[e]:i})),x(t=>[...new Set([...t,e])]),n?.__switchLocale&&await n.__switchLocale(e),h(e)}catch(n){t===C.current&&console.error(`[fluenti] Failed to load locale "${e}"`,n)}finally{t===C.current&&_(!1)}},[c]),E=(0,t.useCallback)(async e=>{let t=o();if(!(S.current[e]||!c))try{let n=await c(e),r=typeof n==`object`&&n&&`default`in n?n.default:n;y(t=>({...t,[e]:r})),x(t=>[...new Set([...t,e])]),t?.__preloadLocale&&await t.__preloadLocale(e)}catch{}},[c]),D=(0,t.useMemo)(()=>({i18n:w,t:w.t.bind(w),d:w.d.bind(w),n:w.n.bind(w),format:w.format.bind(w),loadMessages:w.loadMessages.bind(w),getLocales:w.getLocales.bind(w),locale:m,setLocale:T,isLoading:g,loadedLocales:b,preloadLocale:E}),[w,m,T,g,b,E]);return(0,r.jsx)(i.Provider,{value:D,children:p})}function c(){let e=(0,t.useContext)(i);if(!e)throw Error(`[fluenti] useI18n() must be used within an <I18nProvider>. Wrap your app with <I18nProvider> to provide i18n context.`);return e}var l=((...e)=>{throw Error("[fluenti] `t` imported from '@fluenti/react' 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(...).")}),u=(0,t.memo)(function({children:a,id:o,context:s,comment:c,render:l,__id:u,__message:d,__components:f}){let p=(0,t.useContext)(i);if(!p)throw Error(`[fluenti] <Trans> must be used within an <I18nProvider>`);let m=d!==void 0,{message:h,components:g}=(0,t.useMemo)(()=>m?{message:d,components:f??[]}:e.s(a),[m,d,f,a]),_=(0,t.useMemo)(()=>o??u??(0,n.hashMessage)(h,s),[o,u,h,s]),v=e.c(p.i18n.t({id:_,message:h,...s===void 0?{}:{context:s},...c===void 0?{}:{comment:c}}),g);return l?l(v):(0,r.jsx)(r.Fragment,{children:v})}),d=(0,t.memo)(function({value:a,id:o,context:s,comment:c,zero:l,one:u,two:d,few:f,many:p,other:m,offset:h}){let g=(0,t.useContext)(i);if(!g)throw Error(`[fluenti] <Plural> must be used within an <I18nProvider>`);let{messages:_,components:v}=e.a(e.o,{zero:l,one:u,two:d,few:f,many:p,other:m}),y=e.t({..._.zero!==void 0&&{zero:_.zero},..._.one!==void 0&&{one:_.one},..._.two!==void 0&&{two:_.two},..._.few!==void 0&&{few:_.few},..._.many!==void 0&&{many:_.many},other:_.other??``},h);return(0,r.jsx)(r.Fragment,{children:e.i({id:o??(s===void 0?y:(0,n.hashMessage)(y,s)),message:y,...s===void 0?{}:{context:s},...c===void 0?{}:{comment:c}},{count:a},(e,t)=>g.i18n.t(e,t),v)})}),f=(0,t.memo)(function(a){let o=(0,t.useContext)(i);if(!o)throw Error(`[fluenti] <Select> must be used within an <I18nProvider>`);let{value:s,id:c,context:l,comment:u,other:d,options:f,...p}=a,m=f===void 0?{...Object.fromEntries(Object.entries(p).filter(([e])=>![`value`,`id`,`context`,`comment`,`options`,`other`].includes(e))),other:d}:{...f,other:d},h=[...Object.keys(m).filter(e=>e!==`other`),`other`],{messages:g,components:_}=e.a(h,m),v=e.r(Object.fromEntries([...h].map(e=>[e,g[e]??``]))),y=e.n(v.forms);return(0,r.jsx)(r.Fragment,{children:e.i({id:c??(l===void 0?y:(0,n.hashMessage)(y,l)),message:y,...l===void 0?{}:{context:l},...u===void 0?{}:{comment:u}},{value:v.valueMap[s]??`other`},(e,t)=>o.i18n.t(e,t),_)})}),p=(0,t.memo)(function({value:e,style:n}){let a=(0,t.useContext)(i);if(!a)throw Error(`[fluenti] <DateTime> must be used within an <I18nProvider>`);return(0,r.jsx)(r.Fragment,{children:a.i18n.d(e,n)})}),m=(0,t.memo)(function({value:e,style:n}){let a=(0,t.useContext)(i);if(!a)throw Error(`[fluenti] <Number> must be used within an <I18nProvider>`);return(0,r.jsx)(r.Fragment,{children:a.i18n.n(e,n)})});exports.DateTime=p,exports.I18nContext=i,exports.I18nProvider=s,exports.NumberFormat=m,exports.Plural=d,exports.Select=f,exports.Trans=u,Object.defineProperty(exports,`msg`,{enumerable:!0,get:function(){return n.msg}}),exports.t=l,exports.useI18n=c;
2
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./icu-rich-XY1SdM5K.cjs`);let t=require(`react`),n=require(`@fluenti/core`),r=require(`react/jsx-runtime`);var i=(0,t.createContext)(null);function a(e){let t={};for(let[n,r]of Object.entries(e))t[n]=typeof r==`object`&&r&&`default`in r?r.default:r;return t}var o=Symbol.for(`fluenti.runtime.react`);function s(){let e=globalThis[o];return typeof e==`object`&&e?e:null}function c({locale:e,fallbackLocale:o,messages:c,loadMessages:l,fallbackChain:u,dateFormats:d,numberFormats:f,missing:p,children:m}){let[h,g]=(0,t.useState)(e),[_,v]=(0,t.useState)(!1),[y,b]=(0,t.useState)(c?a(c):{}),[x,S]=(0,t.useState)(c?Object.keys(c):[]),C=(0,t.useRef)(y);C.current=y;let w=(0,t.useRef)(0),T=(0,t.useMemo)(()=>{let e={locale:h,messages:y};return o!==void 0&&(e.fallbackLocale=o),u!==void 0&&(e.fallbackChain=u),d!==void 0&&(e.dateFormats=d),f!==void 0&&(e.numberFormats=f),p!==void 0&&(e.missing=p),(0,n.createFluent)(e)},[h,y,o,u,d,f,p]);(0,t.useEffect)(()=>{e!==h&&E(e)},[e]);let E=(0,t.useCallback)(async e=>{let t=++w.current;if(C.current[e]&&!l){g(e);return}let n=l?s():null;if(C.current[e]){n?.__switchLocale&&await n.__switchLocale(e),g(e);return}if(!l){console.warn(`[fluenti] No messages for locale "${e}" and no loadMessages function provided`);return}v(!0);try{let r=await l(e);if(t!==w.current)return;let i=typeof r==`object`&&r&&`default`in r?r.default:r;b(t=>({...t,[e]:i})),S(t=>[...new Set([...t,e])]),n?.__switchLocale&&await n.__switchLocale(e),g(e)}catch(n){t===w.current&&console.error(`[fluenti] Failed to load locale "${e}"`,n)}finally{t===w.current&&v(!1)}},[l]),D=(0,t.useCallback)(async e=>{let t=s();if(!(C.current[e]||!l))try{let n=await l(e),r=typeof n==`object`&&n&&`default`in n?n.default:n;b(t=>({...t,[e]:r})),S(t=>[...new Set([...t,e])]),t?.__preloadLocale&&await t.__preloadLocale(e)}catch{}},[l]),O=(0,t.useMemo)(()=>({i18n:T,t:T.t.bind(T),d:T.d.bind(T),n:T.n.bind(T),format:T.format.bind(T),loadMessages:T.loadMessages.bind(T),getLocales:T.getLocales.bind(T),locale:h,setLocale:E,isLoading:_,loadedLocales:x,preloadLocale:D}),[T,h,E,_,x,D]);return(0,r.jsx)(i.Provider,{value:O,children:m})}function l(){let e=(0,t.useContext)(i);if(!e)throw Error(`[fluenti] useI18n() must be used within an <I18nProvider>. Wrap your app with <I18nProvider> to provide i18n context.`);return e}var u=((...e)=>{throw Error("[fluenti] `t` imported from '@fluenti/react' 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(...).")}),d=(0,t.memo)(function({children:a,id:o,context:s,comment:c,render:l,__id:u,__message:d,__components:f}){let p=(0,t.useContext)(i);if(!p)throw Error(`[fluenti] <Trans> must be used within an <I18nProvider>`);let m=d!==void 0,{message:h,components:g}=(0,t.useMemo)(()=>m?{message:d,components:f??[]}:e.s(a),[m,d,f,a]),_=(0,t.useMemo)(()=>o??u??(0,n.hashMessage)(h,s),[o,u,h,s]),v=e.c(p.i18n.t({id:_,message:h,...s===void 0?{}:{context:s},...c===void 0?{}:{comment:c}}),g);return l?l(v):(0,r.jsx)(r.Fragment,{children:v})}),f=(0,t.memo)(function({value:a,id:o,context:s,comment:c,zero:l,one:u,two:d,few:f,many:p,other:m,offset:h}){let g=(0,t.useContext)(i);if(!g)throw Error(`[fluenti] <Plural> must be used within an <I18nProvider>`);let{messages:_,components:v}=e.a(e.o,{zero:l,one:u,two:d,few:f,many:p,other:m}),y=e.t({..._.zero!==void 0&&{zero:_.zero},..._.one!==void 0&&{one:_.one},..._.two!==void 0&&{two:_.two},..._.few!==void 0&&{few:_.few},..._.many!==void 0&&{many:_.many},other:_.other??``},h);return(0,r.jsx)(r.Fragment,{children:e.i({id:o??(s===void 0?y:(0,n.hashMessage)(y,s)),message:y,...s===void 0?{}:{context:s},...c===void 0?{}:{comment:c}},{count:a},(e,t)=>g.i18n.t(e,t),v)})}),p=(0,t.memo)(function(a){let o=(0,t.useContext)(i);if(!o)throw Error(`[fluenti] <Select> must be used within an <I18nProvider>`);let{value:s,id:c,context:l,comment:u,other:d,options:f,...p}=a,m=f===void 0?{...Object.fromEntries(Object.entries(p).filter(([e])=>![`value`,`id`,`context`,`comment`,`options`,`other`].includes(e))),other:d}:{...f,other:d},h=[...Object.keys(m).filter(e=>e!==`other`),`other`],{messages:g,components:_}=e.a(h,m),v=e.r(Object.fromEntries([...h].map(e=>[e,g[e]??``]))),y=e.n(v.forms);return(0,r.jsx)(r.Fragment,{children:e.i({id:c??(l===void 0?y:(0,n.hashMessage)(y,l)),message:y,...l===void 0?{}:{context:l},...u===void 0?{}:{comment:u}},{value:v.valueMap[s]??`other`},(e,t)=>o.i18n.t(e,t),_)})}),m=(0,t.memo)(function({value:e,style:n}){let a=(0,t.useContext)(i);if(!a)throw Error(`[fluenti] <DateTime> must be used within an <I18nProvider>`);return(0,r.jsx)(r.Fragment,{children:a.i18n.d(e,n)})}),h=(0,t.memo)(function({value:e,style:n}){let a=(0,t.useContext)(i);if(!a)throw Error(`[fluenti] <Number> must be used within an <I18nProvider>`);return(0,r.jsx)(r.Fragment,{children:a.i18n.n(e,n)})});exports.DateTime=m,exports.I18nContext=i,exports.I18nProvider=c,exports.NumberFormat=h,exports.Plural=f,exports.Select=p,exports.Trans=d,Object.defineProperty(exports,`msg`,{enumerable:!0,get:function(){return n.msg}}),exports.t=u,exports.useI18n=l;
3
3
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useI18n.ts","../src/compile-time-t.ts","../src/components/Trans.tsx","../src/components/Plural.tsx","../src/components/Select.tsx","../src/components/DateTime.tsx","../src/components/Number.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { I18nContextValue } from './types'\n\nexport const I18nContext = createContext<I18nContextValue | null>(null)\n","import { useState, useCallback, useEffect, useMemo, useRef } from 'react'\nimport { createFluent } from '@fluenti/core'\nimport type { Messages } from '@fluenti/core'\nimport { I18nContext } from './context'\nimport type { I18nProviderProps } from './types'\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.react')\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\nexport function I18nProvider({\n locale,\n fallbackLocale,\n messages,\n loadMessages,\n fallbackChain,\n dateFormats,\n numberFormats,\n missing,\n children,\n}: I18nProviderProps) {\n const [currentLocale, setCurrentLocale] = useState(locale)\n const [isLoading, setIsLoading] = useState(false)\n const [loadedMessages, setLoadedMessages] = useState<Record<string, Messages>>(\n messages ?? {},\n )\n const [loadedLocales, setLoadedLocales] = useState<string[]>(\n messages ? Object.keys(messages) : [],\n )\n\n // Use ref to avoid stale closures in callbacks\n const loadedMessagesRef = useRef(loadedMessages)\n loadedMessagesRef.current = loadedMessages\n\n // Guard against out-of-order async locale loads (race condition protection)\n const localeRequestRef = useRef(0)\n\n const i18n = useMemo(() => {\n const config: Parameters<typeof createFluent>[0] = {\n locale: currentLocale,\n messages: loadedMessages,\n }\n if (fallbackLocale !== undefined) config.fallbackLocale = fallbackLocale\n if (fallbackChain !== undefined) config.fallbackChain = fallbackChain\n if (dateFormats !== undefined) config.dateFormats = dateFormats\n if (numberFormats !== undefined) config.numberFormats = numberFormats\n if (missing !== undefined) config.missing = missing\n return createFluent(config)\n }, [currentLocale, loadedMessages, fallbackLocale, fallbackChain, dateFormats, numberFormats, missing])\n\n // Sync external locale prop changes\n useEffect(() => {\n if (locale !== currentLocale) {\n void handleSetLocale(locale)\n }\n // Intentionally only depend on `locale` — we want to sync when the\n // external prop changes, not when internal state (`currentLocale`,\n // `handleSetLocale`) updates, which would cause infinite re-renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [locale])\n\n const handleSetLocale = useCallback(\n async (newLocale: string) => {\n const requestId = ++localeRequestRef.current\n\n if (loadedMessagesRef.current[newLocale] && !loadMessages) {\n setCurrentLocale(newLocale)\n return\n }\n\n const splitRuntime = loadMessages ? getSplitRuntimeModule() : null\n\n if (loadedMessagesRef.current[newLocale]) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n return\n }\n\n if (!loadMessages) {\n console.warn(\n `[fluenti] No messages for locale \"${newLocale}\" and no loadMessages function provided`,\n )\n return\n }\n\n setIsLoading(true)\n try {\n const msgs = await loadMessages(newLocale)\n\n // A newer request has superseded this one — discard stale result\n if (requestId !== localeRequestRef.current) return\n\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [newLocale]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, newLocale])])\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n } catch (err) {\n // Only log if this request is still the latest\n if (requestId === localeRequestRef.current) {\n console.error(`[fluenti] Failed to load locale \"${newLocale}\"`, err)\n }\n } finally {\n if (requestId === localeRequestRef.current) {\n setIsLoading(false)\n }\n }\n },\n [loadMessages],\n )\n\n const preloadLocale = useCallback(\n async (loc: string) => {\n const splitRuntime = getSplitRuntimeModule()\n if (loadedMessagesRef.current[loc] || !loadMessages) return\n try {\n const msgs = await loadMessages(loc)\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [loc]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, loc])])\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n } catch {\n // Silent fail for preload\n }\n },\n [loadMessages],\n )\n\n const ctx = useMemo(\n () => ({\n i18n,\n t: i18n.t.bind(i18n),\n d: i18n.d.bind(i18n),\n n: i18n.n.bind(i18n),\n format: i18n.format.bind(i18n),\n loadMessages: i18n.loadMessages.bind(i18n),\n getLocales: i18n.getLocales.bind(i18n),\n locale: currentLocale,\n setLocale: handleSetLocale,\n isLoading,\n loadedLocales,\n preloadLocale,\n }),\n [i18n, currentLocale, handleSetLocale, isLoading, loadedLocales, preloadLocale],\n )\n\n return <I18nContext.Provider value={ctx}>{children}</I18nContext.Provider>\n}\n","import { useContext } from 'react'\nimport { I18nContext } from '../context'\nimport type { I18nContextValue } from '../types'\n\n/**\n * Primary hook for accessing i18n functions.\n *\n * Returns locale, setLocale, isLoading, loadedLocales, preloadLocale,\n * and the underlying i18n instance with t(), d(), n() methods.\n *\n * @throws If used outside of `<I18nProvider>`\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error(\n '[fluenti] useI18n() must be used within an <I18nProvider>. ' +\n 'Wrap your app with <I18nProvider> to provide i18n context.',\n )\n }\n return ctx\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/react' 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 {\n memo,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { I18nContext } from '../context'\nimport { hashMessage, extractMessage, reconstruct } from './trans-core'\n\nexport interface TransProps {\n /** Source text with embedded components */\n children: ReactNode\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Context comment for translators */\n comment?: string\n /** Custom render wrapper */\n render?: (translation: ReactNode) => ReactNode\n /** @internal Pre-computed message ID from build plugin */\n __id?: string\n /** @internal Pre-computed ICU message from build plugin */\n __message?: string\n /** @internal Pre-computed component list from build plugin */\n __components?: ReactElement[]\n}\n\n/**\n * `<Trans>` component for rich-text translations containing nested React elements.\n *\n * @example\n * ```tsx\n * <Trans>Read the <a href=\"/docs\">documentation</a> for more info.</Trans>\n * ```\n */\nexport const Trans = memo(function Trans({\n children,\n id,\n context,\n comment,\n render,\n __id,\n __message,\n __components,\n}: TransProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Trans> must be used within an <I18nProvider>')\n }\n\n // Fast path: build plugin pre-computed message and components\n const hasPrecomputed = __message !== undefined\n\n const { message, components } = useMemo(\n () => hasPrecomputed\n ? { message: __message!, components: __components ?? [] }\n : extractMessage(children),\n [hasPrecomputed, __message, __components, children],\n )\n const messageId = useMemo(\n () => id ?? __id ?? hashMessage(message, context),\n [id, __id, message, context],\n )\n\n const translated = ctx.i18n.t(\n {\n id: messageId,\n message,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n },\n )\n\n const result = reconstruct(translated, components)\n return render ? render(result) : <>{result}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { PLURAL_CATEGORIES, type PluralCategory } from './plural-core'\nimport { buildICUPluralMessage, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface PluralProps {\n /** The count value */\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 /** Text for zero (if language supports) */\n zero?: ReactNode\n /** Singular form. `#` replaced with value */\n one?: ReactNode\n /** Dual form (Arabic, etc.) */\n two?: ReactNode\n /** Few form (Slavic languages, etc.) */\n few?: ReactNode\n /** Many form */\n many?: ReactNode\n /** Default plural form */\n other: ReactNode\n /** Offset from value before selecting form */\n offset?: number\n}\n\n/**\n * `<Plural>` — ICU plural handling as a component.\n *\n * @example\n * ```tsx\n * <Plural value={count} zero=\"No messages\" one=\"# message\" other=\"# messages\" />\n * ```\n */\nexport const Plural = memo(function Plural({\n value,\n id,\n context,\n comment,\n zero,\n one,\n two,\n few,\n many,\n other,\n offset,\n}: PluralProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Plural> must be used within an <I18nProvider>')\n }\n\n const forms: Record<PluralCategory, ReactNode | undefined> = {\n zero,\n one,\n two,\n few,\n many,\n other,\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, forms)\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 offset,\n )\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(descriptor, { count: value }, (desc, values) => ctx.i18n.t(desc, values), components)}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { buildICUSelectMessage, normalizeSelectForms, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface SelectProps {\n /** The selector value */\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 /** Default case */\n other: ReactNode\n /** Type-safe named options. Takes precedence over direct case props. */\n options?: Record<string, ReactNode>\n /** Named cases — any string key maps to a ReactNode */\n [key: string]: ReactNode | Record<string, ReactNode> | undefined\n}\n\n/**\n * `<Select>` — ICU select for gender, role, or other categorical values.\n *\n * @example\n * ```tsx\n * <Select\n * value={gender}\n * male=\"He liked your post\"\n * female=\"She liked your post\"\n * other=\"They liked your post\"\n * />\n * ```\n */\nexport const Select = memo(function Select(props: SelectProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Select> must be used within an <I18nProvider>')\n }\n\n const { value, id, context, comment, other, options, ...cases } = props\n const forms: Record<string, ReactNode | undefined> = options === undefined\n ? {\n ...Object.fromEntries(\n Object.entries(cases).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other'].includes(key)),\n ),\n other,\n }\n : {\n ...options,\n 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(\n [...orderedKeys].map((key) => [key, messages[key] ?? '']),\n ),\n )\n const icuMessage = buildICUSelectMessage(normalized.forms)\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(\n descriptor,\n { value: normalized.valueMap[value] ?? 'other' },\n (desc, values) => ctx.i18n.t(desc, values),\n components,\n )}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\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>` — formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport const DateTime = memo(function DateTime({ value, style }: DateTimeProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <DateTime> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.d(value, style)}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<Number>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <Number value={1234.56} style=\"currency\" />\n * ```\n */\nexport const NumberFormat = memo(function NumberFormat({ value, style }: NumberProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Number> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.n(value, style)}</>\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useI18n.ts","../src/compile-time-t.ts","../src/components/Trans.tsx","../src/components/Plural.tsx","../src/components/Select.tsx","../src/components/DateTime.tsx","../src/components/Number.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { I18nContextValue } from './types'\n\nexport const I18nContext = createContext<I18nContextValue | null>(null)\n","import { useState, useCallback, useEffect, useMemo, useRef } from 'react'\nimport { createFluent } from '@fluenti/core'\nimport type { Messages } from '@fluenti/core'\nimport { I18nContext } from './context'\nimport type { I18nProviderProps } from './types'\n\ninterface SplitRuntimeModule {\n __switchLocale?: (locale: string) => Promise<void>\n __preloadLocale?: (locale: string) => Promise<void>\n}\n\nfunction unwrapMessages(allMessages: Record<string, unknown>): Record<string, Messages> {\n const result: Record<string, Messages> = {}\n for (const [locale, msgs] of Object.entries(allMessages)) {\n result[locale] = typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : msgs as Messages\n }\n return result\n}\n\nconst SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.react')\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\nexport function I18nProvider({\n locale,\n fallbackLocale,\n messages,\n loadMessages,\n fallbackChain,\n dateFormats,\n numberFormats,\n missing,\n children,\n}: I18nProviderProps) {\n const [currentLocale, setCurrentLocale] = useState(locale)\n const [isLoading, setIsLoading] = useState(false)\n const [loadedMessages, setLoadedMessages] = useState<Record<string, Messages>>(\n messages ? unwrapMessages(messages) : {},\n )\n const [loadedLocales, setLoadedLocales] = useState<string[]>(\n messages ? Object.keys(messages) : [],\n )\n\n // Use ref to avoid stale closures in callbacks\n const loadedMessagesRef = useRef(loadedMessages)\n loadedMessagesRef.current = loadedMessages\n\n // Guard against out-of-order async locale loads (race condition protection)\n const localeRequestRef = useRef(0)\n\n const i18n = useMemo(() => {\n const config: Parameters<typeof createFluent>[0] = {\n locale: currentLocale,\n messages: loadedMessages,\n }\n if (fallbackLocale !== undefined) config.fallbackLocale = fallbackLocale\n if (fallbackChain !== undefined) config.fallbackChain = fallbackChain\n if (dateFormats !== undefined) config.dateFormats = dateFormats\n if (numberFormats !== undefined) config.numberFormats = numberFormats\n if (missing !== undefined) config.missing = missing\n return createFluent(config)\n }, [currentLocale, loadedMessages, fallbackLocale, fallbackChain, dateFormats, numberFormats, missing])\n\n // Sync external locale prop changes\n useEffect(() => {\n if (locale !== currentLocale) {\n void handleSetLocale(locale)\n }\n // Intentionally only depend on `locale` — we want to sync when the\n // external prop changes, not when internal state (`currentLocale`,\n // `handleSetLocale`) updates, which would cause infinite re-renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [locale])\n\n const handleSetLocale = useCallback(\n async (newLocale: string) => {\n const requestId = ++localeRequestRef.current\n\n if (loadedMessagesRef.current[newLocale] && !loadMessages) {\n setCurrentLocale(newLocale)\n return\n }\n\n const splitRuntime = loadMessages ? getSplitRuntimeModule() : null\n\n if (loadedMessagesRef.current[newLocale]) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n return\n }\n\n if (!loadMessages) {\n console.warn(\n `[fluenti] No messages for locale \"${newLocale}\" and no loadMessages function provided`,\n )\n return\n }\n\n setIsLoading(true)\n try {\n const msgs = await loadMessages(newLocale)\n\n // A newer request has superseded this one — discard stale result\n if (requestId !== localeRequestRef.current) return\n\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [newLocale]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, newLocale])])\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n } catch (err) {\n // Only log if this request is still the latest\n if (requestId === localeRequestRef.current) {\n console.error(`[fluenti] Failed to load locale \"${newLocale}\"`, err)\n }\n } finally {\n if (requestId === localeRequestRef.current) {\n setIsLoading(false)\n }\n }\n },\n [loadMessages],\n )\n\n const preloadLocale = useCallback(\n async (loc: string) => {\n const splitRuntime = getSplitRuntimeModule()\n if (loadedMessagesRef.current[loc] || !loadMessages) return\n try {\n const msgs = await loadMessages(loc)\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [loc]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, loc])])\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n } catch {\n // Silent fail for preload\n }\n },\n [loadMessages],\n )\n\n const ctx = useMemo(\n () => ({\n i18n,\n t: i18n.t.bind(i18n),\n d: i18n.d.bind(i18n),\n n: i18n.n.bind(i18n),\n format: i18n.format.bind(i18n),\n loadMessages: i18n.loadMessages.bind(i18n),\n getLocales: i18n.getLocales.bind(i18n),\n locale: currentLocale,\n setLocale: handleSetLocale,\n isLoading,\n loadedLocales,\n preloadLocale,\n }),\n [i18n, currentLocale, handleSetLocale, isLoading, loadedLocales, preloadLocale],\n )\n\n return <I18nContext.Provider value={ctx}>{children}</I18nContext.Provider>\n}\n","import { useContext } from 'react'\nimport { I18nContext } from '../context'\nimport type { I18nContextValue } from '../types'\n\n/**\n * Primary hook for accessing i18n functions.\n *\n * Returns locale, setLocale, isLoading, loadedLocales, preloadLocale,\n * and the underlying i18n instance with t(), d(), n() methods.\n *\n * @throws If used outside of `<I18nProvider>`\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error(\n '[fluenti] useI18n() must be used within an <I18nProvider>. ' +\n 'Wrap your app with <I18nProvider> to provide i18n context.',\n )\n }\n return ctx\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/react' 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 {\n memo,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { I18nContext } from '../context'\nimport { hashMessage, extractMessage, reconstruct } from './trans-core'\n\nexport interface TransProps {\n /** Source text with embedded components */\n children: ReactNode\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Context comment for translators */\n comment?: string\n /** Custom render wrapper */\n render?: (translation: ReactNode) => ReactNode\n /** @internal Pre-computed message ID from build plugin */\n __id?: string\n /** @internal Pre-computed ICU message from build plugin */\n __message?: string\n /** @internal Pre-computed component list from build plugin */\n __components?: ReactElement[]\n}\n\n/**\n * `<Trans>` component for rich-text translations containing nested React elements.\n *\n * @example\n * ```tsx\n * <Trans>Read the <a href=\"/docs\">documentation</a> for more info.</Trans>\n * ```\n */\nexport const Trans = memo(function Trans({\n children,\n id,\n context,\n comment,\n render,\n __id,\n __message,\n __components,\n}: TransProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Trans> must be used within an <I18nProvider>')\n }\n\n // Fast path: build plugin pre-computed message and components\n const hasPrecomputed = __message !== undefined\n\n const { message, components } = useMemo(\n () => hasPrecomputed\n ? { message: __message!, components: __components ?? [] }\n : extractMessage(children),\n [hasPrecomputed, __message, __components, children],\n )\n const messageId = useMemo(\n () => id ?? __id ?? hashMessage(message, context),\n [id, __id, message, context],\n )\n\n const translated = ctx.i18n.t(\n {\n id: messageId,\n message,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n },\n )\n\n const result = reconstruct(translated, components)\n return render ? render(result) : <>{result}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { PLURAL_CATEGORIES, type PluralCategory } from './plural-core'\nimport { buildICUPluralMessage, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface PluralProps {\n /** The count value */\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 /** Text for zero (if language supports) */\n zero?: ReactNode\n /** Singular form. `#` replaced with value */\n one?: ReactNode\n /** Dual form (Arabic, etc.) */\n two?: ReactNode\n /** Few form (Slavic languages, etc.) */\n few?: ReactNode\n /** Many form */\n many?: ReactNode\n /** Default plural form */\n other: ReactNode\n /** Offset from value before selecting form */\n offset?: number\n}\n\n/**\n * `<Plural>` — ICU plural handling as a component.\n *\n * @example\n * ```tsx\n * <Plural value={count} zero=\"No messages\" one=\"# message\" other=\"# messages\" />\n * ```\n */\nexport const Plural = memo(function Plural({\n value,\n id,\n context,\n comment,\n zero,\n one,\n two,\n few,\n many,\n other,\n offset,\n}: PluralProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Plural> must be used within an <I18nProvider>')\n }\n\n const forms: Record<PluralCategory, ReactNode | undefined> = {\n zero,\n one,\n two,\n few,\n many,\n other,\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, forms)\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 offset,\n )\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(descriptor, { count: value }, (desc, values) => ctx.i18n.t(desc, values), components)}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { buildICUSelectMessage, normalizeSelectForms, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface SelectProps {\n /** The selector value */\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 /** Default case */\n other: ReactNode\n /** Type-safe named options. Takes precedence over direct case props. */\n options?: Record<string, ReactNode>\n /** Named cases — any string key maps to a ReactNode */\n [key: string]: ReactNode | Record<string, ReactNode> | undefined\n}\n\n/**\n * `<Select>` — ICU select for gender, role, or other categorical values.\n *\n * @example\n * ```tsx\n * <Select\n * value={gender}\n * male=\"He liked your post\"\n * female=\"She liked your post\"\n * other=\"They liked your post\"\n * />\n * ```\n */\nexport const Select = memo(function Select(props: SelectProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Select> must be used within an <I18nProvider>')\n }\n\n const { value, id, context, comment, other, options, ...cases } = props\n const forms: Record<string, ReactNode | undefined> = options === undefined\n ? {\n ...Object.fromEntries(\n Object.entries(cases).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other'].includes(key)),\n ),\n other,\n }\n : {\n ...options,\n 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(\n [...orderedKeys].map((key) => [key, messages[key] ?? '']),\n ),\n )\n const icuMessage = buildICUSelectMessage(normalized.forms)\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(\n descriptor,\n { value: normalized.valueMap[value] ?? 'other' },\n (desc, values) => ctx.i18n.t(desc, values),\n components,\n )}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\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>` — formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport const DateTime = memo(function DateTime({ value, style }: DateTimeProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <DateTime> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.d(value, style)}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<Number>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <Number value={1234.56} style=\"currency\" />\n * ```\n */\nexport const NumberFormat = memo(function NumberFormat({ value, style }: NumberProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Number> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.n(value, style)}</>\n})\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -5,15 +5,23 @@ import { createContext as l, memo as u, useCallback as d, useContext as f, useEf
5
5
  import { createFluent as _, hashMessage as v, msg as y } from "@fluenti/core";
6
6
  import { Fragment as b, jsx as x } from "react/jsx-runtime";
7
7
  //#region src/context.ts
8
- var S = l(null), C = Symbol.for("fluenti.runtime.react");
9
- function w() {
10
- let e = globalThis[C];
8
+ var S = l(null);
9
+ //#endregion
10
+ //#region src/provider.tsx
11
+ function C(e) {
12
+ let t = {};
13
+ for (let [n, r] of Object.entries(e)) t[n] = typeof r == "object" && r && "default" in r ? r.default : r;
14
+ return t;
15
+ }
16
+ var w = Symbol.for("fluenti.runtime.react");
17
+ function T() {
18
+ let e = globalThis[w];
11
19
  return typeof e == "object" && e ? e : null;
12
20
  }
13
- function T({ locale: e, fallbackLocale: t, messages: n, loadMessages: r, fallbackChain: i, dateFormats: a, numberFormats: o, missing: s, children: c }) {
14
- let [l, u] = g(e), [f, v] = g(!1), [y, b] = g(n ?? {}), [C, T] = g(n ? Object.keys(n) : []), E = h(y);
15
- E.current = y;
16
- let D = h(0), O = m(() => {
21
+ function E({ locale: e, fallbackLocale: t, messages: n, loadMessages: r, fallbackChain: i, dateFormats: a, numberFormats: o, missing: s, children: c }) {
22
+ let [l, u] = g(e), [f, v] = g(!1), [y, b] = g(n ? C(n) : {}), [w, E] = g(n ? Object.keys(n) : []), D = h(y);
23
+ D.current = y;
24
+ let O = h(0), k = m(() => {
17
25
  let e = {
18
26
  locale: l,
19
27
  messages: y
@@ -29,16 +37,16 @@ function T({ locale: e, fallbackLocale: t, messages: n, loadMessages: r, fallbac
29
37
  s
30
38
  ]);
31
39
  p(() => {
32
- e !== l && k(e);
40
+ e !== l && A(e);
33
41
  }, [e]);
34
- let k = d(async (e) => {
35
- let t = ++D.current;
36
- if (E.current[e] && !r) {
42
+ let A = d(async (e) => {
43
+ let t = ++O.current;
44
+ if (D.current[e] && !r) {
37
45
  u(e);
38
46
  return;
39
47
  }
40
- let n = r ? w() : null;
41
- if (E.current[e]) {
48
+ let n = r ? T() : null;
49
+ if (D.current[e]) {
42
50
  n?.__switchLocale && await n.__switchLocale(e), u(e);
43
51
  return;
44
52
  }
@@ -49,64 +57,64 @@ function T({ locale: e, fallbackLocale: t, messages: n, loadMessages: r, fallbac
49
57
  v(!0);
50
58
  try {
51
59
  let i = await r(e);
52
- if (t !== D.current) return;
60
+ if (t !== O.current) return;
53
61
  let a = typeof i == "object" && i && "default" in i ? i.default : i;
54
62
  b((t) => ({
55
63
  ...t,
56
64
  [e]: a
57
- })), T((t) => [...new Set([...t, e])]), n?.__switchLocale && await n.__switchLocale(e), u(e);
65
+ })), E((t) => [...new Set([...t, e])]), n?.__switchLocale && await n.__switchLocale(e), u(e);
58
66
  } catch (n) {
59
- t === D.current && console.error(`[fluenti] Failed to load locale "${e}"`, n);
67
+ t === O.current && console.error(`[fluenti] Failed to load locale "${e}"`, n);
60
68
  } finally {
61
- t === D.current && v(!1);
69
+ t === O.current && v(!1);
62
70
  }
63
- }, [r]), A = d(async (e) => {
64
- let t = w();
65
- if (!(E.current[e] || !r)) try {
71
+ }, [r]), j = d(async (e) => {
72
+ let t = T();
73
+ if (!(D.current[e] || !r)) try {
66
74
  let n = await r(e), i = typeof n == "object" && n && "default" in n ? n.default : n;
67
75
  b((t) => ({
68
76
  ...t,
69
77
  [e]: i
70
- })), T((t) => [...new Set([...t, e])]), t?.__preloadLocale && await t.__preloadLocale(e);
78
+ })), E((t) => [...new Set([...t, e])]), t?.__preloadLocale && await t.__preloadLocale(e);
71
79
  } catch {}
72
- }, [r]), j = m(() => ({
73
- i18n: O,
74
- t: O.t.bind(O),
75
- d: O.d.bind(O),
76
- n: O.n.bind(O),
77
- format: O.format.bind(O),
78
- loadMessages: O.loadMessages.bind(O),
79
- getLocales: O.getLocales.bind(O),
80
+ }, [r]), M = m(() => ({
81
+ i18n: k,
82
+ t: k.t.bind(k),
83
+ d: k.d.bind(k),
84
+ n: k.n.bind(k),
85
+ format: k.format.bind(k),
86
+ loadMessages: k.loadMessages.bind(k),
87
+ getLocales: k.getLocales.bind(k),
80
88
  locale: l,
81
- setLocale: k,
89
+ setLocale: A,
82
90
  isLoading: f,
83
- loadedLocales: C,
84
- preloadLocale: A
91
+ loadedLocales: w,
92
+ preloadLocale: j
85
93
  }), [
86
- O,
87
- l,
88
94
  k,
95
+ l,
96
+ A,
89
97
  f,
90
- C,
91
- A
98
+ w,
99
+ j
92
100
  ]);
93
101
  return /* @__PURE__ */ x(S.Provider, {
94
- value: j,
102
+ value: M,
95
103
  children: c
96
104
  });
97
105
  }
98
106
  //#endregion
99
107
  //#region src/hooks/useI18n.ts
100
- function E() {
108
+ function D() {
101
109
  let e = f(S);
102
110
  if (!e) throw Error("[fluenti] useI18n() must be used within an <I18nProvider>. Wrap your app with <I18nProvider> to provide i18n context.");
103
111
  return e;
104
112
  }
105
113
  //#endregion
106
114
  //#region src/compile-time-t.ts
107
- var D = ((...e) => {
115
+ var O = ((...e) => {
108
116
  throw Error("[fluenti] `t` imported from '@fluenti/react' 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(...).");
109
- }), O = u(function({ children: e, id: n, context: i, comment: a, render: o, __id: c, __message: l, __components: u }) {
117
+ }), k = u(function({ children: e, id: n, context: i, comment: a, render: o, __id: c, __message: l, __components: u }) {
110
118
  let d = f(S);
111
119
  if (!d) throw Error("[fluenti] <Trans> must be used within an <I18nProvider>");
112
120
  let p = l !== void 0, { message: h, components: g } = m(() => p ? {
@@ -129,7 +137,7 @@ var D = ((...e) => {
129
137
  ...a === void 0 ? {} : { comment: a }
130
138
  }), g);
131
139
  return o ? o(v) : /* @__PURE__ */ x(b, { children: v });
132
- }), k = u(function({ value: t, id: r, context: i, comment: o, zero: s, one: l, two: u, few: d, many: p, other: m, offset: h }) {
140
+ }), A = u(function({ value: t, id: r, context: i, comment: o, zero: s, one: l, two: u, few: d, many: p, other: m, offset: h }) {
133
141
  let g = f(S);
134
142
  if (!g) throw Error("[fluenti] <Plural> must be used within an <I18nProvider>");
135
143
  let { messages: _, components: y } = e(a, {
@@ -153,7 +161,7 @@ var D = ((...e) => {
153
161
  ...i === void 0 ? {} : { context: i },
154
162
  ...o === void 0 ? {} : { comment: o }
155
163
  }, { count: t }, (e, t) => g.i18n.t(e, t), y) });
156
- }), A = u(function(t) {
164
+ }), j = u(function(t) {
157
165
  let r = f(S);
158
166
  if (!r) throw Error("[fluenti] <Select> must be used within an <I18nProvider>");
159
167
  let { value: a, id: s, context: c, comment: l, other: u, options: d, ...p } = t, m = d === void 0 ? {
@@ -176,16 +184,16 @@ var D = ((...e) => {
176
184
  ...c === void 0 ? {} : { context: c },
177
185
  ...l === void 0 ? {} : { comment: l }
178
186
  }, { value: y.valueMap[a] ?? "other" }, (e, t) => r.i18n.t(e, t), _) });
179
- }), j = u(function({ value: e, style: t }) {
187
+ }), M = u(function({ value: e, style: t }) {
180
188
  let n = f(S);
181
189
  if (!n) throw Error("[fluenti] <DateTime> must be used within an <I18nProvider>");
182
190
  return /* @__PURE__ */ x(b, { children: n.i18n.d(e, t) });
183
- }), M = u(function({ value: e, style: t }) {
191
+ }), N = u(function({ value: e, style: t }) {
184
192
  let n = f(S);
185
193
  if (!n) throw Error("[fluenti] <Number> must be used within an <I18nProvider>");
186
194
  return /* @__PURE__ */ x(b, { children: n.i18n.n(e, t) });
187
195
  });
188
196
  //#endregion
189
- export { j as DateTime, S as I18nContext, T as I18nProvider, M as NumberFormat, k as Plural, A as Select, O as Trans, y as msg, D as t, E as useI18n };
197
+ export { M as DateTime, S as I18nContext, E as I18nProvider, N as NumberFormat, A as Plural, j as Select, k as Trans, y as msg, O as t, D as useI18n };
190
198
 
191
199
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useI18n.ts","../src/compile-time-t.ts","../src/components/Trans.tsx","../src/components/Plural.tsx","../src/components/Select.tsx","../src/components/DateTime.tsx","../src/components/Number.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { I18nContextValue } from './types'\n\nexport const I18nContext = createContext<I18nContextValue | null>(null)\n","import { useState, useCallback, useEffect, useMemo, useRef } from 'react'\nimport { createFluent } from '@fluenti/core'\nimport type { Messages } from '@fluenti/core'\nimport { I18nContext } from './context'\nimport type { I18nProviderProps } from './types'\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.react')\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\nexport function I18nProvider({\n locale,\n fallbackLocale,\n messages,\n loadMessages,\n fallbackChain,\n dateFormats,\n numberFormats,\n missing,\n children,\n}: I18nProviderProps) {\n const [currentLocale, setCurrentLocale] = useState(locale)\n const [isLoading, setIsLoading] = useState(false)\n const [loadedMessages, setLoadedMessages] = useState<Record<string, Messages>>(\n messages ?? {},\n )\n const [loadedLocales, setLoadedLocales] = useState<string[]>(\n messages ? Object.keys(messages) : [],\n )\n\n // Use ref to avoid stale closures in callbacks\n const loadedMessagesRef = useRef(loadedMessages)\n loadedMessagesRef.current = loadedMessages\n\n // Guard against out-of-order async locale loads (race condition protection)\n const localeRequestRef = useRef(0)\n\n const i18n = useMemo(() => {\n const config: Parameters<typeof createFluent>[0] = {\n locale: currentLocale,\n messages: loadedMessages,\n }\n if (fallbackLocale !== undefined) config.fallbackLocale = fallbackLocale\n if (fallbackChain !== undefined) config.fallbackChain = fallbackChain\n if (dateFormats !== undefined) config.dateFormats = dateFormats\n if (numberFormats !== undefined) config.numberFormats = numberFormats\n if (missing !== undefined) config.missing = missing\n return createFluent(config)\n }, [currentLocale, loadedMessages, fallbackLocale, fallbackChain, dateFormats, numberFormats, missing])\n\n // Sync external locale prop changes\n useEffect(() => {\n if (locale !== currentLocale) {\n void handleSetLocale(locale)\n }\n // Intentionally only depend on `locale` — we want to sync when the\n // external prop changes, not when internal state (`currentLocale`,\n // `handleSetLocale`) updates, which would cause infinite re-renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [locale])\n\n const handleSetLocale = useCallback(\n async (newLocale: string) => {\n const requestId = ++localeRequestRef.current\n\n if (loadedMessagesRef.current[newLocale] && !loadMessages) {\n setCurrentLocale(newLocale)\n return\n }\n\n const splitRuntime = loadMessages ? getSplitRuntimeModule() : null\n\n if (loadedMessagesRef.current[newLocale]) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n return\n }\n\n if (!loadMessages) {\n console.warn(\n `[fluenti] No messages for locale \"${newLocale}\" and no loadMessages function provided`,\n )\n return\n }\n\n setIsLoading(true)\n try {\n const msgs = await loadMessages(newLocale)\n\n // A newer request has superseded this one — discard stale result\n if (requestId !== localeRequestRef.current) return\n\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [newLocale]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, newLocale])])\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n } catch (err) {\n // Only log if this request is still the latest\n if (requestId === localeRequestRef.current) {\n console.error(`[fluenti] Failed to load locale \"${newLocale}\"`, err)\n }\n } finally {\n if (requestId === localeRequestRef.current) {\n setIsLoading(false)\n }\n }\n },\n [loadMessages],\n )\n\n const preloadLocale = useCallback(\n async (loc: string) => {\n const splitRuntime = getSplitRuntimeModule()\n if (loadedMessagesRef.current[loc] || !loadMessages) return\n try {\n const msgs = await loadMessages(loc)\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [loc]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, loc])])\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n } catch {\n // Silent fail for preload\n }\n },\n [loadMessages],\n )\n\n const ctx = useMemo(\n () => ({\n i18n,\n t: i18n.t.bind(i18n),\n d: i18n.d.bind(i18n),\n n: i18n.n.bind(i18n),\n format: i18n.format.bind(i18n),\n loadMessages: i18n.loadMessages.bind(i18n),\n getLocales: i18n.getLocales.bind(i18n),\n locale: currentLocale,\n setLocale: handleSetLocale,\n isLoading,\n loadedLocales,\n preloadLocale,\n }),\n [i18n, currentLocale, handleSetLocale, isLoading, loadedLocales, preloadLocale],\n )\n\n return <I18nContext.Provider value={ctx}>{children}</I18nContext.Provider>\n}\n","import { useContext } from 'react'\nimport { I18nContext } from '../context'\nimport type { I18nContextValue } from '../types'\n\n/**\n * Primary hook for accessing i18n functions.\n *\n * Returns locale, setLocale, isLoading, loadedLocales, preloadLocale,\n * and the underlying i18n instance with t(), d(), n() methods.\n *\n * @throws If used outside of `<I18nProvider>`\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error(\n '[fluenti] useI18n() must be used within an <I18nProvider>. ' +\n 'Wrap your app with <I18nProvider> to provide i18n context.',\n )\n }\n return ctx\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/react' 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 {\n memo,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { I18nContext } from '../context'\nimport { hashMessage, extractMessage, reconstruct } from './trans-core'\n\nexport interface TransProps {\n /** Source text with embedded components */\n children: ReactNode\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Context comment for translators */\n comment?: string\n /** Custom render wrapper */\n render?: (translation: ReactNode) => ReactNode\n /** @internal Pre-computed message ID from build plugin */\n __id?: string\n /** @internal Pre-computed ICU message from build plugin */\n __message?: string\n /** @internal Pre-computed component list from build plugin */\n __components?: ReactElement[]\n}\n\n/**\n * `<Trans>` component for rich-text translations containing nested React elements.\n *\n * @example\n * ```tsx\n * <Trans>Read the <a href=\"/docs\">documentation</a> for more info.</Trans>\n * ```\n */\nexport const Trans = memo(function Trans({\n children,\n id,\n context,\n comment,\n render,\n __id,\n __message,\n __components,\n}: TransProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Trans> must be used within an <I18nProvider>')\n }\n\n // Fast path: build plugin pre-computed message and components\n const hasPrecomputed = __message !== undefined\n\n const { message, components } = useMemo(\n () => hasPrecomputed\n ? { message: __message!, components: __components ?? [] }\n : extractMessage(children),\n [hasPrecomputed, __message, __components, children],\n )\n const messageId = useMemo(\n () => id ?? __id ?? hashMessage(message, context),\n [id, __id, message, context],\n )\n\n const translated = ctx.i18n.t(\n {\n id: messageId,\n message,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n },\n )\n\n const result = reconstruct(translated, components)\n return render ? render(result) : <>{result}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { PLURAL_CATEGORIES, type PluralCategory } from './plural-core'\nimport { buildICUPluralMessage, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface PluralProps {\n /** The count value */\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 /** Text for zero (if language supports) */\n zero?: ReactNode\n /** Singular form. `#` replaced with value */\n one?: ReactNode\n /** Dual form (Arabic, etc.) */\n two?: ReactNode\n /** Few form (Slavic languages, etc.) */\n few?: ReactNode\n /** Many form */\n many?: ReactNode\n /** Default plural form */\n other: ReactNode\n /** Offset from value before selecting form */\n offset?: number\n}\n\n/**\n * `<Plural>` — ICU plural handling as a component.\n *\n * @example\n * ```tsx\n * <Plural value={count} zero=\"No messages\" one=\"# message\" other=\"# messages\" />\n * ```\n */\nexport const Plural = memo(function Plural({\n value,\n id,\n context,\n comment,\n zero,\n one,\n two,\n few,\n many,\n other,\n offset,\n}: PluralProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Plural> must be used within an <I18nProvider>')\n }\n\n const forms: Record<PluralCategory, ReactNode | undefined> = {\n zero,\n one,\n two,\n few,\n many,\n other,\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, forms)\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 offset,\n )\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(descriptor, { count: value }, (desc, values) => ctx.i18n.t(desc, values), components)}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { buildICUSelectMessage, normalizeSelectForms, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface SelectProps {\n /** The selector value */\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 /** Default case */\n other: ReactNode\n /** Type-safe named options. Takes precedence over direct case props. */\n options?: Record<string, ReactNode>\n /** Named cases — any string key maps to a ReactNode */\n [key: string]: ReactNode | Record<string, ReactNode> | undefined\n}\n\n/**\n * `<Select>` — ICU select for gender, role, or other categorical values.\n *\n * @example\n * ```tsx\n * <Select\n * value={gender}\n * male=\"He liked your post\"\n * female=\"She liked your post\"\n * other=\"They liked your post\"\n * />\n * ```\n */\nexport const Select = memo(function Select(props: SelectProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Select> must be used within an <I18nProvider>')\n }\n\n const { value, id, context, comment, other, options, ...cases } = props\n const forms: Record<string, ReactNode | undefined> = options === undefined\n ? {\n ...Object.fromEntries(\n Object.entries(cases).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other'].includes(key)),\n ),\n other,\n }\n : {\n ...options,\n 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(\n [...orderedKeys].map((key) => [key, messages[key] ?? '']),\n ),\n )\n const icuMessage = buildICUSelectMessage(normalized.forms)\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(\n descriptor,\n { value: normalized.valueMap[value] ?? 'other' },\n (desc, values) => ctx.i18n.t(desc, values),\n components,\n )}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\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>` — formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport const DateTime = memo(function DateTime({ value, style }: DateTimeProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <DateTime> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.d(value, style)}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<Number>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <Number value={1234.56} style=\"currency\" />\n * ```\n */\nexport const NumberFormat = memo(function NumberFormat({ value, style }: NumberProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Number> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.n(value, style)}</>\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/context.ts","../src/provider.tsx","../src/hooks/useI18n.ts","../src/compile-time-t.ts","../src/components/Trans.tsx","../src/components/Plural.tsx","../src/components/Select.tsx","../src/components/DateTime.tsx","../src/components/Number.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { I18nContextValue } from './types'\n\nexport const I18nContext = createContext<I18nContextValue | null>(null)\n","import { useState, useCallback, useEffect, useMemo, useRef } from 'react'\nimport { createFluent } from '@fluenti/core'\nimport type { Messages } from '@fluenti/core'\nimport { I18nContext } from './context'\nimport type { I18nProviderProps } from './types'\n\ninterface SplitRuntimeModule {\n __switchLocale?: (locale: string) => Promise<void>\n __preloadLocale?: (locale: string) => Promise<void>\n}\n\nfunction unwrapMessages(allMessages: Record<string, unknown>): Record<string, Messages> {\n const result: Record<string, Messages> = {}\n for (const [locale, msgs] of Object.entries(allMessages)) {\n result[locale] = typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : msgs as Messages\n }\n return result\n}\n\nconst SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.react')\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\nexport function I18nProvider({\n locale,\n fallbackLocale,\n messages,\n loadMessages,\n fallbackChain,\n dateFormats,\n numberFormats,\n missing,\n children,\n}: I18nProviderProps) {\n const [currentLocale, setCurrentLocale] = useState(locale)\n const [isLoading, setIsLoading] = useState(false)\n const [loadedMessages, setLoadedMessages] = useState<Record<string, Messages>>(\n messages ? unwrapMessages(messages) : {},\n )\n const [loadedLocales, setLoadedLocales] = useState<string[]>(\n messages ? Object.keys(messages) : [],\n )\n\n // Use ref to avoid stale closures in callbacks\n const loadedMessagesRef = useRef(loadedMessages)\n loadedMessagesRef.current = loadedMessages\n\n // Guard against out-of-order async locale loads (race condition protection)\n const localeRequestRef = useRef(0)\n\n const i18n = useMemo(() => {\n const config: Parameters<typeof createFluent>[0] = {\n locale: currentLocale,\n messages: loadedMessages,\n }\n if (fallbackLocale !== undefined) config.fallbackLocale = fallbackLocale\n if (fallbackChain !== undefined) config.fallbackChain = fallbackChain\n if (dateFormats !== undefined) config.dateFormats = dateFormats\n if (numberFormats !== undefined) config.numberFormats = numberFormats\n if (missing !== undefined) config.missing = missing\n return createFluent(config)\n }, [currentLocale, loadedMessages, fallbackLocale, fallbackChain, dateFormats, numberFormats, missing])\n\n // Sync external locale prop changes\n useEffect(() => {\n if (locale !== currentLocale) {\n void handleSetLocale(locale)\n }\n // Intentionally only depend on `locale` — we want to sync when the\n // external prop changes, not when internal state (`currentLocale`,\n // `handleSetLocale`) updates, which would cause infinite re-renders.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [locale])\n\n const handleSetLocale = useCallback(\n async (newLocale: string) => {\n const requestId = ++localeRequestRef.current\n\n if (loadedMessagesRef.current[newLocale] && !loadMessages) {\n setCurrentLocale(newLocale)\n return\n }\n\n const splitRuntime = loadMessages ? getSplitRuntimeModule() : null\n\n if (loadedMessagesRef.current[newLocale]) {\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n return\n }\n\n if (!loadMessages) {\n console.warn(\n `[fluenti] No messages for locale \"${newLocale}\" and no loadMessages function provided`,\n )\n return\n }\n\n setIsLoading(true)\n try {\n const msgs = await loadMessages(newLocale)\n\n // A newer request has superseded this one — discard stale result\n if (requestId !== localeRequestRef.current) return\n\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [newLocale]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, newLocale])])\n if (splitRuntime?.__switchLocale) {\n await splitRuntime.__switchLocale(newLocale)\n }\n setCurrentLocale(newLocale)\n } catch (err) {\n // Only log if this request is still the latest\n if (requestId === localeRequestRef.current) {\n console.error(`[fluenti] Failed to load locale \"${newLocale}\"`, err)\n }\n } finally {\n if (requestId === localeRequestRef.current) {\n setIsLoading(false)\n }\n }\n },\n [loadMessages],\n )\n\n const preloadLocale = useCallback(\n async (loc: string) => {\n const splitRuntime = getSplitRuntimeModule()\n if (loadedMessagesRef.current[loc] || !loadMessages) return\n try {\n const msgs = await loadMessages(loc)\n const resolved: Messages =\n typeof msgs === 'object' && msgs !== null && 'default' in msgs\n ? (msgs as { default: Messages }).default\n : (msgs as Messages)\n setLoadedMessages((prev) => ({ ...prev, [loc]: resolved }))\n setLoadedLocales((prev) => [...new Set([...prev, loc])])\n if (splitRuntime?.__preloadLocale) {\n await splitRuntime.__preloadLocale(loc)\n }\n } catch {\n // Silent fail for preload\n }\n },\n [loadMessages],\n )\n\n const ctx = useMemo(\n () => ({\n i18n,\n t: i18n.t.bind(i18n),\n d: i18n.d.bind(i18n),\n n: i18n.n.bind(i18n),\n format: i18n.format.bind(i18n),\n loadMessages: i18n.loadMessages.bind(i18n),\n getLocales: i18n.getLocales.bind(i18n),\n locale: currentLocale,\n setLocale: handleSetLocale,\n isLoading,\n loadedLocales,\n preloadLocale,\n }),\n [i18n, currentLocale, handleSetLocale, isLoading, loadedLocales, preloadLocale],\n )\n\n return <I18nContext.Provider value={ctx}>{children}</I18nContext.Provider>\n}\n","import { useContext } from 'react'\nimport { I18nContext } from '../context'\nimport type { I18nContextValue } from '../types'\n\n/**\n * Primary hook for accessing i18n functions.\n *\n * Returns locale, setLocale, isLoading, loadedLocales, preloadLocale,\n * and the underlying i18n instance with t(), d(), n() methods.\n *\n * @throws If used outside of `<I18nProvider>`\n */\nexport function useI18n(): I18nContextValue {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error(\n '[fluenti] useI18n() must be used within an <I18nProvider>. ' +\n 'Wrap your app with <I18nProvider> to provide i18n context.',\n )\n }\n return ctx\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/react' 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 {\n memo,\n useContext,\n useMemo,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { I18nContext } from '../context'\nimport { hashMessage, extractMessage, reconstruct } from './trans-core'\n\nexport interface TransProps {\n /** Source text with embedded components */\n children: ReactNode\n /** Override auto-generated hash ID */\n id?: string\n /** Message context used for identity and translator disambiguation */\n context?: string\n /** Context comment for translators */\n comment?: string\n /** Custom render wrapper */\n render?: (translation: ReactNode) => ReactNode\n /** @internal Pre-computed message ID from build plugin */\n __id?: string\n /** @internal Pre-computed ICU message from build plugin */\n __message?: string\n /** @internal Pre-computed component list from build plugin */\n __components?: ReactElement[]\n}\n\n/**\n * `<Trans>` component for rich-text translations containing nested React elements.\n *\n * @example\n * ```tsx\n * <Trans>Read the <a href=\"/docs\">documentation</a> for more info.</Trans>\n * ```\n */\nexport const Trans = memo(function Trans({\n children,\n id,\n context,\n comment,\n render,\n __id,\n __message,\n __components,\n}: TransProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Trans> must be used within an <I18nProvider>')\n }\n\n // Fast path: build plugin pre-computed message and components\n const hasPrecomputed = __message !== undefined\n\n const { message, components } = useMemo(\n () => hasPrecomputed\n ? { message: __message!, components: __components ?? [] }\n : extractMessage(children),\n [hasPrecomputed, __message, __components, children],\n )\n const messageId = useMemo(\n () => id ?? __id ?? hashMessage(message, context),\n [id, __id, message, context],\n )\n\n const translated = ctx.i18n.t(\n {\n id: messageId,\n message,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n },\n )\n\n const result = reconstruct(translated, components)\n return render ? render(result) : <>{result}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { PLURAL_CATEGORIES, type PluralCategory } from './plural-core'\nimport { buildICUPluralMessage, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface PluralProps {\n /** The count value */\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 /** Text for zero (if language supports) */\n zero?: ReactNode\n /** Singular form. `#` replaced with value */\n one?: ReactNode\n /** Dual form (Arabic, etc.) */\n two?: ReactNode\n /** Few form (Slavic languages, etc.) */\n few?: ReactNode\n /** Many form */\n many?: ReactNode\n /** Default plural form */\n other: ReactNode\n /** Offset from value before selecting form */\n offset?: number\n}\n\n/**\n * `<Plural>` — ICU plural handling as a component.\n *\n * @example\n * ```tsx\n * <Plural value={count} zero=\"No messages\" one=\"# message\" other=\"# messages\" />\n * ```\n */\nexport const Plural = memo(function Plural({\n value,\n id,\n context,\n comment,\n zero,\n one,\n two,\n few,\n many,\n other,\n offset,\n}: PluralProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Plural> must be used within an <I18nProvider>')\n }\n\n const forms: Record<PluralCategory, ReactNode | undefined> = {\n zero,\n one,\n two,\n few,\n many,\n other,\n }\n const { messages, components } = serializeRichForms(PLURAL_CATEGORIES, forms)\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 offset,\n )\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(descriptor, { count: value }, (desc, values) => ctx.i18n.t(desc, values), components)}</>\n})\n","import { memo, useContext, type ReactNode } from 'react'\nimport { hashMessage } from '@fluenti/core'\nimport { I18nContext } from '../context'\nimport { buildICUSelectMessage, normalizeSelectForms, renderRichTranslation, serializeRichForms } from './icu-rich'\n\nexport interface SelectProps {\n /** The selector value */\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 /** Default case */\n other: ReactNode\n /** Type-safe named options. Takes precedence over direct case props. */\n options?: Record<string, ReactNode>\n /** Named cases — any string key maps to a ReactNode */\n [key: string]: ReactNode | Record<string, ReactNode> | undefined\n}\n\n/**\n * `<Select>` — ICU select for gender, role, or other categorical values.\n *\n * @example\n * ```tsx\n * <Select\n * value={gender}\n * male=\"He liked your post\"\n * female=\"She liked your post\"\n * other=\"They liked your post\"\n * />\n * ```\n */\nexport const Select = memo(function Select(props: SelectProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Select> must be used within an <I18nProvider>')\n }\n\n const { value, id, context, comment, other, options, ...cases } = props\n const forms: Record<string, ReactNode | undefined> = options === undefined\n ? {\n ...Object.fromEntries(\n Object.entries(cases).filter(([key]) => !['value', 'id', 'context', 'comment', 'options', 'other'].includes(key)),\n ),\n other,\n }\n : {\n ...options,\n 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(\n [...orderedKeys].map((key) => [key, messages[key] ?? '']),\n ),\n )\n const icuMessage = buildICUSelectMessage(normalized.forms)\n\n const descriptor = {\n id: id ?? (context === undefined ? icuMessage : hashMessage(icuMessage, context)),\n message: icuMessage,\n ...(context !== undefined ? { context } : {}),\n ...(comment !== undefined ? { comment } : {}),\n }\n\n return <>{renderRichTranslation(\n descriptor,\n { value: normalized.valueMap[value] ?? 'other' },\n (desc, values) => ctx.i18n.t(desc, values),\n components,\n )}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\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>` — formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <DateTime value={new Date()} style=\"long\" />\n * ```\n */\nexport const DateTime = memo(function DateTime({ value, style }: DateTimeProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <DateTime> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.d(value, style)}</>\n})\n","import { memo, useContext } from 'react'\nimport { I18nContext } from '../context'\n\nexport interface NumberProps {\n /** Number value to format */\n value: number\n /** Named format style */\n style?: string\n}\n\n/**\n * `<Number>` — number formatting component using Intl APIs.\n *\n * @example\n * ```tsx\n * <Number value={1234.56} style=\"currency\" />\n * ```\n */\nexport const NumberFormat = memo(function NumberFormat({ value, style }: NumberProps) {\n const ctx = useContext(I18nContext)\n if (!ctx) {\n throw new Error('[fluenti] <Number> must be used within an <I18nProvider>')\n }\n return <>{ctx.i18n.n(value, style)}</>\n})\n"],"mappings":";;;;;;AAGA,IAAa,IAAc,EAAuC,KAAK;;;ACQvE,SAAS,EAAe,GAAgE;CACtF,IAAM,IAAmC,EAAE;AAC3C,MAAK,IAAM,CAAC,GAAQ,MAAS,OAAO,QAAQ,EAAY,CACtD,GAAO,KAAU,OAAO,KAAS,YAAY,KAAiB,aAAa,IACtE,EAA+B,UAChC;AAEN,QAAO;;AAGT,IAAM,IAAoB,OAAO,IAAI,wBAAwB;AAE7D,SAAS,IAAmD;CAC1D,IAAM,IAAW,WAA4C;AAC7D,QAAO,OAAO,KAAY,YAAY,IAClC,IACA;;AAGN,SAAgB,EAAa,EAC3B,WACA,mBACA,aACA,iBACA,kBACA,gBACA,kBACA,YACA,eACoB;CACpB,IAAM,CAAC,GAAe,KAAoB,EAAS,EAAO,EACpD,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAgB,KAAqB,EAC1C,IAAW,EAAe,EAAS,GAAG,EAAE,CACzC,EACK,CAAC,GAAe,KAAoB,EACxC,IAAW,OAAO,KAAK,EAAS,GAAG,EAAE,CACtC,EAGK,IAAoB,EAAO,EAAe;AAChD,GAAkB,UAAU;CAG5B,IAAM,IAAmB,EAAO,EAAE,EAE5B,IAAO,QAAc;EACzB,IAAM,IAA6C;GACjD,QAAQ;GACR,UAAU;GACX;AAMD,SALI,MAAmB,KAAA,MAAW,EAAO,iBAAiB,IACtD,MAAkB,KAAA,MAAW,EAAO,gBAAgB,IACpD,MAAgB,KAAA,MAAW,EAAO,cAAc,IAChD,MAAkB,KAAA,MAAW,EAAO,gBAAgB,IACpD,MAAY,KAAA,MAAW,EAAO,UAAU,IACrC,EAAa,EAAO;IAC1B;EAAC;EAAe;EAAgB;EAAgB;EAAe;EAAa;EAAe;EAAQ,CAAC;AAGvG,SAAgB;AACd,EAAI,MAAW,KACR,EAAgB,EAAO;IAM7B,CAAC,EAAO,CAAC;CAEZ,IAAM,IAAkB,EACtB,OAAO,MAAsB;EAC3B,IAAM,IAAY,EAAE,EAAiB;AAErC,MAAI,EAAkB,QAAQ,MAAc,CAAC,GAAc;AACzD,KAAiB,EAAU;AAC3B;;EAGF,IAAM,IAAe,IAAe,GAAuB,GAAG;AAE9D,MAAI,EAAkB,QAAQ,IAAY;AAIxC,GAHI,GAAc,kBAChB,MAAM,EAAa,eAAe,EAAU,EAE9C,EAAiB,EAAU;AAC3B;;AAGF,MAAI,CAAC,GAAc;AACjB,WAAQ,KACN,qCAAqC,EAAU,yCAChD;AACD;;AAGF,IAAa,GAAK;AAClB,MAAI;GACF,IAAM,IAAO,MAAM,EAAa,EAAU;AAG1C,OAAI,MAAc,EAAiB,QAAS;GAE5C,IAAM,IACJ,OAAO,KAAS,YAAY,KAAiB,aAAa,IACrD,EAA+B,UAC/B;AAMP,GALA,GAAmB,OAAU;IAAE,GAAG;KAAO,IAAY;IAAU,EAAE,EACjE,GAAkB,MAAS,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,GAAM,EAAU,CAAC,CAAC,CAAC,EAC1D,GAAc,kBAChB,MAAM,EAAa,eAAe,EAAU,EAE9C,EAAiB,EAAU;WACpB,GAAK;AAEZ,GAAI,MAAc,EAAiB,WACjC,QAAQ,MAAM,oCAAoC,EAAU,IAAI,EAAI;YAE9D;AACR,GAAI,MAAc,EAAiB,WACjC,EAAa,GAAM;;IAIzB,CAAC,EAAa,CACf,EAEK,IAAgB,EACpB,OAAO,MAAgB;EACrB,IAAM,IAAe,GAAuB;AACxC,UAAkB,QAAQ,MAAQ,CAAC,GACvC,KAAI;GACF,IAAM,IAAO,MAAM,EAAa,EAAI,EAC9B,IACJ,OAAO,KAAS,YAAY,KAAiB,aAAa,IACrD,EAA+B,UAC/B;AAGP,GAFA,GAAmB,OAAU;IAAE,GAAG;KAAO,IAAM;IAAU,EAAE,EAC3D,GAAkB,MAAS,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,GAAM,EAAI,CAAC,CAAC,CAAC,EACpD,GAAc,mBAChB,MAAM,EAAa,gBAAgB,EAAI;UAEnC;IAIV,CAAC,EAAa,CACf,EAEK,IAAM,SACH;EACL;EACA,GAAG,EAAK,EAAE,KAAK,EAAK;EACpB,GAAG,EAAK,EAAE,KAAK,EAAK;EACpB,GAAG,EAAK,EAAE,KAAK,EAAK;EACpB,QAAQ,EAAK,OAAO,KAAK,EAAK;EAC9B,cAAc,EAAK,aAAa,KAAK,EAAK;EAC1C,YAAY,EAAK,WAAW,KAAK,EAAK;EACtC,QAAQ;EACR,WAAW;EACX;EACA;EACA;EACD,GACD;EAAC;EAAM;EAAe;EAAiB;EAAW;EAAe;EAAc,CAChF;AAED,QAAO,kBAAC,EAAY,UAAb;EAAsB,OAAO;EAAM;EAAgC,CAAA;;;;ACtK5E,SAAgB,IAA4B;CAC1C,IAAM,IAAM,EAAW,EAAY;AACnC,KAAI,CAAC,EACH,OAAU,MACR,wHAED;AAEH,QAAO;;;;AClBT,IAAa,MAAoB,GAAG,MAAqB;AACvD,OAAU,MACR,iMAGD;IC8BU,IAAQ,EAAK,SAAe,EACvC,aACA,OACA,YACA,YACA,WACA,SACA,cACA,mBACa;CACb,IAAM,IAAM,EAAW,EAAY;AACnC,KAAI,CAAC,EACH,OAAU,MAAM,0DAA0D;CAI5E,IAAM,IAAiB,MAAc,KAAA,GAE/B,EAAE,YAAS,kBAAe,QACxB,IACF;EAAE,SAAS;EAAY,YAAY,KAAgB,EAAE;EAAE,GACvD,EAAe,EAAS,EAC5B;EAAC;EAAgB;EAAW;EAAc;EAAS,CACpD,EACK,IAAY,QACV,KAAM,KAAQ,EAAY,GAAS,EAAQ,EACjD;EAAC;EAAI;EAAM;EAAS;EAAQ,CAC7B,EAWK,IAAS,EATI,EAAI,KAAK,EAC1B;EACE,IAAI;EACJ;EACA,GAAI,MAAY,KAAA,IAA0B,EAAE,GAAhB,EAAE,YAAS;EACvC,GAAI,MAAY,KAAA,IAA0B,EAAE,GAAhB,EAAE,YAAS;EACxC,CACF,EAEsC,EAAW;AAClD,QAAO,IAAS,EAAO,EAAO,GAAG,kBAAA,GAAA,EAAA,UAAG,GAAU,CAAA;EAC9C,ECtCW,IAAS,EAAK,SAAgB,EACzC,UACA,OACA,YACA,YACA,SACA,QACA,QACA,QACA,SACA,UACA,aACc;CACd,IAAM,IAAM,EAAW,EAAY;AACnC,KAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;CAW7E,IAAM,EAAE,aAAU,kBAAe,EAAmB,GARS;EAC3D;EACA;EACA;EACA;EACA;EACA;EACD,CAC4E,EACvE,IAAa,EACjB;EACE,GAAI,EAAS,SAAS,KAAA,KAAa,EAAE,MAAM,EAAS,MAAM;EAC1D,GAAI,EAAS,QAAQ,KAAA,KAAa,EAAE,KAAK,EAAS,KAAK;EACvD,GAAI,EAAS,QAAQ,KAAA,KAAa,EAAE,KAAK,EAAS,KAAK;EACvD,GAAI,EAAS,QAAQ,KAAA,KAAa,EAAE,KAAK,EAAS,KAAK;EACvD,GAAI,EAAS,SAAS,KAAA,KAAa,EAAE,MAAM,EAAS,MAAM;EAC1D,OAAO,EAAS,SAAS;EAC1B,EACD,EACD;AASD,QAAO,kBAAA,GAAA,EAAA,UAAG,EAPS;EACjB,IAAI,MAAO,MAAY,KAAA,IAAY,IAAa,EAAY,GAAY,EAAQ;EAChF,SAAS;EACT,GAAI,MAAY,KAAA,IAA0B,EAAE,GAAhB,EAAE,YAAS;EACvC,GAAI,MAAY,KAAA,IAA0B,EAAE,GAAhB,EAAE,YAAS;EACxC,EAE2C,EAAE,OAAO,GAAO,GAAG,GAAM,MAAW,EAAI,KAAK,EAAE,GAAM,EAAO,EAAE,EAAW,EAAI,CAAA;EACzH,ECnDW,IAAS,EAAK,SAAgB,GAAoB;CAC7D,IAAM,IAAM,EAAW,EAAY;AACnC,KAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;CAG7E,IAAM,EAAE,UAAO,OAAI,YAAS,YAAS,UAAO,YAAS,GAAG,MAAU,GAC5D,IAA+C,MAAY,KAAA,IAC7D;EACA,GAAG,OAAO,YACR,OAAO,QAAQ,EAAM,CAAC,QAAQ,CAAC,OAAS,CAAC;GAAC;GAAS;GAAM;GAAW;GAAW;GAAW;GAAQ,CAAC,SAAS,EAAI,CAAC,CAClH;EACD;EACD,GACC;EACA,GAAG;EACH;EACD,EAEG,IAAc,CAAC,GAAG,OAAO,KAAK,EAAM,CAAC,QAAO,MAAO,MAAQ,QAAQ,EAAE,QAAQ,EAC7E,EAAE,aAAU,kBAAe,EAAmB,GAAa,EAAM,EACjE,IAAa,EACjB,OAAO,YACL,CAAC,GAAG,EAAY,CAAC,KAAK,MAAQ,CAAC,GAAK,EAAS,MAAQ,GAAG,CAAC,CAC1D,CACF,EACK,IAAa,EAAsB,EAAW,MAAM;AAS1D,QAAO,kBAAA,GAAA,EAAA,UAAG,EAPS;EACjB,IAAI,MAAO,MAAY,KAAA,IAAY,IAAa,EAAY,GAAY,EAAQ;EAChF,SAAS;EACT,GAAI,MAAY,KAAA,IAA0B,EAAE,GAAhB,EAAE,YAAS;EACvC,GAAI,MAAY,KAAA,IAA0B,EAAE,GAAhB,EAAE,YAAS;EACxC,EAIC,EAAE,OAAO,EAAW,SAAS,MAAU,SAAS,GAC/C,GAAM,MAAW,EAAI,KAAK,EAAE,GAAM,EAAO,EAC1C,EACD,EAAI,CAAA;EACL,EC1DW,IAAW,EAAK,SAAkB,EAAE,UAAO,YAAwB;CAC9E,IAAM,IAAM,EAAW,EAAY;AACnC,KAAI,CAAC,EACH,OAAU,MAAM,6DAA6D;AAE/E,QAAO,kBAAA,GAAA,EAAA,UAAG,EAAI,KAAK,EAAE,GAAO,EAAM,EAAI,CAAA;EACtC,ECNW,IAAe,EAAK,SAAsB,EAAE,UAAO,YAAsB;CACpF,IAAM,IAAM,EAAW,EAAY;AACnC,KAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;AAE7E,QAAO,kBAAA,GAAA,EAAA,UAAG,EAAI,KAAK,EAAE,GAAO,EAAM,EAAI,CAAA;EACtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAgBhD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,OAAO,EACP,QAAQ,GACT,EAAE,iBAAiB,2CA2InB"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AA0BhD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,OAAO,EACP,QAAQ,GACT,EAAE,iBAAiB,2CA2InB"}
@@ -0,0 +1,3 @@
1
+ import { RuntimeGenerator } from '@fluenti/vite-plugin';
2
+ export declare const reactRuntimeGenerator: RuntimeGenerator;
3
+ //# sourceMappingURL=react-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-runtime.d.ts","sourceRoot":"","sources":["../src/react-runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAA2B,MAAM,sBAAsB,CAAA;AAErF,eAAO,MAAM,qBAAqB,EAAE,gBA8HnC,CAAA"}
@@ -0,0 +1,110 @@
1
+ let e=require(`@fluenti/vite-plugin`),t=require(`node:path`);var n={generateRuntime(e){let{catalogDir:n,locales:r,sourceLocale:i,defaultBuildLocale:a}=e,o=a||i,s=(0,t.resolve)(process.cwd(),n);return`
2
+ import __defaultMsgs from '${s}/${o}.js'
3
+
4
+ const __catalog = { ...__defaultMsgs }
5
+ let __currentLocale = '${o}'
6
+ const __loadedLocales = new Set(['${o}'])
7
+ let __loading = false
8
+ const __cache = new Map()
9
+ const __normalizeMessages = (mod) => mod.default ?? mod
10
+
11
+ const __loaders = {
12
+ ${r.filter(e=>e!==o).map(e=>` '${e}': () => import('${s}/${e}.js'),`).join(`
13
+ `)}
14
+ }
15
+
16
+ async function __switchLocale(locale) {
17
+ if (__loadedLocales.has(locale)) {
18
+ Object.assign(__catalog, __cache.get(locale) || __defaultMsgs)
19
+ __currentLocale = locale
20
+ return
21
+ }
22
+ __loading = true
23
+ try {
24
+ const mod = __normalizeMessages(await __loaders[locale]())
25
+ __cache.set(locale, mod)
26
+ __loadedLocales.add(locale)
27
+ Object.assign(__catalog, mod)
28
+ __currentLocale = locale
29
+ } finally {
30
+ __loading = false
31
+ }
32
+ }
33
+
34
+ async function __preloadLocale(locale) {
35
+ if (__loadedLocales.has(locale) || !__loaders[locale]) return
36
+ try {
37
+ const mod = __normalizeMessages(await __loaders[locale]())
38
+ __cache.set(locale, mod)
39
+ __loadedLocales.add(locale)
40
+ } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }
41
+ }
42
+
43
+ globalThis[Symbol.for('fluenti.runtime.react')] = { __switchLocale, __preloadLocale }
44
+
45
+ export { __catalog, __switchLocale, __preloadLocale, __currentLocale, __loading, __loadedLocales }
46
+ `},generateRouteRuntime(e){let{catalogDir:n,locales:r,sourceLocale:i,defaultBuildLocale:a}=e,o=a||i,s=(0,t.resolve)(process.cwd(),n);return`
47
+ import __defaultMsgs from '${s}/${o}.js'
48
+
49
+ const __catalog = { ...__defaultMsgs }
50
+ let __currentLocale = '${o}'
51
+ const __loadedLocales = new Set(['${o}'])
52
+ let __loading = false
53
+ const __cache = new Map()
54
+ const __loadedRoutes = new Set()
55
+ const __normalizeMessages = (mod) => mod.default ?? mod
56
+
57
+ const __loaders = {
58
+ ${r.filter(e=>e!==o).map(e=>` '${e}': () => import('${s}/${e}.js'),`).join(`
59
+ `)}
60
+ }
61
+
62
+ const __routeLoaders = {}
63
+
64
+ function __registerRouteLoader(routeId, locale, loader) {
65
+ const key = routeId + ':' + locale
66
+ __routeLoaders[key] = loader
67
+ }
68
+
69
+ async function __loadRoute(routeId, locale) {
70
+ const key = routeId + ':' + (locale || __currentLocale)
71
+ if (__loadedRoutes.has(key)) return
72
+ const loader = __routeLoaders[key]
73
+ if (!loader) return
74
+ const mod = __normalizeMessages(await loader())
75
+ Object.assign(__catalog, mod)
76
+ __loadedRoutes.add(key)
77
+ }
78
+
79
+ async function __switchLocale(locale) {
80
+ if (locale === __currentLocale) return
81
+ __loading = true
82
+ try {
83
+ if (__cache.has(locale)) {
84
+ Object.assign(__catalog, __cache.get(locale))
85
+ } else {
86
+ const mod = __normalizeMessages(await __loaders[locale]())
87
+ __cache.set(locale, mod)
88
+ Object.assign(__catalog, mod)
89
+ }
90
+ __loadedLocales.add(locale)
91
+ __currentLocale = locale
92
+ } finally {
93
+ __loading = false
94
+ }
95
+ }
96
+
97
+ async function __preloadLocale(locale) {
98
+ if (__cache.has(locale) || !__loaders[locale]) return
99
+ try {
100
+ const mod = __normalizeMessages(await __loaders[locale]())
101
+ __cache.set(locale, mod)
102
+ __loadedLocales.add(locale)
103
+ } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }
104
+ }
105
+
106
+ globalThis[Symbol.for('fluenti.runtime.react')] = { __switchLocale, __preloadLocale }
107
+
108
+ export { __catalog, __switchLocale, __preloadLocale, __loadRoute, __registerRouteLoader, __currentLocale, __loading, __loadedLocales }
109
+ `}};function r(t){return(0,e.createFluentiPlugins)({...t,framework:`react`},[],n)}module.exports=r;
110
+ //# sourceMappingURL=vite-plugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.cjs","names":[],"sources":["../src/react-runtime.ts","../src/vite-plugin.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { RuntimeGenerator, RuntimeGeneratorOptions } from '@fluenti/vite-plugin'\n\nexport const reactRuntimeGenerator: RuntimeGenerator = {\n generateRuntime(options: RuntimeGeneratorOptions): string {\n const { catalogDir, locales, sourceLocale, defaultBuildLocale } = options\n const defaultLocale = defaultBuildLocale || sourceLocale\n const absoluteCatalogDir = resolve(process.cwd(), catalogDir)\n const runtimeKey = 'fluenti.runtime.react'\n const lazyLocales = locales.filter((locale) => locale !== defaultLocale)\n\n return `\nimport __defaultMsgs from '${absoluteCatalogDir}/${defaultLocale}.js'\n\nconst __catalog = { ...__defaultMsgs }\nlet __currentLocale = '${defaultLocale}'\nconst __loadedLocales = new Set(['${defaultLocale}'])\nlet __loading = false\nconst __cache = new Map()\nconst __normalizeMessages = (mod) => mod.default ?? mod\n\nconst __loaders = {\n${lazyLocales.map((l) => ` '${l}': () => import('${absoluteCatalogDir}/${l}.js'),`).join('\\n')}\n}\n\nasync function __switchLocale(locale) {\n if (__loadedLocales.has(locale)) {\n Object.assign(__catalog, __cache.get(locale) || __defaultMsgs)\n __currentLocale = locale\n return\n }\n __loading = true\n try {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n __loadedLocales.add(locale)\n Object.assign(__catalog, mod)\n __currentLocale = locale\n } finally {\n __loading = false\n }\n}\n\nasync function __preloadLocale(locale) {\n if (__loadedLocales.has(locale) || !__loaders[locale]) return\n try {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n __loadedLocales.add(locale)\n } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }\n}\n\nglobalThis[Symbol.for('${runtimeKey}')] = { __switchLocale, __preloadLocale }\n\nexport { __catalog, __switchLocale, __preloadLocale, __currentLocale, __loading, __loadedLocales }\n`\n },\n\n generateRouteRuntime(options: RuntimeGeneratorOptions): string {\n const { catalogDir, locales, sourceLocale, defaultBuildLocale } = options\n const defaultLocale = defaultBuildLocale || sourceLocale\n const absoluteCatalogDir = resolve(process.cwd(), catalogDir)\n const runtimeKey = 'fluenti.runtime.react'\n const lazyLocales = locales.filter((locale) => locale !== defaultLocale)\n\n return `\nimport __defaultMsgs from '${absoluteCatalogDir}/${defaultLocale}.js'\n\nconst __catalog = { ...__defaultMsgs }\nlet __currentLocale = '${defaultLocale}'\nconst __loadedLocales = new Set(['${defaultLocale}'])\nlet __loading = false\nconst __cache = new Map()\nconst __loadedRoutes = new Set()\nconst __normalizeMessages = (mod) => mod.default ?? mod\n\nconst __loaders = {\n${lazyLocales.map((l) => ` '${l}': () => import('${absoluteCatalogDir}/${l}.js'),`).join('\\n')}\n}\n\nconst __routeLoaders = {}\n\nfunction __registerRouteLoader(routeId, locale, loader) {\n const key = routeId + ':' + locale\n __routeLoaders[key] = loader\n}\n\nasync function __loadRoute(routeId, locale) {\n const key = routeId + ':' + (locale || __currentLocale)\n if (__loadedRoutes.has(key)) return\n const loader = __routeLoaders[key]\n if (!loader) return\n const mod = __normalizeMessages(await loader())\n Object.assign(__catalog, mod)\n __loadedRoutes.add(key)\n}\n\nasync function __switchLocale(locale) {\n if (locale === __currentLocale) return\n __loading = true\n try {\n if (__cache.has(locale)) {\n Object.assign(__catalog, __cache.get(locale))\n } else {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n Object.assign(__catalog, mod)\n }\n __loadedLocales.add(locale)\n __currentLocale = locale\n } finally {\n __loading = false\n }\n}\n\nasync function __preloadLocale(locale) {\n if (__cache.has(locale) || !__loaders[locale]) return\n try {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n __loadedLocales.add(locale)\n } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }\n}\n\nglobalThis[Symbol.for('${runtimeKey}')] = { __switchLocale, __preloadLocale }\n\nexport { __catalog, __switchLocale, __preloadLocale, __loadRoute, __registerRouteLoader, __currentLocale, __loading, __loadedLocales }\n`\n },\n}\n","import type { Plugin } from 'vite'\nimport type { FluentiPluginOptions } from '@fluenti/vite-plugin'\nimport { createFluentiPlugins } from '@fluenti/vite-plugin'\nimport { reactRuntimeGenerator } from './react-runtime'\n\nexport type { FluentiPluginOptions as FluentiReactOptions } from '@fluenti/vite-plugin'\n\nexport default function fluentiReact(options?: FluentiPluginOptions): Plugin[] {\n return createFluentiPlugins(\n { ...options, framework: 'react' },\n [],\n reactRuntimeGenerator,\n )\n}\n"],"mappings":"6DAGA,IAAa,EAA0C,CACrD,gBAAgB,EAA0C,CACxD,GAAM,CAAE,aAAY,UAAS,eAAc,sBAAuB,EAC5D,EAAgB,GAAsB,EACtC,GAAA,EAAA,EAAA,SAA6B,QAAQ,KAAK,CAAE,EAAW,CAI7D,MAAO;6BACkB,EAAmB,GAAG,EAAc;;;yBAGxC,EAAc;oCACH,EAAc;;;;;;EAP1B,EAAQ,OAAQ,GAAW,IAAW,EAAc,CAa9D,IAAK,GAAM,MAAM,EAAE,mBAAmB,EAAmB,GAAG,EAAE,QAAQ,CAAC,KAAK;EAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoC9F,qBAAqB,EAA0C,CAC7D,GAAM,CAAE,aAAY,UAAS,eAAc,sBAAuB,EAC5D,EAAgB,GAAsB,EACtC,GAAA,EAAA,EAAA,SAA6B,QAAQ,KAAK,CAAE,EAAW,CAI7D,MAAO;6BACkB,EAAmB,GAAG,EAAc;;;yBAGxC,EAAc;oCACH,EAAc;;;;;;;EAP1B,EAAQ,OAAQ,GAAW,IAAW,EAAc,CAc9D,IAAK,GAAM,MAAM,EAAE,mBAAmB,EAAmB,GAAG,EAAE,QAAQ,CAAC,KAAK;EAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoD/F,CC1HD,SAAwB,EAAa,EAA0C,CAC7E,OAAA,EAAA,EAAA,sBACE,CAAE,GAAG,EAAS,UAAW,QAAS,CAClC,EAAE,CACF,EACD"}
@@ -0,0 +1,5 @@
1
+ import { Plugin } from 'vite';
2
+ import { FluentiPluginOptions } from '@fluenti/vite-plugin';
3
+ export type { FluentiPluginOptions as FluentiReactOptions } from '@fluenti/vite-plugin';
4
+ export default function fluentiReact(options?: FluentiPluginOptions): Plugin[];
5
+ //# sourceMappingURL=vite-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.d.ts","sourceRoot":"","sources":["../src/vite-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAIhE,YAAY,EAAE,oBAAoB,IAAI,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAEvF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,MAAM,EAAE,CAM7E"}
@@ -0,0 +1,131 @@
1
+ import { createFluentiPlugins as e } from "@fluenti/vite-plugin";
2
+ import { resolve as t } from "node:path";
3
+ //#region src/react-runtime.ts
4
+ var n = {
5
+ generateRuntime(e) {
6
+ let { catalogDir: n, locales: r, sourceLocale: i, defaultBuildLocale: a } = e, o = a || i, s = t(process.cwd(), n);
7
+ return `
8
+ import __defaultMsgs from '${s}/${o}.js'
9
+
10
+ const __catalog = { ...__defaultMsgs }
11
+ let __currentLocale = '${o}'
12
+ const __loadedLocales = new Set(['${o}'])
13
+ let __loading = false
14
+ const __cache = new Map()
15
+ const __normalizeMessages = (mod) => mod.default ?? mod
16
+
17
+ const __loaders = {
18
+ ${r.filter((e) => e !== o).map((e) => ` '${e}': () => import('${s}/${e}.js'),`).join("\n")}
19
+ }
20
+
21
+ async function __switchLocale(locale) {
22
+ if (__loadedLocales.has(locale)) {
23
+ Object.assign(__catalog, __cache.get(locale) || __defaultMsgs)
24
+ __currentLocale = locale
25
+ return
26
+ }
27
+ __loading = true
28
+ try {
29
+ const mod = __normalizeMessages(await __loaders[locale]())
30
+ __cache.set(locale, mod)
31
+ __loadedLocales.add(locale)
32
+ Object.assign(__catalog, mod)
33
+ __currentLocale = locale
34
+ } finally {
35
+ __loading = false
36
+ }
37
+ }
38
+
39
+ async function __preloadLocale(locale) {
40
+ if (__loadedLocales.has(locale) || !__loaders[locale]) return
41
+ try {
42
+ const mod = __normalizeMessages(await __loaders[locale]())
43
+ __cache.set(locale, mod)
44
+ __loadedLocales.add(locale)
45
+ } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }
46
+ }
47
+
48
+ globalThis[Symbol.for('fluenti.runtime.react')] = { __switchLocale, __preloadLocale }
49
+
50
+ export { __catalog, __switchLocale, __preloadLocale, __currentLocale, __loading, __loadedLocales }
51
+ `;
52
+ },
53
+ generateRouteRuntime(e) {
54
+ let { catalogDir: n, locales: r, sourceLocale: i, defaultBuildLocale: a } = e, o = a || i, s = t(process.cwd(), n);
55
+ return `
56
+ import __defaultMsgs from '${s}/${o}.js'
57
+
58
+ const __catalog = { ...__defaultMsgs }
59
+ let __currentLocale = '${o}'
60
+ const __loadedLocales = new Set(['${o}'])
61
+ let __loading = false
62
+ const __cache = new Map()
63
+ const __loadedRoutes = new Set()
64
+ const __normalizeMessages = (mod) => mod.default ?? mod
65
+
66
+ const __loaders = {
67
+ ${r.filter((e) => e !== o).map((e) => ` '${e}': () => import('${s}/${e}.js'),`).join("\n")}
68
+ }
69
+
70
+ const __routeLoaders = {}
71
+
72
+ function __registerRouteLoader(routeId, locale, loader) {
73
+ const key = routeId + ':' + locale
74
+ __routeLoaders[key] = loader
75
+ }
76
+
77
+ async function __loadRoute(routeId, locale) {
78
+ const key = routeId + ':' + (locale || __currentLocale)
79
+ if (__loadedRoutes.has(key)) return
80
+ const loader = __routeLoaders[key]
81
+ if (!loader) return
82
+ const mod = __normalizeMessages(await loader())
83
+ Object.assign(__catalog, mod)
84
+ __loadedRoutes.add(key)
85
+ }
86
+
87
+ async function __switchLocale(locale) {
88
+ if (locale === __currentLocale) return
89
+ __loading = true
90
+ try {
91
+ if (__cache.has(locale)) {
92
+ Object.assign(__catalog, __cache.get(locale))
93
+ } else {
94
+ const mod = __normalizeMessages(await __loaders[locale]())
95
+ __cache.set(locale, mod)
96
+ Object.assign(__catalog, mod)
97
+ }
98
+ __loadedLocales.add(locale)
99
+ __currentLocale = locale
100
+ } finally {
101
+ __loading = false
102
+ }
103
+ }
104
+
105
+ async function __preloadLocale(locale) {
106
+ if (__cache.has(locale) || !__loaders[locale]) return
107
+ try {
108
+ const mod = __normalizeMessages(await __loaders[locale]())
109
+ __cache.set(locale, mod)
110
+ __loadedLocales.add(locale)
111
+ } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }
112
+ }
113
+
114
+ globalThis[Symbol.for('fluenti.runtime.react')] = { __switchLocale, __preloadLocale }
115
+
116
+ export { __catalog, __switchLocale, __preloadLocale, __loadRoute, __registerRouteLoader, __currentLocale, __loading, __loadedLocales }
117
+ `;
118
+ }
119
+ };
120
+ //#endregion
121
+ //#region src/vite-plugin.ts
122
+ function r(t) {
123
+ return e({
124
+ ...t,
125
+ framework: "react"
126
+ }, [], n);
127
+ }
128
+ //#endregion
129
+ export { r as default };
130
+
131
+ //# sourceMappingURL=vite-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vite-plugin.js","names":[],"sources":["../src/react-runtime.ts","../src/vite-plugin.ts"],"sourcesContent":["import { resolve } from 'node:path'\nimport type { RuntimeGenerator, RuntimeGeneratorOptions } from '@fluenti/vite-plugin'\n\nexport const reactRuntimeGenerator: RuntimeGenerator = {\n generateRuntime(options: RuntimeGeneratorOptions): string {\n const { catalogDir, locales, sourceLocale, defaultBuildLocale } = options\n const defaultLocale = defaultBuildLocale || sourceLocale\n const absoluteCatalogDir = resolve(process.cwd(), catalogDir)\n const runtimeKey = 'fluenti.runtime.react'\n const lazyLocales = locales.filter((locale) => locale !== defaultLocale)\n\n return `\nimport __defaultMsgs from '${absoluteCatalogDir}/${defaultLocale}.js'\n\nconst __catalog = { ...__defaultMsgs }\nlet __currentLocale = '${defaultLocale}'\nconst __loadedLocales = new Set(['${defaultLocale}'])\nlet __loading = false\nconst __cache = new Map()\nconst __normalizeMessages = (mod) => mod.default ?? mod\n\nconst __loaders = {\n${lazyLocales.map((l) => ` '${l}': () => import('${absoluteCatalogDir}/${l}.js'),`).join('\\n')}\n}\n\nasync function __switchLocale(locale) {\n if (__loadedLocales.has(locale)) {\n Object.assign(__catalog, __cache.get(locale) || __defaultMsgs)\n __currentLocale = locale\n return\n }\n __loading = true\n try {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n __loadedLocales.add(locale)\n Object.assign(__catalog, mod)\n __currentLocale = locale\n } finally {\n __loading = false\n }\n}\n\nasync function __preloadLocale(locale) {\n if (__loadedLocales.has(locale) || !__loaders[locale]) return\n try {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n __loadedLocales.add(locale)\n } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }\n}\n\nglobalThis[Symbol.for('${runtimeKey}')] = { __switchLocale, __preloadLocale }\n\nexport { __catalog, __switchLocale, __preloadLocale, __currentLocale, __loading, __loadedLocales }\n`\n },\n\n generateRouteRuntime(options: RuntimeGeneratorOptions): string {\n const { catalogDir, locales, sourceLocale, defaultBuildLocale } = options\n const defaultLocale = defaultBuildLocale || sourceLocale\n const absoluteCatalogDir = resolve(process.cwd(), catalogDir)\n const runtimeKey = 'fluenti.runtime.react'\n const lazyLocales = locales.filter((locale) => locale !== defaultLocale)\n\n return `\nimport __defaultMsgs from '${absoluteCatalogDir}/${defaultLocale}.js'\n\nconst __catalog = { ...__defaultMsgs }\nlet __currentLocale = '${defaultLocale}'\nconst __loadedLocales = new Set(['${defaultLocale}'])\nlet __loading = false\nconst __cache = new Map()\nconst __loadedRoutes = new Set()\nconst __normalizeMessages = (mod) => mod.default ?? mod\n\nconst __loaders = {\n${lazyLocales.map((l) => ` '${l}': () => import('${absoluteCatalogDir}/${l}.js'),`).join('\\n')}\n}\n\nconst __routeLoaders = {}\n\nfunction __registerRouteLoader(routeId, locale, loader) {\n const key = routeId + ':' + locale\n __routeLoaders[key] = loader\n}\n\nasync function __loadRoute(routeId, locale) {\n const key = routeId + ':' + (locale || __currentLocale)\n if (__loadedRoutes.has(key)) return\n const loader = __routeLoaders[key]\n if (!loader) return\n const mod = __normalizeMessages(await loader())\n Object.assign(__catalog, mod)\n __loadedRoutes.add(key)\n}\n\nasync function __switchLocale(locale) {\n if (locale === __currentLocale) return\n __loading = true\n try {\n if (__cache.has(locale)) {\n Object.assign(__catalog, __cache.get(locale))\n } else {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n Object.assign(__catalog, mod)\n }\n __loadedLocales.add(locale)\n __currentLocale = locale\n } finally {\n __loading = false\n }\n}\n\nasync function __preloadLocale(locale) {\n if (__cache.has(locale) || !__loaders[locale]) return\n try {\n const mod = __normalizeMessages(await __loaders[locale]())\n __cache.set(locale, mod)\n __loadedLocales.add(locale)\n } catch (e) { console.warn('[fluenti] preload failed:', locale, e) }\n}\n\nglobalThis[Symbol.for('${runtimeKey}')] = { __switchLocale, __preloadLocale }\n\nexport { __catalog, __switchLocale, __preloadLocale, __loadRoute, __registerRouteLoader, __currentLocale, __loading, __loadedLocales }\n`\n },\n}\n","import type { Plugin } from 'vite'\nimport type { FluentiPluginOptions } from '@fluenti/vite-plugin'\nimport { createFluentiPlugins } from '@fluenti/vite-plugin'\nimport { reactRuntimeGenerator } from './react-runtime'\n\nexport type { FluentiPluginOptions as FluentiReactOptions } from '@fluenti/vite-plugin'\n\nexport default function fluentiReact(options?: FluentiPluginOptions): Plugin[] {\n return createFluentiPlugins(\n { ...options, framework: 'react' },\n [],\n reactRuntimeGenerator,\n )\n}\n"],"mappings":";;;AAGA,IAAa,IAA0C;CACrD,gBAAgB,GAA0C;EACxD,IAAM,EAAE,eAAY,YAAS,iBAAc,0BAAuB,GAC5D,IAAgB,KAAsB,GACtC,IAAqB,EAAQ,QAAQ,KAAK,EAAE,EAAW;AAI7D,SAAO;6BACkB,EAAmB,GAAG,EAAc;;;yBAGxC,EAAc;oCACH,EAAc;;;;;;EAP1B,EAAQ,QAAQ,MAAW,MAAW,EAAc,CAa9D,KAAK,MAAM,MAAM,EAAE,mBAAmB,EAAmB,GAAG,EAAE,QAAQ,CAAC,KAAK,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC9F,qBAAqB,GAA0C;EAC7D,IAAM,EAAE,eAAY,YAAS,iBAAc,0BAAuB,GAC5D,IAAgB,KAAsB,GACtC,IAAqB,EAAQ,QAAQ,KAAK,EAAE,EAAW;AAI7D,SAAO;6BACkB,EAAmB,GAAG,EAAc;;;yBAGxC,EAAc;oCACH,EAAc;;;;;;;EAP1B,EAAQ,QAAQ,MAAW,MAAW,EAAc,CAc9D,KAAK,MAAM,MAAM,EAAE,mBAAmB,EAAmB,GAAG,EAAE,QAAQ,CAAC,KAAK,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoD/F;;;AC1HD,SAAwB,EAAa,GAA0C;AAC7E,QAAO,EACL;EAAE,GAAG;EAAS,WAAW;EAAS,EAClC,EAAE,EACF,EACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluenti/react",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "React bindings for Fluenti — I18nProvider, useI18n, Trans/Plural/Select components",
6
6
  "homepage": "https://fluenti.dev",
@@ -49,16 +49,33 @@
49
49
  "types": "./dist/server.d.ts",
50
50
  "default": "./dist/server.cjs"
51
51
  }
52
+ },
53
+ "./vite-plugin": {
54
+ "import": {
55
+ "types": "./dist/vite-plugin.d.ts",
56
+ "default": "./dist/vite-plugin.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/vite-plugin.d.ts",
60
+ "default": "./dist/vite-plugin.cjs"
61
+ }
52
62
  }
53
63
  },
54
64
  "files": [
55
65
  "dist"
56
66
  ],
57
67
  "peerDependencies": {
58
- "react": ">=18.0.0"
68
+ "react": ">=18.0.0",
69
+ "vite": "^5 || ^6 || ^8"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "vite": {
73
+ "optional": true
74
+ }
59
75
  },
60
76
  "dependencies": {
61
- "@fluenti/core": "0.1.3"
77
+ "@fluenti/core": "0.2.0",
78
+ "@fluenti/vite-plugin": "0.2.0"
62
79
  },
63
80
  "devDependencies": {
64
81
  "typescript": "^5.9",