svelte-component-i18n 0.1.0 → 0.1.3
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 +95 -42
- package/dist/detect.d.ts +3 -3
- package/dist/detect.js +6 -7
- package/dist/translator.svelte.d.ts +4 -1
- package/dist/translator.svelte.js +14 -8
- package/package.json +12 -5
package/README.md
CHANGED
|
@@ -1,18 +1,74 @@
|
|
|
1
1
|
# svelte-component-i18n
|
|
2
2
|
|
|
3
3
|
A lightweight internationalization library for Svelte.
|
|
4
|
+
|
|
4
5
|
It acts as a thin wrapper around native browser APIs, prioritizing type safety and component-level
|
|
5
6
|
translation management.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i -S svelte-component-i18n
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
This library consists of two parts:
|
|
17
|
+
|
|
18
|
+
1. `Translator` keeps track of the current locale and propagates changes to all components.
|
|
19
|
+
2. `Dictionary` is a component level record of translations.
|
|
20
|
+
|
|
21
|
+
First should create just one global `Translator` instance and reuse it in every component.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// src/lib/i18n.ts
|
|
25
|
+
|
|
26
|
+
import { Translator } from 'svelte-component-i18n';
|
|
27
|
+
|
|
28
|
+
export const translator = new Translator({
|
|
29
|
+
// All languages you want to support.
|
|
30
|
+
supportedLanguages: ['en', 'de'],
|
|
31
|
+
// The fallback language is the only one, where translations are required to be defined.
|
|
32
|
+
// If a translation for a given language is missing, the fallback language is used instead.
|
|
33
|
+
fallbackLanguage: 'en',
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Next you should define a `Dictionary` in your components.
|
|
38
|
+
|
|
39
|
+
```svelte
|
|
40
|
+
<!--
|
|
41
|
+
You can define translations in a `module`-Block, as it is shared across instances of the
|
|
42
|
+
same component.
|
|
43
|
+
-->
|
|
44
|
+
<script lang="ts" module>
|
|
45
|
+
import { translator } from '$lib/i18n';
|
|
46
|
+
|
|
47
|
+
// You can destruct the dictionary into the required translation functions.
|
|
48
|
+
// - `translate` and `t`: Translate a key
|
|
49
|
+
// - `pluralize` and `p`: Translate a key taking pluralization into account
|
|
50
|
+
const { t } = translator.define({
|
|
51
|
+
myFirstTranslation: {
|
|
52
|
+
en: 'My first translation',
|
|
53
|
+
de: 'Meine erste Übersetzung',
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<!--
|
|
59
|
+
The keys and parameters are type-checked.
|
|
60
|
+
If the key is not defined, the component will not compile.
|
|
61
|
+
-->
|
|
62
|
+
<p>{t('myFirstTranslation')}</p>
|
|
63
|
+
```
|
|
11
64
|
|
|
12
65
|
### Language Detection
|
|
13
66
|
|
|
14
|
-
Detecting the user's preferred language using `navigator.languages` or a previously selected
|
|
15
|
-
from the `localStorage`.
|
|
67
|
+
Detecting the user's preferred language using `navigator.languages` or a previously selected
|
|
68
|
+
language from the `localStorage`.
|
|
69
|
+
|
|
70
|
+
Browser globals are not defined when using server-side rendering in SvelteKit.
|
|
71
|
+
The provided strategies and hooks skip execution, when `navigator` or `localStorage` are missing.
|
|
16
72
|
|
|
17
73
|
```typescript
|
|
18
74
|
import { fromLocalStorage, fromNavigator, toLocalStorage, Translator } from 'svelte-component-i18n';
|
|
@@ -20,25 +76,22 @@ import { fromLocalStorage, fromNavigator, toLocalStorage, Translator } from 'sve
|
|
|
20
76
|
const localStorageKey = 'my-app-language';
|
|
21
77
|
|
|
22
78
|
const translator = new Translator({
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
79
|
+
supportedLanguages: ['en', 'de'],
|
|
80
|
+
fallbackLanguage: 'en',
|
|
81
|
+
languageStrategies: [fromLocalStorage(localStorageKey), fromNavigator()],
|
|
82
|
+
languageHooks: [toLocalStorage(localStorageKey)],
|
|
27
83
|
});
|
|
28
84
|
```
|
|
29
85
|
|
|
30
86
|
### Language Selection
|
|
31
87
|
|
|
32
|
-
|
|
88
|
+
The `Translator` exposes both the `currentLanguage` and the initially provided `supportedLanguages`.
|
|
89
|
+
You can use both to create a language selector.
|
|
90
|
+
The `currentLanguage` is a rune and will automatically propagate changes to all components.
|
|
33
91
|
|
|
34
92
|
```svelte
|
|
35
|
-
<script lang="ts">
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
const translator = new Translator({
|
|
39
|
-
supportedLanguages: ['en', 'de'],
|
|
40
|
-
fallbackLanguage: 'en',
|
|
41
|
-
});
|
|
93
|
+
<script lang="ts" module>
|
|
94
|
+
import { translator } from '$lib/i18n';
|
|
42
95
|
</script>
|
|
43
96
|
|
|
44
97
|
<select bind:value={translator.currentLanguage}>
|
|
@@ -53,15 +106,15 @@ Managing and updating the current active locale store.
|
|
|
53
106
|
Basic key-value mapping for static text.
|
|
54
107
|
|
|
55
108
|
```svelte
|
|
56
|
-
<script lang="ts">
|
|
57
|
-
import { translator } from '$lib/
|
|
109
|
+
<script lang="ts" module>
|
|
110
|
+
import { translator } from '$lib/i18n';
|
|
58
111
|
|
|
59
|
-
const { t } = translator.define({
|
|
112
|
+
const { t } = translator.define({
|
|
60
113
|
simple: {
|
|
61
|
-
|
|
62
|
-
|
|
114
|
+
en: 'Simple',
|
|
115
|
+
de: 'Einfach',
|
|
63
116
|
},
|
|
64
|
-
});
|
|
117
|
+
});
|
|
65
118
|
</script>
|
|
66
119
|
|
|
67
120
|
{t('simple')}
|
|
@@ -69,24 +122,24 @@ const { t } = translator.define({
|
|
|
69
122
|
|
|
70
123
|
### Pluralization
|
|
71
124
|
|
|
72
|
-
Using `Intl.PluralRules` logic to handle count-based text variations.
|
|
125
|
+
Using `Intl.PluralRules`[^1] logic to handle count-based text variations.
|
|
73
126
|
|
|
74
127
|
```svelte
|
|
75
|
-
<script lang="ts">
|
|
76
|
-
import { translator } from '$lib/
|
|
128
|
+
<script lang="ts" module>
|
|
129
|
+
import { translator } from '$lib/i18n';
|
|
77
130
|
|
|
78
|
-
const { p } = translator.define({
|
|
131
|
+
const { p } = translator.define({
|
|
79
132
|
pluralized: {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
133
|
+
en: {
|
|
134
|
+
one: 'One thing',
|
|
135
|
+
other: 'Many things',
|
|
136
|
+
},
|
|
137
|
+
de: {
|
|
138
|
+
one: 'Ein Ding',
|
|
139
|
+
other: 'Viele Dinge',
|
|
140
|
+
},
|
|
88
141
|
},
|
|
89
|
-
});
|
|
142
|
+
});
|
|
90
143
|
</script>
|
|
91
144
|
|
|
92
145
|
{p('pluralized', 1)}
|
|
@@ -99,15 +152,15 @@ Instead of defining static structures you can also define functions, which retur
|
|
|
99
152
|
Template literals can be used to interpolate text.
|
|
100
153
|
|
|
101
154
|
```svelte
|
|
102
|
-
<script lang="ts">
|
|
103
|
-
import { translator } from '$lib/
|
|
155
|
+
<script lang="ts" module>
|
|
156
|
+
import { translator } from '$lib/i18n';
|
|
104
157
|
|
|
105
|
-
const { t } = translator.define({
|
|
158
|
+
const { t } = translator.define({
|
|
106
159
|
parameterized: (name: string) => ({
|
|
107
|
-
|
|
108
|
-
|
|
160
|
+
en: `Hello ${name}`,
|
|
161
|
+
de: `Hallo ${name}`,
|
|
109
162
|
}),
|
|
110
|
-
});
|
|
163
|
+
});
|
|
111
164
|
</script>
|
|
112
165
|
|
|
113
166
|
{t('parameterized', 'Svelte')}
|
package/dist/detect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type LanguageStrategy<Languages extends string> = (supportedLanguages: Languages[]) => Languages | undefined;
|
|
2
|
-
export declare function fromNavigator<Languages extends string>(): LanguageStrategy<Languages>;
|
|
3
|
-
export declare function fromLocalStorage<Languages extends string>(key: string): LanguageStrategy<Languages>;
|
|
2
|
+
export declare function fromNavigator<Languages extends string>(navigator?: typeof globalThis.navigator): LanguageStrategy<Languages>;
|
|
3
|
+
export declare function fromLocalStorage<Languages extends string>(key: string, localStorage?: Storage): LanguageStrategy<Languages>;
|
|
4
4
|
export type LanguageHook<Languages extends string> = (language: Languages) => unknown;
|
|
5
|
-
export declare function toLocalStorage<Languages extends string>(key: string): LanguageHook<Languages>;
|
|
5
|
+
export declare function toLocalStorage<Languages extends string>(key: string, localStorage?: Storage): LanguageHook<Languages>;
|
package/dist/detect.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
export function fromNavigator() {
|
|
1
|
+
export function fromNavigator(navigator = globalThis.navigator) {
|
|
3
2
|
return (supportedLanguages) => {
|
|
4
|
-
if (!
|
|
3
|
+
if (!navigator) {
|
|
5
4
|
return;
|
|
6
5
|
}
|
|
7
6
|
for (const navigatorLanguage of navigator.languages) {
|
|
@@ -12,9 +11,9 @@ export function fromNavigator() {
|
|
|
12
11
|
}
|
|
13
12
|
};
|
|
14
13
|
}
|
|
15
|
-
export function fromLocalStorage(key) {
|
|
14
|
+
export function fromLocalStorage(key, localStorage = globalThis.localStorage) {
|
|
16
15
|
return (supportedLanguages) => {
|
|
17
|
-
if (!
|
|
16
|
+
if (!localStorage) {
|
|
18
17
|
return;
|
|
19
18
|
}
|
|
20
19
|
const localStorageLanguage = localStorage.getItem(key);
|
|
@@ -27,9 +26,9 @@ export function fromLocalStorage(key) {
|
|
|
27
26
|
}
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
|
-
export function toLocalStorage(key) {
|
|
29
|
+
export function toLocalStorage(key, localStorage = globalThis.localStorage) {
|
|
31
30
|
return (language) => {
|
|
32
|
-
if (!
|
|
31
|
+
if (!localStorage) {
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
35
34
|
localStorage.setItem(key, language);
|
|
@@ -2,16 +2,19 @@ import { Dictionary, type Texts } from './dictionary.ts';
|
|
|
2
2
|
import type { LanguageHook, LanguageStrategy } from './detect.ts';
|
|
3
3
|
type MustStringUnion<T extends string> = string extends T ? never : T;
|
|
4
4
|
export declare class Translator<Languages extends string, Fallback extends Languages> {
|
|
5
|
+
#private;
|
|
5
6
|
readonly supportedLanguages: Languages[];
|
|
6
7
|
readonly fallbackLanguage: Fallback;
|
|
7
|
-
currentLanguage: Languages;
|
|
8
8
|
private readonly locale;
|
|
9
|
+
private readonly languageHooks;
|
|
9
10
|
constructor({ supportedLanguages, fallbackLanguage, languageStrategies, languageHooks, }: {
|
|
10
11
|
supportedLanguages: MustStringUnion<Languages>[];
|
|
11
12
|
fallbackLanguage: Fallback;
|
|
12
13
|
languageStrategies?: LanguageStrategy<Languages>[];
|
|
13
14
|
languageHooks?: LanguageHook<Languages>[];
|
|
14
15
|
});
|
|
16
|
+
get currentLanguage(): Languages;
|
|
17
|
+
set currentLanguage(language: Languages);
|
|
15
18
|
define<T>(texts: Texts<Languages, Fallback, T>): Dictionary<Languages, Fallback, T>;
|
|
16
19
|
}
|
|
17
20
|
export {};
|
|
@@ -2,24 +2,30 @@ import { Dictionary } from "./dictionary.js";
|
|
|
2
2
|
export class Translator {
|
|
3
3
|
supportedLanguages;
|
|
4
4
|
fallbackLanguage;
|
|
5
|
-
currentLanguage;
|
|
5
|
+
#currentLanguage;
|
|
6
6
|
locale;
|
|
7
|
+
languageHooks;
|
|
7
8
|
constructor({ supportedLanguages, fallbackLanguage, languageStrategies = [], languageHooks = [], }) {
|
|
8
9
|
checkSupportedLanguages(supportedLanguages);
|
|
9
10
|
this.supportedLanguages = supportedLanguages;
|
|
10
11
|
this.fallbackLanguage = fallbackLanguage;
|
|
11
12
|
const initialLanguage = determineInitialLanguage(languageStrategies, supportedLanguages, fallbackLanguage);
|
|
12
|
-
this
|
|
13
|
+
this.#currentLanguage = $state(initialLanguage);
|
|
13
14
|
this.locale = $derived({
|
|
14
|
-
language: this
|
|
15
|
+
language: this.#currentLanguage,
|
|
15
16
|
fallback: this.fallbackLanguage,
|
|
16
17
|
pluralRules: new Intl.PluralRules(this.currentLanguage),
|
|
17
18
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
this.languageHooks = languageHooks;
|
|
20
|
+
}
|
|
21
|
+
get currentLanguage() {
|
|
22
|
+
return this.#currentLanguage;
|
|
23
|
+
}
|
|
24
|
+
set currentLanguage(language) {
|
|
25
|
+
this.#currentLanguage = language;
|
|
26
|
+
for (const hook of this.languageHooks) {
|
|
27
|
+
hook(this.currentLanguage);
|
|
28
|
+
}
|
|
23
29
|
}
|
|
24
30
|
define(texts) {
|
|
25
31
|
return new Dictionary(() => this.locale, texts);
|
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-component-i18n",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"description": "Lightweight internationalization library for Svelte.",
|
|
6
|
+
"author": "Lukas Dietrich <lukas@lukasdietrich.com>",
|
|
7
|
+
"repository": "github:lukasdietrich/svelte-component-i18n",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"svelte",
|
|
10
|
+
"i18n",
|
|
11
|
+
"internationalization",
|
|
12
|
+
"translation"
|
|
13
|
+
],
|
|
4
14
|
"scripts": {
|
|
5
15
|
"dev": "vite dev",
|
|
6
16
|
"build": "vite build && npm run prepack",
|
|
@@ -55,8 +65,5 @@
|
|
|
55
65
|
"typescript-eslint": "^8.48.1",
|
|
56
66
|
"vite": "^7.2.6",
|
|
57
67
|
"vitest": "^4.0.15"
|
|
58
|
-
}
|
|
59
|
-
"keywords": [
|
|
60
|
-
"svelte"
|
|
61
|
-
]
|
|
68
|
+
}
|
|
62
69
|
}
|