svelte-tiny-i18n 1.0.2 β†’ 1.1.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/README.md CHANGED
@@ -8,253 +8,106 @@
8
8
 
9
9
  `svelte-tiny-i18n` is a lightweight, type-safe, and reactive i18n (internationalization) library for [Svelte](https://svelte.dev/) and [SvelteKit](https://kit.svelte.dev/), built entirely on Svelte Stores.
10
10
 
11
- This library is "headless," meaning it only provides the core logic and Svelte stores, leaving the UI and component integration entirely up to the developer.
11
+ > **πŸ’‘ Check out the [Examples Directory](./examples/README.md) for executable demos of all Type-Safety patterns!**
12
12
 
13
13
  ## TL;DR
14
14
 
15
- `svelte-tiny-i18n` is for developers who value **extreme lightweightness, zero dependencies, and zero build-time configuration**, while still enjoying a **native Svelte store experience and instant TypeScript inference**.
15
+ `svelte-tiny-i18n` is for developers who value **extreme lightweightness, zero dependencies, and zero build-time configuration**, while still enjoying **Svelte store reactivity and instant TypeScript inference**.
16
16
 
17
- Its key advantage: **Zero-config type safety**. You define your supported locales (e.g., `['en', 'es']`) in a single config file, and TypeScript immediately provides type-checking and autocompletion for your `setLocale` function (e.g., `setLocale('es')` is valid, `setLocale('fr')` is an error) β€” all without running a code generator.
17
+ Its key advantage: **Hybrid Type Safety**.
18
18
 
19
- This makes it the ideal choice for small-to-medium projects and Svelte purists.
20
-
21
- ### Example: Minimal SvelteKit Integration
22
-
23
- Assuming a project structure like this:
24
-
25
- ```tree
26
- /src
27
- β”œβ”€β”€ /lib
28
- β”‚ └── i18n.ts <- 1. Config File
29
- └── /routes
30
- └── /[lang]
31
- β”œβ”€β”€ +layout.ts <- 2. SvelteKit Integration
32
- β”œβ”€β”€ +page.svelte <- 3. Usage
33
- └── /about
34
- └── +page.svelte
35
- ```
36
-
37
- 1. **`src/lib/i18n.ts`** (Config File)
38
-
39
- ```ts
40
- import { createI18nStore, defineI18nConfig, type inferSupportedLocale } from 'svelte-tiny-i18n';
41
-
42
- const config = defineI18nConfig({
43
- supportedLocales: ['en', 'es'],
44
- defaultLocale: 'en',
45
- localStorageKey: 'lang',
46
- initialTranslations: [
47
- {
48
- hello: { en: 'Hello', es: 'Hola' }
49
- }
50
- ]
51
- });
52
-
53
- export const i18n = createI18nStore(config);
54
- export type SupportedLocale = inferSupportedLocale<typeof i18n>;
55
- ```
56
-
57
- 2. **`src/routes/[lang]/+layout.ts`** (SvelteKit Integration)
58
-
59
- ```ts
60
- import { i18n } from '$lib/i18n';
61
- import type { LayoutLoad } from './$types';
62
-
63
- export const load: LayoutLoad = ({ params }) => {
64
- // 'lang' comes from your route, e.g., /[lang]/
65
- i18n.setLocale(params.lang);
66
- return {};
67
- };
68
- ```
69
-
70
- 3. **`src/routes/[lang]/+page.svelte`** (Usage)
71
-
72
- ```svelte
73
- <script lang="ts">
74
- import { i18n } from '$lib/i18n';
75
- const { t, setLocale } = i18n;
76
- </script>
77
-
78
- <h1>{$t('hello')}</h1>
79
- <button on:click={() => setLocale('es')}>EspaΓ±ol</button>
80
- ```
81
-
82
- ## Installation
83
-
84
- ```bash
85
- npm install svelte-tiny-i18n
86
- ```
87
-
88
- ```bash
89
- pnpm add svelte-tiny-i18n
90
- ```
91
-
92
- ```bash
93
- yarn add svelte-tiny-i18n
94
- ```
19
+ 1. **Config Inference**: For static translations, keys are inferred automatically.
20
+ 2. **Global Augmentation**: For dynamic loading, simply point to your files in a `d.ts`, and types work globally.
21
+ 3. **Zero-Config Scope**: Or, just get a typed store back from `extendTranslations`.
95
22
 
96
23
  ## Quick Start
97
24
 
98
- The best way to use `svelte-tiny-i18n` is to create a dedicated singleton instance.
99
-
100
25
  ### 1. Create the i18n Instance
101
26
 
102
- Create a file at `/src/lib/i18n.ts` (or your preferred location).
103
-
104
27
  ```ts
105
28
  // /src/lib/i18n.ts
106
- import {
107
- createI18nStore,
108
- defineI18nConfig,
109
- type inferSupportedLocale,
110
- type inferPartialTranslationEntry,
111
- type inferTranslationEntry
112
- } from 'svelte-tiny-i18n';
113
-
114
- // 1. Define the config
29
+ import { createI18nStore, defineI18nConfig } from 'svelte-tiny-i18n';
30
+
115
31
  const i18nConfig = defineI18nConfig({
116
- // Define all supported languages
117
32
  supportedLocales: ['en', 'es', 'zh-TW'],
118
-
119
- // Default language
120
33
  defaultLocale: 'en',
121
-
122
- // Key for storing the language in localStorage
123
34
  localStorageKey: 'my-app-language',
124
35
 
125
- // (Optional) Log warnings to the console if a key is not found
126
- devLogs: true,
36
+ // (Optional) Custom Error Handler
37
+ // e.g., send to Sentry in production
38
+ onError: (err) => {
39
+ console.error('i18n error:', err.type, err.key);
40
+ },
127
41
 
128
- // Define initial, global translations
42
+ // 1. Nested JSON is fully supported
43
+ // 2. TypeScript infers these keys automatically!
129
44
  initialTranslations: [
130
45
  {
131
- hello: {
132
- en: 'Hello, {name}!',
133
- es: 'Β‘Hola, {name}!',
134
- 'zh-TW': 'δ½ ε₯½, {name}!'
135
- },
136
- goodbye: {
137
- en: 'Goodbye',
138
- es: 'AdiΓ³s',
139
- 'zh-TW': '再見'
46
+ hello: { en: 'Hello', 'zh-TW': 'δ½ ε₯½' },
47
+ home: {
48
+ title: { en: 'Home Page' },
49
+ btn: { en: 'Click Me' }
140
50
  }
141
51
  }
142
52
  ]
143
53
  });
144
54
 
145
- // 2. Create and export the i18n instance
146
55
  export const i18n = createI18nStore(i18nConfig);
147
-
148
- // 3. (Optional) Export inferred types for app-wide type safety
149
- export type SupportedLocale = inferSupportedLocale<typeof i18n>;
150
- export type TranslationEntry = inferTranslationEntry<typeof i18n>;
151
- export type PartialTranslationEntry = inferPartialTranslationEntry<typeof i18n>;
152
56
  ```
153
57
 
154
58
  ### 2. Use in Svelte Components
155
59
 
156
- Use the derived store `$t` to get translations, and the `locale` store to read or `setLocale` to set the language.
157
-
158
60
  ```svelte
159
61
  <script lang="ts">
160
62
  import { i18n } from '$lib/i18n';
161
-
162
- // Destructure the stores and functions
163
63
  const { t, locale, setLocale } = i18n;
164
64
  </script>
165
65
 
166
- <h1>{$t('hello', { name: 'World' })}</h1>
167
-
168
- <nav>
169
- <p>Current language: {$locale}</p>
66
+ <h1>{$t('hello')}</h1>
67
+ <p>{$t('home.title')}</p>
170
68
 
171
- <button on:click={() => setLocale('en')}>English</button>
172
- <button on:click={() => setLocale('es')}>EspaΓ±ol</button>
173
- <button on:click={() => setLocale('zh-TW')}>繁體中文</button>
174
- </nav>
175
-
176
- <p>{$t('a.missing.key')}</p>
69
+ <button on:click={() => setLocale('zh-TW')}> δΈ­ζ–‡ </button>
177
70
  ```
178
71
 
179
- ### 3. SvelteKit Integration (Recommended)
72
+ ## Advanced Usage: Dynamic Loading & Types
180
73
 
181
- To make the i18n state available on both the server and client, and to initialize from a URL parameter (e.g., `/es/about`), use it in your root `+layout.ts`.
74
+ `svelte-tiny-i18n` offers two powerful strategies for handling types when you load translations dynamically (e.g., using `extendTranslations`).
182
75
 
183
- ```ts
184
- // /src/routes/+layout.ts
185
- import { i18n } from '$lib/i18n';
186
- import type { LayoutLoad } from './$types';
187
-
188
- // This load function runs on both SSR and CSR
189
- export const load: LayoutLoad = ({ params }) => {
190
- // 'lang' must match your route parameter, e.g., /[lang]/
191
- const { lang } = params;
192
-
193
- // The setLocale function validates the lang
194
- // and sets the 'locale' store.
195
- i18n.setLocale(lang);
196
-
197
- // You can optionally return lang, but the store itself is already set
198
- return { lang };
199
- };
200
- ```
76
+ ### Strategy A: Global Augmentation (Recommended)
201
77
 
202
- **Note:** Your SvelteKit route structure must be similar to `/src/routes/[lang]/...` for `params.lang` to be available.
78
+ Keep your `$t` global, but automate the types using TypeScript's `typeof import`.
203
79
 
204
- ## Advanced Usage
205
-
206
- ### Dynamic (Async) Translation Loading
207
-
208
- You don't need to load all translations at startup. You can load them on demand in a page's `+page.ts` or `+layout.ts` using `extendTranslations`.
209
-
210
- 1. Define page-specific translations:
80
+ 1. Create `src/i18n.d.ts`:
211
81
 
212
82
  ```ts
213
- // /src/locales/profile.ts
214
- import type { PartialTranslationEntry } from '$lib/i18n';
215
-
216
- export const profileTranslations: PartialTranslationEntry = {
217
- 'profile.title': {
218
- en: 'My Profile',
219
- es: 'Mi Perfil',
220
- 'zh-TW': '個人資料'
221
- },
222
- 'profile.edit_button': {
223
- en: 'Edit'
224
- // Missing 'es' and 'zh-TW' is allowed!
83
+ import 'svelte-tiny-i18n';
84
+
85
+ declare module 'svelte-tiny-i18n' {
86
+ export interface TinyI18nTranslations {
87
+ // Simply point to your translation files!
88
+ profile: typeof import('./locales/profile.json');
89
+ dashboard: typeof import('./features/dashboard/locales').default;
225
90
  }
226
- };
91
+ }
227
92
  ```
228
93
 
229
- 2. Load them in the page's loader:
94
+ 2. Now `$t('profile.name')` is typed everywhere, even before you load it!
230
95
 
231
- ```ts
232
- // /src/routes/profile/+page.ts
233
- import { i18n } from '$lib/i18n';
234
- import { profileTranslations } from '$locales/profile';
235
- import type { PageLoad } from './$types';
236
-
237
- export const load: PageLoad = () => {
238
- // Dynamically add the translations for this page
239
- i18n.extendTranslations([profileTranslations]);
96
+ ### Strategy B: Zero-Config (Local Scope)
240
97
 
241
- // You can also await an async import
242
- // const { jsonTranslations } = await import('$locales/profile.json');
243
- // i18n.extendTranslations([jsonTranslations]);
244
- };
245
- ```
98
+ Don't want to touch `d.ts`? No problem. `extendTranslations` returns a store typed specifically for the new content.
246
99
 
247
- The new translations are now merged into the store and available via the `$t` function.
100
+ ```ts
101
+ // /src/routes/profile/+page.svelte
102
+ import { profileTranslations } from './locales';
248
103
 
249
- ## Advantages
104
+ // The returned 't' knows about 'profile.*' keys immediately
105
+ const { t } = i18n.extendTranslations([profileTranslations]);
250
106
 
251
- `svelte-tiny-i18n` is designed to be the "sweet spot" for Svelte developers who need a simple, fast, and type-safe solution without the overhead of larger libraries.
107
+ $t('profile.title'); // βœ… Typed!
108
+ ```
252
109
 
253
- - **Zero-Config Type Safety**: Get full type-safety for your language codes _without_ a build step, code generator, or complex setup. Type-safety is achieved instantly via TypeScript inference from your single configuration object.
254
- - **Extremely Lightweight**: This library is "tiny" (likely <1kb gzipped). It has **zero dependencies** and does not bundle a heavy ICU message parser, making your app faster.
255
- - **Svelte Native**: Built purely with Svelte stores (`writable` and `derived`), it integrates seamlessly into the Svelte reactivity model.
256
- - **Simple but Powerful**: Provides all the essential features: SSR/CSR support for SvelteKit, dynamic/async translation loading (`extendTranslations`), and simple variable substitution.
257
- - **"Headless" by Design**: It provides only the core logic, giving you full control over your UI and integration. (This also means you are responsible for updating the `<html>` `lang` attribute; see the [FAQ for a recipe](#q-how-do-i-dynamically-update-the-html-lang-attribute-or-handle-rtl-right-to-left-languages).)
110
+ See [**Example 2: Global Augmentation**](./examples/2-global-augmentation.ts) and [**Example 3: Zero-Config**](./examples/3-zero-config.ts) for full code.
258
111
 
259
112
  ## Comparison with Other Libraries
260
113
 
@@ -262,107 +115,37 @@ The new translations are now merged into the store and available via the `$t` fu
262
115
  | :------------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------- | :--------------------------------------------------------- |
263
116
  | **Bundle Size** | **Tiny (<1kb)** | **Tiny (~1kb)** | **Medium (~15kb+)** |
264
117
  | **Core Mechanism** | Zero-dependency Svelte Stores + Simple String Replace | **Build-time Generator** | **Runtime ICU Parser** |
265
- | **Type Safety** | **High (Instant Inference)** | **Very High (Code-Gen)** | Medium (Manual setup) |
118
+ | **Type Safety** | **Hybrid (Inference + Augmentation)** | **Very High (Code-Gen)** | Medium (Manual setup) |
266
119
  | **Setup Complexity** | **Very Low** (Single config file) | Medium (Requires generator setup) | Low (Install and use) |
267
120
  | **Adv. Formatting** | Simple `{var}` replacement only | **Yes** (Plurals, Dates, Numbers) | **Yes (Full ICU Support)** |
268
121
  | **Key Trade-Off** | Trades ICU features for **extreme lightness** & **zero-config type safety**. | Trades setup simplicity for the **strongest type safety** (incl. args). | Trades bundle size for the **most powerful ICU features**. |
269
122
 
270
123
  ### Philosophy Comparison
271
124
 
272
- - **If you need advanced formatting** (complex plurals, date/number localization) and don't mind a larger bundle size, **`svelte-i18n`** is a great choice. It uses the industry-standard `formatjs` and ICU syntax.
273
- - **If you need _absolute_ type safety** (including for translation arguments, e.g., `$t('key', { arg: 'val' })`) and are willing to set up a code generator, **`typesafe-i18n`** is excellent.
274
- - **`svelte-tiny-i18n`** is the ideal choice if you value:
275
- 1. **Simplicity**: Get started in 2 minutes.
276
- 2. **Bundle Size**: A minimal footprint is critical.
277
- 3. **Effortless Type Safety**: You want strong type guarantees _without_ a build step.
278
-
279
- This library intentionally trades complex ICU formatting (which `svelte-i18n` provides) and argument-level type safety (which `typesafe-i18n` provides) for **extreme simplicity, minimal size, and zero-config type safety.**
280
-
281
- ## API Reference
282
-
283
- ### Factory Functions
284
-
285
- #### `createI18nStore(config)`
286
-
287
- Creates the core i18n instance. Returns an object containing stores and functions.
288
-
289
- #### `defineI18nConfig(config)`
290
-
291
- A helper function for defining your `I18nConfig` that provides full type safety and inference.
292
-
293
- ### The Returned Instance (`i18n`)
294
-
295
- When you call `createI18nStore`, you get an object with:
296
-
297
- - `t`: (Read-only derived store) The translation function.
298
- - `$t('key')`
299
- - `$t('key', { placeholder: 'value' })`
300
- - `locale`: (Readable store) The currently active language code (e.g., `en`). This store is readable; to update it, use the `setLocale()` function.
301
- - `setLocale(lang: string | null | undefined)`: A function to safely set the initial language, typically called from the root `+layout.ts`.
302
- - If `lang` is a supported language, it sets the `locale` store.
303
- - If `lang` is invalid (or `null`/`undefined`), it's ignored, and the `locale` store **keeps its current value**.
304
- - `extendTranslations(newTranslations: PartialTranslationEntry[])`: Merges new translations (an _array_) into the main store and triggers an update.
305
- - `supportedLocales`: (Read-only `readonly string[]`) The array of supported languages from your config.
306
- - `defaultLocale`: (Read-only `string`) The default language from your config.
307
- - `localStorageKey`: (Read-only `string`) The `localStorage` key from your config.
308
-
309
- ### Type Helpers
310
-
311
- For robust type safety in your app, you can import type helpers directly from `svelte-tiny-i18n`.
312
-
313
- - `inferSupportedLocale<typeof i18n>`: Infers the union of supported language codes (e.g., `'en' | 'es' | 'zh-TW'`).
314
- - `inferTranslationEntry<typeof i18n>`: Infers the _full_ translation entry type (e.g., `{ en: string; es: string; 'zh-TW': string; }`).
315
- - `inferPartialTranslationEntry<typeof i18n>`: Infers the type for translation files (e.g., `{ [key: string]: { en?: string; es?: string; 'zh-TW'?: string; } }`).
316
-
317
- **Example:**
318
-
319
- ```ts
320
- // /src/lib/i18n.ts
321
- // ... (as shown in "Quick Start")
322
- export type SupportedLocale = inferSupportedLocale<typeof i18n>;
323
- ```
324
-
325
- ```ts
326
- // /src/components/SomeComponent.svelte
327
- import { i18n } from '$lib/i18n';
328
- import type { SupportedLocale } from '$lib/i18n';
329
-
330
- // The 'lang' variable is now type-checked
331
- function setLanguage(lang: SupportedLocale) {
332
- i18n.setLocale(lang);
333
- }
334
-
335
- setLanguage('en'); // OK
336
- setLanguage('fr'); // TypeScript Error
337
- ```
125
+ - **If you need advanced formatting** (complex plurals, date/number localization) and don't mind a larger bundle size, **`svelte-i18n`** is a great choice.
126
+ - **If you need _absolute_ type safety** (including for translation arguments) and are willing to set up a code generator, **`typesafe-i18n`** is excellent.
127
+ - **`svelte-tiny-i18n`** is the ideal choice if you prioritize **simplicity**, **minimal bundle size**, and **effortless type safety** without a build step.
338
128
 
339
129
  ## FAQ / Recipes
340
130
 
341
131
  ### Q: How do I use this in a Svelte (Vite) project _without_ SvelteKit?
342
132
 
343
- A: It's even simpler. You don't need the `+layout.ts` file or the `i18n.setLocale()` step.
344
-
133
+ A: It's even simpler. You don't need the `+layout.ts` or the `i18n.setLocale()` step.
345
134
  The store will automatically initialize its language in the browser by checking `localStorage` and `navigator.language`. You can change the language at any time by simply calling `i18n.setLocale('new_lang')` in your components.
346
135
 
347
136
  ### Q: How do I dynamically update the `<html>` `lang` attribute or handle RTL (Right-to-Left) languages?
348
137
 
349
- A: This library is "headless," so it doesn't modify the DOM for you. You can easily manage this yourself by subscribing to the `locale` store in your root layout component (e.g., `+layout.svelte` for SvelteKit or `App.svelte` for Svelte/Vite).
350
-
351
- Here is an example for a SvelteKit project that sets both the `lang` and `dir` attributes:
138
+ A: This library is "headless," so it doesn't modify the DOM for you. You can easily manage this yourself by subscribing to the `locale` store in your root layout component.
352
139
 
353
140
  ```svelte
354
141
  <script lang="ts">
355
142
  import { i18n } from '$lib/i18n';
356
143
  const { locale } = i18n;
357
144
 
358
- // Define which of your supported locales are RTL
359
- // (e.g., Arabic 'ar', Hebrew 'he')
360
145
  const rtlLocales: string[] = ['ar', 'he'];
361
146
 
362
147
  $: if (typeof document !== 'undefined') {
363
148
  const direction = rtlLocales.includes($locale) ? 'rtl' : 'ltr';
364
-
365
- // Dynamically set attributes on <html>
366
149
  document.documentElement.lang = $locale;
367
150
  document.documentElement.dir = direction;
368
151
  }
@@ -371,6 +154,30 @@ Here is an example for a SvelteKit project that sets both the `lang` and `dir` a
371
154
  <slot />
372
155
  ```
373
156
 
157
+ ## API Reference
158
+
159
+ ### `defineI18nConfig(config)`
160
+
161
+ Helper for defining config with type inference.
162
+
163
+ ### `createI18nStore(config)`
164
+
165
+ Creates the instance. Returns:
166
+
167
+ - `t`: Derived store for translation.
168
+ - `locale`: Writable store for current language.
169
+ - `setLocale(lang)`: Safely changes language.
170
+ - `extendTranslations(modules)`:
171
+ - Merges new translations.
172
+ - Returns `{ t }`: A new store instance typed with the union of existing + new keys.
173
+
174
+ ### `onError: (error) => void`
175
+
176
+ Callback in `config` to handle missing keys or locales.
177
+
178
+ - `error.type`: `'missing_key' | 'missing_locale'`
179
+ - `error.key`: The key or locale that failed.
180
+
374
181
  ## License
375
182
 
376
183
  [MIT](https://opensource.org/licenses/MIT)