Public
Edited
Dec 21, 2023
Insert cell
Insert cell
render((_) => jsx`<${TranslationApp} />`)
Insert cell
async function translateText(text, sourceLang, targetLang) {
console.log("called to translate", text, sourceLang, targetLang,Date.now());
const response = await fetch("https://libretranslate.de/translate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
q: text,
source: sourceLang,
target: targetLang
})
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
return data.translatedText;
}
Insert cell
function TranslationComponent({
textToTranslate,
sourceLanguage,
targetLanguages
}) {
const [translations, setTranslations] = useState({});

useEffect(() => {
targetLanguages.forEach((lang) => {
cachedExpFetchTranslate(textToTranslate, sourceLanguage, lang).then(
(translation) => {
setTranslations((prevTranslations) => ({
...prevTranslations,
[lang]: translation
}));
}
);
});
}, [textToTranslate, sourceLanguage, targetLanguages]);

return jsx`
<div>
<ul>
${targetLanguages.map(
(lang) => jsx`
<li key=${lang}>${lang.toUpperCase()}: ${
translations[lang] || "Loading..."
}</li>
`
)}
</ul>
</div>
`;
}
Insert cell
throttledFetchTranslation = throttle(fetchTranslation, 300)
Insert cell
async function fetchTranslation(text, sourceLang, targetLang) {
console.log("called to translate", text, sourceLang, targetLang, Date.now());
try {
const response = await fetch("https://libretranslate.de/translate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
q: text,
source: sourceLang,
target: targetLang
})
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
return data.translatedText;
} catch (error) {
console.error(error);
return `Error: ${error.message}`;
}
}
Insert cell
import {
render,
jsx,
component,
useState,
useEffect,
useRef
} from "@j-f1/react"
Insert cell
function throttle(func, delay) {
let lastExecutionTime = 0;
let timer = null;
let pendingCalls = [];

const executeFunction = (resolve, reject, context, args) => {
lastExecutionTime = Date.now();
Promise.resolve()
.then(() => func.apply(context, args))
.then(resolve)
.catch(reject);
};

const scheduler = () => {
const now = Date.now();
const nextExecutionTime = lastExecutionTime + delay;
const waitTime = Math.max(nextExecutionTime - now, 0);

if (waitTime <= 0 && pendingCalls.length > 0) {
const [resolve, reject, context, args] = pendingCalls.shift();
executeFunction(resolve, reject, context, args);
}

if (pendingCalls.length > 0) {
timer = setTimeout(scheduler, waitTime);
} else {
timer = null;
}
};

return function (...args) {
return new Promise((resolve, reject) => {
pendingCalls.push([resolve, reject, this, args]);

if (!timer) {
timer = setTimeout(scheduler, delay);
}
});
};
}
Insert cell
function TranslationForm({
inputText,
onInputTextChange,
sourceLanguage,
onSourceLanguageChange,
targetLanguages,
onTargetLanguagesChange
}) {
const availableLanguages = ["en", "es", "de", "it", "fr", "tr"];

//const debouncedInputTextChange = debounce(onInputTextChange, 1500); // 500ms delay

const handleTextChange = (event) => {
onInputTextChange(event.target.value);
};

const handleSourceLanguageChange = (event) => {
onSourceLanguageChange(event.target.value);
onTargetLanguagesChange(
targetLanguages.filter((lang) => lang !== event.target.value)
);
};

const handleTargetLanguageChange = (language, isChecked) => {
const newTargetLanguages = isChecked
? [...targetLanguages, language]
: targetLanguages.filter((lang) => lang !== language);
onTargetLanguagesChange(newTargetLanguages);
};

return jsx`
<div>
<textarea value=${inputText} onChange=${handleTextChange} />
<br />
<select value=${sourceLanguage} onChange=${handleSourceLanguageChange}>
${availableLanguages.map(
(lang) => jsx`<option value=${lang}>${lang.toUpperCase()}</option>`
)}
</select>
<br />
${availableLanguages
.filter((lang) => lang !== sourceLanguage)
.map(
(lang) => jsx`
<label>
<input
type="checkbox"
checked=${targetLanguages.includes(lang)}
onChange=${(e) =>
handleTargetLanguageChange(lang, e.target.checked)}
/>
${lang.toUpperCase()}
</label>
`
)}
</div>
`;
}
Insert cell
function TranslationApp() {
const [inputText, setInputText] = useState("Hello!");
const [debouncedInputText, setDebouncedInputText] = useState(inputText);
const [sourceLanguage, setSourceLanguage] = useState("en");
const [targetLanguages, setTargetLanguages] = useState([
"es",
"de",
"it",
"fr"
]);

// Debounce input text
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedInputText(inputText);
}, 500); // 500ms delay

return () => clearTimeout(timer);
}, [inputText]);

return jsx`
<div>
<${TranslationForm}
inputText=${inputText}
onInputTextChange=${setInputText}
sourceLanguage=${sourceLanguage}
onSourceLanguageChange=${setSourceLanguage}
targetLanguages=${targetLanguages}
onTargetLanguagesChange=${setTargetLanguages}
/>
<${TranslationComponent}
textToTranslate=${debouncedInputText}
sourceLanguage=${sourceLanguage}
targetLanguages=${targetLanguages}
/>
</div>
`;
}
Insert cell
function exponentialBackoffWrapper(
throttledFunc,
baseDelay = 5000,
maxDelay = 1200000
) {
let currentDelay = baseDelay;

async function wrappedFunc(...args) {
while (true) {
try {
const result = await throttledFunc(...args);
console.log("giving result back", result);
if (result == "Error: HTTP error! status: 429") {
throw { status: 429 };
}
currentDelay = baseDelay; // Reset delay after a successful call
return result;
} catch (error) {
if (error.status === 429) {
console.log("Retrying because of 429");
if (currentDelay > maxDelay) {
throw new Error("Maximum wait time exceeded");
}
await wait(currentDelay);
currentDelay *= 2; // Exponentially increase delay
} else {
throw error; // Rethrow other errors
}
}
}
}

return wrappedFunc;
}
Insert cell
function debounce(func, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
Insert cell
function wait(ms) {
return new Promise((resolve) => setTimeout(resolve, ms * Math.random()));
}
Insert cell
expFetchTranslate = exponentialBackoffWrapper(throttledFetchTranslation)
Insert cell
console.log("hi")
Insert cell
throttle(() => Promise.resolve("hi"), 0)()
Insert cell
async function cachedExpFetchTranslate(text, sourceLang, targetLang) {
async function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("TranslationCacheDB", 1);

request.onerror = (event) => {
reject("IndexedDB error: ", event.target.errorCode);
};

request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore("translations", { keyPath: "key" });
};

request.onsuccess = (event) => {
resolve(event.target.result);
};
});
}

async function getCachedTranslation(db, key) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["translations"], "readonly");
const store = transaction.objectStore("translations");
const request = store.get(key);

request.onsuccess = (event) => {
if (event.target.result) {
resolve(event.target.result.translation);
} else {
resolve(null);
}
};

request.onerror = (event) => {
reject("Error in retrieving from IndexedDB: ", event.target.errorCode);
};
});
}

async function cacheTranslation(db, key, translation) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(["translations"], "readwrite");
const store = transaction.objectStore("translations");
const request = store.put({ key, translation });

request.onsuccess = () => {
resolve();
};

request.onerror = (event) => {
reject("Error in saving to IndexedDB: ", event.target.errorCode);
};
});
}
const db = await openDatabase();
const key = `${text}-${sourceLang}-${targetLang}`;
const cachedTranslation = await getCachedTranslation(db, key);

if (cachedTranslation) {
return cachedTranslation;
} else {
const translation = await expFetchTranslate(text, sourceLang, targetLang);
await cacheTranslation(db, key, translation);
return translation;
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more