@sveltia/i18n 0.1.1

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.
@@ -0,0 +1,514 @@
1
+ import { MessageFormat } from 'messageformat';
2
+ import { DraftFunctions } from 'messageformat/functions';
3
+ import { SvelteMap, SvelteURLSearchParams } from 'svelte/reactivity';
4
+
5
+ // Polyfill
6
+ Intl.MessageFormat ??= MessageFormat;
7
+
8
+ /**
9
+ * @typedef {object} Formats
10
+ * @property {Record<string, Intl.NumberFormatOptions>} [number] Custom number format presets.
11
+ * @property {Record<string, Intl.DateTimeFormatOptions>} [date] Custom date format presets.
12
+ * @property {Record<string, Intl.DateTimeFormatOptions>} [time] Custom time format presets.
13
+ */
14
+
15
+ /**
16
+ * @callback MissingKeyHandler
17
+ * @param {string} key The missing message key.
18
+ * @param {string} locale The active locale.
19
+ * @param {string | undefined} defaultValue The default value passed to `format()`, if any.
20
+ * @returns {string | void} A replacement string, or `undefined` to fall through to the default.
21
+ */
22
+
23
+ /**
24
+ * @typedef {object} MessageObject
25
+ * @property {string} id Message key.
26
+ * @property {Record<string, any>} [values] Variables to interpolate into the message.
27
+ * @property {string} [locale] Locale override for this call.
28
+ * @property {string} [default] Fallback string if the key is not found.
29
+ */
30
+
31
+ /**
32
+ * Date/time formatting options, extending `Intl.DateTimeFormatOptions` with `locale` and `format`
33
+ * overrides.
34
+ * @typedef {Intl.DateTimeFormatOptions & { locale?: string, format?: string }} DateFormatOptions
35
+ */
36
+
37
+ /**
38
+ * Number formatting options, extending `Intl.NumberFormatOptions` with `locale` and `format`
39
+ * overrides.
40
+ * @typedef {Intl.NumberFormatOptions & { locale?: string, format?: string }} NumberFormatOptions
41
+ */
42
+
43
+ // --- State ---
44
+
45
+ /** @type {string} */
46
+ let _locale = $state('');
47
+ /**
48
+ * All registered locales.
49
+ * @type {string[]}
50
+ */
51
+ const locales = $state([]);
52
+ /**
53
+ * All registered resources.
54
+ * @type {Record<string, Record<string, Intl.MessageFormat>>}
55
+ */
56
+ const dictionary = $state({});
57
+ /**
58
+ * Whether locale messages are currently being loaded. Returns `true` after a locale is set but
59
+ * before its messages are available.
60
+ * @returns {boolean} `true` if messages are pending for the current locale, `false` otherwise.
61
+ */
62
+ const isLoading = () => !!_locale && !dictionary[_locale];
63
+
64
+ // Languages written right-to-left; used as a fallback when Intl.Locale.textInfo is not available
65
+ // (e.g. Firefox).
66
+ const RTL_LANGS = new Set([
67
+ 'ar',
68
+ 'arc',
69
+ 'ckb',
70
+ 'dv',
71
+ 'fa',
72
+ 'ha',
73
+ 'he',
74
+ 'khw',
75
+ 'ks',
76
+ 'ku',
77
+ 'nqo',
78
+ 'ps',
79
+ 'sd',
80
+ 'ug',
81
+ 'ur',
82
+ 'yi',
83
+ ]);
84
+
85
+ /**
86
+ * Return the text direction for a resolved `Intl.Locale` object. Uses `textInfo.direction` when
87
+ * available (Chrome/Safari) and falls back to the `RTL_LANGS` set (Firefox).
88
+ * @param {Intl.Locale} localeObj The locale object to inspect.
89
+ * @returns {'ltr' | 'rtl'} The text direction of the locale.
90
+ */
91
+ const getTextDirection = (localeObj) => {
92
+ /* v8 ignore next */
93
+ const dir = /** @type {any} */ (localeObj).textInfo?.direction;
94
+
95
+ /* v8 ignore next */
96
+ return dir ?? (RTL_LANGS.has(localeObj.language) ? 'rtl' : 'ltr');
97
+ };
98
+
99
+ /**
100
+ * Whether the current locale is written right-to-left. Reactive: re-evaluates whenever the locale
101
+ * changes.
102
+ * @returns {boolean} `true` if the active locale is RTL, `false` otherwise.
103
+ */
104
+ const isRTL = () => {
105
+ if (!_locale) return false;
106
+
107
+ try {
108
+ return getTextDirection(new Intl.Locale(_locale)) === 'rtl';
109
+ } catch {
110
+ return false;
111
+ }
112
+ };
113
+
114
+ // --- Messages ---
115
+
116
+ /**
117
+ * Recursively flatten a nested message map into dot-separated keys. `{ field: { name: 'Name' } }` →
118
+ * `{ 'field.name': 'Name' }` Top-level keys that already contain dots are preserved as-is.
119
+ * @param {Record<string, any>} map Nested or flat message map to flatten.
120
+ * @param {string} [prefix] Key prefix for recursive calls.
121
+ * @returns {Record<string, string>} Flat map with dot-separated keys.
122
+ */
123
+ const flattenMessages = (map, prefix = '') =>
124
+ Object.entries(map).reduce((acc, [key, value]) => {
125
+ const flatKey = prefix ? `${prefix}.${key}` : key;
126
+
127
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
128
+ Object.assign(acc, flattenMessages(value, flatKey));
129
+ } else {
130
+ acc[flatKey] = value;
131
+ }
132
+
133
+ return acc;
134
+ }, /** @type {Record<string, string>} */ ({}));
135
+
136
+ /**
137
+ * Add new messages for a locale. Accepts flat or nested maps; nested objects are flattened to
138
+ * dot-separated keys (`field.name`). Multiple dicts can be passed and are merged in order, matching
139
+ * svelte-i18n's `addMessages(locale, ...dicts)` signature.
140
+ * @param {string} localeCode Locale.
141
+ * @param {...Record<string, any>} maps One or more message maps (flat or nested).
142
+ * @see https://messageformat.github.io/messageformat/api/messageformat.messageformat/
143
+ */
144
+ const addMessages = (localeCode, ...maps) => {
145
+ if (!locales.includes(localeCode)) {
146
+ locales.push(localeCode);
147
+ }
148
+
149
+ dictionary[localeCode] ??= {};
150
+
151
+ maps.forEach((map) => {
152
+ Object.entries(flattenMessages(map)).forEach(([key, value]) => {
153
+ dictionary[localeCode][key] = new Intl.MessageFormat(localeCode, String(value), {
154
+ functions: DraftFunctions,
155
+ });
156
+ });
157
+ });
158
+ };
159
+
160
+ // --- Loader ---
161
+
162
+ /** @type {SvelteMap<string, () => Promise<Record<string, string>>>} */
163
+ const loaderQueue = new SvelteMap();
164
+ /** @type {SvelteMap<string, Promise<void>>} */
165
+ const loaderPromises = new SvelteMap();
166
+
167
+ /**
168
+ * Register an async loader for a locale. The loader is called the first time
169
+ * `waitLocale(localeCode)` is invoked for that locale.
170
+ * @param {string} localeCode Locale.
171
+ * @param {() => Promise<Record<string, string>>} loader Function returning a message map.
172
+ */
173
+ const register = (localeCode, loader) => {
174
+ loaderQueue.set(localeCode, loader);
175
+ // Invalidate any cached promise so the new loader is picked up on next waitLocale call.
176
+ loaderPromises.delete(localeCode);
177
+
178
+ if (!locales.includes(localeCode)) {
179
+ locales.push(localeCode);
180
+ }
181
+ };
182
+
183
+ /**
184
+ * Execute the registered loader for the given locale (or the current locale if omitted) and wait
185
+ * until the messages are loaded. Subsequent calls for the same locale return the same promise.
186
+ * @param {string} [localeCode] Defaults to `locale.current`.
187
+ * @returns {Promise<void>}
188
+ */
189
+ const waitLocale = (localeCode = _locale) => {
190
+ if (!localeCode) return Promise.resolve();
191
+
192
+ if (!loaderPromises.has(localeCode)) {
193
+ const loader = loaderQueue.get(localeCode);
194
+
195
+ if (loader) {
196
+ const promise = Promise.resolve(loader()).then(
197
+ (map) => {
198
+ addMessages(localeCode, map);
199
+ },
200
+ () => {
201
+ loaderPromises.delete(localeCode);
202
+ },
203
+ );
204
+
205
+ loaderPromises.set(localeCode, promise);
206
+ } else {
207
+ loaderPromises.set(localeCode, Promise.resolve());
208
+ }
209
+ }
210
+
211
+ /* v8 ignore next */
212
+ return loaderPromises.get(localeCode) ?? Promise.resolve();
213
+ };
214
+
215
+ // --- Locale ---
216
+
217
+ /**
218
+ * Negotiate the best available locale for a requested tag.
219
+ * 1. Exact match 2. Same language subtag (e.g. En-CA → en-US) 3. Original value.
220
+ * @param {string} requested The requested locale tag.
221
+ * @param {string[]} available List of available locale codes.
222
+ * @returns {string} The best-matching available locale, or `requested` if no match is found.
223
+ */
224
+ const negotiateLocale = (requested, available) => {
225
+ if (!requested || !available.length) return requested;
226
+ if (available.includes(requested)) return requested;
227
+
228
+ try {
229
+ const lang = new Intl.Locale(requested).language;
230
+
231
+ return (
232
+ available.find((l) => {
233
+ try {
234
+ return new Intl.Locale(l).language === lang;
235
+ } catch {
236
+ return false;
237
+ }
238
+ }) ?? requested
239
+ );
240
+ } catch {
241
+ return requested;
242
+ }
243
+ };
244
+
245
+ /**
246
+ * Current locale.
247
+ */
248
+ const locale = {
249
+ /**
250
+ * Returns the current locale code.
251
+ * @returns {string} The active locale code.
252
+ */
253
+ get current() {
254
+ return _locale;
255
+ },
256
+ /**
257
+ * Set the current locale. Negotiates against registered locales (e.g. En-CA → en-US), updates
258
+ * `<html lang>` / `<html dir>`, and auto-triggers any registered loader.
259
+ * @param {string} value The locale to set.
260
+ * @returns {Promise<void>}
261
+ */
262
+ set(value) {
263
+ const resolved = locales.length ? negotiateLocale(value, locales) : value;
264
+
265
+ _locale = resolved;
266
+
267
+ if (typeof document !== 'undefined' && resolved) {
268
+ document.documentElement.lang = resolved;
269
+
270
+ try {
271
+ const localeObj = new Intl.Locale(resolved);
272
+
273
+ document.documentElement.dir = getTextDirection(localeObj);
274
+ } catch {
275
+ // resolved is not a valid BCP 47 tag; skip dir update
276
+ }
277
+ }
278
+
279
+ return waitLocale(resolved);
280
+ },
281
+ };
282
+
283
+ /**
284
+ * Get the user's preferred locale from the browser.
285
+ * @returns {string | undefined} The first navigator language, or `undefined` in non-browser
286
+ * environments.
287
+ */
288
+ const getLocaleFromNavigator = () =>
289
+ typeof navigator === 'undefined' ? undefined : (navigator.languages?.[0] ?? navigator.language);
290
+
291
+ /**
292
+ * Get the locale from a pattern matched against `window.location.hostname`.
293
+ * @param {RegExp} hostnamePattern Pattern with a capture group for the locale code.
294
+ * @returns {string | undefined} The matched locale code, or `undefined` if not in a browser or no
295
+ * match.
296
+ */
297
+ const getLocaleFromHostname = (hostnamePattern) =>
298
+ typeof window === 'undefined' || !window.location
299
+ ? undefined
300
+ : window.location.hostname.match(hostnamePattern)?.[1];
301
+
302
+ /**
303
+ * Get the locale from a pattern matched against `window.location.pathname`.
304
+ * @param {RegExp} pathnamePattern Pattern with a capture group for the locale code.
305
+ * @returns {string | undefined} The matched locale code, or `undefined` if not in a browser or no
306
+ * match.
307
+ */
308
+ const getLocaleFromPathname = (pathnamePattern) =>
309
+ typeof window === 'undefined' || !window.location
310
+ ? undefined
311
+ : window.location.pathname.match(pathnamePattern)?.[1];
312
+
313
+ /**
314
+ * Get the locale from a URL query string parameter.
315
+ * @param {string} queryKey The query string key to read.
316
+ * @returns {string | undefined} The query parameter value, or `undefined` if not in a browser or
317
+ * not found.
318
+ */
319
+ const getLocaleFromQueryString = (queryKey) =>
320
+ typeof window === 'undefined' || !window.location
321
+ ? undefined
322
+ : (new SvelteURLSearchParams(window.location.search).get(queryKey) ?? undefined);
323
+
324
+ /**
325
+ * Get the locale from a `key=value` pair in `window.location.hash`.
326
+ * @param {string} hashKey The key to look for in the hash.
327
+ * @returns {string | undefined} The hash parameter value, or `undefined` if not in a browser or not
328
+ * found.
329
+ */
330
+ const getLocaleFromHash = (hashKey) => {
331
+ if (typeof window === 'undefined' || !window.location) return undefined;
332
+
333
+ const params = new SvelteURLSearchParams(window.location.hash.replace(/^#/, ''));
334
+
335
+ return params.get(hashKey) ?? undefined;
336
+ };
337
+
338
+ // --- Configuration ---
339
+
340
+ let fallbackLocale = '';
341
+ /** @type {MissingKeyHandler | undefined} */
342
+ let missingMessageHandler;
343
+ /** @type {Formats} */
344
+ let customFormats = {};
345
+
346
+ /**
347
+ * Initialize the locales.
348
+ * @param {object} args Arguments.
349
+ * @param {string} args.fallbackLocale Locale to be used for fallback.
350
+ * @param {string} [args.initialLocale] Locale to be used for the initial selection.
351
+ * @param {Formats} [args.formats] Custom named formats.
352
+ * @param {MissingKeyHandler} [args.handleMissingMessage] Called when a message key is not found.
353
+ * May return a string to use as a fallback.
354
+ */
355
+ const init = (args) => {
356
+ fallbackLocale = args.fallbackLocale;
357
+ missingMessageHandler = args.handleMissingMessage;
358
+ customFormats = args.formats ?? {};
359
+ if (args.initialLocale) locale.set(args.initialLocale);
360
+ };
361
+
362
+ // --- Formatting ---
363
+
364
+ /**
365
+ * Format a message by key.
366
+ *
367
+ * Supports two call signatures (matching svelte-i18n):
368
+ * - `format(id, options?)` — key as first argument
369
+ * - `format({ id, values, locale, default })` — options object only.
370
+ * @param {string | MessageObject} key Message key, or an object with `id` and options.
371
+ * @param {{ values?: Record<string, any>, locale?: string, default?: string }} [options] Formatting
372
+ * options when `key` is a string.
373
+ * @returns {string} The formatted message string.
374
+ */
375
+ const format = (key, { values = {}, locale: localeOverride, default: defaultString } = {}) => {
376
+ if (typeof key === 'object') {
377
+ const { id, values: v = {}, locale: l, default: d } = key;
378
+
379
+ return format(id, { values: v, locale: l, default: d });
380
+ }
381
+
382
+ const active = localeOverride ?? _locale;
383
+ const fallback = fallbackLocale;
384
+
385
+ const result =
386
+ dictionary[active]?.[key]?.format(values) ??
387
+ (active !== fallback ? dictionary[fallback]?.[key]?.format(values) : undefined);
388
+
389
+ if (result !== undefined) return result;
390
+
391
+ if (missingMessageHandler) {
392
+ const handled = missingMessageHandler(key, active, defaultString);
393
+
394
+ if (handled !== undefined) return handled;
395
+ }
396
+
397
+ return defaultString ?? key;
398
+ };
399
+
400
+ /**
401
+ * Return a nested object of formatted strings for all keys under the given prefix. Equivalent to
402
+ * svelte-i18n's `$json()`. Useful for iterating over a group of messages.
403
+ * @param {string} prefix Key prefix (e.g. `'nav'` matches `nav.home`, `nav.about`, …).
404
+ * @param {{ locale?: string }} [options] Lookup options.
405
+ * @returns {Record<string, string> | undefined} Object mapping suffix keys to formatted strings, or
406
+ * `undefined` if no keys match the prefix.
407
+ */
408
+ const json = (prefix, { locale: localeOverride } = {}) => {
409
+ const active = localeOverride ?? _locale;
410
+ const fallback = fallbackLocale;
411
+ const source = dictionary[active] ?? dictionary[fallback] ?? {};
412
+ const pfx = `${prefix}.`;
413
+ const result = /** @type {Record<string, string>} */ ({});
414
+
415
+ Object.entries(source).forEach(([key, mf]) => {
416
+ if (key.startsWith(pfx)) {
417
+ result[key.slice(pfx.length)] = mf.format({});
418
+ }
419
+ });
420
+
421
+ return Object.keys(result).length ? result : undefined;
422
+ };
423
+
424
+ // --- Date, time & number ---
425
+
426
+ // Built-in named formats matching svelte-i18n defaults
427
+ /** @type {Record<string, Intl.DateTimeFormatOptions>} */
428
+ const BUILT_IN_DATE_FORMATS = {
429
+ short: { month: 'numeric', day: 'numeric', year: '2-digit' },
430
+ medium: { month: 'short', day: 'numeric', year: 'numeric' },
431
+ long: { month: 'long', day: 'numeric', year: 'numeric' },
432
+ full: { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' },
433
+ };
434
+
435
+ /** @type {Record<string, Intl.DateTimeFormatOptions>} */
436
+ const BUILT_IN_TIME_FORMATS = {
437
+ short: { hour: 'numeric', minute: 'numeric' },
438
+ medium: { hour: 'numeric', minute: 'numeric', second: 'numeric' },
439
+ long: { hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' },
440
+ full: { hour: 'numeric', minute: 'numeric', second: 'numeric', timeZoneName: 'short' },
441
+ };
442
+
443
+ /** @type {Record<string, Intl.NumberFormatOptions>} */
444
+ const BUILT_IN_NUMBER_FORMATS = {
445
+ currency: { style: 'currency' },
446
+ percent: { style: 'percent' },
447
+ scientific: { notation: 'scientific' },
448
+ engineering: { notation: 'engineering' },
449
+ compactLong: { notation: 'compact', compactDisplay: 'long' },
450
+ compactShort: { notation: 'compact', compactDisplay: 'short' },
451
+ };
452
+
453
+ /**
454
+ * Format a date value as a localized date string.
455
+ * @param {Date} value The date to format.
456
+ * @param {DateFormatOptions} [options] Formatting options.
457
+ * @returns {string} The formatted date string.
458
+ */
459
+ const date = (value, { locale: loc, format: fmt, ...rest } = {}) => {
460
+ const named = fmt ? (customFormats.date?.[fmt] ?? BUILT_IN_DATE_FORMATS[fmt] ?? {}) : {};
461
+
462
+ return new Intl.DateTimeFormat(loc ?? _locale, { ...named, ...rest }).format(value);
463
+ };
464
+
465
+ /**
466
+ * Format a date value as a localized time string.
467
+ * @param {Date} value The date to format.
468
+ * @param {DateFormatOptions} [options] Formatting options.
469
+ * @returns {string} The formatted time string.
470
+ */
471
+ const time = (value, { locale: loc, format: fmt, ...rest } = {}) => {
472
+ const named = fmt ? (customFormats.time?.[fmt] ?? BUILT_IN_TIME_FORMATS[fmt] ?? {}) : {};
473
+
474
+ return new Intl.DateTimeFormat(loc ?? _locale, { ...named, ...rest }).format(value);
475
+ };
476
+
477
+ /**
478
+ * Format a number as a localized string.
479
+ * @param {number} value The number to format.
480
+ * @param {NumberFormatOptions} [options] Formatting options.
481
+ * @returns {string} The formatted number string.
482
+ */
483
+ const number = (value, { locale: loc, format: fmt, ...rest } = {}) => {
484
+ const named = fmt ? (customFormats.number?.[fmt] ?? BUILT_IN_NUMBER_FORMATS[fmt] ?? {}) : {};
485
+
486
+ return new Intl.NumberFormat(loc ?? _locale, { ...named, ...rest }).format(value);
487
+ };
488
+
489
+ // Export all public API as named exports, and also alias `format` as `_` and `t` for convenience.
490
+ // We cannot use `export const` syntax for each symbol because the TypeScript conversion fails to
491
+ // export the comments with the functions.
492
+ export {
493
+ format as _,
494
+ addMessages,
495
+ date,
496
+ dictionary,
497
+ format,
498
+ getLocaleFromHash,
499
+ getLocaleFromHostname,
500
+ getLocaleFromNavigator,
501
+ getLocaleFromPathname,
502
+ getLocaleFromQueryString,
503
+ init,
504
+ isLoading,
505
+ isRTL,
506
+ json,
507
+ locale,
508
+ locales,
509
+ number,
510
+ register,
511
+ format as t,
512
+ time,
513
+ waitLocale,
514
+ };
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "@sveltia/i18n",
3
+ "version": "0.1.1",
4
+ "description": "An internationalization (i18n) library for Svelte applications that uses Unicode MessageFormat 2 (MF2) to format messages.",
5
+ "keywords": [
6
+ "svelte",
7
+ "i18n",
8
+ "internationalization",
9
+ "l10n",
10
+ "localization",
11
+ "translation",
12
+ "messageformat",
13
+ "mf2"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/sveltia/sveltia-i18n.git"
18
+ },
19
+ "license": "MIT",
20
+ "author": {
21
+ "name": "Kohei Yoshino",
22
+ "url": "https://github.com/kyoshino"
23
+ },
24
+ "sideEffects": false,
25
+ "type": "module",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.svelte.d.ts",
29
+ "svelte": "./dist/index.svelte.js",
30
+ "default": "./dist/index.svelte.js"
31
+ }
32
+ },
33
+ "svelte": "./dist/index.svelte.js",
34
+ "types": "./dist/index.svelte.d.ts",
35
+ "files": [
36
+ "dist",
37
+ "!dist/**/*.test.*",
38
+ "!dist/**/*.spec.*"
39
+ ],
40
+ "scripts": {
41
+ "build": "svelte-kit sync && svelte-package",
42
+ "build:watch": "svelte-kit sync && svelte-package --watch",
43
+ "check": "pnpm run '/^check:.*/'",
44
+ "check:audit": "pnpm audit",
45
+ "check:cspell": "cspell --no-progress",
46
+ "check:eslint": "eslint .",
47
+ "check:oxlint": "oxlint .",
48
+ "check:prettier": "prettier --check .",
49
+ "check:stylelint": "stylelint '**/*.{css,scss,svelte}'",
50
+ "check:svelte": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
51
+ "dev": "vite dev",
52
+ "format": "prettier --write .",
53
+ "prepublishOnly": "npm run build",
54
+ "preview": "vite preview",
55
+ "test": "vitest run",
56
+ "test:coverage": "vitest run --coverage",
57
+ "test:watch": "vitest"
58
+ },
59
+ "dependencies": {
60
+ "messageformat": "^4.0.0",
61
+ "yaml": "^2.8.3"
62
+ },
63
+ "devDependencies": {
64
+ "@eslint/js": "^9.39.4",
65
+ "@sveltejs/adapter-auto": "^7.0.1",
66
+ "@sveltejs/kit": "^2.56.1",
67
+ "@sveltejs/package": "^2.5.7",
68
+ "@sveltejs/vite-plugin-svelte": "^7.0.0",
69
+ "@vitest/coverage-v8": "^4.1.2",
70
+ "cspell": "^9.7.0",
71
+ "eslint": "^9.39.4",
72
+ "eslint-config-airbnb-extended": "^3.0.1",
73
+ "eslint-config-prettier": "^10.1.8",
74
+ "eslint-plugin-import": "^2.32.0",
75
+ "eslint-plugin-jsdoc": "^62.9.0",
76
+ "eslint-plugin-package-json": "^0.91.1",
77
+ "eslint-plugin-svelte": "^3.17.0",
78
+ "globals": "^17.4.0",
79
+ "happy-dom": "^20.8.9",
80
+ "oxlint": "^1.58.0",
81
+ "postcss": "^8.5.8",
82
+ "postcss-html": "^1.8.1",
83
+ "prettier": "^3.8.1",
84
+ "prettier-plugin-svelte": "^3.5.1",
85
+ "stylelint": "^17.6.0",
86
+ "stylelint-config-recommended-scss": "^17.0.0",
87
+ "stylelint-scss": "^7.0.0",
88
+ "svelte": "^5.55.1",
89
+ "svelte-check": "^4.4.6",
90
+ "svelte-preprocess": "^6.0.3",
91
+ "typescript": "^5.9.3",
92
+ "vite": "^8.0.3",
93
+ "vitest": "^4.1.2"
94
+ },
95
+ "peerDependencies": {
96
+ "svelte": "^5.0.0"
97
+ },
98
+ "publishConfig": {
99
+ "access": "public"
100
+ }
101
+ }