@urbicon-ui/i18n 6.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 +322 -0
- package/dist/components/I18nProvider.svelte +62 -0
- package/dist/components/I18nProvider.svelte.d.ts +28 -0
- package/dist/components/T.svelte +31 -0
- package/dist/components/T.svelte.d.ts +11 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/i18n/__fixtures__/SetLocaleChild.svelte +20 -0
- package/dist/i18n/__fixtures__/SetLocaleChild.svelte.d.ts +18 -0
- package/dist/i18n/__fixtures__/SetLocaleHarness.svelte +14 -0
- package/dist/i18n/__fixtures__/SetLocaleHarness.svelte.d.ts +6 -0
- package/dist/i18n/__fixtures__/SsrChild.svelte +7 -0
- package/dist/i18n/__fixtures__/SsrChild.svelte.d.ts +18 -0
- package/dist/i18n/__fixtures__/SsrHarness.svelte +15 -0
- package/dist/i18n/__fixtures__/SsrHarness.svelte.d.ts +7 -0
- package/dist/i18n/context.svelte.d.ts +107 -0
- package/dist/i18n/context.svelte.js +167 -0
- package/dist/i18n/package-integration.d.ts +118 -0
- package/dist/i18n/package-integration.js +211 -0
- package/dist/i18n/registry.svelte.d.ts +93 -0
- package/dist/i18n/registry.svelte.js +460 -0
- package/dist/i18n/resolve-locale.d.ts +41 -0
- package/dist/i18n/resolve-locale.js +81 -0
- package/dist/i18n/types.d.ts +127 -0
- package/dist/i18n/types.js +18 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/dist/utils/deep-keys.d.ts +27 -0
- package/dist/utils/deep-keys.js +54 -0
- package/package.json +74 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { DeepKeys } from '../utils/deep-keys';
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for the locales the library declares support for.
|
|
4
|
+
*
|
|
5
|
+
* "Declared, data optional": `en`/`de` ship translation data; `fr`/`es`/`it`/`nl`
|
|
6
|
+
* are valid target locales a consumer can register its own bundles for. The list
|
|
7
|
+
* lives here exactly once — both the `Locale` union and the runtime
|
|
8
|
+
* `isLocaleSupported` guard derive from it, so the type and the runtime check can
|
|
9
|
+
* never drift apart (previously the same six codes were hardcoded twice).
|
|
10
|
+
*/
|
|
11
|
+
export declare const SUPPORTED_LOCALES: readonly ["en", "de", "fr", "es", "it", "nl"];
|
|
12
|
+
export type Locale = (typeof SUPPORTED_LOCALES)[number];
|
|
13
|
+
/**
|
|
14
|
+
* Runtime guard derived from {@link SUPPORTED_LOCALES} — the single source of
|
|
15
|
+
* truth. Used by both the registry and the request-scoped locale state, so the
|
|
16
|
+
* type and the runtime check can never drift apart.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isLocaleSupported(locale: string): locale is Locale;
|
|
19
|
+
export type Translations = {
|
|
20
|
+
[key: string]: string | Translations;
|
|
21
|
+
};
|
|
22
|
+
export type PackageTranslations = Partial<Record<Locale, Translations>>;
|
|
23
|
+
/**
|
|
24
|
+
* Base translation function signature
|
|
25
|
+
*/
|
|
26
|
+
export type TranslationFunction = (key: string, params?: Record<string, unknown>) => string;
|
|
27
|
+
/**
|
|
28
|
+
* Translation store interface
|
|
29
|
+
*/
|
|
30
|
+
export interface TranslationStore {
|
|
31
|
+
locale: Locale;
|
|
32
|
+
translations: PackageTranslations;
|
|
33
|
+
t: TranslationFunction;
|
|
34
|
+
}
|
|
35
|
+
export type TranslationParams = Record<string, string | number | boolean | (() => string)>;
|
|
36
|
+
type ExtractParams<T extends string> = T extends `${string}{{${infer Param}}}${infer Rest}` ? Param | ExtractParams<Rest> : never;
|
|
37
|
+
export type TranslationParametersFor<T extends Translations, K extends DeepKeys<T>> = PathValue<T, K> extends string ? ExtractParams<PathValue<T, K>> extends never ? Record<string, never> : Record<ExtractParams<PathValue<T, K>>, string | number | boolean> : TranslationParams;
|
|
38
|
+
export type TypedTranslationFunction<T extends Translations> = <K extends DeepKeys<T>>(key: K, ...args: TranslationParametersFor<T, K> extends Record<string, never> ? [params?: TranslationParams, options?: TranslationOptions] : [params: TranslationParametersFor<T, K>, options?: TranslationOptions]) => string;
|
|
39
|
+
export interface I18nStore {
|
|
40
|
+
locale: Locale;
|
|
41
|
+
translations: Partial<Record<Locale, Translations>>;
|
|
42
|
+
fallbackLocale: Locale;
|
|
43
|
+
packageTranslations: Map<string, Partial<Record<Locale, Translations>>>;
|
|
44
|
+
loadedLocales: Set<Locale>;
|
|
45
|
+
loadingLocales: Set<Locale>;
|
|
46
|
+
}
|
|
47
|
+
export interface I18nConfig {
|
|
48
|
+
defaultLocale?: Locale;
|
|
49
|
+
fallbackLocale?: Locale;
|
|
50
|
+
translations?: Partial<Record<Locale, Translations>>;
|
|
51
|
+
detectBrowserLocale?: boolean;
|
|
52
|
+
lazyLoad?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Optional error handler. Invoked when a registered loader rejects
|
|
55
|
+
* (loadLocale failure) or when an unsupported locale is requested
|
|
56
|
+
* (setLocale failure). When omitted, the service falls back to
|
|
57
|
+
* `console.warn` so consumers still see something during development.
|
|
58
|
+
*/
|
|
59
|
+
onError?: (error: I18nError) => void;
|
|
60
|
+
}
|
|
61
|
+
export type I18nError = {
|
|
62
|
+
type: 'load-failed';
|
|
63
|
+
locale: Locale;
|
|
64
|
+
cause: unknown;
|
|
65
|
+
} | {
|
|
66
|
+
type: 'unsupported-locale';
|
|
67
|
+
locale: string;
|
|
68
|
+
} | {
|
|
69
|
+
type: 'load-failed-no-fallback';
|
|
70
|
+
locale: Locale;
|
|
71
|
+
};
|
|
72
|
+
type WidenStringLiteralsDeep<T> = T extends string ? string : T extends Array<infer U> ? Array<WidenStringLiteralsDeep<U>> : T extends object ? {
|
|
73
|
+
[K in keyof T]: WidenStringLiteralsDeep<T[K]>;
|
|
74
|
+
} : T;
|
|
75
|
+
export type TranslationSchema<T extends Translations> = WidenStringLiteralsDeep<T>;
|
|
76
|
+
export interface TranslationOptions {
|
|
77
|
+
packageName?: string;
|
|
78
|
+
fallbackToGlobal?: boolean;
|
|
79
|
+
interpolate?: boolean;
|
|
80
|
+
}
|
|
81
|
+
export type TranslationLoader = (locale: Locale) => Promise<Translations>;
|
|
82
|
+
export type PackageTranslationLoader = (packageName: string, locale: Locale) => Promise<Translations>;
|
|
83
|
+
type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
|
84
|
+
export interface PluralRules {
|
|
85
|
+
zero?: string;
|
|
86
|
+
one?: string;
|
|
87
|
+
two?: string;
|
|
88
|
+
few?: string;
|
|
89
|
+
many?: string;
|
|
90
|
+
other: string;
|
|
91
|
+
}
|
|
92
|
+
export interface PluralParams extends TranslationParams {
|
|
93
|
+
count: number;
|
|
94
|
+
}
|
|
95
|
+
export type ValidateTemplate<T extends string> = T extends `${string}{{${string}}}${string}` ? T : never;
|
|
96
|
+
export type CreatePackageTypes<T extends Translations> = {
|
|
97
|
+
keys: DeepKeys<T>;
|
|
98
|
+
params: {
|
|
99
|
+
[K in DeepKeys<T>]: PathValue<T, K> extends string ? ExtractParams<PathValue<T, K>> extends never ? Record<string, never> : Record<ExtractParams<PathValue<T, K>>, string | number | boolean> : Record<string, never>;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
export interface PackageI18n<T extends Translations> {
|
|
103
|
+
/**
|
|
104
|
+
* Context-scoped translation **hook**. Call during component initialisation to
|
|
105
|
+
* get a typed `t` bound to the nearest `<I18nProvider>`'s locale (or the base
|
|
106
|
+
* locale when none is mounted). This is the SSR-correct, reactive accessor —
|
|
107
|
+
* re-exported by consumers as `use<Package>I18n`.
|
|
108
|
+
*/
|
|
109
|
+
useTranslate: () => TypedTranslationFunction<T>;
|
|
110
|
+
/**
|
|
111
|
+
* Typed `t` for non-component use (tests, server-side utilities) where no
|
|
112
|
+
* component context is available. Not bound to a `<I18nProvider>`. In
|
|
113
|
+
* components always prefer {@link useTranslate}, which is provider-scoped and
|
|
114
|
+
* reactive.
|
|
115
|
+
*/
|
|
116
|
+
t: TypedTranslationFunction<T>;
|
|
117
|
+
exists: (key: string) => boolean;
|
|
118
|
+
getLocales: () => Locale[];
|
|
119
|
+
register: () => void;
|
|
120
|
+
types: CreatePackageTypes<T>;
|
|
121
|
+
}
|
|
122
|
+
export interface I18nComponentProps {
|
|
123
|
+
locale?: Locale;
|
|
124
|
+
fallbackToGlobal?: boolean;
|
|
125
|
+
useI18n?: boolean;
|
|
126
|
+
}
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the locales the library declares support for.
|
|
3
|
+
*
|
|
4
|
+
* "Declared, data optional": `en`/`de` ship translation data; `fr`/`es`/`it`/`nl`
|
|
5
|
+
* are valid target locales a consumer can register its own bundles for. The list
|
|
6
|
+
* lives here exactly once — both the `Locale` union and the runtime
|
|
7
|
+
* `isLocaleSupported` guard derive from it, so the type and the runtime check can
|
|
8
|
+
* never drift apart (previously the same six codes were hardcoded twice).
|
|
9
|
+
*/
|
|
10
|
+
export const SUPPORTED_LOCALES = ['en', 'de', 'fr', 'es', 'it', 'nl'];
|
|
11
|
+
/**
|
|
12
|
+
* Runtime guard derived from {@link SUPPORTED_LOCALES} — the single source of
|
|
13
|
+
* truth. Used by both the registry and the request-scoped locale state, so the
|
|
14
|
+
* type and the runtime check can never drift apart.
|
|
15
|
+
*/
|
|
16
|
+
export function isLocaleSupported(locale) {
|
|
17
|
+
return SUPPORTED_LOCALES.includes(locale);
|
|
18
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { I18nProvider, T } from './components';
|
|
2
|
+
export type { I18nApi, I18nConfigureOptions } from './i18n/context.svelte';
|
|
3
|
+
export { BASE_LOCALE, configureI18n, provideI18n, useI18n } from './i18n/context.svelte';
|
|
4
|
+
export type { CreatePackageI18nOptions } from './i18n/package-integration';
|
|
5
|
+
export { createComponentI18n, createPackageI18n, createPackageTranslations, createTypedPackage, registerPackages, registerTranslationLoaders, validatePackageTranslations } from './i18n/package-integration';
|
|
6
|
+
export type { LocaleSource, ResolveLocaleOptions } from './i18n/resolve-locale';
|
|
7
|
+
export { resolveLocale } from './i18n/resolve-locale';
|
|
8
|
+
export type { CreatePackageTypes, I18nComponentProps, I18nConfig, I18nError, I18nStore, Locale, PackageI18n, PackageTranslations, PluralParams, PluralRules, TranslationFunction, TranslationLoader, TranslationOptions, TranslationParams, Translations, TypedTranslationFunction } from './i18n/types';
|
|
9
|
+
export { isLocaleSupported, SUPPORTED_LOCALES } from './i18n/types';
|
|
10
|
+
export type { DeepKeys, DeepValue } from './utils/deep-keys';
|
|
11
|
+
export { collectDeepKeys, getDeepValue, hasDeepKey } from './utils/deep-keys';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// --- Request-scoped i18n (WP2: SSR-correct, Context-based) ---
|
|
2
|
+
// Provider + general hook: mount one <I18nProvider> at the app root, read locale
|
|
3
|
+
// control and locale-aware formatting via useI18n() inside components.
|
|
4
|
+
export { I18nProvider, T } from './components';
|
|
5
|
+
export { BASE_LOCALE, configureI18n, provideI18n, useI18n } from './i18n/context.svelte';
|
|
6
|
+
// Package integration utilities
|
|
7
|
+
export { createComponentI18n, createPackageI18n, createPackageTranslations, createTypedPackage, registerPackages, registerTranslationLoaders, validatePackageTranslations } from './i18n/package-integration';
|
|
8
|
+
// Server-side initial-locale resolution (cookie + Accept-Language) for the
|
|
9
|
+
// provider's `locale` prop. SSR/hydration-stable.
|
|
10
|
+
export { resolveLocale } from './i18n/resolve-locale';
|
|
11
|
+
// Supported-locale list + runtime guard — single source of truth; `Locale` derives from it.
|
|
12
|
+
export { isLocaleSupported, SUPPORTED_LOCALES } from './i18n/types';
|
|
13
|
+
export { collectDeepKeys, getDeepValue, hasDeepKey } from './utils/deep-keys';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility type to extract all possible deep keys from a nested object
|
|
3
|
+
* Used for type-safe translation key access
|
|
4
|
+
*/
|
|
5
|
+
export type DeepKeys<T> = T extends Record<string, unknown> ? {
|
|
6
|
+
[K in keyof T]: K extends string ? T[K] extends Record<string, unknown> ? `${K}` | `${K}.${DeepKeys<T[K]>}` : `${K}` : never;
|
|
7
|
+
}[keyof T] : never;
|
|
8
|
+
/**
|
|
9
|
+
* Utility type to get the value at a specific deep key path
|
|
10
|
+
*/
|
|
11
|
+
export type DeepValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? DeepValue<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
|
12
|
+
/**
|
|
13
|
+
* Runtime function to get nested value from object
|
|
14
|
+
*/
|
|
15
|
+
export declare function getDeepValue<T extends Record<string, unknown>>(obj: T, path: string): unknown;
|
|
16
|
+
/**
|
|
17
|
+
* Runtime function to check if a deep key exists in an object
|
|
18
|
+
*/
|
|
19
|
+
export declare function hasDeepKey<T extends Record<string, unknown>>(obj: T, path: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Runtime counterpart to the `DeepKeys` type: collects every leaf-key path of a
|
|
22
|
+
* nested translation object (dotted, e.g. `form.label.required`). A leaf is any
|
|
23
|
+
* non-object value (the translation strings); nested objects recurse. Used by
|
|
24
|
+
* `validatePackageTranslations` to diff locale bundles structurally — two
|
|
25
|
+
* bundles with the same leaf-key set are key-compatible.
|
|
26
|
+
*/
|
|
27
|
+
export declare function collectDeepKeys(obj: Record<string, unknown>, prefix?: string): string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime function to get nested value from object
|
|
3
|
+
*/
|
|
4
|
+
export function getDeepValue(obj, path) {
|
|
5
|
+
const keys = path.split('.');
|
|
6
|
+
let value = obj;
|
|
7
|
+
for (const key of keys) {
|
|
8
|
+
if (typeof value !== 'object' || value === null)
|
|
9
|
+
return undefined;
|
|
10
|
+
value = value[key];
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runtime function to check if a deep key exists in an object
|
|
16
|
+
*/
|
|
17
|
+
export function hasDeepKey(obj, path) {
|
|
18
|
+
const keys = path.split('.');
|
|
19
|
+
let value = obj;
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
if (typeof value !== 'object' || value === null || !(key in value)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
value = value[key];
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Runtime counterpart to the `DeepKeys` type: collects every leaf-key path of a
|
|
30
|
+
* nested translation object (dotted, e.g. `form.label.required`). A leaf is any
|
|
31
|
+
* non-object value (the translation strings); nested objects recurse. Used by
|
|
32
|
+
* `validatePackageTranslations` to diff locale bundles structurally — two
|
|
33
|
+
* bundles with the same leaf-key set are key-compatible.
|
|
34
|
+
*/
|
|
35
|
+
export function collectDeepKeys(obj, prefix = '') {
|
|
36
|
+
const entries = Object.entries(obj);
|
|
37
|
+
// An empty nested object still occupies its key path — emit it instead of
|
|
38
|
+
// recursing into nothing, so `{ a: {} }` yields `['a']` rather than `[]`.
|
|
39
|
+
// Otherwise a structural divergence like `{a:{}}` vs `{a:'x'}` would go
|
|
40
|
+
// undetected. (The empty ROOT object, prefix='', correctly yields `[]`.)
|
|
41
|
+
if (entries.length === 0)
|
|
42
|
+
return prefix ? [prefix] : [];
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const [key, value] of entries) {
|
|
45
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
46
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
47
|
+
out.push(...collectDeepKeys(value, path));
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
out.push(path);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@urbicon-ui/i18n",
|
|
3
|
+
"version": "6.1.4",
|
|
4
|
+
"description": "Runes-based localization for Svelte 5 apps and the Urbicon UI design system",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://codeberg.org/urbicon/ui.git",
|
|
9
|
+
"directory": "packages/i18n"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://ui.urbicon.de",
|
|
12
|
+
"bugs": "https://codeberg.org/urbicon/ui/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"svelte",
|
|
15
|
+
"i18n",
|
|
16
|
+
"internationalization",
|
|
17
|
+
"localization",
|
|
18
|
+
"urbicon-ui",
|
|
19
|
+
"design-system"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@sveltejs/kit": "^2.65.2",
|
|
24
|
+
"@sveltejs/package": "^2.5.8",
|
|
25
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
26
|
+
"@types/node": "^25.9.3",
|
|
27
|
+
"@urbicon-ui/shared-types": "6.1.4",
|
|
28
|
+
"prettier": "^3.8.4",
|
|
29
|
+
"prettier-plugin-svelte": "^4.1.1",
|
|
30
|
+
"prettier-plugin-tailwindcss": "^0.8.0",
|
|
31
|
+
"svelte": "^5.56.3",
|
|
32
|
+
"svelte-check": "^4.6.0",
|
|
33
|
+
"typescript": "^6.0.3",
|
|
34
|
+
"vite": "^8.0.16",
|
|
35
|
+
"vitest": "^4.1.9"
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"svelte": "./dist/index.js",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"!dist/**/*.test.*",
|
|
47
|
+
"!dist/**/*.spec.*",
|
|
48
|
+
"README.md"
|
|
49
|
+
],
|
|
50
|
+
"main": "dist/index.js",
|
|
51
|
+
"sideEffects": false,
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@sveltejs/kit": "^2.65.2",
|
|
54
|
+
"svelte": "^5.56.3",
|
|
55
|
+
"@urbicon-ui/shared-types": "^6.0.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"dev": "svelte-package --watch",
|
|
59
|
+
"build": "svelte-kit sync && svelte-package",
|
|
60
|
+
"clean": "rm -rf dist .svelte-kit",
|
|
61
|
+
"clean-all": "bun --bun run clean && rm -rf node_modules",
|
|
62
|
+
"package": "svelte-kit sync && svelte-package",
|
|
63
|
+
"preview": "vite preview",
|
|
64
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
65
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
66
|
+
"format": "biome format --write . && prettier --write \"**/*.svelte\"",
|
|
67
|
+
"lint": "biome check . && svelte-check --tsconfig ./tsconfig.json",
|
|
68
|
+
"test": "vitest",
|
|
69
|
+
"test:run": "vitest run"
|
|
70
|
+
},
|
|
71
|
+
"svelte": "./dist/index.js",
|
|
72
|
+
"type": "module",
|
|
73
|
+
"types": "./dist/index.d.ts"
|
|
74
|
+
}
|