@pie-players/pie-players-shared 0.2.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/dist/config/profile.d.ts +15 -0
- package/dist/config/profile.d.ts.map +1 -0
- package/dist/config/profile.js +27 -0
- package/dist/config/profile.js.map +1 -0
- package/dist/i18n/index.d.ts +13 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +12 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/loader.d.ts +36 -0
- package/dist/i18n/loader.d.ts.map +1 -0
- package/dist/i18n/loader.js +133 -0
- package/dist/i18n/loader.js.map +1 -0
- package/dist/i18n/scripts/check-coverage.d.ts +16 -0
- package/dist/i18n/scripts/check-coverage.d.ts.map +1 -0
- package/dist/i18n/scripts/check-coverage.js +262 -0
- package/dist/i18n/scripts/check-coverage.js.map +1 -0
- package/dist/i18n/scripts/scan-hardcoded.d.ts +16 -0
- package/dist/i18n/scripts/scan-hardcoded.d.ts.map +1 -0
- package/dist/i18n/scripts/scan-hardcoded.js +266 -0
- package/dist/i18n/scripts/scan-hardcoded.js.map +1 -0
- package/dist/i18n/simple-i18n.d.ts +69 -0
- package/dist/i18n/simple-i18n.d.ts.map +1 -0
- package/dist/i18n/simple-i18n.js +199 -0
- package/dist/i18n/simple-i18n.js.map +1 -0
- package/dist/i18n/translations/ar/common.json +36 -0
- package/dist/i18n/translations/ar/toolkit.json +48 -0
- package/dist/i18n/translations/ar/tools.json +109 -0
- package/dist/i18n/translations/en/common.json +36 -0
- package/dist/i18n/translations/en/toolkit.json +48 -0
- package/dist/i18n/translations/en/tools.json +109 -0
- package/dist/i18n/translations/es/common.json +36 -0
- package/dist/i18n/translations/es/toolkit.json +48 -0
- package/dist/i18n/translations/es/tools.json +109 -0
- package/dist/i18n/translations/zh/common.json +36 -0
- package/dist/i18n/translations/zh/toolkit.json +48 -0
- package/dist/i18n/translations/zh/tools.json +109 -0
- package/dist/i18n/types.d.ts +58 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +8 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/i18n/use-i18n-standalone.svelte.d.ts +87 -0
- package/dist/i18n/use-i18n-standalone.svelte.d.ts.map +1 -0
- package/dist/i18n/use-i18n-standalone.svelte.js +151 -0
- package/dist/i18n/use-i18n-standalone.svelte.js.map +1 -0
- package/dist/i18n/use-i18n.svelte.d.ts +67 -0
- package/dist/i18n/use-i18n.svelte.d.ts.map +1 -0
- package/dist/i18n/use-i18n.svelte.js +144 -0
- package/dist/i18n/use-i18n.svelte.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +53 -0
- package/dist/instrumentation/index.d.ts.map +1 -0
- package/dist/instrumentation/index.js +53 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts +197 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js +267 -0
- package/dist/instrumentation/providers/BaseInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts +106 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js +182 -0
- package/dist/instrumentation/providers/ConsoleInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts +170 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.js +183 -0
- package/dist/instrumentation/providers/DataDogInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts +86 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.d.ts.map +1 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js +135 -0
- package/dist/instrumentation/providers/NewRelicInstrumentationProvider.js.map +1 -0
- package/dist/instrumentation/providers/index.d.ts +12 -0
- package/dist/instrumentation/providers/index.d.ts.map +1 -0
- package/dist/instrumentation/providers/index.js +12 -0
- package/dist/instrumentation/providers/index.js.map +1 -0
- package/dist/instrumentation/types.d.ts +348 -0
- package/dist/instrumentation/types.d.ts.map +1 -0
- package/dist/instrumentation/types.js +9 -0
- package/dist/instrumentation/types.js.map +1 -0
- package/dist/loader-config.d.ts +76 -0
- package/dist/loader-config.d.ts.map +1 -0
- package/dist/loader-config.js +12 -0
- package/dist/loader-config.js.map +1 -0
- package/dist/loaders/ElementLoader.d.ts +72 -0
- package/dist/loaders/ElementLoader.d.ts.map +1 -0
- package/dist/loaders/ElementLoader.js +52 -0
- package/dist/loaders/ElementLoader.js.map +1 -0
- package/dist/loaders/EsmElementLoader.d.ts +67 -0
- package/dist/loaders/EsmElementLoader.d.ts.map +1 -0
- package/dist/loaders/EsmElementLoader.js +71 -0
- package/dist/loaders/EsmElementLoader.js.map +1 -0
- package/dist/loaders/IifeElementLoader.d.ts +61 -0
- package/dist/loaders/IifeElementLoader.d.ts.map +1 -0
- package/dist/loaders/IifeElementLoader.js +63 -0
- package/dist/loaders/IifeElementLoader.js.map +1 -0
- package/dist/loaders/index.d.ts +28 -0
- package/dist/loaders/index.d.ts.map +1 -0
- package/dist/loaders/index.js +25 -0
- package/dist/loaders/index.js.map +1 -0
- package/dist/object/index.d.ts +12 -0
- package/dist/object/index.d.ts.map +1 -0
- package/dist/object/index.js +40 -0
- package/dist/object/index.js.map +1 -0
- package/dist/pie/asset-handler.d.ts +64 -0
- package/dist/pie/asset-handler.d.ts.map +1 -0
- package/dist/pie/asset-handler.js +238 -0
- package/dist/pie/asset-handler.js.map +1 -0
- package/dist/pie/component-context.d.ts +22 -0
- package/dist/pie/component-context.d.ts.map +1 -0
- package/dist/pie/component-context.js +30 -0
- package/dist/pie/component-context.js.map +1 -0
- package/dist/pie/config.d.ts +39 -0
- package/dist/pie/config.d.ts.map +1 -0
- package/dist/pie/config.js +174 -0
- package/dist/pie/config.js.map +1 -0
- package/dist/pie/configure-initialization.d.ts +35 -0
- package/dist/pie/configure-initialization.d.ts.map +1 -0
- package/dist/pie/configure-initialization.js +141 -0
- package/dist/pie/configure-initialization.js.map +1 -0
- package/dist/pie/esm-loader.d.ts +93 -0
- package/dist/pie/esm-loader.d.ts.map +1 -0
- package/dist/pie/esm-loader.js +308 -0
- package/dist/pie/esm-loader.js.map +1 -0
- package/dist/pie/iife-loader.d.ts +76 -0
- package/dist/pie/iife-loader.d.ts.map +1 -0
- package/dist/pie/iife-loader.js +303 -0
- package/dist/pie/iife-loader.js.map +1 -0
- package/dist/pie/index.d.ts +31 -0
- package/dist/pie/index.d.ts.map +1 -0
- package/dist/pie/index.js +34 -0
- package/dist/pie/index.js.map +1 -0
- package/dist/pie/initialization.d.ts +40 -0
- package/dist/pie/initialization.d.ts.map +1 -0
- package/dist/pie/initialization.js +349 -0
- package/dist/pie/initialization.js.map +1 -0
- package/dist/pie/logger.d.ts +64 -0
- package/dist/pie/logger.d.ts.map +1 -0
- package/dist/pie/logger.js +45 -0
- package/dist/pie/logger.js.map +1 -0
- package/dist/pie/math-rendering.d.ts +69 -0
- package/dist/pie/math-rendering.d.ts.map +1 -0
- package/dist/pie/math-rendering.js +98 -0
- package/dist/pie/math-rendering.js.map +1 -0
- package/dist/pie/overrides.d.ts +43 -0
- package/dist/pie/overrides.d.ts.map +1 -0
- package/dist/pie/overrides.js +146 -0
- package/dist/pie/overrides.js.map +1 -0
- package/dist/pie/player-initializer.d.ts +55 -0
- package/dist/pie/player-initializer.d.ts.map +1 -0
- package/dist/pie/player-initializer.js +123 -0
- package/dist/pie/player-initializer.js.map +1 -0
- package/dist/pie/registry.d.ts +11 -0
- package/dist/pie/registry.d.ts.map +1 -0
- package/dist/pie/registry.js +21 -0
- package/dist/pie/registry.js.map +1 -0
- package/dist/pie/resource-monitor.d.ts +208 -0
- package/dist/pie/resource-monitor.d.ts.map +1 -0
- package/dist/pie/resource-monitor.js +969 -0
- package/dist/pie/resource-monitor.js.map +1 -0
- package/dist/pie/scoring.d.ts +17 -0
- package/dist/pie/scoring.d.ts.map +1 -0
- package/dist/pie/scoring.js +84 -0
- package/dist/pie/scoring.js.map +1 -0
- package/dist/pie/types.d.ts +136 -0
- package/dist/pie/types.d.ts.map +1 -0
- package/dist/pie/types.js +52 -0
- package/dist/pie/types.js.map +1 -0
- package/dist/pie/updates.d.ts +20 -0
- package/dist/pie/updates.d.ts.map +1 -0
- package/dist/pie/updates.js +175 -0
- package/dist/pie/updates.js.map +1 -0
- package/dist/pie/use-resource-monitor.svelte.d.ts +56 -0
- package/dist/pie/use-resource-monitor.svelte.d.ts.map +1 -0
- package/dist/pie/use-resource-monitor.svelte.js +117 -0
- package/dist/pie/use-resource-monitor.svelte.js.map +1 -0
- package/dist/pie/utils.d.ts +44 -0
- package/dist/pie/utils.d.ts.map +1 -0
- package/dist/pie/utils.js +74 -0
- package/dist/pie/utils.js.map +1 -0
- package/dist/types/custom-elements.d.ts +183 -0
- package/dist/types/custom-elements.d.ts.map +1 -0
- package/dist/types/custom-elements.js +8 -0
- package/dist/types/custom-elements.js.map +1 -0
- package/dist/types/index.d.ts +761 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +120 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/search.d.ts +105 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +12 -0
- package/dist/types/search.js.map +1 -0
- package/dist/types/transform.d.ts +48 -0
- package/dist/types/transform.d.ts.map +1 -0
- package/dist/types/transform.js +21 -0
- package/dist/types/transform.js.map +1 -0
- package/dist/ui/focus-trap.d.ts +10 -0
- package/dist/ui/focus-trap.d.ts.map +1 -0
- package/dist/ui/focus-trap.js +30 -0
- package/dist/ui/focus-trap.js.map +1 -0
- package/dist/ui/safe-storage.d.ts +3 -0
- package/dist/ui/safe-storage.d.ts.map +1 -0
- package/dist/ui/safe-storage.js +21 -0
- package/dist/ui/safe-storage.js.map +1 -0
- package/package.json +118 -0
- package/src/components/PieItemPlayer.svelte +604 -0
- package/src/components/PiePreviewLayout.svelte +144 -0
- package/src/components/PiePreviewToggle.svelte +110 -0
- package/src/components/PieSpinner.svelte +85 -0
- package/src/components/ToolSettingsButton.svelte +31 -0
- package/src/components/ToolSettingsPanel.svelte +90 -0
- package/src/components/index.ts +6 -0
- package/src/i18n/README.md +223 -0
- package/src/i18n/index.ts +26 -0
- package/src/i18n/loader.ts +156 -0
- package/src/i18n/scripts/check-coverage.ts +345 -0
- package/src/i18n/scripts/scan-hardcoded.ts +342 -0
- package/src/i18n/simple-i18n.ts +236 -0
- package/src/i18n/translations/ar/common.json +36 -0
- package/src/i18n/translations/ar/toolkit.json +48 -0
- package/src/i18n/translations/ar/tools.json +109 -0
- package/src/i18n/translations/en/common.json +36 -0
- package/src/i18n/translations/en/toolkit.json +48 -0
- package/src/i18n/translations/en/tools.json +109 -0
- package/src/i18n/translations/es/common.json +36 -0
- package/src/i18n/translations/es/toolkit.json +48 -0
- package/src/i18n/translations/es/tools.json +109 -0
- package/src/i18n/translations/zh/common.json +36 -0
- package/src/i18n/translations/zh/toolkit.json +48 -0
- package/src/i18n/translations/zh/tools.json +109 -0
- package/src/i18n/types.ts +66 -0
- package/src/i18n/use-i18n-standalone.svelte.ts +184 -0
- package/src/i18n/use-i18n.svelte.ts +163 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
PiePreviewLayout - Layout container for authoring with preview
|
|
3
|
+
|
|
4
|
+
Combines PiePreviewToggle with conditional rendering of PieItemPlayer instances.
|
|
5
|
+
Switches between authoring mode (configure elements) and preview mode (regular player elements).
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { tick } from 'svelte';
|
|
9
|
+
import type { LoaderConfig } from '../loader-config';
|
|
10
|
+
import { DEFAULT_LOADER_CONFIG } from '../loader-config';
|
|
11
|
+
import { createPieLogger, isGlobalDebugEnabled } from '../pie/logger';
|
|
12
|
+
import { BundleType } from '../pie/types';
|
|
13
|
+
import type { ConfigEntity, Env, ImageHandler, SoundHandler } from '../types';
|
|
14
|
+
import PieItemPlayer from './PieItemPlayer.svelte';
|
|
15
|
+
import PiePreviewToggle from './PiePreviewToggle.svelte';
|
|
16
|
+
|
|
17
|
+
const logger = createPieLogger('pie-preview-layout', () => isGlobalDebugEnabled());
|
|
18
|
+
|
|
19
|
+
// Props using Svelte 5 runes
|
|
20
|
+
let {
|
|
21
|
+
mode = $bindable('author' as 'author' | 'preview'),
|
|
22
|
+
itemConfig,
|
|
23
|
+
passageConfig = null,
|
|
24
|
+
configuration = {} as Record<string, any>,
|
|
25
|
+
env = { mode: 'gather', role: 'student' } as Env,
|
|
26
|
+
session = [] as any[],
|
|
27
|
+
addCorrectResponse = false,
|
|
28
|
+
customClassname = '',
|
|
29
|
+
passageContainerClass = '',
|
|
30
|
+
containerClass = '',
|
|
31
|
+
// Legacy: authoring uses editor.js, preview uses player/client-player.
|
|
32
|
+
// For backwards-compat, keep `bundleType` but allow per-mode overrides.
|
|
33
|
+
bundleType = BundleType.player,
|
|
34
|
+
bundleTypeAuthor = BundleType.editor,
|
|
35
|
+
bundleTypePreview = BundleType.player,
|
|
36
|
+
loaderConfig = DEFAULT_LOADER_CONFIG as LoaderConfig,
|
|
37
|
+
// Asset handler callbacks
|
|
38
|
+
onInsertImage,
|
|
39
|
+
onDeleteImage,
|
|
40
|
+
onInsertSound,
|
|
41
|
+
onDeleteSound,
|
|
42
|
+
// Event callbacks
|
|
43
|
+
onLoadComplete,
|
|
44
|
+
onPlayerError,
|
|
45
|
+
onSessionChanged,
|
|
46
|
+
onModelUpdated
|
|
47
|
+
}: {
|
|
48
|
+
mode?: 'author' | 'preview';
|
|
49
|
+
itemConfig: ConfigEntity;
|
|
50
|
+
passageConfig?: ConfigEntity | null;
|
|
51
|
+
configuration?: Record<string, any>;
|
|
52
|
+
env?: Env;
|
|
53
|
+
session?: any[];
|
|
54
|
+
addCorrectResponse?: boolean;
|
|
55
|
+
customClassname?: string;
|
|
56
|
+
passageContainerClass?: string;
|
|
57
|
+
containerClass?: string;
|
|
58
|
+
bundleType?: BundleType;
|
|
59
|
+
bundleTypeAuthor?: BundleType;
|
|
60
|
+
bundleTypePreview?: BundleType;
|
|
61
|
+
loaderConfig?: LoaderConfig;
|
|
62
|
+
// Asset handlers
|
|
63
|
+
onInsertImage?: (handler: ImageHandler) => void;
|
|
64
|
+
onDeleteImage?: (src: string, done: (err?: Error) => void) => void;
|
|
65
|
+
onInsertSound?: (handler: SoundHandler) => void;
|
|
66
|
+
onDeleteSound?: (src: string, done: (err?: Error) => void) => void;
|
|
67
|
+
// Event callbacks
|
|
68
|
+
onLoadComplete?: (detail?: any) => void;
|
|
69
|
+
onPlayerError?: (detail?: any) => void;
|
|
70
|
+
onSessionChanged?: (detail?: any) => void;
|
|
71
|
+
onModelUpdated?: (detail?: any) => void;
|
|
72
|
+
} = $props();
|
|
73
|
+
|
|
74
|
+
// Track current mode state
|
|
75
|
+
let currentMode = $state(mode);
|
|
76
|
+
|
|
77
|
+
// Handle mode changes from toggle
|
|
78
|
+
function handleModeChange(newMode: 'author' | 'preview') {
|
|
79
|
+
logger.debug('[PiePreviewLayout] Mode changed:', newMode);
|
|
80
|
+
currentMode = newMode;
|
|
81
|
+
mode = newMode; // Update parent via bindable
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Derive player mode from current mode
|
|
85
|
+
const playerMode = $derived.by(() => {
|
|
86
|
+
return currentMode === 'preview' ? 'view' : 'author';
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Derive bundle type from current mode (author vs preview).
|
|
90
|
+
// Prefer per-mode overrides; fall back to `bundleType` for older callers.
|
|
91
|
+
const effectiveBundleType = $derived.by(() => {
|
|
92
|
+
if (currentMode === 'preview') return bundleTypePreview ?? bundleType;
|
|
93
|
+
return bundleTypeAuthor ?? BundleType.editor;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Key to force re-mount when switching modes
|
|
97
|
+
const playerKey = $derived.by(() => {
|
|
98
|
+
return `${currentMode}-${Date.now()}`;
|
|
99
|
+
});
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<div class="pie-preview-layout">
|
|
103
|
+
<PiePreviewToggle mode={currentMode} onModeChange={handleModeChange} />
|
|
104
|
+
|
|
105
|
+
<div class="preview-content" role="tabpanel" id="{currentMode}-panel" aria-labelledby="{currentMode}-tab">
|
|
106
|
+
{#key playerKey}
|
|
107
|
+
<PieItemPlayer
|
|
108
|
+
{itemConfig}
|
|
109
|
+
{passageConfig}
|
|
110
|
+
{env}
|
|
111
|
+
{session}
|
|
112
|
+
{addCorrectResponse}
|
|
113
|
+
{customClassname}
|
|
114
|
+
{passageContainerClass}
|
|
115
|
+
{containerClass}
|
|
116
|
+
bundleType={effectiveBundleType}
|
|
117
|
+
{loaderConfig}
|
|
118
|
+
mode={playerMode}
|
|
119
|
+
{configuration}
|
|
120
|
+
{onInsertImage}
|
|
121
|
+
{onDeleteImage}
|
|
122
|
+
{onInsertSound}
|
|
123
|
+
{onDeleteSound}
|
|
124
|
+
{onLoadComplete}
|
|
125
|
+
{onPlayerError}
|
|
126
|
+
{onSessionChanged}
|
|
127
|
+
{onModelUpdated}
|
|
128
|
+
/>
|
|
129
|
+
{/key}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<style>
|
|
134
|
+
.pie-preview-layout {
|
|
135
|
+
display: block;
|
|
136
|
+
width: 100%;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.preview-content {
|
|
140
|
+
display: block;
|
|
141
|
+
width: 100%;
|
|
142
|
+
padding: 1rem 0;
|
|
143
|
+
}
|
|
144
|
+
</style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
PiePreviewToggle - Toggle between authoring and preview modes
|
|
3
|
+
|
|
4
|
+
Simple tab-based interface for switching between author and preview views.
|
|
5
|
+
Emits mode changes via callback prop.
|
|
6
|
+
-->
|
|
7
|
+
<script lang="ts">
|
|
8
|
+
import { createPieLogger, isGlobalDebugEnabled } from '../pie/logger';
|
|
9
|
+
|
|
10
|
+
const logger = createPieLogger('pie-preview-toggle', () => isGlobalDebugEnabled());
|
|
11
|
+
|
|
12
|
+
// Props using Svelte 5 runes
|
|
13
|
+
let {
|
|
14
|
+
mode = 'author' as 'author' | 'preview',
|
|
15
|
+
onModeChange
|
|
16
|
+
}: {
|
|
17
|
+
mode?: 'author' | 'preview';
|
|
18
|
+
onModeChange?: (mode: 'author' | 'preview') => void;
|
|
19
|
+
} = $props();
|
|
20
|
+
|
|
21
|
+
// Handle mode change
|
|
22
|
+
function handleModeChange(newMode: 'author' | 'preview') {
|
|
23
|
+
logger.debug('[PiePreviewToggle] Mode changed to:', newMode);
|
|
24
|
+
|
|
25
|
+
if (onModeChange) {
|
|
26
|
+
onModeChange(newMode);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Also dispatch DOM event for backward compatibility
|
|
30
|
+
const event = new CustomEvent('mode-changed', {
|
|
31
|
+
detail: { mode: newMode },
|
|
32
|
+
bubbles: true,
|
|
33
|
+
composed: true
|
|
34
|
+
});
|
|
35
|
+
dispatchEvent(event);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<div class="pie-preview-toggle">
|
|
40
|
+
<div class="toggle-tabs" role="tablist">
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
role="tab"
|
|
44
|
+
class="toggle-tab"
|
|
45
|
+
class:active={mode === 'author'}
|
|
46
|
+
aria-selected={mode === 'author'}
|
|
47
|
+
aria-controls="author-panel"
|
|
48
|
+
onclick={() => handleModeChange('author')}
|
|
49
|
+
>
|
|
50
|
+
Author
|
|
51
|
+
</button>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
role="tab"
|
|
55
|
+
class="toggle-tab"
|
|
56
|
+
class:active={mode === 'preview'}
|
|
57
|
+
aria-selected={mode === 'preview'}
|
|
58
|
+
aria-controls="preview-panel"
|
|
59
|
+
onclick={() => handleModeChange('preview')}
|
|
60
|
+
>
|
|
61
|
+
Preview
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<style>
|
|
67
|
+
.pie-preview-toggle {
|
|
68
|
+
display: block;
|
|
69
|
+
width: 100%;
|
|
70
|
+
margin-bottom: 1rem;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.toggle-tabs {
|
|
74
|
+
display: flex;
|
|
75
|
+
border-bottom: 2px solid #e0e0e0;
|
|
76
|
+
gap: 0.5rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.toggle-tab {
|
|
80
|
+
padding: 0.75rem 1.5rem;
|
|
81
|
+
background: transparent;
|
|
82
|
+
border: none;
|
|
83
|
+
border-bottom: 3px solid transparent;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
font-size: 1rem;
|
|
86
|
+
font-weight: 500;
|
|
87
|
+
color: #666;
|
|
88
|
+
transition: all 0.2s ease;
|
|
89
|
+
margin-bottom: -2px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.toggle-tab:hover {
|
|
93
|
+
color: #333;
|
|
94
|
+
background: rgba(0, 0, 0, 0.05);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.toggle-tab:focus {
|
|
98
|
+
outline: 2px solid #1976d2;
|
|
99
|
+
outline-offset: 2px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.toggle-tab.active {
|
|
103
|
+
color: #1976d2;
|
|
104
|
+
border-bottom-color: #1976d2;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.toggle-tab:active {
|
|
108
|
+
transform: translateY(1px);
|
|
109
|
+
}
|
|
110
|
+
</style>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let fixed: boolean = false;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="pie-loading {fixed ? 'fixed' : ''}"></div>
|
|
6
|
+
|
|
7
|
+
<style>
|
|
8
|
+
.pie-loading {
|
|
9
|
+
position: absolute;
|
|
10
|
+
height: 100%;
|
|
11
|
+
width: 100%;
|
|
12
|
+
background-color: #fff;
|
|
13
|
+
bottom: 0;
|
|
14
|
+
left: 0;
|
|
15
|
+
right: 0;
|
|
16
|
+
top: 0;
|
|
17
|
+
z-index: 9999;
|
|
18
|
+
opacity: 0.4;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.pie-loading.fixed {
|
|
22
|
+
position: fixed;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.pie-loading:before {
|
|
26
|
+
content: "";
|
|
27
|
+
background-color: rgba(0, 0, 0, 0);
|
|
28
|
+
border: 5px solid rgba(63, 81, 181, 0.9);
|
|
29
|
+
opacity: 0.9;
|
|
30
|
+
border-right: 5px solid rgba(0, 0, 0, 0);
|
|
31
|
+
border-left: 5px solid rgba(0, 0, 0, 0);
|
|
32
|
+
border-radius: 50px;
|
|
33
|
+
box-shadow: 0 0 35px rgba(63, 81, 181, 0.9);
|
|
34
|
+
width: 50px;
|
|
35
|
+
height: 50px;
|
|
36
|
+
animation: spinPulse 1s infinite linear;
|
|
37
|
+
margin: -25px 0 0 -25px;
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 50%;
|
|
40
|
+
left: 50%;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.pie-loading:after {
|
|
44
|
+
content: "";
|
|
45
|
+
background-color: rgba(0, 0, 0, 0);
|
|
46
|
+
border: 5px solid rgba(63, 81, 181, 0.9);
|
|
47
|
+
opacity: 0.9;
|
|
48
|
+
border-left: 5px solid rgba(0, 0, 0, 0);
|
|
49
|
+
border-right: 5px solid rgba(0, 0, 0, 0);
|
|
50
|
+
border-radius: 50px;
|
|
51
|
+
box-shadow: 0 0 15px rgba(63, 81, 181, 0.9);
|
|
52
|
+
width: 30px;
|
|
53
|
+
height: 30px;
|
|
54
|
+
animation: spinoffPulse 1s infinite linear;
|
|
55
|
+
margin: -15px 0 0 -15px;
|
|
56
|
+
position: absolute;
|
|
57
|
+
top: 50%;
|
|
58
|
+
left: 50%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes spinPulse {
|
|
62
|
+
0% {
|
|
63
|
+
transform: rotate(160deg);
|
|
64
|
+
opacity: 0;
|
|
65
|
+
box-shadow: 0 0 1px rgba(63, 81, 181, 0.9);
|
|
66
|
+
}
|
|
67
|
+
50% {
|
|
68
|
+
transform: rotate(145deg);
|
|
69
|
+
opacity: 1;
|
|
70
|
+
}
|
|
71
|
+
100% {
|
|
72
|
+
transform: rotate(-320deg);
|
|
73
|
+
opacity: 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@keyframes spinoffPulse {
|
|
78
|
+
0% {
|
|
79
|
+
transform: rotate(0deg);
|
|
80
|
+
}
|
|
81
|
+
100% {
|
|
82
|
+
transform: rotate(360deg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
</style>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
buttonEl = null as HTMLButtonElement | null,
|
|
4
|
+
onClick,
|
|
5
|
+
ariaLabel = 'Settings',
|
|
6
|
+
active = false
|
|
7
|
+
}: {
|
|
8
|
+
buttonEl?: HTMLButtonElement | null;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
ariaLabel?: string;
|
|
11
|
+
active?: boolean;
|
|
12
|
+
} = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<button
|
|
16
|
+
bind:this={buttonEl}
|
|
17
|
+
type="button"
|
|
18
|
+
class="btn btn-ghost btn-sm btn-circle"
|
|
19
|
+
aria-label={ariaLabel}
|
|
20
|
+
aria-pressed={active}
|
|
21
|
+
onclick={onClick}
|
|
22
|
+
>
|
|
23
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
|
24
|
+
<path
|
|
25
|
+
stroke-linecap="round"
|
|
26
|
+
stroke-linejoin="round"
|
|
27
|
+
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
|
28
|
+
/>
|
|
29
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
30
|
+
</svg>
|
|
31
|
+
</button>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
|
|
4
|
+
let {
|
|
5
|
+
open,
|
|
6
|
+
title = 'Settings',
|
|
7
|
+
onClose,
|
|
8
|
+
anchorEl = null
|
|
9
|
+
}: {
|
|
10
|
+
open: boolean;
|
|
11
|
+
title?: string;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
anchorEl?: HTMLElement | null;
|
|
14
|
+
} = $props();
|
|
15
|
+
|
|
16
|
+
let panelEl = $state<HTMLDivElement | null>(null);
|
|
17
|
+
let panelPosition = $state<{ top: number; left?: number; right?: number } | null>(null);
|
|
18
|
+
|
|
19
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
20
|
+
if (e.key === 'Escape') onClose();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (open) queueMicrotask(() => panelEl?.focus?.());
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Calculate position based on anchor element
|
|
28
|
+
$effect(() => {
|
|
29
|
+
if (open && anchorEl) {
|
|
30
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
31
|
+
const panelWidth = 320; // w-80 = 20rem = 320px
|
|
32
|
+
const spacing = 8; // Gap from anchor
|
|
33
|
+
|
|
34
|
+
// Try to position to the right of anchor first
|
|
35
|
+
const rightPosition = anchorRect.right + spacing;
|
|
36
|
+
const hasSpaceOnRight = rightPosition + panelWidth <= window.innerWidth;
|
|
37
|
+
|
|
38
|
+
if (hasSpaceOnRight) {
|
|
39
|
+
// Position to the right of anchor
|
|
40
|
+
panelPosition = {
|
|
41
|
+
top: anchorRect.top,
|
|
42
|
+
left: rightPosition
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
// Position to the left of anchor
|
|
46
|
+
panelPosition = {
|
|
47
|
+
top: anchorRect.top,
|
|
48
|
+
right: window.innerWidth - anchorRect.left + spacing
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
panelPosition = null;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
onMount(() => {
|
|
57
|
+
const onDocClick = (e: MouseEvent) => {
|
|
58
|
+
if (!open) return;
|
|
59
|
+
const target = e.target as Node | null;
|
|
60
|
+
if (!target) return;
|
|
61
|
+
if (panelEl?.contains(target)) return;
|
|
62
|
+
if (anchorEl?.contains(target)) return;
|
|
63
|
+
onClose();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
document.addEventListener('mousedown', onDocClick);
|
|
67
|
+
return () => document.removeEventListener('mousedown', onDocClick);
|
|
68
|
+
});
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
{#if open}
|
|
72
|
+
<div class="fixed inset-0" aria-hidden="true" onmousedown={onClose} style="z-index: 4000;"></div>
|
|
73
|
+
<div
|
|
74
|
+
bind:this={panelEl}
|
|
75
|
+
class="tool-settings-panel fixed w-80 rounded-box bg-base-100 shadow p-3 text-base-content"
|
|
76
|
+
role="dialog"
|
|
77
|
+
aria-label={title}
|
|
78
|
+
tabindex="-1"
|
|
79
|
+
onkeydown={onKeyDown}
|
|
80
|
+
style="z-index: 4100; {panelPosition ? `top: ${panelPosition.top}px; ${panelPosition.left !== undefined ? `left: ${panelPosition.left}px;` : `right: ${panelPosition.right}px;`}` : 'top: 4rem; right: 1rem;'}"
|
|
81
|
+
>
|
|
82
|
+
<div class="flex items-center justify-between gap-2 mb-2">
|
|
83
|
+
<h2 class="font-semibold text-sm">{title}</h2>
|
|
84
|
+
<button type="button" class="btn btn-ghost btn-xs" onclick={onClose} aria-label="Close settings">Close</button>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="text-sm">
|
|
87
|
+
<slot />
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as PieItemPlayer } from "./PieItemPlayer.svelte";
|
|
2
|
+
export { default as PiePreviewLayout } from "./PiePreviewLayout.svelte";
|
|
3
|
+
export { default as PiePreviewToggle } from "./PiePreviewToggle.svelte";
|
|
4
|
+
export { default as PieSpinner } from "./PieSpinner.svelte";
|
|
5
|
+
export { default as ToolSettingsButton } from "./ToolSettingsButton.svelte";
|
|
6
|
+
export { default as ToolSettingsPanel } from "./ToolSettingsPanel.svelte";
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# PIE Players i18n System
|
|
2
|
+
|
|
3
|
+
Comprehensive internationalization system with support for English, Spanish, Chinese, and Arabic (RTL).
|
|
4
|
+
|
|
5
|
+
## Usage Patterns
|
|
6
|
+
|
|
7
|
+
### Pattern 1: Standalone Components (Recommended for Individual Components)
|
|
8
|
+
|
|
9
|
+
Use `useI18nStandalone()` when building standalone components (tools, players) that don't require the full toolkit architecture.
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { useI18nStandalone } from '@pie-framework/pie-players-shared/i18n';
|
|
14
|
+
|
|
15
|
+
// Simple setup - no service injection needed
|
|
16
|
+
const i18n = useI18nStandalone({
|
|
17
|
+
locale: 'en', // Optional: defaults to browser language
|
|
18
|
+
debug: false // Optional: enable debug logging
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div dir={i18n.direction}>
|
|
23
|
+
<button>{i18n.t('common.save')}</button>
|
|
24
|
+
<span>{i18n.tn('assessment.questions', 10)}</span>
|
|
25
|
+
|
|
26
|
+
<!-- Change locale dynamically -->
|
|
27
|
+
<button onclick={() => i18n.setLocale('es')}>Español</button>
|
|
28
|
+
<button onclick={() => i18n.setLocale('ar')}>العربية</button>
|
|
29
|
+
</div>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Benefits:**
|
|
33
|
+
- ✅ No service architecture required
|
|
34
|
+
- ✅ Self-contained - manages its own I18nService instance
|
|
35
|
+
- ✅ Full feature support (hybrid loading, RTL, pluralization)
|
|
36
|
+
- ✅ Perfect for reusable components and tools
|
|
37
|
+
|
|
38
|
+
### Pattern 2: Integrated with Assessment Toolkit
|
|
39
|
+
|
|
40
|
+
Use `useI18n()` when working within the full assessment toolkit where centralized locale management is needed.
|
|
41
|
+
|
|
42
|
+
```svelte
|
|
43
|
+
<script lang="ts">
|
|
44
|
+
import { useI18n } from '@pie-framework/pie-players-shared/i18n';
|
|
45
|
+
|
|
46
|
+
// Receive service from parent/context
|
|
47
|
+
let { player } = $props();
|
|
48
|
+
const i18n = useI18n(() => player.getI18nService());
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div dir={i18n.direction}>
|
|
52
|
+
<button>{i18n.t('common.save')}</button>
|
|
53
|
+
<span>{i18n.tn('assessment.questions', totalQuestions)}</span>
|
|
54
|
+
</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Benefits:**
|
|
58
|
+
- ✅ Centralized locale management across entire application
|
|
59
|
+
- ✅ Locale preferences from student profile/IEP
|
|
60
|
+
- ✅ Shared service instance - locale changes sync everywhere
|
|
61
|
+
- ✅ Integration with accommodation system
|
|
62
|
+
|
|
63
|
+
### Pattern 3: Direct Service Usage (Advanced)
|
|
64
|
+
|
|
65
|
+
For non-Svelte contexts or advanced use cases:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { SimpleI18n, BUNDLED_TRANSLATIONS, loadTranslations } from '@pie-framework/pie-players-shared/i18n';
|
|
69
|
+
|
|
70
|
+
const i18n = new SimpleI18n({
|
|
71
|
+
locale: 'en',
|
|
72
|
+
bundledTranslations: BUNDLED_TRANSLATIONS,
|
|
73
|
+
loadTranslations,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await i18n.initialize({ locale: 'es' });
|
|
77
|
+
|
|
78
|
+
console.log(i18n.t('common.save')); // "Guardar"
|
|
79
|
+
console.log(i18n.getDirection()); // "ltr"
|
|
80
|
+
|
|
81
|
+
// Subscribe to changes
|
|
82
|
+
const unsubscribe = i18n.subscribe(() => {
|
|
83
|
+
console.log('Locale changed to:', i18n.getLocale());
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Translation Functions
|
|
88
|
+
|
|
89
|
+
### `t(key, params?)` - Basic Translation
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
i18n.t('common.save') // "Save"
|
|
93
|
+
i18n.t('common.cancel') // "Cancel"
|
|
94
|
+
|
|
95
|
+
// With interpolation
|
|
96
|
+
i18n.t('assessment.question_of', { current: 5, total: 20 })
|
|
97
|
+
// "Question 5 of 20"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `tn(key, count, params?)` - Pluralization
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
i18n.tn('assessment.questions', 1) // "1 Question"
|
|
104
|
+
i18n.tn('assessment.questions', 10) // "10 Questions"
|
|
105
|
+
|
|
106
|
+
// With additional params
|
|
107
|
+
i18n.tn('common.item', count, { type: 'folder' })
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Available Translations
|
|
111
|
+
|
|
112
|
+
### Common Namespace (`common.*`)
|
|
113
|
+
- Buttons: `save`, `cancel`, `close`, `back`, `next`, `previous`, `submit`
|
|
114
|
+
- States: `loading`, `error`, `retry`
|
|
115
|
+
- Common words: `question`, `item`, `character` (all with pluralization)
|
|
116
|
+
|
|
117
|
+
### Assessment Namespace (`assessment.*`)
|
|
118
|
+
- `title` - Assessment title
|
|
119
|
+
- `questions` - Question count (plural)
|
|
120
|
+
- `question_of` - "Question X of Y"
|
|
121
|
+
- `student_name` - Student name label
|
|
122
|
+
- `fullscreen`, `exit_fullscreen` - Fullscreen controls
|
|
123
|
+
|
|
124
|
+
### Accommodation Namespace (`accommodation.*`)
|
|
125
|
+
- `audio`, `audio_aria` - Audio/TTS controls
|
|
126
|
+
- `contrast`, `contrast_aria` - Contrast controls
|
|
127
|
+
|
|
128
|
+
### Navigation Namespace (`navigation.*`)
|
|
129
|
+
- `back`, `next`, `previous`, `submit` - Navigation buttons
|
|
130
|
+
- `navigate_to` - "Navigate to question X"
|
|
131
|
+
- `section` - Section label
|
|
132
|
+
|
|
133
|
+
### Tool Namespace (`tool.*`)
|
|
134
|
+
- Tool names: `calculator`, `graph`, `periodic_table`, etc.
|
|
135
|
+
- TTS controls: `tts.speak`, `tts.pause`, `tts.stop`, `tts.rate`
|
|
136
|
+
- Calculator types: `calculator.basic`, `calculator.scientific`, `calculator.graphing`
|
|
137
|
+
- Color schemes: `color_scheme.default`, `color_scheme.high_contrast`, `color_scheme.dark`
|
|
138
|
+
|
|
139
|
+
## Supported Languages
|
|
140
|
+
|
|
141
|
+
- **English (en)** - Bundled (~15KB)
|
|
142
|
+
- **Spanish (es)** - Lazy-loaded (~12KB)
|
|
143
|
+
- **Chinese (zh)** - Lazy-loaded (~12KB)
|
|
144
|
+
- **Arabic (ar)** - Lazy-loaded (~12KB) with RTL support
|
|
145
|
+
|
|
146
|
+
## RTL Support
|
|
147
|
+
|
|
148
|
+
Arabic automatically switches to RTL mode:
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
<script>
|
|
152
|
+
const i18n = useI18nStandalone({ locale: 'ar' });
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<!-- direction automatically set to "rtl" -->
|
|
156
|
+
<div dir={i18n.direction}>
|
|
157
|
+
{i18n.t('assessment.title')} <!-- التقييم -->
|
|
158
|
+
</div>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## API Reference
|
|
162
|
+
|
|
163
|
+
### Composable Return Value
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
{
|
|
167
|
+
// Reactive getters
|
|
168
|
+
locale: string; // Current locale (e.g., 'en', 'es')
|
|
169
|
+
direction: 'ltr' | 'rtl'; // Text direction
|
|
170
|
+
isLoading: boolean; // Locale loading state
|
|
171
|
+
availableLocales: string[]; // List of loaded locales
|
|
172
|
+
|
|
173
|
+
// Methods
|
|
174
|
+
t(key: string, params?: Record<string, any>): string;
|
|
175
|
+
tn(key: string, count: number, params?: Record<string, any>): string;
|
|
176
|
+
setLocale(locale: string): Promise<void>;
|
|
177
|
+
isLocaleLoaded(locale: string): boolean;
|
|
178
|
+
hasKey(key: string): boolean;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## When to Use Each Pattern
|
|
183
|
+
|
|
184
|
+
| Scenario | Use Pattern |
|
|
185
|
+
|----------|-------------|
|
|
186
|
+
| Standalone tool component | `useI18nStandalone()` |
|
|
187
|
+
| Reusable UI component | `useI18nStandalone()` |
|
|
188
|
+
| Individual player | `useI18nStandalone()` |
|
|
189
|
+
| Full assessment application | `useI18n()` |
|
|
190
|
+
| Student profile integration | `useI18n()` |
|
|
191
|
+
| IEP/504 locale requirements | `useI18n()` |
|
|
192
|
+
| Non-Svelte code | `SimpleI18n` class |
|
|
193
|
+
|
|
194
|
+
## Performance
|
|
195
|
+
|
|
196
|
+
- **Initial load**: ~15KB (English bundled)
|
|
197
|
+
- **Lazy loading**: ~12KB per additional language (cached)
|
|
198
|
+
- **Hybrid strategy**: Only loads languages when needed
|
|
199
|
+
- **Service reuse**: `useI18nStandalone()` creates one service per component
|
|
200
|
+
|
|
201
|
+
## Adding New Translations
|
|
202
|
+
|
|
203
|
+
Translation files are located at:
|
|
204
|
+
```
|
|
205
|
+
packages/players-shared/src/i18n/translations/
|
|
206
|
+
├── en/ (common.json, toolkit.json, tools.json)
|
|
207
|
+
├── es/ (common.json, toolkit.json, tools.json)
|
|
208
|
+
├── zh/ (common.json, toolkit.json, tools.json)
|
|
209
|
+
└── ar/ (common.json, toolkit.json, tools.json)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Format:
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"common": {
|
|
216
|
+
"save": "Save",
|
|
217
|
+
"question": {
|
|
218
|
+
"one": "Question",
|
|
219
|
+
"other": "Questions"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|