added translations export and import
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { apiGet, apiPut, apiDelete } from '../api.js';
|
||||
import { showToast } from '../app.js';
|
||||
import { apiGet, apiPost, apiPut, apiDelete } from '../api.js';
|
||||
import { canEdit, showToast } from '../app.js';
|
||||
import {
|
||||
SOURCE_LANG,
|
||||
normalizeEntry,
|
||||
@ -23,10 +23,20 @@ export async function translationsPage() {
|
||||
app.innerHTML = `
|
||||
<div class="page-header">
|
||||
<h1>Translations</h1>
|
||||
<div class="actions" id="transHeaderActions"></div>
|
||||
</div>
|
||||
<div id="transPageRoot"><div class="spinner"></div></div>
|
||||
`;
|
||||
|
||||
if (canEdit()) {
|
||||
document.getElementById('transHeaderActions').innerHTML = `
|
||||
<input type="file" id="importTransBundleFile" accept=".json,application/json" hidden>
|
||||
<button type="button" class="btn" id="importTransBundleBtn">Import translations</button>
|
||||
<button type="button" class="btn btn-primary" id="exportTransBundleBtn">Export translations</button>
|
||||
`;
|
||||
bindTranslationBundleActions();
|
||||
}
|
||||
|
||||
try {
|
||||
renderShell();
|
||||
await loadTranslations();
|
||||
@ -387,3 +397,85 @@ function applyFilters() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bindTranslationBundleActions() {
|
||||
const fileInput = document.getElementById('importTransBundleFile');
|
||||
const importBtn = document.getElementById('importTransBundleBtn');
|
||||
const exportBtn = document.getElementById('exportTransBundleBtn');
|
||||
if (!fileInput || !importBtn || !exportBtn) return;
|
||||
|
||||
exportBtn.addEventListener('click', async () => {
|
||||
exportBtn.disabled = true;
|
||||
const prev = exportBtn.textContent;
|
||||
exportBtn.textContent = 'Preparing…';
|
||||
try {
|
||||
const token = localStorage.getItem('qdb_token');
|
||||
const res = await fetch('../api/translations?exportBundle=1', {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
throw new Error(err.error?.message || err.error || 'Export failed');
|
||||
}
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const stamp = new Date().toISOString().slice(0, 10);
|
||||
a.download = `translations_bundle_${stamp}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showToast('Translations bundle downloaded', 'success');
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
} finally {
|
||||
exportBtn.disabled = false;
|
||||
exportBtn.textContent = prev;
|
||||
}
|
||||
});
|
||||
|
||||
importBtn.addEventListener('click', () => fileInput.click());
|
||||
|
||||
fileInput.addEventListener('change', async () => {
|
||||
const file = fileInput.files?.[0];
|
||||
fileInput.value = '';
|
||||
if (!file) return;
|
||||
|
||||
let bundle;
|
||||
try {
|
||||
bundle = JSON.parse(await file.text());
|
||||
} catch {
|
||||
showToast('Invalid JSON file', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bundle?.entries || !Array.isArray(bundle.entries)) {
|
||||
showToast('Not a translations bundle (missing entries array)', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const count = bundle.entries.length;
|
||||
if (!confirm(`Import translations for ${count} entries from this file?`)) return;
|
||||
|
||||
importBtn.disabled = true;
|
||||
const prev = importBtn.textContent;
|
||||
importBtn.textContent = 'Importing…';
|
||||
try {
|
||||
const result = await apiPost('translations.php', {
|
||||
action: 'importBundle',
|
||||
bundle,
|
||||
});
|
||||
const imported = result.imported ?? 0;
|
||||
const skipped = result.skipped ?? 0;
|
||||
let msg = `Imported ${imported} translation(s)`;
|
||||
if (skipped) msg += ` (${skipped} skipped)`;
|
||||
showToast(msg, 'success');
|
||||
await loadTranslations();
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
} finally {
|
||||
importBtn.disabled = false;
|
||||
importBtn.textContent = prev;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user