@bleedingdev/modern-js-plugin-i18n 3.9.0-ultramodern.3 → 3.9.0-ultramodern.5

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 (79) hide show
  1. package/dist/cjs/cli/index.js +0 -22
  2. package/dist/cjs/runtime/Link.js +19 -32
  3. package/dist/cjs/runtime/context.js +26 -61
  4. package/dist/cjs/runtime/contextHelpers.js +5 -5
  5. package/dist/cjs/runtime/core.js +17 -14
  6. package/dist/cjs/runtime/hooks.js +7 -8
  7. package/dist/cjs/runtime/localizedPaths.js +11 -9
  8. package/dist/cjs/runtime/providerComposition.js +37 -13
  9. package/dist/cjs/runtime/routerAdapter.js +35 -140
  10. package/dist/cjs/runtime/utils.js +17 -19
  11. package/dist/cjs/server/index.js +10 -7
  12. package/dist/cjs/server/redirectPolicy.js +36 -31
  13. package/dist/cjs/shared/urlStrategy.js +18 -0
  14. package/dist/esm/cli/index.mjs +0 -22
  15. package/dist/esm/runtime/Link.mjs +19 -32
  16. package/dist/esm/runtime/context.mjs +18 -62
  17. package/dist/esm/runtime/contextHelpers.mjs +5 -5
  18. package/dist/esm/runtime/core.mjs +8 -5
  19. package/dist/esm/runtime/hooks.mjs +7 -8
  20. package/dist/esm/runtime/localizedPaths.mjs +11 -9
  21. package/dist/esm/runtime/providerComposition.mjs +38 -14
  22. package/dist/esm/runtime/routerAdapter.mjs +30 -141
  23. package/dist/esm/runtime/utils.mjs +15 -17
  24. package/dist/esm/server/index.mjs +10 -7
  25. package/dist/esm/server/redirectPolicy.mjs +29 -24
  26. package/dist/esm/shared/urlStrategy.mjs +0 -0
  27. package/dist/esm-node/cli/index.mjs +0 -22
  28. package/dist/esm-node/runtime/Link.mjs +19 -32
  29. package/dist/esm-node/runtime/context.mjs +18 -62
  30. package/dist/esm-node/runtime/contextHelpers.mjs +5 -5
  31. package/dist/esm-node/runtime/core.mjs +8 -5
  32. package/dist/esm-node/runtime/hooks.mjs +7 -8
  33. package/dist/esm-node/runtime/localizedPaths.mjs +11 -9
  34. package/dist/esm-node/runtime/providerComposition.mjs +38 -14
  35. package/dist/esm-node/runtime/routerAdapter.mjs +30 -141
  36. package/dist/esm-node/runtime/utils.mjs +15 -17
  37. package/dist/esm-node/server/index.mjs +10 -7
  38. package/dist/esm-node/server/redirectPolicy.mjs +29 -24
  39. package/dist/esm-node/shared/urlStrategy.mjs +1 -0
  40. package/dist/types/runtime/context.d.ts +8 -11
  41. package/dist/types/runtime/contextHelpers.d.ts +2 -2
  42. package/dist/types/runtime/core.d.ts +11 -2
  43. package/dist/types/runtime/hooks.d.ts +4 -5
  44. package/dist/types/runtime/localizedPaths.d.ts +4 -4
  45. package/dist/types/runtime/providerComposition.d.ts +13 -1
  46. package/dist/types/runtime/routerAdapter.d.ts +23 -6
  47. package/dist/types/runtime/utils.d.ts +4 -12
  48. package/dist/types/server/index.d.ts +2 -0
  49. package/dist/types/server/redirectPolicy.d.ts +5 -13
  50. package/dist/types/shared/type.d.ts +0 -14
  51. package/dist/types/shared/urlStrategy.d.ts +7 -0
  52. package/package.json +21 -11
  53. package/rstest.config.mts +2 -8
  54. package/src/cli/index.ts +1 -43
  55. package/src/runtime/Link.tsx +20 -40
  56. package/src/runtime/context.tsx +34 -133
  57. package/src/runtime/contextHelpers.ts +7 -5
  58. package/src/runtime/core.tsx +26 -6
  59. package/src/runtime/hooks.ts +14 -9
  60. package/src/runtime/localizedPaths.ts +24 -19
  61. package/src/runtime/providerComposition.tsx +71 -23
  62. package/src/runtime/routerAdapter.tsx +73 -338
  63. package/src/runtime/utils.ts +40 -31
  64. package/src/server/index.ts +44 -9
  65. package/src/server/redirectPolicy.ts +70 -70
  66. package/src/shared/type.ts +0 -14
  67. package/src/shared/urlStrategy.ts +11 -0
  68. package/tests/federatedI18nBoundaryResolver.test.tsx +71 -0
  69. package/tests/i18nUtils.test.ts +31 -65
  70. package/tests/link.test.tsx +13 -511
  71. package/tests/linkTypes.test.ts +12 -3
  72. package/tests/localisedUrlRewriteMatrix.fork.test.ts +4 -96
  73. package/tests/localisedUrls.test.ts +7 -216
  74. package/tests/reactI18nextRuntimeBoundary.test.ts +1 -82
  75. package/tests/redirectPolicy.test.ts +39 -30
  76. package/tests/routerAdapter.test.tsx +95 -1055
  77. package/tests/federatedI18nBoundary.client.test.tsx +0 -129
  78. package/tests/federatedI18nBoundary.test.tsx +0 -132
  79. package/tests/peerCohort.test.ts +0 -25
@@ -1,129 +0,0 @@
1
- import { expect, test } from '@rstest/core';
2
- import i18next, { type i18n } from 'i18next';
3
- import { act, useState } from 'react';
4
- import { hydrateRoot } from 'react-dom/client';
5
- import { renderToString } from 'react-dom/server';
6
- import { I18nextProvider, useTranslation } from 'react-i18next';
7
- import { ModernI18nProvider } from '../src/runtime/context';
8
- import { FederatedI18nBoundary, useModernI18n } from '../src/runtime/core';
9
-
10
- const remoteResources = {
11
- cs: {
12
- inventory: {
13
- 'inventory.widgetBody': 'C1 skladová plocha vlastněná MicroVerticalem.',
14
- },
15
- },
16
- en: {
17
- inventory: {
18
- 'inventory.widgetBody':
19
- 'C1 inventory surface owned by the MicroVertical.',
20
- },
21
- },
22
- };
23
-
24
- function InventoryCopy() {
25
- const modernI18n = useModernI18n<i18n>();
26
- const reactI18n = useTranslation('inventory');
27
- const reactI18nInstance = reactI18n.i18n as i18n & { __original?: i18n };
28
- const sharesScopedInstance =
29
- reactI18nInstance === modernI18n.i18nInstance ||
30
- reactI18nInstance.__original === modernI18n.i18nInstance;
31
-
32
- return (
33
- <p data-testid="inventory-copy">
34
- {modernI18n.t('inventory.widgetBody')}|
35
- {reactI18n.t('inventory.widgetBody')}|{String(sharesScopedInstance)}
36
- </p>
37
- );
38
- }
39
-
40
- function Host({ hostI18n }: { hostI18n: i18n }) {
41
- const [language, setLanguage] = useState('en');
42
-
43
- return (
44
- <ModernI18nProvider
45
- i18nextProvider={I18nextProvider}
46
- value={{
47
- i18nInstance: hostI18n,
48
- language,
49
- languages: ['en', 'cs'],
50
- }}
51
- >
52
- <button
53
- onClick={async () => {
54
- await hostI18n.changeLanguage('cs');
55
- setLanguage('cs');
56
- }}
57
- type="button"
58
- >
59
- cs
60
- </button>
61
- <FederatedI18nBoundary
62
- defaultNamespace="inventory"
63
- fallbackLanguage="en"
64
- resources={remoteResources}
65
- supportedLanguages={['en', 'cs']}
66
- >
67
- <InventoryCopy />
68
- </FederatedI18nBoundary>
69
- </ModernI18nProvider>
70
- );
71
- }
72
-
73
- async function createHostI18n() {
74
- const instance = i18next.createInstance();
75
- await instance.init({
76
- defaultNS: 'inventory',
77
- fallbackLng: 'en',
78
- initImmediate: false,
79
- lng: 'en',
80
- resources: {
81
- cs: {
82
- inventory: {
83
- 'inventory.widgetBody': 'stale host Czech copy',
84
- },
85
- },
86
- en: {
87
- inventory: {
88
- 'inventory.widgetBody': 'stale host English copy',
89
- },
90
- },
91
- },
92
- supportedLngs: ['en', 'cs'],
93
- });
94
- return instance;
95
- }
96
-
97
- test('hydrates remote-owned copy and follows the host language', async () => {
98
- const serverI18n = await createHostI18n();
99
- const container = document.createElement('div');
100
- container.innerHTML = renderToString(<Host hostI18n={serverI18n} />);
101
- document.body.appendChild(container);
102
-
103
- const clientI18n = await createHostI18n();
104
- const hydrationErrors: unknown[] = [];
105
- const root = hydrateRoot(container, <Host hostI18n={clientI18n} />, {
106
- onRecoverableError: error => hydrationErrors.push(error),
107
- });
108
-
109
- await act(async () => undefined);
110
- expect(
111
- container.querySelector('[data-testid="inventory-copy"]')?.textContent,
112
- ).toBe(
113
- 'C1 inventory surface owned by the MicroVertical.|C1 inventory surface owned by the MicroVertical.|true',
114
- );
115
-
116
- await act(async () => {
117
- (container.querySelector('button') as HTMLButtonElement).click();
118
- });
119
- expect(
120
- container.querySelector('[data-testid="inventory-copy"]')?.textContent,
121
- ).toBe(
122
- 'C1 skladová plocha vlastněná MicroVerticalem.|C1 skladová plocha vlastněná MicroVerticalem.|true',
123
- );
124
- expect(clientI18n.t('inventory.widgetBody')).toBe('stale host Czech copy');
125
- expect(hydrationErrors).toEqual([]);
126
-
127
- await act(async () => root.unmount());
128
- container.remove();
129
- });
@@ -1,132 +0,0 @@
1
- import { describe, expect, test } from '@rstest/core';
2
- import i18next, { type i18n } from 'i18next';
3
- import { renderToStaticMarkup } from 'react-dom/server';
4
- import { I18nextProvider, useTranslation } from 'react-i18next';
5
- import { ModernI18nProvider } from '../src/runtime/context';
6
- import { FederatedI18nBoundary, useModernI18n } from '../src/runtime/core';
7
-
8
- const C0_COPY = 'C0 copy bundled by the shell';
9
- const C1_COPY =
10
- 'C1 operational independence: inventory UI and localization moved together.';
11
- const HOST_ONLY_INVENTORY_COPY = 'inventory copy owned only by the shell';
12
- const HOST_ONLY_SHELL_COPY = 'private shell copy';
13
-
14
- function InventoryCopy() {
15
- const modernI18n = useModernI18n<i18n>();
16
- const reactI18n = useTranslation('inventory');
17
- const reactI18nInstance = reactI18n.i18n as i18n & { __original?: i18n };
18
- const sharesScopedInstance =
19
- reactI18nInstance === modernI18n.i18nInstance ||
20
- reactI18nInstance.__original === modernI18n.i18nInstance;
21
-
22
- return (
23
- <p>
24
- {modernI18n.t('inventory.widgetBody')}|
25
- {reactI18n.t('inventory.widgetBody')}|{String(sharesScopedInstance)}|
26
- {modernI18n.t('inventory.hostOnly')}|{modernI18n.t('shell:private')}|
27
- {reactI18n.t('inventory.hostOnly')}|{reactI18n.t('shell:private')}
28
- </p>
29
- );
30
- }
31
-
32
- describe('FederatedI18nBoundary', () => {
33
- test('renders remote-owned resources instead of stale host resources', async () => {
34
- const hostI18n = i18next.createInstance();
35
- await hostI18n.init({
36
- defaultNS: 'inventory',
37
- fallbackLng: 'en',
38
- initImmediate: false,
39
- lng: 'en',
40
- ns: ['inventory'],
41
- resources: {
42
- en: {
43
- inventory: {
44
- 'inventory.hostOnly': HOST_ONLY_INVENTORY_COPY,
45
- 'inventory.widgetBody': C0_COPY,
46
- },
47
- shell: {
48
- private: HOST_ONLY_SHELL_COPY,
49
- },
50
- },
51
- },
52
- supportedLngs: ['en', 'cs'],
53
- });
54
-
55
- const html = renderToStaticMarkup(
56
- <ModernI18nProvider
57
- i18nextProvider={I18nextProvider}
58
- value={{
59
- i18nInstance: hostI18n,
60
- language: 'en',
61
- languages: ['en', 'cs'],
62
- }}
63
- >
64
- <FederatedI18nBoundary
65
- defaultNamespace="inventory"
66
- resources={{
67
- en: {
68
- inventory: {
69
- 'inventory.widgetBody': C1_COPY,
70
- },
71
- },
72
- }}
73
- >
74
- <InventoryCopy />
75
- </FederatedI18nBoundary>
76
- </ModernI18nProvider>,
77
- );
78
-
79
- expect(html).toContain(C1_COPY);
80
- expect(html).not.toContain(C0_COPY);
81
- expect(html).toContain(`${C1_COPY}|${C1_COPY}|true`);
82
- expect(html).not.toContain(HOST_ONLY_INVENTORY_COPY);
83
- expect(html).not.toContain(HOST_ONLY_SHELL_COPY);
84
- expect(hostI18n.t('inventory.widgetBody')).toBe(C0_COPY);
85
- expect(hostI18n.t('inventory.hostOnly')).toBe(HOST_ONLY_INVENTORY_COPY);
86
- expect(hostI18n.t('shell:private')).toBe(HOST_ONLY_SHELL_COPY);
87
- });
88
-
89
- test('fails before mutation when cloneInstance does not isolate the host store', async () => {
90
- const hostI18n = i18next.createInstance();
91
- await hostI18n.init({
92
- defaultNS: 'inventory',
93
- initImmediate: false,
94
- lng: 'en',
95
- resources: {
96
- en: {
97
- inventory: {
98
- 'inventory.widgetBody': C0_COPY,
99
- },
100
- },
101
- },
102
- });
103
- hostI18n.cloneInstance = () => hostI18n;
104
-
105
- expect(() =>
106
- renderToStaticMarkup(
107
- <ModernI18nProvider
108
- i18nextProvider={I18nextProvider}
109
- value={{
110
- i18nInstance: hostI18n,
111
- language: 'en',
112
- languages: ['en'],
113
- }}
114
- >
115
- <FederatedI18nBoundary
116
- defaultNamespace="inventory"
117
- resources={{
118
- en: {
119
- inventory: {
120
- 'inventory.widgetBody': C1_COPY,
121
- },
122
- },
123
- }}
124
- >
125
- <InventoryCopy />
126
- </FederatedI18nBoundary>
127
- </ModernI18nProvider>,
128
- ),
129
- ).toThrow(/did not isolate the host resource store/u);
130
- expect(hostI18n.t('inventory.widgetBody')).toBe(C0_COPY);
131
- });
132
- });
@@ -1,25 +0,0 @@
1
- import { createRequire } from 'node:module';
2
-
3
- const require = createRequire(import.meta.url);
4
- const pluginManifest = require('../package.json') as {
5
- peerDependencies: Record<string, string>;
6
- peerDependenciesMeta: Record<string, { optional?: boolean }>;
7
- };
8
- const runtimeManifest = require('../../plugin-runtime/package.json') as {
9
- peerDependencies: Record<string, string>;
10
- };
11
-
12
- describe('plugin-i18n peer cohort', () => {
13
- test('matches the required runtime React cohort without overstating i18n floors', () => {
14
- expect(pluginManifest.peerDependencies).toMatchObject({
15
- i18next: '>=25.7.4',
16
- react: runtimeManifest.peerDependencies.react,
17
- 'react-dom': runtimeManifest.peerDependencies['react-dom'],
18
- 'react-i18next': '>=15.7.4',
19
- });
20
- expect(pluginManifest.peerDependenciesMeta).toMatchObject({
21
- i18next: { optional: true },
22
- 'react-i18next': { optional: true },
23
- });
24
- });
25
- });