@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.
Files changed (124) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/README.md +625 -0
  3. package/examples/basic/aeon.config.ts +39 -0
  4. package/examples/basic/components/Cursor.tsx +86 -0
  5. package/examples/basic/components/OfflineIndicator.tsx +103 -0
  6. package/examples/basic/components/PresenceBar.tsx +77 -0
  7. package/examples/basic/package.json +20 -0
  8. package/examples/basic/pages/index.tsx +80 -0
  9. package/package.json +101 -0
  10. package/packages/analytics/README.md +309 -0
  11. package/packages/analytics/build.ts +35 -0
  12. package/packages/analytics/package.json +50 -0
  13. package/packages/analytics/src/click-tracker.ts +368 -0
  14. package/packages/analytics/src/context-bridge.ts +319 -0
  15. package/packages/analytics/src/data-layer.ts +302 -0
  16. package/packages/analytics/src/gtm-loader.ts +239 -0
  17. package/packages/analytics/src/index.ts +230 -0
  18. package/packages/analytics/src/merkle-tree.ts +489 -0
  19. package/packages/analytics/src/provider.tsx +300 -0
  20. package/packages/analytics/src/types.ts +320 -0
  21. package/packages/analytics/src/use-analytics.ts +296 -0
  22. package/packages/analytics/tsconfig.json +19 -0
  23. package/packages/benchmarks/src/benchmark.test.ts +691 -0
  24. package/packages/cli/dist/index.js +61899 -0
  25. package/packages/cli/package.json +43 -0
  26. package/packages/cli/src/commands/build.test.ts +682 -0
  27. package/packages/cli/src/commands/build.ts +890 -0
  28. package/packages/cli/src/commands/dev.ts +473 -0
  29. package/packages/cli/src/commands/init.ts +409 -0
  30. package/packages/cli/src/commands/start.ts +297 -0
  31. package/packages/cli/src/index.ts +105 -0
  32. package/packages/directives/src/use-aeon.ts +272 -0
  33. package/packages/mcp-server/package.json +51 -0
  34. package/packages/mcp-server/src/index.ts +178 -0
  35. package/packages/mcp-server/src/resources.ts +346 -0
  36. package/packages/mcp-server/src/tools/index.ts +36 -0
  37. package/packages/mcp-server/src/tools/navigation.ts +545 -0
  38. package/packages/mcp-server/tsconfig.json +21 -0
  39. package/packages/react/package.json +40 -0
  40. package/packages/react/src/Link.tsx +388 -0
  41. package/packages/react/src/components/InstallPrompt.tsx +286 -0
  42. package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
  43. package/packages/react/src/components/PushNotifications.tsx +453 -0
  44. package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
  45. package/packages/react/src/hooks/useConflicts.ts +277 -0
  46. package/packages/react/src/hooks/useNetworkState.ts +209 -0
  47. package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
  48. package/packages/react/src/hooks/useServiceWorker.ts +278 -0
  49. package/packages/react/src/hooks.ts +195 -0
  50. package/packages/react/src/index.ts +151 -0
  51. package/packages/react/src/provider.tsx +467 -0
  52. package/packages/react/tsconfig.json +19 -0
  53. package/packages/runtime/README.md +399 -0
  54. package/packages/runtime/build.ts +48 -0
  55. package/packages/runtime/package.json +71 -0
  56. package/packages/runtime/schema.sql +40 -0
  57. package/packages/runtime/src/api-routes.ts +465 -0
  58. package/packages/runtime/src/benchmark.ts +171 -0
  59. package/packages/runtime/src/cache.ts +479 -0
  60. package/packages/runtime/src/durable-object.ts +1341 -0
  61. package/packages/runtime/src/index.ts +360 -0
  62. package/packages/runtime/src/navigation.test.ts +421 -0
  63. package/packages/runtime/src/navigation.ts +422 -0
  64. package/packages/runtime/src/nextjs-adapter.ts +272 -0
  65. package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
  66. package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
  67. package/packages/runtime/src/offline/encryption.test.ts +412 -0
  68. package/packages/runtime/src/offline/encryption.ts +397 -0
  69. package/packages/runtime/src/offline/types.ts +465 -0
  70. package/packages/runtime/src/predictor.ts +371 -0
  71. package/packages/runtime/src/registry.ts +351 -0
  72. package/packages/runtime/src/router/context-extractor.ts +661 -0
  73. package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
  74. package/packages/runtime/src/router/esi-control.ts +541 -0
  75. package/packages/runtime/src/router/esi-cyrano.ts +779 -0
  76. package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
  77. package/packages/runtime/src/router/esi-react.tsx +1065 -0
  78. package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
  79. package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
  80. package/packages/runtime/src/router/esi-translate.ts +503 -0
  81. package/packages/runtime/src/router/esi.ts +666 -0
  82. package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
  83. package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
  84. package/packages/runtime/src/router/index.ts +298 -0
  85. package/packages/runtime/src/router/merkle-capability.ts +473 -0
  86. package/packages/runtime/src/router/speculation.ts +451 -0
  87. package/packages/runtime/src/router/types.ts +630 -0
  88. package/packages/runtime/src/router.test.ts +470 -0
  89. package/packages/runtime/src/router.ts +302 -0
  90. package/packages/runtime/src/server.ts +481 -0
  91. package/packages/runtime/src/service-worker-push.ts +319 -0
  92. package/packages/runtime/src/service-worker.ts +553 -0
  93. package/packages/runtime/src/skeleton-hydrate.ts +237 -0
  94. package/packages/runtime/src/speculation.test.ts +389 -0
  95. package/packages/runtime/src/speculation.ts +486 -0
  96. package/packages/runtime/src/storage.test.ts +1297 -0
  97. package/packages/runtime/src/storage.ts +1048 -0
  98. package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
  99. package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
  100. package/packages/runtime/src/sync/coordinator.test.ts +608 -0
  101. package/packages/runtime/src/sync/coordinator.ts +596 -0
  102. package/packages/runtime/src/tree-compiler.ts +295 -0
  103. package/packages/runtime/src/types.ts +728 -0
  104. package/packages/runtime/src/worker.ts +327 -0
  105. package/packages/runtime/tsconfig.json +20 -0
  106. package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
  107. package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
  108. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
  109. package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
  110. package/packages/runtime/wasm/package.json +21 -0
  111. package/packages/runtime/wrangler.toml +41 -0
  112. package/packages/runtime-wasm/Cargo.lock +436 -0
  113. package/packages/runtime-wasm/Cargo.toml +29 -0
  114. package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
  115. package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
  116. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
  117. package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
  118. package/packages/runtime-wasm/pkg/package.json +21 -0
  119. package/packages/runtime-wasm/src/hydrate.rs +352 -0
  120. package/packages/runtime-wasm/src/lib.rs +191 -0
  121. package/packages/runtime-wasm/src/render.rs +629 -0
  122. package/packages/runtime-wasm/src/router.rs +298 -0
  123. package/packages/runtime-wasm/src/skeleton.rs +430 -0
  124. package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
@@ -0,0 +1,503 @@
1
+ /**
2
+ * ESI Translation Module
3
+ *
4
+ * Provides automatic translation capabilities for aeon-flux applications.
5
+ * Uses AI Gateway LLM inference for translation, with multi-layer caching.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Using ESI.Translate component
10
+ * <ESI.Translate targetLanguage="es">Hello world</ESI.Translate>
11
+ *
12
+ * // Using data-attribute decoration
13
+ * <p data-translate data-target-lang="es">Hello world</p>
14
+ * ```
15
+ */
16
+
17
+ import type {
18
+ ESIDirective,
19
+ ESIParams,
20
+ TranslationResult,
21
+ TranslationProviderConfig,
22
+ SupportedLanguageCode,
23
+ } from './types';
24
+
25
+ // ============================================================================
26
+ // Translation Cache
27
+ // ============================================================================
28
+
29
+ interface TranslationCacheEntry {
30
+ result: TranslationResult;
31
+ expiresAt: number;
32
+ }
33
+
34
+ /** In-memory LRU cache for translations */
35
+ const translationCache = new Map<string, TranslationCacheEntry>();
36
+ const MAX_CACHE_SIZE = 1000;
37
+
38
+ /**
39
+ * Generate a cache key for a translation
40
+ */
41
+ export function generateTranslationCacheKey(
42
+ text: string,
43
+ sourceLanguage: string,
44
+ targetLanguage: string,
45
+ context?: string,
46
+ ): string {
47
+ // Create a deterministic key from the inputs
48
+ const input = `${text}:${sourceLanguage}:${targetLanguage}:${context || ''}`;
49
+ // Simple hash function for cache key
50
+ let hash = 0;
51
+ for (let i = 0; i < input.length; i++) {
52
+ const char = input.charCodeAt(i);
53
+ hash = (hash << 5) - hash + char;
54
+ hash = hash & hash; // Convert to 32-bit integer
55
+ }
56
+ return `translate:${Math.abs(hash).toString(36)}`;
57
+ }
58
+
59
+ /**
60
+ * Get a cached translation result
61
+ */
62
+ export function getCachedTranslation(key: string): TranslationResult | null {
63
+ const entry = translationCache.get(key);
64
+ if (!entry) return null;
65
+
66
+ if (Date.now() > entry.expiresAt) {
67
+ translationCache.delete(key);
68
+ return null;
69
+ }
70
+
71
+ return { ...entry.result, cached: true };
72
+ }
73
+
74
+ /**
75
+ * Cache a translation result
76
+ */
77
+ export function setCachedTranslation(
78
+ key: string,
79
+ result: TranslationResult,
80
+ ttl: number,
81
+ ): void {
82
+ if (ttl <= 0) return;
83
+
84
+ // LRU eviction
85
+ if (translationCache.size >= MAX_CACHE_SIZE) {
86
+ const firstKey = translationCache.keys().next().value;
87
+ if (firstKey) translationCache.delete(firstKey);
88
+ }
89
+
90
+ translationCache.set(key, {
91
+ result,
92
+ expiresAt: Date.now() + ttl * 1000,
93
+ });
94
+ }
95
+
96
+ /**
97
+ * Clear the translation cache
98
+ */
99
+ export function clearTranslationCache(): void {
100
+ translationCache.clear();
101
+ }
102
+
103
+ // ============================================================================
104
+ // ESI Directive Builder
105
+ // ============================================================================
106
+
107
+ /**
108
+ * System prompt for translation
109
+ */
110
+ const TRANSLATION_SYSTEM_PROMPT = `You are a professional translator. Translate the following text accurately while:
111
+ - Preserving the original meaning and tone
112
+ - Using culturally appropriate expressions in the target language
113
+ - Keeping proper nouns, technical terms, and brand names as appropriate
114
+ - Maintaining any formatting if present
115
+
116
+ Respond with ONLY the translated text, no explanations, markdown, or quotes.`;
117
+
118
+ /**
119
+ * Create an ESI directive for translation
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const directive = esiTranslate('Hello world', 'es');
124
+ * const result = await processor.process(directive, userContext);
125
+ * // Returns ESIResult with translated text
126
+ * ```
127
+ */
128
+ export function esiTranslate(
129
+ text: string,
130
+ targetLanguage: string,
131
+ options: {
132
+ sourceLanguage?: string;
133
+ context?: string;
134
+ cacheTtl?: number;
135
+ temperature?: number;
136
+ } = {},
137
+ ): ESIDirective {
138
+ const {
139
+ sourceLanguage = 'auto',
140
+ context,
141
+ cacheTtl = 86400, // 24 hours default
142
+ temperature = 0.1, // Low temperature for consistency
143
+ } = options;
144
+
145
+ // Build the prompt
146
+ let prompt = text;
147
+ if (context) {
148
+ prompt = `[Context: ${context}]\n\n${text}`;
149
+ }
150
+
151
+ const systemPrompt =
152
+ sourceLanguage === 'auto'
153
+ ? `${TRANSLATION_SYSTEM_PROMPT}\n\nTarget language: ${targetLanguage}`
154
+ : `${TRANSLATION_SYSTEM_PROMPT}\n\nSource language: ${sourceLanguage}\nTarget language: ${targetLanguage}`;
155
+
156
+ return {
157
+ id: `esi-translate-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
158
+ params: {
159
+ model: 'llm',
160
+ system: systemPrompt,
161
+ temperature,
162
+ maxTokens: Math.min(text.length * 3, 2000), // Estimate max tokens
163
+ cacheTtl,
164
+ } as ESIParams,
165
+ content: {
166
+ type: 'text',
167
+ value: prompt,
168
+ },
169
+ contextAware: false, // Translation doesn't need user context
170
+ };
171
+ }
172
+
173
+ // ============================================================================
174
+ // Head Tag Configuration Reader
175
+ // ============================================================================
176
+
177
+ /**
178
+ * Read translation configuration from document head
179
+ *
180
+ * Supports:
181
+ * - <meta name="aeon-language" content="es">
182
+ * - <meta name="aeon-language-source" content="en">
183
+ * - <meta name="aeon-translation-endpoint" content="https://...">
184
+ * - <script type="application/json" id="aeon-translation-config">...</script>
185
+ */
186
+ export function readHeadTranslationConfig(): Partial<TranslationProviderConfig> {
187
+ if (typeof document === 'undefined') return {};
188
+
189
+ const config: Partial<TranslationProviderConfig> = {};
190
+
191
+ // Read meta tags
192
+ const langMeta = document.querySelector('meta[name="aeon-language"]');
193
+ if (langMeta) {
194
+ const content = langMeta.getAttribute('content');
195
+ if (content) config.defaultLanguage = content;
196
+ }
197
+
198
+ const sourceLangMeta = document.querySelector(
199
+ 'meta[name="aeon-language-source"]',
200
+ );
201
+ if (sourceLangMeta) {
202
+ // Source language hint (used if autoDetectSource is false)
203
+ // Store in config for reference
204
+ }
205
+
206
+ const endpointMeta = document.querySelector(
207
+ 'meta[name="aeon-translation-endpoint"]',
208
+ );
209
+ if (endpointMeta) {
210
+ const content = endpointMeta.getAttribute('content');
211
+ if (content) config.endpoint = content;
212
+ }
213
+
214
+ // Read JSON config from script tag
215
+ const jsonConfig = document.getElementById('aeon-translation-config');
216
+ if (jsonConfig && jsonConfig.textContent) {
217
+ try {
218
+ const parsed = JSON.parse(jsonConfig.textContent);
219
+ Object.assign(config, parsed);
220
+ } catch {
221
+ console.warn('[ESI Translate] Failed to parse translation config JSON');
222
+ }
223
+ }
224
+
225
+ return config;
226
+ }
227
+
228
+ // ============================================================================
229
+ // Language Utilities
230
+ // ============================================================================
231
+
232
+ /**
233
+ * Common language name to ISO 639-1 code mapping
234
+ */
235
+ const LANGUAGE_NAME_TO_CODE: Record<string, SupportedLanguageCode> = {
236
+ english: 'en',
237
+ spanish: 'es',
238
+ french: 'fr',
239
+ german: 'de',
240
+ italian: 'it',
241
+ portuguese: 'pt',
242
+ dutch: 'nl',
243
+ polish: 'pl',
244
+ russian: 'ru',
245
+ chinese: 'zh',
246
+ japanese: 'ja',
247
+ korean: 'ko',
248
+ arabic: 'ar',
249
+ hindi: 'hi',
250
+ bengali: 'bn',
251
+ vietnamese: 'vi',
252
+ thai: 'th',
253
+ turkish: 'tr',
254
+ indonesian: 'id',
255
+ malay: 'ms',
256
+ tagalog: 'tl',
257
+ filipino: 'tl',
258
+ swedish: 'sv',
259
+ danish: 'da',
260
+ norwegian: 'no',
261
+ finnish: 'fi',
262
+ czech: 'cs',
263
+ greek: 'el',
264
+ hebrew: 'he',
265
+ ukrainian: 'uk',
266
+ romanian: 'ro',
267
+ hungarian: 'hu',
268
+ catalan: 'ca',
269
+ armenian: 'hy',
270
+ };
271
+
272
+ /**
273
+ * ISO 639-1 code to language name mapping
274
+ */
275
+ const LANGUAGE_CODE_TO_NAME: Record<SupportedLanguageCode, string> = {
276
+ en: 'English',
277
+ es: 'Spanish',
278
+ fr: 'French',
279
+ de: 'German',
280
+ it: 'Italian',
281
+ pt: 'Portuguese',
282
+ nl: 'Dutch',
283
+ pl: 'Polish',
284
+ ru: 'Russian',
285
+ zh: 'Chinese',
286
+ ja: 'Japanese',
287
+ ko: 'Korean',
288
+ ar: 'Arabic',
289
+ hi: 'Hindi',
290
+ bn: 'Bengali',
291
+ vi: 'Vietnamese',
292
+ th: 'Thai',
293
+ tr: 'Turkish',
294
+ id: 'Indonesian',
295
+ ms: 'Malay',
296
+ tl: 'Tagalog',
297
+ sv: 'Swedish',
298
+ da: 'Danish',
299
+ no: 'Norwegian',
300
+ fi: 'Finnish',
301
+ cs: 'Czech',
302
+ el: 'Greek',
303
+ he: 'Hebrew',
304
+ uk: 'Ukrainian',
305
+ ro: 'Romanian',
306
+ hu: 'Hungarian',
307
+ ca: 'Catalan',
308
+ hy: 'Armenian',
309
+ };
310
+
311
+ /**
312
+ * Normalize language input to ISO 639-1 code
313
+ * Accepts language names (e.g., "Spanish") or codes (e.g., "es")
314
+ */
315
+ export function normalizeLanguageCode(
316
+ input: string,
317
+ ): SupportedLanguageCode | string {
318
+ const lower = input.toLowerCase().trim();
319
+
320
+ // Check if it's already a valid code
321
+ if (
322
+ lower.length === 2 &&
323
+ LANGUAGE_CODE_TO_NAME[lower as SupportedLanguageCode]
324
+ ) {
325
+ return lower as SupportedLanguageCode;
326
+ }
327
+
328
+ // Check language name mapping
329
+ if (LANGUAGE_NAME_TO_CODE[lower]) {
330
+ return LANGUAGE_NAME_TO_CODE[lower];
331
+ }
332
+
333
+ // Return as-is if unknown (let the AI handle it)
334
+ return input;
335
+ }
336
+
337
+ /**
338
+ * Get the display name for a language code
339
+ */
340
+ export function getLanguageName(code: SupportedLanguageCode | string): string {
341
+ return LANGUAGE_CODE_TO_NAME[code as SupportedLanguageCode] || code;
342
+ }
343
+
344
+ /**
345
+ * Get the list of supported language codes
346
+ */
347
+ export function getSupportedLanguages(): SupportedLanguageCode[] {
348
+ return Object.keys(LANGUAGE_CODE_TO_NAME) as SupportedLanguageCode[];
349
+ }
350
+
351
+ // ============================================================================
352
+ // Language Detection from Context
353
+ // ============================================================================
354
+
355
+ /**
356
+ * Detect target language from various sources (priority order)
357
+ *
358
+ * 1. Explicit targetLanguage prop
359
+ * 2. TranslationProvider context (not available here)
360
+ * 3. GlobalESIState preferences.language
361
+ * 4. HTML <meta name="aeon-language"> tag
362
+ * 5. Browser navigator.language
363
+ * 6. Default to 'en'
364
+ */
365
+ export function detectTargetLanguage(
366
+ explicitLanguage?: string,
367
+ globalState?: { preferences?: { language?: string } },
368
+ ): string {
369
+ // 1. Explicit prop
370
+ if (explicitLanguage) {
371
+ return normalizeLanguageCode(explicitLanguage);
372
+ }
373
+
374
+ // 2. Global ESI state
375
+ if (globalState?.preferences?.language) {
376
+ return normalizeLanguageCode(globalState.preferences.language);
377
+ }
378
+
379
+ // 3. Head meta tag
380
+ if (typeof document !== 'undefined') {
381
+ const langMeta = document.querySelector('meta[name="aeon-language"]');
382
+ if (langMeta) {
383
+ const content = langMeta.getAttribute('content');
384
+ if (content) return normalizeLanguageCode(content);
385
+ }
386
+ }
387
+
388
+ // 4. Browser language
389
+ if (typeof navigator !== 'undefined' && navigator.language) {
390
+ // Extract just the language code (e.g., 'en-US' -> 'en')
391
+ const browserLang = navigator.language.split('-')[0];
392
+ return normalizeLanguageCode(browserLang);
393
+ }
394
+
395
+ // 5. Default
396
+ return 'en';
397
+ }
398
+
399
+ // ============================================================================
400
+ // AI Gateway Translation
401
+ // ============================================================================
402
+
403
+ /**
404
+ * Translate text using the AI Gateway
405
+ *
406
+ * This function directly calls the AI Gateway translation endpoint.
407
+ * For React components, use ESI.Translate instead which integrates with
408
+ * the ESI context and caching system.
409
+ */
410
+ export async function translateWithAIGateway(
411
+ text: string,
412
+ targetLanguage: string,
413
+ options: {
414
+ sourceLanguage?: string;
415
+ context?: string;
416
+ endpoint?: string;
417
+ timeout?: number;
418
+ } = {},
419
+ ): Promise<TranslationResult> {
420
+ const {
421
+ sourceLanguage = 'auto',
422
+ context,
423
+ endpoint = process.env.AI_GATEWAY_URL ||
424
+ 'https://ai-gateway.taylorbuley.workers.dev',
425
+ timeout = 10000,
426
+ } = options;
427
+
428
+ const startTime = Date.now();
429
+
430
+ // Check cache first
431
+ const cacheKey = generateTranslationCacheKey(
432
+ text,
433
+ sourceLanguage,
434
+ targetLanguage,
435
+ context,
436
+ );
437
+ const cached = getCachedTranslation(cacheKey);
438
+ if (cached) {
439
+ return cached;
440
+ }
441
+
442
+ try {
443
+ const response = await fetch(`${endpoint}/translate`, {
444
+ method: 'POST',
445
+ headers: { 'Content-Type': 'application/json' },
446
+ body: JSON.stringify({
447
+ text,
448
+ sourceLanguage: sourceLanguage === 'auto' ? undefined : sourceLanguage,
449
+ targetLanguage: normalizeLanguageCode(targetLanguage),
450
+ context,
451
+ }),
452
+ signal: AbortSignal.timeout(timeout),
453
+ });
454
+
455
+ if (!response.ok) {
456
+ throw new Error(
457
+ `Translation failed: ${response.status} ${response.statusText}`,
458
+ );
459
+ }
460
+
461
+ const data = (await response.json()) as {
462
+ translatedText: string;
463
+ detectedSourceLanguage?: string;
464
+ confidence?: number;
465
+ };
466
+
467
+ const result: TranslationResult = {
468
+ original: text,
469
+ translated: data.translatedText,
470
+ sourceLanguage: data.detectedSourceLanguage || sourceLanguage,
471
+ targetLanguage: normalizeLanguageCode(targetLanguage),
472
+ confidence: data.confidence ?? 1.0,
473
+ cached: false,
474
+ latencyMs: Date.now() - startTime,
475
+ };
476
+
477
+ // Cache the result
478
+ setCachedTranslation(cacheKey, result, 86400); // 24 hours
479
+
480
+ return result;
481
+ } catch (error) {
482
+ // Return original text on error
483
+ return {
484
+ original: text,
485
+ translated: text, // Fallback to original
486
+ sourceLanguage,
487
+ targetLanguage: normalizeLanguageCode(targetLanguage),
488
+ confidence: 0,
489
+ cached: false,
490
+ latencyMs: Date.now() - startTime,
491
+ };
492
+ }
493
+ }
494
+
495
+ // ============================================================================
496
+ // Exports
497
+ // ============================================================================
498
+
499
+ export type {
500
+ TranslationResult,
501
+ TranslationProviderConfig,
502
+ SupportedLanguageCode,
503
+ };