//HTML and CSS to make website look nice
document.write(`
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
color: #333;
text-align: center;
}
.container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 2rem;
color: #F67280;
}
.section {
margin-top: 20px;
padding: 20px;
border-radius: 8px;
background-color: #F8B195;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h3 {
color: #fff;
}
input[type="password"],
input[type="text"],
select {
width: calc(100% - 20px);
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
button {
background-color: #6C5B7B;
color: #fff;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin: 5px;
}
button:hover {
background-color: #355C7D;
}
#apiStatus,
#rankingsResult {
margin-top: 10px;
font-size: 14px;
color: #333;
}
.translation-result {
text-align: left;
background: #fff;
padding: 10px;
margin-top: 10px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.translation-result strong {
display: block;
margin-bottom: 5px;
}
</style>
<div class="container">
<h1>Translation Comparison</h1>
<p>Welcome to the Translation Comparison tool. Enter your text in any language and select your target language!</p>
<div class="section">
<h3>API Setup</h3>
<input type="password" id="lectoApiKey" placeholder="Lecto API Key">
<input type="password" id="googleApiKey" placeholder="Google Cloud API Key">
<input type="password" id="openAiApiKey" placeholder="OpenAI API Key">
<div>
<button onclick="setApiKeys();">Set API Keys</button>
<button onclick="runApiTests();">Run Automated Tests</button>
</div>
<div id="apiStatus"></div>
</div>
<div class="section">
<h3>Translation Options</h3>
<label for="languageSelect">Translate to:</label>
<select id="languageSelect">
<option value="EN">English</option>
<option value="ES">Spanish</option>
<option value="FR">French</option>
<option value="DE">German</option>
<option value="IT">Italian</option>
<option value="PT">Portuguese</option>
<option value="RU">Russian</option>
<option value="JA">Japanese</option>
<option value="ZH">Chinese (Simplified)</option>
</select>
<input type="text" id="sourceText" placeholder="Type text in any language">
<button onclick="sendTranslation();">Translate</button>
</div>
<div class="section">
<h3>Translations</h3>
<div class="translation-result" id="lectoTranslation">
<strong>Lecto Translation:</strong>
<div id="lectoResult"></div>
</div>
<div class="translation-result" id="googleTranslation">
<strong>Google Cloud Translation:</strong>
<div id="googleResult"></div>
</div>
<div class="translation-result" id="openAiTranslation">
<strong>OpenAI Translation:</strong>
<div id="openAiResult"></div>
</div>
<button onclick="rankResults();">Rank Results</button>
<div id="rankingsResult"></div>
</div>
</div>
`);
// Global varaibles to store API keys
var lectoApiKey = '';
var googleApiKey = '';
var openAiApiKey = '';
// Set API keys from input fields
function setApiKeys() {
var lectoKey = document.getElementById('lectoApiKey').value.trim();
var googleKey = document.getElementById('googleApiKey').value.trim();
var openAiKey = document.getElementById('openAiApiKey').value.trim();
var statusDiv = document.getElementById('apiStatus');
statusDiv.innerHTML = ''; // Clear previous status
// Check and Lecto API Key
if (lectoKey) {
lectoApiKey = lectoKey;
statusDiv.innerHTML += `<span style="color: green;">Lecto API key set! </span>`;
} else {
statusDiv.innerHTML += `<span style="color: red;">Please enter a valid Lecto API key. </span>`;
}
// Check and Google Cloud API Key
if (googleKey) {
googleApiKey = googleKey;
statusDiv.innerHTML += `<span style="color: green;">Google Cloud API key set! </span>`;
} else {
statusDiv.innerHTML += `<span style="color: red;">Please enter a valid Google Cloud API key. </span>`;
}
// Check and set OpenAI API key
if (openAiKey) {
openAiApiKey = openAiKey;
statusDiv.innerHTML += `<span style="color: green;">OpenAI API key set! </span>`;
} else {
statusDiv.innerHTML += `<span style="color: red;">Please enter a valid OpenAI API key. </span>`;
}
}
// Send translations requests to multiple services
function sendTranslation() {
// Reset previous results
document.getElementById('lectoResult').innerHTML = '';
document.getElementById('googleResult').innerHTML = '';
document.getElementById('openAiResult').innerHTML = '';
// Validate inputs
var sourceText = document.getElementById('sourceText').value.trim();
var targetLang = document.getElementById('languageSelect').value;
if (!sourceText) {
alert('Please enter text to translate.');
return;
}
// Translate with Lecto if API key is set
if (lectoApiKey) {
translateWithLecto(sourceText, targetLang);
} else {
document.getElementById('lectoResult').innerHTML =
'<span style="color: red; font-weight: bold;">Lecto API key not set</span>';
}
// Translate with Google Cloud if API key is set
if (googleApiKey) {
translateWithGoogleCloud(sourceText, targetLang);
} else {
document.getElementById('googleResult').innerHTML =
'<span style="color: red; font-weight: bold;">Google Cloud API key not set</span>';
}
// Translate with OPENAI if API Key is set
if (openAiApiKey) {
translateWithOpenAI(sourceText, targetLang);
} else {
document.getElementById('openAiResult').innerHTML =
'<span style="color: red; font-weight: bold;">OpenAI API key not set</span>';
}
}
// Generate n-grams from a list of tokens
function ngrams(tokens, n) {
// Create n-grams of specified length from token array
return Array.from({ length: Math.max(0, tokens.length - n + 1) }, (_, i) => tokens.slice(i, i + n).join(' '));
}
// Calculate modified precision for n-grams
function modifiedPrecision(candidateNgrams, referenceNgrams) {
// Count occurences of reference n-grams
const referenceCounts = {};
referenceNgrams.forEach(ng => referenceCounts[ng] = (referenceCounts[ng] || 0) + 1);
// Count matches between candidate and reference n-grams
let matches = 0;
candidateNgrams.forEach(ng => {
if (referenceCounts[ng]) {
matches += 1;
referenceCounts[ng] -= 1;
}
});
// Return precision (matches divided by candidate n-grams)
return matches / candidateNgrams.length || 0;
}
// Calculate BLEU score for translation quality
function calculateBLEU(reference, candidate) {
//Tokenize reference and candidate texts
const candidateTokens = candidate.split(' ');
const referenceTokens = reference.split(' ');
// Calcualte precision for n-grams from 1 to 4
const precisions = [];
for (let n = 1; n <= 4; n++) {
const candidateNgrams = ngrams(candidateTokens, n);
const referenceNgrams = ngrams(referenceTokens, n);
precisions.push(modifiedPrecision(candidateNgrams, referenceNgrams));
}
// Calculate geometric mean of precisions
const geometricMean = Math.exp(precisions.reduce((sum, p) => sum + (p > 0 ? Math.log(p) : 0), 0) / 4);
// Apply brevity penalty to handle short translations
const brevityPenalty = candidateTokens.length < referenceTokens.length
? Math.exp(1 - referenceTokens.length / candidateTokens.length)
: 1;
//Return final BLEU score
return brevityPenalty * geometricMean;
}
function translateWithLecto(text, targetLang) {
const url = 'https://api.lecto.ai/v1/translate/text';
const sourceLang = 'en'; //English set as default because Lecto requires input for sourceLang
if (!lectoApiKey) {
document.getElementById('lectoResult').innerHTML =
'<span style="color: red;">Lecto API key not set</span>';
return Promise.reject("Lecto API key is not set.");
}
const data = JSON.stringify({
texts: [text],
to: [targetLang],
from: sourceLang
});
return fetch(url, {
method: 'POST',
headers: {
'X-API-Key': lectoApiKey,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: data
})
.then(response => {
if (!response.ok) {
throw new Error(`Lecto API error: ${response.statusText}`);
}
return response.json();
})
.then(response => {
if (response && response.translations) {
const translatedText = response.translations[0].translated[0];
document.getElementById('lectoResult').innerHTML = translatedText;
return translatedText;
} else {
const errorMsg = "Lecto: Translation not found in response.";
document.getElementById('lectoResult').innerHTML =
'<span style="color: red;">' + errorMsg + '</span>';
throw new Error(errorMsg);
}
})
.catch(error => {
const errorMsg = `Lecto API Error: ${error.message}`;
document.getElementById('lectoResult').innerHTML =
'<span style="color: red;">Error contacting Lecto API</span>';
throw new Error(errorMsg);
});
}
// Translate text using Google Cloud Translation API
function translateWithGoogleCloud(text, targetLang) {
const url = `https://translation.googleapis.com/language/translate/v2?key=${googleApiKey}`;
// Check if Google API key is set
if (!googleApiKey) {
document.getElementById('googleResult').innerHTML =
'<span style="color: red;">Google API key not set</span>';
return Promise.reject("Google API key is not set.");
}
// Prepare request payload
const data = {
q: text,
target: targetLang
};
// Fetch translation from Google Cloud API
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => {
if (!response.ok) {
throw new Error(`Google API error: ${response.statusText}`);
}
return response.json();
})
.then(data => {
const translatedText = data.data.translations[0].translatedText;
document.getElementById('googleResult').innerHTML = translatedText;
return translatedText;
})
.catch(error => {
const errorMsg = `Google API Error: ${error.message}`;
document.getElementById('googleResult').innerHTML =
'<span style="color: red;">Error contacting Google Cloud Translation API</span>';
throw new Error(errorMsg);
});
}
function translateWithOpenAI(text, targetLang) {
const url = 'https://api.openai.com/v1/chat/completions';
if (!openAiApiKey) {
document.getElementById('openAiResult').innerHTML =
'<span style="color: red;">OpenAI API key not set</span>';
return Promise.reject("OpenAI API key is not set.");
}
const payload = {
model: 'gpt-3.5-turbo',
messages: [
{ role: "system", content: `Translate to ${targetLang}` },
{ role: "user", content: text }
],
max_tokens: 300,
temperature: 0.7
};
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${openAiApiKey}`
},
body: JSON.stringify(payload)
})
.then(response => {
if (!response.ok) {
throw new Error(`OpenAI API error: ${response.statusText}`);
}
return response.json();
})
.then(data => {
const translatedText = data.choices[0].message.content.trim();
document.getElementById('openAiResult').innerHTML = translatedText;
return translatedText;
})
.catch(error => {
const errorMsg = `OpenAI API Error: ${error.message}`;
document.getElementById('openAiResult').innerHTML =
'<span style="color: red;">Error contacting OpenAI API</span>';
throw new Error(errorMsg);
});
}
// Rank translations based on quality and naturalness
async function rankResults() {
// Get translations from the page
const lectoResult = document.getElementById('lectoResult').innerText.trim();
const googleResult = document.getElementById('googleResult').innerText.trim();
const openAiResult = document.getElementById('openAiResult').innerText.trim();
const sourceText = document.getElementById('sourceText').value.trim();
const targetLang = document.getElementById('languageSelect').value;
// Validate inputs
if (!sourceText) {
alert('Please enter text to translate.');
return;
}
if (!lectoResult || !googleResult || !openAiResult) {
alert('Make sure all translations are available before ranking.');
return;
}
try {
// Call OpenAI to rank translations
const ranking = await rankTranslations({
sourceText,
targetLang,
translations: [
{ name: 'Lecto', text: lectoResult },
{ name: 'Google Cloud', text: googleResult },
{ name: 'OpenAI', text: openAiResult },
],
});
// Display rankings
document.getElementById('rankingsResult').innerHTML = `
<h3>Rankings</h3>
<strong>Most Human:</strong> ${ranking.mostHuman.name}<br>
<strong>Most Accurate:</strong> ${ranking.mostAccurate.name}<br>
<strong>Best Overall:</strong> ${ranking.bestOverall.name}<br>
`;
} catch (error) {
console.error('Ranking Error:', error);
document.getElementById('rankingsResult').innerHTML = `
<span style="color: red;">Error getting rankings from OpenAI</span>
`;
}
}
// OpenAI ranks translations accross different criteria
async function rankTranslations({ sourceText, targetLang, translations }) {
const url = 'https://api.openai.com/v1/chat/completions';
if (!openAiApiKey) {
throw new Error('OpenAI API key not set');
}
//Prompt for translation ranking. Code names for Lecto, Google Cloud and OpenAI to prevent bias.
const prompt = `
You are a multilingual expert. Below are translations of a text into ${targetLang}.
Rank them in terms of:
1. "Most Human": Which translation sounds the most natural and human-like?
2. "Most Accurate": Which translation is closest to what the source text means in ${targetLang}?
3. "Best Overall": The overall best translation based on naturalness and accuracy.
Source Text: "${sourceText}"
Translations:
- L: "${translations[0].text}"
- GC: "${translations[1].text}"
- O: "${translations[2].text}"
Provide your rankings as a VALID JSON object. Return STRICTLY in this format:
{
"mostHuman": { "name": "L|GC|O", "reason": "..." },
"mostAccurate": { "name": "L|GC|O", "reason": "..." },
"bestOverall": { "name": "L|GC|O", "reason": "..." }
}
`;
// Make the API call to OpenAI
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${openAiApiKey}`,
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: 'You are an expert in translation evaluation. ONLY respond in the specified JSON format.' },
{ role: 'user', content: prompt },
],
max_tokens: 300,
temperature: 0,
}),
});
//Handle OpenAI response errors
if (!response.ok) {
throw new Error(`OpenAI API error: ${response.statusText}`);
}
const data = await response.json();
const content = data.choices[0].message.content.trim();
console.log('Full OpenAI Response:', content);
// Extract JSON from code block if present
const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/);
const jsonContent = jsonMatch ? jsonMatch[1] : content;
try {
// Attempt to parse JSON, with more flexible parsing
const ranking = JSON.parse(jsonContent.replace(/[\r\n\t]/g, ''));
// Validate the ranking object structure
if (!ranking.mostHuman || !ranking.mostAccurate || !ranking.bestOverall) {
throw new Error('Invalid JSON structure');
}
return ranking;
} catch (error) {
console.error('Parsing Error:', error);
console.error('Problematic Content:', jsonContent);
// Fallback ranking if parsing fails
return {
mostHuman: { name: 'Unable to determine', reason: 'JSON parsing failed' },
mostAccurate: { name: 'Unable to determine', reason: 'JSON parsing failed' },
bestOverall: { name: 'Unable to determine', reason: 'JSON parsing failed' }
};
}
}
function compareBLEU(reference, translation) {
const bleuScore = calculateBLEU(reference, translation);
return bleuScore;
}
async function runApiTests() {
const testCases = [
{
source: "Hello, how are you?",
reference: "Hola, ¿cómo estás?",
targetLang: "es"
},
{
source: "The quick brown fox jumps over the lazy dog",
reference: "El rápido zorro marrón salta sobre el perro perezoso",
targetLang: "es"
},
{
source: "Where is the nearest train station?",
reference: "¿Dónde está la estación de tren más cercana?",
targetLang: "es"
},
{
source: "Hello, how are you?",
reference: "你好,你好吗?",
targetLang: "zh"
},
{
source: "The quick brown fox jumps over the lazy dog",
reference: "敏捷的棕色狐狸跳过了懒狗。",
targetLang: "zh"
},
{
source: "Where is the nearest train station?",
reference: "最近的火车站在哪里?",
targetLang: "zh"
},
{
source: `What these things have in common is that kids will take a chance.
If they don't know, they'll have a go. Am I right? They're not frightened of being wrong. I don't mean to say that being wrong is the same thing as being creative. What we do know is, if you're not prepared to be wrong,
you'll never come up with anything original -- if you're not prepared to be wrong. And by the time they get to be adults, most kids have lost that capacity. They have become frightened of being wrong. And we run our companies like this. We stigmatize mistakes. And we're now running national education systems where mistakes are the worst thing you can make. And the result is that we are educating people
out of their creative capacities.`,
reference: `Lo que estas cosas tienen en común, es que los niños se arriesgan.
Si no saben, prueban. ¿Verdad? No tienen miedo a equivocarse. Ahora, no estoy diciendo que equivocarse es lo mismo que ser creativo. Lo que sí sabemos es que, si no estás abierto a equivocarte, nunca se te va a ocurrir algo original. Si no estás abierto a equivocarte. Y para cuando llegan a ser adultos, la mayoría de los niños ha perdido esa capacidad. Tienen miedo a equivocarse. Y manejamos nuestras empresas así. Estigmatizamos los errores. Y ahora estamos administrando sistemas nacionales de educación donde los errores son lo peor que puedes hacer. Y el resultado es que estamos educando a la gente para que dejen sus capacidades creativas.`,
targetLang: "es"
},
{
source: `What these things have in common is that kids will take a chance.
If they don't know, they'll have a go. Am I right? They're not frightened of being wrong. I don't mean to say that being wrong is the same thing as being creative. What we do know is, if you're not prepared to be wrong,
you'll never come up with anything original -- if you're not prepared to be wrong. And by the time they get to be adults, most kids have lost that capacity. They have become frightened of being wrong. And we run our companies like this. We stigmatize mistakes. And we're now running national education systems where mistakes are the worst thing you can make. And the result is that we are educating people
out of their creative capacities.`,
reference: `以上例子的共同点就是孩子们愿意冒险。
对于未知的事物,他们愿意去尝试。
难道不是吗? 即使尝试的结果是错误的,他们也不惧怕。
当然,我并不认为错误的尝试等同于创新。
但我们都知道
如果你不打算做错误的尝试
你永远不会创造出新东西。
如果你不想让孩子们做错误的尝试, 等他们长大了,
多数孩子就会丧失创新的能力。
那就会使他们也变得惧怕错误的尝试。
这种情况也存在于公司经营方面。
我们不能容忍任何错误。 这就使得现在的
教育体系成为
最不能容忍错误的领域。
这样做的后果就是 我们的教育体制正在扼杀
孩子们的创造力。毕加索曾说过:
“孩子们是天生的艺术家”
问题是我们长大后 能否继续保有艺术灵感。我坚信:
我们随着年龄的增长而丧失了创造力,
甚至可以说,是我们所受的教育 让我们丧失了创造力。
为什么会这样?`,
targetLang: "zh"
},
{
source: `The speed of a crash landing on its surface is typically between 70 and 100% of the escape velocity of the target moon, and thus this is the total velocity which must be shed from the target moon's gravitational attraction for a soft landing to occur.
For Earth's Moon, the escape velocity is 2.38 km/h. The change in velocity (referred to as a delta-v) is usually provided by a landing rocket, which must be carried into space by the original launch vehicle as part of the overall spacecraft.
An exception is the soft moon landing on Titan carried out by the Huygens probe in 2005.
As the moon with the thickest atmosphere, landings on Titan may be accomplished by using atmospheric entry techniques that are generally lighter in weight than a rocket with equivalent capability.`,
reference: `La velocidad de un alunizaje forzoso en su superficie suele estar entre el 70 y el 100% de la velocidad de escape de la luna objetivo, y por tanto ésta es la velocidad total que debe desprenderse de la atracción gravitatoria de la luna objetivo para que se produzca un alunizaje suave.
Para la Luna de la Tierra, la velocidad de escape es de 2,38 km/s.
El cambio de velocidad (denominado delta-v) suele proporcionarlo un cohete de alunizaje, que debe ser transportado al espacio por el vehículo de lanzamiento original como parte de la nave espacial en su conjunto.
Una excepción es el alunizaje suave en Titán realizado por la sonda Huygens en 2005.
Al ser la luna con la atmósfera más densa, los alunizajes en Titán pueden lograrse utilizando técnicas de entrada atmosférica que suelen ser más ligeras que un cohete de capacidad equivalente.`,
targetLang: "es"
}
];
console.log("Running API Tests...");
for (const testCase of testCases) {
const { source, reference, targetLang } = testCase;
console.log(`Testing: ${source} -> ${targetLang}`);
try {
const [lectoTranslation, googleTranslation, openAiTranslation] = await Promise.all([
translateWithLecto(source, targetLang),
translateWithGoogleCloud(source, targetLang),
translateWithOpenAI(source, targetLang)
]);
console.log("Lecto:", lectoTranslation);
console.log("Google:", googleTranslation);
console.log("OpenAI:", openAiTranslation);
console.log("BLEU Scores:");
console.log("Lecto BLEU:", compareBLEU(reference, lectoTranslation).toFixed(4));
console.log("Google BLEU:", compareBLEU(reference, googleTranslation).toFixed(4));
console.log("OpenAI BLEU:", compareBLEU(reference, openAiTranslation).toFixed(4));
} catch (error) {
console.error("Error during API test:", error);
}
}
}