Skip to content

Instantly share code, notes, and snippets.

@sunmeat
Created December 29, 2025 19:54
Show Gist options
  • Select an option

  • Save sunmeat/42a229f47fcd5f620fb783e789488bac to your computer and use it in GitHub Desktop.

Select an option

Save sunmeat/42a229f47fcd5f620fb783e789488bac to your computer and use it in GitHub Desktop.
api.mymemory.translated.net in web api client app example
const API_BASE_URL = 'https://localhost:7110/api/students';
// !!! кеш для перекладів: ключ = "текст у нижньому регістрі | цільова мова"
const translationCache = new Map();
// !!! функція перекладу тексту з української на потрібну мову через mymemory api (це безкоштовно, з підтримкою cors, без апі-ключів)
async function translateText(text, targetLng) {
if (!text || typeof text !== 'string') return text;
text = text.trim();
if (targetLng === 'uk' || !text) return text;
const cacheKey = `${text.toLowerCase()}|${targetLng}`;
if (translationCache.has(cacheKey)) {
return translationCache.get(cacheKey);
}
const target = targetLng === 'en' ? 'en' : 'fr';
try {
const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=uk|${target}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
// найкращий метч або перший результат
const translated = data.matches?.[0]?.translation || data.responseData?.translatedText || text;
translationCache.set(cacheKey, translated);
return translated;
} catch (err) {
console.warn(`переклад не вдався для "${text}" → ${targetLng}:`, err);
return text; // fallback на оригінал
}
}
// !!! створення рядка таблиці з перекладеними ім'ям та прізвищем
async function createRow(student, currentLng = 'uk') {
const name = currentLng === 'uk' ? student.name : await translateText(student.name, currentLng);
const surname = currentLng === 'uk' ? student.surname : await translateText(student.surname, currentLng);
return `
<tr data-rowid="${student.id}" class="student-row">
<td class="student-cell student-id-cell">${student.id}</td>
<td class="student-cell student-name-cell">${name}</td>
<td class="student-cell student-surname-cell">${surname}</td>
<td class="student-cell student-age-cell">${student.age}</td>
<td class="student-cell student-gpa-cell">${student.gpa}</td>
<td class="student-cell student-actions-cell">
<a href="#" class="edit-link action-link" data-id="${student.id}">${i18next.t('table.edit')}</a> |
<a href="#" class="remove-link action-link" data-id="${student.id}">${i18next.t('table.delete')}</a>
</td>
</tr>
`;
}
document.addEventListener('DOMContentLoaded', () => {
i18next
.use(i18nextHttpBackend)
.use(i18nextBrowserLanguageDetector)
.init({
lng: 'uk',
fallbackLng: 'uk',
supportedLngs: ['uk', 'en', 'fr'],
backend: {
loadPath: '/locales/{{lng}}/translation.json'
},
detection: {
order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'],
lookupQuerystring: 'lang',
lookupCookie: 'i18nextLng',
lookupLocalStorage: 'i18nextLng',
caches: ['localStorage', 'cookie']
},
debug: false
}, async (err, t) => {
if (err) return console.error(err);
updateContent();
await getStudents();
initEventListeners();
document.getElementById('languageSwitcher').addEventListener('change', (e) => {
const newLng = e.target.value;
i18next.changeLanguage(newLng, async () => {
updateContent();
// !!! очищаємо кеш перекладів при зміні мови
translationCache.clear();
await getStudents();
});
});
});
});
function updateContent() {
document.documentElement.lang = i18next.language;
document.querySelector('title').textContent = i18next.t('title');
document.querySelector('.page-title').textContent = i18next.t('title');
document.querySelector('label[for="name"]').textContent = i18next.t('form.name');
document.querySelector('label[for="surname"]').textContent = i18next.t('form.surname');
document.querySelector('label[for="age"]').textContent = i18next.t('form.age');
document.querySelector('label[for="gpa"]').textContent = i18next.t('form.gpa');
document.querySelector('.btn-submit').textContent = i18next.t('form.save');
document.querySelector('.btn-reset').textContent = i18next.t('form.reset');
document.querySelector('.table-header th:nth-child(1)').textContent = i18next.t('table.id');
document.querySelector('.table-header th:nth-child(2)').textContent = i18next.t('table.name');
document.querySelector('.table-header th:nth-child(3)').textContent = i18next.t('table.surname');
document.querySelector('.table-header th:nth-child(4)').textContent = i18next.t('table.age');
document.querySelector('.table-header th:nth-child(5)').textContent = i18next.t('table.gpa');
document.querySelector('.table-header th:nth-child(6)').textContent = i18next.t('table.actions');
}
// !!! отримання списку студентів з перекладом даних з БД
async function getStudents() {
try {
const response = await fetch(API_BASE_URL, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error(i18next.t('errors.httpError', { status: response.status }));
}
const students = await response.json();
const tbody = document.querySelector('.students-tbody');
tbody.innerHTML = '';
const currentLng = i18next.language;
for (const student of students) {
const rowHtml = await createRow(student, currentLng);
tbody.insertAdjacentHTML('beforeend', rowHtml);
}
} catch (error) {
alert(i18next.t('errors.load', { message: error.message }));
}
}
async function getStudent(id) {
try {
const response = await fetch(`${API_BASE_URL}/${id}`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error(i18next.t('errors.httpError', { status: response.status }));
}
const student = await response.json();
const form = document.forms['studentForm'];
form.elements['Id'].value = student.id;
form.elements['name'].value = student.name;
form.elements['surname'].value = student.surname;
form.elements['age'].value = student.age;
form.elements['gpa'].value = student.gpa;
} catch (error) {
alert(i18next.t('errors.fetchOne', { message: error.message }));
}
}
// !!! створення нового студента з подальшим перекладом у таблиці
async function createStudent(studentData) {
try {
const response = await fetch(API_BASE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(studentData)
});
if (!response.ok) {
throw new Error(i18next.t('errors.httpError', { status: response.status }));
}
const student = await response.json();
const rowHtml = await createRow(student, i18next.language);
document.querySelector('.students-tbody').insertAdjacentHTML('beforeend', rowHtml);
resetForm();
} catch (error) {
alert(i18next.t('errors.create', { message: error.message }));
}
}
// !!! редагування студента з оновленням перекладеного рядка
async function editStudent(studentData) {
try {
const response = await fetch(API_BASE_URL, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(studentData)
});
if (!response.ok) {
throw new Error(i18next.t('errors.httpError', { status: response.status }));
}
const student = await response.json();
const row = document.querySelector(`tr[data-rowid='${student.id}']`);
if (row) {
const newRowHtml = await createRow(student, i18next.language);
row.outerHTML = newRowHtml;
}
resetForm();
} catch (error) {
alert(i18next.t('errors.update', { message: error.message }));
}
}
async function deleteStudent(id) {
if (!confirm(i18next.t('confirmDelete'))) {
return;
}
try {
const response = await fetch(`${API_BASE_URL}/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
});
if (!response.ok) {
throw new Error(i18next.t('errors.httpError', { status: response.status }));
}
const row = document.querySelector(`tr[data-rowid='${id}']`);
if (row) {
row.remove();
}
} catch (error) {
alert(i18next.t('errors.delete', { message: error.message }));
}
}
function initEventListeners() {
const form = document.forms['studentForm'];
const submitBtn = document.querySelector('.btn-submit');
const resetBtn = document.querySelector('.btn-reset');
const tbody = document.querySelector('.students-tbody');
submitBtn.addEventListener('click', handleSubmit);
resetBtn.addEventListener('click', handleReset);
tbody.addEventListener('click', (e) => {
if (e.target.classList.contains('edit-link')) {
const id = e.target.dataset.id;
getStudent(id);
} else if (e.target.classList.contains('remove-link')) {
const id = e.target.dataset.id;
deleteStudent(id);
}
});
}
function resetForm() {
const form = document.forms['studentForm'];
form.reset();
form.elements['Id'].value = 0;
}
function handleReset(e) {
e.preventDefault();
resetForm();
}
function handleSubmit(e) {
e.preventDefault();
const form = document.forms['studentForm'];
const id = parseInt(form.elements['Id'].value);
const name = form.elements['name'].value.trim();
const surname = form.elements['surname'].value.trim();
const age = parseInt(form.elements['age'].value);
const gpa = parseFloat(form.elements['gpa'].value);
const studentData = { name, surname, age, gpa };
if (id === 0) {
createStudent(studentData);
} else {
studentData.id = id;
editStudent(studentData);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment