solid-translate 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9,8 +9,18 @@ interface SolidTranslatePluginConfig {
9
9
  targetLocales: string[];
10
10
  /** Directory containing locale JSON files, relative to project root (default: "./src/locales") */
11
11
  localesDir?: string;
12
- /** AI model to use for translations (any Vercel AI SDK LanguageModelV1) */
13
- model: LanguageModelV1;
12
+ /**
13
+ * AI model to use for translations (any Vercel AI SDK LanguageModelV1).
14
+ * Required unless `translate` is `false`.
15
+ */
16
+ model?: LanguageModelV1;
17
+ /**
18
+ * When `false`, the plugin only serves the virtual translation modules
19
+ * from the committed locale JSON files — no extraction, no AI calls, no
20
+ * file writes. Use this to keep app builds hermetic and run extraction/
21
+ * translation exclusively through the CLI (e.g. in CI). Default: `true`.
22
+ */
23
+ translate?: boolean;
14
24
  /** Custom system prompt for the AI translator */
15
25
  systemPrompt?: string;
16
26
  /** Max keys per API call (default: 50) */
@@ -142,11 +152,15 @@ interface PluralProps {
142
152
  /**
143
153
  * Renders the appropriate plural form based on CLDR plural rules for the current locale.
144
154
  *
155
+ * String forms are translated through the translation dictionary (the source
156
+ * string is the key — matching extraction) and support an `{n}` placeholder
157
+ * interpolated with the count. Non-string forms render as-is, untranslated.
158
+ *
145
159
  * ```tsx
146
160
  * <Plural n={count()}
147
161
  * zero="No items"
148
162
  * one="1 item"
149
- * other={`${count()} items`}
163
+ * other="{n} items"
150
164
  * />
151
165
  * ```
152
166
  */
package/dist/index.js CHANGED
@@ -3,8 +3,7 @@ import {
3
3
  createComponent,
4
4
  useContext as useContext2,
5
5
  createSignal,
6
- createMemo as createMemo2,
7
- children as resolveChildren
6
+ createMemo as createMemo2
8
7
  } from "solid-js";
9
8
 
10
9
  // src/context.ts
@@ -84,7 +83,11 @@ function Plural(props) {
84
83
  many: props.many,
85
84
  other: props.other
86
85
  };
87
- return forms[category] ?? props.other;
86
+ const form = forms[category] ?? props.other;
87
+ if (ctx && typeof form === "string") {
88
+ return ctx.t(form, { n: props.n });
89
+ }
90
+ return form;
88
91
  });
89
92
  }
90
93
  function LocaleSelector(props) {
@@ -228,25 +231,9 @@ function useLocale() {
228
231
  }
229
232
  function T(props) {
230
233
  const ctx = useContext2(TranslationContext);
231
- const resolved = resolveChildren(() => props.children);
232
234
  return createMemo2(() => {
233
- const kids = resolved.toArray();
235
+ const kids = flattenChildren(props.children);
234
236
  if (!ctx) return kids.length === 1 ? kids[0] : kids;
235
- if (kids.length === 1 && typeof kids[0] === "string") {
236
- const key = props.id || kids[0];
237
- return ctx.t(key, props.params);
238
- }
239
- if (props.id) {
240
- const translated2 = ctx.t(props.id, props.params);
241
- if (!/{(\d+)}/.test(translated2)) return translated2;
242
- const slots2 = [];
243
- for (const kid of kids) {
244
- if (typeof kid !== "string" && typeof kid !== "number") {
245
- slots2.push(kid);
246
- }
247
- }
248
- return interpolateSlots(translated2, slots2);
249
- }
250
237
  const slots = [];
251
238
  let template = "";
252
239
  for (const kid of kids) {
@@ -254,16 +241,30 @@ function T(props) {
254
241
  template += kid;
255
242
  } else if (typeof kid === "number") {
256
243
  template += String(kid);
244
+ } else if (kid == null || typeof kid === "boolean") {
257
245
  } else {
258
246
  template += `{${slots.length}}`;
259
247
  slots.push(kid);
260
248
  }
261
249
  }
262
- const translated = ctx.t(template, props.params);
263
- if (slots.length === 0) return translated;
250
+ const lead = /^\s*/.exec(template)[0];
251
+ const rest = template.slice(lead.length);
252
+ const trail = /\s*$/.exec(rest)[0];
253
+ const body = rest.slice(0, rest.length - trail.length);
254
+ const key = props.id || body;
255
+ const translated = lead + ctx.t(key, props.params) + trail;
256
+ if (slots.length === 0 || !/{(\d+)}/.test(translated)) return translated;
264
257
  return interpolateSlots(translated, slots);
265
258
  });
266
259
  }
260
+ function flattenChildren(child, out = []) {
261
+ if (Array.isArray(child)) {
262
+ for (const c of child) flattenChildren(c, out);
263
+ } else {
264
+ out.push(child);
265
+ }
266
+ return out;
267
+ }
267
268
  function interpolateSlots(text, slots) {
268
269
  const parts = text.split(/\{(\d+)\}/);
269
270
  const result = [];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/context.ts","../src/locale-detect.ts","../src/components.tsx","../src/msg.ts"],"sourcesContent":["import {\n createComponent,\n useContext,\n createSignal,\n createMemo,\n children as resolveChildren,\n type JSX,\n} from \"solid-js\";\nimport {\n TranslationContext,\n type TranslationContextValue,\n} from \"./context.js\";\nimport { detectLocale } from \"./locale-detect.js\";\nimport type {\n LazyTranslations,\n TranslationDictionary,\n Translations,\n TranslationsInput,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type { TranslationContextValue } from \"./context.js\";\nexport type {\n LazyTranslations,\n TranslationDictionary,\n Translations,\n TranslationsInput,\n} from \"./types.js\";\nexport type { SolidTranslatePluginConfig } from \"./types.js\";\nexport { Var, Num, Currency, DateTime, Plural, LocaleSelector } from \"./components.js\";\nexport type {\n VarProps,\n NumProps,\n CurrencyProps,\n DateTimeProps,\n PluralProps,\n LocaleSelectorProps,\n} from \"./components.js\";\nexport { msg } from \"./msg.js\";\nexport { detectLocale } from \"./locale-detect.js\";\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface TranslationProviderProps {\n /**\n * Initial locale. If omitted, auto-detects from the browser's\n * `navigator.languages` header, falling back to `sourceLocale`.\n */\n locale?: string;\n /** Source locale code (default: \"en\") */\n sourceLocale?: string;\n /**\n * Translation dictionaries keyed by locale (from `virtual:solid-translate`),\n * or a lazy manifest (from `virtual:solid-translate/lazy`) whose per-locale\n * dictionaries are loaded on demand via dynamic import.\n */\n translations: TranslationsInput;\n /**\n * Persist the active locale to `localStorage` (default: false).\n * When enabled, the initial locale is read from storage (if still valid)\n * before falling back to browser detection, and `setLocale` writes through.\n * Pass `{ key: \"...\" }` to customize the storage key.\n */\n persistLocale?: boolean | { key?: string };\n children: JSX.Element;\n}\n\nconst DEFAULT_PERSIST_KEY = \"solid-translate:locale\";\n\nfunction isLazyTranslations(\n input: TranslationsInput,\n): input is LazyTranslations {\n return (\n typeof input === \"object\" &&\n input !== null &&\n Array.isArray((input as LazyTranslations).locales) &&\n typeof (input as LazyTranslations).loaders === \"object\" &&\n (input as LazyTranslations).loaders !== null\n );\n}\n\nfunction readPersistedLocale(key: string): string | undefined {\n try {\n if (typeof localStorage === \"undefined\") return undefined;\n return localStorage.getItem(key) ?? undefined;\n } catch {\n // SSR / storage disabled\n return undefined;\n }\n}\n\nfunction writePersistedLocale(key: string, locale: string): void {\n try {\n if (typeof localStorage === \"undefined\") return;\n localStorage.setItem(key, locale);\n } catch {\n // SSR / storage disabled / quota exceeded — ignore\n }\n}\n\nexport function TranslationProvider(props: TranslationProviderProps) {\n const lazy = isLazyTranslations(props.translations)\n ? props.translations\n : undefined;\n const sourceLocale = props.sourceLocale || lazy?.sourceLocale || \"en\";\n const availableLocales = createMemo(() =>\n lazy ? lazy.locales : Object.keys(props.translations),\n );\n\n const persistKey = props.persistLocale\n ? (typeof props.persistLocale === \"object\"\n ? props.persistLocale.key\n : undefined) || DEFAULT_PERSIST_KEY\n : undefined;\n\n // Initial locale: explicit prop > persisted value (if valid) > detection\n const persisted = persistKey ? readPersistedLocale(persistKey) : undefined;\n const persistedValid =\n persisted !== undefined &&\n (persisted === sourceLocale || availableLocales().includes(persisted));\n const initialLocale =\n props.locale ||\n (persistedValid ? persisted : undefined) ||\n detectLocale(availableLocales()) ||\n sourceLocale;\n const [locale, setLocaleSignal] = createSignal(initialLocale);\n\n // Lazily loaded dictionaries, keyed by locale (lazy manifest mode only).\n // Loading NEVER throws or suspends — while a dictionary is in flight,\n // t() falls back to the source text.\n const [loadedDicts, setLoadedDicts] = createSignal<Translations>({});\n const pendingLoads = new Set<string>();\n\n const loadLocale = (target: string): void => {\n if (!lazy) return;\n const loader = lazy.loaders[target];\n if (!loader) return;\n if (target in loadedDicts() || pendingLoads.has(target)) return;\n pendingLoads.add(target);\n loader()\n .then((dict) => {\n setLoadedDicts((prev) => ({ ...prev, [target]: dict }));\n })\n .catch((err) => {\n console.warn(\n `[solid-translate] Failed to load locale \"${target}\":`,\n err,\n );\n })\n .finally(() => {\n pendingLoads.delete(target);\n });\n };\n\n const setLocale = (next: string): void => {\n loadLocale(next);\n setLocaleSignal(next);\n if (persistKey) writePersistedLocale(persistKey, next);\n };\n\n // Kick off loading for the initial locale (no-op in eager mode, or when\n // the locale has no loader — e.g. the source locale without a dict).\n loadLocale(initialLocale);\n\n const t = (\n key: string,\n params?: Record<string, string | number>,\n ): string => {\n const cur = locale();\n let text = key;\n\n // Look up in translation dictionary (works for both source and target locales)\n const dict = lazy\n ? loadedDicts()[cur]\n : (props.translations as Translations)[cur];\n if (dict && key in dict) {\n text = dict[key]!;\n }\n\n // Interpolate {{variable}} and {variable} placeholders\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n text = text.replace(\n new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}|\\\\{${k}\\\\}`, \"g\"),\n String(v),\n );\n }\n }\n\n return text;\n };\n\n const value: TranslationContextValue = {\n locale,\n setLocale,\n t,\n sourceLocale,\n availableLocales,\n translations: props.translations,\n };\n\n return createComponent(TranslationContext.Provider, {\n value,\n get children() {\n return props.children;\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/** Access the full translation context. Must be inside a TranslationProvider. */\nexport function useTranslation(): TranslationContextValue {\n const ctx = useContext(TranslationContext);\n if (!ctx) {\n throw new Error(\n \"useTranslation() must be used within a <TranslationProvider>\",\n );\n }\n return ctx;\n}\n\n/** Access just the current locale and setter. */\nexport function useLocale(): {\n locale: () => string;\n setLocale: (locale: string) => void;\n sourceLocale: string;\n availableLocales: () => string[];\n} {\n const ctx = useTranslation();\n return {\n locale: ctx.locale,\n setLocale: ctx.setLocale,\n sourceLocale: ctx.sourceLocale,\n availableLocales: ctx.availableLocales,\n };\n}\n\n// ---------------------------------------------------------------------------\n// <T> Component\n// ---------------------------------------------------------------------------\n\nexport interface TProps {\n /** Explicit translation key. If omitted, children text is used as the key. */\n id?: string;\n /** Interpolation parameters */\n params?: Record<string, string | number>;\n /**\n * AI context hint — tells the AI translator about the meaning of this text.\n * Only used at build time for disambiguation; has no runtime effect.\n *\n * ```tsx\n * <T context=\"Button to save a document, not save money\">Save</T>\n * ```\n */\n context?: string;\n /** Source text / JSX content */\n children?: JSX.Element;\n}\n\n/**\n * Translatable content component.\n *\n * ```tsx\n * <T>Hello world</T>\n * <T id=\"greeting\" params={{ name: \"Alice\" }}>Hello {{name}}</T>\n * <T context=\"the physical bank\">Bank</T>\n * <T>Welcome <Var>{userName()}</Var>, you have <Num>{count()}</Num> items</T>\n * ```\n */\nexport function T(props: TProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n const resolved = resolveChildren(() => props.children);\n\n return createMemo(() => {\n const kids = resolved.toArray();\n\n // No context — just render children\n if (!ctx) return kids.length === 1 ? kids[0] : kids;\n\n // Simple case: single text child\n if (kids.length === 1 && typeof kids[0] === \"string\") {\n const key = props.id || (kids[0] as string);\n return ctx.t(key, props.params);\n }\n\n // Explicit id with non-text children — translate via id\n if (props.id) {\n const translated = ctx.t(props.id, props.params);\n\n // If translation is just text (no slot placeholders), return it\n if (!/{(\\d+)}/.test(translated)) return translated;\n\n // Collect non-text children (Var, Num, etc.) as ordered slots\n const slots: JSX.Element[] = [];\n for (const kid of kids) {\n if (typeof kid !== \"string\" && typeof kid !== \"number\") {\n slots.push(kid as JSX.Element);\n }\n }\n\n return interpolateSlots(translated, slots);\n }\n\n // Mixed children without explicit id — build a template key\n const slots: JSX.Element[] = [];\n let template = \"\";\n for (const kid of kids) {\n if (typeof kid === \"string\") {\n template += kid;\n } else if (typeof kid === \"number\") {\n template += String(kid);\n } else {\n template += `{${slots.length}}`;\n slots.push(kid as JSX.Element);\n }\n }\n\n const translated = ctx.t(template, props.params);\n if (slots.length === 0) return translated;\n return interpolateSlots(translated, slots);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Split a translated string by `{0}`, `{1}`, etc. and interleave with slots */\nfunction interpolateSlots(\n text: string,\n slots: JSX.Element[],\n): (string | JSX.Element)[] {\n const parts = text.split(/\\{(\\d+)\\}/);\n const result: (string | JSX.Element)[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n if (parts[i]) result.push(parts[i]!);\n } else {\n const idx = parseInt(parts[i]!, 10);\n if (slots[idx] !== undefined) result.push(slots[idx]!);\n }\n }\n return result;\n}\n","import { createContext } from \"solid-js\";\nimport type { TranslationsInput } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Context value type\n// ---------------------------------------------------------------------------\n\nexport interface TranslationContextValue {\n /** Current locale as a reactive signal */\n locale: () => string;\n /** Switch to a different locale */\n setLocale: (locale: string) => void;\n /** Translate a key with optional interpolation params */\n t: (key: string, params?: Record<string, string | number>) => string;\n /** The source locale code */\n sourceLocale: string;\n /** All available locale codes (reactive) */\n availableLocales: () => string[];\n /** Raw translations object (eager record or lazy manifest) */\n translations: TranslationsInput;\n}\n\n// ---------------------------------------------------------------------------\n// Shared context instance\n// ---------------------------------------------------------------------------\n\nexport const TranslationContext = createContext<TranslationContextValue>();\n","/**\n * Detect the user's preferred locale from browser settings.\n *\n * Checks `navigator.languages` (and falls back to `navigator.language`)\n * then matches against the list of available locales. Tries exact match\n * first, then language-only match (e.g. \"en-US\" → \"en\").\n */\nexport function detectLocale(availableLocales?: string[]): string {\n if (typeof navigator === \"undefined\") return \"en\";\n\n const browserLocales = navigator.languages\n ? [...navigator.languages]\n : [navigator.language || \"en\"];\n\n if (!availableLocales || availableLocales.length === 0) {\n return normalizeLocale(browserLocales[0] || \"en\");\n }\n\n // Map normalized available locales back to their canonical casing so a\n // browser \"pt-br\" can match an available \"pt-BR\" (and return \"pt-BR\").\n const canonical = new Map<string, string>();\n for (const al of availableLocales) {\n const normalized = normalizeLocale(al);\n if (!canonical.has(normalized)) canonical.set(normalized, al);\n }\n\n // Exact match (case-insensitive)\n for (const bl of browserLocales) {\n const match = canonical.get(normalizeLocale(bl));\n if (match) return match;\n }\n\n // Language-only match (e.g. \"en-US\" → \"en\")\n for (const bl of browserLocales) {\n const lang = normalizeLocale(bl).split(\"-\")[0]!;\n const match = canonical.get(lang);\n if (match) return match;\n }\n\n return availableLocales[0] || \"en\";\n}\n\nfunction normalizeLocale(locale: string): string {\n return locale.toLowerCase().replace(\"_\", \"-\");\n}\n","import { useContext, type JSX, For, createMemo } from \"solid-js\";\nimport { TranslationContext } from \"./context.js\";\n\n// ---------------------------------------------------------------------------\n// <Var> — protect dynamic content from translation\n// ---------------------------------------------------------------------------\n\nexport interface VarProps {\n /** Optional name for the variable (used as placeholder in templates) */\n name?: string;\n children: JSX.Element;\n}\n\n/**\n * Marks content as untranslatable. When used inside `<T>`, the content\n * is preserved as-is while surrounding text is translated.\n *\n * ```tsx\n * <T>Hello <Var>{userName()}</Var>, welcome!</T>\n * ```\n */\nexport function Var(props: VarProps): JSX.Element {\n return (() => props.children) as unknown as JSX.Element;\n}\n\n// Mark Var for identification by T component\n(Var as any).__st_var = true;\n\n// ---------------------------------------------------------------------------\n// <Num> — locale-aware number formatting\n// ---------------------------------------------------------------------------\n\nexport interface NumProps {\n /** The number to format */\n children: number;\n /** Intl.NumberFormat options */\n options?: Intl.NumberFormatOptions;\n}\n\n/**\n * Formats a number according to the current locale using `Intl.NumberFormat`.\n *\n * ```tsx\n * <Num>{1000000}</Num> // \"1,000,000\" in en, \"1.000.000\" in de\n * <Num options={{ style: \"percent\" }}>{0.42}</Num> // \"42%\"\n * ```\n */\nexport function Num(props: NumProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n return new Intl.NumberFormat(locale, props.options).format(props.children);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <Currency> — locale-aware currency formatting\n// ---------------------------------------------------------------------------\n\nexport interface CurrencyProps {\n /** The numeric value */\n children: number;\n /** ISO 4217 currency code (e.g. \"USD\", \"EUR\") */\n currency: string;\n /** Additional Intl.NumberFormat options */\n options?: Intl.NumberFormatOptions;\n}\n\n/**\n * Formats a number as currency according to the current locale.\n *\n * ```tsx\n * <Currency currency=\"USD\">{29.99}</Currency> // \"$29.99\" in en-US\n * <Currency currency=\"EUR\">{29.99}</Currency> // \"29,99 €\" in de\n * ```\n */\nexport function Currency(props: CurrencyProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n return new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency: props.currency,\n ...props.options,\n }).format(props.children);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <DateTime> — locale-aware date/time formatting\n// ---------------------------------------------------------------------------\n\nexport interface DateTimeProps {\n /** The date to format (Date object, timestamp, or ISO string) */\n children: Date | number | string;\n /** Intl.DateTimeFormat options */\n options?: Intl.DateTimeFormatOptions;\n}\n\n/**\n * Formats a date/time according to the current locale using `Intl.DateTimeFormat`.\n *\n * ```tsx\n * <DateTime>{new Date()}</DateTime>\n * <DateTime options={{ dateStyle: \"long\" }}>{new Date()}</DateTime>\n * ```\n */\nexport function DateTime(props: DateTimeProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n const date =\n props.children instanceof Date\n ? props.children\n : new Date(props.children);\n return new Intl.DateTimeFormat(locale, props.options).format(date);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <Plural> — CLDR plural rules\n// ---------------------------------------------------------------------------\n\nexport interface PluralProps {\n /** The count value to determine which plural form to use */\n n: number;\n /** Form for zero items */\n zero?: JSX.Element;\n /** Form for exactly one item */\n one?: JSX.Element;\n /** Form for exactly two items */\n two?: JSX.Element;\n /** Form for \"few\" items (language-dependent) */\n few?: JSX.Element;\n /** Form for \"many\" items (language-dependent) */\n many?: JSX.Element;\n /** Default/fallback form */\n other: JSX.Element;\n}\n\n/**\n * Renders the appropriate plural form based on CLDR plural rules for the current locale.\n *\n * ```tsx\n * <Plural n={count()}\n * zero=\"No items\"\n * one=\"1 item\"\n * other={`${count()} items`}\n * />\n * ```\n */\nexport function Plural(props: PluralProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n const rules = new Intl.PluralRules(locale);\n const category = rules.select(props.n);\n\n const forms: Record<string, JSX.Element | undefined> = {\n zero: props.zero,\n one: props.one,\n two: props.two,\n few: props.few,\n many: props.many,\n other: props.other,\n };\n\n return forms[category] ?? props.other;\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <LocaleSelector> — drop-in locale picker\n// ---------------------------------------------------------------------------\n\nexport interface LocaleSelectorProps {\n /** Override which locales to show (defaults to all available) */\n locales?: string[];\n /** Map locale codes to display names, e.g. { en: \"English\", es: \"Español\" } */\n labels?: Record<string, string>;\n /** Additional CSS class */\n class?: string;\n}\n\n/**\n * A ready-to-use locale selector dropdown.\n *\n * ```tsx\n * <LocaleSelector labels={{ en: \"English\", es: \"Español\", fr: \"Français\" }} />\n * ```\n */\nexport function LocaleSelector(props: LocaleSelectorProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n if (!ctx) {\n throw new Error(\n \"<LocaleSelector> must be used within a <TranslationProvider>\",\n );\n }\n\n const locales = createMemo(() => props.locales || ctx.availableLocales());\n\n const displayName = (code: string): string => {\n if (props.labels?.[code]) return props.labels[code]!;\n try {\n const dn = new Intl.DisplayNames([code], { type: \"language\" });\n return dn.of(code) || code;\n } catch {\n return code;\n }\n };\n\n return (\n <select\n class={props.class}\n value={ctx.locale()}\n onChange={(e) => ctx.setLocale(e.currentTarget.value)}\n >\n <For each={locales()}>\n {(code) => <option value={code}>{displayName(code)}</option>}\n </For>\n </select>\n ) as JSX.Element;\n}\n","/**\n * Mark a string for translation extraction.\n *\n * At build time, the Vite plugin and CLI scan for `msg()` calls and add\n * the strings to the source locale file for AI translation.\n *\n * At runtime, `msg()` is a no-op — it returns the source text as-is.\n * Use `t()` from `useTranslation()` for runtime translation.\n *\n * ```ts\n * // Marks \"Save changes\" for extraction\n * const label = msg(\"Save changes\");\n *\n * // With interpolation template\n * const greeting = msg(\"Hello {{name}}\", { name: \"World\" });\n *\n * // In a component, translate at runtime:\n * const { t } = useTranslation();\n * <button>{t(label)}</button>\n * ```\n */\nexport function msg(\n text: string,\n _params?: Record<string, string | number>,\n): string {\n return text;\n}\n"],"mappings":"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","names":["useContext","createMemo","createMemo","useContext","translated","slots"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/context.ts","../src/locale-detect.ts","../src/components.tsx","../src/msg.ts"],"sourcesContent":["import {\n createComponent,\n useContext,\n createSignal,\n createMemo,\n type JSX,\n} from \"solid-js\";\nimport {\n TranslationContext,\n type TranslationContextValue,\n} from \"./context.js\";\nimport { detectLocale } from \"./locale-detect.js\";\nimport type {\n LazyTranslations,\n TranslationDictionary,\n Translations,\n TranslationsInput,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport type { TranslationContextValue } from \"./context.js\";\nexport type {\n LazyTranslations,\n TranslationDictionary,\n Translations,\n TranslationsInput,\n} from \"./types.js\";\nexport type { SolidTranslatePluginConfig } from \"./types.js\";\nexport { Var, Num, Currency, DateTime, Plural, LocaleSelector } from \"./components.js\";\nexport type {\n VarProps,\n NumProps,\n CurrencyProps,\n DateTimeProps,\n PluralProps,\n LocaleSelectorProps,\n} from \"./components.js\";\nexport { msg } from \"./msg.js\";\nexport { detectLocale } from \"./locale-detect.js\";\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\nexport interface TranslationProviderProps {\n /**\n * Initial locale. If omitted, auto-detects from the browser's\n * `navigator.languages` header, falling back to `sourceLocale`.\n */\n locale?: string;\n /** Source locale code (default: \"en\") */\n sourceLocale?: string;\n /**\n * Translation dictionaries keyed by locale (from `virtual:solid-translate`),\n * or a lazy manifest (from `virtual:solid-translate/lazy`) whose per-locale\n * dictionaries are loaded on demand via dynamic import.\n */\n translations: TranslationsInput;\n /**\n * Persist the active locale to `localStorage` (default: false).\n * When enabled, the initial locale is read from storage (if still valid)\n * before falling back to browser detection, and `setLocale` writes through.\n * Pass `{ key: \"...\" }` to customize the storage key.\n */\n persistLocale?: boolean | { key?: string };\n children: JSX.Element;\n}\n\nconst DEFAULT_PERSIST_KEY = \"solid-translate:locale\";\n\nfunction isLazyTranslations(\n input: TranslationsInput,\n): input is LazyTranslations {\n return (\n typeof input === \"object\" &&\n input !== null &&\n Array.isArray((input as LazyTranslations).locales) &&\n typeof (input as LazyTranslations).loaders === \"object\" &&\n (input as LazyTranslations).loaders !== null\n );\n}\n\nfunction readPersistedLocale(key: string): string | undefined {\n try {\n if (typeof localStorage === \"undefined\") return undefined;\n return localStorage.getItem(key) ?? undefined;\n } catch {\n // SSR / storage disabled\n return undefined;\n }\n}\n\nfunction writePersistedLocale(key: string, locale: string): void {\n try {\n if (typeof localStorage === \"undefined\") return;\n localStorage.setItem(key, locale);\n } catch {\n // SSR / storage disabled / quota exceeded — ignore\n }\n}\n\nexport function TranslationProvider(props: TranslationProviderProps) {\n const lazy = isLazyTranslations(props.translations)\n ? props.translations\n : undefined;\n const sourceLocale = props.sourceLocale || lazy?.sourceLocale || \"en\";\n const availableLocales = createMemo(() =>\n lazy ? lazy.locales : Object.keys(props.translations),\n );\n\n const persistKey = props.persistLocale\n ? (typeof props.persistLocale === \"object\"\n ? props.persistLocale.key\n : undefined) || DEFAULT_PERSIST_KEY\n : undefined;\n\n // Initial locale: explicit prop > persisted value (if valid) > detection\n const persisted = persistKey ? readPersistedLocale(persistKey) : undefined;\n const persistedValid =\n persisted !== undefined &&\n (persisted === sourceLocale || availableLocales().includes(persisted));\n const initialLocale =\n props.locale ||\n (persistedValid ? persisted : undefined) ||\n detectLocale(availableLocales()) ||\n sourceLocale;\n const [locale, setLocaleSignal] = createSignal(initialLocale);\n\n // Lazily loaded dictionaries, keyed by locale (lazy manifest mode only).\n // Loading NEVER throws or suspends — while a dictionary is in flight,\n // t() falls back to the source text.\n const [loadedDicts, setLoadedDicts] = createSignal<Translations>({});\n const pendingLoads = new Set<string>();\n\n const loadLocale = (target: string): void => {\n if (!lazy) return;\n const loader = lazy.loaders[target];\n if (!loader) return;\n if (target in loadedDicts() || pendingLoads.has(target)) return;\n pendingLoads.add(target);\n loader()\n .then((dict) => {\n setLoadedDicts((prev) => ({ ...prev, [target]: dict }));\n })\n .catch((err) => {\n console.warn(\n `[solid-translate] Failed to load locale \"${target}\":`,\n err,\n );\n })\n .finally(() => {\n pendingLoads.delete(target);\n });\n };\n\n const setLocale = (next: string): void => {\n loadLocale(next);\n setLocaleSignal(next);\n if (persistKey) writePersistedLocale(persistKey, next);\n };\n\n // Kick off loading for the initial locale (no-op in eager mode, or when\n // the locale has no loader — e.g. the source locale without a dict).\n loadLocale(initialLocale);\n\n const t = (\n key: string,\n params?: Record<string, string | number>,\n ): string => {\n const cur = locale();\n let text = key;\n\n // Look up in translation dictionary (works for both source and target locales)\n const dict = lazy\n ? loadedDicts()[cur]\n : (props.translations as Translations)[cur];\n if (dict && key in dict) {\n text = dict[key]!;\n }\n\n // Interpolate {{variable}} and {variable} placeholders\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n text = text.replace(\n new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}|\\\\{${k}\\\\}`, \"g\"),\n String(v),\n );\n }\n }\n\n return text;\n };\n\n const value: TranslationContextValue = {\n locale,\n setLocale,\n t,\n sourceLocale,\n availableLocales,\n translations: props.translations,\n };\n\n return createComponent(TranslationContext.Provider, {\n value,\n get children() {\n return props.children;\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Hooks\n// ---------------------------------------------------------------------------\n\n/** Access the full translation context. Must be inside a TranslationProvider. */\nexport function useTranslation(): TranslationContextValue {\n const ctx = useContext(TranslationContext);\n if (!ctx) {\n throw new Error(\n \"useTranslation() must be used within a <TranslationProvider>\",\n );\n }\n return ctx;\n}\n\n/** Access just the current locale and setter. */\nexport function useLocale(): {\n locale: () => string;\n setLocale: (locale: string) => void;\n sourceLocale: string;\n availableLocales: () => string[];\n} {\n const ctx = useTranslation();\n return {\n locale: ctx.locale,\n setLocale: ctx.setLocale,\n sourceLocale: ctx.sourceLocale,\n availableLocales: ctx.availableLocales,\n };\n}\n\n// ---------------------------------------------------------------------------\n// <T> Component\n// ---------------------------------------------------------------------------\n\nexport interface TProps {\n /** Explicit translation key. If omitted, children text is used as the key. */\n id?: string;\n /** Interpolation parameters */\n params?: Record<string, string | number>;\n /**\n * AI context hint — tells the AI translator about the meaning of this text.\n * Only used at build time for disambiguation; has no runtime effect.\n *\n * ```tsx\n * <T context=\"Button to save a document, not save money\">Save</T>\n * ```\n */\n context?: string;\n /** Source text / JSX content */\n children?: JSX.Element;\n}\n\n/**\n * Translatable content component.\n *\n * ```tsx\n * <T>Hello world</T>\n * <T id=\"greeting\" params={{ name: \"Alice\" }}>Hello {{name}}</T>\n * <T context=\"the physical bank\">Bank</T>\n * <T>Welcome <Var>{userName()}</Var>, you have <Num>{count()}</Num> items</T>\n * ```\n */\nexport function T(props: TProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n // IMPORTANT: children are read raw, WITHOUT resolveChildren(). The Solid\n // compiler passes static text as plain strings and wraps every dynamic\n // part (expressions, <Var>, <Num>, elements) in a function or object —\n // that boundary is exactly what separates translatable text from {n}\n // slots. Resolving children first would collapse dynamic strings into\n // text and destroy the key.\n const kids = flattenChildren(props.children);\n\n // No context — just render children\n if (!ctx) return kids.length === 1 ? kids[0] : kids;\n\n // Build the template key + ordered slots from the raw children\n const slots: JSX.Element[] = [];\n let template = \"\";\n for (const kid of kids) {\n if (typeof kid === \"string\") {\n template += kid;\n } else if (typeof kid === \"number\") {\n template += String(kid);\n } else if (kid == null || typeof kid === \"boolean\") {\n // {null} / {undefined} / booleans render nothing\n } else {\n template += `{${slots.length}}`;\n slots.push(kid as JSX.Element);\n }\n }\n\n // Leading/trailing whitespace is layout, not copy — keep it out of the\n // key, restore it around the translation.\n const lead = /^\\s*/.exec(template)![0];\n const rest = template.slice(lead.length);\n const trail = /\\s*$/.exec(rest)![0];\n const body = rest.slice(0, rest.length - trail.length);\n\n const key = props.id || body;\n const translated = lead + ctx.t(key, props.params) + trail;\n\n // If translation has no slot placeholders, return it as plain text\n if (slots.length === 0 || !/{(\\d+)}/.test(translated)) return translated;\n\n return interpolateSlots(translated, slots);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Flatten (possibly nested) children arrays WITHOUT resolving functions */\nfunction flattenChildren(child: unknown, out: unknown[] = []): unknown[] {\n if (Array.isArray(child)) {\n for (const c of child) flattenChildren(c, out);\n } else {\n out.push(child);\n }\n return out;\n}\n\n/** Split a translated string by `{0}`, `{1}`, etc. and interleave with slots */\nfunction interpolateSlots(\n text: string,\n slots: JSX.Element[],\n): (string | JSX.Element)[] {\n const parts = text.split(/\\{(\\d+)\\}/);\n const result: (string | JSX.Element)[] = [];\n for (let i = 0; i < parts.length; i++) {\n if (i % 2 === 0) {\n if (parts[i]) result.push(parts[i]!);\n } else {\n const idx = parseInt(parts[i]!, 10);\n if (slots[idx] !== undefined) result.push(slots[idx]!);\n }\n }\n return result;\n}\n","import { createContext } from \"solid-js\";\nimport type { TranslationsInput } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Context value type\n// ---------------------------------------------------------------------------\n\nexport interface TranslationContextValue {\n /** Current locale as a reactive signal */\n locale: () => string;\n /** Switch to a different locale */\n setLocale: (locale: string) => void;\n /** Translate a key with optional interpolation params */\n t: (key: string, params?: Record<string, string | number>) => string;\n /** The source locale code */\n sourceLocale: string;\n /** All available locale codes (reactive) */\n availableLocales: () => string[];\n /** Raw translations object (eager record or lazy manifest) */\n translations: TranslationsInput;\n}\n\n// ---------------------------------------------------------------------------\n// Shared context instance\n// ---------------------------------------------------------------------------\n\nexport const TranslationContext = createContext<TranslationContextValue>();\n","/**\n * Detect the user's preferred locale from browser settings.\n *\n * Checks `navigator.languages` (and falls back to `navigator.language`)\n * then matches against the list of available locales. Tries exact match\n * first, then language-only match (e.g. \"en-US\" → \"en\").\n */\nexport function detectLocale(availableLocales?: string[]): string {\n if (typeof navigator === \"undefined\") return \"en\";\n\n const browserLocales = navigator.languages\n ? [...navigator.languages]\n : [navigator.language || \"en\"];\n\n if (!availableLocales || availableLocales.length === 0) {\n return normalizeLocale(browserLocales[0] || \"en\");\n }\n\n // Map normalized available locales back to their canonical casing so a\n // browser \"pt-br\" can match an available \"pt-BR\" (and return \"pt-BR\").\n const canonical = new Map<string, string>();\n for (const al of availableLocales) {\n const normalized = normalizeLocale(al);\n if (!canonical.has(normalized)) canonical.set(normalized, al);\n }\n\n // Exact match (case-insensitive)\n for (const bl of browserLocales) {\n const match = canonical.get(normalizeLocale(bl));\n if (match) return match;\n }\n\n // Language-only match (e.g. \"en-US\" → \"en\")\n for (const bl of browserLocales) {\n const lang = normalizeLocale(bl).split(\"-\")[0]!;\n const match = canonical.get(lang);\n if (match) return match;\n }\n\n return availableLocales[0] || \"en\";\n}\n\nfunction normalizeLocale(locale: string): string {\n return locale.toLowerCase().replace(\"_\", \"-\");\n}\n","import { useContext, type JSX, For, createMemo } from \"solid-js\";\nimport { TranslationContext } from \"./context.js\";\n\n// ---------------------------------------------------------------------------\n// <Var> — protect dynamic content from translation\n// ---------------------------------------------------------------------------\n\nexport interface VarProps {\n /** Optional name for the variable (used as placeholder in templates) */\n name?: string;\n children: JSX.Element;\n}\n\n/**\n * Marks content as untranslatable. When used inside `<T>`, the content\n * is preserved as-is while surrounding text is translated.\n *\n * ```tsx\n * <T>Hello <Var>{userName()}</Var>, welcome!</T>\n * ```\n */\nexport function Var(props: VarProps): JSX.Element {\n return (() => props.children) as unknown as JSX.Element;\n}\n\n// Mark Var for identification by T component\n(Var as any).__st_var = true;\n\n// ---------------------------------------------------------------------------\n// <Num> — locale-aware number formatting\n// ---------------------------------------------------------------------------\n\nexport interface NumProps {\n /** The number to format */\n children: number;\n /** Intl.NumberFormat options */\n options?: Intl.NumberFormatOptions;\n}\n\n/**\n * Formats a number according to the current locale using `Intl.NumberFormat`.\n *\n * ```tsx\n * <Num>{1000000}</Num> // \"1,000,000\" in en, \"1.000.000\" in de\n * <Num options={{ style: \"percent\" }}>{0.42}</Num> // \"42%\"\n * ```\n */\nexport function Num(props: NumProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n return new Intl.NumberFormat(locale, props.options).format(props.children);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <Currency> — locale-aware currency formatting\n// ---------------------------------------------------------------------------\n\nexport interface CurrencyProps {\n /** The numeric value */\n children: number;\n /** ISO 4217 currency code (e.g. \"USD\", \"EUR\") */\n currency: string;\n /** Additional Intl.NumberFormat options */\n options?: Intl.NumberFormatOptions;\n}\n\n/**\n * Formats a number as currency according to the current locale.\n *\n * ```tsx\n * <Currency currency=\"USD\">{29.99}</Currency> // \"$29.99\" in en-US\n * <Currency currency=\"EUR\">{29.99}</Currency> // \"29,99 €\" in de\n * ```\n */\nexport function Currency(props: CurrencyProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n return new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency: props.currency,\n ...props.options,\n }).format(props.children);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <DateTime> — locale-aware date/time formatting\n// ---------------------------------------------------------------------------\n\nexport interface DateTimeProps {\n /** The date to format (Date object, timestamp, or ISO string) */\n children: Date | number | string;\n /** Intl.DateTimeFormat options */\n options?: Intl.DateTimeFormatOptions;\n}\n\n/**\n * Formats a date/time according to the current locale using `Intl.DateTimeFormat`.\n *\n * ```tsx\n * <DateTime>{new Date()}</DateTime>\n * <DateTime options={{ dateStyle: \"long\" }}>{new Date()}</DateTime>\n * ```\n */\nexport function DateTime(props: DateTimeProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n const date =\n props.children instanceof Date\n ? props.children\n : new Date(props.children);\n return new Intl.DateTimeFormat(locale, props.options).format(date);\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <Plural> — CLDR plural rules\n// ---------------------------------------------------------------------------\n\nexport interface PluralProps {\n /** The count value to determine which plural form to use */\n n: number;\n /** Form for zero items */\n zero?: JSX.Element;\n /** Form for exactly one item */\n one?: JSX.Element;\n /** Form for exactly two items */\n two?: JSX.Element;\n /** Form for \"few\" items (language-dependent) */\n few?: JSX.Element;\n /** Form for \"many\" items (language-dependent) */\n many?: JSX.Element;\n /** Default/fallback form */\n other: JSX.Element;\n}\n\n/**\n * Renders the appropriate plural form based on CLDR plural rules for the current locale.\n *\n * String forms are translated through the translation dictionary (the source\n * string is the key — matching extraction) and support an `{n}` placeholder\n * interpolated with the count. Non-string forms render as-is, untranslated.\n *\n * ```tsx\n * <Plural n={count()}\n * zero=\"No items\"\n * one=\"1 item\"\n * other=\"{n} items\"\n * />\n * ```\n */\nexport function Plural(props: PluralProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n\n return createMemo(() => {\n const locale = ctx?.locale() || \"en\";\n const rules = new Intl.PluralRules(locale);\n const category = rules.select(props.n);\n\n const forms: Record<string, JSX.Element | undefined> = {\n zero: props.zero,\n one: props.one,\n two: props.two,\n few: props.few,\n many: props.many,\n other: props.other,\n };\n\n const form = forms[category] ?? props.other;\n\n // Translate string forms through the dictionary, keyed by source string\n if (ctx && typeof form === \"string\") {\n return ctx.t(form, { n: props.n });\n }\n\n return form;\n }) as unknown as JSX.Element;\n}\n\n// ---------------------------------------------------------------------------\n// <LocaleSelector> — drop-in locale picker\n// ---------------------------------------------------------------------------\n\nexport interface LocaleSelectorProps {\n /** Override which locales to show (defaults to all available) */\n locales?: string[];\n /** Map locale codes to display names, e.g. { en: \"English\", es: \"Español\" } */\n labels?: Record<string, string>;\n /** Additional CSS class */\n class?: string;\n}\n\n/**\n * A ready-to-use locale selector dropdown.\n *\n * ```tsx\n * <LocaleSelector labels={{ en: \"English\", es: \"Español\", fr: \"Français\" }} />\n * ```\n */\nexport function LocaleSelector(props: LocaleSelectorProps): JSX.Element {\n const ctx = useContext(TranslationContext);\n if (!ctx) {\n throw new Error(\n \"<LocaleSelector> must be used within a <TranslationProvider>\",\n );\n }\n\n const locales = createMemo(() => props.locales || ctx.availableLocales());\n\n const displayName = (code: string): string => {\n if (props.labels?.[code]) return props.labels[code]!;\n try {\n const dn = new Intl.DisplayNames([code], { type: \"language\" });\n return dn.of(code) || code;\n } catch {\n return code;\n }\n };\n\n return (\n <select\n class={props.class}\n value={ctx.locale()}\n onChange={(e) => ctx.setLocale(e.currentTarget.value)}\n >\n <For each={locales()}>\n {(code) => <option value={code}>{displayName(code)}</option>}\n </For>\n </select>\n ) as JSX.Element;\n}\n","/**\n * Mark a string for translation extraction.\n *\n * At build time, the Vite plugin and CLI scan for `msg()` calls and add\n * the strings to the source locale file for AI translation.\n *\n * At runtime, `msg()` is a no-op — it returns the source text as-is.\n * Use `t()` from `useTranslation()` for runtime translation.\n *\n * ```ts\n * // Marks \"Save changes\" for extraction\n * const label = msg(\"Save changes\");\n *\n * // With interpolation template\n * const greeting = msg(\"Hello {{name}}\", { name: \"World\" });\n *\n * // In a component, translate at runtime:\n * const { t } = useTranslation();\n * <button>{t(label)}</button>\n * ```\n */\nexport function msg(\n text: string,\n _params?: Record<string, string | number>,\n): string {\n return text;\n}\n"],"mappings":"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","names":["useContext","createMemo","createMemo","useContext"]}
package/dist/vite.d.ts CHANGED
@@ -9,8 +9,18 @@ interface SolidTranslatePluginConfig {
9
9
  targetLocales: string[];
10
10
  /** Directory containing locale JSON files, relative to project root (default: "./src/locales") */
11
11
  localesDir?: string;
12
- /** AI model to use for translations (any Vercel AI SDK LanguageModelV1) */
13
- model: LanguageModelV1;
12
+ /**
13
+ * AI model to use for translations (any Vercel AI SDK LanguageModelV1).
14
+ * Required unless `translate` is `false`.
15
+ */
16
+ model?: LanguageModelV1;
17
+ /**
18
+ * When `false`, the plugin only serves the virtual translation modules
19
+ * from the committed locale JSON files — no extraction, no AI calls, no
20
+ * file writes. Use this to keep app builds hermetic and run extraction/
21
+ * translation exclusively through the CLI (e.g. in CI). Default: `true`.
22
+ */
23
+ translate?: boolean;
14
24
  /** Custom system prompt for the AI translator */
15
25
  systemPrompt?: string;
16
26
  /** Max keys per API call (default: 50) */