@bleedingdev/modern-js-i18n-integration 0.0.0 → 3.9.0-ultramodern.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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/cli.js +71 -0
  3. package/dist/cjs/federation/boundary.js +100 -0
  4. package/dist/cjs/federation/registration.js +43 -0
  5. package/dist/cjs/languageSynchronization.js +44 -0
  6. package/dist/cjs/navigation.js +43 -0
  7. package/dist/cjs/options.js +18 -0
  8. package/dist/cjs/runtime-no-react-i18next.js +46 -0
  9. package/dist/cjs/runtime.js +46 -0
  10. package/dist/cjs/runtimeComposition.js +60 -0
  11. package/dist/cjs/server.js +50 -0
  12. package/dist/cjs/urlStrategy.js +51 -0
  13. package/dist/esm/cli.mjs +31 -0
  14. package/dist/esm/federation/boundary.mjs +62 -0
  15. package/dist/esm/federation/registration.mjs +5 -0
  16. package/dist/esm/languageSynchronization.mjs +6 -0
  17. package/dist/esm/navigation.mjs +1 -0
  18. package/dist/esm/options.mjs +0 -0
  19. package/dist/esm/runtime-no-react-i18next.mjs +6 -0
  20. package/dist/esm/runtime.mjs +6 -0
  21. package/dist/esm/runtimeComposition.mjs +22 -0
  22. package/dist/esm/server.mjs +10 -0
  23. package/dist/esm/urlStrategy.mjs +13 -0
  24. package/dist/esm-node/cli.mjs +32 -0
  25. package/dist/esm-node/federation/boundary.mjs +63 -0
  26. package/dist/esm-node/federation/registration.mjs +6 -0
  27. package/dist/esm-node/languageSynchronization.mjs +7 -0
  28. package/dist/esm-node/navigation.mjs +2 -0
  29. package/dist/esm-node/options.mjs +1 -0
  30. package/dist/esm-node/runtime-no-react-i18next.mjs +7 -0
  31. package/dist/esm-node/runtime.mjs +7 -0
  32. package/dist/esm-node/runtimeComposition.mjs +23 -0
  33. package/dist/esm-node/server.mjs +11 -0
  34. package/dist/esm-node/urlStrategy.mjs +14 -0
  35. package/dist/types/cli.d.ts +4 -0
  36. package/dist/types/federation/boundary.d.ts +9 -0
  37. package/dist/types/federation/registration.d.ts +2 -0
  38. package/dist/types/languageSynchronization.d.ts +3 -0
  39. package/dist/types/navigation.d.ts +9 -0
  40. package/dist/types/options.d.ts +13 -0
  41. package/dist/types/runtime-no-react-i18next.d.ts +2 -0
  42. package/dist/types/runtime.d.ts +2 -0
  43. package/dist/types/runtimeComposition.d.ts +4 -0
  44. package/dist/types/server.d.ts +3 -0
  45. package/dist/types/urlStrategy.d.ts +4 -0
  46. package/package.json +139 -5
  47. package/src/cli.ts +38 -0
  48. package/src/federation/boundary.tsx +129 -0
  49. package/src/federation/registration.ts +8 -0
  50. package/src/languageSynchronization.tsx +23 -0
  51. package/src/navigation.tsx +12 -0
  52. package/src/options.ts +18 -0
  53. package/src/runtime-no-react-i18next.ts +5 -0
  54. package/src/runtime.ts +5 -0
  55. package/src/runtimeComposition.ts +33 -0
  56. package/src/server.ts +27 -0
  57. package/src/urlStrategy.ts +19 -0
  58. package/README.md +0 -3
@@ -0,0 +1,129 @@
1
+ import {
2
+ type FederatedI18nBoundaryProps,
3
+ getActualI18nextInstance,
4
+ ModernI18nContext,
5
+ ModernI18nProvider,
6
+ ReactI18nextProviderContext,
7
+ } from '@modern-js/plugin-i18n/runtime/consumer';
8
+ import { type FC, useContext, useMemo } from 'react';
9
+
10
+ export type { FederatedI18nBoundaryProps } from '@modern-js/plugin-i18n/runtime/consumer';
11
+
12
+ /**
13
+ * Keeps a federated surface's translation resources inside its delivery unit.
14
+ * The host supplies only the active language; the remote owns and versions the
15
+ * resources used below this boundary.
16
+ */
17
+ export const FederatedI18nBoundary: FC<FederatedI18nBoundaryProps> = ({
18
+ children,
19
+ defaultNamespace,
20
+ fallbackLanguage,
21
+ resources,
22
+ supportedLanguages,
23
+ }) => {
24
+ const parent = useContext(ModernI18nContext);
25
+ const I18nextProvider = useContext(ReactI18nextProviderContext);
26
+ if (!parent) {
27
+ throw new Error(
28
+ 'FederatedI18nBoundary must be used within ModernI18nProvider',
29
+ );
30
+ }
31
+
32
+ const languages =
33
+ supportedLanguages ?? parent.languages ?? Object.keys(resources);
34
+ const scopedInstance = useMemo(() => {
35
+ const parentInstance = getActualI18nextInstance(parent.i18nInstance);
36
+ const clone = parentInstance.cloneInstance?.({
37
+ defaultNS: defaultNamespace,
38
+ fallbackLng: fallbackLanguage ?? languages[0] ?? parent.language,
39
+ forkResourceStore: true,
40
+ initImmediate: false,
41
+ lng: parent.language,
42
+ ns: [defaultNamespace],
43
+ resources,
44
+ supportedLngs: languages,
45
+ });
46
+ if (!clone) {
47
+ throw new Error(
48
+ 'FederatedI18nBoundary requires an i18n instance with cloneInstance support',
49
+ );
50
+ }
51
+ const resourceStore = clone.store;
52
+ const parentResourceStore = parentInstance.store;
53
+ const sharesNestedResourceState = Object.entries(
54
+ resourceStore?.data ?? {},
55
+ ).some(([language, namespaces]) => {
56
+ const parentNamespaces = parentResourceStore?.data?.[language];
57
+ return (
58
+ namespaces === parentNamespaces ||
59
+ Object.entries(namespaces).some(
60
+ ([namespace, resource]) =>
61
+ resource !== null &&
62
+ typeof resource === 'object' &&
63
+ resource === parentNamespaces?.[namespace],
64
+ )
65
+ );
66
+ });
67
+ if (
68
+ clone === parentInstance ||
69
+ resourceStore === parentResourceStore ||
70
+ resourceStore?.data === parentResourceStore?.data ||
71
+ sharesNestedResourceState
72
+ ) {
73
+ throw new Error(
74
+ 'FederatedI18nBoundary cloneInstance did not isolate the host resource store',
75
+ );
76
+ }
77
+ if (
78
+ !resourceStore?.addResourceBundle ||
79
+ !clone.removeResourceBundle ||
80
+ !resourceStore.data
81
+ ) {
82
+ throw new Error(
83
+ 'FederatedI18nBoundary requires an isolated mutable i18n resource store',
84
+ );
85
+ }
86
+ for (const [language, namespaces] of Object.entries(resourceStore.data)) {
87
+ for (const namespace of Object.keys(namespaces)) {
88
+ clone.removeResourceBundle(language, namespace);
89
+ }
90
+ }
91
+ for (const [language, namespaces] of Object.entries(resources)) {
92
+ for (const [namespace, resource] of Object.entries(namespaces)) {
93
+ resourceStore.addResourceBundle(
94
+ language,
95
+ namespace,
96
+ resource as Record<string, string>,
97
+ true,
98
+ true,
99
+ );
100
+ }
101
+ }
102
+ return clone;
103
+ }, [
104
+ defaultNamespace,
105
+ fallbackLanguage,
106
+ languages,
107
+ parent.i18nInstance,
108
+ parent.language,
109
+ resources,
110
+ ]);
111
+ const value = useMemo(
112
+ () => ({
113
+ ...parent,
114
+ i18nInstance: scopedInstance,
115
+ language: parent.language,
116
+ languages,
117
+ }),
118
+ [languages, parent, scopedInstance],
119
+ );
120
+
121
+ const scopedContent = (
122
+ <ModernI18nProvider value={value}>{children}</ModernI18nProvider>
123
+ );
124
+ return I18nextProvider ? (
125
+ <I18nextProvider i18n={scopedInstance}>{scopedContent}</I18nextProvider>
126
+ ) : (
127
+ scopedContent
128
+ );
129
+ };
@@ -0,0 +1,8 @@
1
+ import type { RuntimePluginAPI } from '@modern-js/plugin/runtime';
2
+ import { FederatedI18nBoundary } from './boundary';
3
+
4
+ export function registerFederatedI18nBoundary(api: RuntimePluginAPI<{}>): void {
5
+ api.resolveComponent((component, { name }) =>
6
+ name === 'i18n.FederatedI18nBoundary' ? FederatedI18nBoundary : component,
7
+ );
8
+ }
@@ -0,0 +1,23 @@
1
+ import { useLanguageSync } from '@modern-js/i18n-runtime-extensions';
2
+ import type { I18nLanguageSynchronizationProps } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
3
+
4
+ /** Bind the shared controller to this native provider's instance and state. */
5
+ export const I18nLanguageSynchronization = ({
6
+ i18nInstance,
7
+ localePathRedirect,
8
+ languages,
9
+ pathname,
10
+ prevLangRef,
11
+ setLang,
12
+ children,
13
+ }: I18nLanguageSynchronizationProps) => {
14
+ const synchronizeLanguage = useLanguageSync(
15
+ i18nInstance,
16
+ localePathRedirect,
17
+ languages,
18
+ pathname,
19
+ prevLangRef,
20
+ setLang,
21
+ );
22
+ return children(synchronizeLanguage);
23
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The selected-router navigation adapter now lives in
3
+ * `@modern-js/plugin-i18n`, so a bare `appTools() + i18nPlugin()` app gets
4
+ * mapped `<Link>` navigation and language-invariant active state without this
5
+ * integration package. Re-exported here so the composed
6
+ * `ultramodernI18nIntegrationPlugin` keeps its own entry point and any consumer
7
+ * importing it from this package continues to work.
8
+ */
9
+ export {
10
+ I18nRouterNavigationProvider,
11
+ useIntegratedRouterAdapter,
12
+ } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
package/src/options.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { LocalisedUrlsOption } from '@modern-js/i18n-runtime-extensions';
2
+ import type { I18nPluginOptions as NativeRuntimeOptions } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
3
+ import type { I18nPluginOptions as NativeServerOptions } from '@modern-js/plugin-i18n/server';
4
+
5
+ export type IntegratedLocaleDetectionOptions = NonNullable<
6
+ NativeRuntimeOptions['localeDetection']
7
+ > & {
8
+ localisedUrls?: LocalisedUrlsOption;
9
+ localeDetectionByEntry?: Record<string, IntegratedLocaleDetectionOptions>;
10
+ };
11
+
12
+ export type I18nPluginOptions = NativeRuntimeOptions & {
13
+ localeDetection?: IntegratedLocaleDetectionOptions;
14
+ };
15
+
16
+ export type I18nServerPluginOptions = NativeServerOptions & {
17
+ localeDetection: IntegratedLocaleDetectionOptions;
18
+ };
@@ -0,0 +1,5 @@
1
+ import { i18nPlugin as nativeI18nPlugin } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
2
+ import { composeI18nPlugin } from './runtimeComposition';
3
+
4
+ export const i18nPlugin = composeI18nPlugin(nativeI18nPlugin);
5
+ export default i18nPlugin;
package/src/runtime.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { i18nPlugin as nativeI18nPlugin } from '@modern-js/plugin-i18n/runtime';
2
+ import { composeI18nPlugin } from './runtimeComposition';
3
+
4
+ export const i18nPlugin = composeI18nPlugin(nativeI18nPlugin);
5
+ export default i18nPlugin;
@@ -0,0 +1,33 @@
1
+ import type { I18nPluginOptions as NativeI18nPluginOptions } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
2
+ import type { RuntimePlugin } from '@modern-js/runtime';
3
+ import { registerFederatedI18nBoundary } from './federation/registration';
4
+ import { I18nLanguageSynchronization } from './languageSynchronization';
5
+ import { I18nRouterNavigationProvider } from './navigation';
6
+ import type { I18nPluginOptions } from './options';
7
+ import { createI18nUrlStrategy } from './urlStrategy';
8
+
9
+ export const composeI18nPlugin =
10
+ (nativeI18nPlugin: (options: NativeI18nPluginOptions) => RuntimePlugin) =>
11
+ (options: I18nPluginOptions = {}): RuntimePlugin => {
12
+ const {
13
+ localisedUrls,
14
+ localeDetectionByEntry: _entryOptions,
15
+ ...nativeLocaleDetection
16
+ } = options.localeDetection ?? {};
17
+ const native = nativeI18nPlugin({
18
+ ...options,
19
+ localeDetection: nativeLocaleDetection,
20
+ urlStrategy: options.urlStrategy ?? createI18nUrlStrategy(localisedUrls),
21
+ NavigationProvider:
22
+ options.NavigationProvider ?? I18nRouterNavigationProvider,
23
+ LanguageSynchronization:
24
+ options.LanguageSynchronization ?? I18nLanguageSynchronization,
25
+ });
26
+ return {
27
+ ...native,
28
+ setup(api) {
29
+ registerFederatedI18nBoundary(api);
30
+ return native.setup?.(api);
31
+ },
32
+ };
33
+ };
package/src/server.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { getLocaleDetectionOptions } from '@modern-js/plugin-i18n/config';
2
+ import { i18nServerPlugin as nativeI18nServerPlugin } from '@modern-js/plugin-i18n/server';
3
+ import type {
4
+ I18nServerPluginOptions,
5
+ IntegratedLocaleDetectionOptions,
6
+ } from './options';
7
+ import { createI18nUrlStrategy } from './urlStrategy';
8
+
9
+ export const i18nServerPlugin = (options: I18nServerPluginOptions) =>
10
+ nativeI18nServerPlugin({
11
+ ...options,
12
+ resolveUrlStrategy:
13
+ options.resolveUrlStrategy ??
14
+ (entryName =>
15
+ createI18nUrlStrategy(
16
+ options.localeDetection
17
+ ? (
18
+ getLocaleDetectionOptions(
19
+ entryName,
20
+ options.localeDetection,
21
+ ) as IntegratedLocaleDetectionOptions
22
+ ).localisedUrls
23
+ : undefined,
24
+ )),
25
+ });
26
+
27
+ export default i18nServerPlugin;
@@ -0,0 +1,19 @@
1
+ import {
2
+ canonicalTargetPathname,
3
+ isDefaultLocaleRedirectSkipPath,
4
+ type LocalisedUrlsOption,
5
+ localiseTargetPathname,
6
+ } from '@modern-js/i18n-runtime-extensions';
7
+ import type { I18nUrlStrategy } from '@modern-js/plugin-i18n/runtime/no-react-i18next';
8
+
9
+ /** Each integration owns its map; no process-wide configuration is installed. */
10
+ export const createI18nUrlStrategy = (
11
+ localisedUrls?: LocalisedUrlsOption,
12
+ ): I18nUrlStrategy => ({
13
+ localizePathname: (pathname, language, languages) =>
14
+ localiseTargetPathname(pathname, language, [...languages], localisedUrls),
15
+ canonicalPathname: (pathname, languages) =>
16
+ canonicalTargetPathname(pathname, [...languages], localisedUrls),
17
+ shouldSkipRedirect: (pathname, languages) =>
18
+ isDefaultLocaleRedirectSkipPath(pathname, [...languages]),
19
+ });
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # @bleedingdev/modern-js-i18n-integration
2
-
3
- This 0.0.0 version only registers the package name so npm trusted publishing can be configured. It contains no runtime implementation. Use the subsequent UltraModern.js release, published from the repository workflow with provenance.