@idioma/react 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/interpolate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentType, ReactNode } from "react";
|
|
1
|
+
import { ComponentType, ReactElement, ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
//#region src/interpolate.d.ts
|
|
4
4
|
|
|
@@ -17,7 +17,7 @@ import { ComponentType, ReactNode } from "react";
|
|
|
17
17
|
declare function interpolateValues(message: string, args: Record<string, unknown>): string;
|
|
18
18
|
type TransComponent = ComponentType<{
|
|
19
19
|
children?: ReactNode;
|
|
20
|
-
}
|
|
20
|
+
}> | ReactElement;
|
|
21
21
|
/**
|
|
22
22
|
* Interpolates tags in a translated string with React components.
|
|
23
23
|
* Supports both named tags (<Link>text</Link>) and numbered tags (<0>text</0>).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interpolate.d.ts","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"interpolate.d.ts","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":[],"mappings":";;;;;;AAsBA;AAcA;;;;;AAqCA;;;;AAKY,iBAxDI,iBAAA,CAwDJ,OAAA,EAAA,MAAA,EAAA,IAAA,EAtDJ,MAsDI,CAAA,MAAA,EAAA,OAAA,CAAA,CAAA,EAAA,MAAA;AAqOI,KA/QJ,cAAA,GACR,aA8QyB,CAAA;EACL,QAAA,CAAA,EA/QO,SA+QP;CAAqC,CAAA,GA9QzD,YA8QyD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA3O7C,eAAA,8BAEF,yBACL,qDAEN;;;;;;;;;;iBAqOa,aAAA,uBACQ,qCAAqC,mBACpD,sCACM,8CAEZ"}
|
package/dist/interpolate.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Fragment as e,createElement as
|
|
1
|
+
import{Fragment as e,cloneElement as t,createElement as n,isValidElement as r}from"react";function i(e,t){return e.replace(/\{([^}]+)\}/g,(e,n)=>{let r=t[n];return r===void 0?e:String(r)})}function a(e,t,n,r){return o(e,t,n,0,void 0,r,new Set).result}function o(a,c,l,u,d,f,p){let m=[],h=u,g=u;for(;h<a.length;){if(d&&a.startsWith(d,h)){if(h>g){let e=a.slice(g,h);m.push(l?i(e,l):e)}return{result:m.length===1?m[0]:n(e,null,...m),endIndex:h+d.length}}let u=a.slice(h).match(/^<([a-zA-Z][a-zA-Z0-9]*|\d+)>/);if(u){if(h>g){let e=a.slice(g,h);m.push(l?i(e,l):e)}let e=u[1],d=/^\d+$/.test(e),_;_=d?parseInt(e,10):s(e,f,p);let v=`</${e}>`,y=u[0].length;p?.add(_);let b=o(a,c,l,h+y,v,f,p),x=c[_];x?r(x)?m.push(t(x,{key:`${e}-${h}`},b.result)):m.push(n(x,{key:`${e}-${h}`,children:b.result})):m.push(b.result),h=b.endIndex,g=h}else{let e=a.slice(h).match(/^<([a-zA-Z][a-zA-Z0-9]*|\d+)\/>/);if(e){if(h>g){let e=a.slice(g,h);m.push(l?i(e,l):e)}let o=e[1],u=/^\d+$/.test(o),d;d=u?parseInt(o,10):s(o,f,p),p?.add(d);let _=c[d];_&&(r(_)?m.push(t(_,{key:`${o}-${h}`})):m.push(n(_,{key:`${o}-${h}`}))),h+=e[0].length,g=h}else h++}}if(g<a.length){let e=a.slice(g);m.push(l?i(e,l):e)}return m.length===0?{result:``,endIndex:h}:m.length===1?{result:m[0],endIndex:h}:{result:n(e,null,...m),endIndex:h}}function s(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 c(e,t,n,r){return typeof e==`function`?e(t||{}):n&&n.length>0?a(e,n,t,r):t&&Object.keys(t).length>0?i(e,t):e}export{a as interpolateTags,i as interpolateValues,c as renderMessage};
|
|
2
2
|
//# sourceMappingURL=interpolate.js.map
|
package/dist/interpolate.js.map
CHANGED
|
@@ -1 +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":"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"}
|
|
1
|
+
{"version":3,"file":"interpolate.js","names":["cloneElement","createElement","Fragment","isValidElement","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 cloneElement,\n createElement,\n Fragment,\n isValidElement,\n type ComponentType,\n type ReactElement,\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 =\n | ComponentType<{ children?: ReactNode }>\n | ReactElement;\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 if (isValidElement(Component)) {\n parts.push(\n cloneElement(\n Component,\n { key: `${tagIdentifier}-${i}` },\n innerResult.result,\n ),\n );\n } else {\n parts.push(\n createElement(Component, {\n key: `${tagIdentifier}-${i}`,\n children: innerResult.result,\n }),\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 if (isValidElement(Component)) {\n parts.push(\n cloneElement(Component, { key: `${tagIdentifier}-${i}` }),\n );\n } else {\n parts.push(\n createElement(Component, { key: `${tagIdentifier}-${i}` }),\n );\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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idioma/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Compile-time React i18n - runtime package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
],
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@idioma/core": "0.1.
|
|
52
|
+
"@idioma/core": "0.1.7"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsdown",
|