svelte-component-i18n 0.0.1 → 0.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/LICENSE +28 -0
- package/README.md +91 -33
- package/dist/detect.d.ts +5 -0
- package/dist/detect.js +37 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/translator.svelte.d.ts +11 -3
- package/dist/translator.svelte.js +23 -8
- package/package.json +4 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Lukas Dietrich
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,58 +1,116 @@
|
|
|
1
|
-
#
|
|
1
|
+
# svelte-component-i18n
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A lightweight internationalization library for Svelte.
|
|
4
|
+
It acts as a thin wrapper around native browser APIs, prioritizing type safety and component-level
|
|
5
|
+
translation management.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
## Usage Examples
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
The following examples are simplified. Ideally you construct one `Translator` and reuse it in every
|
|
10
|
+
component, only defining translations there.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
### Language Detection
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
npx sv create
|
|
14
|
+
Detecting the user's preferred language using `navigator.languages` or a previously selected language
|
|
15
|
+
from the `localStorage`.
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
```typescript
|
|
18
|
+
import { fromLocalStorage, fromNavigator, toLocalStorage, Translator } from 'svelte-component-i18n';
|
|
19
|
+
|
|
20
|
+
const localStorageKey = 'my-app-language';
|
|
21
|
+
|
|
22
|
+
const translator = new Translator({
|
|
23
|
+
supportedLanguages: ['en', 'de'],
|
|
24
|
+
fallbackLanguage: 'en',
|
|
25
|
+
languageStrategies: [fromLocalStorage(localStorageKey), fromNavigator()],
|
|
26
|
+
languageHooks: [toLocalStorage(localStorageKey)],
|
|
27
|
+
});
|
|
17
28
|
```
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
### Language Selection
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
Managing and updating the current active locale store.
|
|
22
33
|
|
|
23
|
-
```
|
|
24
|
-
|
|
34
|
+
```svelte
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import { Translator } from 'svelte-component-i18n';
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
const translator = new Translator({
|
|
39
|
+
supportedLanguages: ['en', 'de'],
|
|
40
|
+
fallbackLanguage: 'en',
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<select bind:value={translator.currentLanguage}>
|
|
45
|
+
{#each translator.supportedLanguages as language (language)}
|
|
46
|
+
<option value={language}>{language}</option>
|
|
47
|
+
{/each}
|
|
48
|
+
</select>
|
|
28
49
|
```
|
|
29
50
|
|
|
30
|
-
|
|
51
|
+
### Simple Translations
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
Basic key-value mapping for static text.
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
```svelte
|
|
56
|
+
<script lang="ts">
|
|
57
|
+
import { translator } from '$lib/my-globally-defined-translator';
|
|
35
58
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
const { t } = translator.define({
|
|
60
|
+
simple: {
|
|
61
|
+
en: 'Simple',
|
|
62
|
+
de: 'Einfach',
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
39
66
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```sh
|
|
43
|
-
npm run build
|
|
67
|
+
{t('simple')}
|
|
44
68
|
```
|
|
45
69
|
|
|
46
|
-
|
|
70
|
+
### Pluralization
|
|
71
|
+
|
|
72
|
+
Using `Intl.PluralRules` logic to handle count-based text variations.
|
|
73
|
+
|
|
74
|
+
```svelte
|
|
75
|
+
<script lang="ts">
|
|
76
|
+
import { translator } from '$lib/my-globally-defined-translator';
|
|
77
|
+
|
|
78
|
+
const { p } = translator.define({
|
|
79
|
+
pluralized: {
|
|
80
|
+
en: {
|
|
81
|
+
one: 'One thing',
|
|
82
|
+
other: 'Many things',
|
|
83
|
+
},
|
|
84
|
+
de: {
|
|
85
|
+
one: 'Ein Ding',
|
|
86
|
+
other: 'Viele Dinge',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
{p('pluralized', 1)}
|
|
93
|
+
{p('pluralized', 42)}
|
|
94
|
+
```
|
|
47
95
|
|
|
48
|
-
|
|
96
|
+
### Interpolation
|
|
49
97
|
|
|
50
|
-
|
|
98
|
+
Instead of defining static structures you can also define functions, which return the same structures.
|
|
99
|
+
Template literals can be used to interpolate text.
|
|
51
100
|
|
|
52
|
-
|
|
101
|
+
```svelte
|
|
102
|
+
<script lang="ts">
|
|
103
|
+
import { translator } from '$lib/my-globally-defined-translator';
|
|
53
104
|
|
|
54
|
-
|
|
105
|
+
const { t } = translator.define({
|
|
106
|
+
parameterized: (name: string) => ({
|
|
107
|
+
en: `Hello ${name}`,
|
|
108
|
+
de: `Hallo ${name}`,
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
</script>
|
|
55
112
|
|
|
56
|
-
|
|
57
|
-
npm publish
|
|
113
|
+
{t('parameterized', 'Svelte')}
|
|
58
114
|
```
|
|
115
|
+
|
|
116
|
+
[^1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
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>;
|
|
4
|
+
export type LanguageHook<Languages extends string> = (language: Languages) => unknown;
|
|
5
|
+
export declare function toLocalStorage<Languages extends string>(key: string): LanguageHook<Languages>;
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { browser } from '$app/environment';
|
|
2
|
+
export function fromNavigator() {
|
|
3
|
+
return (supportedLanguages) => {
|
|
4
|
+
if (!browser) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
for (const navigatorLanguage of navigator.languages) {
|
|
8
|
+
const language = navigatorLanguage;
|
|
9
|
+
if (supportedLanguages.includes(language)) {
|
|
10
|
+
return language;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function fromLocalStorage(key) {
|
|
16
|
+
return (supportedLanguages) => {
|
|
17
|
+
if (!browser) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const localStorageLanguage = localStorage.getItem(key);
|
|
21
|
+
if (typeof localStorageLanguage !== 'string') {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const language = localStorageLanguage;
|
|
25
|
+
if (supportedLanguages.includes(language)) {
|
|
26
|
+
return language;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function toLocalStorage(key) {
|
|
31
|
+
return (language) => {
|
|
32
|
+
if (!browser) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
localStorage.setItem(key, language);
|
|
36
|
+
};
|
|
37
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { Dictionary, type Texts } from './dictionary.ts';
|
|
2
|
+
import type { LanguageHook, LanguageStrategy } from './detect.ts';
|
|
3
|
+
type MustStringUnion<T extends string> = string extends T ? never : T;
|
|
2
4
|
export declare class Translator<Languages extends string, Fallback extends Languages> {
|
|
3
|
-
readonly
|
|
4
|
-
readonly
|
|
5
|
+
readonly supportedLanguages: Languages[];
|
|
6
|
+
readonly fallbackLanguage: Fallback;
|
|
5
7
|
currentLanguage: Languages;
|
|
6
8
|
private readonly locale;
|
|
7
|
-
constructor(
|
|
9
|
+
constructor({ supportedLanguages, fallbackLanguage, languageStrategies, languageHooks, }: {
|
|
10
|
+
supportedLanguages: MustStringUnion<Languages>[];
|
|
11
|
+
fallbackLanguage: Fallback;
|
|
12
|
+
languageStrategies?: LanguageStrategy<Languages>[];
|
|
13
|
+
languageHooks?: LanguageHook<Languages>[];
|
|
14
|
+
});
|
|
8
15
|
define<T>(texts: Texts<Languages, Fallback, T>): Dictionary<Languages, Fallback, T>;
|
|
9
16
|
}
|
|
17
|
+
export {};
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { Dictionary } from "./dictionary.js";
|
|
2
2
|
export class Translator {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
supportedLanguages;
|
|
4
|
+
fallbackLanguage;
|
|
5
5
|
currentLanguage;
|
|
6
6
|
locale;
|
|
7
|
-
constructor(
|
|
8
|
-
|
|
9
|
-
this.
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
constructor({ supportedLanguages, fallbackLanguage, languageStrategies = [], languageHooks = [], }) {
|
|
8
|
+
checkSupportedLanguages(supportedLanguages);
|
|
9
|
+
this.supportedLanguages = supportedLanguages;
|
|
10
|
+
this.fallbackLanguage = fallbackLanguage;
|
|
11
|
+
const initialLanguage = determineInitialLanguage(languageStrategies, supportedLanguages, fallbackLanguage);
|
|
12
|
+
this.currentLanguage = $state(initialLanguage);
|
|
12
13
|
this.locale = $derived({
|
|
13
14
|
language: this.currentLanguage,
|
|
14
|
-
fallback: this.
|
|
15
|
+
fallback: this.fallbackLanguage,
|
|
15
16
|
pluralRules: new Intl.PluralRules(this.currentLanguage),
|
|
16
17
|
});
|
|
18
|
+
$effect(() => {
|
|
19
|
+
for (const hook of languageHooks) {
|
|
20
|
+
hook(this.currentLanguage);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
17
23
|
}
|
|
18
24
|
define(texts) {
|
|
19
25
|
return new Dictionary(() => this.locale, texts);
|
|
@@ -26,3 +32,12 @@ function checkSupportedLanguages(languages) {
|
|
|
26
32
|
throw new Error(`Unsupported languages: ${unsupportedLanguages}`);
|
|
27
33
|
}
|
|
28
34
|
}
|
|
35
|
+
function determineInitialLanguage(strategies, languages, fallback) {
|
|
36
|
+
for (const strategy of strategies) {
|
|
37
|
+
const language = strategy(languages);
|
|
38
|
+
if (language !== undefined) {
|
|
39
|
+
return language;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-component-i18n",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "vite dev",
|
|
6
6
|
"build": "vite build && npm run prepack",
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
"svelte": "^5.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@eslint/compat": "^
|
|
38
|
+
"@eslint/compat": "^2.0.0",
|
|
39
39
|
"@eslint/js": "^9.39.1",
|
|
40
40
|
"@sveltejs/adapter-auto": "^7.0.0",
|
|
41
41
|
"@sveltejs/kit": "^2.49.1",
|
|
42
42
|
"@sveltejs/package": "^2.5.7",
|
|
43
43
|
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
44
|
-
"@types/node": "^
|
|
44
|
+
"@types/node": "^25.0.3",
|
|
45
45
|
"eslint": "^9.39.1",
|
|
46
46
|
"eslint-config-prettier": "^10.1.8",
|
|
47
47
|
"eslint-plugin-svelte": "^3.13.1",
|
|
48
|
-
"globals": "^
|
|
48
|
+
"globals": "^17.0.0",
|
|
49
49
|
"prettier": "^3.7.4",
|
|
50
50
|
"prettier-plugin-svelte": "^3.4.0",
|
|
51
51
|
"publint": "^0.3.15",
|