@idioma/react 0.1.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.
@@ -0,0 +1,36 @@
1
+ import { TransComponent } from "./interpolate.js";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/Trans.d.ts
5
+ type MessageFunction = (args: any) => string | ReactNode;
6
+ type LocaleMessages = Record<string, string | MessageFunction>;
7
+ interface TransProps {
8
+ /** Translation object keyed by locale (may be undefined if Babel didn't transform) */
9
+ __t?: LocaleMessages;
10
+ /** Arguments for interpolation (named like "user.name" or numbered like "0") */
11
+ __a?: Record<string, unknown>;
12
+ /** Components for tag interpolation */
13
+ __c?: TransComponent[];
14
+ /** Component names for named tag matching (parallel to __c array) */
15
+ __cn?: string[];
16
+ /** Children (used as fallback when __t is missing) */
17
+ children?: ReactNode;
18
+ }
19
+ /**
20
+ * Internal Trans component used by Babel-compiled output.
21
+ * Not intended for direct use - import Trans from the generated idioma/ folder instead.
22
+ *
23
+ * @example
24
+ * // Compiled output:
25
+ * <__Trans __t={__$idioma["key"]} __a={{ name }} __c={[Link]} />
26
+ */
27
+ declare function __Trans({
28
+ __t,
29
+ __a,
30
+ __c,
31
+ __cn,
32
+ children
33
+ }: TransProps): ReactNode;
34
+ //#endregion
35
+ export { TransProps, __Trans };
36
+ //# sourceMappingURL=Trans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Trans.d.ts","names":[],"sources":["../src/Trans.tsx"],"sourcesContent":[],"mappings":";;;;KAKK,eAAA,2BAA0C;KAC1C,cAAA,GAAiB,wBAAwB;AADzC,UAGY,UAAA,CAHG;EACf;EAEY,GAAA,CAAA,EAET,cAFmB;EAEnB;EAEA,GAAA,CAAA,EAAA,MAAA,CAAA,MAAA,EAAA,OAAA,CAAA;EAEA;EAIK,GAAA,CAAA,EAJL,cAIK,EAAA;EAAS;EAWN,IAAA,CAAA,EAAA,MAAO,EAAA;EACrB;EACA,QAAA,CAAA,EAbW,SAaX;;;;;;;;;;iBAFc,OAAA;;;;;;GAMb,aAAa"}
package/dist/Trans.js ADDED
@@ -0,0 +1,2 @@
1
+ import{IdiomaContext as e}from"./context.js";import{renderMessage as t}from"./interpolate.js";import{useContext as n}from"react";function r({__t:r,__a:i,__c:a,__cn:o,children:s}){let c=n(e);if(!c)throw Error(`[idioma] Trans must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);if(!r)return process.env.NODE_ENV!==`production`&&console.error(`Idioma: Missing translations. Ensure the Babel plugin is configured.`),s??null;let{locale:l}=c,u=r[l];if(u===void 0){let e=Object.values(r)[0];return e===void 0?null:t(e,i,a,o)}return t(u,i,a,o)}export{r as __Trans};
2
+ //# sourceMappingURL=Trans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Trans.js","names":["useContext","IdiomaContext","renderMessage","__Trans","__t","__a","__c","__cn","children","context","Error","process","env","NODE_ENV","console","error","locale","msg","undefined","fallback","Object","values"],"sources":["../src/Trans.tsx"],"sourcesContent":["import { useContext, type ReactNode } from 'react';\nimport { IdiomaContext } from './context';\nimport { renderMessage, type TransComponent } from './interpolate';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MessageFunction = (args: any) => string | ReactNode;\ntype LocaleMessages = Record<string, string | MessageFunction>;\n\nexport interface TransProps {\n /** Translation object keyed by locale (may be undefined if Babel didn't transform) */\n __t?: LocaleMessages;\n /** Arguments for interpolation (named like \"user.name\" or numbered like \"0\") */\n __a?: Record<string, unknown>;\n /** Components for tag interpolation */\n __c?: TransComponent[];\n /** Component names for named tag matching (parallel to __c array) */\n __cn?: string[];\n /** Children (used as fallback when __t is missing) */\n children?: ReactNode;\n}\n\n/**\n * Internal Trans component used by Babel-compiled output.\n * Not intended for direct use - import Trans from the generated idioma/ folder instead.\n *\n * @example\n * // Compiled output:\n * <__Trans __t={__$idioma[\"key\"]} __a={{ name }} __c={[Link]} />\n */\nexport function __Trans({\n __t,\n __a,\n __c,\n __cn,\n children,\n}: TransProps): ReactNode {\n const context = useContext(IdiomaContext);\n if (!context) {\n throw new Error(\n '[idioma] Trans must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n\n // Graceful fallback when Babel didn't transform (missing __t)\n if (!__t) {\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n 'Idioma: Missing translations. Ensure the Babel plugin is configured.',\n );\n }\n return children ?? null;\n }\n\n const { locale } = context;\n const msg = __t[locale];\n\n if (msg === undefined) {\n // Fallback: try to find any available message\n const fallback = Object.values(__t)[0];\n if (fallback === undefined) {\n return null;\n }\n return renderMessage(fallback, __a, __c, __cn);\n }\n\n return renderMessage(msg, __a, __c, __cn);\n}\n"],"mappings":"iIA6BA,SAAgBG,EAAQ,CACtBC,MACAC,MACAC,MACAC,OACAC,YACwB,CACxB,IAAMC,EAAUT,EAAWC,EAAc,CACzC,GAAI,CAACQ,EACH,MAAUC,MACR,0GAED,CAIH,GAAI,CAACN,EAMH,OALIO,QAAQC,IAAIC,WAAa,cAC3BC,QAAQC,MACN,uEACD,CAEIP,GAAY,KAGrB,GAAM,CAAEQ,UAAWP,EACbQ,EAAMb,EAAIY,GAEhB,GAAIC,IAAQC,IAAAA,GAAW,CAErB,IAAMC,EAAWC,OAAOC,OAAOjB,EAAI,CAAC,GAIpC,OAHIe,IAAaD,IAAAA,GACR,KAEFhB,EAAciB,EAAUd,EAAKC,EAAKC,EAAK,CAGhD,OAAOL,EAAce,EAAKZ,EAAKC,EAAKC,EAAK"}
@@ -0,0 +1,48 @@
1
+ import * as react0 from "react";
2
+ import { ReactNode } from "react";
3
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
+
5
+ //#region src/context.d.ts
6
+ interface IdiomaContextValue {
7
+ locale: string;
8
+ }
9
+ declare const IdiomaContext: react0.Context<IdiomaContextValue | null>;
10
+ interface IdiomaProviderProps {
11
+ children: ReactNode;
12
+ locale: string;
13
+ }
14
+ /**
15
+ * Creates an IdiomaProvider component that provides locale context to children.
16
+ *
17
+ * @example
18
+ * const IdiomaProvider = createIdiomaProvider()
19
+ *
20
+ * function App() {
21
+ * return (
22
+ * <IdiomaProvider locale="en">
23
+ * <Router />
24
+ * </IdiomaProvider>
25
+ * )
26
+ * }
27
+ */
28
+ declare function createIdiomaProvider(): ({
29
+ children,
30
+ locale
31
+ }: IdiomaProviderProps) => react_jsx_runtime0.JSX.Element;
32
+ /**
33
+ * Creates a useLocale hook that returns the current locale from context.
34
+ *
35
+ * @throws Error if used outside of IdiomaProvider
36
+ *
37
+ * @example
38
+ * const useLocale = createUseLocale()
39
+ *
40
+ * function Component() {
41
+ * const locale = useLocale()
42
+ * return <div>Current locale: {locale}</div>
43
+ * }
44
+ */
45
+ declare function createUseLocale(): () => string;
46
+ //#endregion
47
+ export { IdiomaContext, IdiomaContextValue, IdiomaProviderProps, createIdiomaProvider, createUseLocale };
48
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","names":[],"sources":["../src/context.tsx"],"sourcesContent":[],"mappings":";;;;;UASiB,kBAAA;;;cAIJ,eAAa,MAAA,CAAA,QAAA;AAJT,UAMA,mBAAA,CANkB;EAItB,QAAA,EAGD,SAH+D;EAE1D,MAAA,EAAA,MAAA;AAmBjB;;;;;;AAgCA;;;;;;;;;iBAhCgB,oBAAA,CAAA;;;GACuC,wBAAmB,kBAAA,CAAA,GAAA,CAAA;;;;;;;;;;;;;;iBA+B1D,eAAA,CAAA"}
@@ -0,0 +1,2 @@
1
+ import{createContext as e,useContext as t,useEffect as n,useMemo as r}from"react";import{_syncLocale as i}from"@idioma/core/icu";import{jsx as a}from"react/jsx-runtime";const o=e(null);function s(){return function({children:e,locale:t}){let s=r(()=>({locale:t}),[t]);return i(t),n(()=>{i(t)},[t]),a(o.Provider,{value:s,children:e})}}function c(){return function(){let e=t(o);if(!e)throw Error(`[idioma] useLocale must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);return e.locale}}export{o as IdiomaContext,s as createIdiomaProvider,c as createUseLocale};
2
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","names":["_syncLocale","createContext","useContext","useEffect","useMemo","IdiomaContext","createIdiomaProvider","IdiomaProvider","children","locale","value","createUseLocale","useLocale","context","Error"],"sources":["../src/context.tsx"],"sourcesContent":["import { _syncLocale } from '@idioma/core/icu';\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n type ReactNode,\n} from 'react';\n\nexport interface IdiomaContextValue {\n locale: string;\n}\n\nexport const IdiomaContext = createContext<IdiomaContextValue | null>(null);\n\nexport interface IdiomaProviderProps {\n children: ReactNode;\n locale: string;\n}\n\n/**\n * Creates an IdiomaProvider component that provides locale context to children.\n *\n * @example\n * const IdiomaProvider = createIdiomaProvider()\n *\n * function App() {\n * return (\n * <IdiomaProvider locale=\"en\">\n * <Router />\n * </IdiomaProvider>\n * )\n * }\n */\nexport function createIdiomaProvider() {\n return function IdiomaProvider({ children, locale }: IdiomaProviderProps) {\n const value = useMemo(() => ({ locale }), [locale]);\n\n // Sync locale for plural() function fallback\n // Sync immediately for SSR (before effects run)\n _syncLocale(locale);\n\n // Also sync in effect for client-side updates\n useEffect(() => {\n _syncLocale(locale);\n }, [locale]);\n\n return (\n <IdiomaContext.Provider value={value}>{children}</IdiomaContext.Provider>\n );\n };\n}\n\n/**\n * Creates a useLocale hook that returns the current locale from context.\n *\n * @throws Error if used outside of IdiomaProvider\n *\n * @example\n * const useLocale = createUseLocale()\n *\n * function Component() {\n * const locale = useLocale()\n * return <div>Current locale: {locale}</div>\n * }\n */\nexport function createUseLocale() {\n return function useLocale(): string {\n const context = useContext(IdiomaContext);\n if (!context) {\n throw new Error(\n '[idioma] useLocale must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n return context.locale;\n };\n}\n"],"mappings":"yKAaA,MAAaK,EAAgBJ,EAAyC,KAAK,CAqB3E,SAAgBK,GAAuB,CACrC,OAAO,SAAwB,CAAEE,WAAUC,UAA+B,CACxE,IAAMC,EAAQN,OAAe,CAAEK,SAAQ,EAAG,CAACA,EAAO,CAAC,CAWnD,OAPAT,EAAYS,EAAO,CAGnBN,MAAgB,CACdH,EAAYS,EAAO,EAClB,CAACA,EAAO,CAAC,CAGV,EAAC,EAAc,SAAA,CAAgBC,QAAQF,YAAkC,EAkB/E,SAAgBG,GAAkB,CAChC,OAAO,UAA6B,CAClC,IAAME,EAAUX,EAAWG,EAAc,CACzC,GAAI,CAACQ,EACH,MAAUC,MACR,8GAED,CAEH,OAAOD,EAAQJ"}
@@ -0,0 +1,81 @@
1
+ import { TransComponent } from "./interpolate.js";
2
+ import { BaseIdiomaConfig } from "./createUseT.js";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/createTrans.d.ts
6
+
7
+ /**
8
+ * Inline mode props - when children are present.
9
+ * Used for development and extracted by Babel in production.
10
+ */
11
+ interface TransInlineModeProps {
12
+ children: ReactNode;
13
+ /** Optional explicit key (overrides auto-hash) */
14
+ id?: string;
15
+ /** Translator context for key disambiguation (affects hash) */
16
+ context?: string;
17
+ /** Translator comment (extracted to PO #. comment for translators) */
18
+ comment?: string;
19
+ /** Namespace for large apps */
20
+ ns?: string;
21
+ }
22
+ /**
23
+ * Base props for key-only mode.
24
+ */
25
+ interface TransKeyOnlyBaseProps<K extends string> {
26
+ /** Translation key - must be a valid key from PO */
27
+ id: K;
28
+ /** No children allowed in key-only mode */
29
+ children?: never;
30
+ /** No context in key-only mode - message is defined in PO */
31
+ context?: never;
32
+ /** Namespace for large apps */
33
+ ns?: string;
34
+ }
35
+ /**
36
+ * Conditional values prop - required if message has placeholders.
37
+ */
38
+ type TransValuesProps<K extends string, MV extends Record<string, Record<string, unknown>>> = K extends keyof MV ? MV[K] extends Record<string, never> ? {
39
+ values?: never;
40
+ } : {
41
+ values: MV[K];
42
+ } : {
43
+ values?: Record<string, unknown>;
44
+ };
45
+ /**
46
+ * Conditional components prop - required if message has component tags.
47
+ */
48
+ type TransComponentsProps<K extends string, MC extends Record<string, TransComponent[]>> = K extends keyof MC ? MC[K] extends [] ? {
49
+ components?: never;
50
+ } : {
51
+ components: MC[K];
52
+ } : {
53
+ components?: TransComponent[];
54
+ };
55
+ /**
56
+ * Key-only mode props - when looking up by id.
57
+ * Values are required if the message has {placeholders}.
58
+ * Components are required if the message has <0>tags</0>.
59
+ */
60
+ type TransKeyOnlyModeProps<K extends string, MV extends Record<string, Record<string, unknown>>, MC extends Record<string, TransComponent[]>> = TransKeyOnlyBaseProps<K> & TransValuesProps<K, MV> & TransComponentsProps<K, MC>;
61
+ /**
62
+ * Creates a typed Trans component that supports both inline and key-only modes.
63
+ *
64
+ * @example
65
+ * // In generated idioma/index.ts:
66
+ * export const Trans = createTrans<IdiomaTypes>()
67
+ *
68
+ * // Inline mode (dev, Babel extracts):
69
+ * <Trans>Hello {name}</Trans>
70
+ *
71
+ * // Key-only mode (typed lookup):
72
+ * <Trans id="welcome" values={{ name: "Ben" }} />
73
+ * <Trans id="legal.links" components={[TermsLink, PrivacyLink]} />
74
+ */
75
+ declare function createTrans<C extends BaseIdiomaConfig = BaseIdiomaConfig>(): {
76
+ (props: TransInlineModeProps): ReactNode;
77
+ <K extends C["TranslationKey"]>(props: TransKeyOnlyModeProps<K & string, C["MessageValues"], C["MessageComponents"] & Record<string, TransComponent[]>>): ReactNode;
78
+ };
79
+ //#endregion
80
+ export { TransInlineModeProps, TransKeyOnlyModeProps, createTrans };
81
+ //# sourceMappingURL=createTrans.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTrans.d.ts","names":[],"sources":["../src/createTrans.tsx"],"sourcesContent":[],"mappings":";;;;;;;;AASA;AAUC;AAmBI,UA7BY,oBAAA,CA6BI;EAEO,QAAA,EA9BhB,SA8BgB;EAAf;EACT,EAAA,CAAA,EAAA,MAAA;EAAgB;EAChB,OAAA,CAAA,EAAA,MAAA;EAAG;EAAW,OAAA,CAAA,EAAA,MAAA;EAEF;EAAG,EAAA,CAAA,EAAA,MAAA;;;AACE;;UArBX,qBA4BG,CAAA,UAAA,MAAA,CAAA,CAAA;EACT;EAAgB,EAAA,EA3Bd,CA2Bc;EAChB;EAAG,QAAA,CAAA,EAAA,KAAA;EAEa;EAAG,OAAA,CAAA,EAAA,KAAA;EACJ;EAAc,EAAA,CAAA,EAAA,MAAA;AAOjC;;;;KA1BK,gBA6BQ,CAAA,UAAA,MAAA,EAAA,WA3BA,MA2BA,CAAA,MAAA,EA3Be,MA2Bf,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,CAAA,GA1BT,CA0BS,SAAA,MA1BO,EA0BP,GAzBT,EAyBS,CAzBN,CAyBM,CAAA,SAzBK,MAyBL,CAAA,MAAA,EAAA,KAAA,CAAA,GAAA;EACa,MAAA,CAAA,EAAA,KAAA;CAAtB,GAAA;EACe,MAAA,EAzBH,EAyBG,CAzBA,CAyBA,CAAA;CAAG,GAAA;EAApB,MAAA,CAAA,EAxBa,MAwBb,CAAA,MAAA,EAAA,OAAA,CAAA;CACqB;;;;AAgBvB,KApCK,oBAoCsB,CAAA,UAAA,MAAA,EAAA,WAlCd,MAkCc,CAAA,MAAA,EAlCC,cAkCD,EAAA,CAAA,CAAA,GAjCvB,CAiCuB,SAAA,MAjCP,EAiCO,GAhCvB,EAgCuB,CAhCpB,CAgCoB,CAAA,SAAA,EAAA,GAAA;EAAW,UAAA,CAAA,EAAA,KAAA;CAAmB,GAAA;EAajC,UAAA,EA3CJ,EA2CI,CA3CD,CA2CC,CAAA;CAAuB,GAAA;EAC7B,UAAA,CAAA,EA3CC,cA2CD,EAAA;CAEZ;;;;;;AAID,KA1CO,qBA0CP,CAAA,UAAA,MAAA,EAAA,WAxCQ,MAwCR,CAAA,MAAA,EAxCuB,MAwCvB,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,EAAA,WAvCQ,MAuCR,CAAA,MAAA,EAvCuB,cAuCvB,EAAA,CAAA,CAAA,GAtCD,qBAsCC,CAtCqB,CAsCrB,CAAA,GArCH,gBAqCG,CArCc,CAqCd,EArCiB,EAqCjB,CAAA,GApCH,oBAoCG,CApCkB,CAoClB,EApCqB,EAoCrB,CAAA;;;;;;;;;;;;;;;iBApBW,sBAAsB,mBAAmB;UAajC,uBAAuB;aAC7B,4BACP,sBACL,YAAU,oBAEV,yBAAK,eAAe,qBAErB"}
@@ -0,0 +1,2 @@
1
+ import{IdiomaContext as e}from"./context.js";import{useContext as t}from"react";function n(){function n(n){if(!t(e))throw Error(`[idioma] Trans must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);if(`children`in n&&n.children!==void 0)return n.children;let{id:r}=n;if(r)return console.warn(`[idioma] Trans with id="${r}" was not transformed by Babel. Make sure @idioma/core Babel plugin is configured.`),r;throw Error(`[idioma] Trans requires either children or id prop`)}return n}export{n as createTrans};
2
+ //# sourceMappingURL=createTrans.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTrans.js","names":["useContext","IdiomaContext","createTrans","Trans","props","ctx","Error","children","undefined","id","console","warn"],"sources":["../src/createTrans.tsx"],"sourcesContent":["import { useContext, type ReactNode } from 'react';\nimport { IdiomaContext } from './context';\nimport { type BaseIdiomaConfig } from './createUseT';\nimport { type TransComponent } from './interpolate';\n\n/**\n * Inline mode props - when children are present.\n * Used for development and extracted by Babel in production.\n */\nexport interface TransInlineModeProps {\n children: ReactNode;\n /** Optional explicit key (overrides auto-hash) */\n id?: string;\n /** Translator context for key disambiguation (affects hash) */\n context?: string;\n /** Translator comment (extracted to PO #. comment for translators) */\n comment?: string;\n /** Namespace for large apps */\n ns?: string;\n}\n\n/**\n * Base props for key-only mode.\n */\ninterface TransKeyOnlyBaseProps<K extends string> {\n /** Translation key - must be a valid key from PO */\n id: K;\n /** No children allowed in key-only mode */\n children?: never;\n /** No context in key-only mode - message is defined in PO */\n context?: never;\n /** Namespace for large apps */\n ns?: string;\n}\n\n/**\n * Conditional values prop - required if message has placeholders.\n */\ntype TransValuesProps<\n K extends string,\n MV extends Record<string, Record<string, unknown>>,\n> = K extends keyof MV\n ? MV[K] extends Record<string, never>\n ? { values?: never }\n : { values: MV[K] }\n : { values?: Record<string, unknown> };\n\n/**\n * Conditional components prop - required if message has component tags.\n */\ntype TransComponentsProps<\n K extends string,\n MC extends Record<string, TransComponent[]>,\n> = K extends keyof MC\n ? MC[K] extends []\n ? { components?: never }\n : { components: MC[K] }\n : { components?: TransComponent[] };\n\n/**\n * Key-only mode props - when looking up by id.\n * Values are required if the message has {placeholders}.\n * Components are required if the message has <0>tags</0>.\n */\nexport type TransKeyOnlyModeProps<\n K extends string,\n MV extends Record<string, Record<string, unknown>>,\n MC extends Record<string, TransComponent[]>,\n> = TransKeyOnlyBaseProps<K> &\n TransValuesProps<K, MV> &\n TransComponentsProps<K, MC>;\n\n/**\n * Creates a typed Trans component that supports both inline and key-only modes.\n *\n * @example\n * // In generated idioma/index.ts:\n * export const Trans = createTrans<IdiomaTypes>()\n *\n * // Inline mode (dev, Babel extracts):\n * <Trans>Hello {name}</Trans>\n *\n * // Key-only mode (typed lookup):\n * <Trans id=\"welcome\" values={{ name: \"Ben\" }} />\n * <Trans id=\"legal.links\" components={[TermsLink, PrivacyLink]} />\n */\nexport function createTrans<C extends BaseIdiomaConfig = BaseIdiomaConfig>() {\n // Extract types from config\n type TK = C['TranslationKey'];\n type MV = C['MessageValues'];\n type MC = C['MessageComponents'];\n\n /**\n * Trans component for inline translations.\n *\n * In development and production, Babel transforms Trans to __Trans with\n * inlined translations. This function exists for type safety and to provide\n * a fallback when Babel hasn't transformed the code.\n */\n function Trans(props: TransInlineModeProps): ReactNode;\n function Trans<K extends TK>(\n props: TransKeyOnlyModeProps<\n K & string,\n MV,\n MC & Record<string, TransComponent[]>\n >,\n ): ReactNode;\n function Trans(\n props:\n | TransInlineModeProps\n | TransKeyOnlyModeProps<\n TK & string,\n MV,\n MC & Record<string, TransComponent[]>\n >,\n ): ReactNode {\n const ctx = useContext(IdiomaContext);\n if (!ctx) {\n throw new Error(\n '[idioma] Trans must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n\n // Inline mode: children present - render them directly\n // Babel transforms this to __Trans with inlined translations\n if ('children' in props && props.children !== undefined) {\n return props.children;\n }\n\n // Key-only mode: Babel should transform this too\n // If we reach here, Babel hasn't transformed the code\n const { id } = props as TransKeyOnlyModeProps<\n TK & string,\n MV,\n MC & Record<string, TransComponent[]>\n >;\n if (id) {\n console.warn(\n `[idioma] Trans with id=\"${id}\" was not transformed by Babel. ` +\n 'Make sure @idioma/core Babel plugin is configured.',\n );\n return id;\n }\n\n throw new Error('[idioma] Trans requires either children or id prop');\n }\n\n return Trans;\n}\n"],"mappings":"gFAsFA,SAAgBE,GAA6D,CAqB3E,SAAAC,EAAAC,EAAA,CASwC,GAAA,CAA1BJ,EAAAC,EAAyB,CAC7B,MAAAK,MAEJ,0GACqD,CAAA,GAMrD,aAAcF,GAASA,EAAKG,WAAAC,IAAAA,GAAuB,OAC9CJ,EAAKG,SAKd,GAAA,CAAAE,MAAeL,EAIb,GACEK,EAID,OAHDC,QAAAC,KACE,2BAA2BF,EAAE,oFAE9B,CACMA,EAAE,MAAAH,MAGK,qDAAoD,CAGtE,OAAOH"}
@@ -0,0 +1,85 @@
1
+ //#region src/createUseT.d.ts
2
+ /**
3
+ * Base interface for project-specific Idioma types.
4
+ * Generated by the compiler in types.d.ts.
5
+ *
6
+ * @example
7
+ * // In generated types.d.ts:
8
+ * export interface IdiomaTypes {
9
+ * TranslationKey: 'greeting' | 'farewell';
10
+ * MessageValues: { greeting: { name: string } };
11
+ * MessageComponents: { greeting: [] };
12
+ * }
13
+ */
14
+ interface BaseIdiomaConfig {
15
+ TranslationKey: string;
16
+ MessageValues: Record<string, Record<string, unknown>>;
17
+ MessageComponents: Record<string, unknown[]>;
18
+ }
19
+ /** Keys in MessageValues that have at least one required value */
20
+ type KeysWithValues<MV extends Record<string, Record<string, unknown>>> = { [K in keyof MV]: MV[K] extends Record<string, never> ? never : K }[keyof MV];
21
+ /** Keys in MessageValues that have no required values */
22
+ type KeysWithoutValues<MV extends Record<string, Record<string, unknown>>> = { [K in keyof MV]: MV[K] extends Record<string, never> ? K : never }[keyof MV];
23
+ /**
24
+ * Derive string-only keys from TranslationKey + MessageComponents.
25
+ * Keys with empty component arrays [] can be used with useT.
26
+ * Keys with non-empty component arrays [TransComponent, ...] must use Trans.
27
+ */
28
+ type StringOnlyKeys<TK extends string, MC extends Record<string, unknown[]>> = { [K in TK & keyof MC]: MC[K] extends [] ? K : never }[TK & keyof MC];
29
+ /**
30
+ * Recursively extract placeholder names from a template string.
31
+ * "Hello {name}, you have {count} items" → "name" | "count"
32
+ */
33
+ type ExtractPlaceholders<S extends string> = S extends `${infer _}{${infer Key}}${infer Rest}` ? Key | ExtractPlaceholders<Rest> : never;
34
+ /**
35
+ * Build the required values object from extracted placeholders.
36
+ * "Hello {name}" → { name: string | number }
37
+ * "No placeholders" → undefined
38
+ */
39
+ type PlaceholderValues<S extends string> = ExtractPlaceholders<S> extends never ? undefined : { [K in ExtractPlaceholders<S>]: string | number };
40
+ /** Check if a string has any placeholders */
41
+ type HasPlaceholders<S extends string> = ExtractPlaceholders<S> extends never ? false : true;
42
+ /**
43
+ * Source text mode options: t("Submit", undefined, { context: "button", ns: "common" })
44
+ */
45
+ interface SourceTextOptions {
46
+ /** Translator context (changes key hash) */
47
+ context?: string;
48
+ /** Translator comment (extracted to PO #. comment for translators) */
49
+ comment?: string;
50
+ /** Namespace for scoped lookups (placeholder for future) */
51
+ ns?: string;
52
+ }
53
+ /**
54
+ * Type for the t function returned by useT.
55
+ * Supports both source text mode and key-only mode with full type safety.
56
+ *
57
+ * @template C - IdiomaTypes interface containing TranslationKey, MessageValues, MessageComponents
58
+ *
59
+ * The TFunction only accepts keys where MessageComponents[K] is an empty array (string-only keys).
60
+ */
61
+ type TFunction<C extends BaseIdiomaConfig = BaseIdiomaConfig> = {
62
+ <K$1 extends StringOnlyKeys<C['TranslationKey'], C['MessageComponents']> & string>(args: K$1 extends KeysWithValues<C['MessageValues']> ? {
63
+ id: K$1;
64
+ values: C['MessageValues'][K$1];
65
+ context?: string;
66
+ ns?: string;
67
+ } : K$1 extends KeysWithoutValues<C['MessageValues']> ? {
68
+ id: K$1;
69
+ values?: never;
70
+ context?: string;
71
+ ns?: string;
72
+ } : {
73
+ id: K$1;
74
+ values?: Record<string, unknown>;
75
+ context?: string;
76
+ ns?: string;
77
+ }): string;
78
+ <S extends string>(source: HasPlaceholders<S> extends true ? S : never, values: PlaceholderValues<S>): string;
79
+ <S extends string>(source: HasPlaceholders<S> extends false ? S : never): string;
80
+ <S extends string>(source: S, values: PlaceholderValues<S>, options: SourceTextOptions): string;
81
+ };
82
+ declare function createUseT<C extends BaseIdiomaConfig = BaseIdiomaConfig>(): () => TFunction<C>;
83
+ //#endregion
84
+ export { BaseIdiomaConfig, ExtractPlaceholders, HasPlaceholders, KeysWithValues, KeysWithoutValues, PlaceholderValues, SourceTextOptions, StringOnlyKeys, TFunction, createUseT };
85
+ //# sourceMappingURL=createUseT.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createUseT.d.ts","names":[],"sources":["../src/createUseT.ts"],"sourcesContent":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{IdiomaContext as e}from"./context.js";import{interpolateValues as t}from"./interpolate.js";import{useCallback as n,useContext as r}from"react";function i(){return function(){let i=r(e);if(!i)throw Error(`[idioma] useT must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);let{locale:a}=i;return n((e,n,r)=>{if(typeof e==`object`){let{id:t}=e;return console.warn(`[idioma] t({ id: "${t}" }) was not transformed by Babel. Make sure @idioma/core Babel plugin is configured.`),t}let i=e,o=n,s,c=n?Object.keys(n):[],l=c[0],u=l&&n?n[l]:void 0;if(c.length===1&&u&&typeof u==`object`&&u[a]!==void 0&&(s=u,o=r),!s)return o&&Object.keys(o).length>0?t(i,o):i;let d=s[a]??Object.values(s)[0];return d===void 0?o&&Object.keys(o).length>0?t(i,o):i:typeof d==`function`?d(o||{}):o&&Object.keys(o).length>0?t(d,o):d},[a])}}export{i as createUseT};
2
+ //# sourceMappingURL=createUseT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createUseT.js","names":["useCallback","useContext","IdiomaContext","interpolateValues","createUseT","useT","ctx","Error","locale","sourceOrArgs","values","options","id","console","warn","source","actualValues","localeMessages","valuesKeys","Object","keys","firstKey","firstValue","undefined","length","msg"],"sources":["../src/createUseT.ts"],"sourcesContent":["import { useCallback, useContext } from 'react';\nimport { IdiomaContext } from './context';\nimport { interpolateValues } from './interpolate';\n\ntype MessageFunction = (args: Record<string, unknown>) => string;\n\n// =============================================================================\n// IdiomaTypes - Single type parameter for cleaner API\n// =============================================================================\n\n/**\n * Base interface for project-specific Idioma types.\n * Generated by the compiler in types.d.ts.\n *\n * @example\n * // In generated types.d.ts:\n * export interface IdiomaTypes {\n * TranslationKey: 'greeting' | 'farewell';\n * MessageValues: { greeting: { name: string } };\n * MessageComponents: { greeting: [] };\n * }\n */\nexport interface BaseIdiomaConfig {\n TranslationKey: string;\n MessageValues: Record<string, Record<string, unknown>>;\n MessageComponents: Record<string, unknown[]>;\n}\n\n// =============================================================================\n// Helper Types for Strict Typing\n// =============================================================================\n\n// --- Key-only mode helpers ---\n\n/** Keys in MessageValues that have at least one required value */\nexport type KeysWithValues<MV extends Record<string, Record<string, unknown>>> =\n {\n [K in keyof MV]: MV[K] extends Record<string, never> ? never : K;\n }[keyof MV];\n\n/** Keys in MessageValues that have no required values */\nexport type KeysWithoutValues<\n MV extends Record<string, Record<string, unknown>>,\n> = {\n [K in keyof MV]: MV[K] extends Record<string, never> ? K : never;\n}[keyof MV];\n\n/**\n * Derive string-only keys from TranslationKey + MessageComponents.\n * Keys with empty component arrays [] can be used with useT.\n * Keys with non-empty component arrays [TransComponent, ...] must use Trans.\n */\nexport type StringOnlyKeys<\n TK extends string,\n MC extends Record<string, unknown[]>,\n> = {\n [K in TK & keyof MC]: MC[K] extends [] ? K : never;\n}[TK & keyof MC];\n\n// --- Source text mode helpers ---\n\n/**\n * Recursively extract placeholder names from a template string.\n * \"Hello {name}, you have {count} items\" → \"name\" | \"count\"\n */\nexport type ExtractPlaceholders<S extends string> =\n S extends `${infer _}{${infer Key}}${infer Rest}`\n ? Key | ExtractPlaceholders<Rest>\n : never;\n\n/**\n * Build the required values object from extracted placeholders.\n * \"Hello {name}\" → { name: string | number }\n * \"No placeholders\" → undefined\n */\nexport type PlaceholderValues<S extends string> =\n ExtractPlaceholders<S> extends never\n ? undefined\n : { [K in ExtractPlaceholders<S>]: string | number };\n\n/** Check if a string has any placeholders */\nexport type HasPlaceholders<S extends string> =\n ExtractPlaceholders<S> extends never ? false : true;\n\n/**\n * Source text mode options: t(\"Submit\", undefined, { context: \"button\", ns: \"common\" })\n */\nexport interface SourceTextOptions {\n /** Translator context (changes key hash) */\n context?: string;\n /** Translator comment (extracted to PO #. comment for translators) */\n comment?: string;\n /** Namespace for scoped lookups (placeholder for future) */\n ns?: string;\n}\n\n/**\n * Type for the t function returned by useT.\n * Supports both source text mode and key-only mode with full type safety.\n *\n * @template C - IdiomaTypes interface containing TranslationKey, MessageValues, MessageComponents\n *\n * The TFunction only accepts keys where MessageComponents[K] is an empty array (string-only keys).\n */\nexport type TFunction<C extends BaseIdiomaConfig = BaseIdiomaConfig> = {\n // === Key-only mode ===\n\n // Keys that require values (only string-only keys allowed)\n <\n K extends StringOnlyKeys<C['TranslationKey'], C['MessageComponents']> &\n string,\n >(\n args: K extends KeysWithValues<C['MessageValues']>\n ? { id: K; values: C['MessageValues'][K]; context?: string; ns?: string }\n : K extends KeysWithoutValues<C['MessageValues']>\n ? { id: K; values?: never; context?: string; ns?: string }\n : {\n id: K;\n values?: Record<string, unknown>;\n context?: string;\n ns?: string;\n },\n ): string;\n\n // === Source text mode ===\n\n // Source text WITH placeholders - values required\n <S extends string>(\n source: HasPlaceholders<S> extends true ? S : never,\n values: PlaceholderValues<S>,\n ): string;\n\n // Source text WITHOUT placeholders - no values\n <S extends string>(\n source: HasPlaceholders<S> extends false ? S : never,\n ): string;\n\n // Source text with options (3rd arg)\n <S extends string>(\n source: S,\n values: PlaceholderValues<S>,\n options: SourceTextOptions,\n ): string;\n};\n\n/**\n * Creates a typed useT hook for imperative translations.\n *\n * Supports both source text mode and key-only mode:\n * - Source text: t('Hello world!') - hashes and looks up\n * - Key-only: t({ id: 'welcome' }) - direct key lookup\n *\n * Only works with messages that don't require component interpolation,\n * since it returns a string. For messages with <0>...</0> tags, use <Trans>.\n *\n * @example\n * // In generated idioma/index.ts:\n * export const useT = createUseT<IdiomaTypes>()\n *\n * // Usage:\n * const t = useT()\n * t('Hello world!') // source text mode\n * t('Hello {name}', { name: 'Ben' }) // with values (2nd arg)\n * t('Submit', undefined, { context: 'button' }) // with options (3rd arg)\n * t('Hello {name}', { name: 'Ben' }, { context: 'greeting' }) // both\n * t({ id: 'welcome' }) // key-only mode\n * t({ id: 'greeting', values: { name: 'Ben' }, ns: 'common' }) // with ns\n */\n// Internal runtime type for key-only args (untyped at runtime)\ninterface KeyOnlyArgsRuntime {\n id: string;\n values?: Record<string, unknown>;\n context?: string;\n ns?: string;\n}\n\nexport function createUseT<\n C extends BaseIdiomaConfig = BaseIdiomaConfig,\n>(): () => TFunction<C> {\n /**\n * useT hook for imperative translations.\n *\n * Babel transforms t() calls to include inlined translations.\n * This hook handles the transformed calls and provides fallback behavior.\n */\n return function useT(): TFunction<C> {\n const ctx = useContext(IdiomaContext);\n if (!ctx) {\n throw new Error(\n '[idioma] useT must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n\n const { locale } = ctx;\n\n return useCallback(\n (\n sourceOrArgs: string | KeyOnlyArgsRuntime,\n values?: Record<string, unknown>,\n options?: SourceTextOptions,\n ): string => {\n // Key-only mode: t({ id: 'welcome', values: { name }, ns: 'common' })\n // Babel should transform this, but if not, return the id as fallback\n if (typeof sourceOrArgs === 'object') {\n const { id } = sourceOrArgs;\n console.warn(\n `[idioma] t({ id: \"${id}\" }) was not transformed by Babel. ` +\n 'Make sure @idioma/core Babel plugin is configured.',\n );\n return id;\n }\n\n // Source text mode: t('Hello {name}', { name: 'Ben' }, { context: 'button' })\n // OR after Babel transformation: t('Hello {name}', { key: { en: '...', es: '...' } }, { name: 'Ben' })\n const source = sourceOrArgs;\n\n // Detect if Babel has inlined translations in arg 2\n // Babel transforms: t('src', { key: { en: '...', es: '...' } }, { actualValues })\n // The inlined object has ONE key (the message key) whose value has locale keys\n let actualValues = values;\n let localeMessages:\n | Record<string, string | MessageFunction>\n | undefined;\n\n const valuesKeys = values ? Object.keys(values) : [];\n const firstKey = valuesKeys[0];\n const firstValue =\n firstKey && values\n ? (values as Record<string, unknown>)[firstKey]\n : undefined;\n\n // Check if this looks like Babel-inlined translations:\n // - Has exactly one key (the message key)\n // - That key's value is an object with locale as a property\n if (\n valuesKeys.length === 1 &&\n firstValue &&\n typeof firstValue === 'object' &&\n typeof (firstValue as Record<string, unknown>)[locale] !== 'undefined'\n ) {\n // Babel transformed: arg 2 is { messageKey: { en: '...', es: '...' } }\n localeMessages = firstValue as Record<\n string,\n string | MessageFunction\n >;\n actualValues = options as unknown as Record<string, unknown>;\n }\n\n if (!localeMessages) {\n // Not transformed by Babel - fallback to source text\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(source, actualValues)\n : source;\n }\n\n const msg = localeMessages[locale] ?? Object.values(localeMessages)[0];\n if (msg === undefined) {\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(source, actualValues)\n : source;\n }\n\n if (typeof msg === 'function') {\n return msg(actualValues || {});\n }\n\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(msg, actualValues)\n : msg;\n },\n [locale],\n ) as TFunction<C>;\n };\n}\n"],"mappings":"sJAgLA,SAAgBI,GAEQ,CAOtB,OAAO,UAA8B,CACnC,IAAME,EAAML,EAAWC,EAAc,CACrC,GAAI,CAACI,EACH,MAAUC,MACR,yGAED,CAGH,GAAM,CAAEC,UAAWF,EAEnB,OAAON,GAEHS,EACAC,EACAC,IACW,CAGX,GAAI,OAAOF,GAAiB,SAAU,CACpC,GAAM,CAAEG,MAAOH,EAKf,OAJAI,QAAQC,KACN,qBAAqBF,EAAE,uFAExB,CACMA,EAKT,IAAMG,EAASN,EAKXO,EAAeN,EACfO,EAIEC,EAAaR,EAASS,OAAOC,KAAKV,EAAO,CAAG,EAAE,CAC9CW,EAAWH,EAAW,GACtBI,EACJD,GAAYX,EACPA,EAAmCW,GACpCE,IAAAA,GAmBN,GAbEL,EAAWM,SAAW,GACtBF,GACA,OAAOA,GAAe,UACdA,EAAuCd,KAAY,SAG3DS,EAAiBK,EAIjBN,EAAeL,GAGb,CAACM,EAEH,OAAOD,GAAgBG,OAAOC,KAAKJ,EAAa,CAACQ,OAAS,EACtDrB,EAAkBY,EAAQC,EAAa,CACvCD,EAGN,IAAMU,EAAMR,EAAeT,IAAWW,OAAOT,OAAOO,EAAe,CAAC,GAWpE,OAVIQ,IAAQF,IAAAA,GACHP,GAAgBG,OAAOC,KAAKJ,EAAa,CAACQ,OAAS,EACtDrB,EAAkBY,EAAQC,EAAa,CACvCD,EAGF,OAAOU,GAAQ,WACVA,EAAIT,GAAgB,EAAE,CAAC,CAGzBA,GAAgBG,OAAOC,KAAKJ,EAAa,CAACQ,OAAS,EACtDrB,EAAkBsB,EAAKT,EAAa,CACpCS,GAEN,CAACjB,EACH,CAAC"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,2 @@
1
+ import{createTrans as e}from"./createTrans.js";import{createUseT as n}from"./createUseT.js";import{jsx as r}from"react/jsx-runtime";n(),t({id:`nonexistent`}),t({id:`legal.links`}),t({id:`greeting.name`}),t({id:`greeting.name`,values:{wrong:`x`}}),t({id:`items.count`,values:{name:`Ben`}}),t({id:`greeting`}),t({id:`greeting.name`,values:{name:`Ben`}}),t({id:`greeting.name`,values:{name:42}}),t({id:`items.count`,values:{count:5}}),t({id:`greeting`,context:`modal`}),t({id:`greeting`,ns:`common`}),t({id:`greeting.name`,values:{name:`Ben`},context:`header`,ns:`app`}),t(`Hello {name}`),t(`Hello {name}`,{wrong:`x`}),t(`Hello {name}, you have {count} items`,{name:`Ben`}),t(`Hello {name}`,{name:`Ben`,extra:`x`}),t(`Hello {name}`,{name:`Ben`}),t(`Hello {name}`,{name:42}),t(`Hello {name}, you have {count} items`,{name:`Ben`,count:5}),t(`No placeholders here`),t(`Submit`,void 0,{context:`button`}),t(`Hello {name}`,{name:`Ben`},{context:`greeting`}),t(`Hello {name}`,{name:`Ben`},{context:`greeting`,ns:`common`}),t(``),t(`Hello {name}, goodbye {name}`,{name:`Ben`}),e(),TermsLink,TermsLink,PrivacyLink;
2
+ //# sourceMappingURL=createUseT.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createUseT.types.js","names":["createTrans","createUseT","useT","t","id","values","wrong","name","count","context","ns","extra","undefined","Trans","TermsLink","PrivacyLink"],"sources":["../src/createUseT.types.tsx"],"sourcesContent":["/**\n * Type-level tests for useT hook and Trans component.\n *\n * These tests verify that TypeScript correctly catches type errors.\n * The @ts-expect-error comments should suppress errors on invalid usage,\n * and valid usage should compile without errors.\n *\n * Run with: pnpm typecheck\n */\n\nimport { createTrans } from './createTrans';\nimport { createUseT } from './createUseT';\nimport type { TransComponent } from './interpolate';\n\n// Simulated generated types (what the compiler would produce)\n// All types bundled into a single IdiomaTypes interface for cleaner API\ninterface IdiomaTypes {\n TranslationKey: 'greeting' | 'greeting.name' | 'items.count' | 'legal.links';\n MessageValues: {\n greeting: Record<string, never>;\n 'greeting.name': { name: string | number };\n 'items.count': { count: number };\n 'legal.links': Record<string, never>;\n };\n MessageComponents: {\n greeting: [];\n 'greeting.name': [];\n 'items.count': [];\n 'legal.links': [TransComponent, TransComponent];\n };\n}\n\n// Create typed useT (simulating what idioma/index.ts exports)\n// Single type parameter instead of 3!\nconst useT = createUseT<IdiomaTypes>();\n\n// Get the t function (in real usage this would be inside a component)\ndeclare const t: ReturnType<typeof useT>;\n\n// =============================================================================\n// KEY-ONLY MODE TESTS\n// =============================================================================\n\n// --- Invalid key should error ---\n// @ts-expect-error - 'nonexistent' is not a valid key\nt({ id: 'nonexistent' });\n\n// --- Keys with component tags should error (must use Trans instead) ---\n// @ts-expect-error - 'legal.links' has component tags, can't use with useT\nt({ id: 'legal.links' });\n\n// --- Missing values for key with placeholders should error ---\n// @ts-expect-error - 'greeting.name' requires values: { name }\nt({ id: 'greeting.name' });\n\n// --- Wrong values shape should error ---\n// @ts-expect-error - 'greeting.name' needs { name }, not { wrong }\nt({ id: 'greeting.name', values: { wrong: 'x' } });\n\n// @ts-expect-error - 'items.count' needs { count }, not { name }\nt({ id: 'items.count', values: { name: 'Ben' } });\n\n// --- Valid key-only usage should work ---\nt({ id: 'greeting' }); // No values needed\nt({ id: 'greeting.name', values: { name: 'Ben' } });\nt({ id: 'greeting.name', values: { name: 42 } }); // number also valid\nt({ id: 'items.count', values: { count: 5 } });\n\n// --- With optional context/ns ---\nt({ id: 'greeting', context: 'modal' });\nt({ id: 'greeting', ns: 'common' });\nt({\n id: 'greeting.name',\n values: { name: 'Ben' },\n context: 'header',\n ns: 'app',\n});\n\n// =============================================================================\n// SOURCE TEXT MODE TESTS\n// =============================================================================\n\n// --- Missing values for source with placeholders should error ---\n// @ts-expect-error - 'Hello {name}' has placeholder, needs values\nt('Hello {name}');\n\n// --- Wrong values shape should error ---\n// @ts-expect-error - needs { name }, not { wrong }\nt('Hello {name}', { wrong: 'x' });\n\n// @ts-expect-error - needs { name, count }, got only { name }\nt('Hello {name}, you have {count} items', { name: 'Ben' });\n\n// --- Extra values not in template should error ---\n// @ts-expect-error - { extra } is not in the template\nt('Hello {name}', { name: 'Ben', extra: 'x' });\n\n// --- Valid source text usage should work ---\nt('Hello {name}', { name: 'Ben' });\nt('Hello {name}', { name: 42 }); // number also valid\nt('Hello {name}, you have {count} items', { name: 'Ben', count: 5 });\nt('No placeholders here'); // No values needed\n\n// --- With options (3rd argument) ---\nt('Submit', undefined, { context: 'button' });\nt('Hello {name}', { name: 'Ben' }, { context: 'greeting' });\nt('Hello {name}', { name: 'Ben' }, { context: 'greeting', ns: 'common' });\n\n// =============================================================================\n// EDGE CASES\n// =============================================================================\n\n// --- Empty string should work without values ---\nt('');\n\n// --- Curly braces that aren't placeholders ---\n// These are tricky - { without matching } or escaped braces\n// For now, the simple regex will treat {foo as a placeholder start\n// This is a known limitation - ICU syntax like {count, plural, ...} will be parsed\n\n// --- Multiple same placeholders should only require one value ---\nt('Hello {name}, goodbye {name}', { name: 'Ben' });\n\n// =============================================================================\n// TRANS COMPONENT (createTrans)\n// =============================================================================\n\n// Create typed Trans (simulating what idioma/index.ts exports)\n// Single type parameter instead of 3!\nconst Trans = createTrans<IdiomaTypes>();\n\n// Mock components for testing\ndeclare const TermsLink: TransComponent;\ndeclare const PrivacyLink: TransComponent;\n\n// === Invalid key should error ===\n\n// @ts-expect-error - 'nonexistent' is not a valid TranslationKey\n<Trans id=\"nonexistent\" />;\n\n// === Missing values for key with placeholders should error ===\n\n// @ts-expect-error - 'greeting.name' requires values: { name }\n<Trans id=\"greeting.name\" />;\n\n// === Wrong values shape should error ===\n\n// @ts-expect-error - 'greeting.name' needs { name }, not { wrong }\n<Trans id=\"greeting.name\" values={{ wrong: 'x' }} />;\n\n// === Missing components for key with tags should error ===\n\n// @ts-expect-error - 'legal.links' requires components\n<Trans id=\"legal.links\" />;\n\n// @ts-expect-error - 'legal.links' requires 2 components, not 1\n<Trans id=\"legal.links\" components={[TermsLink]} />;\n\n// === Valid Trans usage ===\n\n// Key without values or components\n<Trans id=\"greeting\" />;\n\n// Key with values\n<Trans id=\"greeting.name\" values={{ name: 'Ben' }} />;\n<Trans id=\"items.count\" values={{ count: 5 }} />;\n\n// Key with components\n<Trans id=\"legal.links\" components={[TermsLink, PrivacyLink]} />;\n\n// Inline mode (children present)\n<Trans>Hello world</Trans>;\n<Trans context=\"button\">Submit</Trans>;\n"],"mappings":"oIAkCaC,GAAyB,CAWtCE,EAAE,CAAEC,GAAI,cAAe,CAAC,CAIxBD,EAAE,CAAEC,GAAI,cAAe,CAAC,CAIxBD,EAAE,CAAEC,GAAI,gBAAiB,CAAC,CAI1BD,EAAE,CAAEC,GAAI,gBAAiBC,OAAQ,CAAEC,MAAO,IAAI,CAAG,CAAC,CAGlDH,EAAE,CAAEC,GAAI,cAAeC,OAAQ,CAAEE,KAAM,MAAM,CAAG,CAAC,CAGjDJ,EAAE,CAAEC,GAAI,WAAY,CAAC,CACrBD,EAAE,CAAEC,GAAI,gBAAiBC,OAAQ,CAAEE,KAAM,MAAM,CAAG,CAAC,CACnDJ,EAAE,CAAEC,GAAI,gBAAiBC,OAAQ,CAAEE,KAAM,GAAG,CAAG,CAAC,CAChDJ,EAAE,CAAEC,GAAI,cAAeC,OAAQ,CAAEG,MAAO,EAAE,CAAG,CAAC,CAG9CL,EAAE,CAAEC,GAAI,WAAYK,QAAS,QAAS,CAAC,CACvCN,EAAE,CAAEC,GAAI,WAAYM,GAAI,SAAU,CAAC,CACnCP,EAAE,CACAC,GAAI,gBACJC,OAAQ,CAAEE,KAAM,MAAO,CACvBE,QAAS,SACTC,GAAI,MACL,CAAC,CAQFP,EAAE,eAAe,CAIjBA,EAAE,eAAgB,CAAEG,MAAO,IAAK,CAAC,CAGjCH,EAAE,uCAAwC,CAAEI,KAAM,MAAO,CAAC,CAI1DJ,EAAE,eAAgB,CAAEI,KAAM,MAAOI,MAAO,IAAK,CAAC,CAG9CR,EAAE,eAAgB,CAAEI,KAAM,MAAO,CAAC,CAClCJ,EAAE,eAAgB,CAAEI,KAAM,GAAI,CAAC,CAC/BJ,EAAE,uCAAwC,CAAEI,KAAM,MAAOC,MAAO,EAAG,CAAC,CACpEL,EAAE,uBAAuB,CAGzBA,EAAE,SAAUS,IAAAA,GAAW,CAAEH,QAAS,SAAU,CAAC,CAC7CN,EAAE,eAAgB,CAAEI,KAAM,MAAO,CAAE,CAAEE,QAAS,WAAY,CAAC,CAC3DN,EAAE,eAAgB,CAAEI,KAAM,MAAO,CAAE,CAAEE,QAAS,WAAYC,GAAI,SAAU,CAAC,CAOzEP,EAAE,GAAG,CAQLA,EAAE,+BAAgC,CAAEI,KAAM,MAAO,CAAC,CAQpCP,GAA0B,CA2BHc,UAYAA,UAAWC"}
@@ -0,0 +1,7 @@
1
+ import { TransComponent, interpolateTags, interpolateValues } from "./interpolate.js";
2
+ import { TransProps, __Trans } from "./Trans.js";
3
+ import { IdiomaContext, IdiomaContextValue, IdiomaProviderProps, createIdiomaProvider, createUseLocale } from "./context.js";
4
+ import { TFunction, createUseT } from "./createUseT.js";
5
+ import { TransInlineModeProps, TransKeyOnlyModeProps, createTrans } from "./createTrans.js";
6
+ import { UseTOptions, __useT } from "./useT.js";
7
+ export { IdiomaContext, type IdiomaContextValue, type IdiomaProviderProps, type TFunction, type TransComponent, type TransInlineModeProps, type TransKeyOnlyModeProps, type TransProps, type UseTOptions, __Trans, __useT, createIdiomaProvider, createTrans, createUseLocale, createUseT, interpolateTags, interpolateValues };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{IdiomaContext as e,createIdiomaProvider as t,createUseLocale as n}from"./context.js";import{interpolateTags as r,interpolateValues as i}from"./interpolate.js";import{__Trans as a}from"./Trans.js";import{createTrans as o}from"./createTrans.js";import{createUseT as s}from"./createUseT.js";import{__useT as c}from"./useT.js";export{e as IdiomaContext,a as __Trans,c as __useT,t as createIdiomaProvider,o as createTrans,n as createUseLocale,s as createUseT,r as interpolateTags,i as interpolateValues};
@@ -0,0 +1,67 @@
1
+ import { ComponentType, ReactNode } from "react";
2
+
3
+ //#region src/interpolate.d.ts
4
+
5
+ /**
6
+ * Interpolates values into a message string.
7
+ * Handles both named placeholders ({user.name}) and numbered ({0}).
8
+ *
9
+ * @example
10
+ * interpolateValues("Hello {name}!", { name: "Ben" })
11
+ * // => "Hello Ben!"
12
+ *
13
+ * @example
14
+ * interpolateValues("Total: {0} items", { "0": 42 })
15
+ * // => "Total: 42 items"
16
+ */
17
+ declare function interpolateValues(message: string, args: Record<string, unknown>): string;
18
+ type TransComponent = ComponentType<{
19
+ children?: ReactNode;
20
+ }>;
21
+ /**
22
+ * Interpolates tags in a translated string with React components.
23
+ * Supports both named tags (<Link>text</Link>) and numbered tags (<0>text</0>).
24
+ * Also handles value placeholders ({name}, {0}) within the text.
25
+ * Supports nested tags like <Bold>outer <Italic>inner</Italic></Bold>.
26
+ *
27
+ * @example
28
+ * // Named tags (preferred)
29
+ * interpolateTags(
30
+ * "Read our <TermsLink>terms</TermsLink> and <PrivacyLink>privacy</PrivacyLink>",
31
+ * [TermsLink, PrivacyLink],
32
+ * undefined,
33
+ * ['TermsLink', 'PrivacyLink']
34
+ * )
35
+ *
36
+ * @example
37
+ * // With placeholders
38
+ * interpolateTags(
39
+ * "Hello {name}, click <Link>here</Link>",
40
+ * [Link],
41
+ * { name: "Ben" },
42
+ * ['Link']
43
+ * )
44
+ *
45
+ * @example
46
+ * // Duplicate component names (matched in order)
47
+ * interpolateTags(
48
+ * "Click <Link>here</Link> or <Link>there</Link>",
49
+ * [LinkA, LinkB],
50
+ * undefined,
51
+ * ['Link', 'Link']
52
+ * )
53
+ */
54
+ declare function interpolateTags(message: string, components: TransComponent[], args?: Record<string, unknown>, componentNames?: string[]): ReactNode;
55
+ /**
56
+ * Shared message rendering logic for both standard and Suspense runtimes.
57
+ * Handles ICU-compiled functions, tag interpolation, and value interpolation.
58
+ *
59
+ * @param msg The message - either a string or a compiled ICU function
60
+ * @param args Optional interpolation values
61
+ * @param components Optional React components for tag interpolation
62
+ * @param componentNames Optional component names for named tag matching
63
+ */
64
+ declare function renderMessage(msg: string | ((args: Record<string, unknown>) => string | ReactNode), args?: Record<string, unknown>, components?: TransComponent[], componentNames?: string[]): ReactNode;
65
+ //#endregion
66
+ export { TransComponent, interpolateTags, interpolateValues, renderMessage };
67
+ //# sourceMappingURL=interpolate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.d.ts","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;AAcA;AAmCA;;;;;AA0NA;;;AAES,iBA7QO,iBAAA,CA6QP,OAAA,EAAA,MAAA,EAAA,IAAA,EA3QD,MA2QC,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,EAAA,MAAA;AACM,KAhQH,cAAA,GAAiB,aAgQd,CAAA;EAEZ,QAAA,CAAA,EAlQqD,SAkQrD;CAAS,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA/NI,eAAA,8BAEF,yBACL,qDAEN;;;;;;;;;;iBAqNa,aAAA,uBACQ,qCAAqC,mBACpD,sCACM,8CAEZ"}
@@ -0,0 +1,2 @@
1
+ import{Fragment as e,createElement as t}from"react";function n(e,t){return e.replace(/\{([^}]+)\}/g,(e,n)=>{let r=t[n];return r===void 0?e:String(r)})}function r(e,t,n,r){return i(e,t,n,0,void 0,r,new Set).result}function i(r,o,s,c,l,u,d){let f=[],p=c,m=c;for(;p<r.length;){if(l&&r.startsWith(l,p)){if(p>m){let e=r.slice(m,p);f.push(s?n(e,s):e)}return{result:f.length===1?f[0]:t(e,null,...f),endIndex:p+l.length}}let c=r.slice(p).match(/^<([a-zA-Z][a-zA-Z0-9]*|\d+)>/);if(c){if(p>m){let e=r.slice(m,p);f.push(s?n(e,s):e)}let e=c[1],l=/^\d+$/.test(e),h;h=l?parseInt(e,10):a(e,u,d);let g=`</${e}>`,_=c[0].length;d?.add(h);let v=i(r,o,s,p+_,g,u,d),y=o[h];y?f.push(t(y,{key:`${e}-${p}`,children:v.result})):f.push(v.result),p=v.endIndex,m=p}else{let e=r.slice(p).match(/^<([a-zA-Z][a-zA-Z0-9]*|\d+)\/>/);if(e){if(p>m){let e=r.slice(m,p);f.push(s?n(e,s):e)}let i=e[1],c=/^\d+$/.test(i),l;l=c?parseInt(i,10):a(i,u,d),d?.add(l);let h=o[l];h&&f.push(t(h,{key:`${i}-${p}`})),p+=e[0].length,m=p}else p++}}if(m<r.length){let e=r.slice(m);f.push(s?n(e,s):e)}return f.length===0?{result:``,endIndex:p}:f.length===1?{result:f[0],endIndex:p}:{result:t(e,null,...f),endIndex:p}}function a(e,t,n){if(!t)return-1;for(let r=0;r<t.length;r++)if(t[r]===e&&!n?.has(r))return r;return-1}function o(e,t,i,a){return typeof e==`function`?e(t||{}):i&&i.length>0?r(e,i,t,a):t&&Object.keys(t).length>0?n(e,t):e}export{r as interpolateTags,n as interpolateValues,o as renderMessage};
2
+ //# sourceMappingURL=interpolate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.js","names":["createElement","Fragment","interpolateValues","message","args","replace","match","key","value","undefined","String","interpolateTags","components","componentNames","usedIndices","Set","parseAndRender","result","startIndex","closingTag","parts","i","textStart","length","startsWith","text","slice","push","endIndex","openMatch","tagIdentifier","isNumbered","test","tagIndex","parseInt","findComponentIndex","tagClosing","openTagLength","add","innerResult","Component","children","selfCloseMatch","name","has","renderMessage","msg","Object","keys"],"sources":["../src/interpolate.ts"],"sourcesContent":["import {\n createElement,\n Fragment,\n type ComponentType,\n type ReactNode,\n} from 'react';\n\n/**\n * Interpolates values into a message string.\n * Handles both named placeholders ({user.name}) and numbered ({0}).\n *\n * @example\n * interpolateValues(\"Hello {name}!\", { name: \"Ben\" })\n * // => \"Hello Ben!\"\n *\n * @example\n * interpolateValues(\"Total: {0} items\", { \"0\": 42 })\n * // => \"Total: 42 items\"\n */\nexport function interpolateValues(\n message: string,\n args: Record<string, unknown>,\n): string {\n return message.replace(/\\{([^}]+)\\}/g, (match, key: string) => {\n const value = args[key];\n if (value === undefined) {\n // Keep placeholder if no value provided (helps debugging)\n return match;\n }\n return String(value);\n });\n}\n\nexport type TransComponent = ComponentType<{ children?: ReactNode }>;\n\n/**\n * Interpolates tags in a translated string with React components.\n * Supports both named tags (<Link>text</Link>) and numbered tags (<0>text</0>).\n * Also handles value placeholders ({name}, {0}) within the text.\n * Supports nested tags like <Bold>outer <Italic>inner</Italic></Bold>.\n *\n * @example\n * // Named tags (preferred)\n * interpolateTags(\n * \"Read our <TermsLink>terms</TermsLink> and <PrivacyLink>privacy</PrivacyLink>\",\n * [TermsLink, PrivacyLink],\n * undefined,\n * ['TermsLink', 'PrivacyLink']\n * )\n *\n * @example\n * // With placeholders\n * interpolateTags(\n * \"Hello {name}, click <Link>here</Link>\",\n * [Link],\n * { name: \"Ben\" },\n * ['Link']\n * )\n *\n * @example\n * // Duplicate component names (matched in order)\n * interpolateTags(\n * \"Click <Link>here</Link> or <Link>there</Link>\",\n * [LinkA, LinkB],\n * undefined,\n * ['Link', 'Link']\n * )\n */\nexport function interpolateTags(\n message: string,\n components: TransComponent[],\n args?: Record<string, unknown>,\n componentNames?: string[],\n): ReactNode {\n // Parse the message into a tree structure, then render\n // Track which component indices have been used (for duplicate name matching)\n const usedIndices = new Set<number>();\n return parseAndRender(\n message,\n components,\n args,\n 0,\n undefined,\n componentNames,\n usedIndices,\n ).result;\n}\n\ninterface ParseResult {\n result: ReactNode;\n endIndex: number;\n}\n\n/**\n * Recursively parse and render tags starting from a given position.\n * @param message The full message string\n * @param components Array of React components for tag substitution\n * @param args Optional interpolation values\n * @param startIndex Where to start parsing\n * @param closingTag Optional: the closing tag we're looking for (e.g., \"</Link>\" or \"</0>\")\n * @param componentNames Optional: names of components in order (for named tag matching)\n * @param usedIndices Set tracking which component indices have been used\n */\nfunction parseAndRender(\n message: string,\n components: TransComponent[],\n args: Record<string, unknown> | undefined,\n startIndex: number,\n closingTag?: string,\n componentNames?: string[],\n usedIndices?: Set<number>,\n): ParseResult {\n const parts: ReactNode[] = [];\n let i = startIndex;\n let textStart = startIndex;\n\n while (i < message.length) {\n // Check for closing tag if we're inside a tag\n if (closingTag && message.startsWith(closingTag, i)) {\n // Add any remaining text before the closing tag\n if (i > textStart) {\n const text = message.slice(textStart, i);\n parts.push(args ? interpolateValues(text, args) : text);\n }\n return {\n result:\n parts.length === 1\n ? parts[0]!\n : createElement(Fragment, null, ...parts),\n endIndex: i + closingTag.length,\n };\n }\n\n // Check for opening tag - supports both named (<Link>) and numbered (<0>)\n // Named tags: <Link>, <Bold>, <MyComponent>\n // Numbered tags (legacy): <0>, <1>, <2>\n const openMatch = message.slice(i).match(/^<([a-zA-Z][a-zA-Z0-9]*|\\d+)>/);\n if (openMatch) {\n // Add text before this tag\n if (i > textStart) {\n const text = message.slice(textStart, i);\n parts.push(args ? interpolateValues(text, args) : text);\n }\n\n const tagIdentifier = openMatch[1]!;\n const isNumbered = /^\\d+$/.test(tagIdentifier);\n let tagIndex: number;\n\n if (isNumbered) {\n // Numbered tag: use the number directly as index\n tagIndex = parseInt(tagIdentifier, 10);\n } else {\n // Named tag: find the next unused component with this name\n tagIndex = findComponentIndex(\n tagIdentifier,\n componentNames,\n usedIndices,\n );\n }\n\n const tagClosing = `</${tagIdentifier}>`;\n const openTagLength = openMatch[0].length;\n\n // Mark this index as used\n usedIndices?.add(tagIndex);\n\n // Recursively parse the content inside this tag\n const innerResult = parseAndRender(\n message,\n components,\n args,\n i + openTagLength,\n tagClosing,\n componentNames,\n usedIndices,\n );\n\n const Component = components[tagIndex];\n if (Component) {\n parts.push(\n createElement(Component, {\n key: `${tagIdentifier}-${i}`,\n children: innerResult.result,\n }),\n );\n } else {\n // Fallback: just include the inner content without wrapper\n parts.push(innerResult.result);\n }\n\n i = innerResult.endIndex;\n textStart = i;\n } else {\n // Check for self-closing tag like <br/> or <Icon/>\n const selfCloseMatch = message\n .slice(i)\n .match(/^<([a-zA-Z][a-zA-Z0-9]*|\\d+)\\/>/);\n if (selfCloseMatch) {\n // Add text before this tag\n if (i > textStart) {\n const text = message.slice(textStart, i);\n parts.push(args ? interpolateValues(text, args) : text);\n }\n\n const tagIdentifier = selfCloseMatch[1]!;\n const isNumbered = /^\\d+$/.test(tagIdentifier);\n let tagIndex: number;\n\n if (isNumbered) {\n tagIndex = parseInt(tagIdentifier, 10);\n } else {\n tagIndex = findComponentIndex(\n tagIdentifier,\n componentNames,\n usedIndices,\n );\n }\n\n // Mark this index as used\n usedIndices?.add(tagIndex);\n\n const Component = components[tagIndex];\n if (Component) {\n parts.push(\n createElement(Component, { key: `${tagIdentifier}-${i}` }),\n );\n }\n\n i += selfCloseMatch[0].length;\n textStart = i;\n } else {\n i++;\n }\n }\n }\n\n // Add any remaining text\n if (textStart < message.length) {\n const text = message.slice(textStart);\n parts.push(args ? interpolateValues(text, args) : text);\n }\n\n // Simplify the result\n if (parts.length === 0) {\n return { result: '', endIndex: i };\n }\n if (parts.length === 1) {\n return { result: parts[0]!, endIndex: i };\n }\n return { result: createElement(Fragment, null, ...parts), endIndex: i };\n}\n\n/**\n * Find the next unused component index matching the given name.\n * For duplicate names, returns them in order of first appearance.\n */\nfunction findComponentIndex(\n name: string,\n componentNames?: string[],\n usedIndices?: Set<number>,\n): number {\n if (!componentNames) {\n // No names provided, can't match - return -1 (will result in fallback)\n return -1;\n }\n\n // Find the first component with this name that hasn't been used yet\n for (let i = 0; i < componentNames.length; i++) {\n if (componentNames[i] === name && !usedIndices?.has(i)) {\n return i;\n }\n }\n\n // No match found - return -1\n return -1;\n}\n\n/**\n * Shared message rendering logic for both standard and Suspense runtimes.\n * Handles ICU-compiled functions, tag interpolation, and value interpolation.\n *\n * @param msg The message - either a string or a compiled ICU function\n * @param args Optional interpolation values\n * @param components Optional React components for tag interpolation\n * @param componentNames Optional component names for named tag matching\n */\nexport function renderMessage(\n msg: string | ((args: Record<string, unknown>) => string | ReactNode),\n args?: Record<string, unknown>,\n components?: TransComponent[],\n componentNames?: string[],\n): ReactNode {\n // Compiled plural/ICU: msg is a function\n if (typeof msg === 'function') {\n return msg(args || {});\n }\n\n // String message - may need interpolation\n // Tag interpolation: replace <Link>...</Link> or <0>...</0> with React components\n // This must happen first if we have components, as it handles value interpolation too\n if (components && components.length > 0) {\n return interpolateTags(msg, components, args, componentNames);\n }\n\n // Value interpolation only: replace {name} or {0} with values from __a\n if (args && Object.keys(args).length > 0) {\n return interpolateValues(msg, args);\n }\n\n return msg;\n}\n"],"mappings":"oDAmBA,SAAgBE,EACdC,EACAC,EACQ,CACR,OAAOD,EAAQE,QAAQ,gBAAiBC,EAAOC,IAAgB,CAC7D,IAAMC,EAAQJ,EAAKG,GAKnB,OAJIC,IAAUC,IAAAA,GAELH,EAEFI,OAAOF,EAAM,EACpB,CAsCJ,SAAgBG,EACdR,EACAS,EACAR,EACAS,EACW,CAIX,OAAOG,EACLb,EACAS,EACAR,EACA,EACAK,IAAAA,GACAI,EAPkB,IAAIE,IASvB,CAACE,OAkBJ,SAASD,EACPb,EACAS,EACAR,EACAc,EACAC,EACAN,EACAC,EACa,CACb,IAAMM,EAAqB,EAAE,CACzBC,EAAIH,EACJI,EAAYJ,EAEhB,KAAOG,EAAIlB,EAAQoB,QAAQ,CAEzB,GAAIJ,GAAchB,EAAQqB,WAAWL,EAAYE,EAAE,CAAE,CAEnD,GAAIA,EAAIC,EAAW,CACjB,IAAMG,EAAOtB,EAAQuB,MAAMJ,EAAWD,EAAE,CACxCD,EAAMO,KAAKvB,EAAOF,EAAkBuB,EAAMrB,EAAK,CAAGqB,EAAK,CAEzD,MAAO,CACLR,OACEG,EAAMG,SAAW,EACbH,EAAM,GACNpB,EAAcC,EAAU,KAAM,GAAGmB,EAAM,CAC7CQ,SAAUP,EAAIF,EAAWI,OAC1B,CAMH,IAAMM,EAAY1B,EAAQuB,MAAML,EAAE,CAACf,MAAM,gCAAgC,CACzE,GAAIuB,EAAW,CAEb,GAAIR,EAAIC,EAAW,CACjB,IAAMG,EAAOtB,EAAQuB,MAAMJ,EAAWD,EAAE,CACxCD,EAAMO,KAAKvB,EAAOF,EAAkBuB,EAAMrB,EAAK,CAAGqB,EAAK,CAGzD,IAAMK,EAAgBD,EAAU,GAC1BE,EAAa,QAAQC,KAAKF,EAAc,CAC1CG,EAEJ,AAKEA,EALEF,EAESG,SAASJ,EAAe,GAAG,CAG3BK,EACTL,EACAjB,EACAC,EACD,CAGH,IAAMsB,EAAa,KAAKN,EAAa,GAC/BO,EAAgBR,EAAU,GAAGN,OAGnCT,GAAawB,IAAIL,EAAS,CAG1B,IAAMM,EAAcvB,EAClBb,EACAS,EACAR,EACAiB,EAAIgB,EACJD,EACAvB,EACAC,EACD,CAEK0B,EAAY5B,EAAWqB,GACzBO,EACFpB,EAAMO,KACJ3B,EAAcwC,EAAW,CACvBjC,IAAK,GAAGuB,EAAa,GAAIT,IACzBoB,SAAUF,EAAYtB,OACvB,CACH,CAAC,CAGDG,EAAMO,KAAKY,EAAYtB,OAAO,CAGhCI,EAAIkB,EAAYX,SAChBN,EAAYD,MACP,CAEL,IAAMqB,EAAiBvC,EACpBuB,MAAML,EAAE,CACRf,MAAM,kCAAkC,CAC3C,GAAIoC,EAAgB,CAElB,GAAIrB,EAAIC,EAAW,CACjB,IAAMG,EAAOtB,EAAQuB,MAAMJ,EAAWD,EAAE,CACxCD,EAAMO,KAAKvB,EAAOF,EAAkBuB,EAAMrB,EAAK,CAAGqB,EAAK,CAGzD,IAAMK,EAAgBY,EAAe,GAC/BX,EAAa,QAAQC,KAAKF,EAAc,CAC1CG,EAEJ,AAGEA,EAHEF,EACSG,SAASJ,EAAe,GAAG,CAE3BK,EACTL,EACAjB,EACAC,EACD,CAIHA,GAAawB,IAAIL,EAAS,CAE1B,IAAMO,EAAY5B,EAAWqB,GACzBO,GACFpB,EAAMO,KACJ3B,EAAcwC,EAAW,CAAEjC,IAAK,GAAGuB,EAAa,GAAIT,IAAK,CAC3D,CAAC,CAGHA,GAAKqB,EAAe,GAAGnB,OACvBD,EAAYD,OAEZA,KAMN,GAAIC,EAAYnB,EAAQoB,OAAQ,CAC9B,IAAME,EAAOtB,EAAQuB,MAAMJ,EAAU,CACrCF,EAAMO,KAAKvB,EAAOF,EAAkBuB,EAAMrB,EAAK,CAAGqB,EAAK,CAUzD,OANIL,EAAMG,SAAW,EACZ,CAAEN,OAAQ,GAAIW,SAAUP,EAAG,CAEhCD,EAAMG,SAAW,EACZ,CAAEN,OAAQG,EAAM,GAAKQ,SAAUP,EAAG,CAEpC,CAAEJ,OAAQjB,EAAcC,EAAU,KAAM,GAAGmB,EAAM,CAAEQ,SAAUP,EAAG,CAOzE,SAASc,EACPQ,EACA9B,EACAC,EACQ,CACR,GAAI,CAACD,EAEH,MAAO,GAIT,IAAK,IAAIQ,EAAI,EAAGA,EAAIR,EAAeU,OAAQF,IACzC,GAAIR,EAAeQ,KAAOsB,GAAQ,CAAC7B,GAAa8B,IAAIvB,EAAE,CACpD,OAAOA,EAKX,MAAO,GAYT,SAAgBwB,EACdC,EACA1C,EACAQ,EACAC,EACW,CAkBX,OAhBI,OAAOiC,GAAQ,WACVA,EAAI1C,GAAQ,EAAE,CAAC,CAMpBQ,GAAcA,EAAWW,OAAS,EAC7BZ,EAAgBmC,EAAKlC,EAAYR,EAAMS,EAAe,CAI3DT,GAAQ2C,OAAOC,KAAK5C,EAAK,CAACmB,OAAS,EAC9BrB,EAAkB4C,EAAK1C,EAAK,CAG9B0C"}
@@ -0,0 +1,141 @@
1
+ import { TransComponent } from "../interpolate.js";
2
+ import * as react0 from "react";
3
+ import { ReactNode } from "react";
4
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
5
+
6
+ //#region src/runtime-suspense/index.d.ts
7
+
8
+ type MessageFunction = (args: Record<string, unknown>) => string | ReactNode;
9
+ type MessageValue = string | MessageFunction;
10
+ type LoaderFn = () => Promise<{
11
+ default: Record<string, MessageValue>;
12
+ }>;
13
+ type Loader = Record<string, LoaderFn>;
14
+ interface SuspenseConfig {
15
+ locales: readonly string[];
16
+ }
17
+ /**
18
+ * Preload translations for a locale/chunk combination.
19
+ * Call this to warm the cache before rendering.
20
+ */
21
+ declare function preloadTranslations(locale: string, chunk: string, loader: Loader): void;
22
+ interface IdiomaContextValue {
23
+ locale: string;
24
+ }
25
+ declare const IdiomaContext: react0.Context<IdiomaContextValue | null>;
26
+ interface IdiomaProviderProps {
27
+ children: ReactNode;
28
+ locale: string;
29
+ }
30
+ /**
31
+ * Creates an IdiomaProvider for Suspense mode.
32
+ * Same controlled API as the inlined runtime.
33
+ */
34
+ declare function createIdiomaProvider(): ({
35
+ children,
36
+ locale
37
+ }: IdiomaProviderProps) => react_jsx_runtime0.JSX.Element;
38
+ /**
39
+ * Creates a useLocale hook that returns the current locale.
40
+ */
41
+ declare function createUseLocale(): () => string;
42
+ /**
43
+ * Props for Babel-transformed Trans in Suspense mode.
44
+ */
45
+ interface TransSuspenseProps {
46
+ /** Translation key */
47
+ __key: string;
48
+ /** Chunk identifier */
49
+ __chunk: string;
50
+ /** Loader object with locale → dynamic import */
51
+ __load: Loader;
52
+ /** Arguments for interpolation */
53
+ __a?: Record<string, unknown>;
54
+ /** Components for tag interpolation */
55
+ __c?: TransComponent[];
56
+ /** Component names for named tag matching (parallel to __c array) */
57
+ __cn?: string[];
58
+ }
59
+ /**
60
+ * Internal Trans component for Suspense mode.
61
+ * Used by Babel-compiled output.
62
+ */
63
+ declare function __TransSuspense({
64
+ __key,
65
+ __chunk,
66
+ __load,
67
+ __a,
68
+ __c,
69
+ __cn
70
+ }: TransSuspenseProps): ReactNode;
71
+ /**
72
+ * Props for key-only Trans in Suspense mode.
73
+ */
74
+ interface TransKeyOnlyModeProps<K$1 extends string, MV extends Record<string, Record<string, unknown>>, MC extends Record<string, TransComponent[]>> {
75
+ id: K$1;
76
+ children?: never;
77
+ context?: never;
78
+ ns?: string;
79
+ values?: K$1 extends keyof MV ? MV[K$1] : Record<string, unknown>;
80
+ components?: K$1 extends keyof MC ? MC[K$1] : TransComponent[];
81
+ __chunk?: string;
82
+ __load?: Loader;
83
+ }
84
+ /**
85
+ * Props for inline Trans in Suspense mode (dev only).
86
+ */
87
+ interface TransInlineModeProps {
88
+ children: ReactNode;
89
+ id?: string;
90
+ context?: string;
91
+ ns?: string;
92
+ }
93
+ /**
94
+ * Base interface for project-specific Idioma types (for Trans).
95
+ */
96
+ interface BaseIdiomaConfigForTrans {
97
+ TranslationKey: string;
98
+ MessageValues: Record<string, Record<string, unknown>>;
99
+ MessageComponents: Record<string, TransComponent[]>;
100
+ }
101
+ /**
102
+ * Creates a typed Trans component for Suspense mode.
103
+ *
104
+ * In development: renders children directly
105
+ * In production: Babel transforms to __TransSuspense
106
+ */
107
+ declare function createTransSuspense<C extends BaseIdiomaConfigForTrans = BaseIdiomaConfigForTrans>(_config: SuspenseConfig): {
108
+ (props: TransInlineModeProps): ReactNode;
109
+ <K$1 extends C["TranslationKey"]>(props: TransKeyOnlyModeProps<K$1 & string, C["MessageValues"], C["MessageComponents"]>): ReactNode;
110
+ };
111
+ /**
112
+ * Base interface for project-specific Idioma types.
113
+ * Same as BaseIdiomaConfig from createUseT.
114
+ */
115
+ interface BaseIdiomaConfig {
116
+ TranslationKey: string;
117
+ MessageValues: Record<string, Record<string, unknown>>;
118
+ MessageComponents: Record<string, unknown[]>;
119
+ }
120
+ /**
121
+ * Derive string-only keys from TranslationKey + MessageComponents.
122
+ * Keys with empty component arrays [] can be used with useT.
123
+ * Keys with non-empty component arrays [TransComponent, ...] must use Trans.
124
+ */
125
+ type StringOnlyKeys<TK extends string, MC extends Record<string, unknown[]>> = { [K in TK & keyof MC]: MC[K] extends [] ? K : never }[TK & keyof MC];
126
+ type TFunction<C extends BaseIdiomaConfig = BaseIdiomaConfig> = <Key extends StringOnlyKeys<C['TranslationKey'], C['MessageComponents']> & string>(key: Key, values?: Key extends keyof C['MessageValues'] ? C['MessageValues'][Key] : Record<string, unknown>) => string;
127
+ /**
128
+ * Internal useT for Suspense mode.
129
+ * Requires chunk and loader from Babel transform.
130
+ */
131
+ declare function __useTSuspense(_chunk: string, _loader: Loader): (key: string, values?: Record<string, unknown>) => string;
132
+ /**
133
+ * Creates a typed useT hook for Suspense mode.
134
+ *
135
+ * Note: In Suspense mode, useT is transformed by Babel to include
136
+ * chunk and loader information.
137
+ */
138
+ declare function createUseTSuspense<C extends BaseIdiomaConfig = BaseIdiomaConfig>(_config: SuspenseConfig): () => TFunction<C>;
139
+ //#endregion
140
+ export { IdiomaContext, IdiomaContextValue, IdiomaProviderProps, TFunction, TransInlineModeProps, TransKeyOnlyModeProps, TransSuspenseProps, __TransSuspense, __useTSuspense, createIdiomaProvider, createTransSuspense, createUseLocale, createUseTSuspense, preloadTranslations };
141
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/runtime-suspense/index.tsx"],"sourcesContent":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{interpolateValues as e,renderMessage as t}from"../interpolate.js";import{createContext as n,use as r,useContext as i,useMemo as a,version as o}from"react";import{jsx as s}from"react/jsx-runtime";import{c}from"react/compiler-runtime";if(parseInt(o.split(`.`)[0],10)<19)throw Error(`[idioma] useSuspense mode requires React 19+. You're using React ${o}. Either upgrade React or set useSuspense: false.`);const l=new Map;function u(e,t,n){let r=`${e}:${t}`;if(!l.has(r)){let t=n[e]().then(e=>e.default);l.set(r,t)}return l.get(r)}function d(e,t,n){u(e,t,n)}const f=n(null);function p(){return function({children:e,locale:t}){let n=a(()=>({locale:t}),[t]);return s(f.Provider,{value:n,children:e})}}function m(){return function(){let e=i(f);if(!e)throw Error(`[idioma] useLocale must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);return e.locale}}function h({__key:e,__chunk:n,__load:a,__a:o,__c:s,__cn:c}){let l=i(f);if(!l)throw Error(`[idioma] Trans must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);let{locale:d}=l,p=r(u(d,n,a))[e];return p===void 0?e:t(p,o,s,c)}function g(e){function t(e){let t=c(6);if(`children`in e&&e.children!==void 0)return e.children;let{__chunk:n,__load:r,id:i,values:a,components:o}=e;if(n&&r){let e;return t[0]!==n||t[1]!==r||t[2]!==o||t[3]!==i||t[4]!==a?(e=s(h,{__key:i,__chunk:n,__load:r,__a:a,__c:o}),t[0]=n,t[1]=r,t[2]=o,t[3]=i,t[4]=a,t[5]=e):e=t[5],e}throw Error(`[idioma] Key-only Trans in Suspense mode requires Babel transform. Make sure the Babel plugin is configured correctly.`)}return t}function _(t,n){let r=i(f);if(!r)throw Error(`[idioma] useT must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);let{locale:a}=r;return(t,n)=>(process.env.NODE_ENV!==`production`&&console.error(`Idioma: Missing translations for "${t}". Ensure the Babel plugin is configured.`),n&&Object.keys(n).length>0?e(t,n):t)}function v(e){return function(){throw Error(`[idioma] useT in Suspense mode requires Babel transform. Make sure the Babel plugin is configured correctly.`)}}export{f as IdiomaContext,h as __TransSuspense,_ as __useTSuspense,p as createIdiomaProvider,g as createTransSuspense,m as createUseLocale,v as createUseTSuspense,d as preloadTranslations};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["createContext","use","useContext","useMemo","version","interpolateValues","renderMessage","majorVersion","parseInt","split","Error","cache","Map","getTranslations","locale","chunk","loader","key","has","promise","then","mod","default","set","get","preloadTranslations","IdiomaContext","createIdiomaProvider","IdiomaProvider","children","value","createUseLocale","useLocale","context","__TransSuspense","__key","__chunk","__load","__a","__c","__cn","translations","msg","undefined","createTransSuspense","_config","Trans","props","$","_c","id","values","components","t0","__useTSuspense","_chunk","_loader","_locale","source","process","env","NODE_ENV","console","error","Object","keys","length","createUseTSuspense","useT"],"sources":["../../src/runtime-suspense/index.tsx"],"sourcesContent":["/**\n * Suspense-based runtime for @idioma/react.\n *\n * This module provides React components that use dynamic imports\n * and the React 19 `use()` hook for Suspense-based lazy loading.\n *\n * Requires React 19+.\n */\n\nimport {\n createContext,\n use,\n useContext,\n useMemo,\n version,\n type ReactNode,\n} from 'react';\nimport {\n interpolateValues,\n renderMessage,\n type TransComponent,\n} from '../interpolate';\n\n// ============ React Version Check ============\n\nconst majorVersion = parseInt(version.split('.')[0]!, 10);\n\nif (majorVersion < 19) {\n throw new Error(\n `[idioma] useSuspense mode requires React 19+. ` +\n `You're using React ${version}. ` +\n `Either upgrade React or set useSuspense: false.`,\n );\n}\n\n// ============ Types ============\n\ntype MessageFunction = (args: Record<string, unknown>) => string | ReactNode;\ntype MessageValue = string | MessageFunction;\n\ntype LoaderFn = () => Promise<{ default: Record<string, MessageValue> }>;\ntype Loader = Record<string, LoaderFn>;\n\ninterface SuspenseConfig {\n locales: readonly string[];\n}\n\n// ============ Promise Cache ============\n\nconst cache = new Map<string, Promise<Record<string, MessageValue>>>();\n\nfunction getTranslations(\n locale: string,\n chunk: string,\n loader: Loader,\n): Promise<Record<string, MessageValue>> {\n const key = `${locale}:${chunk}`;\n\n if (!cache.has(key)) {\n const promise = loader[locale]!().then((mod) => mod.default);\n cache.set(key, promise);\n }\n\n return cache.get(key)!;\n}\n\n/**\n * Preload translations for a locale/chunk combination.\n * Call this to warm the cache before rendering.\n */\nexport function preloadTranslations(\n locale: string,\n chunk: string,\n loader: Loader,\n): void {\n getTranslations(locale, chunk, loader);\n}\n\n// ============ Context ============\n\nexport interface IdiomaContextValue {\n locale: string;\n}\n\nexport const IdiomaContext = createContext<IdiomaContextValue | null>(null);\n\nexport interface IdiomaProviderProps {\n children: ReactNode;\n locale: string;\n}\n\n/**\n * Creates an IdiomaProvider for Suspense mode.\n * Same controlled API as the inlined runtime.\n */\nexport function createIdiomaProvider() {\n return function IdiomaProvider({ children, locale }: IdiomaProviderProps) {\n const value = useMemo(() => ({ locale }), [locale]);\n\n return (\n <IdiomaContext.Provider value={value}>{children}</IdiomaContext.Provider>\n );\n };\n}\n\n/**\n * Creates a useLocale hook that returns the current locale.\n */\nexport function createUseLocale() {\n return function useLocale(): string {\n const context = useContext(IdiomaContext);\n if (!context) {\n throw new Error(\n '[idioma] useLocale must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n return context.locale;\n };\n}\n\n// ============ Trans Component ============\n\n/**\n * Props for Babel-transformed Trans in Suspense mode.\n */\nexport interface TransSuspenseProps {\n /** Translation key */\n __key: string;\n /** Chunk identifier */\n __chunk: string;\n /** Loader object with locale → dynamic import */\n __load: Loader;\n /** Arguments for interpolation */\n __a?: Record<string, unknown>;\n /** Components for tag interpolation */\n __c?: TransComponent[];\n /** Component names for named tag matching (parallel to __c array) */\n __cn?: string[];\n}\n\n/**\n * Internal Trans component for Suspense mode.\n * Used by Babel-compiled output.\n */\nexport function __TransSuspense({\n __key,\n __chunk,\n __load,\n __a,\n __c,\n __cn,\n}: TransSuspenseProps): ReactNode {\n const context = useContext(IdiomaContext);\n if (!context) {\n throw new Error(\n '[idioma] Trans must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n\n const { locale } = context;\n\n // use() suspends until promise resolves\n const translations = use(getTranslations(locale, __chunk, __load));\n const msg = translations[__key];\n\n if (msg === undefined) {\n return __key; // Fallback to key\n }\n\n // Use shared rendering logic (handles ICU functions, tags, and values)\n return renderMessage(msg, __a, __c, __cn);\n}\n\n/**\n * Props for key-only Trans in Suspense mode.\n */\nexport interface TransKeyOnlyModeProps<\n K extends string,\n MV extends Record<string, Record<string, unknown>>,\n MC extends Record<string, TransComponent[]>,\n> {\n id: K;\n children?: never;\n context?: never;\n ns?: string;\n values?: K extends keyof MV ? MV[K] : Record<string, unknown>;\n components?: K extends keyof MC ? MC[K] : TransComponent[];\n // Suspense mode requires chunk/load from Babel transform\n __chunk?: string;\n __load?: Loader;\n}\n\n/**\n * Props for inline Trans in Suspense mode (dev only).\n */\nexport interface TransInlineModeProps {\n children: ReactNode;\n id?: string;\n context?: string;\n ns?: string;\n}\n\n/**\n * Base interface for project-specific Idioma types (for Trans).\n */\ninterface BaseIdiomaConfigForTrans {\n TranslationKey: string;\n MessageValues: Record<string, Record<string, unknown>>;\n MessageComponents: Record<string, TransComponent[]>;\n}\n\n/**\n * Creates a typed Trans component for Suspense mode.\n *\n * In development: renders children directly\n * In production: Babel transforms to __TransSuspense\n */\nexport function createTransSuspense<\n C extends BaseIdiomaConfigForTrans = BaseIdiomaConfigForTrans,\n>(_config: SuspenseConfig) {\n type TK = C['TranslationKey'];\n type MV = C['MessageValues'];\n type MC = C['MessageComponents'];\n\n function Trans(props: TransInlineModeProps): ReactNode;\n function Trans<K extends TK>(\n props: TransKeyOnlyModeProps<K & string, MV, MC>,\n ): ReactNode;\n function Trans(\n props: TransInlineModeProps | TransKeyOnlyModeProps<TK & string, MV, MC>,\n ): ReactNode {\n // Inline mode: children present - render them directly\n // In production, Babel transforms this to __TransSuspense\n if ('children' in props && props.children !== undefined) {\n return props.children;\n }\n\n // Key-only mode in Suspense: requires __chunk and __load from Babel\n const { __chunk, __load, id, values, components } =\n props as TransKeyOnlyModeProps<TK & string, MV, MC>;\n\n if (__chunk && __load) {\n return (\n <__TransSuspense\n __key={id}\n __chunk={__chunk}\n __load={__load}\n __a={values}\n __c={components}\n />\n );\n }\n\n // Fallback: no loader available (shouldn't happen in production)\n throw new Error(\n '[idioma] Key-only Trans in Suspense mode requires Babel transform. ' +\n 'Make sure the Babel plugin is configured correctly.',\n );\n }\n\n return Trans;\n}\n\n// ============ useT Hook ============\n\n/**\n * Base interface for project-specific Idioma types.\n * Same as BaseIdiomaConfig from createUseT.\n */\ninterface BaseIdiomaConfig {\n TranslationKey: string;\n MessageValues: Record<string, Record<string, unknown>>;\n MessageComponents: Record<string, unknown[]>;\n}\n\n/**\n * Derive string-only keys from TranslationKey + MessageComponents.\n * Keys with empty component arrays [] can be used with useT.\n * Keys with non-empty component arrays [TransComponent, ...] must use Trans.\n */\ntype StringOnlyKeys<TK extends string, MC extends Record<string, unknown[]>> = {\n [K in TK & keyof MC]: MC[K] extends [] ? K : never;\n}[TK & keyof MC];\n\nexport type TFunction<C extends BaseIdiomaConfig = BaseIdiomaConfig> = <\n Key extends StringOnlyKeys<C['TranslationKey'], C['MessageComponents']> &\n string,\n>(\n key: Key,\n values?: Key extends keyof C['MessageValues']\n ? C['MessageValues'][Key]\n : Record<string, unknown>,\n) => string;\n\n/**\n * Internal useT for Suspense mode.\n * Requires chunk and loader from Babel transform.\n */\nexport function __useTSuspense(\n _chunk: string,\n _loader: Loader,\n): (key: string, values?: Record<string, unknown>) => string {\n const context = useContext(IdiomaContext);\n if (!context) {\n throw new Error(\n '[idioma] useT must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n\n const { locale: _locale } = context;\n\n // Note: In properly transformed code, Babel provides the key directly.\n // This function is a fallback when Babel didn't transform.\n\n return (source: string, values?: Record<string, unknown>) => {\n // In Suspense mode, Babel should transform useT calls to include the key\n // If we get here with source text, Babel didn't transform - graceful fallback\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `Idioma: Missing translations for \"${source}\". ` +\n 'Ensure the Babel plugin is configured.',\n );\n }\n // Fallback: return source with values interpolated if possible\n if (values && Object.keys(values).length > 0) {\n return interpolateValues(source, values);\n }\n return source;\n };\n}\n\n/**\n * Creates a typed useT hook for Suspense mode.\n *\n * Note: In Suspense mode, useT is transformed by Babel to include\n * chunk and loader information.\n */\nexport function createUseTSuspense<\n C extends BaseIdiomaConfig = BaseIdiomaConfig,\n>(_config: SuspenseConfig): () => TFunction<C> {\n // Return a hook that throws when called\n // In production with proper Babel setup, this would be transformed\n return function useT(): TFunction<C> {\n throw new Error(\n '[idioma] useT in Suspense mode requires Babel transform. ' +\n 'Make sure the Babel plugin is configured correctly.',\n );\n };\n}\n"],"mappings":"gPA2BA,GAFqBQ,SAASJ,EAAQK,MAAM,IAAI,CAAC,GAAK,GAAG,CAEtC,GACjB,MAAUC,MACR,oEACwBN,EAAO,mDAEhC,CAiBH,MAAMO,EAAQ,IAAIC,IAElB,SAASC,EACPC,EACAC,EACAC,EACuC,CACvC,IAAMC,EAAM,GAAGH,EAAM,GAAIC,IAEzB,GAAI,CAACJ,EAAMO,IAAID,EAAI,CAAE,CACnB,IAAME,EAAUH,EAAOF,IAAU,CAACM,KAAMC,GAAQA,EAAIC,QAAQ,CAC5DX,EAAMY,IAAIN,EAAKE,EAAQ,CAGzB,OAAOR,EAAMa,IAAIP,EAAI,CAOvB,SAAgBQ,EACdX,EACAC,EACAC,EACM,CACNH,EAAgBC,EAAQC,EAAOC,EAAO,CASxC,MAAaU,EAAgB1B,EAAyC,KAAK,CAW3E,SAAgB2B,GAAuB,CACrC,OAAO,SAAwB,CAAEE,WAAUf,UAA+B,CACxE,IAAMgB,EAAQ3B,OAAe,CAAEW,SAAQ,EAAG,CAACA,EAAO,CAAC,CAEnD,OACE,EAAC,EAAc,SAAA,CAAgBgB,QAAQD,YAAkC,EAQ/E,SAAgBE,GAAkB,CAChC,OAAO,UAA6B,CAClC,IAAME,EAAU/B,EAAWwB,EAAc,CACzC,GAAI,CAACO,EACH,MAAUvB,MACR,8GAED,CAEH,OAAOuB,EAAQnB,QA4BnB,SAAgBoB,EAAgB,CAC9BC,QACAC,UACAC,SACAC,MACAC,MACAC,QACgC,CAChC,IAAMP,EAAU/B,EAAWwB,EAAc,CACzC,GAAI,CAACO,EACH,MAAUvB,MACR,0GAED,CAGH,GAAM,CAAEI,UAAWmB,EAIbS,EADezC,EAAIY,EAAgBC,EAAQsB,EAASC,EAAO,CAAC,CACzCF,GAOzB,OALIO,IAAQC,IAAAA,GACHR,EAIF7B,EAAcoC,EAAKJ,EAAKC,EAAKC,EAAK,CA+C3C,SAAgBI,EAEdC,EAAyB,CASzB,SAAAC,EAAAC,EAAA,CAAA,IAAAC,EAAAC,EAAA,EAAA,CAAA,GAKM,aAAcF,GAASA,EAAKlB,WAAAc,IAAAA,GAAuB,OAC9CI,EAAKlB,SAId,GAAA,CAAAO,UAAAC,SAAAa,KAAAC,SAAAC,cACEL,EAAoD,GAElDX,GAAWC,EAAM,CAAA,IAAAgB,EAQf,OAReL,EAAA,KAAAZ,GAAAY,EAAA,KAAAX,GAAAW,EAAA,KAAAI,GAAAJ,EAAA,KAAAE,GAAAF,EAAA,KAAAG,GAEjBE,EAAA,EAAA,EAAA,CACSH,MAAAA,EACEd,UACDC,SACHc,IAAAA,EACAC,IAAAA,GACL,CAAAJ,EAAA,GAAAZ,EAAAY,EAAA,GAAAX,EAAAW,EAAA,GAAAI,EAAAJ,EAAA,GAAAE,EAAAF,EAAA,GAAAG,EAAAH,EAAA,GAAAK,GAAAA,EAAAL,EAAA,GANFK,EAME,MAAA3C,MAMJ,yHACuD,CAI3D,OAAOoC,EAsCT,SAAgBQ,EACdC,EACAC,EAC2D,CAC3D,IAAMvB,EAAU/B,EAAWwB,EAAc,CACzC,GAAI,CAACO,EACH,MAAUvB,MACR,yGAED,CAGH,GAAM,CAAEI,OAAQ2C,GAAYxB,EAK5B,OAAQyB,EAAgBP,KAGlBQ,QAAQC,IAAIC,WAAa,cAC3BC,QAAQC,MACN,qCAAqCL,EAAM,2CAE5C,CAGCP,GAAUa,OAAOC,KAAKd,EAAO,CAACe,OAAS,EAClC7D,EAAkBqD,EAAQP,EAAO,CAEnCO,GAUX,SAAgBS,EAEdtB,EAA6C,CAG7C,OAAO,UAA8B,CACnC,MAAUnC,MACR,+GAED"}
@@ -0,0 +1,2 @@
1
+ import { generateKey } from "@idioma/core/keys";
2
+ export { generateKey };
@@ -0,0 +1 @@
1
+ import{generateKey as e}from"@idioma/core/keys";export{e as generateKey};
@@ -0,0 +1,2 @@
1
+ import { generateKey } from "./generateKey.js";
2
+ export { generateKey };
@@ -0,0 +1 @@
1
+ import{generateKey as e}from"./generateKey.js";export{e as generateKey};
package/dist/useT.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ //#region src/useT.d.ts
2
+ type MessageFunction = (args: any) => string;
3
+ type Translations = Record<string, Record<string, string | MessageFunction>>;
4
+ interface UseTOptions {
5
+ /** Optional namespace */
6
+ ns?: string;
7
+ }
8
+ /**
9
+ * Key-only mode arguments: t({ id: "welcome", values: { name }, context: "modal", ns: "common" })
10
+ */
11
+ interface KeyOnlyArgs {
12
+ id: string;
13
+ values?: Record<string, unknown>;
14
+ /** Translator context (saved in PO for human reference, not used in key lookup) */
15
+ context?: string;
16
+ /** Namespace for scoped lookups (placeholder for future) */
17
+ ns?: string;
18
+ }
19
+ /**
20
+ * Source text mode options: t("Submit", undefined, { context: "button", ns: "common" })
21
+ */
22
+ interface SourceTextOptions {
23
+ /** Translator context (changes key hash) */
24
+ context?: string;
25
+ /** Translator comment (extracted to PO #. comment for translators) */
26
+ comment?: string;
27
+ /** Namespace for scoped lookups (placeholder for future) */
28
+ ns?: string;
29
+ }
30
+ /**
31
+ * Type for the t function returned by __useT.
32
+ * Supports both source text mode and key-only mode.
33
+ */
34
+ type UseTFunction = {
35
+ (args: KeyOnlyArgs): string;
36
+ (source: string): string;
37
+ (source: string, values: Record<string, unknown>): string;
38
+ (source: string, values: Record<string, unknown> | undefined, options: SourceTextOptions): string;
39
+ };
40
+ /**
41
+ * Internal useT hook used by Babel-compiled output.
42
+ * Not intended for direct use - import useT from the generated idioma/ folder instead.
43
+ *
44
+ * Supports both source text mode and key-only mode:
45
+ * - Source text: t('Hello world!') - hashes and looks up
46
+ * - Key-only: t({ id: 'welcome' }) - direct key lookup
47
+ *
48
+ * @example
49
+ * // Compiled output:
50
+ * const t = __useT(__$idioma)
51
+ * const label = t('Hello world!')
52
+ * const greeting = t('Hello {name}', { name })
53
+ * t('Submit', undefined, { context: 'button' }) // with options (3rd arg)
54
+ * t({ id: 'key', ns: 'common' }) // key-only with namespace
55
+ */
56
+ declare function __useT(translations: Translations, _options?: UseTOptions): UseTFunction;
57
+ //#endregion
58
+ export { KeyOnlyArgs, SourceTextOptions, UseTFunction, UseTOptions, __useT };
59
+ //# sourceMappingURL=useT.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useT.d.ts","names":[],"sources":["../src/useT.ts"],"sourcesContent":[],"mappings":";KAKK,eAAA;KACA,YAAA,GAAe,MADA,CAAA,MAAA,EACe,MADf,CAAA,MAAA,EAAA,MAAA,GACuC,eADvC,CAAA,CAAA;AACf,UAEY,WAAA,CAFA;EAA0C;EAAxB,EAAA,CAAA,EAAA,MAAA;;;AAEnC;AAQA;AAYiB,UAZA,WAAA,CAYiB;EAatB,EAAA,EAAA,MAAA;EACH,MAAA,CAAA,EAxBE,MAwBF,CAAA,MAAA,EAAA,OAAA,CAAA;EAEkB;EAGf,OAAA,CAAA,EAAA,MAAA;EACC;EAAiB,EAAA,CAAA,EAAA,MAAA;AAoB9B;;;;AAGe,UA3CE,iBAAA,CA2CF;;;;;;;;;;;;KA9BH,YAAA;SACH;;2BAEkB;2BAGf,8CACC;;;;;;;;;;;;;;;;;;iBAoBG,MAAA,eACA,yBACH,cACV"}
package/dist/useT.js ADDED
@@ -0,0 +1,2 @@
1
+ import{IdiomaContext as e}from"./context.js";import{interpolateValues as t}from"./interpolate.js";import{useCallback as n,useContext as r}from"react";function i(i,a){let o=r(e);if(!o)throw Error(`[idioma] useT must be used within an IdiomaProvider. Make sure to wrap your app with <IdiomaProvider>.`);let{locale:s}=o;return n((e,n,r)=>{if(typeof e==`object`){let{id:n,values:r}=e,a=i[n];if(!a)return n;let o=a[s]??Object.values(a)[0];return o===void 0?n:typeof o==`function`?o(r||{}):r?t(o,r):o}let a=e,o=n,c,l=n?Object.keys(n):[],u=l[0],d=u&&n?n[u]:void 0;if(l.length===1&&d&&typeof d==`object`&&d[s]!==void 0)c=d,o=r;else return process.env.NODE_ENV!==`production`&&console.error(`Idioma: Missing translations for "${a}". Ensure the Babel plugin is configured.`),o&&Object.keys(o).length>0?t(a,o):a;if(!c)return o&&Object.keys(o).length>0?t(a,o):a;let f=c[s]??Object.values(c)[0];return f===void 0?o&&Object.keys(o).length>0?t(a,o):a:typeof f==`function`?f(o||{}):o&&Object.keys(o).length>0?t(f,o):f},[i,s])}export{i as __useT};
2
+ //# sourceMappingURL=useT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useT.js","names":["useCallback","useContext","IdiomaContext","interpolateValues","__useT","translations","_options","context","Error","locale","sourceOrArgs","values","options","id","keyValues","localeMessages","msg","Object","undefined","source","actualValues","valuesKeys","keys","firstKey","firstValue","length","process","env","NODE_ENV","console","error"],"sources":["../src/useT.ts"],"sourcesContent":["import { useCallback, useContext } from 'react';\nimport { IdiomaContext } from './context';\nimport { interpolateValues } from './interpolate';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype MessageFunction = (args: any) => string;\ntype Translations = Record<string, Record<string, string | MessageFunction>>;\n\nexport interface UseTOptions {\n /** Optional namespace */\n ns?: string;\n}\n\n/**\n * Key-only mode arguments: t({ id: \"welcome\", values: { name }, context: \"modal\", ns: \"common\" })\n */\nexport interface KeyOnlyArgs {\n id: string;\n values?: Record<string, unknown>;\n /** Translator context (saved in PO for human reference, not used in key lookup) */\n context?: string;\n /** Namespace for scoped lookups (placeholder for future) */\n ns?: string;\n}\n\n/**\n * Source text mode options: t(\"Submit\", undefined, { context: \"button\", ns: \"common\" })\n */\nexport interface SourceTextOptions {\n /** Translator context (changes key hash) */\n context?: string;\n /** Translator comment (extracted to PO #. comment for translators) */\n comment?: string;\n /** Namespace for scoped lookups (placeholder for future) */\n ns?: string;\n}\n\n/**\n * Type for the t function returned by __useT.\n * Supports both source text mode and key-only mode.\n */\nexport type UseTFunction = {\n (args: KeyOnlyArgs): string;\n (source: string): string;\n (source: string, values: Record<string, unknown>): string;\n (\n source: string,\n values: Record<string, unknown> | undefined,\n options: SourceTextOptions,\n ): string;\n};\n\n/**\n * Internal useT hook used by Babel-compiled output.\n * Not intended for direct use - import useT from the generated idioma/ folder instead.\n *\n * Supports both source text mode and key-only mode:\n * - Source text: t('Hello world!') - hashes and looks up\n * - Key-only: t({ id: 'welcome' }) - direct key lookup\n *\n * @example\n * // Compiled output:\n * const t = __useT(__$idioma)\n * const label = t('Hello world!')\n * const greeting = t('Hello {name}', { name })\n * t('Submit', undefined, { context: 'button' }) // with options (3rd arg)\n * t({ id: 'key', ns: 'common' }) // key-only with namespace\n */\nexport function __useT(\n translations: Translations,\n _options?: UseTOptions,\n): UseTFunction {\n const context = useContext(IdiomaContext);\n if (!context) {\n throw new Error(\n '[idioma] useT must be used within an IdiomaProvider. ' +\n 'Make sure to wrap your app with <IdiomaProvider>.',\n );\n }\n\n const { locale } = context;\n\n return useCallback(\n (\n sourceOrArgs: string | KeyOnlyArgs,\n values?: Record<string, unknown>,\n options?: SourceTextOptions,\n ): string => {\n // Key-only mode: t({ id: 'welcome', values: { name }, ns: 'common' })\n if (typeof sourceOrArgs === 'object') {\n const { id, values: keyValues } = sourceOrArgs;\n const localeMessages = translations[id];\n if (!localeMessages) return id;\n\n const msg = localeMessages[locale] ?? Object.values(localeMessages)[0];\n if (msg === undefined) return id;\n if (typeof msg === 'function') return msg(keyValues || {});\n return keyValues ? interpolateValues(msg, keyValues) : msg;\n }\n\n // Source text mode: t('Hello {name}', { name: 'Ben' }, { context: 'button' })\n // OR after Babel transformation: t('Hello {name}', { en: '...', es: '...' }, { name: 'Ben' })\n const source = sourceOrArgs;\n\n // Detect if Babel has inlined translations in arg 2\n // Babel transforms: t('src', { key: { en: '...', es: '...' } }, { actualValues })\n // The inlined object has ONE key (the message key) whose value has locale keys\n let actualValues = values;\n let localeMessages: Record<string, string | MessageFunction> | undefined;\n\n const valuesKeys = values ? Object.keys(values) : [];\n const firstKey = valuesKeys[0];\n const firstValue =\n firstKey && values\n ? (values as Record<string, unknown>)[firstKey]\n : undefined;\n\n // Check if this looks like Babel-inlined translations:\n // - Has exactly one key (the message key)\n // - That key's value is an object with locale as a property\n if (\n valuesKeys.length === 1 &&\n firstValue &&\n typeof firstValue === 'object' &&\n typeof (firstValue as Record<string, unknown>)[locale] !== 'undefined'\n ) {\n // Babel transformed: arg 2 is { messageKey: { en: '...', es: '...' } }\n localeMessages = firstValue as Record<string, string | MessageFunction>;\n actualValues = options as unknown as Record<string, unknown>;\n } else {\n // Babel didn't inline translations - graceful fallback\n if (process.env.NODE_ENV !== 'production') {\n console.error(\n `Idioma: Missing translations for \"${source}\". ` +\n 'Ensure the Babel plugin is configured.',\n );\n }\n // Return source text (with interpolation if values provided)\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(source, actualValues)\n : source;\n }\n\n if (!localeMessages) {\n // Fallback: interpolate source text if values provided\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(source, actualValues)\n : source;\n }\n\n const msg = localeMessages[locale] ?? Object.values(localeMessages)[0];\n if (msg === undefined) {\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(source, actualValues)\n : source;\n }\n\n if (typeof msg === 'function') {\n return msg(actualValues || {});\n }\n\n return actualValues && Object.keys(actualValues).length > 0\n ? interpolateValues(msg, actualValues)\n : msg;\n },\n [translations, locale],\n ) as UseTFunction;\n}\n"],"mappings":"sJAoEA,SAAgBI,EACdC,EACAC,EACc,CACd,IAAMC,EAAUN,EAAWC,EAAc,CACzC,GAAI,CAACK,EACH,MAAUC,MACR,yGAED,CAGH,GAAM,CAAEC,UAAWF,EAEnB,OAAOP,GAEHU,EACAC,EACAC,IACW,CAEX,GAAI,OAAOF,GAAiB,SAAU,CACpC,GAAM,CAAEG,KAAIF,OAAQG,GAAcJ,EAC5BK,EAAiBV,EAAaQ,GACpC,GAAI,CAACE,EAAgB,OAAOF,EAE5B,IAAMG,EAAMD,EAAeN,IAAWQ,OAAON,OAAOI,EAAe,CAAC,GAGpE,OAFIC,IAAQE,IAAAA,GAAkBL,EAC1B,OAAOG,GAAQ,WAAmBA,EAAIF,GAAa,EAAE,CAAC,CACnDA,EAAYX,EAAkBa,EAAKF,EAAU,CAAGE,EAKzD,IAAMG,EAAST,EAKXU,EAAeT,EACfI,EAEEM,EAAaV,EAASM,OAAOK,KAAKX,EAAO,CAAG,EAAE,CAC9CY,EAAWF,EAAW,GACtBG,EACJD,GAAYZ,EACPA,EAAmCY,GACpCL,IAAAA,GAKN,GACEG,EAAWI,SAAW,GACtBD,GACA,OAAOA,GAAe,UACdA,EAAuCf,KAAY,OAG3DM,EAAiBS,EACjBJ,EAAeR,OAUf,OAPIc,QAAQC,IAAIC,WAAa,cAC3BC,QAAQC,MACN,qCAAqCX,EAAM,2CAE5C,CAGIC,GAAgBH,OAAOK,KAAKF,EAAa,CAACK,OAAS,EACtDtB,EAAkBgB,EAAQC,EAAa,CACvCD,EAGN,GAAI,CAACJ,EAEH,OAAOK,GAAgBH,OAAOK,KAAKF,EAAa,CAACK,OAAS,EACtDtB,EAAkBgB,EAAQC,EAAa,CACvCD,EAGN,IAAMH,EAAMD,EAAeN,IAAWQ,OAAON,OAAOI,EAAe,CAAC,GAWpE,OAVIC,IAAQE,IAAAA,GACHE,GAAgBH,OAAOK,KAAKF,EAAa,CAACK,OAAS,EACtDtB,EAAkBgB,EAAQC,EAAa,CACvCD,EAGF,OAAOH,GAAQ,WACVA,EAAII,GAAgB,EAAE,CAAC,CAGzBA,GAAgBH,OAAOK,KAAKF,EAAa,CAACK,OAAS,EACtDtB,EAAkBa,EAAKI,EAAa,CACpCJ,GAEN,CAACX,EAAcI,EACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@idioma/react",
3
+ "version": "0.1.0",
4
+ "description": "Compile-time React i18n - runtime package",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./runtime-suspense": {
15
+ "types": "./dist/runtime-suspense/index.d.ts",
16
+ "import": "./dist/runtime-suspense/index.js"
17
+ },
18
+ "./server": {
19
+ "types": "./dist/server/index.d.ts",
20
+ "import": "./dist/server/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "peerDependencies": {
27
+ "react": "^18.0.0 || ^19.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@babel/preset-typescript": "^7.28.0",
31
+ "@rollup/plugin-babel": "^6.0.0",
32
+ "@testing-library/react": "^16.0.0",
33
+ "babel-plugin-react-compiler": "19.1.0-rc.2",
34
+ "@types/react": "^19.0.0",
35
+ "@types/react-dom": "^19.0.0",
36
+ "jsdom": "^25.0.0",
37
+ "react": "^18.2.0",
38
+ "react-dom": "^18.2.0"
39
+ },
40
+ "keywords": [
41
+ "i18n",
42
+ "react",
43
+ "internationalization",
44
+ "localization",
45
+ "translation"
46
+ ],
47
+ "license": "MIT",
48
+ "dependencies": {
49
+ "@idioma/core": "0.1.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsdown",
53
+ "dev": "tsdown --watch",
54
+ "typecheck": "tsc --noEmit",
55
+ "clean": "rm -rf dist"
56
+ }
57
+ }