@affectively/aeon-pages 1.3.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 +112 -0
- package/README.md +625 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +86 -0
- package/examples/basic/components/OfflineIndicator.tsx +103 -0
- package/examples/basic/components/PresenceBar.tsx +77 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +80 -0
- package/package.json +101 -0
- package/packages/analytics/README.md +309 -0
- package/packages/analytics/build.ts +35 -0
- package/packages/analytics/package.json +50 -0
- package/packages/analytics/src/click-tracker.ts +368 -0
- package/packages/analytics/src/context-bridge.ts +319 -0
- package/packages/analytics/src/data-layer.ts +302 -0
- package/packages/analytics/src/gtm-loader.ts +239 -0
- package/packages/analytics/src/index.ts +230 -0
- package/packages/analytics/src/merkle-tree.ts +489 -0
- package/packages/analytics/src/provider.tsx +300 -0
- package/packages/analytics/src/types.ts +320 -0
- package/packages/analytics/src/use-analytics.ts +296 -0
- package/packages/analytics/tsconfig.json +19 -0
- package/packages/benchmarks/src/benchmark.test.ts +691 -0
- package/packages/cli/dist/index.js +61899 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +682 -0
- package/packages/cli/src/commands/build.ts +890 -0
- package/packages/cli/src/commands/dev.ts +473 -0
- package/packages/cli/src/commands/init.ts +409 -0
- package/packages/cli/src/commands/start.ts +297 -0
- package/packages/cli/src/index.ts +105 -0
- package/packages/directives/src/use-aeon.ts +272 -0
- package/packages/mcp-server/package.json +51 -0
- package/packages/mcp-server/src/index.ts +178 -0
- package/packages/mcp-server/src/resources.ts +346 -0
- package/packages/mcp-server/src/tools/index.ts +36 -0
- package/packages/mcp-server/src/tools/navigation.ts +545 -0
- package/packages/mcp-server/tsconfig.json +21 -0
- package/packages/react/package.json +40 -0
- package/packages/react/src/Link.tsx +388 -0
- package/packages/react/src/components/InstallPrompt.tsx +286 -0
- package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
- package/packages/react/src/components/PushNotifications.tsx +453 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
- package/packages/react/src/hooks/useConflicts.ts +277 -0
- package/packages/react/src/hooks/useNetworkState.ts +209 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
- package/packages/react/src/hooks/useServiceWorker.ts +278 -0
- package/packages/react/src/hooks.ts +195 -0
- package/packages/react/src/index.ts +151 -0
- package/packages/react/src/provider.tsx +467 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/runtime/README.md +399 -0
- package/packages/runtime/build.ts +48 -0
- package/packages/runtime/package.json +71 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +465 -0
- package/packages/runtime/src/benchmark.ts +171 -0
- package/packages/runtime/src/cache.ts +479 -0
- package/packages/runtime/src/durable-object.ts +1341 -0
- package/packages/runtime/src/index.ts +360 -0
- package/packages/runtime/src/navigation.test.ts +421 -0
- package/packages/runtime/src/navigation.ts +422 -0
- package/packages/runtime/src/nextjs-adapter.ts +272 -0
- package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
- package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
- package/packages/runtime/src/offline/encryption.test.ts +412 -0
- package/packages/runtime/src/offline/encryption.ts +397 -0
- package/packages/runtime/src/offline/types.ts +465 -0
- package/packages/runtime/src/predictor.ts +371 -0
- package/packages/runtime/src/registry.ts +351 -0
- package/packages/runtime/src/router/context-extractor.ts +661 -0
- package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
- package/packages/runtime/src/router/esi-control.ts +541 -0
- package/packages/runtime/src/router/esi-cyrano.ts +779 -0
- package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
- package/packages/runtime/src/router/esi-react.tsx +1065 -0
- package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
- package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
- package/packages/runtime/src/router/esi-translate.ts +503 -0
- package/packages/runtime/src/router/esi.ts +666 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
- package/packages/runtime/src/router/index.ts +298 -0
- package/packages/runtime/src/router/merkle-capability.ts +473 -0
- package/packages/runtime/src/router/speculation.ts +451 -0
- package/packages/runtime/src/router/types.ts +630 -0
- package/packages/runtime/src/router.test.ts +470 -0
- package/packages/runtime/src/router.ts +302 -0
- package/packages/runtime/src/server.ts +481 -0
- package/packages/runtime/src/service-worker-push.ts +319 -0
- package/packages/runtime/src/service-worker.ts +553 -0
- package/packages/runtime/src/skeleton-hydrate.ts +237 -0
- package/packages/runtime/src/speculation.test.ts +389 -0
- package/packages/runtime/src/speculation.ts +486 -0
- package/packages/runtime/src/storage.test.ts +1297 -0
- package/packages/runtime/src/storage.ts +1048 -0
- package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
- package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
- package/packages/runtime/src/sync/coordinator.test.ts +608 -0
- package/packages/runtime/src/sync/coordinator.ts +596 -0
- package/packages/runtime/src/tree-compiler.ts +295 -0
- package/packages/runtime/src/types.ts +728 -0
- package/packages/runtime/src/worker.ts +327 -0
- package/packages/runtime/tsconfig.json +20 -0
- package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
- package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
- package/packages/runtime/wasm/package.json +21 -0
- package/packages/runtime/wrangler.toml +41 -0
- package/packages/runtime-wasm/Cargo.lock +436 -0
- package/packages/runtime-wasm/Cargo.toml +29 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
- package/packages/runtime-wasm/pkg/package.json +21 -0
- package/packages/runtime-wasm/src/hydrate.rs +352 -0
- package/packages/runtime-wasm/src/lib.rs +191 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/packages/runtime-wasm/src/skeleton.rs +430 -0
- package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESI Translation React Components
|
|
3
|
+
*
|
|
4
|
+
* Provides React components for automatic translation in aeon-flux applications.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* // Basic usage with explicit target language
|
|
9
|
+
* <ESI.Translate targetLanguage="es">
|
|
10
|
+
* Hello, welcome to our platform!
|
|
11
|
+
* </ESI.Translate>
|
|
12
|
+
*
|
|
13
|
+
* // Using TranslationProvider for app-wide language
|
|
14
|
+
* <TranslationProvider defaultLanguage="es">
|
|
15
|
+
* <ESI.Translate>Hello world</ESI.Translate>
|
|
16
|
+
* </TranslationProvider>
|
|
17
|
+
*
|
|
18
|
+
* // Programmatic translation
|
|
19
|
+
* const { translate, language } = useTranslation();
|
|
20
|
+
* const translated = await translate('Hello world');
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
createContext,
|
|
26
|
+
useContext,
|
|
27
|
+
useEffect,
|
|
28
|
+
useState,
|
|
29
|
+
useCallback,
|
|
30
|
+
useMemo,
|
|
31
|
+
type ReactNode,
|
|
32
|
+
type FC,
|
|
33
|
+
} from 'react';
|
|
34
|
+
import type {
|
|
35
|
+
TranslationResult,
|
|
36
|
+
TranslationProviderConfig,
|
|
37
|
+
SupportedLanguageCode,
|
|
38
|
+
} from './types';
|
|
39
|
+
import {
|
|
40
|
+
esiTranslate,
|
|
41
|
+
generateTranslationCacheKey,
|
|
42
|
+
getCachedTranslation,
|
|
43
|
+
setCachedTranslation,
|
|
44
|
+
readHeadTranslationConfig,
|
|
45
|
+
detectTargetLanguage,
|
|
46
|
+
normalizeLanguageCode,
|
|
47
|
+
getSupportedLanguages,
|
|
48
|
+
getLanguageName,
|
|
49
|
+
} from './esi-translate';
|
|
50
|
+
import { useESI, useGlobalESIState } from './esi-react';
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Translation Context
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
export interface TranslationContextValue {
|
|
57
|
+
/** Current target language (ISO 639-1 code) */
|
|
58
|
+
language: string;
|
|
59
|
+
|
|
60
|
+
/** Set the target language */
|
|
61
|
+
setLanguage: (lang: string) => void;
|
|
62
|
+
|
|
63
|
+
/** Translate text programmatically */
|
|
64
|
+
translate: (
|
|
65
|
+
text: string,
|
|
66
|
+
options?: {
|
|
67
|
+
targetLanguage?: string;
|
|
68
|
+
sourceLanguage?: string;
|
|
69
|
+
context?: string;
|
|
70
|
+
},
|
|
71
|
+
) => Promise<TranslationResult>;
|
|
72
|
+
|
|
73
|
+
/** Is translation currently loading */
|
|
74
|
+
isTranslating: boolean;
|
|
75
|
+
|
|
76
|
+
/** List of supported languages */
|
|
77
|
+
supportedLanguages: SupportedLanguageCode[];
|
|
78
|
+
|
|
79
|
+
/** Translation endpoint */
|
|
80
|
+
endpoint: string;
|
|
81
|
+
|
|
82
|
+
/** Cache TTL in seconds */
|
|
83
|
+
cacheTtl: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const TranslationContext = createContext<TranslationContextValue | null>(null);
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// TranslationProvider
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
export interface TranslationProviderProps {
|
|
93
|
+
children: ReactNode;
|
|
94
|
+
|
|
95
|
+
/** Default target language (ISO 639-1 code or language name) */
|
|
96
|
+
defaultLanguage?: string;
|
|
97
|
+
|
|
98
|
+
/** AI Gateway endpoint for translation */
|
|
99
|
+
endpoint?: string;
|
|
100
|
+
|
|
101
|
+
/** Cache TTL in seconds (default: 86400 = 24 hours) */
|
|
102
|
+
cacheTtl?: number;
|
|
103
|
+
|
|
104
|
+
/** Fallback language if translation fails */
|
|
105
|
+
fallbackLanguage?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* TranslationProvider - Provides translation context to the component tree
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```tsx
|
|
113
|
+
* function App() {
|
|
114
|
+
* return (
|
|
115
|
+
* <TranslationProvider defaultLanguage="es" endpoint="https://ai-gateway.example.com">
|
|
116
|
+
* <MyComponent />
|
|
117
|
+
* </TranslationProvider>
|
|
118
|
+
* );
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export const TranslationProvider: FC<TranslationProviderProps> = ({
|
|
123
|
+
children,
|
|
124
|
+
defaultLanguage,
|
|
125
|
+
endpoint: propEndpoint,
|
|
126
|
+
cacheTtl: propCacheTtl = 86400,
|
|
127
|
+
fallbackLanguage = 'en',
|
|
128
|
+
}) => {
|
|
129
|
+
// Read config from head tags
|
|
130
|
+
const headConfig = useMemo(() => readHeadTranslationConfig(), []);
|
|
131
|
+
|
|
132
|
+
// Get global ESI state for language preferences
|
|
133
|
+
const globalState = useGlobalESIState();
|
|
134
|
+
|
|
135
|
+
// Determine initial language
|
|
136
|
+
const initialLanguage = useMemo(
|
|
137
|
+
() =>
|
|
138
|
+
normalizeLanguageCode(
|
|
139
|
+
defaultLanguage ||
|
|
140
|
+
headConfig.defaultLanguage ||
|
|
141
|
+
globalState.preferences?.language ||
|
|
142
|
+
fallbackLanguage,
|
|
143
|
+
),
|
|
144
|
+
[
|
|
145
|
+
defaultLanguage,
|
|
146
|
+
headConfig.defaultLanguage,
|
|
147
|
+
globalState.preferences?.language,
|
|
148
|
+
fallbackLanguage,
|
|
149
|
+
],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const [language, setLanguageState] = useState(initialLanguage);
|
|
153
|
+
const [isTranslating, setIsTranslating] = useState(false);
|
|
154
|
+
|
|
155
|
+
// Resolve endpoint
|
|
156
|
+
const endpoint =
|
|
157
|
+
propEndpoint ||
|
|
158
|
+
headConfig.endpoint ||
|
|
159
|
+
'https://ai-gateway.taylorbuley.workers.dev';
|
|
160
|
+
const cacheTtl = propCacheTtl || headConfig.cacheTtl || 86400;
|
|
161
|
+
|
|
162
|
+
// Get ESI context for processing
|
|
163
|
+
const esiContext = useESI();
|
|
164
|
+
|
|
165
|
+
// Set language handler
|
|
166
|
+
const setLanguage = useCallback((lang: string) => {
|
|
167
|
+
setLanguageState(normalizeLanguageCode(lang));
|
|
168
|
+
}, []);
|
|
169
|
+
|
|
170
|
+
// Translate function
|
|
171
|
+
const translate = useCallback(
|
|
172
|
+
async (
|
|
173
|
+
text: string,
|
|
174
|
+
options: {
|
|
175
|
+
targetLanguage?: string;
|
|
176
|
+
sourceLanguage?: string;
|
|
177
|
+
context?: string;
|
|
178
|
+
} = {},
|
|
179
|
+
): Promise<TranslationResult> => {
|
|
180
|
+
const targetLang = normalizeLanguageCode(
|
|
181
|
+
options.targetLanguage || language,
|
|
182
|
+
);
|
|
183
|
+
const sourceLang = options.sourceLanguage || 'auto';
|
|
184
|
+
|
|
185
|
+
// Check cache
|
|
186
|
+
const cacheKey = generateTranslationCacheKey(
|
|
187
|
+
text,
|
|
188
|
+
sourceLang,
|
|
189
|
+
targetLang,
|
|
190
|
+
options.context,
|
|
191
|
+
);
|
|
192
|
+
const cached = getCachedTranslation(cacheKey);
|
|
193
|
+
if (cached) {
|
|
194
|
+
return cached;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setIsTranslating(true);
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
// Create ESI directive
|
|
201
|
+
const directive = esiTranslate(text, targetLang, {
|
|
202
|
+
sourceLanguage: sourceLang,
|
|
203
|
+
context: options.context,
|
|
204
|
+
cacheTtl,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Process via ESI
|
|
208
|
+
const esiResult = await esiContext.process(directive);
|
|
209
|
+
|
|
210
|
+
const result: TranslationResult = {
|
|
211
|
+
original: text,
|
|
212
|
+
translated: esiResult.success ? esiResult.output || text : text,
|
|
213
|
+
sourceLanguage: sourceLang,
|
|
214
|
+
targetLanguage: targetLang,
|
|
215
|
+
confidence: esiResult.success ? 0.95 : 0,
|
|
216
|
+
cached: esiResult.cached,
|
|
217
|
+
latencyMs: esiResult.latencyMs,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Cache the result
|
|
221
|
+
if (esiResult.success) {
|
|
222
|
+
setCachedTranslation(cacheKey, result, cacheTtl);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return result;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Return original text on error
|
|
228
|
+
return {
|
|
229
|
+
original: text,
|
|
230
|
+
translated: text,
|
|
231
|
+
sourceLanguage: sourceLang,
|
|
232
|
+
targetLanguage: targetLang,
|
|
233
|
+
confidence: 0,
|
|
234
|
+
cached: false,
|
|
235
|
+
latencyMs: 0,
|
|
236
|
+
};
|
|
237
|
+
} finally {
|
|
238
|
+
setIsTranslating(false);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
[language, cacheTtl, esiContext],
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const contextValue = useMemo(
|
|
245
|
+
() => ({
|
|
246
|
+
language,
|
|
247
|
+
setLanguage,
|
|
248
|
+
translate,
|
|
249
|
+
isTranslating,
|
|
250
|
+
supportedLanguages: getSupportedLanguages(),
|
|
251
|
+
endpoint,
|
|
252
|
+
cacheTtl,
|
|
253
|
+
}),
|
|
254
|
+
[language, setLanguage, translate, isTranslating, endpoint, cacheTtl],
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<TranslationContext.Provider value={contextValue}>
|
|
259
|
+
{children}
|
|
260
|
+
</TranslationContext.Provider>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// useTranslation Hook
|
|
266
|
+
// ============================================================================
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Hook to access translation context
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```tsx
|
|
273
|
+
* function MyComponent() {
|
|
274
|
+
* const { translate, language, setLanguage } = useTranslation();
|
|
275
|
+
*
|
|
276
|
+
* const handleTranslate = async () => {
|
|
277
|
+
* const result = await translate('Hello world');
|
|
278
|
+
* console.log(result.translated);
|
|
279
|
+
* };
|
|
280
|
+
*
|
|
281
|
+
* return (
|
|
282
|
+
* <button onClick={handleTranslate}>
|
|
283
|
+
* Translate (current: {language})
|
|
284
|
+
* </button>
|
|
285
|
+
* );
|
|
286
|
+
* }
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
export function useTranslation(): TranslationContextValue {
|
|
290
|
+
const ctx = useContext(TranslationContext);
|
|
291
|
+
if (!ctx) {
|
|
292
|
+
throw new Error('useTranslation must be used within a TranslationProvider');
|
|
293
|
+
}
|
|
294
|
+
return ctx;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Hook to access translation context (optional - returns null if not available)
|
|
299
|
+
*/
|
|
300
|
+
export function useTranslationOptional(): TranslationContextValue | null {
|
|
301
|
+
return useContext(TranslationContext);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ============================================================================
|
|
305
|
+
// ESI.Translate Component
|
|
306
|
+
// ============================================================================
|
|
307
|
+
|
|
308
|
+
export interface ESITranslateProps {
|
|
309
|
+
/** Content to translate */
|
|
310
|
+
children?: ReactNode;
|
|
311
|
+
|
|
312
|
+
/** Explicit text to translate (overrides children) */
|
|
313
|
+
text?: string;
|
|
314
|
+
|
|
315
|
+
/** Target language (ISO 639-1 code or language name) */
|
|
316
|
+
targetLanguage?: string;
|
|
317
|
+
|
|
318
|
+
/** Source language (ISO 639-1 code, or 'auto' for auto-detect) */
|
|
319
|
+
sourceLanguage?: string;
|
|
320
|
+
|
|
321
|
+
/** Additional context for better translation quality */
|
|
322
|
+
context?: string;
|
|
323
|
+
|
|
324
|
+
/** Fallback content if translation fails */
|
|
325
|
+
fallback?: ReactNode;
|
|
326
|
+
|
|
327
|
+
/** Loading content */
|
|
328
|
+
loading?: ReactNode;
|
|
329
|
+
|
|
330
|
+
/** Cache TTL in seconds (default: 86400 = 24 hours) */
|
|
331
|
+
cacheTtl?: number;
|
|
332
|
+
|
|
333
|
+
/** Enable streaming translation */
|
|
334
|
+
stream?: boolean;
|
|
335
|
+
|
|
336
|
+
/** Custom render function */
|
|
337
|
+
render?: (result: TranslationResult) => ReactNode;
|
|
338
|
+
|
|
339
|
+
/** Class name for wrapper */
|
|
340
|
+
className?: string;
|
|
341
|
+
|
|
342
|
+
/** HTML element to render as */
|
|
343
|
+
as?: 'span' | 'div' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
344
|
+
|
|
345
|
+
/** Callback when translation completes */
|
|
346
|
+
onComplete?: (result: TranslationResult) => void;
|
|
347
|
+
|
|
348
|
+
/** Callback on error */
|
|
349
|
+
onError?: (error: string) => void;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* ESI Translation Component
|
|
354
|
+
*
|
|
355
|
+
* Translates text to the specified language using AI inference.
|
|
356
|
+
* Uses TranslationProvider context if available, otherwise falls back
|
|
357
|
+
* to detecting language from various sources.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```tsx
|
|
361
|
+
* // Basic usage
|
|
362
|
+
* <ESI.Translate targetLanguage="es">
|
|
363
|
+
* Hello, how are you?
|
|
364
|
+
* </ESI.Translate>
|
|
365
|
+
*
|
|
366
|
+
* // With context for better translation
|
|
367
|
+
* <ESI.Translate targetLanguage="ja" context="emotional wellness app">
|
|
368
|
+
* We're here to help you understand your feelings.
|
|
369
|
+
* </ESI.Translate>
|
|
370
|
+
*
|
|
371
|
+
* // Using language from context
|
|
372
|
+
* <ESI.Translate>
|
|
373
|
+
* Welcome to our platform
|
|
374
|
+
* </ESI.Translate>
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
export const ESITranslate: FC<ESITranslateProps> = ({
|
|
378
|
+
children,
|
|
379
|
+
text,
|
|
380
|
+
targetLanguage: propTargetLanguage,
|
|
381
|
+
sourceLanguage = 'auto',
|
|
382
|
+
context,
|
|
383
|
+
fallback,
|
|
384
|
+
loading = '...',
|
|
385
|
+
cacheTtl = 86400,
|
|
386
|
+
stream = false,
|
|
387
|
+
render,
|
|
388
|
+
className,
|
|
389
|
+
as: Component = 'span',
|
|
390
|
+
onComplete,
|
|
391
|
+
onError,
|
|
392
|
+
}) => {
|
|
393
|
+
const { process, processWithStream, enabled } = useESI();
|
|
394
|
+
const globalState = useGlobalESIState();
|
|
395
|
+
const translationContext = useTranslationOptional();
|
|
396
|
+
|
|
397
|
+
const [output, setOutput] = useState<string>('');
|
|
398
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
399
|
+
const [error, setError] = useState<string | null>(null);
|
|
400
|
+
const [result, setResult] = useState<TranslationResult | null>(null);
|
|
401
|
+
|
|
402
|
+
// Get text to translate
|
|
403
|
+
const textToTranslate = useMemo(
|
|
404
|
+
() =>
|
|
405
|
+
text ||
|
|
406
|
+
(typeof children === 'string' ? children : String(children || '')),
|
|
407
|
+
[text, children],
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// Determine target language
|
|
411
|
+
const targetLanguage = useMemo(
|
|
412
|
+
() =>
|
|
413
|
+
normalizeLanguageCode(
|
|
414
|
+
propTargetLanguage ||
|
|
415
|
+
translationContext?.language ||
|
|
416
|
+
detectTargetLanguage(undefined, globalState),
|
|
417
|
+
),
|
|
418
|
+
[propTargetLanguage, translationContext?.language, globalState],
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
// Skip translation if source and target are the same
|
|
422
|
+
const shouldTranslate = useMemo(() => {
|
|
423
|
+
if (!textToTranslate) return false;
|
|
424
|
+
// Don't translate if target is 'en' and we assume source is English
|
|
425
|
+
// (this is a heuristic - could be improved with language detection)
|
|
426
|
+
if (targetLanguage === 'en' && sourceLanguage === 'auto') return false;
|
|
427
|
+
if (sourceLanguage === targetLanguage) return false;
|
|
428
|
+
return true;
|
|
429
|
+
}, [textToTranslate, targetLanguage, sourceLanguage]);
|
|
430
|
+
|
|
431
|
+
useEffect(() => {
|
|
432
|
+
// If translation is disabled or not needed
|
|
433
|
+
if (!enabled || !shouldTranslate) {
|
|
434
|
+
setOutput(textToTranslate);
|
|
435
|
+
setIsLoading(false);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Check cache
|
|
440
|
+
const cacheKey = generateTranslationCacheKey(
|
|
441
|
+
textToTranslate,
|
|
442
|
+
sourceLanguage,
|
|
443
|
+
targetLanguage,
|
|
444
|
+
context,
|
|
445
|
+
);
|
|
446
|
+
const cached = getCachedTranslation(cacheKey);
|
|
447
|
+
if (cached) {
|
|
448
|
+
setOutput(cached.translated);
|
|
449
|
+
setResult(cached);
|
|
450
|
+
setIsLoading(false);
|
|
451
|
+
onComplete?.(cached);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Create translation directive
|
|
456
|
+
const directive = esiTranslate(textToTranslate, targetLanguage, {
|
|
457
|
+
sourceLanguage,
|
|
458
|
+
context,
|
|
459
|
+
cacheTtl,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
const processTranslation = async () => {
|
|
463
|
+
try {
|
|
464
|
+
let esiResult;
|
|
465
|
+
|
|
466
|
+
if (stream && processWithStream) {
|
|
467
|
+
setOutput('');
|
|
468
|
+
esiResult = await processWithStream(directive, (chunk) => {
|
|
469
|
+
setOutput((prev) => prev + chunk);
|
|
470
|
+
});
|
|
471
|
+
} else {
|
|
472
|
+
esiResult = await process(directive);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
setIsLoading(false);
|
|
476
|
+
|
|
477
|
+
if (esiResult.success && esiResult.output) {
|
|
478
|
+
const translatedText = esiResult.output.trim();
|
|
479
|
+
setOutput(translatedText);
|
|
480
|
+
|
|
481
|
+
const translationResult: TranslationResult = {
|
|
482
|
+
original: textToTranslate,
|
|
483
|
+
translated: translatedText,
|
|
484
|
+
sourceLanguage,
|
|
485
|
+
targetLanguage,
|
|
486
|
+
confidence: 0.95,
|
|
487
|
+
cached: esiResult.cached,
|
|
488
|
+
latencyMs: esiResult.latencyMs,
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
setResult(translationResult);
|
|
492
|
+
setCachedTranslation(cacheKey, translationResult, cacheTtl);
|
|
493
|
+
onComplete?.(translationResult);
|
|
494
|
+
} else {
|
|
495
|
+
setError(esiResult.error || 'Translation failed');
|
|
496
|
+
setOutput(textToTranslate); // Fall back to original
|
|
497
|
+
onError?.(esiResult.error || 'Translation failed');
|
|
498
|
+
}
|
|
499
|
+
} catch (err) {
|
|
500
|
+
setIsLoading(false);
|
|
501
|
+
setOutput(textToTranslate);
|
|
502
|
+
const errorMsg =
|
|
503
|
+
err instanceof Error ? err.message : 'Translation failed';
|
|
504
|
+
setError(errorMsg);
|
|
505
|
+
onError?.(errorMsg);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
processTranslation();
|
|
510
|
+
}, [
|
|
511
|
+
textToTranslate,
|
|
512
|
+
targetLanguage,
|
|
513
|
+
sourceLanguage,
|
|
514
|
+
context,
|
|
515
|
+
cacheTtl,
|
|
516
|
+
enabled,
|
|
517
|
+
shouldTranslate,
|
|
518
|
+
stream,
|
|
519
|
+
process,
|
|
520
|
+
processWithStream,
|
|
521
|
+
onComplete,
|
|
522
|
+
onError,
|
|
523
|
+
]);
|
|
524
|
+
|
|
525
|
+
// Render loading state
|
|
526
|
+
if (isLoading && !stream) {
|
|
527
|
+
return <Component className={className}>{loading}</Component>;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Render error with fallback
|
|
531
|
+
if (error && fallback) {
|
|
532
|
+
return <Component className={className}>{fallback}</Component>;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Custom render
|
|
536
|
+
if (render && result) {
|
|
537
|
+
return <Component className={className}>{render(result)}</Component>;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return (
|
|
541
|
+
<Component className={className}>
|
|
542
|
+
{output || (isLoading ? loading : '')}
|
|
543
|
+
</Component>
|
|
544
|
+
);
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// ============================================================================
|
|
548
|
+
// Exports
|
|
549
|
+
// ============================================================================
|
|
550
|
+
|
|
551
|
+
export {
|
|
552
|
+
TranslationContext,
|
|
553
|
+
normalizeLanguageCode,
|
|
554
|
+
getSupportedLanguages,
|
|
555
|
+
getLanguageName,
|
|
556
|
+
};
|