@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,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
|
+
};
|