svelte-intlayer 9.5.10 → 9.5.12
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/dist/client/useDictionaryDynamic.js +17 -8
- package/dist/client/useDictionaryDynamic.test.d.ts +1 -0
- package/dist/client/useDictionaryDynamic.test.js +47 -0
- package/dist/plugins.js +5 -5
- package/dist/renderIntlayerNode.d.ts +11 -2
- package/dist/renderIntlayerNode.js +0 -1
- package/package.json +7 -7
|
@@ -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
|
+
});
|
package/dist/plugins.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { editor, internationalization } from '@intlayer/config/built';
|
|
2
|
-
import { conditionPlugin, enumerationPlugin, fallbackPlugin, filePlugin, genderPlugin, isInterpolableWrapperNode, nestedPlugin, pluralPlugin, resolveInsertedSelector, selectPlugin, transformInterpolableNode, translationPlugin, } from '@intlayer/core/interpreter';
|
|
2
|
+
import { conditionPlugin, enumerationPlugin, fallbackPlugin, filePlugin, genderPlugin, isInterpolableWrapperNode, nestedPlugin, pluralPlugin, resolveInsertedSelector, resolveInterpreterLocale, selectPlugin, transformInterpolableNode, translationPlugin, } from '@intlayer/core/interpreter';
|
|
3
3
|
import * as NodeTypes from '@intlayer/types/nodeType';
|
|
4
4
|
import { reportExposure } from './analytics/exposureSink';
|
|
5
5
|
import { default as ContentSelector } from './editor/ContentSelector.svelte';
|
|
@@ -321,7 +321,7 @@ const pluginsCache = new Map();
|
|
|
321
321
|
* This function is used by both getIntlayer and getDictionary to ensure consistent plugin configuration.
|
|
322
322
|
*/
|
|
323
323
|
export const getPlugins = (locale, fallback = true) => {
|
|
324
|
-
const currentLocale = locale
|
|
324
|
+
const currentLocale = resolveInterpreterLocale(locale);
|
|
325
325
|
const cacheKey = `${currentLocale}_${fallback}`;
|
|
326
326
|
if (pluginsCache.has(cacheKey)) {
|
|
327
327
|
return pluginsCache.get(cacheKey);
|
|
@@ -329,11 +329,11 @@ export const getPlugins = (locale, fallback = true) => {
|
|
|
329
329
|
const plugins = [
|
|
330
330
|
// First: most nodes are plain strings, which every other plugin rejects
|
|
331
331
|
intlayerNodePlugins,
|
|
332
|
-
translationPlugin(
|
|
332
|
+
translationPlugin(currentLocale, fallback ? internationalization.defaultLocale : undefined),
|
|
333
333
|
enumerationPlugin,
|
|
334
|
-
pluralPlugin(
|
|
334
|
+
pluralPlugin(currentLocale),
|
|
335
335
|
conditionPlugin,
|
|
336
|
-
nestedPlugin(
|
|
336
|
+
nestedPlugin(currentLocale),
|
|
337
337
|
filePlugin,
|
|
338
338
|
genderPlugin,
|
|
339
339
|
selectPlugin,
|
|
@@ -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.12",
|
|
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.12",
|
|
87
|
+
"@intlayer/config": "9.5.12",
|
|
88
|
+
"@intlayer/core": "9.5.12",
|
|
89
|
+
"@intlayer/editor": "9.5.12",
|
|
90
|
+
"@intlayer/types": "9.5.12"
|
|
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.12"
|
|
113
113
|
},
|
|
114
114
|
"engines": {
|
|
115
115
|
"node": ">=14.18"
|