langsys-js-react 0.4.0 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## 0.4.1 - 2026-07-08
2
+
3
+ ### Changed
4
+
5
+ - **Base SDK bumped to `langsys-js-typescript@^0.4.1`** — adds the compile-safe `%key%` markup placeholder spelling.
6
+ - **`<Translate>` / `<Phrase>` docs now use `%count%` placeholders** instead of the `{'{count}'}` JSX escape. A bare `{key}` in JSX is a JavaScript expression React evaluates before the SDK's DOM walker sees the text, silently breaking interpolation; `%key%` passes through as literal text and the SDK normalizes it to canonical `{key}` at capture — so translators and the catalog still only ever see `{key}`, and both spellings share the same content-block id. Keys are identifier-shaped (`%[A-Za-z_][A-Za-z0-9_]*%`), so a stray `%` in prose is untouched; an unknown `%key%` renders as `{key}`. `t()` and the hooks keep `{key}` (JS strings, no collision). The prior brace-collision warning is replaced with `%key%` guidance. No wrapper API change — the normalization lives in the base SDK tokenizer the components already delegate to.
7
+
8
+ ### Added
9
+
10
+ - **`example/` params demo** — a `<Translate params>` card using `%count%` with +/− buttons, showing the block re-render via `setParams` on param change.
11
+
1
12
  ## 0.4.0 - 2026-07-08
2
13
 
3
14
  ### Added
package/README.md CHANGED
@@ -188,14 +188,16 @@ The component:
188
188
  </Translate>
189
189
  ```
190
190
 
191
- `<Translate>` also accepts `params` for `{key}` interpolation — the same single-brace syntax as `t()` — applied to the resolved text of content-block nodes, translatable attributes, `<option>` text, and single-token content (untranslated fallbacks included). Number/Date values get CLDR locale formatting. Change `params` after mount and the block re-renders:
191
+ `<Translate>` also accepts `params` for placeholder interpolation. Write placeholders as **`%key%`** directly in the markup — applied to the resolved text of content-block nodes, translatable attributes, `<option>` text, and single-token content (untranslated fallbacks included). Number/Date values get CLDR locale formatting. Change `params` after mount and the block re-renders:
192
192
 
193
193
  ```tsx
194
194
  <Translate category="Cart" params={{ count: itemCount }}>
195
- You have {'{count}'} items in your cart.
195
+ You have %count% items in your cart.
196
196
  </Translate>
197
197
  ```
198
198
 
199
+ > **Use `%key%` in `<Translate>`/`<Phrase>` markup, not bare `{key}`.** In JSX a literal `{count}` is a JavaScript expression that React evaluates *before* the SDK's DOM walker sees the text — the braces vanish and interpolation silently breaks (it still looks fine in the base locale, which hides it). `%count%` passes through JSX as plain text; the SDK normalizes it to canonical `{count}` at capture, so **translators and the catalog only ever see `{count}`**, and both spellings hash to the same content-block id. Keys are identifier-shaped (`%[A-Za-z_][A-Za-z0-9_]*%`), so a stray `%` in prose ("50% off") is left untouched. An unknown `%key%` with no matching param renders as `{key}` — matching `t()`'s behavior for unknown keys. (`t()` and the hooks keep `{key}`: JS strings reach the SDK literally, so there's no collision.)
200
+
199
201
  `<Translate>` props: `category?`, `custom_id?`, `label?`, `params?`, `tag?` (defaults to `translate`), `className?`, `children`.
200
202
 
201
203
  ### `<Phrase>` — markup-bearing phrases (pluralization)
@@ -206,13 +208,13 @@ Keeps a run that contains inline markup as **one** translatable phrase — so a
206
208
  import { Phrase } from 'langsys-js-react';
207
209
 
208
210
  <Phrase category="ProductCard" params={{ n: reviewCount }}>
209
- Based on {'{n}'} <strong>reviews</strong>
211
+ Based on %n% <strong>reviews</strong>
210
212
  </Phrase>
211
213
  ```
212
214
 
213
215
  The inline elements never reach the translator — they're replaced with neutral markup tokens (`{m0o}`…`{m0c}`) and the real framework-owned elements are reconstituted around the translated text at render. This is also what lets reordering languages move emphasis correctly (`<span>White</span> House` → `Casa <span>Blanca</span>`). Pass interpolation values via `params`; keep the markup children static.
214
216
 
215
- > Note: in JSX a literal `{n}` is an expression, so write it as `{'{n}'}` to emit the placeholder text.
217
+ > Note: write placeholders as `%n%` in `<Phrase>` markup (see the `<Translate>` note above) — a bare `{n}` in JSX is an expression and never reaches the SDK.
216
218
 
217
219
  `<Phrase>` props: `category?`, `params?`, `tag?` (defaults to `span`), `className?`, `children`.
218
220
 
package/dist/index.d.mts CHANGED
@@ -95,8 +95,9 @@ interface TranslateProps {
95
95
  /** Optional human-readable label shown in the Translation Manager. */
96
96
  label?: string;
97
97
  /**
98
- * Interpolation params applied to the resolved text (single-brace `{key}`,
99
- * same syntax as `t()`). Applied to content-block text nodes, translatable
98
+ * Interpolation params. Write placeholders as `%key%` in the markup (a bare
99
+ * `{key}` in JSX is a JS expression; the SDK normalizes `%key%` to canonical
100
+ * `{key}` at capture). Applied to content-block text nodes, translatable
100
101
  * attributes, `<option>` text, and single-token content — including
101
102
  * untranslated fallbacks. Number/Date values get CLDR locale formatting.
102
103
  */
@@ -135,7 +136,7 @@ declare function Translate({ category, custom_id, label, params, tag, className,
135
136
  interface PhraseProps {
136
137
  /** Category the phrase registers under (disambiguation for translators). */
137
138
  category?: string;
138
- /** Interpolation params — `{n}` for pluralization, `{name}`, etc. */
139
+ /** Interpolation params. Write placeholders as `%n%` / `%name%` in the markup (a bare `{n}` in JSX is an expression). */
139
140
  params?: Record<string, unknown>;
140
141
  /** Host element tag. Defaults to `<span>`. */
141
142
  tag?: string;
@@ -150,9 +151,13 @@ interface PhraseProps {
150
151
  * count variable stays next to the noun it pluralizes:
151
152
  *
152
153
  * <Phrase category="ProductCard" params={{ n: reviewCount }}>
153
- * Based on {'{n}'} <strong>reviews</strong>
154
+ * Based on %n% <strong>reviews</strong>
154
155
  * </Phrase>
155
156
  *
157
+ * Write placeholders as `%n%` (not bare `{n}`): in JSX a literal `{n}` is a JS
158
+ * expression React evaluates before the SDK sees the text. `%n%` passes through
159
+ * as literal text and the SDK normalizes it to canonical `{n}` at capture.
160
+ *
156
161
  * The inline markup never reaches the translator — it's replaced with neutral
157
162
  * tokens and the real elements are reconstituted at render (see richtext.ts in
158
163
  * the base SDK). The host carries `data-ls-phrase` so a wrapping `<Translate>`
package/dist/index.d.ts CHANGED
@@ -95,8 +95,9 @@ interface TranslateProps {
95
95
  /** Optional human-readable label shown in the Translation Manager. */
96
96
  label?: string;
97
97
  /**
98
- * Interpolation params applied to the resolved text (single-brace `{key}`,
99
- * same syntax as `t()`). Applied to content-block text nodes, translatable
98
+ * Interpolation params. Write placeholders as `%key%` in the markup (a bare
99
+ * `{key}` in JSX is a JS expression; the SDK normalizes `%key%` to canonical
100
+ * `{key}` at capture). Applied to content-block text nodes, translatable
100
101
  * attributes, `<option>` text, and single-token content — including
101
102
  * untranslated fallbacks. Number/Date values get CLDR locale formatting.
102
103
  */
@@ -135,7 +136,7 @@ declare function Translate({ category, custom_id, label, params, tag, className,
135
136
  interface PhraseProps {
136
137
  /** Category the phrase registers under (disambiguation for translators). */
137
138
  category?: string;
138
- /** Interpolation params — `{n}` for pluralization, `{name}`, etc. */
139
+ /** Interpolation params. Write placeholders as `%n%` / `%name%` in the markup (a bare `{n}` in JSX is an expression). */
139
140
  params?: Record<string, unknown>;
140
141
  /** Host element tag. Defaults to `<span>`. */
141
142
  tag?: string;
@@ -150,9 +151,13 @@ interface PhraseProps {
150
151
  * count variable stays next to the noun it pluralizes:
151
152
  *
152
153
  * <Phrase category="ProductCard" params={{ n: reviewCount }}>
153
- * Based on {'{n}'} <strong>reviews</strong>
154
+ * Based on %n% <strong>reviews</strong>
154
155
  * </Phrase>
155
156
  *
157
+ * Write placeholders as `%n%` (not bare `{n}`): in JSX a literal `{n}` is a JS
158
+ * expression React evaluates before the SDK sees the text. `%n%` passes through
159
+ * as literal text and the SDK normalizes it to canonical `{n}` at capture.
160
+ *
156
161
  * The inline markup never reaches the translator — it's replaced with neutral
157
162
  * tokens and the real elements are reconstituted at render (see richtext.ts in
158
163
  * the base SDK). The host carries `data-ls-phrase` so a wrapping `<Translate>`
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters.ts","../src/hooks.ts","../src/components/Translate.tsx","../src/components/Phrase.tsx","../src/components/DontTranslate.tsx","../src/index.ts"],"names":["useSyncExternalStore","createSignal","tSignal","currentlyLoadedLocale","sTranslations","useState","useRef","useEffect","VanillaTranslate","createElement","VanillaPhrase","_LangsysApp"],"mappings":"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","file":"index.js","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport { createSignal, type Signal } from 'langsys-js-typescript';\n\n/**\n * Subscribe a React component to a base-SDK `Signal<T>` and return its current\n * value, re-rendering whenever the signal changes.\n *\n * This is the React mirror of Svelte's `$store` auto-subscription. Where the\n * Svelte wrapper adapts a native store *into* the SDK (`writable` → `Signal`),\n * the React wrapper adapts the SDK's signals *out* to the render cycle. It is\n * built on `useSyncExternalStore`, so it is concurrent-safe and SSR-safe — the\n * server snapshot reads the same `.get()`, which the base SDK seeds from\n * `initialTranslations` during SSR.\n *\n * The base SDK's signals are stable between changes (every `set` replaces the\n * value, so `.get()` returns a fresh reference only after a real change). That\n * is exactly the identity contract `useSyncExternalStore` requires — no tearing,\n * no render loops.\n *\n * `signal` must be stable across renders: a module-level singleton (the SDK's\n * `t` / `currentlyLoadedLocale` / `sTranslations`) or one created once with\n * `useState(() => createLocaleStore(...))`. Passing a freshly-created signal on\n * every render would resubscribe on every render.\n */\nexport function useSignal<T>(signal: Signal<T>): T {\n return useSyncExternalStore(signal.subscribe, signal.get, signal.get);\n}\n\n/**\n * Create a reactive `Signal<string>` to hold the user's selected locale — the\n * React analog of Svelte's `writable('en-US')`.\n *\n * Pass the result as `UserLocaleStore` to `LangsysApp.init`, read it reactively\n * with `useSignal(store)` (or use the all-in-one `useLocaleStore` hook), and\n * switch locale with `store.set('fr-FR')`. The base SDK only ever reads and\n * subscribes to it — it never writes.\n *\n * Locale identifiers are canonicalized to BCP 47 by the base SDK (v0.3.0+), so\n * `'en-us'` still works on input — but `currentlyLoadedLocale` always emits the\n * canonical form (`'en-US'`), so prefer canonical casing to keep comparisons\n * against it straightforward.\n */\nexport function createLocaleStore(initial = 'en-US'): Signal<string> {\n return createSignal<string>(initial);\n}\n","import { useState } from 'react';\nimport { currentlyLoadedLocale, sTranslations, tSignal } from 'langsys-js-typescript';\nimport type { Signal, TFunction, iCategories } from 'langsys-js-typescript';\nimport { createLocaleStore, useSignal } from './adapters.js';\n\n/**\n * The current translation function, re-rendering the calling component whenever\n * translations or the loaded locale change. This is the React analog of Svelte's\n * `$t` store read.\n *\n * const t = useT();\n * return <h1>{t('Welcome to my app', 'UI')}</h1>;\n *\n * The phrase is both the lookup key and the base-language default. Signature:\n * `t(phrase, category?, params?)`. Placeholder names in the phrase are\n * type-checked against the params object at the call site.\n */\nexport function useT(): TFunction {\n return useSignal(tSignal);\n}\n\n/**\n * The locale whose translations are currently loaded. This lags the\n * user-selected locale (`UserLocaleStore`) until the fetch for the new locale\n * settles, which makes it the right value to gate \"translations are ready\" UI on.\n */\nexport function useCurrentLocale(): string {\n return useSignal(currentlyLoadedLocale);\n}\n\n/**\n * The raw translation catalog. Rarely needed in app code — prefer `useT()`.\n * Exposed for advanced cases (inspecting which categories/phrases are loaded).\n */\nexport function useTranslations(): iCategories {\n return useSignal(sTranslations);\n}\n\n/**\n * All-in-one convenience for the user-locale store. Creates one stable\n * `Signal<string>` (the React analog of Svelte's `writable`), subscribes the\n * component to it, and returns `[locale, setLocale, store]`.\n *\n * const [locale, setLocale, store] = useLocaleStore('en-US');\n * useEffect(() => {\n * LangsysApp.init({ projectid, key, UserLocaleStore: store });\n * }, [store]);\n *\n * <select value={locale} onChange={(e) => setLocale(e.target.value)}>...\n *\n * The store is created lazily once and never re-created across renders, so it is\n * safe to pass to `LangsysApp.init` and to depend on in effects.\n */\nexport function useLocaleStore(initial = 'en-US'): [string, (locale: string) => void, Signal<string>] {\n const [store] = useState(() => createLocaleStore(initial));\n const locale = useSignal(store);\n return [locale, store.set, store];\n}\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Translate as VanillaTranslate, type ParamPrimitive } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Translate` component. Mirrors the Svelte component's\n * props 1:1, with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface TranslateProps {\n /** Optional category under which tokens are registered. Helps translators disambiguate. */\n category?: string;\n /** Optional stable id for the content block. If omitted, the SDK hashes category + tokens. */\n custom_id?: string;\n /** Optional human-readable label shown in the Translation Manager. */\n label?: string;\n /**\n * Interpolation params applied to the resolved text (single-brace `{key}`,\n * same syntax as `t()`). Applied to content-block text nodes, translatable\n * attributes, `<option>` text, and single-token content — including\n * untranslated fallbacks. Number/Date values get CLDR locale formatting.\n */\n params?: Record<string, ParamPrimitive>;\n /** Host element tag. Defaults to a `<translate>` custom element. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Translate` DOM class from\n * `langsys-js-typescript`. It renders a host element, then on mount lets the\n * vanilla class walk and tokenize the rendered children (text nodes plus\n * translatable attributes), register the content block, and re-translate on\n * locale change. On unmount it tears the instance down.\n *\n * This is the React analog of the Svelte `<Translate>` component — pure\n * mount/destroy glue. The DOM walking, content-block registration, attribute\n * harvesting, and re-translation lifecycle all live in the base SDK.\n *\n * Like the Svelte component, this lets the SDK mutate the rendered DOM in place,\n * so keep the children static: prose, marketing copy, CMS-rendered HTML — the\n * content-block use case. For dynamic per-string values that React owns and\n * re-renders, use `useT()` instead.\n */\nexport function Translate({\n category = '',\n custom_id = '',\n label = '',\n params,\n tag = 'translate',\n className,\n children,\n}: TranslateProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaTranslate>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaTranslate(host, { category, custom_id, label, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the identity props change; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category, custom_id, label]);\n\n // Re-render on changed params after mount, mirroring <Phrase>.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className }, children);\n}\n\nexport default Translate;\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Phrase as VanillaPhrase } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Phrase` component. Mirrors the Svelte component's props,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface PhraseProps {\n /** Category the phrase registers under (disambiguation for translators). */\n category?: string;\n /** Interpolation params — `{n}` for pluralization, `{name}`, etc. */\n params?: Record<string, unknown>;\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Phrase` rich-text handler.\n *\n * Use it to keep a markup-bearing run as ONE translatable phrase — e.g. so a\n * count variable stays next to the noun it pluralizes:\n *\n * <Phrase category=\"ProductCard\" params={{ n: reviewCount }}>\n * Based on {'{n}'} <strong>reviews</strong>\n * </Phrase>\n *\n * The inline markup never reaches the translator — it's replaced with neutral\n * tokens and the real elements are reconstituted at render (see richtext.ts in\n * the base SDK). The host carries `data-ls-phrase` so a wrapping `<Translate>`\n * skips it and lets this handler own it.\n *\n * Keep children static (literal markup): the handler takes over the rendered\n * subtree. For values React owns and re-renders, pass them through `params`.\n */\nexport function Phrase({ category = '', params = {}, tag = 'span', className, children }: PhraseProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaPhrase>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaPhrase(host, { category, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the category changes; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category]);\n\n // Re-render on a changed count/param after mount.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className, 'data-ls-phrase': '' }, children);\n}\n\nexport default Phrase;\n","import { createElement } from 'react';\nimport type { ReactNode } from 'react';\n\n/**\n * Props for the React `DontTranslate` component. Mirrors the Svelte component,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface DontTranslateProps {\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * Marks a region as never-translated, preserved verbatim:\n *\n * Built with <DontTranslate>Kangen®</DontTranslate> on{' '}\n * <DontTranslate>langsys.dev</DontTranslate>\n *\n * The host carries the standard `translate=\"no\"` attribute, which both the\n * tokenizer and the renderer in the base SDK already honor — so its content is\n * never tokenized, registered, or replaced. Pure presentational glue; no\n * vanilla handler needed.\n */\nexport function DontTranslate({ tag = 'span', className, children }: DontTranslateProps) {\n return createElement(\n tag,\n { translate: 'no', 'data-ls-dont-translate': '', className },\n children,\n );\n}\n\nexport default DontTranslate;\n","/**\n * langsys-js-react — idiomatic React binding over `langsys-js-typescript`.\n *\n * Public API:\n * - `LangsysApp` — `init` accepts a `Signal<string>` (make one with\n * `createLocaleStore`) for the user locale; every other method delegates.\n * - Hooks — `useT`, `useCurrentLocale`, `useTranslations`, `useLocaleStore`,\n * and the low-level `useSignal`. These are the reactive layer; in components\n * prefer them over the raw signals.\n * - `createLocaleStore` — make the user-locale store (React analog of Svelte's\n * `writable`).\n * - `Translate` — React component wrapping the vanilla DOM `Translate` class.\n * - Raw signals `t` / `currentlyLoadedLocale` / `sTranslations` — re-exported\n * for advanced/direct subscription outside React's render cycle.\n */\n\nimport {\n LangsysApp as _LangsysApp,\n type ExtractParamKeys,\n type ParamPrimitive,\n type ParamsFor,\n type Signal,\n type TArgs,\n type TFunction,\n type TranslationParams,\n type iCategories,\n type iContentBlock,\n type iCountry,\n type iCountryDialCode,\n type iCountryList,\n type iCurrency,\n type iCurrencyList,\n type iLangsysInitConfig as iVanillaInitConfig,\n type iLangsysResponse,\n type iLanguageName,\n type iLocaleData,\n type iLocaleDefault,\n type iLocaleFlat,\n type iProject,\n type iTranslations,\n} from 'langsys-js-typescript';\n\n// Reactive primitives (raw signals) — re-exported for advanced/direct\n// subscription. `tSignal` is exposed under the friendlier name `t`. In\n// components, prefer the hooks (`useT`, `useCurrentLocale`, …).\nexport { currentlyLoadedLocale, createSignal, sTranslations, tSignal as t } from 'langsys-js-typescript';\n\n// Locale canonicalization (BCP 47) — the SDK canonicalizes all locale input\n// (v0.3.0+); re-exported so consumers can normalize their own values the same\n// way before comparing against `useCurrentLocale()` / `detectPreferredLocale()`.\nexport { canonicalizeLocale } from 'langsys-js-typescript';\n\n// API client (vanilla — no React concerns)\nexport { LangsysAppAPI } from 'langsys-js-typescript';\n\n// Hooks + adapters (the React-idiomatic reactive layer)\nexport { createLocaleStore, useSignal } from './adapters.js';\nexport { useCurrentLocale, useLocaleStore, useT, useTranslations } from './hooks.js';\n\n// Components\nexport { Translate, type TranslateProps } from './components/Translate.js';\nexport { Phrase, type PhraseProps } from './components/Phrase.js';\nexport { DontTranslate, type DontTranslateProps } from './components/DontTranslate.js';\n\n// Type re-exports — these are framework-agnostic, so consumers can rely on them\n// directly without reaching into `langsys-js-typescript`.\nexport type {\n ExtractParamKeys,\n ParamPrimitive,\n ParamsFor,\n Signal,\n TArgs,\n TFunction,\n TranslationParams,\n iCategories,\n iContentBlock,\n iCountry,\n iCountryDialCode,\n iCountryList,\n iCurrency,\n iCurrencyList,\n iLangsysResponse,\n iLanguageName,\n iLocaleData,\n iLocaleDefault,\n iLocaleFlat,\n iProject,\n iTranslations,\n};\n\n/**\n * React-flavored init config. Identical to the base SDK's config except\n * `UserLocaleStore` is typed as a `Signal<string>` — create one with\n * `createLocaleStore()` (or get one from the `useLocaleStore` hook). The base\n * SDK only reads and subscribes to it.\n */\nexport interface iLangsysInitConfig extends Omit<iVanillaInitConfig, 'UserLocaleStore'> {\n UserLocaleStore: Signal<string>;\n}\n\n/**\n * React SDK entry point. Delegates everything to the underlying\n * `langsys-js-typescript` singleton. Because React's locale store is already a\n * `Signal` (unlike Svelte's `Writable`, which needs adapting), `init` is a\n * straight passthrough — the React-native concerns live in the hooks and the\n * `<Translate>` component, not here.\n */\nclass LangsysAppReact {\n /** Initialize Langsys. Pass a `Signal<string>` (from `createLocaleStore`) as `UserLocaleStore`. */\n public init(config: iLangsysInitConfig): Promise<iLangsysResponse> {\n return _LangsysApp.init(config);\n }\n\n public get Translations() {\n return _LangsysApp.Translations;\n }\n\n public get translationsLoadingPromise() {\n return _LangsysApp.translationsLoadingPromise;\n }\n\n /** Current translation function. Reads fresh state on every call (not reactive on its own — use `useT()` in components). */\n public get t(): TFunction {\n return _LangsysApp.t;\n }\n\n public get debug() {\n return _LangsysApp.debug;\n }\n\n public refresh() {\n return _LangsysApp.refresh();\n }\n\n public getCountries(inLocale?: string) {\n return _LangsysApp.getCountries(inLocale);\n }\n public getCountryName(forCountryCode: string, inLocale?: string) {\n return _LangsysApp.getCountryName(forCountryCode, inLocale);\n }\n public getCurrencies(inLocale?: string) {\n return _LangsysApp.getCurrencies(inLocale);\n }\n public getCurrencyName(forCurrencyCode: string, inLocale?: string) {\n return _LangsysApp.getCurrencyName(forCurrencyCode, inLocale);\n }\n public getDialCodes(inLocale?: string) {\n return _LangsysApp.getDialCodes(inLocale);\n }\n\n public getLocales(inLocale?: string) {\n return _LangsysApp.getLocales(inLocale);\n }\n public getLocalesFlat(inLocale?: string) {\n return _LangsysApp.getLocalesFlat(inLocale);\n }\n public getLocalesData(inLocale?: string, forceRefresh?: boolean) {\n return _LangsysApp.getLocalesData(inLocale, forceRefresh);\n }\n public getLocalesFormat(format: '' | 'flat' | 'data' = '', inLocale?: string) {\n return _LangsysApp.getLocalesFormat(format, inLocale);\n }\n public getLocaleName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleName(forLocale, shortName, inLocale);\n }\n public getLocaleNameWithLookup(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleNameWithLookup(forLocale, shortName, inLocale);\n }\n\n /** @deprecated use `getLocaleNameWithLookup` or `getLocaleName` */\n public getLanguageName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLanguageName(forLocale, shortName, inLocale);\n }\n\n public detectPreferredLocale(acceptLanguageHeader?: string | null, supportedLocales?: string[]) {\n return _LangsysApp.detectPreferredLocale(acceptLanguageHeader, supportedLocales);\n }\n}\n\nexport const LangsysApp = new LangsysAppReact();\n"]}
1
+ {"version":3,"sources":["../src/adapters.ts","../src/hooks.ts","../src/components/Translate.tsx","../src/components/Phrase.tsx","../src/components/DontTranslate.tsx","../src/index.ts"],"names":["useSyncExternalStore","createSignal","tSignal","currentlyLoadedLocale","sTranslations","useState","useRef","useEffect","VanillaTranslate","createElement","VanillaPhrase","_LangsysApp"],"mappings":"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","file":"index.js","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport { createSignal, type Signal } from 'langsys-js-typescript';\n\n/**\n * Subscribe a React component to a base-SDK `Signal<T>` and return its current\n * value, re-rendering whenever the signal changes.\n *\n * This is the React mirror of Svelte's `$store` auto-subscription. Where the\n * Svelte wrapper adapts a native store *into* the SDK (`writable` → `Signal`),\n * the React wrapper adapts the SDK's signals *out* to the render cycle. It is\n * built on `useSyncExternalStore`, so it is concurrent-safe and SSR-safe — the\n * server snapshot reads the same `.get()`, which the base SDK seeds from\n * `initialTranslations` during SSR.\n *\n * The base SDK's signals are stable between changes (every `set` replaces the\n * value, so `.get()` returns a fresh reference only after a real change). That\n * is exactly the identity contract `useSyncExternalStore` requires — no tearing,\n * no render loops.\n *\n * `signal` must be stable across renders: a module-level singleton (the SDK's\n * `t` / `currentlyLoadedLocale` / `sTranslations`) or one created once with\n * `useState(() => createLocaleStore(...))`. Passing a freshly-created signal on\n * every render would resubscribe on every render.\n */\nexport function useSignal<T>(signal: Signal<T>): T {\n return useSyncExternalStore(signal.subscribe, signal.get, signal.get);\n}\n\n/**\n * Create a reactive `Signal<string>` to hold the user's selected locale — the\n * React analog of Svelte's `writable('en-US')`.\n *\n * Pass the result as `UserLocaleStore` to `LangsysApp.init`, read it reactively\n * with `useSignal(store)` (or use the all-in-one `useLocaleStore` hook), and\n * switch locale with `store.set('fr-FR')`. The base SDK only ever reads and\n * subscribes to it — it never writes.\n *\n * Locale identifiers are canonicalized to BCP 47 by the base SDK (v0.3.0+), so\n * `'en-us'` still works on input — but `currentlyLoadedLocale` always emits the\n * canonical form (`'en-US'`), so prefer canonical casing to keep comparisons\n * against it straightforward.\n */\nexport function createLocaleStore(initial = 'en-US'): Signal<string> {\n return createSignal<string>(initial);\n}\n","import { useState } from 'react';\nimport { currentlyLoadedLocale, sTranslations, tSignal } from 'langsys-js-typescript';\nimport type { Signal, TFunction, iCategories } from 'langsys-js-typescript';\nimport { createLocaleStore, useSignal } from './adapters.js';\n\n/**\n * The current translation function, re-rendering the calling component whenever\n * translations or the loaded locale change. This is the React analog of Svelte's\n * `$t` store read.\n *\n * const t = useT();\n * return <h1>{t('Welcome to my app', 'UI')}</h1>;\n *\n * The phrase is both the lookup key and the base-language default. Signature:\n * `t(phrase, category?, params?)`. Placeholder names in the phrase are\n * type-checked against the params object at the call site.\n */\nexport function useT(): TFunction {\n return useSignal(tSignal);\n}\n\n/**\n * The locale whose translations are currently loaded. This lags the\n * user-selected locale (`UserLocaleStore`) until the fetch for the new locale\n * settles, which makes it the right value to gate \"translations are ready\" UI on.\n */\nexport function useCurrentLocale(): string {\n return useSignal(currentlyLoadedLocale);\n}\n\n/**\n * The raw translation catalog. Rarely needed in app code — prefer `useT()`.\n * Exposed for advanced cases (inspecting which categories/phrases are loaded).\n */\nexport function useTranslations(): iCategories {\n return useSignal(sTranslations);\n}\n\n/**\n * All-in-one convenience for the user-locale store. Creates one stable\n * `Signal<string>` (the React analog of Svelte's `writable`), subscribes the\n * component to it, and returns `[locale, setLocale, store]`.\n *\n * const [locale, setLocale, store] = useLocaleStore('en-US');\n * useEffect(() => {\n * LangsysApp.init({ projectid, key, UserLocaleStore: store });\n * }, [store]);\n *\n * <select value={locale} onChange={(e) => setLocale(e.target.value)}>...\n *\n * The store is created lazily once and never re-created across renders, so it is\n * safe to pass to `LangsysApp.init` and to depend on in effects.\n */\nexport function useLocaleStore(initial = 'en-US'): [string, (locale: string) => void, Signal<string>] {\n const [store] = useState(() => createLocaleStore(initial));\n const locale = useSignal(store);\n return [locale, store.set, store];\n}\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Translate as VanillaTranslate, type ParamPrimitive } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Translate` component. Mirrors the Svelte component's\n * props 1:1, with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface TranslateProps {\n /** Optional category under which tokens are registered. Helps translators disambiguate. */\n category?: string;\n /** Optional stable id for the content block. If omitted, the SDK hashes category + tokens. */\n custom_id?: string;\n /** Optional human-readable label shown in the Translation Manager. */\n label?: string;\n /**\n * Interpolation params. Write placeholders as `%key%` in the markup (a bare\n * `{key}` in JSX is a JS expression; the SDK normalizes `%key%` to canonical\n * `{key}` at capture). Applied to content-block text nodes, translatable\n * attributes, `<option>` text, and single-token content — including\n * untranslated fallbacks. Number/Date values get CLDR locale formatting.\n */\n params?: Record<string, ParamPrimitive>;\n /** Host element tag. Defaults to a `<translate>` custom element. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Translate` DOM class from\n * `langsys-js-typescript`. It renders a host element, then on mount lets the\n * vanilla class walk and tokenize the rendered children (text nodes plus\n * translatable attributes), register the content block, and re-translate on\n * locale change. On unmount it tears the instance down.\n *\n * This is the React analog of the Svelte `<Translate>` component — pure\n * mount/destroy glue. The DOM walking, content-block registration, attribute\n * harvesting, and re-translation lifecycle all live in the base SDK.\n *\n * Like the Svelte component, this lets the SDK mutate the rendered DOM in place,\n * so keep the children static: prose, marketing copy, CMS-rendered HTML — the\n * content-block use case. For dynamic per-string values that React owns and\n * re-renders, use `useT()` instead.\n */\nexport function Translate({\n category = '',\n custom_id = '',\n label = '',\n params,\n tag = 'translate',\n className,\n children,\n}: TranslateProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaTranslate>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaTranslate(host, { category, custom_id, label, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the identity props change; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category, custom_id, label]);\n\n // Re-render on changed params after mount, mirroring <Phrase>.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className }, children);\n}\n\nexport default Translate;\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Phrase as VanillaPhrase } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Phrase` component. Mirrors the Svelte component's props,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface PhraseProps {\n /** Category the phrase registers under (disambiguation for translators). */\n category?: string;\n /** Interpolation params. Write placeholders as `%n%` / `%name%` in the markup (a bare `{n}` in JSX is an expression). */\n params?: Record<string, unknown>;\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Phrase` rich-text handler.\n *\n * Use it to keep a markup-bearing run as ONE translatable phrase — e.g. so a\n * count variable stays next to the noun it pluralizes:\n *\n * <Phrase category=\"ProductCard\" params={{ n: reviewCount }}>\n * Based on %n% <strong>reviews</strong>\n * </Phrase>\n *\n * Write placeholders as `%n%` (not bare `{n}`): in JSX a literal `{n}` is a JS\n * expression React evaluates before the SDK sees the text. `%n%` passes through\n * as literal text and the SDK normalizes it to canonical `{n}` at capture.\n *\n * The inline markup never reaches the translator — it's replaced with neutral\n * tokens and the real elements are reconstituted at render (see richtext.ts in\n * the base SDK). The host carries `data-ls-phrase` so a wrapping `<Translate>`\n * skips it and lets this handler own it.\n *\n * Keep children static (literal markup): the handler takes over the rendered\n * subtree. For values React owns and re-renders, pass them through `params`.\n */\nexport function Phrase({ category = '', params = {}, tag = 'span', className, children }: PhraseProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaPhrase>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaPhrase(host, { category, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the category changes; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category]);\n\n // Re-render on a changed count/param after mount.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className, 'data-ls-phrase': '' }, children);\n}\n\nexport default Phrase;\n","import { createElement } from 'react';\nimport type { ReactNode } from 'react';\n\n/**\n * Props for the React `DontTranslate` component. Mirrors the Svelte component,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface DontTranslateProps {\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * Marks a region as never-translated, preserved verbatim:\n *\n * Built with <DontTranslate>Kangen®</DontTranslate> on{' '}\n * <DontTranslate>langsys.dev</DontTranslate>\n *\n * The host carries the standard `translate=\"no\"` attribute, which both the\n * tokenizer and the renderer in the base SDK already honor — so its content is\n * never tokenized, registered, or replaced. Pure presentational glue; no\n * vanilla handler needed.\n */\nexport function DontTranslate({ tag = 'span', className, children }: DontTranslateProps) {\n return createElement(\n tag,\n { translate: 'no', 'data-ls-dont-translate': '', className },\n children,\n );\n}\n\nexport default DontTranslate;\n","/**\n * langsys-js-react — idiomatic React binding over `langsys-js-typescript`.\n *\n * Public API:\n * - `LangsysApp` — `init` accepts a `Signal<string>` (make one with\n * `createLocaleStore`) for the user locale; every other method delegates.\n * - Hooks — `useT`, `useCurrentLocale`, `useTranslations`, `useLocaleStore`,\n * and the low-level `useSignal`. These are the reactive layer; in components\n * prefer them over the raw signals.\n * - `createLocaleStore` — make the user-locale store (React analog of Svelte's\n * `writable`).\n * - `Translate` — React component wrapping the vanilla DOM `Translate` class.\n * - Raw signals `t` / `currentlyLoadedLocale` / `sTranslations` — re-exported\n * for advanced/direct subscription outside React's render cycle.\n */\n\nimport {\n LangsysApp as _LangsysApp,\n type ExtractParamKeys,\n type ParamPrimitive,\n type ParamsFor,\n type Signal,\n type TArgs,\n type TFunction,\n type TranslationParams,\n type iCategories,\n type iContentBlock,\n type iCountry,\n type iCountryDialCode,\n type iCountryList,\n type iCurrency,\n type iCurrencyList,\n type iLangsysInitConfig as iVanillaInitConfig,\n type iLangsysResponse,\n type iLanguageName,\n type iLocaleData,\n type iLocaleDefault,\n type iLocaleFlat,\n type iProject,\n type iTranslations,\n} from 'langsys-js-typescript';\n\n// Reactive primitives (raw signals) — re-exported for advanced/direct\n// subscription. `tSignal` is exposed under the friendlier name `t`. In\n// components, prefer the hooks (`useT`, `useCurrentLocale`, …).\nexport { currentlyLoadedLocale, createSignal, sTranslations, tSignal as t } from 'langsys-js-typescript';\n\n// Locale canonicalization (BCP 47) — the SDK canonicalizes all locale input\n// (v0.3.0+); re-exported so consumers can normalize their own values the same\n// way before comparing against `useCurrentLocale()` / `detectPreferredLocale()`.\nexport { canonicalizeLocale } from 'langsys-js-typescript';\n\n// API client (vanilla — no React concerns)\nexport { LangsysAppAPI } from 'langsys-js-typescript';\n\n// Hooks + adapters (the React-idiomatic reactive layer)\nexport { createLocaleStore, useSignal } from './adapters.js';\nexport { useCurrentLocale, useLocaleStore, useT, useTranslations } from './hooks.js';\n\n// Components\nexport { Translate, type TranslateProps } from './components/Translate.js';\nexport { Phrase, type PhraseProps } from './components/Phrase.js';\nexport { DontTranslate, type DontTranslateProps } from './components/DontTranslate.js';\n\n// Type re-exports — these are framework-agnostic, so consumers can rely on them\n// directly without reaching into `langsys-js-typescript`.\nexport type {\n ExtractParamKeys,\n ParamPrimitive,\n ParamsFor,\n Signal,\n TArgs,\n TFunction,\n TranslationParams,\n iCategories,\n iContentBlock,\n iCountry,\n iCountryDialCode,\n iCountryList,\n iCurrency,\n iCurrencyList,\n iLangsysResponse,\n iLanguageName,\n iLocaleData,\n iLocaleDefault,\n iLocaleFlat,\n iProject,\n iTranslations,\n};\n\n/**\n * React-flavored init config. Identical to the base SDK's config except\n * `UserLocaleStore` is typed as a `Signal<string>` — create one with\n * `createLocaleStore()` (or get one from the `useLocaleStore` hook). The base\n * SDK only reads and subscribes to it.\n */\nexport interface iLangsysInitConfig extends Omit<iVanillaInitConfig, 'UserLocaleStore'> {\n UserLocaleStore: Signal<string>;\n}\n\n/**\n * React SDK entry point. Delegates everything to the underlying\n * `langsys-js-typescript` singleton. Because React's locale store is already a\n * `Signal` (unlike Svelte's `Writable`, which needs adapting), `init` is a\n * straight passthrough — the React-native concerns live in the hooks and the\n * `<Translate>` component, not here.\n */\nclass LangsysAppReact {\n /** Initialize Langsys. Pass a `Signal<string>` (from `createLocaleStore`) as `UserLocaleStore`. */\n public init(config: iLangsysInitConfig): Promise<iLangsysResponse> {\n return _LangsysApp.init(config);\n }\n\n public get Translations() {\n return _LangsysApp.Translations;\n }\n\n public get translationsLoadingPromise() {\n return _LangsysApp.translationsLoadingPromise;\n }\n\n /** Current translation function. Reads fresh state on every call (not reactive on its own — use `useT()` in components). */\n public get t(): TFunction {\n return _LangsysApp.t;\n }\n\n public get debug() {\n return _LangsysApp.debug;\n }\n\n public refresh() {\n return _LangsysApp.refresh();\n }\n\n public getCountries(inLocale?: string) {\n return _LangsysApp.getCountries(inLocale);\n }\n public getCountryName(forCountryCode: string, inLocale?: string) {\n return _LangsysApp.getCountryName(forCountryCode, inLocale);\n }\n public getCurrencies(inLocale?: string) {\n return _LangsysApp.getCurrencies(inLocale);\n }\n public getCurrencyName(forCurrencyCode: string, inLocale?: string) {\n return _LangsysApp.getCurrencyName(forCurrencyCode, inLocale);\n }\n public getDialCodes(inLocale?: string) {\n return _LangsysApp.getDialCodes(inLocale);\n }\n\n public getLocales(inLocale?: string) {\n return _LangsysApp.getLocales(inLocale);\n }\n public getLocalesFlat(inLocale?: string) {\n return _LangsysApp.getLocalesFlat(inLocale);\n }\n public getLocalesData(inLocale?: string, forceRefresh?: boolean) {\n return _LangsysApp.getLocalesData(inLocale, forceRefresh);\n }\n public getLocalesFormat(format: '' | 'flat' | 'data' = '', inLocale?: string) {\n return _LangsysApp.getLocalesFormat(format, inLocale);\n }\n public getLocaleName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleName(forLocale, shortName, inLocale);\n }\n public getLocaleNameWithLookup(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleNameWithLookup(forLocale, shortName, inLocale);\n }\n\n /** @deprecated use `getLocaleNameWithLookup` or `getLocaleName` */\n public getLanguageName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLanguageName(forLocale, shortName, inLocale);\n }\n\n public detectPreferredLocale(acceptLanguageHeader?: string | null, supportedLocales?: string[]) {\n return _LangsysApp.detectPreferredLocale(acceptLanguageHeader, supportedLocales);\n }\n}\n\nexport const LangsysApp = new LangsysAppReact();\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters.ts","../src/hooks.ts","../src/components/Translate.tsx","../src/components/Phrase.tsx","../src/components/DontTranslate.tsx","../src/index.ts"],"names":["VanillaTranslate","useRef","useEffect","VanillaPhrase","createElement","_LangsysApp"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport { createSignal, type Signal } from 'langsys-js-typescript';\n\n/**\n * Subscribe a React component to a base-SDK `Signal<T>` and return its current\n * value, re-rendering whenever the signal changes.\n *\n * This is the React mirror of Svelte's `$store` auto-subscription. Where the\n * Svelte wrapper adapts a native store *into* the SDK (`writable` → `Signal`),\n * the React wrapper adapts the SDK's signals *out* to the render cycle. It is\n * built on `useSyncExternalStore`, so it is concurrent-safe and SSR-safe — the\n * server snapshot reads the same `.get()`, which the base SDK seeds from\n * `initialTranslations` during SSR.\n *\n * The base SDK's signals are stable between changes (every `set` replaces the\n * value, so `.get()` returns a fresh reference only after a real change). That\n * is exactly the identity contract `useSyncExternalStore` requires — no tearing,\n * no render loops.\n *\n * `signal` must be stable across renders: a module-level singleton (the SDK's\n * `t` / `currentlyLoadedLocale` / `sTranslations`) or one created once with\n * `useState(() => createLocaleStore(...))`. Passing a freshly-created signal on\n * every render would resubscribe on every render.\n */\nexport function useSignal<T>(signal: Signal<T>): T {\n return useSyncExternalStore(signal.subscribe, signal.get, signal.get);\n}\n\n/**\n * Create a reactive `Signal<string>` to hold the user's selected locale — the\n * React analog of Svelte's `writable('en-US')`.\n *\n * Pass the result as `UserLocaleStore` to `LangsysApp.init`, read it reactively\n * with `useSignal(store)` (or use the all-in-one `useLocaleStore` hook), and\n * switch locale with `store.set('fr-FR')`. The base SDK only ever reads and\n * subscribes to it — it never writes.\n *\n * Locale identifiers are canonicalized to BCP 47 by the base SDK (v0.3.0+), so\n * `'en-us'` still works on input — but `currentlyLoadedLocale` always emits the\n * canonical form (`'en-US'`), so prefer canonical casing to keep comparisons\n * against it straightforward.\n */\nexport function createLocaleStore(initial = 'en-US'): Signal<string> {\n return createSignal<string>(initial);\n}\n","import { useState } from 'react';\nimport { currentlyLoadedLocale, sTranslations, tSignal } from 'langsys-js-typescript';\nimport type { Signal, TFunction, iCategories } from 'langsys-js-typescript';\nimport { createLocaleStore, useSignal } from './adapters.js';\n\n/**\n * The current translation function, re-rendering the calling component whenever\n * translations or the loaded locale change. This is the React analog of Svelte's\n * `$t` store read.\n *\n * const t = useT();\n * return <h1>{t('Welcome to my app', 'UI')}</h1>;\n *\n * The phrase is both the lookup key and the base-language default. Signature:\n * `t(phrase, category?, params?)`. Placeholder names in the phrase are\n * type-checked against the params object at the call site.\n */\nexport function useT(): TFunction {\n return useSignal(tSignal);\n}\n\n/**\n * The locale whose translations are currently loaded. This lags the\n * user-selected locale (`UserLocaleStore`) until the fetch for the new locale\n * settles, which makes it the right value to gate \"translations are ready\" UI on.\n */\nexport function useCurrentLocale(): string {\n return useSignal(currentlyLoadedLocale);\n}\n\n/**\n * The raw translation catalog. Rarely needed in app code — prefer `useT()`.\n * Exposed for advanced cases (inspecting which categories/phrases are loaded).\n */\nexport function useTranslations(): iCategories {\n return useSignal(sTranslations);\n}\n\n/**\n * All-in-one convenience for the user-locale store. Creates one stable\n * `Signal<string>` (the React analog of Svelte's `writable`), subscribes the\n * component to it, and returns `[locale, setLocale, store]`.\n *\n * const [locale, setLocale, store] = useLocaleStore('en-US');\n * useEffect(() => {\n * LangsysApp.init({ projectid, key, UserLocaleStore: store });\n * }, [store]);\n *\n * <select value={locale} onChange={(e) => setLocale(e.target.value)}>...\n *\n * The store is created lazily once and never re-created across renders, so it is\n * safe to pass to `LangsysApp.init` and to depend on in effects.\n */\nexport function useLocaleStore(initial = 'en-US'): [string, (locale: string) => void, Signal<string>] {\n const [store] = useState(() => createLocaleStore(initial));\n const locale = useSignal(store);\n return [locale, store.set, store];\n}\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Translate as VanillaTranslate, type ParamPrimitive } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Translate` component. Mirrors the Svelte component's\n * props 1:1, with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface TranslateProps {\n /** Optional category under which tokens are registered. Helps translators disambiguate. */\n category?: string;\n /** Optional stable id for the content block. If omitted, the SDK hashes category + tokens. */\n custom_id?: string;\n /** Optional human-readable label shown in the Translation Manager. */\n label?: string;\n /**\n * Interpolation params applied to the resolved text (single-brace `{key}`,\n * same syntax as `t()`). Applied to content-block text nodes, translatable\n * attributes, `<option>` text, and single-token content — including\n * untranslated fallbacks. Number/Date values get CLDR locale formatting.\n */\n params?: Record<string, ParamPrimitive>;\n /** Host element tag. Defaults to a `<translate>` custom element. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Translate` DOM class from\n * `langsys-js-typescript`. It renders a host element, then on mount lets the\n * vanilla class walk and tokenize the rendered children (text nodes plus\n * translatable attributes), register the content block, and re-translate on\n * locale change. On unmount it tears the instance down.\n *\n * This is the React analog of the Svelte `<Translate>` component — pure\n * mount/destroy glue. The DOM walking, content-block registration, attribute\n * harvesting, and re-translation lifecycle all live in the base SDK.\n *\n * Like the Svelte component, this lets the SDK mutate the rendered DOM in place,\n * so keep the children static: prose, marketing copy, CMS-rendered HTML — the\n * content-block use case. For dynamic per-string values that React owns and\n * re-renders, use `useT()` instead.\n */\nexport function Translate({\n category = '',\n custom_id = '',\n label = '',\n params,\n tag = 'translate',\n className,\n children,\n}: TranslateProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaTranslate>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaTranslate(host, { category, custom_id, label, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the identity props change; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category, custom_id, label]);\n\n // Re-render on changed params after mount, mirroring <Phrase>.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className }, children);\n}\n\nexport default Translate;\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Phrase as VanillaPhrase } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Phrase` component. Mirrors the Svelte component's props,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface PhraseProps {\n /** Category the phrase registers under (disambiguation for translators). */\n category?: string;\n /** Interpolation params — `{n}` for pluralization, `{name}`, etc. */\n params?: Record<string, unknown>;\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Phrase` rich-text handler.\n *\n * Use it to keep a markup-bearing run as ONE translatable phrase — e.g. so a\n * count variable stays next to the noun it pluralizes:\n *\n * <Phrase category=\"ProductCard\" params={{ n: reviewCount }}>\n * Based on {'{n}'} <strong>reviews</strong>\n * </Phrase>\n *\n * The inline markup never reaches the translator — it's replaced with neutral\n * tokens and the real elements are reconstituted at render (see richtext.ts in\n * the base SDK). The host carries `data-ls-phrase` so a wrapping `<Translate>`\n * skips it and lets this handler own it.\n *\n * Keep children static (literal markup): the handler takes over the rendered\n * subtree. For values React owns and re-renders, pass them through `params`.\n */\nexport function Phrase({ category = '', params = {}, tag = 'span', className, children }: PhraseProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaPhrase>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaPhrase(host, { category, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the category changes; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category]);\n\n // Re-render on a changed count/param after mount.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className, 'data-ls-phrase': '' }, children);\n}\n\nexport default Phrase;\n","import { createElement } from 'react';\nimport type { ReactNode } from 'react';\n\n/**\n * Props for the React `DontTranslate` component. Mirrors the Svelte component,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface DontTranslateProps {\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * Marks a region as never-translated, preserved verbatim:\n *\n * Built with <DontTranslate>Kangen®</DontTranslate> on{' '}\n * <DontTranslate>langsys.dev</DontTranslate>\n *\n * The host carries the standard `translate=\"no\"` attribute, which both the\n * tokenizer and the renderer in the base SDK already honor — so its content is\n * never tokenized, registered, or replaced. Pure presentational glue; no\n * vanilla handler needed.\n */\nexport function DontTranslate({ tag = 'span', className, children }: DontTranslateProps) {\n return createElement(\n tag,\n { translate: 'no', 'data-ls-dont-translate': '', className },\n children,\n );\n}\n\nexport default DontTranslate;\n","/**\n * langsys-js-react — idiomatic React binding over `langsys-js-typescript`.\n *\n * Public API:\n * - `LangsysApp` — `init` accepts a `Signal<string>` (make one with\n * `createLocaleStore`) for the user locale; every other method delegates.\n * - Hooks — `useT`, `useCurrentLocale`, `useTranslations`, `useLocaleStore`,\n * and the low-level `useSignal`. These are the reactive layer; in components\n * prefer them over the raw signals.\n * - `createLocaleStore` — make the user-locale store (React analog of Svelte's\n * `writable`).\n * - `Translate` — React component wrapping the vanilla DOM `Translate` class.\n * - Raw signals `t` / `currentlyLoadedLocale` / `sTranslations` — re-exported\n * for advanced/direct subscription outside React's render cycle.\n */\n\nimport {\n LangsysApp as _LangsysApp,\n type ExtractParamKeys,\n type ParamPrimitive,\n type ParamsFor,\n type Signal,\n type TArgs,\n type TFunction,\n type TranslationParams,\n type iCategories,\n type iContentBlock,\n type iCountry,\n type iCountryDialCode,\n type iCountryList,\n type iCurrency,\n type iCurrencyList,\n type iLangsysInitConfig as iVanillaInitConfig,\n type iLangsysResponse,\n type iLanguageName,\n type iLocaleData,\n type iLocaleDefault,\n type iLocaleFlat,\n type iProject,\n type iTranslations,\n} from 'langsys-js-typescript';\n\n// Reactive primitives (raw signals) — re-exported for advanced/direct\n// subscription. `tSignal` is exposed under the friendlier name `t`. In\n// components, prefer the hooks (`useT`, `useCurrentLocale`, …).\nexport { currentlyLoadedLocale, createSignal, sTranslations, tSignal as t } from 'langsys-js-typescript';\n\n// Locale canonicalization (BCP 47) — the SDK canonicalizes all locale input\n// (v0.3.0+); re-exported so consumers can normalize their own values the same\n// way before comparing against `useCurrentLocale()` / `detectPreferredLocale()`.\nexport { canonicalizeLocale } from 'langsys-js-typescript';\n\n// API client (vanilla — no React concerns)\nexport { LangsysAppAPI } from 'langsys-js-typescript';\n\n// Hooks + adapters (the React-idiomatic reactive layer)\nexport { createLocaleStore, useSignal } from './adapters.js';\nexport { useCurrentLocale, useLocaleStore, useT, useTranslations } from './hooks.js';\n\n// Components\nexport { Translate, type TranslateProps } from './components/Translate.js';\nexport { Phrase, type PhraseProps } from './components/Phrase.js';\nexport { DontTranslate, type DontTranslateProps } from './components/DontTranslate.js';\n\n// Type re-exports — these are framework-agnostic, so consumers can rely on them\n// directly without reaching into `langsys-js-typescript`.\nexport type {\n ExtractParamKeys,\n ParamPrimitive,\n ParamsFor,\n Signal,\n TArgs,\n TFunction,\n TranslationParams,\n iCategories,\n iContentBlock,\n iCountry,\n iCountryDialCode,\n iCountryList,\n iCurrency,\n iCurrencyList,\n iLangsysResponse,\n iLanguageName,\n iLocaleData,\n iLocaleDefault,\n iLocaleFlat,\n iProject,\n iTranslations,\n};\n\n/**\n * React-flavored init config. Identical to the base SDK's config except\n * `UserLocaleStore` is typed as a `Signal<string>` — create one with\n * `createLocaleStore()` (or get one from the `useLocaleStore` hook). The base\n * SDK only reads and subscribes to it.\n */\nexport interface iLangsysInitConfig extends Omit<iVanillaInitConfig, 'UserLocaleStore'> {\n UserLocaleStore: Signal<string>;\n}\n\n/**\n * React SDK entry point. Delegates everything to the underlying\n * `langsys-js-typescript` singleton. Because React's locale store is already a\n * `Signal` (unlike Svelte's `Writable`, which needs adapting), `init` is a\n * straight passthrough — the React-native concerns live in the hooks and the\n * `<Translate>` component, not here.\n */\nclass LangsysAppReact {\n /** Initialize Langsys. Pass a `Signal<string>` (from `createLocaleStore`) as `UserLocaleStore`. */\n public init(config: iLangsysInitConfig): Promise<iLangsysResponse> {\n return _LangsysApp.init(config);\n }\n\n public get Translations() {\n return _LangsysApp.Translations;\n }\n\n public get translationsLoadingPromise() {\n return _LangsysApp.translationsLoadingPromise;\n }\n\n /** Current translation function. Reads fresh state on every call (not reactive on its own — use `useT()` in components). */\n public get t(): TFunction {\n return _LangsysApp.t;\n }\n\n public get debug() {\n return _LangsysApp.debug;\n }\n\n public refresh() {\n return _LangsysApp.refresh();\n }\n\n public getCountries(inLocale?: string) {\n return _LangsysApp.getCountries(inLocale);\n }\n public getCountryName(forCountryCode: string, inLocale?: string) {\n return _LangsysApp.getCountryName(forCountryCode, inLocale);\n }\n public getCurrencies(inLocale?: string) {\n return _LangsysApp.getCurrencies(inLocale);\n }\n public getCurrencyName(forCurrencyCode: string, inLocale?: string) {\n return _LangsysApp.getCurrencyName(forCurrencyCode, inLocale);\n }\n public getDialCodes(inLocale?: string) {\n return _LangsysApp.getDialCodes(inLocale);\n }\n\n public getLocales(inLocale?: string) {\n return _LangsysApp.getLocales(inLocale);\n }\n public getLocalesFlat(inLocale?: string) {\n return _LangsysApp.getLocalesFlat(inLocale);\n }\n public getLocalesData(inLocale?: string, forceRefresh?: boolean) {\n return _LangsysApp.getLocalesData(inLocale, forceRefresh);\n }\n public getLocalesFormat(format: '' | 'flat' | 'data' = '', inLocale?: string) {\n return _LangsysApp.getLocalesFormat(format, inLocale);\n }\n public getLocaleName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleName(forLocale, shortName, inLocale);\n }\n public getLocaleNameWithLookup(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleNameWithLookup(forLocale, shortName, inLocale);\n }\n\n /** @deprecated use `getLocaleNameWithLookup` or `getLocaleName` */\n public getLanguageName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLanguageName(forLocale, shortName, inLocale);\n }\n\n public detectPreferredLocale(acceptLanguageHeader?: string | null, supportedLocales?: string[]) {\n return _LangsysApp.detectPreferredLocale(acceptLanguageHeader, supportedLocales);\n }\n}\n\nexport const LangsysApp = new LangsysAppReact();\n"]}
1
+ {"version":3,"sources":["../src/adapters.ts","../src/hooks.ts","../src/components/Translate.tsx","../src/components/Phrase.tsx","../src/components/DontTranslate.tsx","../src/index.ts"],"names":["VanillaTranslate","useRef","useEffect","VanillaPhrase","createElement","_LangsysApp"],"mappings":"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","file":"index.mjs","sourcesContent":["import { useSyncExternalStore } from 'react';\nimport { createSignal, type Signal } from 'langsys-js-typescript';\n\n/**\n * Subscribe a React component to a base-SDK `Signal<T>` and return its current\n * value, re-rendering whenever the signal changes.\n *\n * This is the React mirror of Svelte's `$store` auto-subscription. Where the\n * Svelte wrapper adapts a native store *into* the SDK (`writable` → `Signal`),\n * the React wrapper adapts the SDK's signals *out* to the render cycle. It is\n * built on `useSyncExternalStore`, so it is concurrent-safe and SSR-safe — the\n * server snapshot reads the same `.get()`, which the base SDK seeds from\n * `initialTranslations` during SSR.\n *\n * The base SDK's signals are stable between changes (every `set` replaces the\n * value, so `.get()` returns a fresh reference only after a real change). That\n * is exactly the identity contract `useSyncExternalStore` requires — no tearing,\n * no render loops.\n *\n * `signal` must be stable across renders: a module-level singleton (the SDK's\n * `t` / `currentlyLoadedLocale` / `sTranslations`) or one created once with\n * `useState(() => createLocaleStore(...))`. Passing a freshly-created signal on\n * every render would resubscribe on every render.\n */\nexport function useSignal<T>(signal: Signal<T>): T {\n return useSyncExternalStore(signal.subscribe, signal.get, signal.get);\n}\n\n/**\n * Create a reactive `Signal<string>` to hold the user's selected locale — the\n * React analog of Svelte's `writable('en-US')`.\n *\n * Pass the result as `UserLocaleStore` to `LangsysApp.init`, read it reactively\n * with `useSignal(store)` (or use the all-in-one `useLocaleStore` hook), and\n * switch locale with `store.set('fr-FR')`. The base SDK only ever reads and\n * subscribes to it — it never writes.\n *\n * Locale identifiers are canonicalized to BCP 47 by the base SDK (v0.3.0+), so\n * `'en-us'` still works on input — but `currentlyLoadedLocale` always emits the\n * canonical form (`'en-US'`), so prefer canonical casing to keep comparisons\n * against it straightforward.\n */\nexport function createLocaleStore(initial = 'en-US'): Signal<string> {\n return createSignal<string>(initial);\n}\n","import { useState } from 'react';\nimport { currentlyLoadedLocale, sTranslations, tSignal } from 'langsys-js-typescript';\nimport type { Signal, TFunction, iCategories } from 'langsys-js-typescript';\nimport { createLocaleStore, useSignal } from './adapters.js';\n\n/**\n * The current translation function, re-rendering the calling component whenever\n * translations or the loaded locale change. This is the React analog of Svelte's\n * `$t` store read.\n *\n * const t = useT();\n * return <h1>{t('Welcome to my app', 'UI')}</h1>;\n *\n * The phrase is both the lookup key and the base-language default. Signature:\n * `t(phrase, category?, params?)`. Placeholder names in the phrase are\n * type-checked against the params object at the call site.\n */\nexport function useT(): TFunction {\n return useSignal(tSignal);\n}\n\n/**\n * The locale whose translations are currently loaded. This lags the\n * user-selected locale (`UserLocaleStore`) until the fetch for the new locale\n * settles, which makes it the right value to gate \"translations are ready\" UI on.\n */\nexport function useCurrentLocale(): string {\n return useSignal(currentlyLoadedLocale);\n}\n\n/**\n * The raw translation catalog. Rarely needed in app code — prefer `useT()`.\n * Exposed for advanced cases (inspecting which categories/phrases are loaded).\n */\nexport function useTranslations(): iCategories {\n return useSignal(sTranslations);\n}\n\n/**\n * All-in-one convenience for the user-locale store. Creates one stable\n * `Signal<string>` (the React analog of Svelte's `writable`), subscribes the\n * component to it, and returns `[locale, setLocale, store]`.\n *\n * const [locale, setLocale, store] = useLocaleStore('en-US');\n * useEffect(() => {\n * LangsysApp.init({ projectid, key, UserLocaleStore: store });\n * }, [store]);\n *\n * <select value={locale} onChange={(e) => setLocale(e.target.value)}>...\n *\n * The store is created lazily once and never re-created across renders, so it is\n * safe to pass to `LangsysApp.init` and to depend on in effects.\n */\nexport function useLocaleStore(initial = 'en-US'): [string, (locale: string) => void, Signal<string>] {\n const [store] = useState(() => createLocaleStore(initial));\n const locale = useSignal(store);\n return [locale, store.set, store];\n}\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Translate as VanillaTranslate, type ParamPrimitive } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Translate` component. Mirrors the Svelte component's\n * props 1:1, with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface TranslateProps {\n /** Optional category under which tokens are registered. Helps translators disambiguate. */\n category?: string;\n /** Optional stable id for the content block. If omitted, the SDK hashes category + tokens. */\n custom_id?: string;\n /** Optional human-readable label shown in the Translation Manager. */\n label?: string;\n /**\n * Interpolation params. Write placeholders as `%key%` in the markup (a bare\n * `{key}` in JSX is a JS expression; the SDK normalizes `%key%` to canonical\n * `{key}` at capture). Applied to content-block text nodes, translatable\n * attributes, `<option>` text, and single-token content — including\n * untranslated fallbacks. Number/Date values get CLDR locale formatting.\n */\n params?: Record<string, ParamPrimitive>;\n /** Host element tag. Defaults to a `<translate>` custom element. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Translate` DOM class from\n * `langsys-js-typescript`. It renders a host element, then on mount lets the\n * vanilla class walk and tokenize the rendered children (text nodes plus\n * translatable attributes), register the content block, and re-translate on\n * locale change. On unmount it tears the instance down.\n *\n * This is the React analog of the Svelte `<Translate>` component — pure\n * mount/destroy glue. The DOM walking, content-block registration, attribute\n * harvesting, and re-translation lifecycle all live in the base SDK.\n *\n * Like the Svelte component, this lets the SDK mutate the rendered DOM in place,\n * so keep the children static: prose, marketing copy, CMS-rendered HTML — the\n * content-block use case. For dynamic per-string values that React owns and\n * re-renders, use `useT()` instead.\n */\nexport function Translate({\n category = '',\n custom_id = '',\n label = '',\n params,\n tag = 'translate',\n className,\n children,\n}: TranslateProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaTranslate>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaTranslate(host, { category, custom_id, label, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the identity props change; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category, custom_id, label]);\n\n // Re-render on changed params after mount, mirroring <Phrase>.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className }, children);\n}\n\nexport default Translate;\n","import { createElement, useEffect, useRef } from 'react';\nimport type { ReactNode } from 'react';\nimport { Phrase as VanillaPhrase } from 'langsys-js-typescript';\n\n/**\n * Props for the React `Phrase` component. Mirrors the Svelte component's props,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface PhraseProps {\n /** Category the phrase registers under (disambiguation for translators). */\n category?: string;\n /** Interpolation params. Write placeholders as `%n%` / `%name%` in the markup (a bare `{n}` in JSX is an expression). */\n params?: Record<string, unknown>;\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * React wrapper around the vanilla `Phrase` rich-text handler.\n *\n * Use it to keep a markup-bearing run as ONE translatable phrase — e.g. so a\n * count variable stays next to the noun it pluralizes:\n *\n * <Phrase category=\"ProductCard\" params={{ n: reviewCount }}>\n * Based on %n% <strong>reviews</strong>\n * </Phrase>\n *\n * Write placeholders as `%n%` (not bare `{n}`): in JSX a literal `{n}` is a JS\n * expression React evaluates before the SDK sees the text. `%n%` passes through\n * as literal text and the SDK normalizes it to canonical `{n}` at capture.\n *\n * The inline markup never reaches the translator — it's replaced with neutral\n * tokens and the real elements are reconstituted at render (see richtext.ts in\n * the base SDK). The host carries `data-ls-phrase` so a wrapping `<Translate>`\n * skips it and lets this handler own it.\n *\n * Keep children static (literal markup): the handler takes over the rendered\n * subtree. For values React owns and re-renders, pass them through `params`.\n */\nexport function Phrase({ category = '', params = {}, tag = 'span', className, children }: PhraseProps) {\n const hostRef = useRef<HTMLElement | null>(null);\n const instanceRef = useRef<VanillaPhrase>(undefined);\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) return;\n const instance = new VanillaPhrase(host, { category, params });\n instanceRef.current = instance;\n return () => {\n instance.destroy();\n instanceRef.current = undefined;\n };\n // Recreate only when the category changes; param changes flow through setParams below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [category]);\n\n // Re-render on a changed count/param after mount.\n useEffect(() => {\n instanceRef.current?.setParams(params);\n });\n\n return createElement(tag, { ref: hostRef, className, 'data-ls-phrase': '' }, children);\n}\n\nexport default Phrase;\n","import { createElement } from 'react';\nimport type { ReactNode } from 'react';\n\n/**\n * Props for the React `DontTranslate` component. Mirrors the Svelte component,\n * with Svelte's `class` renamed to React's idiomatic `className`.\n */\nexport interface DontTranslateProps {\n /** Host element tag. Defaults to `<span>`. */\n tag?: string;\n /** Class applied to the host element. */\n className?: string;\n children?: ReactNode;\n}\n\n/**\n * Marks a region as never-translated, preserved verbatim:\n *\n * Built with <DontTranslate>Kangen®</DontTranslate> on{' '}\n * <DontTranslate>langsys.dev</DontTranslate>\n *\n * The host carries the standard `translate=\"no\"` attribute, which both the\n * tokenizer and the renderer in the base SDK already honor — so its content is\n * never tokenized, registered, or replaced. Pure presentational glue; no\n * vanilla handler needed.\n */\nexport function DontTranslate({ tag = 'span', className, children }: DontTranslateProps) {\n return createElement(\n tag,\n { translate: 'no', 'data-ls-dont-translate': '', className },\n children,\n );\n}\n\nexport default DontTranslate;\n","/**\n * langsys-js-react — idiomatic React binding over `langsys-js-typescript`.\n *\n * Public API:\n * - `LangsysApp` — `init` accepts a `Signal<string>` (make one with\n * `createLocaleStore`) for the user locale; every other method delegates.\n * - Hooks — `useT`, `useCurrentLocale`, `useTranslations`, `useLocaleStore`,\n * and the low-level `useSignal`. These are the reactive layer; in components\n * prefer them over the raw signals.\n * - `createLocaleStore` — make the user-locale store (React analog of Svelte's\n * `writable`).\n * - `Translate` — React component wrapping the vanilla DOM `Translate` class.\n * - Raw signals `t` / `currentlyLoadedLocale` / `sTranslations` — re-exported\n * for advanced/direct subscription outside React's render cycle.\n */\n\nimport {\n LangsysApp as _LangsysApp,\n type ExtractParamKeys,\n type ParamPrimitive,\n type ParamsFor,\n type Signal,\n type TArgs,\n type TFunction,\n type TranslationParams,\n type iCategories,\n type iContentBlock,\n type iCountry,\n type iCountryDialCode,\n type iCountryList,\n type iCurrency,\n type iCurrencyList,\n type iLangsysInitConfig as iVanillaInitConfig,\n type iLangsysResponse,\n type iLanguageName,\n type iLocaleData,\n type iLocaleDefault,\n type iLocaleFlat,\n type iProject,\n type iTranslations,\n} from 'langsys-js-typescript';\n\n// Reactive primitives (raw signals) — re-exported for advanced/direct\n// subscription. `tSignal` is exposed under the friendlier name `t`. In\n// components, prefer the hooks (`useT`, `useCurrentLocale`, …).\nexport { currentlyLoadedLocale, createSignal, sTranslations, tSignal as t } from 'langsys-js-typescript';\n\n// Locale canonicalization (BCP 47) — the SDK canonicalizes all locale input\n// (v0.3.0+); re-exported so consumers can normalize their own values the same\n// way before comparing against `useCurrentLocale()` / `detectPreferredLocale()`.\nexport { canonicalizeLocale } from 'langsys-js-typescript';\n\n// API client (vanilla — no React concerns)\nexport { LangsysAppAPI } from 'langsys-js-typescript';\n\n// Hooks + adapters (the React-idiomatic reactive layer)\nexport { createLocaleStore, useSignal } from './adapters.js';\nexport { useCurrentLocale, useLocaleStore, useT, useTranslations } from './hooks.js';\n\n// Components\nexport { Translate, type TranslateProps } from './components/Translate.js';\nexport { Phrase, type PhraseProps } from './components/Phrase.js';\nexport { DontTranslate, type DontTranslateProps } from './components/DontTranslate.js';\n\n// Type re-exports — these are framework-agnostic, so consumers can rely on them\n// directly without reaching into `langsys-js-typescript`.\nexport type {\n ExtractParamKeys,\n ParamPrimitive,\n ParamsFor,\n Signal,\n TArgs,\n TFunction,\n TranslationParams,\n iCategories,\n iContentBlock,\n iCountry,\n iCountryDialCode,\n iCountryList,\n iCurrency,\n iCurrencyList,\n iLangsysResponse,\n iLanguageName,\n iLocaleData,\n iLocaleDefault,\n iLocaleFlat,\n iProject,\n iTranslations,\n};\n\n/**\n * React-flavored init config. Identical to the base SDK's config except\n * `UserLocaleStore` is typed as a `Signal<string>` — create one with\n * `createLocaleStore()` (or get one from the `useLocaleStore` hook). The base\n * SDK only reads and subscribes to it.\n */\nexport interface iLangsysInitConfig extends Omit<iVanillaInitConfig, 'UserLocaleStore'> {\n UserLocaleStore: Signal<string>;\n}\n\n/**\n * React SDK entry point. Delegates everything to the underlying\n * `langsys-js-typescript` singleton. Because React's locale store is already a\n * `Signal` (unlike Svelte's `Writable`, which needs adapting), `init` is a\n * straight passthrough — the React-native concerns live in the hooks and the\n * `<Translate>` component, not here.\n */\nclass LangsysAppReact {\n /** Initialize Langsys. Pass a `Signal<string>` (from `createLocaleStore`) as `UserLocaleStore`. */\n public init(config: iLangsysInitConfig): Promise<iLangsysResponse> {\n return _LangsysApp.init(config);\n }\n\n public get Translations() {\n return _LangsysApp.Translations;\n }\n\n public get translationsLoadingPromise() {\n return _LangsysApp.translationsLoadingPromise;\n }\n\n /** Current translation function. Reads fresh state on every call (not reactive on its own — use `useT()` in components). */\n public get t(): TFunction {\n return _LangsysApp.t;\n }\n\n public get debug() {\n return _LangsysApp.debug;\n }\n\n public refresh() {\n return _LangsysApp.refresh();\n }\n\n public getCountries(inLocale?: string) {\n return _LangsysApp.getCountries(inLocale);\n }\n public getCountryName(forCountryCode: string, inLocale?: string) {\n return _LangsysApp.getCountryName(forCountryCode, inLocale);\n }\n public getCurrencies(inLocale?: string) {\n return _LangsysApp.getCurrencies(inLocale);\n }\n public getCurrencyName(forCurrencyCode: string, inLocale?: string) {\n return _LangsysApp.getCurrencyName(forCurrencyCode, inLocale);\n }\n public getDialCodes(inLocale?: string) {\n return _LangsysApp.getDialCodes(inLocale);\n }\n\n public getLocales(inLocale?: string) {\n return _LangsysApp.getLocales(inLocale);\n }\n public getLocalesFlat(inLocale?: string) {\n return _LangsysApp.getLocalesFlat(inLocale);\n }\n public getLocalesData(inLocale?: string, forceRefresh?: boolean) {\n return _LangsysApp.getLocalesData(inLocale, forceRefresh);\n }\n public getLocalesFormat(format: '' | 'flat' | 'data' = '', inLocale?: string) {\n return _LangsysApp.getLocalesFormat(format, inLocale);\n }\n public getLocaleName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleName(forLocale, shortName, inLocale);\n }\n public getLocaleNameWithLookup(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLocaleNameWithLookup(forLocale, shortName, inLocale);\n }\n\n /** @deprecated use `getLocaleNameWithLookup` or `getLocaleName` */\n public getLanguageName(forLocale: string, shortName?: boolean, inLocale?: string) {\n return _LangsysApp.getLanguageName(forLocale, shortName, inLocale);\n }\n\n public detectPreferredLocale(acceptLanguageHeader?: string | null, supportedLocales?: string[]) {\n return _LangsysApp.detectPreferredLocale(acceptLanguageHeader, supportedLocales);\n }\n}\n\nexport const LangsysApp = new LangsysAppReact();\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsys-js-react",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "React binding for the Langsys translation SDK — realtime continuous translations with automatic token discovery, delivered as hooks and a <Translate> component.",
5
5
  "author": "Langsys",
6
6
  "license": "MIT",
@@ -61,7 +61,7 @@
61
61
  "react": "^18.0.0 || ^19.0.0"
62
62
  },
63
63
  "dependencies": {
64
- "langsys-js-typescript": "^0.4.0"
64
+ "langsys-js-typescript": "^0.4.1"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@eslint/js": "^9.0.0",