@furystack/shades-i18n 1.0.27 → 2.0.0
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/CHANGELOG.md +27 -0
- package/esm/create-i18n-component.d.ts +2 -0
- package/esm/create-i18n-component.d.ts.map +1 -1
- package/esm/create-i18n-component.spec.d.ts +2 -0
- package/esm/create-i18n-component.spec.d.ts.map +1 -0
- package/esm/create-i18n-component.spec.js +216 -0
- package/esm/create-i18n-component.spec.js.map +1 -0
- package/package.json +4 -4
- package/src/create-i18n-component.spec.tsx +263 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.0] - 2026-02-09
|
|
4
|
+
|
|
5
|
+
### ⬆️ Dependencies
|
|
6
|
+
|
|
7
|
+
- Updated peer dependency `@furystack/shades` to latest minor version
|
|
8
|
+
- Updated peer dependency `@furystack/shades` to new major version
|
|
9
|
+
- Updated `@furystack/shades` dependency with microtask-based batched rendering
|
|
10
|
+
- Updated `@furystack/*` dependencies
|
|
11
|
+
- Peer dependency on `@furystack/shades` bumped to new major version
|
|
12
|
+
- Updated `@furystack/shades` dependency
|
|
13
|
+
|
|
14
|
+
### 💥 Breaking Changes
|
|
15
|
+
|
|
16
|
+
### Requires `@furystack/shades` v3
|
|
17
|
+
|
|
18
|
+
This package now depends on the new major version of `@furystack/shades` which removed the `constructed` callback from the Shade API.
|
|
19
|
+
|
|
20
|
+
### Peer Dependency Bump
|
|
21
|
+
|
|
22
|
+
Updated peer dependency on `@furystack/shades` to the new major version with VNode-based rendering. No API changes in this package.
|
|
23
|
+
|
|
24
|
+
## [1.0.28] - 2026-02-01
|
|
25
|
+
|
|
26
|
+
### ⬆️ Dependencies
|
|
27
|
+
|
|
28
|
+
- Updated peer dependency `@furystack/shades` to include new CSS styling features
|
|
29
|
+
|
|
3
30
|
## [1.0.27] - 2026-01-26
|
|
4
31
|
|
|
5
32
|
### 🔧 Chores
|
|
@@ -6,5 +6,7 @@ export declare const createI18nComponent: <TKeys extends string>(options: {
|
|
|
6
6
|
key: TKeys;
|
|
7
7
|
} & Omit<Partial<HTMLElement>, "style"> & {
|
|
8
8
|
style?: Partial<CSSStyleDeclaration>;
|
|
9
|
+
} & {
|
|
10
|
+
ref?: import("@furystack/shades").RefObject<Element>;
|
|
9
11
|
}, children?: import("@furystack/shades").ChildrenList) => JSX.Element;
|
|
10
12
|
//# sourceMappingURL=create-i18n-component.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-i18n-component.d.ts","sourceRoot":"","sources":["../src/create-i18n-component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAGlD,eAAO,MAAM,mBAAmB,GAAI,KAAK,SAAS,MAAM,EAAE,SAAS;IACjE,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;CACtB;SACqB,KAAK
|
|
1
|
+
{"version":3,"file":"create-i18n-component.d.ts","sourceRoot":"","sources":["../src/create-i18n-component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAGlD,eAAO,MAAM,mBAAmB,GAAI,KAAK,SAAS,MAAM,EAAE,SAAS;IACjE,OAAO,EAAE,WAAW,CAAC,KAAK,CAAC,CAAA;IAC3B,aAAa,EAAE,MAAM,CAAA;CACtB;SACqB,KAAK;;;;;sEAgB1B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-i18n-component.spec.d.ts","sourceRoot":"","sources":["../src/create-i18n-component.spec.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { I18NService } from '@furystack/i18n';
|
|
2
|
+
import { Injector } from '@furystack/inject';
|
|
3
|
+
import { createComponent, initializeShadeRoot } from '@furystack/shades';
|
|
4
|
+
import { sleepAsync, usingAsync } from '@furystack/utils';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { createI18nComponent } from './create-i18n-component.js';
|
|
7
|
+
const createTestService = () => {
|
|
8
|
+
return new I18NService({
|
|
9
|
+
code: 'en',
|
|
10
|
+
values: {
|
|
11
|
+
hello: 'Hello',
|
|
12
|
+
goodbye: 'Goodbye',
|
|
13
|
+
world: 'World',
|
|
14
|
+
},
|
|
15
|
+
}, {
|
|
16
|
+
code: 'hu',
|
|
17
|
+
values: {
|
|
18
|
+
hello: 'Szia',
|
|
19
|
+
goodbye: 'Viszlát',
|
|
20
|
+
world: 'Világ',
|
|
21
|
+
},
|
|
22
|
+
}, {
|
|
23
|
+
code: 'de',
|
|
24
|
+
values: {
|
|
25
|
+
hello: 'Hallo',
|
|
26
|
+
goodbye: 'Auf Wiedersehen',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
describe('createI18nComponent', () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
document.body.innerHTML = '<div id="root"></div>';
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
document.body.innerHTML = '';
|
|
36
|
+
});
|
|
37
|
+
it('should render with the initial translation', async () => {
|
|
38
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
39
|
+
const service = createTestService();
|
|
40
|
+
const I18n = createI18nComponent({
|
|
41
|
+
service,
|
|
42
|
+
shadowDomName: 'test-i18n-initial',
|
|
43
|
+
});
|
|
44
|
+
const rootElement = document.getElementById('root');
|
|
45
|
+
initializeShadeRoot({
|
|
46
|
+
injector,
|
|
47
|
+
rootElement,
|
|
48
|
+
jsxElement: createComponent(I18n, { key: "hello" }),
|
|
49
|
+
});
|
|
50
|
+
await sleepAsync(50);
|
|
51
|
+
expect(rootElement.textContent).toBe('Hello');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it('should render with the correct translation for different keys', async () => {
|
|
55
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
56
|
+
const service = createTestService();
|
|
57
|
+
const I18n = createI18nComponent({
|
|
58
|
+
service,
|
|
59
|
+
shadowDomName: 'test-i18n-keys',
|
|
60
|
+
});
|
|
61
|
+
const rootElement = document.getElementById('root');
|
|
62
|
+
initializeShadeRoot({
|
|
63
|
+
injector,
|
|
64
|
+
rootElement,
|
|
65
|
+
jsxElement: (createComponent(createComponent, null,
|
|
66
|
+
createComponent(I18n, { key: "hello" }),
|
|
67
|
+
createComponent("span", null, " "),
|
|
68
|
+
createComponent(I18n, { key: "world" }))),
|
|
69
|
+
});
|
|
70
|
+
await sleepAsync(50);
|
|
71
|
+
expect(rootElement.textContent).toBe('Hello World');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it('should update when language changes', async () => {
|
|
75
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
76
|
+
const service = createTestService();
|
|
77
|
+
const I18n = createI18nComponent({
|
|
78
|
+
service,
|
|
79
|
+
shadowDomName: 'test-i18n-language-change',
|
|
80
|
+
});
|
|
81
|
+
const rootElement = document.getElementById('root');
|
|
82
|
+
initializeShadeRoot({
|
|
83
|
+
injector,
|
|
84
|
+
rootElement,
|
|
85
|
+
jsxElement: createComponent(I18n, { key: "hello" }),
|
|
86
|
+
});
|
|
87
|
+
await sleepAsync(50);
|
|
88
|
+
expect(rootElement.textContent).toBe('Hello');
|
|
89
|
+
service.currentLanguage = 'hu';
|
|
90
|
+
await sleepAsync(50);
|
|
91
|
+
expect(rootElement.textContent).toBe('Szia');
|
|
92
|
+
service.currentLanguage = 'de';
|
|
93
|
+
await sleepAsync(50);
|
|
94
|
+
expect(rootElement.textContent).toBe('Hallo');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
it('should fallback to default language for missing keys', async () => {
|
|
98
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
99
|
+
const service = createTestService();
|
|
100
|
+
const I18n = createI18nComponent({
|
|
101
|
+
service,
|
|
102
|
+
shadowDomName: 'test-i18n-fallback',
|
|
103
|
+
});
|
|
104
|
+
const rootElement = document.getElementById('root');
|
|
105
|
+
initializeShadeRoot({
|
|
106
|
+
injector,
|
|
107
|
+
rootElement,
|
|
108
|
+
jsxElement: createComponent(I18n, { key: "world" }),
|
|
109
|
+
});
|
|
110
|
+
await sleepAsync(50);
|
|
111
|
+
expect(rootElement.textContent).toBe('World');
|
|
112
|
+
// German doesn't have 'world' translation, should fallback to English
|
|
113
|
+
service.currentLanguage = 'de';
|
|
114
|
+
await sleepAsync(50);
|
|
115
|
+
expect(rootElement.textContent).toBe('World');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
it('should handle rapid language changes', async () => {
|
|
119
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
120
|
+
const service = createTestService();
|
|
121
|
+
const I18n = createI18nComponent({
|
|
122
|
+
service,
|
|
123
|
+
shadowDomName: 'test-i18n-rapid-changes',
|
|
124
|
+
});
|
|
125
|
+
const rootElement = document.getElementById('root');
|
|
126
|
+
initializeShadeRoot({
|
|
127
|
+
injector,
|
|
128
|
+
rootElement,
|
|
129
|
+
jsxElement: createComponent(I18n, { key: "hello" }),
|
|
130
|
+
});
|
|
131
|
+
await sleepAsync(50);
|
|
132
|
+
// Rapid language changes
|
|
133
|
+
service.currentLanguage = 'hu';
|
|
134
|
+
service.currentLanguage = 'de';
|
|
135
|
+
service.currentLanguage = 'en';
|
|
136
|
+
service.currentLanguage = 'hu';
|
|
137
|
+
await sleepAsync(50);
|
|
138
|
+
expect(rootElement.textContent).toBe('Szia');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
it('should cleanup subscription on unmount', async () => {
|
|
142
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
143
|
+
const service = createTestService();
|
|
144
|
+
const unsubscribeSpy = vi.fn();
|
|
145
|
+
const originalSubscribe = service.subscribe.bind(service);
|
|
146
|
+
vi.spyOn(service, 'subscribe').mockImplementation((event, callback) => {
|
|
147
|
+
const subscription = originalSubscribe(event, callback);
|
|
148
|
+
return {
|
|
149
|
+
[Symbol.dispose]: () => {
|
|
150
|
+
unsubscribeSpy();
|
|
151
|
+
subscription[Symbol.dispose]();
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
const I18n = createI18nComponent({
|
|
156
|
+
service,
|
|
157
|
+
shadowDomName: 'test-i18n-cleanup',
|
|
158
|
+
});
|
|
159
|
+
const rootElement = document.getElementById('root');
|
|
160
|
+
initializeShadeRoot({
|
|
161
|
+
injector,
|
|
162
|
+
rootElement,
|
|
163
|
+
jsxElement: createComponent(I18n, { key: "hello" }),
|
|
164
|
+
});
|
|
165
|
+
await sleepAsync(50);
|
|
166
|
+
expect(unsubscribeSpy).not.toHaveBeenCalled();
|
|
167
|
+
// Unmount by clearing the DOM
|
|
168
|
+
document.body.innerHTML = '';
|
|
169
|
+
await sleepAsync(50);
|
|
170
|
+
expect(unsubscribeSpy).toHaveBeenCalled();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
it('should render as span element', async () => {
|
|
174
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
175
|
+
const service = createTestService();
|
|
176
|
+
const I18n = createI18nComponent({
|
|
177
|
+
service,
|
|
178
|
+
shadowDomName: 'test-i18n-span',
|
|
179
|
+
});
|
|
180
|
+
const rootElement = document.getElementById('root');
|
|
181
|
+
initializeShadeRoot({
|
|
182
|
+
injector,
|
|
183
|
+
rootElement,
|
|
184
|
+
jsxElement: createComponent(I18n, { key: "hello" }),
|
|
185
|
+
});
|
|
186
|
+
await sleepAsync(50);
|
|
187
|
+
// The component uses elementBaseName: 'span', so it renders as <span is="test-i18n-span">
|
|
188
|
+
const i18nElement = rootElement.querySelector('span[is="test-i18n-span"]');
|
|
189
|
+
expect(i18nElement).toBeInstanceOf(HTMLSpanElement);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
it('should work with multiple instances using different keys', async () => {
|
|
193
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
194
|
+
const service = createTestService();
|
|
195
|
+
const I18n = createI18nComponent({
|
|
196
|
+
service,
|
|
197
|
+
shadowDomName: 'test-i18n-multiple',
|
|
198
|
+
});
|
|
199
|
+
const rootElement = document.getElementById('root');
|
|
200
|
+
initializeShadeRoot({
|
|
201
|
+
injector,
|
|
202
|
+
rootElement,
|
|
203
|
+
jsxElement: (createComponent("div", null,
|
|
204
|
+
createComponent(I18n, { key: "hello" }),
|
|
205
|
+
' - ',
|
|
206
|
+
createComponent(I18n, { key: "goodbye" }))),
|
|
207
|
+
});
|
|
208
|
+
await sleepAsync(50);
|
|
209
|
+
expect(rootElement.textContent).toBe('Hello - Goodbye');
|
|
210
|
+
service.currentLanguage = 'hu';
|
|
211
|
+
await sleepAsync(50);
|
|
212
|
+
expect(rootElement.textContent).toBe('Szia - Viszlát');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
//# sourceMappingURL=create-i18n-component.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-i18n-component.spec.js","sourceRoot":"","sources":["../src/create-i18n-component.spec.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/shades-i18n",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "I18n translation package and components for Shades",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://github.com/furystack/furystack",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@furystack/i18n": "^1.0.
|
|
41
|
-
"@furystack/inject": "^12.0.
|
|
42
|
-
"@furystack/shades": "^
|
|
40
|
+
"@furystack/i18n": "^1.0.28",
|
|
41
|
+
"@furystack/inject": "^12.0.29",
|
|
42
|
+
"@furystack/shades": "^12.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.10",
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { I18NService } from '@furystack/i18n'
|
|
2
|
+
import { Injector } from '@furystack/inject'
|
|
3
|
+
import { createComponent, initializeShadeRoot } from '@furystack/shades'
|
|
4
|
+
import { sleepAsync, usingAsync } from '@furystack/utils'
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
|
+
import { createI18nComponent } from './create-i18n-component.js'
|
|
7
|
+
|
|
8
|
+
type TestKeys = 'hello' | 'goodbye' | 'world'
|
|
9
|
+
|
|
10
|
+
const createTestService = () => {
|
|
11
|
+
return new I18NService<TestKeys>(
|
|
12
|
+
{
|
|
13
|
+
code: 'en',
|
|
14
|
+
values: {
|
|
15
|
+
hello: 'Hello',
|
|
16
|
+
goodbye: 'Goodbye',
|
|
17
|
+
world: 'World',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
code: 'hu',
|
|
22
|
+
values: {
|
|
23
|
+
hello: 'Szia',
|
|
24
|
+
goodbye: 'Viszlát',
|
|
25
|
+
world: 'Világ',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: 'de',
|
|
30
|
+
values: {
|
|
31
|
+
hello: 'Hallo',
|
|
32
|
+
goodbye: 'Auf Wiedersehen',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('createI18nComponent', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
document.body.innerHTML = '<div id="root"></div>'
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
document.body.innerHTML = ''
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should render with the initial translation', async () => {
|
|
48
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
49
|
+
const service = createTestService()
|
|
50
|
+
const I18n = createI18nComponent({
|
|
51
|
+
service,
|
|
52
|
+
shadowDomName: 'test-i18n-initial',
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
56
|
+
initializeShadeRoot({
|
|
57
|
+
injector,
|
|
58
|
+
rootElement,
|
|
59
|
+
jsxElement: <I18n key="hello" />,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await sleepAsync(50)
|
|
63
|
+
expect(rootElement.textContent).toBe('Hello')
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should render with the correct translation for different keys', async () => {
|
|
68
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
69
|
+
const service = createTestService()
|
|
70
|
+
const I18n = createI18nComponent({
|
|
71
|
+
service,
|
|
72
|
+
shadowDomName: 'test-i18n-keys',
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
76
|
+
initializeShadeRoot({
|
|
77
|
+
injector,
|
|
78
|
+
rootElement,
|
|
79
|
+
jsxElement: (
|
|
80
|
+
<>
|
|
81
|
+
<I18n key="hello" />
|
|
82
|
+
<span> </span>
|
|
83
|
+
<I18n key="world" />
|
|
84
|
+
</>
|
|
85
|
+
),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
await sleepAsync(50)
|
|
89
|
+
expect(rootElement.textContent).toBe('Hello World')
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should update when language changes', async () => {
|
|
94
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
95
|
+
const service = createTestService()
|
|
96
|
+
const I18n = createI18nComponent({
|
|
97
|
+
service,
|
|
98
|
+
shadowDomName: 'test-i18n-language-change',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
102
|
+
initializeShadeRoot({
|
|
103
|
+
injector,
|
|
104
|
+
rootElement,
|
|
105
|
+
jsxElement: <I18n key="hello" />,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
await sleepAsync(50)
|
|
109
|
+
expect(rootElement.textContent).toBe('Hello')
|
|
110
|
+
|
|
111
|
+
service.currentLanguage = 'hu'
|
|
112
|
+
await sleepAsync(50)
|
|
113
|
+
expect(rootElement.textContent).toBe('Szia')
|
|
114
|
+
|
|
115
|
+
service.currentLanguage = 'de'
|
|
116
|
+
await sleepAsync(50)
|
|
117
|
+
expect(rootElement.textContent).toBe('Hallo')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should fallback to default language for missing keys', async () => {
|
|
122
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
123
|
+
const service = createTestService()
|
|
124
|
+
const I18n = createI18nComponent({
|
|
125
|
+
service,
|
|
126
|
+
shadowDomName: 'test-i18n-fallback',
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
130
|
+
initializeShadeRoot({
|
|
131
|
+
injector,
|
|
132
|
+
rootElement,
|
|
133
|
+
jsxElement: <I18n key="world" />,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
await sleepAsync(50)
|
|
137
|
+
expect(rootElement.textContent).toBe('World')
|
|
138
|
+
|
|
139
|
+
// German doesn't have 'world' translation, should fallback to English
|
|
140
|
+
service.currentLanguage = 'de'
|
|
141
|
+
await sleepAsync(50)
|
|
142
|
+
expect(rootElement.textContent).toBe('World')
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should handle rapid language changes', async () => {
|
|
147
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
148
|
+
const service = createTestService()
|
|
149
|
+
const I18n = createI18nComponent({
|
|
150
|
+
service,
|
|
151
|
+
shadowDomName: 'test-i18n-rapid-changes',
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
155
|
+
initializeShadeRoot({
|
|
156
|
+
injector,
|
|
157
|
+
rootElement,
|
|
158
|
+
jsxElement: <I18n key="hello" />,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await sleepAsync(50)
|
|
162
|
+
|
|
163
|
+
// Rapid language changes
|
|
164
|
+
service.currentLanguage = 'hu'
|
|
165
|
+
service.currentLanguage = 'de'
|
|
166
|
+
service.currentLanguage = 'en'
|
|
167
|
+
service.currentLanguage = 'hu'
|
|
168
|
+
|
|
169
|
+
await sleepAsync(50)
|
|
170
|
+
expect(rootElement.textContent).toBe('Szia')
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('should cleanup subscription on unmount', async () => {
|
|
175
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
176
|
+
const service = createTestService()
|
|
177
|
+
const unsubscribeSpy = vi.fn()
|
|
178
|
+
const originalSubscribe = service.subscribe.bind(service)
|
|
179
|
+
vi.spyOn(service, 'subscribe').mockImplementation((event, callback) => {
|
|
180
|
+
const subscription = originalSubscribe(event, callback)
|
|
181
|
+
return {
|
|
182
|
+
[Symbol.dispose]: () => {
|
|
183
|
+
unsubscribeSpy()
|
|
184
|
+
subscription[Symbol.dispose]()
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const I18n = createI18nComponent({
|
|
190
|
+
service,
|
|
191
|
+
shadowDomName: 'test-i18n-cleanup',
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
195
|
+
initializeShadeRoot({
|
|
196
|
+
injector,
|
|
197
|
+
rootElement,
|
|
198
|
+
jsxElement: <I18n key="hello" />,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
await sleepAsync(50)
|
|
202
|
+
expect(unsubscribeSpy).not.toHaveBeenCalled()
|
|
203
|
+
|
|
204
|
+
// Unmount by clearing the DOM
|
|
205
|
+
document.body.innerHTML = ''
|
|
206
|
+
await sleepAsync(50)
|
|
207
|
+
|
|
208
|
+
expect(unsubscribeSpy).toHaveBeenCalled()
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should render as span element', async () => {
|
|
213
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
214
|
+
const service = createTestService()
|
|
215
|
+
const I18n = createI18nComponent({
|
|
216
|
+
service,
|
|
217
|
+
shadowDomName: 'test-i18n-span',
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
221
|
+
initializeShadeRoot({
|
|
222
|
+
injector,
|
|
223
|
+
rootElement,
|
|
224
|
+
jsxElement: <I18n key="hello" />,
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
await sleepAsync(50)
|
|
228
|
+
// The component uses elementBaseName: 'span', so it renders as <span is="test-i18n-span">
|
|
229
|
+
const i18nElement = rootElement.querySelector('span[is="test-i18n-span"]')
|
|
230
|
+
expect(i18nElement).toBeInstanceOf(HTMLSpanElement)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('should work with multiple instances using different keys', async () => {
|
|
235
|
+
await usingAsync(new Injector(), async (injector) => {
|
|
236
|
+
const service = createTestService()
|
|
237
|
+
const I18n = createI18nComponent({
|
|
238
|
+
service,
|
|
239
|
+
shadowDomName: 'test-i18n-multiple',
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const rootElement = document.getElementById('root') as HTMLDivElement
|
|
243
|
+
initializeShadeRoot({
|
|
244
|
+
injector,
|
|
245
|
+
rootElement,
|
|
246
|
+
jsxElement: (
|
|
247
|
+
<div>
|
|
248
|
+
<I18n key="hello" />
|
|
249
|
+
{' - '}
|
|
250
|
+
<I18n key="goodbye" />
|
|
251
|
+
</div>
|
|
252
|
+
),
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
await sleepAsync(50)
|
|
256
|
+
expect(rootElement.textContent).toBe('Hello - Goodbye')
|
|
257
|
+
|
|
258
|
+
service.currentLanguage = 'hu'
|
|
259
|
+
await sleepAsync(50)
|
|
260
|
+
expect(rootElement.textContent).toBe('Szia - Viszlát')
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
})
|