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 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
- # Svelte library
1
+ # svelte-component-i18n
2
2
 
3
- Everything you need to build a Svelte library, powered by [`sv`](https://npmjs.com/package/sv).
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
- Read more about creating a library [in the docs](https://svelte.dev/docs/kit/packaging).
7
+ ## Usage Examples
6
8
 
7
- ## Creating a project
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
- If you're seeing this, you've probably already done this step. Congrats!
12
+ ### Language Detection
10
13
 
11
- ```sh
12
- # create a new project in the current directory
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
- # create a new project in my-app
16
- npx sv create my-app
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
- ## Developing
30
+ ### Language Selection
20
31
 
21
- Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
32
+ Managing and updating the current active locale store.
22
33
 
23
- ```sh
24
- npm run dev
34
+ ```svelte
35
+ <script lang="ts">
36
+ import { Translator } from 'svelte-component-i18n';
25
37
 
26
- # or start the server and open the app in a new browser tab
27
- npm run dev -- --open
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
- Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
51
+ ### Simple Translations
31
52
 
32
- ## Building
53
+ Basic key-value mapping for static text.
33
54
 
34
- To build your library:
55
+ ```svelte
56
+ <script lang="ts">
57
+ import { translator } from '$lib/my-globally-defined-translator';
35
58
 
36
- ```sh
37
- npm pack
38
- ```
59
+ const { t } = translator.define({
60
+ simple: {
61
+ en: 'Simple',
62
+ de: 'Einfach',
63
+ },
64
+ });
65
+ </script>
39
66
 
40
- To create a production version of your showcase app:
41
-
42
- ```sh
43
- npm run build
67
+ {t('simple')}
44
68
  ```
45
69
 
46
- You can preview the production build with `npm run preview`.
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
- > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
96
+ ### Interpolation
49
97
 
50
- ## Publishing
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
- Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
101
+ ```svelte
102
+ <script lang="ts">
103
+ import { translator } from '$lib/my-globally-defined-translator';
53
104
 
54
- To publish your library to [npm](https://www.npmjs.com):
105
+ const { t } = translator.define({
106
+ parameterized: (name: string) => ({
107
+ en: `Hello ${name}`,
108
+ de: `Hallo ${name}`,
109
+ }),
110
+ });
111
+ </script>
55
112
 
56
- ```sh
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
@@ -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
@@ -1,2 +1,3 @@
1
1
  export { Translator } from './translator.svelte.ts';
2
2
  export { type Dictionary, type Texts } from './dictionary.ts';
3
+ export { type LanguageStrategy, type LanguageHook, fromNavigator, fromLocalStorage, toLocalStorage, } from './detect.ts';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Translator } from "./translator.svelte.js";
2
2
  export {} from "./dictionary.js";
3
+ export { fromNavigator, fromLocalStorage, toLocalStorage, } from "./detect.js";
@@ -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 languages: Languages[];
4
- readonly fallback: Fallback;
5
+ readonly supportedLanguages: Languages[];
6
+ readonly fallbackLanguage: Fallback;
5
7
  currentLanguage: Languages;
6
8
  private readonly locale;
7
- constructor(languages: Languages[], fallback: Fallback);
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
- languages;
4
- fallback;
3
+ supportedLanguages;
4
+ fallbackLanguage;
5
5
  currentLanguage;
6
6
  locale;
7
- constructor(languages, fallback) {
8
- this.languages = languages;
9
- this.fallback = fallback;
10
- checkSupportedLanguages(languages);
11
- this.currentLanguage = $state(fallback);
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.fallback,
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.1",
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": "^1.4.0",
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": "^22",
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": "^16.5.0",
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",