svelte-intlayer 9.5.10 → 9.5.11
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.
|
@@ -31,14 +31,14 @@ const loadingProxy = () => new Proxy({ isLoading: true, error: null }, {
|
|
|
31
31
|
},
|
|
32
32
|
});
|
|
33
33
|
/** Merges the resolved content with the loading/error state. */
|
|
34
|
-
const withState = (value) => {
|
|
34
|
+
const withState = (value, isLoading = false) => {
|
|
35
35
|
if (Array.isArray(value)) {
|
|
36
|
-
return Object.assign(value.slice(), { isLoading
|
|
36
|
+
return Object.assign(value.slice(), { isLoading, error: null });
|
|
37
37
|
}
|
|
38
38
|
if (value && typeof value === 'object') {
|
|
39
|
-
return { ...value, isLoading
|
|
39
|
+
return { ...value, isLoading, error: null };
|
|
40
40
|
}
|
|
41
|
-
return { isLoading
|
|
41
|
+
return { isLoading, error: null };
|
|
42
42
|
};
|
|
43
43
|
/**
|
|
44
44
|
* Svelte hook for dynamic dictionary loading.
|
|
@@ -70,7 +70,15 @@ export function useDictionaryDynamic(dictionaryPromise, key, localeOrSelector) {
|
|
|
70
70
|
? localeOrSelector.locale
|
|
71
71
|
: localeOrSelector;
|
|
72
72
|
const localeStore = derived(intlayerStore, ($store) => explicitLocale ?? context?.locale ?? $store.locale);
|
|
73
|
+
// Once content has resolved, a locale switch keeps it on screen (flagged as
|
|
74
|
+
// loading) until the new chunk lands. Falling back to `loadingProxy()` would
|
|
75
|
+
// blank every text node for a frame before rendering the target locale.
|
|
76
|
+
let resolvedContent;
|
|
73
77
|
return derived(localeStore, ($locale, set) => {
|
|
78
|
+
const publish = (content) => {
|
|
79
|
+
resolvedContent = content;
|
|
80
|
+
set(withState(content));
|
|
81
|
+
};
|
|
74
82
|
// A build-tool plugin may have resolved this locale's dictionary while
|
|
75
83
|
// the entry point evaluated. Publishing it synchronously lets the store
|
|
76
84
|
// open on real content — otherwise every dynamic dictionary starts on
|
|
@@ -78,11 +86,12 @@ export function useDictionaryDynamic(dictionaryPromise, key, localeOrSelector) {
|
|
|
78
86
|
// each, even when nothing has to be fetched.
|
|
79
87
|
const preloadedDictionary = getPreloadedDictionary(dictionaryPromise, $locale);
|
|
80
88
|
if (preloadedDictionary) {
|
|
81
|
-
|
|
89
|
+
publish(getDictionary(preloadedDictionary, $locale));
|
|
82
90
|
return;
|
|
83
91
|
}
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
set(resolvedContent === undefined
|
|
93
|
+
? loadingProxy()
|
|
94
|
+
: withState(resolvedContent, true));
|
|
86
95
|
let isCancelled = false;
|
|
87
96
|
const load = async () => {
|
|
88
97
|
try {
|
|
@@ -106,7 +115,7 @@ export function useDictionaryDynamic(dictionaryPromise, key, localeOrSelector) {
|
|
|
106
115
|
}
|
|
107
116
|
if (isCancelled)
|
|
108
117
|
return;
|
|
109
|
-
|
|
118
|
+
publish(resolved);
|
|
110
119
|
}
|
|
111
120
|
catch (error) {
|
|
112
121
|
if (isCancelled)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { get } from 'svelte/store';
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
vi.mock('@intlayer/config/built', () => {
|
|
4
|
+
const internationalization = { defaultLocale: 'en', locales: ['en', 'fr'] };
|
|
5
|
+
return { internationalization, default: { internationalization } };
|
|
6
|
+
});
|
|
7
|
+
vi.mock('./intlayerContext', () => ({
|
|
8
|
+
getIntlayerContext: () => undefined,
|
|
9
|
+
}));
|
|
10
|
+
// Interpretation is covered elsewhere: expose the raw content.
|
|
11
|
+
vi.mock('../getDictionary', () => ({
|
|
12
|
+
getDictionary: (dictionary) => dictionary.content,
|
|
13
|
+
}));
|
|
14
|
+
import { intlayerStore } from './intlayerStore';
|
|
15
|
+
import { useDictionaryDynamic } from './useDictionaryDynamic';
|
|
16
|
+
/** Loader map whose chunks resolve only when the test says so. */
|
|
17
|
+
const createDeferredLoaders = () => {
|
|
18
|
+
const resolvers = new Map();
|
|
19
|
+
const createLoader = (locale) => () => new Promise((resolve) => {
|
|
20
|
+
resolvers.set(locale, resolve);
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
loaders: { en: createLoader('en'), fr: createLoader('fr') },
|
|
24
|
+
resolve: (locale, title) => resolvers.get(locale)?.({ key: 'message', content: { title } }),
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
28
|
+
describe('useDictionaryDynamic', () => {
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
intlayerStore.reset();
|
|
31
|
+
});
|
|
32
|
+
it('keeps the resolved content, flagged as loading, while the next locale loads', async () => {
|
|
33
|
+
const { loaders, resolve } = createDeferredLoaders();
|
|
34
|
+
const content = useDictionaryDynamic(loaders, 'message');
|
|
35
|
+
const unsubscribe = content.subscribe(() => { });
|
|
36
|
+
expect(get(content).isLoading).toBe(true);
|
|
37
|
+
resolve('en', 'Hello');
|
|
38
|
+
await flushPromises();
|
|
39
|
+
expect(get(content)).toMatchObject({ title: 'Hello', isLoading: false });
|
|
40
|
+
intlayerStore.setLocale('fr');
|
|
41
|
+
expect(get(content)).toMatchObject({ title: 'Hello', isLoading: true });
|
|
42
|
+
resolve('fr', 'Bonjour');
|
|
43
|
+
await flushPromises();
|
|
44
|
+
expect(get(content)).toMatchObject({ title: 'Bonjour', isLoading: false });
|
|
45
|
+
unsubscribe();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import type { ResolvedEditor } from '@intlayer/types/module_augmentation';
|
|
1
2
|
type IntlayerNodeProps = {
|
|
2
3
|
value: any;
|
|
3
4
|
component: any;
|
|
4
5
|
props: Record<string, any>;
|
|
5
6
|
additionalProps?: Record<string, any>;
|
|
6
7
|
};
|
|
7
|
-
|
|
8
|
+
type IntlayerNodeComponent<T> = {
|
|
8
9
|
new (...args: any[]): any;
|
|
9
10
|
(anchor: any, props: any): any;
|
|
10
11
|
value: T;
|
|
11
|
-
}
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Content node returned by `useIntlayer`: a component (`<Title />`).
|
|
15
|
+
*
|
|
16
|
+
* Without the editor it is also typed as its value, so it can be used wherever
|
|
17
|
+
* a string is expected. With the editor, it renders the editable selector, so
|
|
18
|
+
* string usages must go through `.value`.
|
|
19
|
+
*/
|
|
20
|
+
export type IntlayerNode<T, AdditionalProps = Record<string, any>> = ResolvedEditor<IntlayerNodeComponent<T> & AdditionalProps & T, IntlayerNodeComponent<T> & AdditionalProps>;
|
|
12
21
|
export declare const renderIntlayerNode: <T, AdditionalProps = Record<string, any>>(args: IntlayerNodeProps) => IntlayerNode<T, AdditionalProps>;
|
|
13
22
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-intlayer",
|
|
3
|
-
"version": "9.5.
|
|
3
|
+
"version": "9.5.11",
|
|
4
4
|
"description": "Easily internationalize i18n your Svelte applications with type-safe multilingual content management.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"intlayer",
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
"typecheck": "tsc --noEmit --project tsconfig.types.json"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@intlayer/api": "9.5.
|
|
87
|
-
"@intlayer/config": "9.5.
|
|
88
|
-
"@intlayer/core": "9.5.
|
|
89
|
-
"@intlayer/editor": "9.5.
|
|
90
|
-
"@intlayer/types": "9.5.
|
|
86
|
+
"@intlayer/api": "9.5.11",
|
|
87
|
+
"@intlayer/config": "9.5.11",
|
|
88
|
+
"@intlayer/core": "9.5.11",
|
|
89
|
+
"@intlayer/editor": "9.5.11",
|
|
90
|
+
"@intlayer/types": "9.5.11"
|
|
91
91
|
},
|
|
92
92
|
"devDependencies": {
|
|
93
93
|
"@sveltejs/adapter-auto": "7.0.1",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"svelte": ">=5.0.0"
|
|
110
110
|
},
|
|
111
111
|
"optionalDependencies": {
|
|
112
|
-
"@intlayer/analytics": "9.5.
|
|
112
|
+
"@intlayer/analytics": "9.5.11"
|
|
113
113
|
},
|
|
114
114
|
"engines": {
|
|
115
115
|
"node": ">=14.18"
|