solid-translate 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Omni Aura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,451 @@
1
+ # solid-translate
2
+
3
+ AI-powered i18n for SolidJS — full feature parity with [General Translation](https://generaltranslation.com), but open-source and BYOK (bring your own API key).
4
+
5
+ Write your app in one language. Wrap text in `<T>`. Get translations generated automatically at build time. No JSON key management. No external service required.
6
+
7
+ ## Features
8
+
9
+ - **`<T>` Component** — wrap any text for translation. Source text = key (no JSON wrangling)
10
+ - **`<Var>`** — protect dynamic content from translation
11
+ - **`<Num>`** — locale-aware number formatting via `Intl.NumberFormat`
12
+ - **`<Currency>`** — locale-aware currency formatting
13
+ - **`<DateTime>`** — locale-aware date/time formatting
14
+ - **`<Plural>`** — CLDR plural rules (zero/one/two/few/many/other)
15
+ - **`<LocaleSelector>`** — drop-in locale picker component
16
+ - **AI Context** — `context` prop for disambiguation ("Save" = save file vs. save money)
17
+ - **Auto Locale Detection** — detects from `navigator.languages` when `locale` prop is omitted
18
+ - **`msg()`** — mark strings for extraction outside of JSX
19
+ - **CLI Tool** — translate JSON, Markdown, and MDX files from the command line
20
+ - **Vite Plugin** — build-time translation with smart change detection
21
+ - **BYOK** — use any [Vercel AI SDK](https://ai-sdk.dev/) provider (OpenRouter, OpenAI, Anthropic, Google, etc.)
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ bun add solid-translate
27
+ bun add -d ai @ai-sdk/openai # or any AI SDK provider
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Configure the Vite plugin
33
+
34
+ ```ts
35
+ // vite.config.ts
36
+ import { defineConfig } from "vite";
37
+ import solidPlugin from "vite-plugin-solid";
38
+ import { solidTranslate } from "solid-translate/vite";
39
+ import { createOpenAI } from "@ai-sdk/openai";
40
+
41
+ const openrouter = createOpenAI({
42
+ baseURL: "https://openrouter.ai/api/v1",
43
+ apiKey: process.env.OPENROUTER_API_KEY,
44
+ });
45
+
46
+ export default defineConfig({
47
+ plugins: [
48
+ solidPlugin(),
49
+ solidTranslate({
50
+ sourceLocale: "en",
51
+ targetLocales: ["es", "fr", "de", "ja"],
52
+ localesDir: "./src/locales",
53
+ model: openrouter("openai/gpt-4o-mini"),
54
+ autoExtract: true, // auto-discover <T> and msg() strings
55
+ }),
56
+ ],
57
+ });
58
+ ```
59
+
60
+ ### 2. Write your app
61
+
62
+ ```tsx
63
+ import { TranslationProvider, T, Var, Num, Plural, useTranslation, LocaleSelector } from "solid-translate";
64
+ import translations from "virtual:solid-translate";
65
+
66
+ function App() {
67
+ return (
68
+ <TranslationProvider
69
+ sourceLocale="en"
70
+ translations={translations}
71
+ // locale="es" ← optional! auto-detects from browser if omitted
72
+ >
73
+ <Page />
74
+ </TranslationProvider>
75
+ );
76
+ }
77
+
78
+ function Page() {
79
+ const { t } = useTranslation();
80
+ const [count, setCount] = createSignal(3);
81
+ const userName = () => "Alice";
82
+
83
+ return (
84
+ <div>
85
+ <h1><T>Welcome to our app!</T></h1>
86
+
87
+ {/* Dynamic content protected with <Var> */}
88
+ <p><T>Hello <Var>{userName()}</Var>, nice to see you!</T></p>
89
+
90
+ {/* AI context for disambiguation */}
91
+ <button><T context="save a document to disk">Save</T></button>
92
+
93
+ {/* Explicit key */}
94
+ <a><T id="nav.home">Home</T></a>
95
+
96
+ {/* Interpolation */}
97
+ <p>{t("items.count", { count: count() })}</p>
98
+
99
+ {/* Pluralization */}
100
+ <Plural n={count()}
101
+ zero="No items in your cart"
102
+ one="1 item in your cart"
103
+ other={`${count()} items in your cart`}
104
+ />
105
+
106
+ {/* Locale-aware number */}
107
+ <p>Total: <Num>{1234567.89}</Num></p>
108
+
109
+ {/* Locale switcher */}
110
+ <LocaleSelector labels={{ en: "English", es: "Español", fr: "Français" }} />
111
+ </div>
112
+ );
113
+ }
114
+ ```
115
+
116
+ ### 3. Build
117
+
118
+ ```bash
119
+ bun run build
120
+ ```
121
+
122
+ On the first build, the plugin generates translation files:
123
+
124
+ ```
125
+ src/locales/
126
+ ├── en.json # Source (auto-generated or manual)
127
+ ├── es.json # AI-generated
128
+ ├── fr.json # AI-generated
129
+ ├── de.json # AI-generated
130
+ ├── ja.json # AI-generated
131
+ └── .solid-translate.lock # Change tracking
132
+ ```
133
+
134
+ On subsequent builds, only changed/new keys are re-translated. Check everything into git.
135
+
136
+ ## API Reference
137
+
138
+ ### Components
139
+
140
+ #### `<T>` — Translatable Text
141
+
142
+ ```tsx
143
+ // Source text as key (no JSON file entry needed)
144
+ <T>Hello world</T>
145
+
146
+ // Explicit key
147
+ <T id="greeting">Hello world</T>
148
+
149
+ // With interpolation
150
+ <T params={{ name: userName() }}>Hello {{name}}</T>
151
+
152
+ // AI context for disambiguation
153
+ <T context="financial institution, not river bank">Bank</T>
154
+
155
+ // Mixed JSX with Var
156
+ <T>Welcome <Var>{userName()}</Var>, you have <Num>{count()}</Num> items</T>
157
+ ```
158
+
159
+ #### `<Var>` — Variable Protection
160
+
161
+ Marks dynamic content that should NOT be translated. When used inside `<T>`, the surrounding text is translated but `<Var>` content is preserved.
162
+
163
+ ```tsx
164
+ <T>Hello <Var>{userName()}</Var></T>
165
+ // Spanish: "Hola {userName()}"
166
+ ```
167
+
168
+ #### `<Num>` — Number Formatting
169
+
170
+ Locale-aware number formatting using `Intl.NumberFormat`.
171
+
172
+ ```tsx
173
+ <Num>{1000000}</Num> // "1,000,000" (en) / "1.000.000" (de)
174
+ <Num options={{ style: "percent" }}>{0.42}</Num> // "42%"
175
+ <Num options={{ notation: "compact" }}>{1500}</Num> // "1.5K"
176
+ ```
177
+
178
+ #### `<Currency>` — Currency Formatting
179
+
180
+ ```tsx
181
+ <Currency currency="USD">{29.99}</Currency> // "$29.99" (en-US) / "29,99 $US" (fr)
182
+ <Currency currency="EUR">{1000}</Currency> // "€1,000.00" (en) / "1.000,00 €" (de)
183
+ ```
184
+
185
+ #### `<DateTime>` — Date/Time Formatting
186
+
187
+ ```tsx
188
+ <DateTime>{new Date()}</DateTime>
189
+ <DateTime options={{ dateStyle: "long" }}>{new Date()}</DateTime>
190
+ <DateTime options={{ hour: "numeric", minute: "numeric" }}>{Date.now()}</DateTime>
191
+ ```
192
+
193
+ #### `<Plural>` — Pluralization (CLDR)
194
+
195
+ Uses `Intl.PluralRules` for locale-correct plural forms.
196
+
197
+ ```tsx
198
+ <Plural n={count()}
199
+ zero="No items"
200
+ one="1 item"
201
+ two="2 items" // Used in Arabic, Welsh, etc.
202
+ few={`${count()} items`} // Used in Polish, Czech, etc.
203
+ many={`${count()} items`} // Used in Arabic, etc.
204
+ other={`${count()} items`}
205
+ />
206
+ ```
207
+
208
+ #### `<LocaleSelector>` — Locale Picker
209
+
210
+ Drop-in `<select>` for switching locales.
211
+
212
+ ```tsx
213
+ // Auto-generates display names via Intl.DisplayNames
214
+ <LocaleSelector />
215
+
216
+ // Custom labels
217
+ <LocaleSelector labels={{ en: "English", es: "Español" }} />
218
+
219
+ // Subset of locales
220
+ <LocaleSelector locales={["en", "es"]} />
221
+ ```
222
+
223
+ ### Hooks
224
+
225
+ #### `useTranslation()`
226
+
227
+ Full translation context.
228
+
229
+ ```tsx
230
+ const { t, locale, setLocale, sourceLocale, availableLocales } = useTranslation();
231
+
232
+ t("greeting") // translated string
233
+ t("items.count", { count: 3 }) // with interpolation
234
+ locale() // "es"
235
+ setLocale("fr") // switch locale
236
+ availableLocales() // ["en", "es", "fr", ...]
237
+ ```
238
+
239
+ #### `useLocale()`
240
+
241
+ Lightweight hook for just locale info.
242
+
243
+ ```tsx
244
+ const { locale, setLocale, sourceLocale, availableLocales } = useLocale();
245
+ ```
246
+
247
+ ### `<TranslationProvider>`
248
+
249
+ Root provider. Wraps your app.
250
+
251
+ ```tsx
252
+ <TranslationProvider
253
+ translations={translations} // Translation dictionaries
254
+ sourceLocale="en" // Source locale (default: "en")
255
+ // locale="es" // Optional: explicit locale
256
+ // // If omitted, auto-detects from navigator.languages
257
+ >
258
+ {children}
259
+ </TranslationProvider>
260
+ ```
261
+
262
+ ### `msg()` — Shared Strings
263
+
264
+ Mark strings for extraction outside of JSX. At build time, the Vite plugin extracts them. At runtime, use `t()` to translate.
265
+
266
+ ```tsx
267
+ import { msg } from "solid-translate";
268
+
269
+ // Mark for extraction (build-time)
270
+ const SAVE = msg("Save changes");
271
+ const DELETE = msg("Delete");
272
+
273
+ // Translate at runtime
274
+ function Toolbar() {
275
+ const { t } = useTranslation();
276
+ return (
277
+ <div>
278
+ <button>{t(SAVE)}</button>
279
+ <button>{t(DELETE)}</button>
280
+ </div>
281
+ );
282
+ }
283
+ ```
284
+
285
+ ## Vite Plugin Config
286
+
287
+ ```ts
288
+ solidTranslate({
289
+ sourceLocale: "en", // Source locale (default: "en")
290
+ targetLocales: ["es", "fr"], // Target locales
291
+ localesDir: "./src/locales", // Locale files dir (default: "./src/locales")
292
+ model: openai("gpt-4o-mini"), // Any Vercel AI SDK model
293
+ systemPrompt: "...", // Custom AI prompt (optional)
294
+ batchSize: 50, // Keys per API call (default: 50)
295
+ autoExtract: true, // Auto-extract <T> and msg() strings (default: false)
296
+ include: ["src/**/*.tsx"], // Files to scan for extraction
297
+ })
298
+ ```
299
+
300
+ ## CLI
301
+
302
+ For translating locale files, JSON, Markdown, and MDX outside of the Vite build.
303
+
304
+ ```bash
305
+ # Initialize config
306
+ npx solid-translate init
307
+
308
+ # Extract strings from source files
309
+ npx solid-translate extract
310
+
311
+ # Translate everything
312
+ npx solid-translate translate
313
+ ```
314
+
315
+ ### CLI Config (`solid-translate.config.json`)
316
+
317
+ ```json
318
+ {
319
+ "sourceLocale": "en",
320
+ "targetLocales": ["es", "fr", "de"],
321
+ "localesDir": "./src/locales",
322
+ "provider": "openrouter",
323
+ "model": "openai/gpt-4o-mini",
324
+ "batchSize": 50,
325
+ "include": ["src/**/*.tsx", "src/**/*.ts"],
326
+ "files": {
327
+ "json": {
328
+ "include": ["i18n/[locale]/*.json"]
329
+ },
330
+ "md": {
331
+ "include": ["docs/[locale]/**/*.md"]
332
+ },
333
+ "mdx": {
334
+ "include": ["content/[locale]/**/*.mdx"]
335
+ }
336
+ }
337
+ }
338
+ ```
339
+
340
+ The `[locale]` placeholder is replaced with each target locale. Source files are found by replacing `[locale]` with the source locale.
341
+
342
+ ### Environment Variables
343
+
344
+ ```bash
345
+ OPENROUTER_API_KEY=... # OpenRouter
346
+ OPENAI_API_KEY=... # OpenAI
347
+ ANTHROPIC_API_KEY=... # Anthropic
348
+ GOOGLE_API_KEY=... # Google AI
349
+ ```
350
+
351
+ ## Using with different AI providers
352
+
353
+ The plugin and CLI accept any [Vercel AI SDK](https://ai-sdk.dev/) compatible model:
354
+
355
+ ```ts
356
+ // OpenRouter (access to 100+ models)
357
+ import { createOpenAI } from "@ai-sdk/openai";
358
+ const openrouter = createOpenAI({
359
+ baseURL: "https://openrouter.ai/api/v1",
360
+ apiKey: process.env.OPENROUTER_API_KEY,
361
+ });
362
+ const model = openrouter("anthropic/claude-sonnet-4-5");
363
+
364
+ // OpenAI directly
365
+ import { openai } from "@ai-sdk/openai";
366
+ const model = openai("gpt-4o-mini");
367
+
368
+ // Anthropic directly
369
+ import { anthropic } from "@ai-sdk/anthropic";
370
+ const model = anthropic("claude-haiku-4-5-20251001");
371
+
372
+ // Google
373
+ import { google } from "@ai-sdk/google";
374
+ const model = google("gemini-2.0-flash");
375
+ ```
376
+
377
+ ## CI/CD Integration
378
+
379
+ Add to your build script for automatic translations on every deploy:
380
+
381
+ ```json
382
+ {
383
+ "scripts": {
384
+ "translate": "solid-translate translate",
385
+ "build": "bun run translate && vite build"
386
+ }
387
+ }
388
+ ```
389
+
390
+ Or in GitHub Actions:
391
+
392
+ ```yaml
393
+ - name: Translate
394
+ env:
395
+ OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
396
+ run: npx solid-translate translate
397
+
398
+ - name: Build
399
+ run: bun run build
400
+ ```
401
+
402
+ ## How change detection works
403
+
404
+ The `.solid-translate.lock` file tracks a content hash for each source key. On build:
405
+
406
+ 1. Source locale file is read and each value is hashed
407
+ 2. Hashes are compared against the lock file
408
+ 3. Only new or changed keys are sent to the AI for translation
409
+ 4. If a key's `context` prop changed, it's re-translated for better accuracy
410
+ 5. Unchanged translations are preserved from existing locale files
411
+ 6. Deleted source keys are removed from all target files
412
+
413
+ This means you can safely check in all translation files. Rebuilds are free unless you change source text.
414
+
415
+ ## TypeScript
416
+
417
+ For the virtual module import, add to your `env.d.ts` or `vite-env.d.ts`:
418
+
419
+ ```ts
420
+ declare module "virtual:solid-translate" {
421
+ const translations: Record<string, Record<string, string>>;
422
+ export default translations;
423
+ }
424
+ ```
425
+
426
+ ## Comparison with General Translation (gt-react)
427
+
428
+ | Feature | gt-react | solid-translate |
429
+ |---------|----------|-----------------|
430
+ | `<T>` component | ✅ | ✅ |
431
+ | `<Var>` variable protection | ✅ | ✅ |
432
+ | `<Num>` number formatting | ✅ | ✅ |
433
+ | `<Currency>` formatting | ✅ | ✅ |
434
+ | `<DateTime>` formatting | ✅ | ✅ |
435
+ | `<Plural>` CLDR rules | ✅ | ✅ |
436
+ | `<LocaleSelector>` | ✅ | ✅ |
437
+ | AI context disambiguation | ✅ | ✅ |
438
+ | Auto locale detection | ✅ | ✅ |
439
+ | Shared strings (`msg()`) | ✅ | ✅ |
440
+ | CLI for JSON/MD/MDX | ✅ | ✅ |
441
+ | CI/CD integration | ✅ | ✅ |
442
+ | Zero refactoring | ✅ | ✅ |
443
+ | BYOK (bring your own key) | ❌ (SaaS) | ✅ |
444
+ | No vendor lock-in | ❌ | ✅ |
445
+ | SolidJS native | ❌ (React) | ✅ |
446
+ | Build-time translation | ❌ (runtime) | ✅ |
447
+ | Open source | Partial | ✅ MIT |
448
+
449
+ ## License
450
+
451
+ MIT