@bleedingdev/modern-js-plugin-i18n 3.9.0-ultramodern.2 → 3.9.0-ultramodern.20
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/cjs/cli/index.js +10 -21
- package/dist/cjs/cli/localisedRoutes.js +47 -0
- package/dist/cjs/runtime/Link.js +19 -32
- package/dist/cjs/runtime/context.js +30 -73
- package/dist/cjs/runtime/contextHelpers.js +5 -5
- package/dist/cjs/runtime/contexts.js +49 -0
- package/dist/cjs/runtime/core.js +27 -14
- package/dist/cjs/runtime/hooks.js +7 -8
- package/dist/cjs/runtime/localizedPaths.js +11 -9
- package/dist/cjs/runtime/navigation.js +48 -0
- package/dist/cjs/runtime/providerComposition.js +37 -13
- package/dist/cjs/runtime/routerAdapter.js +33 -140
- package/dist/cjs/runtime/sharedContexts.js +43 -0
- package/dist/cjs/runtime/utils.js +21 -19
- package/dist/cjs/server/defaultUrlStrategy.js +48 -0
- package/dist/cjs/server/index.js +11 -7
- package/dist/cjs/server/mappedUrlStrategy.js +43 -0
- package/dist/cjs/server/redirectPolicy.js +33 -24
- package/dist/cjs/shared/mappedUrlStrategy.js +63 -0
- package/dist/cjs/shared/urlStrategy.js +48 -0
- package/dist/esm/cli/index.mjs +10 -21
- package/dist/esm/cli/localisedRoutes.mjs +9 -0
- package/dist/esm/runtime/Link.mjs +19 -32
- package/dist/esm/runtime/context.mjs +20 -72
- package/dist/esm/runtime/contextHelpers.mjs +5 -5
- package/dist/esm/runtime/contexts.mjs +5 -0
- package/dist/esm/runtime/core.mjs +13 -6
- package/dist/esm/runtime/hooks.mjs +7 -8
- package/dist/esm/runtime/localizedPaths.mjs +11 -9
- package/dist/esm/runtime/navigation.mjs +7 -0
- package/dist/esm/runtime/providerComposition.mjs +38 -14
- package/dist/esm/runtime/routerAdapter.mjs +28 -141
- package/dist/esm/runtime/sharedContexts.mjs +1 -0
- package/dist/esm/runtime/utils.mjs +19 -17
- package/dist/esm/server/defaultUrlStrategy.mjs +7 -0
- package/dist/esm/server/index.mjs +11 -7
- package/dist/esm/server/mappedUrlStrategy.mjs +1 -0
- package/dist/esm/server/redirectPolicy.mjs +33 -24
- package/dist/esm/shared/mappedUrlStrategy.mjs +19 -0
- package/dist/esm/shared/urlStrategy.mjs +7 -0
- package/dist/esm-node/cli/index.mjs +10 -21
- package/dist/esm-node/cli/localisedRoutes.mjs +10 -0
- package/dist/esm-node/runtime/Link.mjs +19 -32
- package/dist/esm-node/runtime/context.mjs +20 -72
- package/dist/esm-node/runtime/contextHelpers.mjs +5 -5
- package/dist/esm-node/runtime/contexts.mjs +6 -0
- package/dist/esm-node/runtime/core.mjs +13 -6
- package/dist/esm-node/runtime/hooks.mjs +7 -8
- package/dist/esm-node/runtime/localizedPaths.mjs +11 -9
- package/dist/esm-node/runtime/navigation.mjs +8 -0
- package/dist/esm-node/runtime/providerComposition.mjs +38 -14
- package/dist/esm-node/runtime/routerAdapter.mjs +28 -141
- package/dist/esm-node/runtime/sharedContexts.mjs +2 -0
- package/dist/esm-node/runtime/utils.mjs +19 -17
- package/dist/esm-node/server/defaultUrlStrategy.mjs +8 -0
- package/dist/esm-node/server/index.mjs +11 -7
- package/dist/esm-node/server/mappedUrlStrategy.mjs +2 -0
- package/dist/esm-node/server/redirectPolicy.mjs +33 -24
- package/dist/esm-node/shared/mappedUrlStrategy.mjs +20 -0
- package/dist/esm-node/shared/urlStrategy.mjs +8 -0
- package/dist/types/cli/localisedRoutes.d.ts +19 -0
- package/dist/types/runtime/context.d.ts +8 -11
- package/dist/types/runtime/contextHelpers.d.ts +2 -2
- package/dist/types/runtime/contexts.d.ts +6 -0
- package/dist/types/runtime/core.d.ts +12 -2
- package/dist/types/runtime/hooks.d.ts +4 -5
- package/dist/types/runtime/localizedPaths.d.ts +4 -4
- package/dist/types/runtime/navigation.d.ts +10 -0
- package/dist/types/runtime/providerComposition.d.ts +13 -1
- package/dist/types/runtime/routerAdapter.d.ts +23 -6
- package/dist/types/runtime/sharedContexts.d.ts +1 -0
- package/dist/types/runtime/utils.d.ts +4 -12
- package/dist/types/server/defaultUrlStrategy.d.ts +11 -0
- package/dist/types/server/index.d.ts +2 -0
- package/dist/types/server/mappedUrlStrategy.d.ts +7 -0
- package/dist/types/server/redirectPolicy.d.ts +5 -13
- package/dist/types/shared/mappedUrlStrategy.d.ts +27 -0
- package/dist/types/shared/type.d.ts +7 -11
- package/dist/types/shared/urlStrategy.d.ts +18 -0
- package/package.json +42 -19
- package/rslib.config.mts +8 -1
- package/rstest.config.mts +6 -5
- package/src/cli/index.ts +42 -42
- package/src/cli/localisedRoutes.ts +53 -0
- package/src/runtime/Link.tsx +20 -40
- package/src/runtime/context.tsx +33 -157
- package/src/runtime/contextHelpers.ts +7 -5
- package/src/runtime/contexts.ts +16 -0
- package/src/runtime/core.tsx +53 -6
- package/src/runtime/hooks.ts +14 -9
- package/src/runtime/i18n/instance.ts +1 -2
- package/src/runtime/localizedPaths.ts +24 -19
- package/src/runtime/navigation.tsx +20 -0
- package/src/runtime/providerComposition.tsx +71 -23
- package/src/runtime/routerAdapter.tsx +65 -339
- package/src/runtime/sharedContexts.ts +8 -0
- package/src/runtime/utils.ts +49 -31
- package/src/server/defaultUrlStrategy.ts +27 -0
- package/src/server/index.ts +39 -9
- package/src/server/mappedUrlStrategy.ts +10 -0
- package/src/server/redirectPolicy.ts +85 -70
- package/src/shared/mappedUrlStrategy.ts +59 -0
- package/src/shared/type.ts +7 -11
- package/src/shared/urlStrategy.ts +38 -0
- package/tests/backendDefaults.test.ts +2 -23
- package/tests/defaultUrlStrategy.test.ts +107 -0
- package/tests/federatedI18nBoundaryResolver.test.tsx +65 -0
- package/tests/i18nUtils.test.ts +31 -65
- package/tests/link.test.tsx +13 -511
- package/tests/localisedRouteGeneration.test.ts +200 -0
- package/tests/localisedUrls.test.ts +16 -208
- package/tests/mappedUrlStrategyDerivation.test.ts +62 -0
- package/tests/navigation.test.tsx +403 -0
- package/tests/reactI18nextRuntimeBoundary.test.ts +8 -83
- package/tests/redirectPolicy.test.ts +89 -27
- package/tests/routerAdapter.test.tsx +98 -1171
- package/tests/urlStrategySerialization.test.ts +122 -0
- package/tsconfig.json +4 -2
- package/tests/federatedI18nBoundary.client.test.tsx +0 -129
- package/tests/federatedI18nBoundary.test.tsx +0 -132
- package/tests/linkTypes.test.ts +0 -59
- package/tests/localisedUrlRewriteMatrix.fork.test.ts +0 -147
- package/tests/peerCohort.test.ts +0 -25
- package/tests/type-fixture/fixture-globals.d.ts +0 -11
- package/tests/type-fixture/i18nInstanceTypes.fixture.ts +0 -29
- package/tests/type-fixture/linkTypes.fixture.tsx +0 -51
- package/tests/type-fixture/tsconfig.json +0 -16
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
canonicalTargetPathname,
|
|
3
|
-
type LocalisedUrlsOption,
|
|
4
|
-
} from '@modern-js/i18n-runtime-extensions';
|
|
5
1
|
import { useMemo } from 'react';
|
|
2
|
+
import type { I18nUrlStrategy } from '../shared/urlStrategy';
|
|
6
3
|
import { useModernI18n } from './context';
|
|
7
4
|
import { useI18nRouterAdapter } from './routerAdapter';
|
|
8
5
|
import { buildLocalizedUrl, splitUrlTarget } from './utils';
|
|
9
6
|
|
|
10
7
|
export interface LocalizedPathsConfig {
|
|
11
8
|
languages: string[];
|
|
12
|
-
|
|
9
|
+
urlStrategy?: I18nUrlStrategy;
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
/**
|
|
16
13
|
* Localize a canonical, language-agnostic target for the given language:
|
|
17
|
-
* adds the language prefix and applies
|
|
14
|
+
* adds the language prefix and applies the configured URL strategy.
|
|
18
15
|
* `?search`/`#hash` suffixes are preserved verbatim.
|
|
19
16
|
*/
|
|
20
17
|
export const localizePath = (
|
|
@@ -22,7 +19,7 @@ export const localizePath = (
|
|
|
22
19
|
language: string,
|
|
23
20
|
config: LocalizedPathsConfig,
|
|
24
21
|
): string =>
|
|
25
|
-
buildLocalizedUrl(pathname, language, config.languages, config.
|
|
22
|
+
buildLocalizedUrl(pathname, language, config.languages, config.urlStrategy);
|
|
26
23
|
|
|
27
24
|
/**
|
|
28
25
|
* Reverse of {@link localizePath}: strip the language prefix and map localized
|
|
@@ -34,11 +31,19 @@ export const canonicalPath = (
|
|
|
34
31
|
config: LocalizedPathsConfig,
|
|
35
32
|
): string => {
|
|
36
33
|
const { pathname, search, hash } = splitUrlTarget(target);
|
|
37
|
-
|
|
38
|
-
pathname,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
if (config.urlStrategy) {
|
|
35
|
+
return `${config.urlStrategy.canonicalPathname(pathname, config.languages)}${search}${hash}`;
|
|
36
|
+
}
|
|
37
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
38
|
+
if (
|
|
39
|
+
segments[0] &&
|
|
40
|
+
config.languages.some(
|
|
41
|
+
language => language.toLowerCase() === segments[0].toLowerCase(),
|
|
42
|
+
)
|
|
43
|
+
) {
|
|
44
|
+
segments.shift();
|
|
45
|
+
}
|
|
46
|
+
const resolvedPath = `/${segments.join('/')}`;
|
|
42
47
|
|
|
43
48
|
return `${resolvedPath}${search}${hash}`;
|
|
44
49
|
};
|
|
@@ -50,16 +55,16 @@ export interface UseLocalizedPathsReturn {
|
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
57
|
* Context-bound versions of {@link localizePath} and {@link canonicalPath} —
|
|
53
|
-
* the
|
|
58
|
+
* the languages and URL strategy are read from the i18n
|
|
54
59
|
* provider, so apps never copy pattern-matching helpers again.
|
|
55
60
|
*/
|
|
56
61
|
export const useLocalizedPaths = (): UseLocalizedPathsReturn => {
|
|
57
|
-
const { supportedLanguages,
|
|
62
|
+
const { supportedLanguages, urlStrategy } = useModernI18n();
|
|
58
63
|
|
|
59
64
|
return useMemo(() => {
|
|
60
65
|
const config: LocalizedPathsConfig = {
|
|
61
66
|
languages: supportedLanguages,
|
|
62
|
-
|
|
67
|
+
urlStrategy,
|
|
63
68
|
};
|
|
64
69
|
|
|
65
70
|
return {
|
|
@@ -67,7 +72,7 @@ export const useLocalizedPaths = (): UseLocalizedPathsReturn => {
|
|
|
67
72
|
localizePath(pathname, language, config),
|
|
68
73
|
canonicalPath: (pathname: string) => canonicalPath(pathname, config),
|
|
69
74
|
};
|
|
70
|
-
}, [supportedLanguages,
|
|
75
|
+
}, [supportedLanguages, urlStrategy]);
|
|
71
76
|
};
|
|
72
77
|
|
|
73
78
|
export interface UseLocalizedLocationReturn {
|
|
@@ -83,7 +88,7 @@ export interface UseLocalizedLocationReturn {
|
|
|
83
88
|
* language switchers. SSR-safe: the location comes from the router adapter.
|
|
84
89
|
*/
|
|
85
90
|
export const useLocalizedLocation = (): UseLocalizedLocationReturn => {
|
|
86
|
-
const { language, supportedLanguages,
|
|
91
|
+
const { language, supportedLanguages, urlStrategy } = useModernI18n();
|
|
87
92
|
const { location } = useI18nRouterAdapter();
|
|
88
93
|
const pathname = location?.pathname ?? '/';
|
|
89
94
|
const search = location?.search ?? '';
|
|
@@ -92,7 +97,7 @@ export const useLocalizedLocation = (): UseLocalizedLocationReturn => {
|
|
|
92
97
|
return useMemo(() => {
|
|
93
98
|
const config: LocalizedPathsConfig = {
|
|
94
99
|
languages: supportedLanguages,
|
|
95
|
-
|
|
100
|
+
urlStrategy,
|
|
96
101
|
};
|
|
97
102
|
const alternates: Record<string, string> = {};
|
|
98
103
|
for (const supportedLanguage of supportedLanguages) {
|
|
@@ -105,5 +110,5 @@ export const useLocalizedLocation = (): UseLocalizedLocationReturn => {
|
|
|
105
110
|
canonical: canonicalPath(pathname, config),
|
|
106
111
|
alternates,
|
|
107
112
|
};
|
|
108
|
-
}, [language, supportedLanguages,
|
|
113
|
+
}, [language, supportedLanguages, urlStrategy, pathname, search, hash]);
|
|
109
114
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createI18nRouterNavigation } from '@modern-js/i18n-runtime-extensions/router-navigation';
|
|
2
|
+
import {
|
|
3
|
+
I18nNavigationProvider,
|
|
4
|
+
type I18nRouterAdapter,
|
|
5
|
+
useNativeI18nRouterAdapter,
|
|
6
|
+
} from './routerAdapter';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The seam, and only the seam. The selected-router navigation adapter is a
|
|
10
|
+
* fork subsystem, so it lives in the fork-owned
|
|
11
|
+
* `@modern-js/i18n-runtime-extensions` and is attached here through the
|
|
12
|
+
* `NavigationProvider` extension point this package already exposes. The
|
|
13
|
+
* factory takes this package's own primitives as arguments, so the fork
|
|
14
|
+
* package never imports back into it and the dependency stays one-way.
|
|
15
|
+
*/
|
|
16
|
+
export const { I18nRouterNavigationProvider, useIntegratedRouterAdapter } =
|
|
17
|
+
createI18nRouterNavigation<I18nRouterAdapter>({
|
|
18
|
+
I18nNavigationProvider,
|
|
19
|
+
useNativeI18nRouterAdapter,
|
|
20
|
+
});
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { useLanguageSync } from '@modern-js/i18n-runtime-extensions';
|
|
2
1
|
import { RuntimeContext } from '@modern-js/runtime';
|
|
3
2
|
import { Helmet } from '@modern-js/runtime/head';
|
|
4
3
|
import type React from 'react';
|
|
5
4
|
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
5
|
import type { BaseLocaleDetectionOptions } from '../shared/type';
|
|
6
|
+
import type { I18nUrlStrategy } from '../shared/urlStrategy';
|
|
7
7
|
import { ModernI18nProvider } from './context';
|
|
8
8
|
import {
|
|
9
9
|
createContextValue,
|
|
10
10
|
useClientSideRedirect,
|
|
11
|
+
useLanguageSync,
|
|
11
12
|
useSdkResourcesLoader,
|
|
12
13
|
} from './hooks';
|
|
13
14
|
import type { I18nInstance } from './i18n';
|
|
@@ -15,6 +16,38 @@ import { getI18nextInstanceForProvider } from './i18n/instance';
|
|
|
15
16
|
import type { RuntimeContextWithI18n } from './pluginSetup';
|
|
16
17
|
import { useI18nRouterAdapter } from './routerAdapter';
|
|
17
18
|
|
|
19
|
+
export interface I18nLanguageSynchronizationProps {
|
|
20
|
+
i18nInstance: I18nInstance | undefined;
|
|
21
|
+
localePathRedirect: boolean;
|
|
22
|
+
languages: string[];
|
|
23
|
+
pathname: string | undefined;
|
|
24
|
+
prevLangRef: React.MutableRefObject<string>;
|
|
25
|
+
setLang: (language: string) => void;
|
|
26
|
+
children: (
|
|
27
|
+
synchronizeLanguage: (language: string) => void,
|
|
28
|
+
) => React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const NativeLanguageSynchronization = ({
|
|
32
|
+
i18nInstance,
|
|
33
|
+
localePathRedirect,
|
|
34
|
+
languages,
|
|
35
|
+
pathname,
|
|
36
|
+
prevLangRef,
|
|
37
|
+
setLang,
|
|
38
|
+
children,
|
|
39
|
+
}: I18nLanguageSynchronizationProps) => {
|
|
40
|
+
const synchronizeLanguage = useLanguageSync(
|
|
41
|
+
i18nInstance,
|
|
42
|
+
localePathRedirect,
|
|
43
|
+
languages,
|
|
44
|
+
pathname,
|
|
45
|
+
prevLangRef,
|
|
46
|
+
setLang,
|
|
47
|
+
);
|
|
48
|
+
return children(synchronizeLanguage);
|
|
49
|
+
};
|
|
50
|
+
|
|
18
51
|
interface I18nRootWrapperOptions {
|
|
19
52
|
entryName?: string;
|
|
20
53
|
htmlLangAttr: boolean;
|
|
@@ -22,14 +55,18 @@ interface I18nRootWrapperOptions {
|
|
|
22
55
|
languages: string[];
|
|
23
56
|
fallbackLanguage: string;
|
|
24
57
|
ignoreRedirectRoutes?: BaseLocaleDetectionOptions['ignoreRedirectRoutes'];
|
|
25
|
-
|
|
58
|
+
urlStrategy?: I18nUrlStrategy;
|
|
59
|
+
NavigationProvider?: React.ComponentType<React.PropsWithChildren>;
|
|
60
|
+
LanguageSynchronization?: React.ComponentType<I18nLanguageSynchronizationProps>;
|
|
26
61
|
getLatestI18nInstance: () => I18nInstance | undefined;
|
|
27
62
|
getI18nextProvider: () => React.ComponentType<any> | null | undefined;
|
|
28
63
|
}
|
|
29
64
|
|
|
30
65
|
export const createI18nRootWrapper =
|
|
31
66
|
(options: I18nRootWrapperOptions) => (App: React.ComponentType<any>) => {
|
|
32
|
-
|
|
67
|
+
const LanguageSynchronization =
|
|
68
|
+
options.LanguageSynchronization ?? NativeLanguageSynchronization;
|
|
69
|
+
const I18nRoot = (props: Record<string, unknown>) => {
|
|
33
70
|
const {
|
|
34
71
|
entryName,
|
|
35
72
|
htmlLangAttr,
|
|
@@ -37,7 +74,7 @@ export const createI18nRootWrapper =
|
|
|
37
74
|
languages,
|
|
38
75
|
fallbackLanguage,
|
|
39
76
|
ignoreRedirectRoutes,
|
|
40
|
-
|
|
77
|
+
urlStrategy,
|
|
41
78
|
getLatestI18nInstance,
|
|
42
79
|
getI18nextProvider,
|
|
43
80
|
} = options;
|
|
@@ -69,15 +106,6 @@ export const createI18nRootWrapper =
|
|
|
69
106
|
}, [lang]);
|
|
70
107
|
|
|
71
108
|
useSdkResourcesLoader(i18nInstance, setForceUpdate);
|
|
72
|
-
const synchronizeLanguage = useLanguageSync(
|
|
73
|
-
i18nInstance,
|
|
74
|
-
localePathRedirect,
|
|
75
|
-
languages,
|
|
76
|
-
location?.pathname,
|
|
77
|
-
prevLangRef,
|
|
78
|
-
setLang,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
109
|
// Handle client-side redirect for static deployments.
|
|
82
110
|
useClientSideRedirect(
|
|
83
111
|
i18nInstance,
|
|
@@ -85,7 +113,7 @@ export const createI18nRootWrapper =
|
|
|
85
113
|
languages,
|
|
86
114
|
fallbackLanguage,
|
|
87
115
|
ignoreRedirectRoutes,
|
|
88
|
-
|
|
116
|
+
urlStrategy,
|
|
89
117
|
);
|
|
90
118
|
|
|
91
119
|
const contextValue = useMemo(
|
|
@@ -97,9 +125,8 @@ export const createI18nRootWrapper =
|
|
|
97
125
|
languages,
|
|
98
126
|
localePathRedirect,
|
|
99
127
|
ignoreRedirectRoutes,
|
|
100
|
-
localisedUrls,
|
|
101
128
|
setLang,
|
|
102
|
-
|
|
129
|
+
urlStrategy,
|
|
103
130
|
),
|
|
104
131
|
[
|
|
105
132
|
lang,
|
|
@@ -108,9 +135,8 @@ export const createI18nRootWrapper =
|
|
|
108
135
|
languages,
|
|
109
136
|
localePathRedirect,
|
|
110
137
|
ignoreRedirectRoutes,
|
|
111
|
-
|
|
138
|
+
urlStrategy,
|
|
112
139
|
forceUpdate,
|
|
113
|
-
synchronizeLanguage,
|
|
114
140
|
],
|
|
115
141
|
);
|
|
116
142
|
const I18nextProvider = getI18nextProvider();
|
|
@@ -140,13 +166,35 @@ export const createI18nRootWrapper =
|
|
|
140
166
|
return (
|
|
141
167
|
<>
|
|
142
168
|
{Boolean(htmlLangAttr) && <Helmet htmlAttributes={{ lang }} />}
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
<LanguageSynchronization
|
|
170
|
+
i18nInstance={i18nInstance}
|
|
171
|
+
localePathRedirect={localePathRedirect}
|
|
172
|
+
languages={languages}
|
|
173
|
+
pathname={location?.pathname}
|
|
174
|
+
prevLangRef={prevLangRef}
|
|
175
|
+
setLang={setLang}
|
|
146
176
|
>
|
|
147
|
-
{
|
|
148
|
-
|
|
177
|
+
{synchronizeLanguage => (
|
|
178
|
+
<ModernI18nProvider
|
|
179
|
+
i18nextProvider={I18nextProvider}
|
|
180
|
+
value={{ ...contextValue, synchronizeLanguage }}
|
|
181
|
+
>
|
|
182
|
+
{appContent}
|
|
183
|
+
</ModernI18nProvider>
|
|
184
|
+
)}
|
|
185
|
+
</LanguageSynchronization>
|
|
149
186
|
</>
|
|
150
187
|
);
|
|
151
188
|
};
|
|
189
|
+
const { NavigationProvider } = options;
|
|
190
|
+
if (!NavigationProvider) {
|
|
191
|
+
return I18nRoot;
|
|
192
|
+
}
|
|
193
|
+
return function I18nRootWithNavigation(props: Record<string, unknown>) {
|
|
194
|
+
return (
|
|
195
|
+
<NavigationProvider>
|
|
196
|
+
<I18nRoot {...props} />
|
|
197
|
+
</NavigationProvider>
|
|
198
|
+
);
|
|
199
|
+
};
|
|
152
200
|
};
|