langie 1.12.0 → 1.13.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/components/index.cjs +25 -9
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.css.map +1 -1
- package/dist/components/index.mjs +26 -10
- package/dist/components/index.mjs.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/core.cjs +12 -3
- package/dist/core.cjs.map +1 -1
- package/dist/core.mjs +12 -3
- package/dist/core.mjs.map +1 -1
- package/dist/index.cjs +25 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.mjs +26 -10
- package/dist/index.mjs.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/utils/flagUrl.d.ts +12 -0
- package/package.json +1 -1
package/dist/constants.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export declare const API_FIELD_TEXT = "t";
|
|
|
24
24
|
export declare const API_FIELD_FROM = "from";
|
|
25
25
|
export declare const API_FIELD_TO = "to";
|
|
26
26
|
export declare const API_FIELD_CTX = "ctx";
|
|
27
|
+
export declare const API_FIELD_GLOSSARY_SRC = "gs";
|
|
28
|
+
export declare const API_FIELD_GLOSSARY_TGT = "gt";
|
|
27
29
|
export declare const API_FIELD_TRANSLATED = "t";
|
|
28
30
|
export declare const API_FIELD_TRANSLATIONS = "translations";
|
|
29
31
|
export declare const API_FIELD_ERROR = "error";
|
package/dist/core.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* langie v1.
|
|
2
|
+
* langie v1.13.0
|
|
3
3
|
* (c) 2026 nlit
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*
|
|
@@ -41,6 +41,8 @@ var API_FIELD_TEXT = "t";
|
|
|
41
41
|
var API_FIELD_FROM = "from";
|
|
42
42
|
var API_FIELD_TO = "to";
|
|
43
43
|
var API_FIELD_CTX = "ctx";
|
|
44
|
+
var API_FIELD_GLOSSARY_SRC = "gs";
|
|
45
|
+
var API_FIELD_GLOSSARY_TGT = "gt";
|
|
44
46
|
var API_FIELD_TRANSLATIONS = "translations";
|
|
45
47
|
var API_FIELD_ERROR = "error";
|
|
46
48
|
var API_FIELD_TURNSTILE = "turnstileToken";
|
|
@@ -54,6 +56,11 @@ function resolveApiKey(options) {
|
|
|
54
56
|
if (!env) return void 0;
|
|
55
57
|
return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV];
|
|
56
58
|
}
|
|
59
|
+
function glossaryFields(t) {
|
|
60
|
+
const src = t[API_FIELD_GLOSSARY_SRC];
|
|
61
|
+
const tgt = t[API_FIELD_GLOSSARY_TGT];
|
|
62
|
+
return src && tgt ? { [API_FIELD_GLOSSARY_SRC]: src, [API_FIELD_GLOSSARY_TGT]: tgt } : {};
|
|
63
|
+
}
|
|
57
64
|
var translationCache = /* @__PURE__ */ new Map();
|
|
58
65
|
function getTranslationCacheKey(serviceTranslations, options) {
|
|
59
66
|
return JSON.stringify({
|
|
@@ -61,7 +68,8 @@ function getTranslationCacheKey(serviceTranslations, options) {
|
|
|
61
68
|
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
62
69
|
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
63
70
|
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
64
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
71
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui",
|
|
72
|
+
...glossaryFields(t)
|
|
65
73
|
})),
|
|
66
74
|
host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
|
|
67
75
|
context: options[API_FIELD_CTX] || "ui"
|
|
@@ -106,7 +114,8 @@ async function translateBatch(translations = [], options = {}) {
|
|
|
106
114
|
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
107
115
|
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
108
116
|
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
109
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
117
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui",
|
|
118
|
+
...glossaryFields(t)
|
|
110
119
|
})),
|
|
111
120
|
[API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
|
|
112
121
|
...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
|
package/dist/core.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core.ts","../src/constants.ts"],"sourcesContent":["/**\n * Core translation functions\n */\nimport type {\n TranslateRequestBody,\n TranslateServiceResponse,\n TranslatorLanguage,\n TranslatorOptions\n} from './types'\nimport {\n API_FIELD_TEXT,\n API_FIELD_FROM,\n API_FIELD_TO,\n API_FIELD_CTX,\n API_FIELD_TRANSLATIONS,\n API_FIELD_ERROR,\n API_FIELD_TURNSTILE\n} from './constants'\n\nconst DEFAULT_TRANSLATOR_HOST = 'http://localhost:8081'\nconst DEFAULT_API_KEY_ENV = 'TRANSLATOR_API_KEY'\n\n/**\n * Resolve the API key: explicit `apiKey` wins, then a host-configured env var\n * name (`apiKeyEnv`), then the default `TRANSLATOR_API_KEY`.\n */\nfunction resolveApiKey(options: TranslatorOptions): string | undefined {\n if (options.apiKey) return options.apiKey\n const env = typeof process !== 'undefined' ? process.env : undefined\n if (!env) return undefined\n return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV]\n}\n\n// --- Translation cache ---\nconst translationCache = new Map<string, Promise<TranslateServiceResponse[]>>()\n\nfunction getTranslationCacheKey(\n serviceTranslations: TranslateRequestBody[],\n options: TranslatorOptions\n): string {\n // Create a stable cache key based on translations and options\n return JSON.stringify({\n translations: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui'\n })),\n host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,\n context: options[API_FIELD_CTX] || 'ui'\n })\n}\n\n/**\n * Translates a batch of texts\n *\n * @param translations Array of translation requests\n * @param options Configuration options\n * @returns Array of translated texts\n */\nexport async function translateBatch(\n translations: TranslateRequestBody[] = [],\n options: TranslatorOptions = {}\n): Promise<TranslateServiceResponse[]> {\n // const startTime = Date.now()\n\n // try {\n // console.debug('[translator-sdk] translateBatch called', {\n // count: translations.length,\n // sample: translations.slice(0, 3).map((t) => ({\n // text: t.text?.slice?.(0, 30) ?? '',\n // from: t.from_lang,\n // to: t.to_lang\n // })),\n // opts: { ...options }\n // })\n // } catch (_) {\n // // Ignore logging errors – never block translation\n // }\n\n if (!Array.isArray(translations) || translations.length === 0) {\n // console.error('[translator-sdk] Invalid translations input:', { translations })\n throw new Error('translations must be a non-empty array')\n }\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n\n // console.debug('[translator-sdk] Configuration', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // apiKeyLength: apiKey?.length || 0\n // })\n\n // Partition translations: identical language pairs can be returned as-is\n const serviceTranslations: TranslateRequestBody[] = []\n const indexMap: number[] = []\n\n translations.forEach((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) {\n // console.debug('[translator-sdk] Skipping translation (same language)', { from, to, text: tr.t?.slice(0, 20) })\n return // no need to translate\n }\n serviceTranslations.push(tr)\n indexMap.push(idx)\n })\n\n // console.debug('[translator-sdk] Translation partitioning', {\n // total: translations.length,\n // needsTranslation: serviceTranslations.length,\n // skipped: translations.length - serviceTranslations.length\n // })\n\n let serviceResults: TranslateServiceResponse[] = []\n if (serviceTranslations.length > 0) {\n // Check cache first\n const cacheKey = getTranslationCacheKey(serviceTranslations, options)\n if (translationCache.has(cacheKey)) {\n // console.debug('[translator-sdk] Cache hit for translation batch')\n serviceResults = await translationCache.get(cacheKey)!\n } else {\n // const requestStartTime = Date.now()\n // console.debug('[translator-sdk] Sending request to translator', {\n // host: translatorHost,\n // apiKeySnippet: apiKey ? `${apiKey.slice(0, 4)}…${apiKey.slice(-4)}` : 'none',\n // batchCount: serviceTranslations.length,\n // requestSize: JSON.stringify({ translations: serviceTranslations }).length\n // })\n\n const translationPromise = (async () => {\n try {\n // Fresh Turnstile token per flush (single-use); only the keyless\n // /frontend/translate proxy requires it, other hosts ignore the field.\n const turnstileToken = options.getTurnstileToken\n ? await options.getTurnstileToken()\n : undefined\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout\n\n const resp = await fetch(`${translatorHost}/translate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n },\n body: JSON.stringify({\n [API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui'\n })),\n [API_FIELD_CTX]: options[API_FIELD_CTX] || 'ui',\n ...(turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {})\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeoutId)\n // const requestDuration = Date.now() - requestStartTime\n\n // console.debug('[translator-sdk] Translator response received', {\n // status: resp.status,\n // statusText: resp.statusText,\n // duration: `${requestDuration}ms`,\n // contentType: resp.headers.get('content-type'),\n // contentLength: resp.headers.get('content-length')\n // })\n\n if (!resp.ok) {\n console.error(\n '[translator-sdk] Translator error response:',\n resp.status,\n resp.statusText\n )\n throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`)\n }\n\n let parsed: {\n translations?: TranslateServiceResponse[]\n t?: string\n error?: string\n } | null\n try {\n parsed = await resp.clone().json()\n // console.debug('[translator-sdk] Response parsed successfully', {\n // hasTranslations: !!parsed?.translations,\n // translationCount: parsed?.translations?.length || 0,\n // hasLegacyT: !!parsed?.t\n // })\n } catch (jsonErr) {\n console.error('[translator-sdk] Failed to parse JSON response:', jsonErr)\n throw new Error(`Failed to parse JSON response: ${jsonErr}`)\n }\n\n // if (parsed) {\n // console.debug(\n // '[translator-sdk] Translator response sample',\n // JSON.stringify(parsed).slice(0, 500)\n // )\n // }\n\n const data = parsed || {}\n\n // Handle top-level error responses\n if (data[API_FIELD_ERROR]) {\n console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR])\n\n // Create error responses for all translations\n return serviceTranslations.map((translation) => ({\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT],\n [API_FIELD_ERROR]: data[API_FIELD_ERROR]\n }))\n }\n\n const results = Array.isArray(data[API_FIELD_TRANSLATIONS])\n ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {\n const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || ''\n\n // Handle error responses\n if (translation[API_FIELD_ERROR]) {\n console.warn(\n `[translator-sdk] Translation error for \"${originalText}\":`,\n translation[API_FIELD_ERROR]\n )\n return {\n [API_FIELD_TEXT]: originalText, // Return original text on error\n [API_FIELD_ERROR]: translation[API_FIELD_ERROR]\n }\n }\n\n // Handle language detection response\n if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {\n return {\n [API_FIELD_TEXT]: originalText, // For detection, return original text\n [API_FIELD_FROM]: translation[API_FIELD_FROM]\n }\n }\n\n // Handle translation response\n return {\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText\n }\n })\n : data[API_FIELD_TEXT]\n ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }]\n : []\n\n // console.debug('[translator-sdk] Service results processed', {\n // resultCount: results.length,\n // expectedCount: serviceTranslations.length\n // })\n\n return results\n } catch (error: unknown) {\n // Remove from cache on error\n translationCache.delete(cacheKey)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const message = `Translator request to ${translatorHost} timed out after 5 seconds.`\n console.error(message)\n throw new Error(message)\n }\n const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`\n console.error(message)\n throw new Error(message)\n }\n })()\n\n // Cache the promise\n translationCache.set(cacheKey, translationPromise)\n serviceResults = await translationPromise\n }\n }\n\n // Assemble final results in original order\n const final = translations.map((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n\n const svcIdx = indexMap.indexOf(idx)\n if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n })\n\n // const totalDuration = Date.now() - startTime\n // console.debug('[translator-sdk] translateBatch completed', {\n // totalDuration: `${totalDuration}ms`,\n // resultCount: final.length,\n // successCount: final.filter(r => r.translated && r.translated !== r.text).length\n // })\n\n return final as TranslateServiceResponse[]\n}\n\n// --- Cache management functions ---\nexport function clearTranslationCache(): void {\n translationCache.clear()\n}\n\nexport function getTranslationCacheSize(): number {\n return translationCache.size\n}\n\n/**\n * Vendor/legacy codes whose primary subtag isn't their BCP-47 base language.\n * Folding `kazlat` → `kk` treats Latin and Cyrillic Kazakh as siblings, so a\n * popularity-passing variant keeps its below-floor sibling visible (both scripts\n * stay selectable). It does NOT dedup them — both rows survive.\n */\nconst BASE_CODE_ALIASES: Record<string, string> = {\n kazlat: 'kk' // Kazakh (Latin) — vendor code, not \"kk-latn\"\n}\n\n/**\n * Reduce a language code to its BCP-47 primary subtag, so script/region variants\n * share a base: `sr-cyrl`/`sr-latn` → `sr`, `kk`/`kazlat` → `kk`.\n */\nexport function baseLanguageCode(code: string): string {\n const c = (code || '').trim().toLowerCase().replace(/_/g, '-')\n return BASE_CODE_ALIASES[c] ?? (c.split('-')[0] || c)\n}\n\n/**\n * Fetches available languages from the translation service\n *\n * @param options Configuration options\n * @returns Array of available languages\n */\nexport async function fetchAvailableLanguages(\n options: TranslatorOptions = {}\n): Promise<TranslatorLanguage[]> {\n // const startTime = Date.now()\n\n // console.debug('[translator-sdk] fetchAvailableLanguages called', { options })\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n // Popularity filtering is opt-in. Default 0 returns the full catalog so the\n // caller (e.g. a searchable picker) gets every language and every script\n // variant; a host env var must not silently prune the list.\n const minPop = options.minPopularity !== undefined ? Number(options.minPopularity) : 0\n\n // console.debug('[translator-sdk] Languages request config', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // minPopularity: minPop,\n // country: options.country,\n // region: options.region\n // })\n\n const queryParams = []\n if (options.country) queryParams.push(`country=${encodeURIComponent(options.country)}`)\n if (options.region) queryParams.push(`region=${encodeURIComponent(options.region)}`)\n const queryStr = queryParams.length ? `?${queryParams.join('&')}` : ''\n\n const requestUrl = `${translatorHost}/languages${queryStr}`\n\n try {\n const resp = await fetch(requestUrl, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n }\n })\n\n if (!resp.ok) {\n throw new Error(`Translator languages error: ${resp.status}`)\n }\n\n const data = await resp.json()\n const languages: TranslatorLanguage[] = Array.isArray(data) ? data : data.languages || []\n\n if (minPop <= 0) return languages\n\n const passes = (lang: TranslatorLanguage): boolean =>\n lang.popularity === undefined || lang.popularity === null || Number(lang.popularity) >= minPop\n\n // Keep every script/region variant of a base language that has at least one\n // passing entry, so a low-popularity variant (e.g. Kazakh Cyrillic with\n // popularity 0) survives next to its popular sibling and the UI can still\n // offer the \"which variant?\" choice. Otherwise a flat floor splits the pair.\n const keptBases = new Set(languages.filter(passes).map((l) => baseLanguageCode(l.code)))\n return languages.filter((lang) => passes(lang) || keptBases.has(baseLanguageCode(lang.code)))\n } catch (error) {\n console.error('[translator-sdk] Languages fetch error:', error)\n throw error\n }\n}\n","/**\n * Default API configuration constants\n */\nexport const DEFAULT_API_HOST = 'https://api.langie.uk/v1'\n\n/**\n * Development/Demo API host for local testing.\n * Uses the keyless first-party `/frontend` proxy (the backend injects\n * PAGES_API_KEY); the raw `/v1` endpoints require a client API key.\n */\nexport const DEV_API_HOST = 'http://localhost:8081/frontend'\n\n/**\n * Default translator options\n */\nexport const DEFAULT_TRANSLATOR_OPTIONS = {\n translatorHost: DEFAULT_API_HOST,\n defaultLanguage: 'en',\n fallbackLanguage: 'en'\n} as const\n\n/**\n * API field constants\n * Used throughout the SDK to ensure consistency\n */\n\n// Translation text field\nexport const API_FIELD_TEXT = 't'\n\n// Source language field\nexport const API_FIELD_FROM = 'from'\n\n// Target language field\nexport const API_FIELD_TO = 'to'\n\n// Context field\nexport const API_FIELD_CTX = 'ctx'\n\n// Translated text field (same as text field for response)\nexport const API_FIELD_TRANSLATED = 't'\n\n// Translations array field\nexport const API_FIELD_TRANSLATIONS = 'translations'\n\n// Error field for API responses\nexport const API_FIELD_ERROR = 'error'\n\n// Cloudflare Turnstile token field — anti-abuse on the keyless /frontend/translate proxy\nexport const API_FIELD_TURNSTILE = 'turnstileToken'\n"],"mappings":"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","names":["message"]}
|
|
1
|
+
{"version":3,"sources":["../src/core.ts","../src/constants.ts"],"sourcesContent":["/**\n * Core translation functions\n */\nimport type {\n TranslateRequestBody,\n TranslateServiceResponse,\n TranslatorLanguage,\n TranslatorOptions\n} from './types'\nimport {\n API_FIELD_TEXT,\n API_FIELD_FROM,\n API_FIELD_TO,\n API_FIELD_CTX,\n API_FIELD_GLOSSARY_SRC,\n API_FIELD_GLOSSARY_TGT,\n API_FIELD_TRANSLATIONS,\n API_FIELD_ERROR,\n API_FIELD_TURNSTILE\n} from './constants'\n\nconst DEFAULT_TRANSLATOR_HOST = 'http://localhost:8081'\nconst DEFAULT_API_KEY_ENV = 'TRANSLATOR_API_KEY'\n\n/**\n * Resolve the API key: explicit `apiKey` wins, then a host-configured env var\n * name (`apiKeyEnv`), then the default `TRANSLATOR_API_KEY`.\n */\nfunction resolveApiKey(options: TranslatorOptions): string | undefined {\n if (options.apiKey) return options.apiKey\n const env = typeof process !== 'undefined' ? process.env : undefined\n if (!env) return undefined\n return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV]\n}\n\n// Optional glossary pair, included only when both terms are set. Shared by the\n// cache key and the request body so a forced request never collides with a plain one.\nfunction glossaryFields(t: TranslateRequestBody): Record<string, string> {\n const src = t[API_FIELD_GLOSSARY_SRC]\n const tgt = t[API_FIELD_GLOSSARY_TGT]\n return src && tgt ? { [API_FIELD_GLOSSARY_SRC]: src, [API_FIELD_GLOSSARY_TGT]: tgt } : {}\n}\n\n// --- Translation cache ---\nconst translationCache = new Map<string, Promise<TranslateServiceResponse[]>>()\n\nfunction getTranslationCacheKey(\n serviceTranslations: TranslateRequestBody[],\n options: TranslatorOptions\n): string {\n // Create a stable cache key based on translations and options\n return JSON.stringify({\n translations: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui',\n ...glossaryFields(t)\n })),\n host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,\n context: options[API_FIELD_CTX] || 'ui'\n })\n}\n\n/**\n * Translates a batch of texts\n *\n * @param translations Array of translation requests\n * @param options Configuration options\n * @returns Array of translated texts\n */\nexport async function translateBatch(\n translations: TranslateRequestBody[] = [],\n options: TranslatorOptions = {}\n): Promise<TranslateServiceResponse[]> {\n // const startTime = Date.now()\n\n // try {\n // console.debug('[translator-sdk] translateBatch called', {\n // count: translations.length,\n // sample: translations.slice(0, 3).map((t) => ({\n // text: t.text?.slice?.(0, 30) ?? '',\n // from: t.from_lang,\n // to: t.to_lang\n // })),\n // opts: { ...options }\n // })\n // } catch (_) {\n // // Ignore logging errors – never block translation\n // }\n\n if (!Array.isArray(translations) || translations.length === 0) {\n // console.error('[translator-sdk] Invalid translations input:', { translations })\n throw new Error('translations must be a non-empty array')\n }\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n\n // console.debug('[translator-sdk] Configuration', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // apiKeyLength: apiKey?.length || 0\n // })\n\n // Partition translations: identical language pairs can be returned as-is\n const serviceTranslations: TranslateRequestBody[] = []\n const indexMap: number[] = []\n\n translations.forEach((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) {\n // console.debug('[translator-sdk] Skipping translation (same language)', { from, to, text: tr.t?.slice(0, 20) })\n return // no need to translate\n }\n serviceTranslations.push(tr)\n indexMap.push(idx)\n })\n\n // console.debug('[translator-sdk] Translation partitioning', {\n // total: translations.length,\n // needsTranslation: serviceTranslations.length,\n // skipped: translations.length - serviceTranslations.length\n // })\n\n let serviceResults: TranslateServiceResponse[] = []\n if (serviceTranslations.length > 0) {\n // Check cache first\n const cacheKey = getTranslationCacheKey(serviceTranslations, options)\n if (translationCache.has(cacheKey)) {\n // console.debug('[translator-sdk] Cache hit for translation batch')\n serviceResults = await translationCache.get(cacheKey)!\n } else {\n // const requestStartTime = Date.now()\n // console.debug('[translator-sdk] Sending request to translator', {\n // host: translatorHost,\n // apiKeySnippet: apiKey ? `${apiKey.slice(0, 4)}…${apiKey.slice(-4)}` : 'none',\n // batchCount: serviceTranslations.length,\n // requestSize: JSON.stringify({ translations: serviceTranslations }).length\n // })\n\n const translationPromise = (async () => {\n try {\n // Fresh Turnstile token per flush (single-use); only the keyless\n // /frontend/translate proxy requires it, other hosts ignore the field.\n const turnstileToken = options.getTurnstileToken\n ? await options.getTurnstileToken()\n : undefined\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout\n\n const resp = await fetch(`${translatorHost}/translate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n },\n body: JSON.stringify({\n [API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui',\n ...glossaryFields(t)\n })),\n [API_FIELD_CTX]: options[API_FIELD_CTX] || 'ui',\n ...(turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {})\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeoutId)\n // const requestDuration = Date.now() - requestStartTime\n\n // console.debug('[translator-sdk] Translator response received', {\n // status: resp.status,\n // statusText: resp.statusText,\n // duration: `${requestDuration}ms`,\n // contentType: resp.headers.get('content-type'),\n // contentLength: resp.headers.get('content-length')\n // })\n\n if (!resp.ok) {\n console.error(\n '[translator-sdk] Translator error response:',\n resp.status,\n resp.statusText\n )\n throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`)\n }\n\n let parsed: {\n translations?: TranslateServiceResponse[]\n t?: string\n error?: string\n } | null\n try {\n parsed = await resp.clone().json()\n // console.debug('[translator-sdk] Response parsed successfully', {\n // hasTranslations: !!parsed?.translations,\n // translationCount: parsed?.translations?.length || 0,\n // hasLegacyT: !!parsed?.t\n // })\n } catch (jsonErr) {\n console.error('[translator-sdk] Failed to parse JSON response:', jsonErr)\n throw new Error(`Failed to parse JSON response: ${jsonErr}`)\n }\n\n // if (parsed) {\n // console.debug(\n // '[translator-sdk] Translator response sample',\n // JSON.stringify(parsed).slice(0, 500)\n // )\n // }\n\n const data = parsed || {}\n\n // Handle top-level error responses\n if (data[API_FIELD_ERROR]) {\n console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR])\n\n // Create error responses for all translations\n return serviceTranslations.map((translation) => ({\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT],\n [API_FIELD_ERROR]: data[API_FIELD_ERROR]\n }))\n }\n\n const results = Array.isArray(data[API_FIELD_TRANSLATIONS])\n ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {\n const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || ''\n\n // Handle error responses\n if (translation[API_FIELD_ERROR]) {\n console.warn(\n `[translator-sdk] Translation error for \"${originalText}\":`,\n translation[API_FIELD_ERROR]\n )\n return {\n [API_FIELD_TEXT]: originalText, // Return original text on error\n [API_FIELD_ERROR]: translation[API_FIELD_ERROR]\n }\n }\n\n // Handle language detection response\n if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {\n return {\n [API_FIELD_TEXT]: originalText, // For detection, return original text\n [API_FIELD_FROM]: translation[API_FIELD_FROM]\n }\n }\n\n // Handle translation response\n return {\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText\n }\n })\n : data[API_FIELD_TEXT]\n ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }]\n : []\n\n // console.debug('[translator-sdk] Service results processed', {\n // resultCount: results.length,\n // expectedCount: serviceTranslations.length\n // })\n\n return results\n } catch (error: unknown) {\n // Remove from cache on error\n translationCache.delete(cacheKey)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const message = `Translator request to ${translatorHost} timed out after 5 seconds.`\n console.error(message)\n throw new Error(message)\n }\n const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`\n console.error(message)\n throw new Error(message)\n }\n })()\n\n // Cache the promise\n translationCache.set(cacheKey, translationPromise)\n serviceResults = await translationPromise\n }\n }\n\n // Assemble final results in original order\n const final = translations.map((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n\n const svcIdx = indexMap.indexOf(idx)\n if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n })\n\n // const totalDuration = Date.now() - startTime\n // console.debug('[translator-sdk] translateBatch completed', {\n // totalDuration: `${totalDuration}ms`,\n // resultCount: final.length,\n // successCount: final.filter(r => r.translated && r.translated !== r.text).length\n // })\n\n return final as TranslateServiceResponse[]\n}\n\n// --- Cache management functions ---\nexport function clearTranslationCache(): void {\n translationCache.clear()\n}\n\nexport function getTranslationCacheSize(): number {\n return translationCache.size\n}\n\n/**\n * Vendor/legacy codes whose primary subtag isn't their BCP-47 base language.\n * Folding `kazlat` → `kk` treats Latin and Cyrillic Kazakh as siblings, so a\n * popularity-passing variant keeps its below-floor sibling visible (both scripts\n * stay selectable). It does NOT dedup them — both rows survive.\n */\nconst BASE_CODE_ALIASES: Record<string, string> = {\n kazlat: 'kk' // Kazakh (Latin) — vendor code, not \"kk-latn\"\n}\n\n/**\n * Reduce a language code to its BCP-47 primary subtag, so script/region variants\n * share a base: `sr-cyrl`/`sr-latn` → `sr`, `kk`/`kazlat` → `kk`.\n */\nexport function baseLanguageCode(code: string): string {\n const c = (code || '').trim().toLowerCase().replace(/_/g, '-')\n return BASE_CODE_ALIASES[c] ?? (c.split('-')[0] || c)\n}\n\n/**\n * Fetches available languages from the translation service\n *\n * @param options Configuration options\n * @returns Array of available languages\n */\nexport async function fetchAvailableLanguages(\n options: TranslatorOptions = {}\n): Promise<TranslatorLanguage[]> {\n // const startTime = Date.now()\n\n // console.debug('[translator-sdk] fetchAvailableLanguages called', { options })\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n // Popularity filtering is opt-in. Default 0 returns the full catalog so the\n // caller (e.g. a searchable picker) gets every language and every script\n // variant; a host env var must not silently prune the list.\n const minPop = options.minPopularity !== undefined ? Number(options.minPopularity) : 0\n\n // console.debug('[translator-sdk] Languages request config', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // minPopularity: minPop,\n // country: options.country,\n // region: options.region\n // })\n\n const queryParams = []\n if (options.country) queryParams.push(`country=${encodeURIComponent(options.country)}`)\n if (options.region) queryParams.push(`region=${encodeURIComponent(options.region)}`)\n const queryStr = queryParams.length ? `?${queryParams.join('&')}` : ''\n\n const requestUrl = `${translatorHost}/languages${queryStr}`\n\n try {\n const resp = await fetch(requestUrl, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n }\n })\n\n if (!resp.ok) {\n throw new Error(`Translator languages error: ${resp.status}`)\n }\n\n const data = await resp.json()\n const languages: TranslatorLanguage[] = Array.isArray(data) ? data : data.languages || []\n\n if (minPop <= 0) return languages\n\n const passes = (lang: TranslatorLanguage): boolean =>\n lang.popularity === undefined || lang.popularity === null || Number(lang.popularity) >= minPop\n\n // Keep every script/region variant of a base language that has at least one\n // passing entry, so a low-popularity variant (e.g. Kazakh Cyrillic with\n // popularity 0) survives next to its popular sibling and the UI can still\n // offer the \"which variant?\" choice. Otherwise a flat floor splits the pair.\n const keptBases = new Set(languages.filter(passes).map((l) => baseLanguageCode(l.code)))\n return languages.filter((lang) => passes(lang) || keptBases.has(baseLanguageCode(lang.code)))\n } catch (error) {\n console.error('[translator-sdk] Languages fetch error:', error)\n throw error\n }\n}\n","/**\n * Default API configuration constants\n */\nexport const DEFAULT_API_HOST = 'https://api.langie.uk/v1'\n\n/**\n * Development/Demo API host for local testing.\n * Uses the keyless first-party `/frontend` proxy (the backend injects\n * PAGES_API_KEY); the raw `/v1` endpoints require a client API key.\n */\nexport const DEV_API_HOST = 'http://localhost:8081/frontend'\n\n/**\n * Default translator options\n */\nexport const DEFAULT_TRANSLATOR_OPTIONS = {\n translatorHost: DEFAULT_API_HOST,\n defaultLanguage: 'en',\n fallbackLanguage: 'en'\n} as const\n\n/**\n * API field constants\n * Used throughout the SDK to ensure consistency\n */\n\n// Translation text field\nexport const API_FIELD_TEXT = 't'\n\n// Source language field\nexport const API_FIELD_FROM = 'from'\n\n// Target language field\nexport const API_FIELD_TO = 'to'\n\n// Context field\nexport const API_FIELD_CTX = 'ctx'\n\n// Glossary term-forcing fields (optional): render API_FIELD_GLOSSARY_SRC as exactly\n// API_FIELD_GLOSSARY_TGT so a headword and its example stay consistent. Only\n// providers with an inline glossary honour them; the API ignores the pair otherwise.\nexport const API_FIELD_GLOSSARY_SRC = 'gs'\nexport const API_FIELD_GLOSSARY_TGT = 'gt'\n\n// Translated text field (same as text field for response)\nexport const API_FIELD_TRANSLATED = 't'\n\n// Translations array field\nexport const API_FIELD_TRANSLATIONS = 'translations'\n\n// Error field for API responses\nexport const API_FIELD_ERROR = 'error'\n\n// Cloudflare Turnstile token field — anti-abuse on the keyless /frontend/translate proxy\nexport const API_FIELD_TURNSTILE = 'turnstileToken'\n"],"mappings":"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","names":["message"]}
|
package/dist/core.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* langie v1.
|
|
2
|
+
* langie v1.13.0
|
|
3
3
|
* (c) 2026 nlit
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*
|
|
@@ -12,6 +12,8 @@ var API_FIELD_TEXT = "t";
|
|
|
12
12
|
var API_FIELD_FROM = "from";
|
|
13
13
|
var API_FIELD_TO = "to";
|
|
14
14
|
var API_FIELD_CTX = "ctx";
|
|
15
|
+
var API_FIELD_GLOSSARY_SRC = "gs";
|
|
16
|
+
var API_FIELD_GLOSSARY_TGT = "gt";
|
|
15
17
|
var API_FIELD_TRANSLATIONS = "translations";
|
|
16
18
|
var API_FIELD_ERROR = "error";
|
|
17
19
|
var API_FIELD_TURNSTILE = "turnstileToken";
|
|
@@ -25,6 +27,11 @@ function resolveApiKey(options) {
|
|
|
25
27
|
if (!env) return void 0;
|
|
26
28
|
return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV];
|
|
27
29
|
}
|
|
30
|
+
function glossaryFields(t) {
|
|
31
|
+
const src = t[API_FIELD_GLOSSARY_SRC];
|
|
32
|
+
const tgt = t[API_FIELD_GLOSSARY_TGT];
|
|
33
|
+
return src && tgt ? { [API_FIELD_GLOSSARY_SRC]: src, [API_FIELD_GLOSSARY_TGT]: tgt } : {};
|
|
34
|
+
}
|
|
28
35
|
var translationCache = /* @__PURE__ */ new Map();
|
|
29
36
|
function getTranslationCacheKey(serviceTranslations, options) {
|
|
30
37
|
return JSON.stringify({
|
|
@@ -32,7 +39,8 @@ function getTranslationCacheKey(serviceTranslations, options) {
|
|
|
32
39
|
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
33
40
|
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
34
41
|
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
35
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
42
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui",
|
|
43
|
+
...glossaryFields(t)
|
|
36
44
|
})),
|
|
37
45
|
host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
|
|
38
46
|
context: options[API_FIELD_CTX] || "ui"
|
|
@@ -77,7 +85,8 @@ async function translateBatch(translations = [], options = {}) {
|
|
|
77
85
|
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
78
86
|
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
79
87
|
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
80
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
88
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui",
|
|
89
|
+
...glossaryFields(t)
|
|
81
90
|
})),
|
|
82
91
|
[API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
|
|
83
92
|
...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
|
package/dist/core.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/core.ts"],"sourcesContent":["/**\n * Default API configuration constants\n */\nexport const DEFAULT_API_HOST = 'https://api.langie.uk/v1'\n\n/**\n * Development/Demo API host for local testing.\n * Uses the keyless first-party `/frontend` proxy (the backend injects\n * PAGES_API_KEY); the raw `/v1` endpoints require a client API key.\n */\nexport const DEV_API_HOST = 'http://localhost:8081/frontend'\n\n/**\n * Default translator options\n */\nexport const DEFAULT_TRANSLATOR_OPTIONS = {\n translatorHost: DEFAULT_API_HOST,\n defaultLanguage: 'en',\n fallbackLanguage: 'en'\n} as const\n\n/**\n * API field constants\n * Used throughout the SDK to ensure consistency\n */\n\n// Translation text field\nexport const API_FIELD_TEXT = 't'\n\n// Source language field\nexport const API_FIELD_FROM = 'from'\n\n// Target language field\nexport const API_FIELD_TO = 'to'\n\n// Context field\nexport const API_FIELD_CTX = 'ctx'\n\n// Translated text field (same as text field for response)\nexport const API_FIELD_TRANSLATED = 't'\n\n// Translations array field\nexport const API_FIELD_TRANSLATIONS = 'translations'\n\n// Error field for API responses\nexport const API_FIELD_ERROR = 'error'\n\n// Cloudflare Turnstile token field — anti-abuse on the keyless /frontend/translate proxy\nexport const API_FIELD_TURNSTILE = 'turnstileToken'\n","/**\n * Core translation functions\n */\nimport type {\n TranslateRequestBody,\n TranslateServiceResponse,\n TranslatorLanguage,\n TranslatorOptions\n} from './types'\nimport {\n API_FIELD_TEXT,\n API_FIELD_FROM,\n API_FIELD_TO,\n API_FIELD_CTX,\n API_FIELD_TRANSLATIONS,\n API_FIELD_ERROR,\n API_FIELD_TURNSTILE\n} from './constants'\n\nconst DEFAULT_TRANSLATOR_HOST = 'http://localhost:8081'\nconst DEFAULT_API_KEY_ENV = 'TRANSLATOR_API_KEY'\n\n/**\n * Resolve the API key: explicit `apiKey` wins, then a host-configured env var\n * name (`apiKeyEnv`), then the default `TRANSLATOR_API_KEY`.\n */\nfunction resolveApiKey(options: TranslatorOptions): string | undefined {\n if (options.apiKey) return options.apiKey\n const env = typeof process !== 'undefined' ? process.env : undefined\n if (!env) return undefined\n return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV]\n}\n\n// --- Translation cache ---\nconst translationCache = new Map<string, Promise<TranslateServiceResponse[]>>()\n\nfunction getTranslationCacheKey(\n serviceTranslations: TranslateRequestBody[],\n options: TranslatorOptions\n): string {\n // Create a stable cache key based on translations and options\n return JSON.stringify({\n translations: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui'\n })),\n host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,\n context: options[API_FIELD_CTX] || 'ui'\n })\n}\n\n/**\n * Translates a batch of texts\n *\n * @param translations Array of translation requests\n * @param options Configuration options\n * @returns Array of translated texts\n */\nexport async function translateBatch(\n translations: TranslateRequestBody[] = [],\n options: TranslatorOptions = {}\n): Promise<TranslateServiceResponse[]> {\n // const startTime = Date.now()\n\n // try {\n // console.debug('[translator-sdk] translateBatch called', {\n // count: translations.length,\n // sample: translations.slice(0, 3).map((t) => ({\n // text: t.text?.slice?.(0, 30) ?? '',\n // from: t.from_lang,\n // to: t.to_lang\n // })),\n // opts: { ...options }\n // })\n // } catch (_) {\n // // Ignore logging errors – never block translation\n // }\n\n if (!Array.isArray(translations) || translations.length === 0) {\n // console.error('[translator-sdk] Invalid translations input:', { translations })\n throw new Error('translations must be a non-empty array')\n }\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n\n // console.debug('[translator-sdk] Configuration', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // apiKeyLength: apiKey?.length || 0\n // })\n\n // Partition translations: identical language pairs can be returned as-is\n const serviceTranslations: TranslateRequestBody[] = []\n const indexMap: number[] = []\n\n translations.forEach((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) {\n // console.debug('[translator-sdk] Skipping translation (same language)', { from, to, text: tr.t?.slice(0, 20) })\n return // no need to translate\n }\n serviceTranslations.push(tr)\n indexMap.push(idx)\n })\n\n // console.debug('[translator-sdk] Translation partitioning', {\n // total: translations.length,\n // needsTranslation: serviceTranslations.length,\n // skipped: translations.length - serviceTranslations.length\n // })\n\n let serviceResults: TranslateServiceResponse[] = []\n if (serviceTranslations.length > 0) {\n // Check cache first\n const cacheKey = getTranslationCacheKey(serviceTranslations, options)\n if (translationCache.has(cacheKey)) {\n // console.debug('[translator-sdk] Cache hit for translation batch')\n serviceResults = await translationCache.get(cacheKey)!\n } else {\n // const requestStartTime = Date.now()\n // console.debug('[translator-sdk] Sending request to translator', {\n // host: translatorHost,\n // apiKeySnippet: apiKey ? `${apiKey.slice(0, 4)}…${apiKey.slice(-4)}` : 'none',\n // batchCount: serviceTranslations.length,\n // requestSize: JSON.stringify({ translations: serviceTranslations }).length\n // })\n\n const translationPromise = (async () => {\n try {\n // Fresh Turnstile token per flush (single-use); only the keyless\n // /frontend/translate proxy requires it, other hosts ignore the field.\n const turnstileToken = options.getTurnstileToken\n ? await options.getTurnstileToken()\n : undefined\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout\n\n const resp = await fetch(`${translatorHost}/translate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n },\n body: JSON.stringify({\n [API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui'\n })),\n [API_FIELD_CTX]: options[API_FIELD_CTX] || 'ui',\n ...(turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {})\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeoutId)\n // const requestDuration = Date.now() - requestStartTime\n\n // console.debug('[translator-sdk] Translator response received', {\n // status: resp.status,\n // statusText: resp.statusText,\n // duration: `${requestDuration}ms`,\n // contentType: resp.headers.get('content-type'),\n // contentLength: resp.headers.get('content-length')\n // })\n\n if (!resp.ok) {\n console.error(\n '[translator-sdk] Translator error response:',\n resp.status,\n resp.statusText\n )\n throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`)\n }\n\n let parsed: {\n translations?: TranslateServiceResponse[]\n t?: string\n error?: string\n } | null\n try {\n parsed = await resp.clone().json()\n // console.debug('[translator-sdk] Response parsed successfully', {\n // hasTranslations: !!parsed?.translations,\n // translationCount: parsed?.translations?.length || 0,\n // hasLegacyT: !!parsed?.t\n // })\n } catch (jsonErr) {\n console.error('[translator-sdk] Failed to parse JSON response:', jsonErr)\n throw new Error(`Failed to parse JSON response: ${jsonErr}`)\n }\n\n // if (parsed) {\n // console.debug(\n // '[translator-sdk] Translator response sample',\n // JSON.stringify(parsed).slice(0, 500)\n // )\n // }\n\n const data = parsed || {}\n\n // Handle top-level error responses\n if (data[API_FIELD_ERROR]) {\n console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR])\n\n // Create error responses for all translations\n return serviceTranslations.map((translation) => ({\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT],\n [API_FIELD_ERROR]: data[API_FIELD_ERROR]\n }))\n }\n\n const results = Array.isArray(data[API_FIELD_TRANSLATIONS])\n ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {\n const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || ''\n\n // Handle error responses\n if (translation[API_FIELD_ERROR]) {\n console.warn(\n `[translator-sdk] Translation error for \"${originalText}\":`,\n translation[API_FIELD_ERROR]\n )\n return {\n [API_FIELD_TEXT]: originalText, // Return original text on error\n [API_FIELD_ERROR]: translation[API_FIELD_ERROR]\n }\n }\n\n // Handle language detection response\n if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {\n return {\n [API_FIELD_TEXT]: originalText, // For detection, return original text\n [API_FIELD_FROM]: translation[API_FIELD_FROM]\n }\n }\n\n // Handle translation response\n return {\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText\n }\n })\n : data[API_FIELD_TEXT]\n ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }]\n : []\n\n // console.debug('[translator-sdk] Service results processed', {\n // resultCount: results.length,\n // expectedCount: serviceTranslations.length\n // })\n\n return results\n } catch (error: unknown) {\n // Remove from cache on error\n translationCache.delete(cacheKey)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const message = `Translator request to ${translatorHost} timed out after 5 seconds.`\n console.error(message)\n throw new Error(message)\n }\n const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`\n console.error(message)\n throw new Error(message)\n }\n })()\n\n // Cache the promise\n translationCache.set(cacheKey, translationPromise)\n serviceResults = await translationPromise\n }\n }\n\n // Assemble final results in original order\n const final = translations.map((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n\n const svcIdx = indexMap.indexOf(idx)\n if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n })\n\n // const totalDuration = Date.now() - startTime\n // console.debug('[translator-sdk] translateBatch completed', {\n // totalDuration: `${totalDuration}ms`,\n // resultCount: final.length,\n // successCount: final.filter(r => r.translated && r.translated !== r.text).length\n // })\n\n return final as TranslateServiceResponse[]\n}\n\n// --- Cache management functions ---\nexport function clearTranslationCache(): void {\n translationCache.clear()\n}\n\nexport function getTranslationCacheSize(): number {\n return translationCache.size\n}\n\n/**\n * Vendor/legacy codes whose primary subtag isn't their BCP-47 base language.\n * Folding `kazlat` → `kk` treats Latin and Cyrillic Kazakh as siblings, so a\n * popularity-passing variant keeps its below-floor sibling visible (both scripts\n * stay selectable). It does NOT dedup them — both rows survive.\n */\nconst BASE_CODE_ALIASES: Record<string, string> = {\n kazlat: 'kk' // Kazakh (Latin) — vendor code, not \"kk-latn\"\n}\n\n/**\n * Reduce a language code to its BCP-47 primary subtag, so script/region variants\n * share a base: `sr-cyrl`/`sr-latn` → `sr`, `kk`/`kazlat` → `kk`.\n */\nexport function baseLanguageCode(code: string): string {\n const c = (code || '').trim().toLowerCase().replace(/_/g, '-')\n return BASE_CODE_ALIASES[c] ?? (c.split('-')[0] || c)\n}\n\n/**\n * Fetches available languages from the translation service\n *\n * @param options Configuration options\n * @returns Array of available languages\n */\nexport async function fetchAvailableLanguages(\n options: TranslatorOptions = {}\n): Promise<TranslatorLanguage[]> {\n // const startTime = Date.now()\n\n // console.debug('[translator-sdk] fetchAvailableLanguages called', { options })\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n // Popularity filtering is opt-in. Default 0 returns the full catalog so the\n // caller (e.g. a searchable picker) gets every language and every script\n // variant; a host env var must not silently prune the list.\n const minPop = options.minPopularity !== undefined ? Number(options.minPopularity) : 0\n\n // console.debug('[translator-sdk] Languages request config', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // minPopularity: minPop,\n // country: options.country,\n // region: options.region\n // })\n\n const queryParams = []\n if (options.country) queryParams.push(`country=${encodeURIComponent(options.country)}`)\n if (options.region) queryParams.push(`region=${encodeURIComponent(options.region)}`)\n const queryStr = queryParams.length ? `?${queryParams.join('&')}` : ''\n\n const requestUrl = `${translatorHost}/languages${queryStr}`\n\n try {\n const resp = await fetch(requestUrl, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n }\n })\n\n if (!resp.ok) {\n throw new Error(`Translator languages error: ${resp.status}`)\n }\n\n const data = await resp.json()\n const languages: TranslatorLanguage[] = Array.isArray(data) ? data : data.languages || []\n\n if (minPop <= 0) return languages\n\n const passes = (lang: TranslatorLanguage): boolean =>\n lang.popularity === undefined || lang.popularity === null || Number(lang.popularity) >= minPop\n\n // Keep every script/region variant of a base language that has at least one\n // passing entry, so a low-popularity variant (e.g. Kazakh Cyrillic with\n // popularity 0) survives next to its popular sibling and the UI can still\n // offer the \"which variant?\" choice. Otherwise a flat floor splits the pair.\n const keptBases = new Set(languages.filter(passes).map((l) => baseLanguageCode(l.code)))\n return languages.filter((lang) => passes(lang) || keptBases.has(baseLanguageCode(lang.code)))\n } catch (error) {\n console.error('[translator-sdk] Languages fetch error:', error)\n throw error\n }\n}\n"],"mappings":"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","names":["message"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/core.ts"],"sourcesContent":["/**\n * Default API configuration constants\n */\nexport const DEFAULT_API_HOST = 'https://api.langie.uk/v1'\n\n/**\n * Development/Demo API host for local testing.\n * Uses the keyless first-party `/frontend` proxy (the backend injects\n * PAGES_API_KEY); the raw `/v1` endpoints require a client API key.\n */\nexport const DEV_API_HOST = 'http://localhost:8081/frontend'\n\n/**\n * Default translator options\n */\nexport const DEFAULT_TRANSLATOR_OPTIONS = {\n translatorHost: DEFAULT_API_HOST,\n defaultLanguage: 'en',\n fallbackLanguage: 'en'\n} as const\n\n/**\n * API field constants\n * Used throughout the SDK to ensure consistency\n */\n\n// Translation text field\nexport const API_FIELD_TEXT = 't'\n\n// Source language field\nexport const API_FIELD_FROM = 'from'\n\n// Target language field\nexport const API_FIELD_TO = 'to'\n\n// Context field\nexport const API_FIELD_CTX = 'ctx'\n\n// Glossary term-forcing fields (optional): render API_FIELD_GLOSSARY_SRC as exactly\n// API_FIELD_GLOSSARY_TGT so a headword and its example stay consistent. Only\n// providers with an inline glossary honour them; the API ignores the pair otherwise.\nexport const API_FIELD_GLOSSARY_SRC = 'gs'\nexport const API_FIELD_GLOSSARY_TGT = 'gt'\n\n// Translated text field (same as text field for response)\nexport const API_FIELD_TRANSLATED = 't'\n\n// Translations array field\nexport const API_FIELD_TRANSLATIONS = 'translations'\n\n// Error field for API responses\nexport const API_FIELD_ERROR = 'error'\n\n// Cloudflare Turnstile token field — anti-abuse on the keyless /frontend/translate proxy\nexport const API_FIELD_TURNSTILE = 'turnstileToken'\n","/**\n * Core translation functions\n */\nimport type {\n TranslateRequestBody,\n TranslateServiceResponse,\n TranslatorLanguage,\n TranslatorOptions\n} from './types'\nimport {\n API_FIELD_TEXT,\n API_FIELD_FROM,\n API_FIELD_TO,\n API_FIELD_CTX,\n API_FIELD_GLOSSARY_SRC,\n API_FIELD_GLOSSARY_TGT,\n API_FIELD_TRANSLATIONS,\n API_FIELD_ERROR,\n API_FIELD_TURNSTILE\n} from './constants'\n\nconst DEFAULT_TRANSLATOR_HOST = 'http://localhost:8081'\nconst DEFAULT_API_KEY_ENV = 'TRANSLATOR_API_KEY'\n\n/**\n * Resolve the API key: explicit `apiKey` wins, then a host-configured env var\n * name (`apiKeyEnv`), then the default `TRANSLATOR_API_KEY`.\n */\nfunction resolveApiKey(options: TranslatorOptions): string | undefined {\n if (options.apiKey) return options.apiKey\n const env = typeof process !== 'undefined' ? process.env : undefined\n if (!env) return undefined\n return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV]\n}\n\n// Optional glossary pair, included only when both terms are set. Shared by the\n// cache key and the request body so a forced request never collides with a plain one.\nfunction glossaryFields(t: TranslateRequestBody): Record<string, string> {\n const src = t[API_FIELD_GLOSSARY_SRC]\n const tgt = t[API_FIELD_GLOSSARY_TGT]\n return src && tgt ? { [API_FIELD_GLOSSARY_SRC]: src, [API_FIELD_GLOSSARY_TGT]: tgt } : {}\n}\n\n// --- Translation cache ---\nconst translationCache = new Map<string, Promise<TranslateServiceResponse[]>>()\n\nfunction getTranslationCacheKey(\n serviceTranslations: TranslateRequestBody[],\n options: TranslatorOptions\n): string {\n // Create a stable cache key based on translations and options\n return JSON.stringify({\n translations: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui',\n ...glossaryFields(t)\n })),\n host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,\n context: options[API_FIELD_CTX] || 'ui'\n })\n}\n\n/**\n * Translates a batch of texts\n *\n * @param translations Array of translation requests\n * @param options Configuration options\n * @returns Array of translated texts\n */\nexport async function translateBatch(\n translations: TranslateRequestBody[] = [],\n options: TranslatorOptions = {}\n): Promise<TranslateServiceResponse[]> {\n // const startTime = Date.now()\n\n // try {\n // console.debug('[translator-sdk] translateBatch called', {\n // count: translations.length,\n // sample: translations.slice(0, 3).map((t) => ({\n // text: t.text?.slice?.(0, 30) ?? '',\n // from: t.from_lang,\n // to: t.to_lang\n // })),\n // opts: { ...options }\n // })\n // } catch (_) {\n // // Ignore logging errors – never block translation\n // }\n\n if (!Array.isArray(translations) || translations.length === 0) {\n // console.error('[translator-sdk] Invalid translations input:', { translations })\n throw new Error('translations must be a non-empty array')\n }\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n\n // console.debug('[translator-sdk] Configuration', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // apiKeyLength: apiKey?.length || 0\n // })\n\n // Partition translations: identical language pairs can be returned as-is\n const serviceTranslations: TranslateRequestBody[] = []\n const indexMap: number[] = []\n\n translations.forEach((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) {\n // console.debug('[translator-sdk] Skipping translation (same language)', { from, to, text: tr.t?.slice(0, 20) })\n return // no need to translate\n }\n serviceTranslations.push(tr)\n indexMap.push(idx)\n })\n\n // console.debug('[translator-sdk] Translation partitioning', {\n // total: translations.length,\n // needsTranslation: serviceTranslations.length,\n // skipped: translations.length - serviceTranslations.length\n // })\n\n let serviceResults: TranslateServiceResponse[] = []\n if (serviceTranslations.length > 0) {\n // Check cache first\n const cacheKey = getTranslationCacheKey(serviceTranslations, options)\n if (translationCache.has(cacheKey)) {\n // console.debug('[translator-sdk] Cache hit for translation batch')\n serviceResults = await translationCache.get(cacheKey)!\n } else {\n // const requestStartTime = Date.now()\n // console.debug('[translator-sdk] Sending request to translator', {\n // host: translatorHost,\n // apiKeySnippet: apiKey ? `${apiKey.slice(0, 4)}…${apiKey.slice(-4)}` : 'none',\n // batchCount: serviceTranslations.length,\n // requestSize: JSON.stringify({ translations: serviceTranslations }).length\n // })\n\n const translationPromise = (async () => {\n try {\n // Fresh Turnstile token per flush (single-use); only the keyless\n // /frontend/translate proxy requires it, other hosts ignore the field.\n const turnstileToken = options.getTurnstileToken\n ? await options.getTurnstileToken()\n : undefined\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 5000) // 5s timeout\n\n const resp = await fetch(`${translatorHost}/translate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n },\n body: JSON.stringify({\n [API_FIELD_TRANSLATIONS]: serviceTranslations.map((t) => ({\n [API_FIELD_TEXT]: t[API_FIELD_TEXT],\n [API_FIELD_FROM]: t[API_FIELD_FROM],\n [API_FIELD_TO]: t[API_FIELD_TO],\n [API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || 'ui',\n ...glossaryFields(t)\n })),\n [API_FIELD_CTX]: options[API_FIELD_CTX] || 'ui',\n ...(turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {})\n }),\n signal: controller.signal\n })\n\n clearTimeout(timeoutId)\n // const requestDuration = Date.now() - requestStartTime\n\n // console.debug('[translator-sdk] Translator response received', {\n // status: resp.status,\n // statusText: resp.statusText,\n // duration: `${requestDuration}ms`,\n // contentType: resp.headers.get('content-type'),\n // contentLength: resp.headers.get('content-length')\n // })\n\n if (!resp.ok) {\n console.error(\n '[translator-sdk] Translator error response:',\n resp.status,\n resp.statusText\n )\n throw new Error(`Translator service error: ${resp.status} ${resp.statusText}`)\n }\n\n let parsed: {\n translations?: TranslateServiceResponse[]\n t?: string\n error?: string\n } | null\n try {\n parsed = await resp.clone().json()\n // console.debug('[translator-sdk] Response parsed successfully', {\n // hasTranslations: !!parsed?.translations,\n // translationCount: parsed?.translations?.length || 0,\n // hasLegacyT: !!parsed?.t\n // })\n } catch (jsonErr) {\n console.error('[translator-sdk] Failed to parse JSON response:', jsonErr)\n throw new Error(`Failed to parse JSON response: ${jsonErr}`)\n }\n\n // if (parsed) {\n // console.debug(\n // '[translator-sdk] Translator response sample',\n // JSON.stringify(parsed).slice(0, 500)\n // )\n // }\n\n const data = parsed || {}\n\n // Handle top-level error responses\n if (data[API_FIELD_ERROR]) {\n console.warn(`[translator-sdk] Top-level API error:`, data[API_FIELD_ERROR])\n\n // Create error responses for all translations\n return serviceTranslations.map((translation) => ({\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT],\n [API_FIELD_ERROR]: data[API_FIELD_ERROR]\n }))\n }\n\n const results = Array.isArray(data[API_FIELD_TRANSLATIONS])\n ? data[API_FIELD_TRANSLATIONS].map((translation, index) => {\n const originalText = serviceTranslations[index]?.[API_FIELD_TEXT] || ''\n\n // Handle error responses\n if (translation[API_FIELD_ERROR]) {\n console.warn(\n `[translator-sdk] Translation error for \"${originalText}\":`,\n translation[API_FIELD_ERROR]\n )\n return {\n [API_FIELD_TEXT]: originalText, // Return original text on error\n [API_FIELD_ERROR]: translation[API_FIELD_ERROR]\n }\n }\n\n // Handle language detection response\n if (translation[API_FIELD_FROM] && !translation[API_FIELD_TEXT]) {\n return {\n [API_FIELD_TEXT]: originalText, // For detection, return original text\n [API_FIELD_FROM]: translation[API_FIELD_FROM]\n }\n }\n\n // Handle translation response\n return {\n [API_FIELD_TEXT]: translation[API_FIELD_TEXT] || originalText\n }\n })\n : data[API_FIELD_TEXT]\n ? [{ [API_FIELD_TEXT]: data[API_FIELD_TEXT] }]\n : []\n\n // console.debug('[translator-sdk] Service results processed', {\n // resultCount: results.length,\n // expectedCount: serviceTranslations.length\n // })\n\n return results\n } catch (error: unknown) {\n // Remove from cache on error\n translationCache.delete(cacheKey)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const message = `Translator request to ${translatorHost} timed out after 5 seconds.`\n console.error(message)\n throw new Error(message)\n }\n const message = `Failed to connect to translator at ${translatorHost}. Is the service running?`\n console.error(message)\n throw new Error(message)\n }\n })()\n\n // Cache the promise\n translationCache.set(cacheKey, translationPromise)\n serviceResults = await translationPromise\n }\n }\n\n // Assemble final results in original order\n const final = translations.map((tr, idx) => {\n const from = (tr[API_FIELD_FROM] || '').toLowerCase()\n const to = (tr[API_FIELD_TO] || '').toLowerCase()\n if (from === to) return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n\n const svcIdx = indexMap.indexOf(idx)\n if (svcIdx !== -1) return serviceResults[svcIdx] || { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n return { [API_FIELD_TEXT]: tr[API_FIELD_TEXT] }\n })\n\n // const totalDuration = Date.now() - startTime\n // console.debug('[translator-sdk] translateBatch completed', {\n // totalDuration: `${totalDuration}ms`,\n // resultCount: final.length,\n // successCount: final.filter(r => r.translated && r.translated !== r.text).length\n // })\n\n return final as TranslateServiceResponse[]\n}\n\n// --- Cache management functions ---\nexport function clearTranslationCache(): void {\n translationCache.clear()\n}\n\nexport function getTranslationCacheSize(): number {\n return translationCache.size\n}\n\n/**\n * Vendor/legacy codes whose primary subtag isn't their BCP-47 base language.\n * Folding `kazlat` → `kk` treats Latin and Cyrillic Kazakh as siblings, so a\n * popularity-passing variant keeps its below-floor sibling visible (both scripts\n * stay selectable). It does NOT dedup them — both rows survive.\n */\nconst BASE_CODE_ALIASES: Record<string, string> = {\n kazlat: 'kk' // Kazakh (Latin) — vendor code, not \"kk-latn\"\n}\n\n/**\n * Reduce a language code to its BCP-47 primary subtag, so script/region variants\n * share a base: `sr-cyrl`/`sr-latn` → `sr`, `kk`/`kazlat` → `kk`.\n */\nexport function baseLanguageCode(code: string): string {\n const c = (code || '').trim().toLowerCase().replace(/_/g, '-')\n return BASE_CODE_ALIASES[c] ?? (c.split('-')[0] || c)\n}\n\n/**\n * Fetches available languages from the translation service\n *\n * @param options Configuration options\n * @returns Array of available languages\n */\nexport async function fetchAvailableLanguages(\n options: TranslatorOptions = {}\n): Promise<TranslatorLanguage[]> {\n // const startTime = Date.now()\n\n // console.debug('[translator-sdk] fetchAvailableLanguages called', { options })\n\n const translatorHost = options.translatorHost || DEFAULT_TRANSLATOR_HOST\n const apiKey = resolveApiKey(options)\n // Popularity filtering is opt-in. Default 0 returns the full catalog so the\n // caller (e.g. a searchable picker) gets every language and every script\n // variant; a host env var must not silently prune the list.\n const minPop = options.minPopularity !== undefined ? Number(options.minPopularity) : 0\n\n // console.debug('[translator-sdk] Languages request config', {\n // translatorHost,\n // hasApiKey: !!apiKey,\n // minPopularity: minPop,\n // country: options.country,\n // region: options.region\n // })\n\n const queryParams = []\n if (options.country) queryParams.push(`country=${encodeURIComponent(options.country)}`)\n if (options.region) queryParams.push(`region=${encodeURIComponent(options.region)}`)\n const queryStr = queryParams.length ? `?${queryParams.join('&')}` : ''\n\n const requestUrl = `${translatorHost}/languages${queryStr}`\n\n try {\n const resp = await fetch(requestUrl, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n ...(apiKey ? { Authorization: `Bearer ${apiKey}`, 'X-Api-Key': apiKey } : {})\n }\n })\n\n if (!resp.ok) {\n throw new Error(`Translator languages error: ${resp.status}`)\n }\n\n const data = await resp.json()\n const languages: TranslatorLanguage[] = Array.isArray(data) ? data : data.languages || []\n\n if (minPop <= 0) return languages\n\n const passes = (lang: TranslatorLanguage): boolean =>\n lang.popularity === undefined || lang.popularity === null || Number(lang.popularity) >= minPop\n\n // Keep every script/region variant of a base language that has at least one\n // passing entry, so a low-popularity variant (e.g. Kazakh Cyrillic with\n // popularity 0) survives next to its popular sibling and the UI can still\n // offer the \"which variant?\" choice. Otherwise a flat floor splits the pair.\n const keptBases = new Set(languages.filter(passes).map((l) => baseLanguageCode(l.code)))\n return languages.filter((lang) => passes(lang) || keptBases.has(baseLanguageCode(lang.code)))\n } catch (error) {\n console.error('[translator-sdk] Languages fetch error:', error)\n throw error\n }\n}\n"],"mappings":"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","names":["message"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* langie v1.
|
|
2
|
+
* langie v1.13.0
|
|
3
3
|
* (c) 2026 nlit
|
|
4
4
|
* @license Apache-2.0
|
|
5
5
|
*
|
|
@@ -87,6 +87,8 @@ var API_FIELD_TEXT = "t";
|
|
|
87
87
|
var API_FIELD_FROM = "from";
|
|
88
88
|
var API_FIELD_TO = "to";
|
|
89
89
|
var API_FIELD_CTX = "ctx";
|
|
90
|
+
var API_FIELD_GLOSSARY_SRC = "gs";
|
|
91
|
+
var API_FIELD_GLOSSARY_TGT = "gt";
|
|
90
92
|
var API_FIELD_TRANSLATED = "t";
|
|
91
93
|
var API_FIELD_TRANSLATIONS = "translations";
|
|
92
94
|
var API_FIELD_ERROR = "error";
|
|
@@ -101,6 +103,11 @@ function resolveApiKey(options) {
|
|
|
101
103
|
if (!env) return void 0;
|
|
102
104
|
return env[options.apiKeyEnv || DEFAULT_API_KEY_ENV];
|
|
103
105
|
}
|
|
106
|
+
function glossaryFields(t) {
|
|
107
|
+
const src = t[API_FIELD_GLOSSARY_SRC];
|
|
108
|
+
const tgt = t[API_FIELD_GLOSSARY_TGT];
|
|
109
|
+
return src && tgt ? { [API_FIELD_GLOSSARY_SRC]: src, [API_FIELD_GLOSSARY_TGT]: tgt } : {};
|
|
110
|
+
}
|
|
104
111
|
var translationCache = /* @__PURE__ */ new Map();
|
|
105
112
|
function getTranslationCacheKey(serviceTranslations, options) {
|
|
106
113
|
return JSON.stringify({
|
|
@@ -108,7 +115,8 @@ function getTranslationCacheKey(serviceTranslations, options) {
|
|
|
108
115
|
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
109
116
|
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
110
117
|
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
111
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
118
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui",
|
|
119
|
+
...glossaryFields(t)
|
|
112
120
|
})),
|
|
113
121
|
host: options.translatorHost || DEFAULT_TRANSLATOR_HOST,
|
|
114
122
|
context: options[API_FIELD_CTX] || "ui"
|
|
@@ -153,7 +161,8 @@ async function translateBatch(translations2 = [], options = {}) {
|
|
|
153
161
|
[API_FIELD_TEXT]: t[API_FIELD_TEXT],
|
|
154
162
|
[API_FIELD_FROM]: t[API_FIELD_FROM],
|
|
155
163
|
[API_FIELD_TO]: t[API_FIELD_TO],
|
|
156
|
-
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui"
|
|
164
|
+
[API_FIELD_CTX]: t[API_FIELD_CTX] || options[API_FIELD_CTX] || "ui",
|
|
165
|
+
...glossaryFields(t)
|
|
157
166
|
})),
|
|
158
167
|
[API_FIELD_CTX]: options[API_FIELD_CTX] || "ui",
|
|
159
168
|
...turnstileToken ? { [API_FIELD_TURNSTILE]: turnstileToken } : {}
|
|
@@ -2039,6 +2048,10 @@ function displayNative(name = "", native = "") {
|
|
|
2039
2048
|
return name !== native && TRAILING_PAREN.test(name) && TRAILING_PAREN.test(native) ? native.replace(TRAILING_PAREN, "") : native;
|
|
2040
2049
|
}
|
|
2041
2050
|
|
|
2051
|
+
// src/utils/flagUrl.ts
|
|
2052
|
+
var isReadyFlagUrl = (flagCode) => flagCode.startsWith("/") || /^https?:\/\//i.test(flagCode);
|
|
2053
|
+
var flagImageUrl = (flagCode) => isReadyFlagUrl(flagCode) ? flagCode : `/flags/${flagCode}.svg`;
|
|
2054
|
+
|
|
2042
2055
|
// src/constants/colors.ts
|
|
2043
2056
|
var COLORS = {
|
|
2044
2057
|
// Primary colors
|
|
@@ -2133,14 +2146,14 @@ var _hoisted_1 = {
|
|
|
2133
2146
|
key: 0,
|
|
2134
2147
|
class: "multiselect-single-label"
|
|
2135
2148
|
};
|
|
2136
|
-
var _hoisted_2 = ["src", "alt"];
|
|
2149
|
+
var _hoisted_2 = ["src", "alt", "data-custom-flag"];
|
|
2137
2150
|
var _hoisted_3 = { class: "language-text" };
|
|
2138
2151
|
var _hoisted_4 = {
|
|
2139
2152
|
key: 0,
|
|
2140
2153
|
class: "native-name"
|
|
2141
2154
|
};
|
|
2142
2155
|
var _hoisted_5 = ["data-lang-code"];
|
|
2143
|
-
var _hoisted_6 = ["src", "alt"];
|
|
2156
|
+
var _hoisted_6 = ["src", "alt", "data-custom-flag"];
|
|
2144
2157
|
var _hoisted_7 = { class: "language-text" };
|
|
2145
2158
|
var _hoisted_8 = {
|
|
2146
2159
|
key: 0,
|
|
@@ -2208,10 +2221,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue3.defineComponent)({
|
|
|
2208
2221
|
const flagCode = lang.flag_country || lang.code;
|
|
2209
2222
|
return flagCode;
|
|
2210
2223
|
};
|
|
2211
|
-
const getFlagImageUrl = (lang) =>
|
|
2212
|
-
const flagCode = getFlagCode(lang);
|
|
2213
|
-
return `/flags/${flagCode}.svg`;
|
|
2214
|
-
};
|
|
2224
|
+
const getFlagImageUrl = (lang) => flagImageUrl(getFlagCode(lang));
|
|
2215
2225
|
const props = __props;
|
|
2216
2226
|
const emit = __emit;
|
|
2217
2227
|
const multiselectRef = (0, import_vue5.ref)(null);
|
|
@@ -2336,6 +2346,10 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue3.defineComponent)({
|
|
|
2336
2346
|
const onFlagError = (event) => {
|
|
2337
2347
|
const target = event.target;
|
|
2338
2348
|
const currentSrc = target.src;
|
|
2349
|
+
if (target.dataset.customFlag) {
|
|
2350
|
+
target.style.display = "none";
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2339
2353
|
if (currentSrc && currentSrc.includes("/flags/")) {
|
|
2340
2354
|
const match = currentSrc.match(/\/flags\/([a-z]{2})\.svg/);
|
|
2341
2355
|
if (match) {
|
|
@@ -2385,6 +2399,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue3.defineComponent)({
|
|
|
2385
2399
|
src: getFlagImageUrl(value),
|
|
2386
2400
|
class: "lang-flag",
|
|
2387
2401
|
alt: `${value.native_name || value.name} flag`,
|
|
2402
|
+
"data-custom-flag": (0, import_vue4.unref)(isReadyFlagUrl)(getFlagCode(value)) || void 0,
|
|
2388
2403
|
onError: onFlagError
|
|
2389
2404
|
}, null, 40, _hoisted_2)) : (0, import_vue4.createCommentVNode)("v-if", true),
|
|
2390
2405
|
(0, import_vue4.createElementVNode)("span", _hoisted_3, [
|
|
@@ -2413,6 +2428,7 @@ var LanguageSelect_default = /* @__PURE__ */ (0, import_vue3.defineComponent)({
|
|
|
2413
2428
|
src: getFlagImageUrl(option),
|
|
2414
2429
|
class: "lang-flag",
|
|
2415
2430
|
alt: `${option.name} flag`,
|
|
2431
|
+
"data-custom-flag": (0, import_vue4.unref)(isReadyFlagUrl)(getFlagCode(option)) || void 0,
|
|
2416
2432
|
onError: onFlagError
|
|
2417
2433
|
}, null, 40, _hoisted_6)),
|
|
2418
2434
|
(0, import_vue4.createElementVNode)("span", _hoisted_7, [
|