@jjlmoya/utils-hardware 1.22.0 → 1.24.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.
Files changed (32) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +1 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/mouseScrollTest/mouse-scroll-test.css +23 -0
  8. package/src/tool/stereoAudioTest/bibliography.astro +14 -0
  9. package/src/tool/stereoAudioTest/bibliography.ts +16 -0
  10. package/src/tool/stereoAudioTest/component.astro +251 -0
  11. package/src/tool/stereoAudioTest/entry.ts +29 -0
  12. package/src/tool/stereoAudioTest/i18n/de.ts +229 -0
  13. package/src/tool/stereoAudioTest/i18n/en.ts +229 -0
  14. package/src/tool/stereoAudioTest/i18n/es.ts +229 -0
  15. package/src/tool/stereoAudioTest/i18n/fr.ts +229 -0
  16. package/src/tool/stereoAudioTest/i18n/id.ts +229 -0
  17. package/src/tool/stereoAudioTest/i18n/it.ts +229 -0
  18. package/src/tool/stereoAudioTest/i18n/ja.ts +229 -0
  19. package/src/tool/stereoAudioTest/i18n/ko.ts +229 -0
  20. package/src/tool/stereoAudioTest/i18n/nl.ts +229 -0
  21. package/src/tool/stereoAudioTest/i18n/pl.ts +229 -0
  22. package/src/tool/stereoAudioTest/i18n/pt.ts +229 -0
  23. package/src/tool/stereoAudioTest/i18n/ru.ts +229 -0
  24. package/src/tool/stereoAudioTest/i18n/sv.ts +229 -0
  25. package/src/tool/stereoAudioTest/i18n/tr.ts +229 -0
  26. package/src/tool/stereoAudioTest/i18n/zh.ts +229 -0
  27. package/src/tool/stereoAudioTest/index.ts +11 -0
  28. package/src/tool/stereoAudioTest/logic.ts +22 -0
  29. package/src/tool/stereoAudioTest/seo.astro +15 -0
  30. package/src/tool/stereoAudioTest/stereo-audio-test.css +411 -0
  31. package/src/tool/stereoAudioTest/ui.ts +24 -0
  32. package/src/tools.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-hardware",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -12,10 +12,11 @@ import { refreshRateDetector } from '../tool/refreshRateDetector/index';
12
12
  import { monitorGhostingTest } from '../tool/monitorGhostingTest/index';
13
13
  import { spectrumCanvas } from '../tool/colorAccuracyTest/index';
14
14
  import { upsRuntimeCalculator } from '../tool/upsRuntimeCalculator/index';
15
+ import { stereoAudioTest } from '../tool/stereoAudioTest/index';
15
16
 
16
17
  export const hardwareCategory: HardwareCategoryEntry = {
17
18
  icon: 'mdi:memory',
18
- tools: [pixelesPantalla, testTeclado, testMando, probadorVibracionMando, testRaton, mouseDoubleClickTest, mouseScrollTest, estimadorSaludBateria, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator],
19
+ tools: [pixelesPantalla, testTeclado, testMando, probadorVibracionMando, testRaton, mouseDoubleClickTest, mouseScrollTest, estimadorSaludBateria, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, stereoAudioTest],
19
20
  i18n: {
20
21
  en: () => import('./i18n/en').then((m) => m.content),
21
22
  es: () => import('./i18n/es').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -24,6 +24,8 @@ export { spectrumCanvas } from './tool/colorAccuracyTest/entry';
24
24
  export type { SpectrumCanvasLocaleContent } from './tool/colorAccuracyTest/entry';
25
25
  export { upsRuntimeCalculator } from './tool/upsRuntimeCalculator/entry';
26
26
  export type { UpsRuntimeCalculatorLocaleContent } from './tool/upsRuntimeCalculator/entry';
27
+ export { stereoAudioTest } from './tool/stereoAudioTest/entry';
28
+ export type { StereoAudioTestLocaleContent } from './tool/stereoAudioTest/entry';
27
29
  export { hardwareCategory } from './category';
28
30
  import { estimadorSaludBateria } from './tool/batteryHealthEstimator/entry';
29
31
  import { pixelesPantalla } from './tool/deadPixelTest/entry';
@@ -38,4 +40,5 @@ import { refreshRateDetector } from './tool/refreshRateDetector/entry';
38
40
  import { monitorGhostingTest } from './tool/monitorGhostingTest/entry';
39
41
  import { spectrumCanvas } from './tool/colorAccuracyTest/entry';
40
42
  import { upsRuntimeCalculator } from './tool/upsRuntimeCalculator/entry';
41
- export const ALL_ENTRIES = [estimadorSaludBateria, pixelesPantalla, testMando, probadorVibracionMando, testTeclado, testRaton, mouseDoubleClickTest, mouseScrollTest, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator];
43
+ import { stereoAudioTest } from './tool/stereoAudioTest/entry';
44
+ export const ALL_ENTRIES = [estimadorSaludBateria, pixelesPantalla, testMando, probadorVibracionMando, testTeclado, testRaton, mouseDoubleClickTest, mouseScrollTest, toneGenerator, refreshRateDetector, monitorGhostingTest, spectrumCanvas, upsRuntimeCalculator, stereoAudioTest];
package/src/index.ts CHANGED
@@ -30,3 +30,4 @@ export { REFRESH_RATE_DETECTOR_TOOL } from './tool/refreshRateDetector/index';
30
30
  export { MONITOR_GHOSTING_TEST_TOOL } from './tool/monitorGhostingTest/index';
31
31
  export { SPECTRUM_CANVAS_TOOL } from './tool/colorAccuracyTest/index';
32
32
  export { UPS_RUNTIME_CALCULATOR_TOOL } from './tool/upsRuntimeCalculator/index';
33
+ export { STEREO_AUDIO_TEST_TOOL } from './tool/stereoAudioTest/index';
@@ -21,8 +21,8 @@ describe('Locale Completeness Validation', () => {
21
21
  });
22
22
  });
23
23
 
24
- it('all 13 tools registered', () => {
25
- expect(ALL_TOOLS.length).toBe(13);
24
+ it('all 14 tools registered', () => {
25
+ expect(ALL_TOOLS.length).toBe(14);
26
26
  });
27
27
  });
28
28
 
@@ -4,8 +4,8 @@ import { hardwareCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
- it('should have 13 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(13);
7
+ it('should have 14 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(14);
9
9
  });
10
10
 
11
11
  it('hardwareCategory should be defined', () => {
@@ -1,5 +1,14 @@
1
1
  .mst-root {
2
+ box-sizing: border-box;
2
3
  width: 100%;
4
+ max-width: 100%;
5
+ min-width: 0;
6
+ }
7
+
8
+ .mst-root *,
9
+ .mst-root *::before,
10
+ .mst-root *::after {
11
+ box-sizing: inherit;
3
12
  }
4
13
 
5
14
  .mst-card {
@@ -7,6 +16,8 @@
7
16
  grid-template-columns: minmax(0, 1.05fr) minmax(280px, 0.95fr);
8
17
  gap: 1rem;
9
18
  width: 100%;
19
+ max-width: 100%;
20
+ min-width: 0;
10
21
  padding: clamp(0.9rem, 3vw, 1.25rem);
11
22
  border: 1px solid rgba(21, 94, 117, 0.22);
12
23
  border-radius: 8px;
@@ -29,6 +40,8 @@
29
40
  .mst-capture {
30
41
  position: relative;
31
42
  display: grid;
43
+ width: 100%;
44
+ min-width: 0;
32
45
  min-height: 430px;
33
46
  overflow: hidden;
34
47
  padding: clamp(1rem, 4vw, 1.5rem);
@@ -89,6 +102,7 @@
89
102
  display: inline-flex;
90
103
  align-items: center;
91
104
  gap: 0.45rem;
105
+ max-width: 100%;
92
106
  padding: 0.5rem 0.7rem;
93
107
  border-radius: 999px;
94
108
  font-size: 0.82rem;
@@ -109,6 +123,7 @@
109
123
  }
110
124
 
111
125
  .mst-lock-text {
126
+ width: 100%;
112
127
  max-width: 28rem;
113
128
  margin: 0;
114
129
  color: #fff;
@@ -196,6 +211,7 @@
196
211
  display: grid;
197
212
  gap: 0.45rem;
198
213
  max-width: 30rem;
214
+ min-width: 0;
199
215
  }
200
216
 
201
217
  @keyframes mst-locked-pulse {
@@ -250,7 +266,9 @@
250
266
  }
251
267
 
252
268
  .mst-capture-copy small {
269
+ display: block;
253
270
  max-width: 24rem;
271
+ overflow-wrap: anywhere;
254
272
  color: #0f172a;
255
273
  font-size: clamp(1.1rem, 3.4vw, 1.35rem);
256
274
  font-weight: 900;
@@ -310,6 +328,7 @@
310
328
  display: grid;
311
329
  gap: 0.85rem;
312
330
  align-content: start;
331
+ min-width: 0;
313
332
  }
314
333
 
315
334
  .mst-score,
@@ -349,6 +368,7 @@
349
368
  font-size: 0.94rem;
350
369
  font-weight: 750;
351
370
  line-height: 1.35;
371
+ overflow-wrap: anywhere;
352
372
  }
353
373
 
354
374
  .theme-dark .mst-direction-note {
@@ -495,10 +515,12 @@
495
515
  align-items: center;
496
516
  justify-content: space-between;
497
517
  gap: 0.75rem;
518
+ min-width: 0;
498
519
  font-weight: 800;
499
520
  }
500
521
 
501
522
  .mst-log-head button {
523
+ flex: 0 0 auto;
502
524
  border: 0;
503
525
  border-radius: 999px;
504
526
  background: #0f766e;
@@ -534,6 +556,7 @@
534
556
  border-radius: 8px;
535
557
  background: rgba(236, 253, 245, 0.82);
536
558
  font-size: 0.86rem;
559
+ overflow-wrap: anywhere;
537
560
  }
538
561
 
539
562
  .mst-log-item.reversal {
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import type { KnownLocale } from '../../types';
4
+ import { stereoAudioTest } from './index';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await stereoAudioTest.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,16 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ {
5
+ name: 'Microsoft Support - Fix sound or audio problems in Windows',
6
+ url: 'https://support.microsoft.com/windows/fix-sound-or-audio-problems-in-windows-73025246-b61c-40fb-671a-2535c7cd56c8',
7
+ },
8
+ {
9
+ name: 'Apple Support - Adjust audio balance and mono audio on Mac',
10
+ url: 'https://support.apple.com/guide/mac-help/change-sound-output-settings-mchlp2256/mac',
11
+ },
12
+ {
13
+ name: 'Crutchfield - Speaker placement for stereo listening',
14
+ url: 'https://www.crutchfield.com/learn/home-stereo-speaker-placement.html',
15
+ },
16
+ ];
@@ -0,0 +1,251 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import type { KnownLocale } from '../../types';
4
+ import type { StereoAudioTestUI } from './ui';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ ui?: Record<string, unknown>;
9
+ }
10
+
11
+ const { ui } = Astro.props;
12
+ const t = (ui ?? {}) as StereoAudioTestUI;
13
+ ---
14
+
15
+ <div
16
+ class="sat-root"
17
+ data-config={JSON.stringify({
18
+ left: t.left,
19
+ center: t.center,
20
+ right: t.right,
21
+ sweep: t.sweep,
22
+ stop: t.stop,
23
+ activeIdle: t.activeIdle,
24
+ activeLeft: t.activeLeft,
25
+ activeCenter: t.activeCenter,
26
+ activeRight: t.activeRight,
27
+ activeSweep: t.activeSweep,
28
+ secondsUnit: t.secondsUnit,
29
+ })}
30
+ >
31
+ <section class="sat-panel">
32
+ <div class="sat-stage" aria-live="polite">
33
+ <div class="sat-speaker sat-left">
34
+ <span></span>
35
+ <strong>{t.leftSpeaker}</strong>
36
+ </div>
37
+ <div class="sat-orbit" aria-hidden="true">
38
+ <i data-dot></i>
39
+ <b>{t.centerLine}</b>
40
+ </div>
41
+ <div class="sat-speaker sat-right">
42
+ <span></span>
43
+ <strong>{t.rightSpeaker}</strong>
44
+ </div>
45
+ </div>
46
+
47
+ <div class="sat-controls">
48
+ <div class="sat-buttons">
49
+ <button type="button" data-play="left"><Icon name="mdi:play" />{t.left}</button>
50
+ <button type="button" data-play="center"><Icon name="mdi:play" />{t.center}</button>
51
+ <button type="button" data-play="right"><Icon name="mdi:play" />{t.right}</button>
52
+ <button type="button" data-play="sweep"><Icon name="mdi:play" />{t.sweep}</button>
53
+ <button type="button" data-stop>{t.stop}</button>
54
+ </div>
55
+
56
+ <div class="sat-sliders">
57
+ <label class="sat-control-row">
58
+ <span>{t.volume}</span>
59
+ <strong data-volume-label>24%</strong>
60
+ <input data-volume type="range" min="1" max="80" value="24" />
61
+ </label>
62
+ <label class="sat-control-row" data-duration-control>
63
+ <span>{t.duration}</span>
64
+ <strong><output data-duration-label>3</output>{t.secondsUnit}</strong>
65
+ <input data-duration type="range" min="1" max="8" value="3" />
66
+ </label>
67
+ <label class="sat-control-row">
68
+ <span>{t.tone}</span>
69
+ <strong><output data-frequency-label>520</output>{t.hertzUnit}</strong>
70
+ <input data-frequency type="range" min="220" max="1200" step="10" value="520" />
71
+ </label>
72
+ </div>
73
+
74
+ <label class="sat-loop">
75
+ <input data-loop type="checkbox" />
76
+ <span>
77
+ <strong>{t.infiniteMode}</strong>
78
+ <em>{t.infiniteModeHint}</em>
79
+ </span>
80
+ </label>
81
+
82
+ <div class="sat-readout">
83
+ <span>{t.balance}</span>
84
+ <strong data-status>{t.activeIdle}</strong>
85
+ <meter min="-1" max="1" low="-0.65" high="0.65" optimum="0" value="0" data-meter></meter>
86
+ </div>
87
+ <p>{t.safety}</p>
88
+ </div>
89
+ </section>
90
+ </div>
91
+
92
+ <link rel="stylesheet" href="./stereo-audio-test.css" />
93
+
94
+ <script>
95
+ type Channel = 'left' | 'center' | 'right' | 'sweep';
96
+ interface Config {
97
+ left: string;
98
+ center: string;
99
+ right: string;
100
+ sweep: string;
101
+ stop: string;
102
+ activeIdle: string;
103
+ activeLeft: string;
104
+ activeCenter: string;
105
+ activeRight: string;
106
+ activeSweep: string;
107
+ secondsUnit: string;
108
+ }
109
+
110
+ const root = document.querySelector<HTMLElement>('.sat-root');
111
+ const config = JSON.parse(root?.dataset.config ?? '{}') as Config;
112
+ const audioState: {
113
+ context?: AudioContext;
114
+ oscillator?: OscillatorNode;
115
+ gain?: GainNode;
116
+ stopTimer?: number;
117
+ sweepTimer?: number;
118
+ } = {};
119
+ const els = {
120
+ dot: root?.querySelector<HTMLElement>('[data-dot]'),
121
+ meter: root?.querySelector<HTMLMeterElement>('[data-meter]'),
122
+ status: root?.querySelector<HTMLElement>('[data-status]'),
123
+ volume: root?.querySelector<HTMLInputElement>('[data-volume]'),
124
+ volumeLabel: root?.querySelector<HTMLElement>('[data-volume-label]'),
125
+ duration: root?.querySelector<HTMLInputElement>('[data-duration]'),
126
+ durationControl: root?.querySelector<HTMLElement>('[data-duration-control]'),
127
+ durationLabel: root?.querySelector<HTMLOutputElement>('[data-duration-label]'),
128
+ frequency: root?.querySelector<HTMLInputElement>('[data-frequency]'),
129
+ frequencyLabel: root?.querySelector<HTMLOutputElement>('[data-frequency-label]'),
130
+ loop: root?.querySelector<HTMLInputElement>('[data-loop]'),
131
+ };
132
+
133
+ function ensureContext() {
134
+ audioState.context ??= new AudioContext();
135
+ return audioState.context;
136
+ }
137
+
138
+ function play(channel: Channel) {
139
+ stop();
140
+ const context = ensureContext();
141
+ const oscillator = context.createOscillator();
142
+ const gain = context.createGain();
143
+ const panner = context.createStereoPanner();
144
+ const duration = readNumber(els.duration, 3);
145
+ const volume = readNumber(els.volume, 24) / 100;
146
+ const frequency = readNumber(els.frequency, 520);
147
+
148
+ oscillator.type = 'sine';
149
+ oscillator.frequency.value = frequency;
150
+ gain.gain.setValueAtTime(0.0001, context.currentTime);
151
+ gain.gain.exponentialRampToValueAtTime(Math.max(0.0001, volume), context.currentTime + 0.03);
152
+ oscillator.connect(gain).connect(panner).connect(context.destination);
153
+ oscillator.start();
154
+ audioState.oscillator = oscillator;
155
+ audioState.gain = gain;
156
+
157
+ const shouldLoop = Boolean(els.loop?.checked);
158
+ if (channel === 'sweep') {
159
+ runSweep(panner, duration);
160
+ } else {
161
+ setPan(panner, getPan(channel));
162
+ }
163
+ if (!shouldLoop) {
164
+ gain.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + duration);
165
+ oscillator.stop(context.currentTime + duration + 0.05);
166
+ audioState.stopTimer = window.setTimeout(stop, (duration + 0.08) * 1000);
167
+ }
168
+ setStatus(channel);
169
+ }
170
+
171
+ function runSweep(panner: StereoPannerNode, duration: number) {
172
+ const start = performance.now();
173
+ const tick = (now: number) => {
174
+ const progress = ((now - start) % (duration * 1000)) / (duration * 1000);
175
+ const pan = Math.sin(progress * Math.PI * 2);
176
+ setPan(panner, pan);
177
+ audioState.sweepTimer = requestAnimationFrame(tick);
178
+ };
179
+ audioState.sweepTimer = requestAnimationFrame(tick);
180
+ }
181
+
182
+ function setPan(panner: StereoPannerNode, pan: number) {
183
+ panner.pan.value = pan;
184
+ root?.style.setProperty('--sat-pan', String(pan));
185
+ els.meter!.value = pan;
186
+ }
187
+
188
+ function getPan(channel: Channel) {
189
+ if (channel === 'left') return -1;
190
+ if (channel === 'right') return 1;
191
+ return 0;
192
+ }
193
+
194
+ function stop() {
195
+ if (audioState.stopTimer) window.clearTimeout(audioState.stopTimer);
196
+ if (audioState.sweepTimer) cancelAnimationFrame(audioState.sweepTimer);
197
+ if (audioState.oscillator) {
198
+ try {
199
+ audioState.oscillator.stop();
200
+ } catch {
201
+ }
202
+ audioState.oscillator = undefined;
203
+ }
204
+ audioState.gain = undefined;
205
+ root?.style.setProperty('--sat-pan', '0');
206
+ if (els.meter) els.meter.value = 0;
207
+ if (els.status) els.status.textContent = config.activeIdle;
208
+ }
209
+
210
+ function setStatus(channel: Channel) {
211
+ const labels = {
212
+ left: config.activeLeft,
213
+ center: config.activeCenter,
214
+ right: config.activeRight,
215
+ sweep: config.activeSweep,
216
+ };
217
+ if (els.status) els.status.textContent = labels[channel];
218
+ }
219
+
220
+ function readNumber(input: HTMLInputElement | null | undefined, fallback: number) {
221
+ return input ? Number(input.value) : fallback;
222
+ }
223
+
224
+ function syncLabels() {
225
+ if (els.volumeLabel && els.volume) els.volumeLabel.textContent = `${els.volume.value}%`;
226
+ if (els.durationLabel && els.duration) els.durationLabel.textContent = els.duration.value;
227
+ if (els.frequencyLabel && els.frequency) els.frequencyLabel.textContent = els.frequency.value;
228
+ syncLoopState();
229
+ updateActiveVolume();
230
+ }
231
+
232
+ function syncLoopState() {
233
+ const isLooping = Boolean(els.loop?.checked);
234
+ if (els.duration) els.duration.disabled = isLooping;
235
+ els.durationControl?.classList.toggle('is-disabled', isLooping);
236
+ }
237
+
238
+ function updateActiveVolume() {
239
+ if (!audioState.context || !audioState.gain || !els.volume) return;
240
+ const volume = Math.max(0.0001, Number(els.volume.value) / 100);
241
+ audioState.gain.gain.setTargetAtTime(volume, audioState.context.currentTime, 0.015);
242
+ }
243
+
244
+ root?.querySelectorAll<HTMLButtonElement>('[data-play]').forEach((button) => {
245
+ button.addEventListener('click', () => play(button.dataset.play as Channel));
246
+ });
247
+ root?.querySelector<HTMLButtonElement>('[data-stop]')?.addEventListener('click', stop);
248
+ [els.volume, els.duration, els.frequency].forEach((input) => input?.addEventListener('input', syncLabels));
249
+ els.loop?.addEventListener('change', syncLabels);
250
+ syncLabels();
251
+ </script>
@@ -0,0 +1,29 @@
1
+ import type { HardwareToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { StereoAudioTestUI } from './ui';
3
+
4
+ export type StereoAudioTestLocaleContent = ToolLocaleContent<StereoAudioTestUI>;
5
+
6
+ export const stereoAudioTest: HardwareToolEntry<StereoAudioTestUI> = {
7
+ id: 'stereo-audio-test',
8
+ icons: {
9
+ bg: 'mdi:speaker-multiple',
10
+ fg: 'mdi:surround-sound',
11
+ },
12
+ i18n: {
13
+ de: () => import('./i18n/de').then((m) => m.content),
14
+ en: () => import('./i18n/en').then((m) => m.content),
15
+ es: () => import('./i18n/es').then((m) => m.content),
16
+ fr: () => import('./i18n/fr').then((m) => m.content),
17
+ id: () => import('./i18n/id').then((m) => m.content),
18
+ it: () => import('./i18n/it').then((m) => m.content),
19
+ ja: () => import('./i18n/ja').then((m) => m.content),
20
+ ko: () => import('./i18n/ko').then((m) => m.content),
21
+ nl: () => import('./i18n/nl').then((m) => m.content),
22
+ pl: () => import('./i18n/pl').then((m) => m.content),
23
+ pt: () => import('./i18n/pt').then((m) => m.content),
24
+ ru: () => import('./i18n/ru').then((m) => m.content),
25
+ sv: () => import('./i18n/sv').then((m) => m.content),
26
+ tr: () => import('./i18n/tr').then((m) => m.content),
27
+ zh: () => import('./i18n/zh').then((m) => m.content),
28
+ },
29
+ };