@lingo.dev/react 1.0.0 → 1.0.2

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/index.cjs CHANGED
@@ -160,7 +160,7 @@ function resolveText(source, messages, locale, options) {
160
160
  }
161
161
  }
162
162
  //#endregion
163
- //#region src/jsx.ts
163
+ //#region src/rich.ts
164
164
  const TAG_RE = /<(\w+)>([\s\S]*?)<\/\1>|<(\w+)\s*\/>/g;
165
165
  function renderTemplate(template, tags, values) {
166
166
  const parts = [];
@@ -183,10 +183,10 @@ function renderTemplate(template, tags, values) {
183
183
  return (0, react.createElement)(react.Fragment, null, ...parts);
184
184
  }
185
185
  /**
186
- * Resolves a translated string with JSX tag interpolation.
186
+ * Resolves a translated string with rich text tag interpolation.
187
187
  * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.
188
188
  */
189
- function resolveJsx(source, messages, locale, options) {
189
+ function resolveRich(source, messages, locale, options) {
190
190
  return renderTemplate(lookupTemplate(source, messages, locale, options?.context), options?.tags ?? {}, options?.values ?? {});
191
191
  }
192
192
  //#endregion
@@ -254,7 +254,7 @@ function createLingo(locale, messages = {}) {
254
254
  script,
255
255
  region,
256
256
  text: (source, options) => resolveText(source, messages, locale, options),
257
- jsx: (source, options) => resolveJsx(source, messages, locale, options),
257
+ rich: (source, options) => resolveRich(source, messages, locale, options),
258
258
  plural: (count, forms, options) => {
259
259
  return resolveText((0, _lingo_dev_spec.buildIcuPlural)(forms), messages, locale, {
260
260
  context: options?.context ?? "",
package/dist/index.d.cts CHANGED
@@ -134,20 +134,20 @@ type TextOptions = {
134
134
  context: string;
135
135
  };
136
136
  //#endregion
137
- //#region src/jsx.d.ts
137
+ //#region src/rich.d.ts
138
138
  /**
139
- * Options for `l.jsx()` rich text translation calls.
139
+ * Options for `l.rich()` rich text translation calls.
140
140
  *
141
141
  * @example
142
142
  * ```tsx
143
- * l.jsx("Click <link>here</link> for {topic}", {
143
+ * l.rich("Click <link>here</link> for {topic}", {
144
144
  * tags: { link: (children) => <a href="/help">{children}</a> },
145
145
  * values: { topic: "details" },
146
146
  * context: "Footer",
147
147
  * })
148
148
  * ```
149
149
  */
150
- type JsxOptions = {
150
+ type RichOptions = {
151
151
  /** Map tag names to React component renderers. `<tag>children</tag>` and `<tag/>` supported. */
152
152
  tags?: Record<string, (children: ReactNode) => ReactNode>;
153
153
  /** Interpolation values for `{placeholder}` substitution in text segments. */
@@ -156,7 +156,7 @@ type JsxOptions = {
156
156
  context: string;
157
157
  };
158
158
  /**
159
- * Resolves a translated string with JSX tag interpolation.
159
+ * Resolves a translated string with rich text tag interpolation.
160
160
  * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.
161
161
  */
162
162
  //#endregion
@@ -177,7 +177,7 @@ type Messages = Record<string, string>;
177
177
  /**
178
178
  * Augment this interface via `declare module "@lingo.dev/react"` in a
179
179
  * generated .d.ts to get autocomplete, value validation, and context enforcement
180
- * on l.text() and l.jsx().
180
+ * on l.text() and l.rich().
181
181
  *
182
182
  * When empty (default): l.text() accepts any string, context required as `string`.
183
183
  * When populated (after `lingo extract`): l.text() only accepts listed source strings,
@@ -204,13 +204,13 @@ type MessageKey = [keyof LingoMessages] extends [never] ? string : Extract<keyof
204
204
  */
205
205
  type TextCallArgs<K extends string> = K extends keyof LingoMessages ? [options: LingoMessages[K]] : [options: TextOptions];
206
206
  /**
207
- * Rest args for l.jsx(): context is always required, tags optional.
207
+ * Rest args for l.rich(): context is always required, tags optional.
208
208
  * Before extraction: accepts any string for context.
209
209
  * After extraction: narrows to exact context union + optional tags.
210
210
  */
211
- type JsxCallArgs<K extends string> = K extends keyof LingoMessages ? [options: LingoMessages[K] & {
211
+ type RichCallArgs<K extends string> = K extends keyof LingoMessages ? [options: LingoMessages[K] & {
212
212
  tags?: Record<string, (children: ReactNode) => ReactNode>;
213
- }] : [options: JsxOptions];
213
+ }] : [options: RichOptions];
214
214
  /**
215
215
  * CLDR plural category forms for `l.plural()`.
216
216
  * `other` is required as the fallback. All other categories are optional
@@ -243,7 +243,7 @@ type SelectForms = {
243
243
  };
244
244
  /**
245
245
  * The localization object returned by `useLingo()` and `createLingo()`.
246
- * Provides text translation, JSX interpolation, plural/select formatting,
246
+ * Provides text translation, rich text interpolation, plural/select formatting,
247
247
  * and locale identity (direction, script, region).
248
248
  *
249
249
  * Context is required on every translation call for better translation quality.
@@ -253,7 +253,7 @@ type SelectForms = {
253
253
  * const l = useLingo();
254
254
  * l.text("Hello", { context: "Hero greeting" });
255
255
  * l.text("Welcome, {name}", { context: "Dashboard", values: { name: "Max" } });
256
- * l.jsx("Click <link>here</link>", {
256
+ * l.rich("Click <link>here</link>", {
257
257
  * context: "Help banner",
258
258
  * tags: { link: (c) => <a href="/help">{c}</a> },
259
259
  * });
@@ -283,18 +283,18 @@ type Lingo = {
283
283
  */
284
284
  readonly text: <K extends MessageKey>(source: K, ...args: TextCallArgs<K>) => string;
285
285
  /**
286
- * Translate rich text with JSX tag interpolation. Returns ReactNode.
286
+ * Translate rich text with tag interpolation. Returns ReactNode.
287
287
  * Context is always required.
288
288
  *
289
289
  * @example
290
290
  * ```tsx
291
- * l.jsx("Click <link>here</link>", {
291
+ * l.rich("Click <link>here</link>", {
292
292
  * context: "Help banner",
293
293
  * tags: { link: (children) => <a href="/help">{children}</a> },
294
294
  * })
295
295
  * ```
296
296
  */
297
- readonly jsx: <K extends MessageKey>(source: K, ...args: JsxCallArgs<K>) => ReactNode;
297
+ readonly rich: <K extends MessageKey>(source: K, ...args: RichCallArgs<K>) => ReactNode;
298
298
  /**
299
299
  * Format a count using locale-aware CLDR plural rules.
300
300
  * `#` in form strings is replaced with the formatted count.
@@ -391,5 +391,5 @@ declare function useLingo(): Lingo;
391
391
  //# sourceMappingURL=provider.d.ts.map
392
392
 
393
393
  //#endregion
394
- export { type FormatMethods, type JsxOptions, type Lingo, type LingoMessages, LingoProvider, type LingoProviderProps, type MessageKey, type Messages, type PluralForms, type SelectForms, type TextOptions, computeKey, createLingo, useLingo };
394
+ export { type FormatMethods, type Lingo, type LingoMessages, LingoProvider, type LingoProviderProps, type MessageKey, type Messages, type PluralForms, type RichOptions, type SelectForms, type TextOptions, computeKey, createLingo, useLingo };
395
395
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":["computeKey","buildIcuPlural","Record","buildIcuSelect","EntryMetadata","LocaleEntry","LocaleFile","getActiveEntries","readLocaleFile"],"sources":["../../spec/dist/index.d.ts","../src/format.ts","../src/text.ts","../src/jsx.ts","../src/types.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["//#region src/hash.d.ts\n/**\n * Deterministic key generation from (source text, context) pairs.\n * Shared between runtime (message lookup) and build tools (extraction, JSONC generation).\n *\n * Uses MurmurHash3 x86_128 on NFC-normalized UTF-8 bytes.\n * Outputs an 8-character base62 string (e.g., \"aB3dEf9x\").\n *\n * Cross-platform deterministic: JS, Rust, Go, Python produce identical keys\n * when given the same (source, context) pair.\n *\n * 1% collision probability at ~2.4 million messages (48-bit effective entropy).\n */\n/**\n * Computes a deterministic short key from source text and optional context.\n *\n * Same source + same context = same key (deterministic).\n * Same source + different context = different key (disambiguation).\n */\ndeclare function computeKey(source: string, context?: string): string;\n//# sourceMappingURL=hash.d.ts.map\n//#endregion\n//#region src/icu.d.ts\n/**\n * Canonical ICU MessageFormat expression builders.\n * Shared between runtime (l.plural/l.select) and build tools (extraction).\n *\n * These must produce identical output everywhere so that computeKey()\n * generates matching hash keys at build time and runtime.\n */\ndeclare function buildIcuPlural(forms: Record<string, string>): string;\ndeclare function buildIcuSelect(forms: Record<string, string>): string;\n//# sourceMappingURL=icu.d.ts.map\n\n//#endregion\n//#region src/jsonc.d.ts\n/**\n * JSONC locale file reader with structured metadata comments.\n * Lives in @lingo.dev/spec so framework adapters can parse locale files\n * without depending on @lingo.dev/cli.\n *\n * File format:\n * ```jsonc\n * {\n * /*\n * * @context Hero heading\n * * @src app/hero.tsx:12\n * *​/\n * \"mK9xqZ\": \"Welcome to Acme\"\n * }\n * ```\n */\ntype EntryMetadata = {\n context?: string;\n src?: string;\n orphan?: boolean;\n};\ntype LocaleEntry = {\n key: string;\n value: string;\n metadata: EntryMetadata;\n};\ntype LocaleFile = {\n entries: LocaleEntry[];\n};\n/** Filters out orphaned entries, returning only active (non-orphaned) ones. */\ndeclare function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[];\n/**\n * Parses a JSONC string into structured locale entries with metadata.\n * Extracts @context, @src, and @orphan from block comments preceding each key.\n * Pure function - no filesystem access. Works in Node.js and edge runtimes.\n */\ndeclare function readLocaleFile(content: string): LocaleFile;\n//# sourceMappingURL=jsonc.d.ts.map\n//#endregion\nexport { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };\n//# sourceMappingURL=index.d.ts.map"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":["computeKey","buildIcuPlural","Record","buildIcuSelect","EntryMetadata","LocaleEntry","LocaleFile","getActiveEntries","readLocaleFile"],"sources":["../../spec/dist/index.d.ts","../src/format.ts","../src/text.ts","../src/rich.ts","../src/types.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["//#region src/hash.d.ts\n/**\n * Deterministic key generation from (source text, context) pairs.\n * Shared between runtime (message lookup) and build tools (extraction, JSONC generation).\n *\n * Uses MurmurHash3 x86_128 on NFC-normalized UTF-8 bytes.\n * Outputs an 8-character base62 string (e.g., \"aB3dEf9x\").\n *\n * Cross-platform deterministic: JS, Rust, Go, Python produce identical keys\n * when given the same (source, context) pair.\n *\n * 1% collision probability at ~2.4 million messages (48-bit effective entropy).\n */\n/**\n * Computes a deterministic short key from source text and optional context.\n *\n * Same source + same context = same key (deterministic).\n * Same source + different context = different key (disambiguation).\n */\ndeclare function computeKey(source: string, context?: string): string;\n//# sourceMappingURL=hash.d.ts.map\n//#endregion\n//#region src/icu.d.ts\n/**\n * Canonical ICU MessageFormat expression builders.\n * Shared between runtime (l.plural/l.select) and build tools (extraction).\n *\n * These must produce identical output everywhere so that computeKey()\n * generates matching hash keys at build time and runtime.\n */\ndeclare function buildIcuPlural(forms: Record<string, string>): string;\ndeclare function buildIcuSelect(forms: Record<string, string>): string;\n//# sourceMappingURL=icu.d.ts.map\n\n//#endregion\n//#region src/jsonc.d.ts\n/**\n * JSONC locale file reader with structured metadata comments.\n * Lives in @lingo.dev/spec so framework adapters can parse locale files\n * without depending on @lingo.dev/cli.\n *\n * File format:\n * ```jsonc\n * {\n * /*\n * * @context Hero heading\n * * @src app/hero.tsx:12\n * *​/\n * \"mK9xqZ\": \"Welcome to Acme\"\n * }\n * ```\n */\ntype EntryMetadata = {\n context?: string;\n src?: string;\n orphan?: boolean;\n};\ntype LocaleEntry = {\n key: string;\n value: string;\n metadata: EntryMetadata;\n};\ntype LocaleFile = {\n entries: LocaleEntry[];\n};\n/** Filters out orphaned entries, returning only active (non-orphaned) ones. */\ndeclare function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[];\n/**\n * Parses a JSONC string into structured locale entries with metadata.\n * Extracts @context, @src, and @orphan from block comments preceding each key.\n * Pure function - no filesystem access. Works in Node.js and edge runtimes.\n */\ndeclare function readLocaleFile(content: string): LocaleFile;\n//# sourceMappingURL=jsonc.d.ts.map\n//#endregion\nexport { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;;;;;;;;ACQA;;;;;;;;;;;iBDWiBA,UAAAA,CCoCY,MAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;;;;;AC3C7B;;;;;;;;KDJY,aAAA;;;AAAZ;;WAK+C,GAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAL,IAAA,CAAK,mBAAA,EAAA,GAAA,MAAA;;;;;WA8BtB,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAxBwC,IAAA,CAAK,mBAwB7C,EAAA,GAAA,MAAA;;;;;WAYkC,OAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EA9Bb,IAAA,CAAK,mBA8BQ,EAAA,GAAA,MAAA;;;;;EA6BM,SAAA,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EArDR,IAAA,CAAK,mBAqDG,EAAA,GAAA,MAAA;;;;ACxEjE;8CDyB8C,IAAA,CAAK;;;AErBnD;;WAEmC,IAAA,EAAA,CAAA,KAAA,EFyBV,IEzBU,GAAA,MAAA,EAAA,OAAA,CAAA,EFyBe,IAAA,CAAK,qBEzBpB,EAAA,GAAA,MAAA;;;;;yBF+BV,yBAAyB,IAAA,CAAK;;;AGhCvD;AAwBA;EAGY,SAAA,QAAU,EAAA,CAAA,KAAA,EHWO,IGXP,GAAA,MAAA,EAAA,OAAA,CAAA,EHWgC,IAAA,CAAK,qBGXrC,EAAA,GAAA,MAAA;EAAA;;;;EAA2D,SAAA,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,IAAA,EHmBvE,IAAA,CAAK,sBGnBkE,EAAA,OAAA,CAAA,EHoBnE,IAAA,CAAK,yBGpB8D,EAAA,GAAA,MAAA;EAOrE;;;;WACE,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,CAAA,EHmB+B,IAAA,CAAK,iBGnBpC,EAAA,GAAA,MAAA;;;;AAQd;EAAwB,SAAA,WAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,UAAA,GAAA,QAAA,GAAA,QAAA,GAAA,UAAA,EAAA,GAAA,MAAA,GAAA,SAAA;;;;;;WAC0D,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,CAAA,EHuBrC,IAAA,CAAK,eGvBgC,EAAA,GAAA,MAAA,EAAA;;;;AAelF;AAiBA;EAsBY,SAAK,OAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,WAAA,CAAA,EAAA,UAAA,GAAA,MAAA,GAAA,UAAA,EAAA,GAAA,MAAA,EAAA;EAAA;;;;WAoB2C,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,MAAA;;;;;;;;;;;;;;AH/H5D;AAAyB,KCIb,WAAA,GDJa;;QAWwC,CAAA,ECLtD,MDK2D,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,CAAA;;SAYb,EAAK,MAAA;;;;;;;;;;AAvB9D;;;;;;AA6B8C,KErBlC,WAAA,GFqBuC;;MAMD,CAAA,EEzBzC,MFyB8C,CAAA,MAAA,EAAA,CAAA,QAAA,EEzBpB,SFyBoB,EAAA,GEzBN,SFyBM,CAAA;;QAML,CAAA,EE7BvC,MF6B4C,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,CAAA;;SAMD,EAAK,MAAA;;;;;;;;;;;;AA/C3D;;;;;;;;AAmCkD,KG1BtC,QAAA,GAAW,MH0BgC,CAAA,MAAA,EAAA,MAAA,CAAA;;;;;;;;;;;;;AC/BvD;;;;ACIA;;;;AAES,UCuBQ,aAAA,CDvBR;;KC0BG,UAAA,UAAoB,0CAA0C,cAAc;;;AA3BxF;AAwBA;AAGA;AAAsB,KAOV,YAPU,CAAA,UAAA,MAAA,CAAA,GAOuB,CAPvB,SAAA,MAOuC,aAPvC,GAAA,CAAA,OAAA,EAQR,aARQ,CAQM,CARN,CAAA,CAAA,GAAA,CAAA,OAAA,EASR,WATQ,CAAA;;;;;AAOtB;AAAwB,KASZ,YATY,CAAA,UAAA,MAAA,CAAA,GASqB,CATrB,SAAA,MASqC,aATrC,GAAA,CAAA,OAAA,EAUV,aAVU,CAUI,CAVJ,CAAA,GAAA;MAAqB,CAAA,EAUH,MAVG,CAAA,MAAA,EAAA,CAAA,QAAA,EAUuB,SAVvB,EAAA,GAUqC,SAVrC,CAAA;aAAgB,EAW/C,WAX+C,CAAA;;;;;AAS7D;;;;;;AACoE,KAexD,WAAA,GAfwD;OAAc,EAAA,MAAA;MAAxC,CAAA,EAAA,MAAA;KAC5B,CAAA,EAAA,MAAA;EAAW,GAAA,CAAA,EAAA,MAAA;EAcb,GAAA,CAAA,EAAA,MAAA;EAiBA,IAAA,CAAA,EAAA,MAAA;AAsBZ,CAAA;;;;;;;;;AAiC4D,KAvDhD,WAAA,GAuDgD;OAAoB,EAAA,MAAA;MAYtC,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;;AC/G1C;;;;;;;;ACtCA;;;;;AAyBA;;AAAgC,KF+EpB,KAAA,GE/EoB;;WAAuB,MAAA,EAAA,MAAA;;WAA8B,SAAA,EAAA,KAAA,GAAA,KAAA;EAAA;EA2BrE,SAAA,MAAQ,EAAA,MAAI,GAAA,SAAK;;;;;;;;;;;;;;4BFwEL,oBAAoB,YAAY,aAAa;;;;;;;;;;;;;4BAa7C,oBAAoB,YAAY,aAAa,OAAO;;;;;;;;;;;;0CAYtC;;;;;;;;;;;;;0CAWA;;;IACtC;;;;;;;;;;AHpKJ;;;;;;;;;AAyCyB,iBIAT,WAAA,CJAS,MAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EIA6B,QJA7B,CAAA,EIA6C,KJA7C;;;;KKtCb,kBAAA;;ENQKA,MAAAA,EAAAA,MAAU;;aMJd;YACD;ALRZ,CAAA;;;;;;;;;;;;;;;;;;iBK4BgB,aAAA;;;;GAAmD,qBAAkB,kBAAA,CAAA,GAAA,CAAA;;;AJxBrF;;;;ACIA;;;;;;;iBG+CgB,QAAA,CAAA,GAAY"}
package/dist/index.d.ts CHANGED
@@ -134,20 +134,20 @@ type TextOptions = {
134
134
  context: string;
135
135
  };
136
136
  //#endregion
137
- //#region src/jsx.d.ts
137
+ //#region src/rich.d.ts
138
138
  /**
139
- * Options for `l.jsx()` rich text translation calls.
139
+ * Options for `l.rich()` rich text translation calls.
140
140
  *
141
141
  * @example
142
142
  * ```tsx
143
- * l.jsx("Click <link>here</link> for {topic}", {
143
+ * l.rich("Click <link>here</link> for {topic}", {
144
144
  * tags: { link: (children) => <a href="/help">{children}</a> },
145
145
  * values: { topic: "details" },
146
146
  * context: "Footer",
147
147
  * })
148
148
  * ```
149
149
  */
150
- type JsxOptions = {
150
+ type RichOptions = {
151
151
  /** Map tag names to React component renderers. `<tag>children</tag>` and `<tag/>` supported. */
152
152
  tags?: Record<string, (children: ReactNode) => ReactNode>;
153
153
  /** Interpolation values for `{placeholder}` substitution in text segments. */
@@ -156,7 +156,7 @@ type JsxOptions = {
156
156
  context: string;
157
157
  };
158
158
  /**
159
- * Resolves a translated string with JSX tag interpolation.
159
+ * Resolves a translated string with rich text tag interpolation.
160
160
  * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.
161
161
  */
162
162
  //#endregion
@@ -177,7 +177,7 @@ type Messages = Record<string, string>;
177
177
  /**
178
178
  * Augment this interface via `declare module "@lingo.dev/react"` in a
179
179
  * generated .d.ts to get autocomplete, value validation, and context enforcement
180
- * on l.text() and l.jsx().
180
+ * on l.text() and l.rich().
181
181
  *
182
182
  * When empty (default): l.text() accepts any string, context required as `string`.
183
183
  * When populated (after `lingo extract`): l.text() only accepts listed source strings,
@@ -204,13 +204,13 @@ type MessageKey = [keyof LingoMessages] extends [never] ? string : Extract<keyof
204
204
  */
205
205
  type TextCallArgs<K extends string> = K extends keyof LingoMessages ? [options: LingoMessages[K]] : [options: TextOptions];
206
206
  /**
207
- * Rest args for l.jsx(): context is always required, tags optional.
207
+ * Rest args for l.rich(): context is always required, tags optional.
208
208
  * Before extraction: accepts any string for context.
209
209
  * After extraction: narrows to exact context union + optional tags.
210
210
  */
211
- type JsxCallArgs<K extends string> = K extends keyof LingoMessages ? [options: LingoMessages[K] & {
211
+ type RichCallArgs<K extends string> = K extends keyof LingoMessages ? [options: LingoMessages[K] & {
212
212
  tags?: Record<string, (children: ReactNode) => ReactNode>;
213
- }] : [options: JsxOptions];
213
+ }] : [options: RichOptions];
214
214
  /**
215
215
  * CLDR plural category forms for `l.plural()`.
216
216
  * `other` is required as the fallback. All other categories are optional
@@ -243,7 +243,7 @@ type SelectForms = {
243
243
  };
244
244
  /**
245
245
  * The localization object returned by `useLingo()` and `createLingo()`.
246
- * Provides text translation, JSX interpolation, plural/select formatting,
246
+ * Provides text translation, rich text interpolation, plural/select formatting,
247
247
  * and locale identity (direction, script, region).
248
248
  *
249
249
  * Context is required on every translation call for better translation quality.
@@ -253,7 +253,7 @@ type SelectForms = {
253
253
  * const l = useLingo();
254
254
  * l.text("Hello", { context: "Hero greeting" });
255
255
  * l.text("Welcome, {name}", { context: "Dashboard", values: { name: "Max" } });
256
- * l.jsx("Click <link>here</link>", {
256
+ * l.rich("Click <link>here</link>", {
257
257
  * context: "Help banner",
258
258
  * tags: { link: (c) => <a href="/help">{c}</a> },
259
259
  * });
@@ -283,18 +283,18 @@ type Lingo = {
283
283
  */
284
284
  readonly text: <K extends MessageKey>(source: K, ...args: TextCallArgs<K>) => string;
285
285
  /**
286
- * Translate rich text with JSX tag interpolation. Returns ReactNode.
286
+ * Translate rich text with tag interpolation. Returns ReactNode.
287
287
  * Context is always required.
288
288
  *
289
289
  * @example
290
290
  * ```tsx
291
- * l.jsx("Click <link>here</link>", {
291
+ * l.rich("Click <link>here</link>", {
292
292
  * context: "Help banner",
293
293
  * tags: { link: (children) => <a href="/help">{children}</a> },
294
294
  * })
295
295
  * ```
296
296
  */
297
- readonly jsx: <K extends MessageKey>(source: K, ...args: JsxCallArgs<K>) => ReactNode;
297
+ readonly rich: <K extends MessageKey>(source: K, ...args: RichCallArgs<K>) => ReactNode;
298
298
  /**
299
299
  * Format a count using locale-aware CLDR plural rules.
300
300
  * `#` in form strings is replaced with the formatted count.
@@ -391,5 +391,5 @@ declare function useLingo(): Lingo;
391
391
  //# sourceMappingURL=provider.d.ts.map
392
392
 
393
393
  //#endregion
394
- export { type FormatMethods, type JsxOptions, type Lingo, type LingoMessages, LingoProvider, type LingoProviderProps, type MessageKey, type Messages, type PluralForms, type SelectForms, type TextOptions, computeKey, createLingo, useLingo };
394
+ export { type FormatMethods, type Lingo, type LingoMessages, LingoProvider, type LingoProviderProps, type MessageKey, type Messages, type PluralForms, type RichOptions, type SelectForms, type TextOptions, computeKey, createLingo, useLingo };
395
395
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":["computeKey","buildIcuPlural","Record","buildIcuSelect","EntryMetadata","LocaleEntry","LocaleFile","getActiveEntries","readLocaleFile"],"sources":["../../spec/dist/index.d.ts","../src/format.ts","../src/text.ts","../src/jsx.ts","../src/types.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["//#region src/hash.d.ts\n/**\n * Deterministic key generation from (source text, context) pairs.\n * Shared between runtime (message lookup) and build tools (extraction, JSONC generation).\n *\n * Uses MurmurHash3 x86_128 on NFC-normalized UTF-8 bytes.\n * Outputs an 8-character base62 string (e.g., \"aB3dEf9x\").\n *\n * Cross-platform deterministic: JS, Rust, Go, Python produce identical keys\n * when given the same (source, context) pair.\n *\n * 1% collision probability at ~2.4 million messages (48-bit effective entropy).\n */\n/**\n * Computes a deterministic short key from source text and optional context.\n *\n * Same source + same context = same key (deterministic).\n * Same source + different context = different key (disambiguation).\n */\ndeclare function computeKey(source: string, context?: string): string;\n//# sourceMappingURL=hash.d.ts.map\n//#endregion\n//#region src/icu.d.ts\n/**\n * Canonical ICU MessageFormat expression builders.\n * Shared between runtime (l.plural/l.select) and build tools (extraction).\n *\n * These must produce identical output everywhere so that computeKey()\n * generates matching hash keys at build time and runtime.\n */\ndeclare function buildIcuPlural(forms: Record<string, string>): string;\ndeclare function buildIcuSelect(forms: Record<string, string>): string;\n//# sourceMappingURL=icu.d.ts.map\n\n//#endregion\n//#region src/jsonc.d.ts\n/**\n * JSONC locale file reader with structured metadata comments.\n * Lives in @lingo.dev/spec so framework adapters can parse locale files\n * without depending on @lingo.dev/cli.\n *\n * File format:\n * ```jsonc\n * {\n * /*\n * * @context Hero heading\n * * @src app/hero.tsx:12\n * *​/\n * \"mK9xqZ\": \"Welcome to Acme\"\n * }\n * ```\n */\ntype EntryMetadata = {\n context?: string;\n src?: string;\n orphan?: boolean;\n};\ntype LocaleEntry = {\n key: string;\n value: string;\n metadata: EntryMetadata;\n};\ntype LocaleFile = {\n entries: LocaleEntry[];\n};\n/** Filters out orphaned entries, returning only active (non-orphaned) ones. */\ndeclare function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[];\n/**\n * Parses a JSONC string into structured locale entries with metadata.\n * Extracts @context, @src, and @orphan from block comments preceding each key.\n * Pure function - no filesystem access. Works in Node.js and edge runtimes.\n */\ndeclare function readLocaleFile(content: string): LocaleFile;\n//# sourceMappingURL=jsonc.d.ts.map\n//#endregion\nexport { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };\n//# sourceMappingURL=index.d.ts.map"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":["computeKey","buildIcuPlural","Record","buildIcuSelect","EntryMetadata","LocaleEntry","LocaleFile","getActiveEntries","readLocaleFile"],"sources":["../../spec/dist/index.d.ts","../src/format.ts","../src/text.ts","../src/rich.ts","../src/types.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["//#region src/hash.d.ts\n/**\n * Deterministic key generation from (source text, context) pairs.\n * Shared between runtime (message lookup) and build tools (extraction, JSONC generation).\n *\n * Uses MurmurHash3 x86_128 on NFC-normalized UTF-8 bytes.\n * Outputs an 8-character base62 string (e.g., \"aB3dEf9x\").\n *\n * Cross-platform deterministic: JS, Rust, Go, Python produce identical keys\n * when given the same (source, context) pair.\n *\n * 1% collision probability at ~2.4 million messages (48-bit effective entropy).\n */\n/**\n * Computes a deterministic short key from source text and optional context.\n *\n * Same source + same context = same key (deterministic).\n * Same source + different context = different key (disambiguation).\n */\ndeclare function computeKey(source: string, context?: string): string;\n//# sourceMappingURL=hash.d.ts.map\n//#endregion\n//#region src/icu.d.ts\n/**\n * Canonical ICU MessageFormat expression builders.\n * Shared between runtime (l.plural/l.select) and build tools (extraction).\n *\n * These must produce identical output everywhere so that computeKey()\n * generates matching hash keys at build time and runtime.\n */\ndeclare function buildIcuPlural(forms: Record<string, string>): string;\ndeclare function buildIcuSelect(forms: Record<string, string>): string;\n//# sourceMappingURL=icu.d.ts.map\n\n//#endregion\n//#region src/jsonc.d.ts\n/**\n * JSONC locale file reader with structured metadata comments.\n * Lives in @lingo.dev/spec so framework adapters can parse locale files\n * without depending on @lingo.dev/cli.\n *\n * File format:\n * ```jsonc\n * {\n * /*\n * * @context Hero heading\n * * @src app/hero.tsx:12\n * *​/\n * \"mK9xqZ\": \"Welcome to Acme\"\n * }\n * ```\n */\ntype EntryMetadata = {\n context?: string;\n src?: string;\n orphan?: boolean;\n};\ntype LocaleEntry = {\n key: string;\n value: string;\n metadata: EntryMetadata;\n};\ntype LocaleFile = {\n entries: LocaleEntry[];\n};\n/** Filters out orphaned entries, returning only active (non-orphaned) ones. */\ndeclare function getActiveEntries(entries: LocaleEntry[]): LocaleEntry[];\n/**\n * Parses a JSONC string into structured locale entries with metadata.\n * Extracts @context, @src, and @orphan from block comments preceding each key.\n * Pure function - no filesystem access. Works in Node.js and edge runtimes.\n */\ndeclare function readLocaleFile(content: string): LocaleFile;\n//# sourceMappingURL=jsonc.d.ts.map\n//#endregion\nexport { type EntryMetadata, type LocaleEntry, type LocaleFile, buildIcuPlural, buildIcuSelect, computeKey, getActiveEntries, readLocaleFile };\n//# sourceMappingURL=index.d.ts.map"],"mappings":";;;;;;;;;;;;;ACQA;;;;;;;;;;;iBDWiBA,UAAAA,CCoCY,MAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;;;;;AC3C7B;;;;;;;;KDJY,aAAA;;;AAAZ;;WAK+C,GAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAAL,IAAA,CAAK,mBAAA,EAAA,GAAA,MAAA;;;;;WA8BtB,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,QAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAxBwC,IAAA,CAAK,mBAwB7C,EAAA,GAAA,MAAA;;;;;WAYkC,OAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EA9Bb,IAAA,CAAK,mBA8BQ,EAAA,GAAA,MAAA;;;;;EA6BM,SAAA,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EArDR,IAAA,CAAK,mBAqDG,EAAA,GAAA,MAAA;;;;ACxEjE;8CDyB8C,IAAA,CAAK;;;AErBnD;;WAEmC,IAAA,EAAA,CAAA,KAAA,EFyBV,IEzBU,GAAA,MAAA,EAAA,OAAA,CAAA,EFyBe,IAAA,CAAK,qBEzBpB,EAAA,GAAA,MAAA;;;;;yBF+BV,yBAAyB,IAAA,CAAK;;;AGhCvD;AAwBA;EAGY,SAAA,QAAU,EAAA,CAAA,KAAA,EHWO,IGXP,GAAA,MAAA,EAAA,OAAA,CAAA,EHWgC,IAAA,CAAK,qBGXrC,EAAA,GAAA,MAAA;EAAA;;;;EAA2D,SAAA,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,IAAA,EHmBvE,IAAA,CAAK,sBGnBkE,EAAA,OAAA,CAAA,EHoBnE,IAAA,CAAK,yBGpB8D,EAAA,GAAA,MAAA;EAOrE;;;;WACE,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,CAAA,EHmB+B,IAAA,CAAK,iBGnBpC,EAAA,GAAA,MAAA;;;;AAQd;EAAwB,SAAA,WAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,UAAA,GAAA,QAAA,GAAA,QAAA,GAAA,UAAA,EAAA,GAAA,MAAA,GAAA,SAAA;;;;;;WAC0D,IAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,EAAA,OAAA,CAAA,EHuBrC,IAAA,CAAK,eGvBgC,EAAA,GAAA,MAAA,EAAA;;;;AAelF;AAiBA;EAsBY,SAAK,OAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,WAAA,CAAA,EAAA,UAAA,GAAA,MAAA,GAAA,UAAA,EAAA,GAAA,MAAA,EAAA;EAAA;;;;WAoB2C,QAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,MAAA;;;;;;;;;;;;;;AH/H5D;AAAyB,KCIb,WAAA,GDJa;;QAWwC,CAAA,ECLtD,MDK2D,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,CAAA;;SAYb,EAAK,MAAA;;;;;;;;;;AAvB9D;;;;;;AA6B8C,KErBlC,WAAA,GFqBuC;;MAMD,CAAA,EEzBzC,MFyB8C,CAAA,MAAA,EAAA,CAAA,QAAA,EEzBpB,SFyBoB,EAAA,GEzBN,SFyBM,CAAA;;QAML,CAAA,EE7BvC,MF6B4C,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,CAAA;;SAMD,EAAK,MAAA;;;;;;;;;;;;AA/C3D;;;;;;;;AAmCkD,KG1BtC,QAAA,GAAW,MH0BgC,CAAA,MAAA,EAAA,MAAA,CAAA;;;;;;;;;;;;;AC/BvD;;;;ACIA;;;;AAES,UCuBQ,aAAA,CDvBR;;KC0BG,UAAA,UAAoB,0CAA0C,cAAc;;;AA3BxF;AAwBA;AAGA;AAAsB,KAOV,YAPU,CAAA,UAAA,MAAA,CAAA,GAOuB,CAPvB,SAAA,MAOuC,aAPvC,GAAA,CAAA,OAAA,EAQR,aARQ,CAQM,CARN,CAAA,CAAA,GAAA,CAAA,OAAA,EASR,WATQ,CAAA;;;;;AAOtB;AAAwB,KASZ,YATY,CAAA,UAAA,MAAA,CAAA,GASqB,CATrB,SAAA,MASqC,aATrC,GAAA,CAAA,OAAA,EAUV,aAVU,CAUI,CAVJ,CAAA,GAAA;MAAqB,CAAA,EAUH,MAVG,CAAA,MAAA,EAAA,CAAA,QAAA,EAUuB,SAVvB,EAAA,GAUqC,SAVrC,CAAA;aAAgB,EAW/C,WAX+C,CAAA;;;;;AAS7D;;;;;;AACoE,KAexD,WAAA,GAfwD;OAAc,EAAA,MAAA;MAAxC,CAAA,EAAA,MAAA;KAC5B,CAAA,EAAA,MAAA;EAAW,GAAA,CAAA,EAAA,MAAA;EAcb,GAAA,CAAA,EAAA,MAAA;EAiBA,IAAA,CAAA,EAAA,MAAA;AAsBZ,CAAA;;;;;;;;;AAiC4D,KAvDhD,WAAA,GAuDgD;OAAoB,EAAA,MAAA;MAYtC,EAAA,MAAA,CAAA,EAAA,MAAA;;;;;;;AC/G1C;;;;;;;;ACtCA;;;;;AAyBA;;AAAgC,KF+EpB,KAAA,GE/EoB;;WAAuB,MAAA,EAAA,MAAA;;WAA8B,SAAA,EAAA,KAAA,GAAA,KAAA;EAAA;EA2BrE,SAAA,MAAQ,EAAA,MAAI,GAAA,SAAK;;;;;;;;;;;;;;4BFwEL,oBAAoB,YAAY,aAAa;;;;;;;;;;;;;4BAa7C,oBAAoB,YAAY,aAAa,OAAO;;;;;;;;;;;;0CAYtC;;;;;;;;;;;;;0CAWA;;;IACtC;;;;;;;;;;AHpKJ;;;;;;;;;AAyCyB,iBIAT,WAAA,CJAS,MAAA,EAAA,MAAA,EAAA,QAAA,CAAA,EIA6B,QJA7B,CAAA,EIA6C,KJA7C;;;;KKtCb,kBAAA;;ENQKA,MAAAA,EAAAA,MAAU;;aMJd;YACD;ALRZ,CAAA;;;;;;;;;;;;;;;;;;iBK4BgB,aAAA;;;;GAAmD,qBAAkB,kBAAA,CAAA,GAAA,CAAA;;;AJxBrF;;;;ACIA;;;;;;;iBG+CgB,QAAA,CAAA,GAAY"}
package/dist/index.js CHANGED
@@ -136,7 +136,7 @@ function resolveText(source, messages, locale, options) {
136
136
  }
137
137
  }
138
138
  //#endregion
139
- //#region src/jsx.ts
139
+ //#region src/rich.ts
140
140
  const TAG_RE = /<(\w+)>([\s\S]*?)<\/\1>|<(\w+)\s*\/>/g;
141
141
  function renderTemplate(template, tags, values) {
142
142
  const parts = [];
@@ -159,10 +159,10 @@ function renderTemplate(template, tags, values) {
159
159
  return createElement(Fragment, null, ...parts);
160
160
  }
161
161
  /**
162
- * Resolves a translated string with JSX tag interpolation.
162
+ * Resolves a translated string with rich text tag interpolation.
163
163
  * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.
164
164
  */
165
- function resolveJsx(source, messages, locale, options) {
165
+ function resolveRich(source, messages, locale, options) {
166
166
  return renderTemplate(lookupTemplate(source, messages, locale, options?.context), options?.tags ?? {}, options?.values ?? {});
167
167
  }
168
168
  //#endregion
@@ -230,7 +230,7 @@ function createLingo(locale, messages = {}) {
230
230
  script,
231
231
  region,
232
232
  text: (source, options) => resolveText(source, messages, locale, options),
233
- jsx: (source, options) => resolveJsx(source, messages, locale, options),
233
+ rich: (source, options) => resolveRich(source, messages, locale, options),
234
234
  plural: (count, forms, options) => {
235
235
  return resolveText(buildIcuPlural(forms), messages, locale, {
236
236
  context: options?.context ?? "",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/format.ts","../src/text.ts","../src/jsx.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["/**\n * Locale-aware formatting methods wrapping native Intl APIs.\n * Near-zero bundle cost - each method delegates to built-in browser/Node Intl formatters.\n * All methods are pure functions that take a locale string and return formatted strings.\n */\n\nconst FILE_SIZE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"] as const;\n\nexport type FormatMethods = {\n /**\n * Format a number with locale-aware grouping and decimals.\n * @example l.num(1234567) // \"1,234,567\" (en) / \"1.234.567\" (de)\n */\n readonly num: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a currency value with locale-aware symbol placement and decimals.\n * @example l.currency(29.99, \"USD\") // \"$29.99\" (en) / \"29,99 $US\" (fr)\n */\n readonly currency: (value: number, currency: string, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a decimal as a percentage.\n * @example l.percent(0.156) // \"16%\" (en) / \"16 %\" (fr)\n */\n readonly percent: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a value with a measurement unit.\n * @example l.unit(32, \"celsius\") // \"32°C\" (en) / \"32 °C\" (de)\n */\n readonly unit: (value: number, unit: string, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a number in compact notation.\n * @example l.compact(1234567) // \"1.2M\" (en) / \"123万\" (ja)\n */\n readonly compact: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a date.\n * @example l.date(new Date()) // \"3/16/2026\" (en) / \"16.03.2026\" (de)\n */\n readonly date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a time.\n * @example l.time(new Date()) // \"3:45 PM\" (en) / \"15:45\" (de)\n */\n readonly time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a date and time together.\n * @example l.datetime(new Date()) // \"3/16/2026, 3:45 PM\" (en)\n */\n readonly datetime: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a relative time span.\n * @example l.relative(-3, \"day\") // \"3 days ago\" (en) / \"vor 3 Tagen\" (de)\n */\n readonly relative: (\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options?: Intl.RelativeTimeFormatOptions,\n ) => string;\n\n /**\n * Format a list of items with locale-aware conjunction.\n * @example l.list([\"A\", \"B\", \"C\"]) // \"A, B, and C\" (en) / \"A, B y C\" (es)\n */\n readonly list: (items: string[], options?: Intl.ListFormatOptions) => string;\n\n /**\n * Get the localized display name for a language, region, script, or currency code.\n * @example l.displayName(\"en\", \"language\") // \"English\" (en) / \"Englisch\" (de)\n */\n readonly displayName: (code: string, type: \"language\" | \"region\" | \"script\" | \"currency\") => string | undefined;\n\n /**\n * Sort an array of strings using locale-aware collation rules.\n * Returns a new sorted array. Does not mutate the input.\n * @example l.sort([\"ä\", \"z\", \"a\"]) // [\"a\", \"ä\", \"z\"] (de) vs [\"a\", \"z\", \"ä\"] (sv)\n */\n readonly sort: (items: string[], options?: Intl.CollatorOptions) => string[];\n\n /**\n * Segment text into graphemes, words, or sentences using locale-aware rules.\n * Essential for CJK text where spaces don't separate words.\n * @example l.segment(\"Hello world\", \"word\") // [\"Hello\", \" \", \"world\"]\n */\n readonly segment: (text: string, granularity?: \"grapheme\" | \"word\" | \"sentence\") => string[];\n\n /**\n * Format a byte count as a human-readable file size with locale-aware number formatting.\n * @example l.fileSize(1073741824) // \"1 GB\" (en) / \"1 Go\" (fr)\n */\n readonly fileSize: (bytes: number) => string;\n};\n\n/**\n * Creates all formatting methods bound to a specific locale.\n */\nexport function createFormatMethods(locale: string): FormatMethods {\n return {\n num: (value, options?) => new Intl.NumberFormat(locale, options).format(value),\n\n currency: (value, currency, options?) =>\n new Intl.NumberFormat(locale, { ...options, style: \"currency\", currency }).format(value),\n\n percent: (value, options?) => new Intl.NumberFormat(locale, { ...options, style: \"percent\" }).format(value),\n\n unit: (value, unit, options?) => new Intl.NumberFormat(locale, { ...options, style: \"unit\", unit }).format(value),\n\n compact: (value, options?) => new Intl.NumberFormat(locale, { ...options, notation: \"compact\" }).format(value),\n\n date: (value, options?) => new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", ...options }).format(value),\n\n time: (value, options?) => new Intl.DateTimeFormat(locale, { timeStyle: \"short\", ...options }).format(value),\n\n datetime: (value, options?) =>\n new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeStyle: \"short\", ...options }).format(value),\n\n relative: (value, unit, options?) => new Intl.RelativeTimeFormat(locale, options).format(value, unit),\n\n list: (items, options?) => new Intl.ListFormat(locale, { type: \"conjunction\", ...options }).format(items),\n\n displayName: (code, type) => {\n try {\n return new Intl.DisplayNames(locale, { type }).of(code);\n } catch {\n return undefined;\n }\n },\n\n sort: (items, options?) => {\n const collator = new Intl.Collator(locale, options);\n return [...items].sort(collator.compare);\n },\n\n segment: (text, granularity = \"grapheme\") =>\n [...new Intl.Segmenter(locale, { granularity }).segment(text)].map((s) => s.segment),\n\n fileSize: (bytes) => {\n if (bytes === 0) return `0 ${FILE_SIZE_UNITS[0]}`;\n const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);\n const value = bytes / 1024 ** exp;\n const formatted = new Intl.NumberFormat(locale, { maximumFractionDigits: exp === 0 ? 0 : 1 }).format(value);\n return `${formatted} ${FILE_SIZE_UNITS[exp]}`;\n },\n };\n}\n","import IntlMessageFormat from \"intl-messageformat\";\nimport { computeKey } from \"./hash\";\nimport type { Messages } from \"./types\";\n\n/**\n * Options for `l.text()` translation calls.\n *\n * @example\n * ```ts\n * l.text(\"Welcome, {name}!\", { values: { name: \"Max\" }, context: \"Dashboard\" })\n * ```\n */\nexport type TextOptions = {\n /** Interpolation values for `{placeholder}` substitution. */\n values?: Record<string, string | number>;\n /** Required context describing where/how this text is used. Improves translation quality and enables disambiguation. */\n context: string;\n};\n\nconst ICU_COMPLEX = /\\{[^}]+,\\s*(?:plural|select|selectordinal|number|date|time)\\s*[,}]/;\n\nconst formatCache = new Map<string, IntlMessageFormat>();\nconst warnedKeys = new Set<string>();\n\nfunction warnOnce(id: string, message: string, level: \"warn\" | \"error\" = \"warn\"): void {\n if (process.env.NODE_ENV === \"production\") return;\n if (warnedKeys.has(id)) return;\n warnedKeys.add(id);\n console[level](`[lingo] ${message}`);\n}\n\nexport function simpleInterpolate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key) => {\n const val = values[key];\n return val !== undefined ? String(val) : `{${key}}`;\n });\n}\n\nfunction getFormatter(template: string, locale: string): IntlMessageFormat {\n const cacheKey = `${locale}\\0${template}`;\n let formatter = formatCache.get(cacheKey);\n if (!formatter) {\n formatter = new IntlMessageFormat(template, locale);\n formatCache.set(cacheKey, formatter);\n }\n return formatter;\n}\n\n/**\n * Looks up a translation by hash key with dev-mode diagnostics.\n * Falls back to source text when translation is missing or empty.\n */\nexport function lookupTemplate(source: string, messages: Messages, locale: string, context?: string): string {\n const key = computeKey(source, context);\n const raw = messages[key];\n\n if (process.env.NODE_ENV !== \"production\") {\n if (raw === undefined) {\n warnOnce(\n `missing:${key}:${locale}`,\n `Missing translation for \"${source}\"\\n Key: ${key}\\n Locale: ${locale}\\n → Run \\`lingo extract\\` to add this string to your locale files`,\n );\n } else if (raw === \"\") {\n warnOnce(\n `empty:${key}:${locale}`,\n `Empty translation for \"${source}\"\\n Key: ${key}\\n Locale: ${locale}\\n → This key exists in your locale file but has no translation`,\n );\n }\n }\n\n return raw || source;\n}\n\n/**\n * Resolves a translated string from messages by hash, falling back to source text.\n * Uses simple regex for {placeholder} substitution; loads ICU parser only for complex syntax.\n */\nexport function resolveText(source: string, messages: Messages, locale: string, options?: TextOptions): string {\n const template = lookupTemplate(source, messages, locale, options?.context);\n\n if (!options?.values) return template;\n\n // Fast path: simple {placeholder} interpolation\n if (!ICU_COMPLEX.test(template)) {\n return simpleInterpolate(template, options.values);\n }\n\n // Full ICU path (plural, select, number, date, time)\n try {\n const formatter = getFormatter(template, locale);\n return formatter.format(options.values) as string;\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n const key = computeKey(source, options?.context);\n warnOnce(\n `icu:${key}:${locale}`,\n `Failed to format \"${template}\"\\n Key: ${key}\\n Locale: ${locale}\\n Error: ${error instanceof Error ? error.message : String(error)}\\n → Check the ICU MessageFormat syntax in your locale file`,\n \"error\",\n );\n }\n return template;\n }\n}\n","import { createElement, Fragment, type ReactNode } from \"react\";\nimport { lookupTemplate, simpleInterpolate } from \"./text\";\nimport type { Messages } from \"./types\";\n\n/**\n * Options for `l.jsx()` rich text translation calls.\n *\n * @example\n * ```tsx\n * l.jsx(\"Click <link>here</link> for {topic}\", {\n * tags: { link: (children) => <a href=\"/help\">{children}</a> },\n * values: { topic: \"details\" },\n * context: \"Footer\",\n * })\n * ```\n */\nexport type JsxOptions = {\n /** Map tag names to React component renderers. `<tag>children</tag>` and `<tag/>` supported. */\n tags?: Record<string, (children: ReactNode) => ReactNode>;\n /** Interpolation values for `{placeholder}` substitution in text segments. */\n values?: Record<string, string | number>;\n /** Required context describing where/how this text is used. Improves translation quality and enables disambiguation. */\n context: string;\n};\n\nconst TAG_RE = /<(\\w+)>([\\s\\S]*?)<\\/\\1>|<(\\w+)\\s*\\/>/g;\n\nfunction renderTemplate(\n template: string,\n tags: Record<string, (children: ReactNode) => ReactNode>,\n values: Record<string, string | number>,\n): ReactNode {\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n let keyCounter = 0;\n\n for (const match of template.matchAll(TAG_RE)) {\n if (match.index > lastIndex) {\n parts.push(simpleInterpolate(template.slice(lastIndex, match.index), values));\n }\n\n const tagName = match[1] ?? match[3];\n const content = match[2];\n const tagFn = tags[tagName];\n\n if (tagFn) {\n const children = content != null ? renderTemplate(content, tags, values) : null;\n parts.push(createElement(Fragment, { key: keyCounter++ }, tagFn(children)));\n } else {\n // No handler for this tag - render raw text\n parts.push(match[0]);\n }\n\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(simpleInterpolate(template.slice(lastIndex), values));\n }\n\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0];\n return createElement(Fragment, null, ...parts);\n}\n\n/**\n * Resolves a translated string with JSX tag interpolation.\n * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.\n */\nexport function resolveJsx(source: string, messages: Messages, locale: string, options?: JsxOptions): ReactNode {\n const template = lookupTemplate(source, messages, locale, options?.context);\n return renderTemplate(template, options?.tags ?? {}, options?.values ?? {});\n}\n","import { buildIcuPlural, buildIcuSelect } from \"@lingo.dev/spec\";\nimport { createFormatMethods } from \"./format\";\nimport { resolveJsx } from \"./jsx\";\nimport { resolveText, type TextOptions } from \"./text\";\nimport type { Lingo, Messages, PluralForms, SelectForms } from \"./types\";\n\nconst RTL_LANGS = new Set([\"ar\", \"arc\", \"dv\", \"fa\", \"ha\", \"he\", \"khw\", \"ks\", \"ku\", \"ps\", \"ur\", \"yi\"]);\n\nfunction fallbackDirection(locale: string): \"ltr\" | \"rtl\" {\n const lang = locale.split(\"-\")[0].toLowerCase();\n return RTL_LANGS.has(lang) ? \"rtl\" : \"ltr\";\n}\n\nexport function resolveDirection(locale: string): \"ltr\" | \"rtl\" {\n try {\n const loc = new Intl.Locale(locale);\n // Intl.Locale.textInfo available in Node 21+, Chrome 99+, Safari 17.4+\n const direction = (loc as any).textInfo?.direction;\n if (direction === \"rtl\" || direction === \"ltr\") return direction;\n } catch {\n // Invalid locale or Intl.Locale not available\n }\n return fallbackDirection(locale);\n}\n\nfunction resolveLocaleInfo(locale: string): { script: string | undefined; region: string | undefined } {\n try {\n const loc = new Intl.Locale(locale).maximize();\n return { script: loc.script, region: loc.region };\n } catch {\n return { script: undefined, region: undefined };\n }\n}\n\n/**\n * Creates a `Lingo` object for translating text outside of React context.\n * Used by `@lingo.dev/react-next` for Server Components and internally by `LingoProvider`.\n *\n * @param locale - BCP-47 locale string (e.g., \"en\", \"es\", \"ar-SA\")\n * @param messages - Hash-keyed translations from JSONC locale files\n *\n * @example\n * ```ts\n * const l = createLingo(\"es\", messages);\n * l.text(\"Hello\", { context: \"Hero greeting\" });\n * l.direction; // \"ltr\"\n * l.script; // \"Latn\"\n * ```\n */\nexport function createLingo(locale: string, messages: Messages = {}): Lingo {\n const direction = resolveDirection(locale);\n const { script, region } = resolveLocaleInfo(locale);\n\n // Cast needed: implementation uses (source, options?) but Lingo type uses\n // conditional rest params (...args) to enforce required context + values per message key.\n // Runtime behavior is identical - rest params with one element === single param.\n return {\n locale,\n direction,\n script,\n region,\n text: (source: string, options?: TextOptions) => resolveText(source, messages, locale, options),\n jsx: (source: string, options?: any) => resolveJsx(source, messages, locale, options),\n plural: (count: number, forms: PluralForms, options?: { context: string }) => {\n const source = buildIcuPlural(forms);\n return resolveText(source, messages, locale, {\n context: options?.context ?? \"\",\n values: { count },\n });\n },\n select: (value: string, forms: SelectForms, options?: { context: string }) => {\n const source = buildIcuSelect(forms);\n return resolveText(source, messages, locale, {\n context: options?.context ?? \"\",\n values: { value },\n });\n },\n ...createFormatMethods(locale),\n } as Lingo;\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport { createLingo } from \"./lingo\";\nimport type { Lingo, Messages } from \"./types\";\n\ntype LingoState = {\n lingo: Lingo;\n messages: Messages;\n};\n\nconst LingoContext = createContext<LingoState | null>(null);\n\nexport type LingoProviderProps = {\n /** BCP-47 locale string (e.g., \"en\", \"es\", \"ar-SA\") */\n locale: string;\n /** Hash-keyed translations loaded from JSONC/JSON locale files */\n messages?: Messages;\n children: ReactNode;\n};\n\n/**\n * Provides locale and translations to all descendant components via `useLingo()`.\n *\n * Nested providers with the **same locale** merge messages (child overrides parent,\n * missing keys fall through). Nested providers with **different locales** are standalone.\n *\n * @example\n * ```tsx\n * // Root layout: shared messages\n * <LingoProvider locale=\"es\" messages={sharedMessages}>\n * {/* Dashboard: adds route-specific messages *​/}\n * <LingoProvider locale=\"es\" messages={dashboardMessages}>\n * <App />\n * </LingoProvider>\n * </LingoProvider>\n * ```\n */\nexport function LingoProvider({ locale, messages = {}, children }: LingoProviderProps) {\n const parent = useContext(LingoContext);\n const parentMessages = parent?.messages;\n const parentLocale = parent?.lingo.locale;\n\n const state = useMemo<LingoState>(() => {\n // Merge with parent when same locale; standalone when locale differs or no parent\n const merged = parentMessages && parentLocale === locale ? { ...parentMessages, ...messages } : messages;\n return { lingo: createLingo(locale, merged), messages: merged };\n }, [parentMessages, parentLocale, locale, messages]);\n\n return <LingoContext value={state}>{children}</LingoContext>;\n}\n\n/**\n * Access the `Lingo` translation object from the nearest `LingoProvider`.\n *\n * @throws If called outside a `<LingoProvider>`.\n *\n * @example\n * ```tsx\n * function Greeting() {\n * const l = useLingo();\n * return <h1>{l.text(\"Hello\")}</h1>;\n * }\n * ```\n */\nexport function useLingo(): Lingo {\n const ctx = useContext(LingoContext);\n if (ctx === null) {\n throw new Error(\n \"useLingo() called outside <LingoProvider>. \" +\n 'Wrap your component tree with <LingoProvider locale=\"en\"> to fix this.',\n );\n }\n return ctx.lingo;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/format.ts","../src/text.ts","../src/rich.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["/**\n * Locale-aware formatting methods wrapping native Intl APIs.\n * Near-zero bundle cost - each method delegates to built-in browser/Node Intl formatters.\n * All methods are pure functions that take a locale string and return formatted strings.\n */\n\nconst FILE_SIZE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"] as const;\n\nexport type FormatMethods = {\n /**\n * Format a number with locale-aware grouping and decimals.\n * @example l.num(1234567) // \"1,234,567\" (en) / \"1.234.567\" (de)\n */\n readonly num: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a currency value with locale-aware symbol placement and decimals.\n * @example l.currency(29.99, \"USD\") // \"$29.99\" (en) / \"29,99 $US\" (fr)\n */\n readonly currency: (value: number, currency: string, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a decimal as a percentage.\n * @example l.percent(0.156) // \"16%\" (en) / \"16 %\" (fr)\n */\n readonly percent: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a value with a measurement unit.\n * @example l.unit(32, \"celsius\") // \"32°C\" (en) / \"32 °C\" (de)\n */\n readonly unit: (value: number, unit: string, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a number in compact notation.\n * @example l.compact(1234567) // \"1.2M\" (en) / \"123万\" (ja)\n */\n readonly compact: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a date.\n * @example l.date(new Date()) // \"3/16/2026\" (en) / \"16.03.2026\" (de)\n */\n readonly date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a time.\n * @example l.time(new Date()) // \"3:45 PM\" (en) / \"15:45\" (de)\n */\n readonly time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a date and time together.\n * @example l.datetime(new Date()) // \"3/16/2026, 3:45 PM\" (en)\n */\n readonly datetime: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a relative time span.\n * @example l.relative(-3, \"day\") // \"3 days ago\" (en) / \"vor 3 Tagen\" (de)\n */\n readonly relative: (\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options?: Intl.RelativeTimeFormatOptions,\n ) => string;\n\n /**\n * Format a list of items with locale-aware conjunction.\n * @example l.list([\"A\", \"B\", \"C\"]) // \"A, B, and C\" (en) / \"A, B y C\" (es)\n */\n readonly list: (items: string[], options?: Intl.ListFormatOptions) => string;\n\n /**\n * Get the localized display name for a language, region, script, or currency code.\n * @example l.displayName(\"en\", \"language\") // \"English\" (en) / \"Englisch\" (de)\n */\n readonly displayName: (code: string, type: \"language\" | \"region\" | \"script\" | \"currency\") => string | undefined;\n\n /**\n * Sort an array of strings using locale-aware collation rules.\n * Returns a new sorted array. Does not mutate the input.\n * @example l.sort([\"ä\", \"z\", \"a\"]) // [\"a\", \"ä\", \"z\"] (de) vs [\"a\", \"z\", \"ä\"] (sv)\n */\n readonly sort: (items: string[], options?: Intl.CollatorOptions) => string[];\n\n /**\n * Segment text into graphemes, words, or sentences using locale-aware rules.\n * Essential for CJK text where spaces don't separate words.\n * @example l.segment(\"Hello world\", \"word\") // [\"Hello\", \" \", \"world\"]\n */\n readonly segment: (text: string, granularity?: \"grapheme\" | \"word\" | \"sentence\") => string[];\n\n /**\n * Format a byte count as a human-readable file size with locale-aware number formatting.\n * @example l.fileSize(1073741824) // \"1 GB\" (en) / \"1 Go\" (fr)\n */\n readonly fileSize: (bytes: number) => string;\n};\n\n/**\n * Creates all formatting methods bound to a specific locale.\n */\nexport function createFormatMethods(locale: string): FormatMethods {\n return {\n num: (value, options?) => new Intl.NumberFormat(locale, options).format(value),\n\n currency: (value, currency, options?) =>\n new Intl.NumberFormat(locale, { ...options, style: \"currency\", currency }).format(value),\n\n percent: (value, options?) => new Intl.NumberFormat(locale, { ...options, style: \"percent\" }).format(value),\n\n unit: (value, unit, options?) => new Intl.NumberFormat(locale, { ...options, style: \"unit\", unit }).format(value),\n\n compact: (value, options?) => new Intl.NumberFormat(locale, { ...options, notation: \"compact\" }).format(value),\n\n date: (value, options?) => new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", ...options }).format(value),\n\n time: (value, options?) => new Intl.DateTimeFormat(locale, { timeStyle: \"short\", ...options }).format(value),\n\n datetime: (value, options?) =>\n new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeStyle: \"short\", ...options }).format(value),\n\n relative: (value, unit, options?) => new Intl.RelativeTimeFormat(locale, options).format(value, unit),\n\n list: (items, options?) => new Intl.ListFormat(locale, { type: \"conjunction\", ...options }).format(items),\n\n displayName: (code, type) => {\n try {\n return new Intl.DisplayNames(locale, { type }).of(code);\n } catch {\n return undefined;\n }\n },\n\n sort: (items, options?) => {\n const collator = new Intl.Collator(locale, options);\n return [...items].sort(collator.compare);\n },\n\n segment: (text, granularity = \"grapheme\") =>\n [...new Intl.Segmenter(locale, { granularity }).segment(text)].map((s) => s.segment),\n\n fileSize: (bytes) => {\n if (bytes === 0) return `0 ${FILE_SIZE_UNITS[0]}`;\n const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);\n const value = bytes / 1024 ** exp;\n const formatted = new Intl.NumberFormat(locale, { maximumFractionDigits: exp === 0 ? 0 : 1 }).format(value);\n return `${formatted} ${FILE_SIZE_UNITS[exp]}`;\n },\n };\n}\n","import IntlMessageFormat from \"intl-messageformat\";\nimport { computeKey } from \"./hash\";\nimport type { Messages } from \"./types\";\n\n/**\n * Options for `l.text()` translation calls.\n *\n * @example\n * ```ts\n * l.text(\"Welcome, {name}!\", { values: { name: \"Max\" }, context: \"Dashboard\" })\n * ```\n */\nexport type TextOptions = {\n /** Interpolation values for `{placeholder}` substitution. */\n values?: Record<string, string | number>;\n /** Required context describing where/how this text is used. Improves translation quality and enables disambiguation. */\n context: string;\n};\n\nconst ICU_COMPLEX = /\\{[^}]+,\\s*(?:plural|select|selectordinal|number|date|time)\\s*[,}]/;\n\nconst formatCache = new Map<string, IntlMessageFormat>();\nconst warnedKeys = new Set<string>();\n\nfunction warnOnce(id: string, message: string, level: \"warn\" | \"error\" = \"warn\"): void {\n if (process.env.NODE_ENV === \"production\") return;\n if (warnedKeys.has(id)) return;\n warnedKeys.add(id);\n console[level](`[lingo] ${message}`);\n}\n\nexport function simpleInterpolate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key) => {\n const val = values[key];\n return val !== undefined ? String(val) : `{${key}}`;\n });\n}\n\nfunction getFormatter(template: string, locale: string): IntlMessageFormat {\n const cacheKey = `${locale}\\0${template}`;\n let formatter = formatCache.get(cacheKey);\n if (!formatter) {\n formatter = new IntlMessageFormat(template, locale);\n formatCache.set(cacheKey, formatter);\n }\n return formatter;\n}\n\n/**\n * Looks up a translation by hash key with dev-mode diagnostics.\n * Falls back to source text when translation is missing or empty.\n */\nexport function lookupTemplate(source: string, messages: Messages, locale: string, context?: string): string {\n const key = computeKey(source, context);\n const raw = messages[key];\n\n if (process.env.NODE_ENV !== \"production\") {\n if (raw === undefined) {\n warnOnce(\n `missing:${key}:${locale}`,\n `Missing translation for \"${source}\"\\n Key: ${key}\\n Locale: ${locale}\\n → Run \\`lingo extract\\` to add this string to your locale files`,\n );\n } else if (raw === \"\") {\n warnOnce(\n `empty:${key}:${locale}`,\n `Empty translation for \"${source}\"\\n Key: ${key}\\n Locale: ${locale}\\n → This key exists in your locale file but has no translation`,\n );\n }\n }\n\n return raw || source;\n}\n\n/**\n * Resolves a translated string from messages by hash, falling back to source text.\n * Uses simple regex for {placeholder} substitution; loads ICU parser only for complex syntax.\n */\nexport function resolveText(source: string, messages: Messages, locale: string, options?: TextOptions): string {\n const template = lookupTemplate(source, messages, locale, options?.context);\n\n if (!options?.values) return template;\n\n // Fast path: simple {placeholder} interpolation\n if (!ICU_COMPLEX.test(template)) {\n return simpleInterpolate(template, options.values);\n }\n\n // Full ICU path (plural, select, number, date, time)\n try {\n const formatter = getFormatter(template, locale);\n return formatter.format(options.values) as string;\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n const key = computeKey(source, options?.context);\n warnOnce(\n `icu:${key}:${locale}`,\n `Failed to format \"${template}\"\\n Key: ${key}\\n Locale: ${locale}\\n Error: ${error instanceof Error ? error.message : String(error)}\\n → Check the ICU MessageFormat syntax in your locale file`,\n \"error\",\n );\n }\n return template;\n }\n}\n","import { createElement, Fragment, type ReactNode } from \"react\";\nimport { lookupTemplate, simpleInterpolate } from \"./text\";\nimport type { Messages } from \"./types\";\n\n/**\n * Options for `l.rich()` rich text translation calls.\n *\n * @example\n * ```tsx\n * l.rich(\"Click <link>here</link> for {topic}\", {\n * tags: { link: (children) => <a href=\"/help\">{children}</a> },\n * values: { topic: \"details\" },\n * context: \"Footer\",\n * })\n * ```\n */\nexport type RichOptions = {\n /** Map tag names to React component renderers. `<tag>children</tag>` and `<tag/>` supported. */\n tags?: Record<string, (children: ReactNode) => ReactNode>;\n /** Interpolation values for `{placeholder}` substitution in text segments. */\n values?: Record<string, string | number>;\n /** Required context describing where/how this text is used. Improves translation quality and enables disambiguation. */\n context: string;\n};\n\nconst TAG_RE = /<(\\w+)>([\\s\\S]*?)<\\/\\1>|<(\\w+)\\s*\\/>/g;\n\nfunction renderTemplate(\n template: string,\n tags: Record<string, (children: ReactNode) => ReactNode>,\n values: Record<string, string | number>,\n): ReactNode {\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n let keyCounter = 0;\n\n for (const match of template.matchAll(TAG_RE)) {\n if (match.index > lastIndex) {\n parts.push(simpleInterpolate(template.slice(lastIndex, match.index), values));\n }\n\n const tagName = match[1] ?? match[3];\n const content = match[2];\n const tagFn = tags[tagName];\n\n if (tagFn) {\n const children = content != null ? renderTemplate(content, tags, values) : null;\n parts.push(createElement(Fragment, { key: keyCounter++ }, tagFn(children)));\n } else {\n // No handler for this tag - render raw text\n parts.push(match[0]);\n }\n\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(simpleInterpolate(template.slice(lastIndex), values));\n }\n\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0];\n return createElement(Fragment, null, ...parts);\n}\n\n/**\n * Resolves a translated string with rich text tag interpolation.\n * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.\n */\nexport function resolveRich(source: string, messages: Messages, locale: string, options?: RichOptions): ReactNode {\n const template = lookupTemplate(source, messages, locale, options?.context);\n return renderTemplate(template, options?.tags ?? {}, options?.values ?? {});\n}\n","import { buildIcuPlural, buildIcuSelect } from \"@lingo.dev/spec\";\nimport { createFormatMethods } from \"./format\";\nimport { resolveRich } from \"./rich\";\nimport { resolveText, type TextOptions } from \"./text\";\nimport type { Lingo, Messages, PluralForms, SelectForms } from \"./types\";\n\nconst RTL_LANGS = new Set([\"ar\", \"arc\", \"dv\", \"fa\", \"ha\", \"he\", \"khw\", \"ks\", \"ku\", \"ps\", \"ur\", \"yi\"]);\n\nfunction fallbackDirection(locale: string): \"ltr\" | \"rtl\" {\n const lang = locale.split(\"-\")[0].toLowerCase();\n return RTL_LANGS.has(lang) ? \"rtl\" : \"ltr\";\n}\n\nexport function resolveDirection(locale: string): \"ltr\" | \"rtl\" {\n try {\n const loc = new Intl.Locale(locale);\n // Intl.Locale.textInfo available in Node 21+, Chrome 99+, Safari 17.4+\n const direction = (loc as any).textInfo?.direction;\n if (direction === \"rtl\" || direction === \"ltr\") return direction;\n } catch {\n // Invalid locale or Intl.Locale not available\n }\n return fallbackDirection(locale);\n}\n\nfunction resolveLocaleInfo(locale: string): { script: string | undefined; region: string | undefined } {\n try {\n const loc = new Intl.Locale(locale).maximize();\n return { script: loc.script, region: loc.region };\n } catch {\n return { script: undefined, region: undefined };\n }\n}\n\n/**\n * Creates a `Lingo` object for translating text outside of React context.\n * Used by `@lingo.dev/react-next` for Server Components and internally by `LingoProvider`.\n *\n * @param locale - BCP-47 locale string (e.g., \"en\", \"es\", \"ar-SA\")\n * @param messages - Hash-keyed translations from JSONC locale files\n *\n * @example\n * ```ts\n * const l = createLingo(\"es\", messages);\n * l.text(\"Hello\", { context: \"Hero greeting\" });\n * l.direction; // \"ltr\"\n * l.script; // \"Latn\"\n * ```\n */\nexport function createLingo(locale: string, messages: Messages = {}): Lingo {\n const direction = resolveDirection(locale);\n const { script, region } = resolveLocaleInfo(locale);\n\n // Cast needed: implementation uses (source, options?) but Lingo type uses\n // conditional rest params (...args) to enforce required context + values per message key.\n // Runtime behavior is identical - rest params with one element === single param.\n return {\n locale,\n direction,\n script,\n region,\n text: (source: string, options?: TextOptions) => resolveText(source, messages, locale, options),\n rich: (source: string, options?: any) => resolveRich(source, messages, locale, options),\n plural: (count: number, forms: PluralForms, options?: { context: string }) => {\n const source = buildIcuPlural(forms);\n return resolveText(source, messages, locale, {\n context: options?.context ?? \"\",\n values: { count },\n });\n },\n select: (value: string, forms: SelectForms, options?: { context: string }) => {\n const source = buildIcuSelect(forms);\n return resolveText(source, messages, locale, {\n context: options?.context ?? \"\",\n values: { value },\n });\n },\n ...createFormatMethods(locale),\n } as Lingo;\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport { createLingo } from \"./lingo\";\nimport type { Lingo, Messages } from \"./types\";\n\ntype LingoState = {\n lingo: Lingo;\n messages: Messages;\n};\n\nconst LingoContext = createContext<LingoState | null>(null);\n\nexport type LingoProviderProps = {\n /** BCP-47 locale string (e.g., \"en\", \"es\", \"ar-SA\") */\n locale: string;\n /** Hash-keyed translations loaded from JSONC/JSON locale files */\n messages?: Messages;\n children: ReactNode;\n};\n\n/**\n * Provides locale and translations to all descendant components via `useLingo()`.\n *\n * Nested providers with the **same locale** merge messages (child overrides parent,\n * missing keys fall through). Nested providers with **different locales** are standalone.\n *\n * @example\n * ```tsx\n * // Root layout: shared messages\n * <LingoProvider locale=\"es\" messages={sharedMessages}>\n * {/* Dashboard: adds route-specific messages *​/}\n * <LingoProvider locale=\"es\" messages={dashboardMessages}>\n * <App />\n * </LingoProvider>\n * </LingoProvider>\n * ```\n */\nexport function LingoProvider({ locale, messages = {}, children }: LingoProviderProps) {\n const parent = useContext(LingoContext);\n const parentMessages = parent?.messages;\n const parentLocale = parent?.lingo.locale;\n\n const state = useMemo<LingoState>(() => {\n // Merge with parent when same locale; standalone when locale differs or no parent\n const merged = parentMessages && parentLocale === locale ? { ...parentMessages, ...messages } : messages;\n return { lingo: createLingo(locale, merged), messages: merged };\n }, [parentMessages, parentLocale, locale, messages]);\n\n return <LingoContext value={state}>{children}</LingoContext>;\n}\n\n/**\n * Access the `Lingo` translation object from the nearest `LingoProvider`.\n *\n * @throws If called outside a `<LingoProvider>`.\n *\n * @example\n * ```tsx\n * function Greeting() {\n * const l = useLingo();\n * return <h1>{l.text(\"Hello\")}</h1>;\n * }\n * ```\n */\nexport function useLingo(): Lingo {\n const ctx = useContext(LingoContext);\n if (ctx === null) {\n throw new Error(\n \"useLingo() called outside <LingoProvider>. \" +\n 'Wrap your component tree with <LingoProvider locale=\"en\"> to fix this.',\n );\n }\n return ctx.lingo;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingo.dev/react",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "intl-messageformat": "^11.1.3",
40
- "@lingo.dev/spec": "1.0.0"
40
+ "@lingo.dev/spec": "1.0.2"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsdown",