svelte-component-i18n 0.1.1 → 0.1.4

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
@@ -1,18 +1,76 @@
1
+ [![NPM Version](https://img.shields.io/npm/v/svelte-component-i18n)](https://www.npmjs.com/package/svelte-component-i18n)
2
+
1
3
  # svelte-component-i18n
2
4
 
3
5
  A lightweight internationalization library for Svelte.
6
+
4
7
  It acts as a thin wrapper around native browser APIs, prioritizing type safety and component-level
5
8
  translation management.
6
9
 
7
- ## Usage Examples
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm i -S svelte-component-i18n
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ This library consists of two parts:
19
+
20
+ 1. `Translator` keeps track of the current locale and propagates changes to all components.
21
+ 2. `Dictionary` is a component level record of translations.
22
+
23
+ First should create just one global `Translator` instance and reuse it in every component.
24
+
25
+ ```typescript
26
+ // src/lib/i18n.ts
27
+
28
+ import { Translator } from 'svelte-component-i18n';
29
+
30
+ export const translator = new Translator({
31
+ // All languages you want to support.
32
+ supportedLanguages: ['en', 'de'],
33
+ // The fallback language is the only one, where translations are required to be defined.
34
+ // If a translation for a given language is missing, the fallback language is used instead.
35
+ fallbackLanguage: 'en',
36
+ });
37
+ ```
38
+
39
+ Next you should define a `Dictionary` in your components.
8
40
 
9
- The following examples are simplified. Ideally you construct one `Translator` and reuse it in every
10
- component, only defining translations there.
41
+ ```svelte
42
+ <!--
43
+ You can define translations in a `module`-Block, as it is shared across instances of the
44
+ same component.
45
+ -->
46
+ <script lang="ts" module>
47
+ import { translator } from '$lib/i18n';
48
+
49
+ // You can destruct the dictionary into the required translation functions.
50
+ // - `translate` and `t`: Translate a key
51
+ // - `pluralize` and `p`: Translate a key taking pluralization into account
52
+ const { t } = translator.define({
53
+ myFirstTranslation: {
54
+ en: 'My first translation',
55
+ de: 'Meine erste Übersetzung',
56
+ },
57
+ });
58
+ </script>
59
+
60
+ <!--
61
+ The keys and parameters are type-checked.
62
+ If the key is not defined, the component will not compile.
63
+ -->
64
+ <p>{t('myFirstTranslation')}</p>
65
+ ```
11
66
 
12
67
  ### Language Detection
13
68
 
14
- Detecting the user's preferred language using `navigator.languages` or a previously selected language
15
- from the `localStorage`.
69
+ Detecting the user's preferred language using `navigator.languages` or a previously selected
70
+ language from the `localStorage`.
71
+
72
+ Browser globals are not defined when using server-side rendering in SvelteKit.
73
+ The provided strategies and hooks skip execution, when `navigator` or `localStorage` are missing.
16
74
 
17
75
  ```typescript
18
76
  import { fromLocalStorage, fromNavigator, toLocalStorage, Translator } from 'svelte-component-i18n';
@@ -20,25 +78,22 @@ import { fromLocalStorage, fromNavigator, toLocalStorage, Translator } from 'sve
20
78
  const localStorageKey = 'my-app-language';
21
79
 
22
80
  const translator = new Translator({
23
- supportedLanguages: ['en', 'de'],
24
- fallbackLanguage: 'en',
25
- languageStrategies: [fromLocalStorage(localStorageKey), fromNavigator()],
26
- languageHooks: [toLocalStorage(localStorageKey)],
81
+ supportedLanguages: ['en', 'de'],
82
+ fallbackLanguage: 'en',
83
+ languageStrategies: [fromLocalStorage(localStorageKey), fromNavigator()],
84
+ languageHooks: [toLocalStorage(localStorageKey)],
27
85
  });
28
86
  ```
29
87
 
30
88
  ### Language Selection
31
89
 
32
- Managing and updating the current active locale store.
90
+ The `Translator` exposes both the `currentLanguage` and the initially provided `supportedLanguages`.
91
+ You can use both to create a language selector.
92
+ The `currentLanguage` is a rune and will automatically propagate changes to all components.
33
93
 
34
94
  ```svelte
35
- <script lang="ts">
36
- import { Translator } from 'svelte-component-i18n';
37
-
38
- const translator = new Translator({
39
- supportedLanguages: ['en', 'de'],
40
- fallbackLanguage: 'en',
41
- });
95
+ <script lang="ts" module>
96
+ import { translator } from '$lib/i18n';
42
97
  </script>
43
98
 
44
99
  <select bind:value={translator.currentLanguage}>
@@ -53,15 +108,15 @@ Managing and updating the current active locale store.
53
108
  Basic key-value mapping for static text.
54
109
 
55
110
  ```svelte
56
- <script lang="ts">
57
- import { translator } from '$lib/my-globally-defined-translator';
111
+ <script lang="ts" module>
112
+ import { translator } from '$lib/i18n';
58
113
 
59
- const { t } = translator.define({
114
+ const { t } = translator.define({
60
115
  simple: {
61
- en: 'Simple',
62
- de: 'Einfach',
116
+ en: 'Simple',
117
+ de: 'Einfach',
63
118
  },
64
- });
119
+ });
65
120
  </script>
66
121
 
67
122
  {t('simple')}
@@ -69,24 +124,24 @@ const { t } = translator.define({
69
124
 
70
125
  ### Pluralization
71
126
 
72
- Using `Intl.PluralRules` logic to handle count-based text variations.
127
+ Using `Intl.PluralRules`[^1] logic to handle count-based text variations.
73
128
 
74
129
  ```svelte
75
- <script lang="ts">
76
- import { translator } from '$lib/my-globally-defined-translator';
130
+ <script lang="ts" module>
131
+ import { translator } from '$lib/i18n';
77
132
 
78
- const { p } = translator.define({
133
+ const { p } = translator.define({
79
134
  pluralized: {
80
- en: {
81
- one: 'One thing',
82
- other: 'Many things',
83
- },
84
- de: {
85
- one: 'Ein Ding',
86
- other: 'Viele Dinge',
87
- },
135
+ en: {
136
+ one: 'One thing',
137
+ other: 'Many things',
138
+ },
139
+ de: {
140
+ one: 'Ein Ding',
141
+ other: 'Viele Dinge',
142
+ },
88
143
  },
89
- });
144
+ });
90
145
  </script>
91
146
 
92
147
  {p('pluralized', 1)}
@@ -99,15 +154,15 @@ Instead of defining static structures you can also define functions, which retur
99
154
  Template literals can be used to interpolate text.
100
155
 
101
156
  ```svelte
102
- <script lang="ts">
103
- import { translator } from '$lib/my-globally-defined-translator';
157
+ <script lang="ts" module>
158
+ import { translator } from '$lib/i18n';
104
159
 
105
- const { t } = translator.define({
160
+ const { t } = translator.define({
106
161
  parameterized: (name: string) => ({
107
- en: `Hello ${name}`,
108
- de: `Hallo ${name}`,
162
+ en: `Hello ${name}`,
163
+ de: `Hallo ${name}`,
109
164
  }),
110
- });
165
+ });
111
166
  </script>
112
167
 
113
168
  {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?: 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,20 +1,23 @@
1
- import { browser } from '$app/environment';
2
- export function fromNavigator() {
1
+ export function fromNavigator(navigator = globalThis.navigator) {
3
2
  return (supportedLanguages) => {
4
- if (!browser) {
3
+ if (!navigator) {
5
4
  return;
6
5
  }
7
6
  for (const navigatorLanguage of navigator.languages) {
8
- const language = navigatorLanguage;
7
+ const language = stripOptionalBCP47SubTags(navigatorLanguage);
9
8
  if (supportedLanguages.includes(language)) {
10
9
  return language;
11
10
  }
12
11
  }
13
12
  };
14
13
  }
15
- export function fromLocalStorage(key) {
14
+ function stripOptionalBCP47SubTags(bcp47Tag) {
15
+ const [language, _script, _region] = bcp47Tag.split('-');
16
+ return language;
17
+ }
18
+ export function fromLocalStorage(key, localStorage = globalThis.localStorage) {
16
19
  return (supportedLanguages) => {
17
- if (!browser) {
20
+ if (!localStorage) {
18
21
  return;
19
22
  }
20
23
  const localStorageLanguage = localStorage.getItem(key);
@@ -27,9 +30,9 @@ export function fromLocalStorage(key) {
27
30
  }
28
31
  };
29
32
  }
30
- export function toLocalStorage(key) {
33
+ export function toLocalStorage(key, localStorage = globalThis.localStorage) {
31
34
  return (language) => {
32
- if (!browser) {
35
+ if (!localStorage) {
33
36
  return;
34
37
  }
35
38
  localStorage.setItem(key, language);
package/package.json CHANGED
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "name": "svelte-component-i18n",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
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",
@@ -38,25 +48,22 @@
38
48
  "@eslint/compat": "^2.0.0",
39
49
  "@eslint/js": "^9.39.1",
40
50
  "@sveltejs/adapter-auto": "^7.0.0",
41
- "@sveltejs/kit": "^2.49.1",
51
+ "@sveltejs/kit": "^2.50.1",
42
52
  "@sveltejs/package": "^2.5.7",
43
- "@sveltejs/vite-plugin-svelte": "^6.2.1",
44
- "@types/node": "^25.0.3",
53
+ "@sveltejs/vite-plugin-svelte": "^6.2.4",
54
+ "@types/node": "^25.0.10",
45
55
  "eslint": "^9.39.1",
46
56
  "eslint-config-prettier": "^10.1.8",
47
57
  "eslint-plugin-svelte": "^3.13.1",
48
58
  "globals": "^17.0.0",
49
- "prettier": "^3.7.4",
59
+ "prettier": "^3.8.1",
50
60
  "prettier-plugin-svelte": "^3.4.0",
51
- "publint": "^0.3.15",
52
- "svelte": "^5.45.6",
61
+ "publint": "^0.3.17",
62
+ "svelte": "^5.48.2",
53
63
  "svelte-check": "^4.3.4",
54
64
  "typescript": "^5.9.3",
55
65
  "typescript-eslint": "^8.48.1",
56
- "vite": "^7.2.6",
66
+ "vite": "^7.3.1",
57
67
  "vitest": "^4.0.15"
58
- },
59
- "keywords": [
60
- "svelte"
61
- ]
68
+ }
62
69
  }