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: false, error: null });
36
+ return Object.assign(value.slice(), { isLoading, error: null });
37
37
  }
38
38
  if (value && typeof value === 'object') {
39
- return { ...value, isLoading: false, error: null };
39
+ return { ...value, isLoading, error: null };
40
40
  }
41
- return { isLoading: false, error: null };
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
- set(withState(getDictionary(preloadedDictionary, $locale)));
89
+ publish(getDictionary(preloadedDictionary, $locale));
82
90
  return;
83
91
  }
84
- // Set loading state immediately with proxy
85
- set(loadingProxy());
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
- set(withState(resolved));
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
- export type IntlayerNode<T, AdditionalProps = Record<string, any>> = {
8
+ type IntlayerNodeComponent<T> = {
8
9
  new (...args: any[]): any;
9
10
  (anchor: any, props: any): any;
10
11
  value: T;
11
- } & AdditionalProps & T;
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 {};
@@ -1,4 +1,3 @@
1
- // import type { ResolvedEditor } from '@intlayer/types/module_augmentation';
2
1
  import { getIntlayerNodePrototype } from '@intlayer/core/utils';
3
2
  import IntlayerNodeWrapper from './IntlayerNodeWrapper.svelte';
4
3
  export const renderIntlayerNode = (args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-intlayer",
3
- "version": "9.5.10",
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.10",
87
- "@intlayer/config": "9.5.10",
88
- "@intlayer/core": "9.5.10",
89
- "@intlayer/editor": "9.5.10",
90
- "@intlayer/types": "9.5.10"
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.10"
112
+ "@intlayer/analytics": "9.5.11"
113
113
  },
114
114
  "engines": {
115
115
  "node": ">=14.18"