solid-translate 1.3.0 → 1.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/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) */
package/dist/index.js CHANGED
@@ -38,9 +38,16 @@ function normalizeLocale(locale) {
38
38
  }
39
39
 
40
40
  // src/components.tsx
41
+ import { template as _$template } from "solid-js/web";
42
+ import { className as _$className } from "solid-js/web";
43
+ import { insert as _$insert } from "solid-js/web";
44
+ import { createComponent as _$createComponent } from "solid-js/web";
45
+ import { effect as _$effect } from "solid-js/web";
41
46
  import { useContext, For, createMemo } from "solid-js";
47
+ var _tmpl$ = /* @__PURE__ */ _$template(`<select>`);
48
+ var _tmpl$2 = /* @__PURE__ */ _$template(`<option>`);
42
49
  function Var(props) {
43
- return (() => props.children);
50
+ return () => props.children;
44
51
  }
45
52
  Var.__st_var = true;
46
53
  function Num(props) {
@@ -85,7 +92,9 @@ function Plural(props) {
85
92
  };
86
93
  const form = forms[category] ?? props.other;
87
94
  if (ctx && typeof form === "string") {
88
- return ctx.t(form, { n: props.n });
95
+ return ctx.t(form, {
96
+ n: props.n
97
+ });
89
98
  }
90
99
  return form;
91
100
  });
@@ -93,29 +102,38 @@ function Plural(props) {
93
102
  function LocaleSelector(props) {
94
103
  const ctx = useContext(TranslationContext);
95
104
  if (!ctx) {
96
- throw new Error(
97
- "<LocaleSelector> must be used within a <TranslationProvider>"
98
- );
105
+ throw new Error("<LocaleSelector> must be used within a <TranslationProvider>");
99
106
  }
100
107
  const locales = createMemo(() => props.locales || ctx.availableLocales());
101
108
  const displayName = (code) => {
102
109
  if (props.labels?.[code]) return props.labels[code];
103
110
  try {
104
- const dn = new Intl.DisplayNames([code], { type: "language" });
111
+ const dn = new Intl.DisplayNames([code], {
112
+ type: "language"
113
+ });
105
114
  return dn.of(code) || code;
106
115
  } catch {
107
116
  return code;
108
117
  }
109
118
  };
110
- return <select
111
- class={props.class}
112
- value={ctx.locale()}
113
- onChange={(e) => ctx.setLocale(e.currentTarget.value)}
114
- >
115
- <For each={locales()}>
116
- {(code) => <option value={code}>{displayName(code)}</option>}
117
- </For>
118
- </select>;
119
+ return (() => {
120
+ var _el$ = _tmpl$();
121
+ _el$.addEventListener("change", (e) => ctx.setLocale(e.currentTarget.value));
122
+ _$insert(_el$, _$createComponent(For, {
123
+ get each() {
124
+ return locales();
125
+ },
126
+ children: (code) => (() => {
127
+ var _el$2 = _tmpl$2();
128
+ _el$2.value = code;
129
+ _$insert(_el$2, () => displayName(code));
130
+ return _el$2;
131
+ })()
132
+ }));
133
+ _$effect(() => _$className(_el$, props.class));
134
+ _$effect(() => _el$.value = ctx.locale());
135
+ return _el$;
136
+ })();
119
137
  }
120
138
 
121
139
  // src/msg.ts
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 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"]}
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","useContext","For","createMemo","Var","props","children","__st_var","Num","ctx","useContext","TranslationContext","createMemo","locale","Intl","NumberFormat","options","format","Currency","style","currency","DateTime","date","Date","DateTimeFormat","Plural","rules","PluralRules","category","select","n","forms","zero","one","two","few","many","other","form","t","LocaleSelector","Error","locales","availableLocales","displayName","code","labels","dn","DisplayNames","type","of","_el$","_tmpl$","addEventListener","e","setLocale","currentTarget","value","_$insert","_$createComponent","For","each","_el$2","_tmpl$2","_$effect","_$className","class","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) */
package/dist/vite.js CHANGED
@@ -15150,6 +15150,7 @@ function solidTranslate(config) {
15150
15150
  model,
15151
15151
  systemPrompt,
15152
15152
  batchSize = 50,
15153
+ translate = true,
15153
15154
  autoExtract = false,
15154
15155
  include = ["src/**/*.tsx", "src/**/*.ts", "src/**/*.jsx"]
15155
15156
  } = config;
@@ -15162,6 +15163,14 @@ function solidTranslate(config) {
15162
15163
  resolvedLocalesDir = resolve(root, localesDir);
15163
15164
  },
15164
15165
  async buildStart() {
15166
+ if (!translate) {
15167
+ return;
15168
+ }
15169
+ if (!model) {
15170
+ throw new Error(
15171
+ "[solid-translate] `model` is required unless `translate: false` is set"
15172
+ );
15173
+ }
15165
15174
  if (!existsSync2(resolvedLocalesDir)) {
15166
15175
  mkdirSync(resolvedLocalesDir, { recursive: true });
15167
15176
  }