-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtranslate.js
More file actions
136 lines (119 loc) · 4.32 KB
/
translate.js
File metadata and controls
136 lines (119 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// translate.js — DeepL wrapper (ESM)
// Krav: Node 18+ (global fetch), ENV: TRANSLATE_PROVIDER=deepl, TRANSLATE_API_KEY=...
const PROVIDER = (process.env.TRANSLATE_PROVIDER || '').toLowerCase();
const API_KEY = process.env.TRANSLATE_API_KEY || '';
// DeepL: free-nycklar börjar ofta med "dlk_" och använder api-free.deepl.com
const DEEPL_BASE =
(API_KEY && API_KEY.startsWith('dlk_'))
? 'https://api-free.deepl.com/v2/translate'
: 'https://api.deepl.com/v2/translate';
// Enkel minnescache för att undvika dubblett-översättningar under samma processkörning
const cache = new Map();
/** Mappar typiska språkalias -> DeepL-koder */
function normalizeLang(lang) {
if (!lang) return null;
const l = String(lang).toLowerCase().trim();
const map = {
sv: 'SV', se: 'SV', 'sv-se': 'SV',
en: 'EN', 'en-gb': 'EN-GB', 'en-us': 'EN-US'
};
return map[l] || l.toUpperCase();
}
/** Liten helper för www-form-urlencoded body */
function formEncode(params) {
const usp = new URLSearchParams();
Object.entries(params).forEach(([k, v]) => {
if (Array.isArray(v)) v.forEach(x => usp.append(k, x));
else if (v !== undefined && v !== null) usp.append(k, String(v));
});
return usp.toString();
}
/**
* translateText
* @param {string|string[]} text - sträng eller array av strängar
* @param {string} targetLang - t.ex. 'EN' eller 'SV'
* @param {string} [sourceLang] - t.ex. 'SV' (valfritt, autodetect annars)
* @returns {Promise<string|string[]>}
*/
export async function translateText(text, targetLang, sourceLang) {
if (PROVIDER !== 'deepl') {
throw new Error('TRANSLATE_PROVIDER är inte satt till "deepl".');
}
if (!API_KEY) {
throw new Error('TRANSLATE_API_KEY saknas.');
}
const target = normalizeLang(targetLang);
const source = normalizeLang(sourceLang);
if (!text || (Array.isArray(text) && text.length === 0)) return text;
const items = Array.isArray(text) ? text : [text];
// Cache-nycklar
const cacheKeys = items.map(t => `deepl:${source||'auto'}>${target}|${t}`);
const results = new Array(items.length).fill(null);
// 1) Plocka ur cache först
const toTranslate = [];
const toTranslateIdx = [];
cacheKeys.forEach((key, i) => {
if (cache.has(key)) {
results[i] = cache.get(key);
} else {
toTranslate.push(items[i]);
toTranslateIdx.push(i);
}
});
// 2) Inget kvar att översätta? returnera direkt
if (toTranslate.length === 0) {
return Array.isArray(text) ? results : results[0];
}
// 3) Gör anrop till DeepL (batch i ett request – DeepL stödjer flera "text" fält)
const body = {
auth_key: API_KEY,
target_lang: target,
// source_lang: source (bara sätt om användaren skickat med något)
text: toTranslate,
// Kvalitet/stil (valfritt): preserve_formatting=1 håller radbrytningar bättre
preserve_formatting: 1
};
if (source) body.source_lang = source;
let resp;
try {
resp = await fetch(DEEPL_BASE, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: formEncode(body),
});
} catch (e) {
throw new Error(`Kunde inte nå DeepL: ${e.message}`);
}
if (!resp.ok) {
// Försök få mer info från DeepL
let detail = '';
try { detail = await resp.text(); } catch {}
throw new Error(`DeepL HTTP ${resp.status}: ${detail || resp.statusText}`);
}
const data = await resp.json();
if (!data || !Array.isArray(data.translations)) {
throw new Error('Oväntat svar från DeepL.');
}
// 4) Lägg tillbaka översättningar i rätt index + cacha
data.translations.forEach((tr, j) => {
const idx = toTranslateIdx[j];
const out = tr.text;
results[idx] = out;
cache.set(cacheKeys[idx], out);
});
return Array.isArray(text) ? results : results[0];
}
/**
* Hjälpmetod: översätt bara om texten verkar vara på fel språk.
* (Mycket enkel heuristik – bra för UI-snuttar.)
*/
export async function translateIfNeeded(text, targetLang, sourceLangGuess) {
// Här kan du bygga in en riktig språkdetektor senare.
// Just nu: om sourceLangGuess redan matchar targetLang → returnera text.
const tgt = normalizeLang(targetLang);
const src = normalizeLang(sourceLangGuess);
if (tgt && src && tgt.split('-')[0] === src.split('-')[0]) {
return text;
}
return translateText(text, targetLang, sourceLangGuess);
}