Add Russian i18n with DB translations, locale API, and curator review UI.
UI chrome via react-i18next, catalog text in entity_translations with ru.wikipedia seeding, locale-aware search, and Translations page for publish workflow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f247b418d8
commit
ca58c43648
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Import translation rows from JSON or CSV.
|
||||
*
|
||||
* JSON format: [{ "entity_type": "artist", "entity_id": 1, "field_name": "name", "value": "...", "status": "draft" }]
|
||||
* CSV format: entity_type,entity_id,field_name,value[,status][,source]
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { upsertTranslation } = require('../server/translation-service');
|
||||
const pool = require('../server/db');
|
||||
|
||||
function parseArgs(argv) {
|
||||
const fileIdx = argv.indexOf('--file');
|
||||
const localeIdx = argv.indexOf('--locale');
|
||||
if (fileIdx === -1 || !argv[fileIdx + 1]) {
|
||||
throw new Error('--file <path> is required');
|
||||
}
|
||||
return {
|
||||
filePath: path.resolve(argv[fileIdx + 1]),
|
||||
locale: localeIdx !== -1 && argv[localeIdx + 1] ? argv[localeIdx + 1] : 'ru',
|
||||
publish: argv.includes('--publish'),
|
||||
};
|
||||
}
|
||||
|
||||
function parseCsv(content) {
|
||||
const lines = content.split(/\r?\n/).filter((l) => l.trim() && !l.trim().startsWith('#'));
|
||||
const rows = [];
|
||||
for (const line of lines) {
|
||||
const parts = line.split(',').map((p) => p.trim().replace(/^"|"$/g, ''));
|
||||
if (parts[0] === 'entity_type') continue;
|
||||
if (parts.length < 4) continue;
|
||||
rows.push({
|
||||
entity_type: parts[0],
|
||||
entity_id: parseInt(parts[1], 10),
|
||||
field_name: parts[2],
|
||||
value: parts[3],
|
||||
status: parts[4] || 'draft',
|
||||
source: parts[5] || 'import_csv',
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { filePath, locale, publish } = parseArgs(process.argv.slice(2));
|
||||
const raw = fs.readFileSync(filePath, 'utf8');
|
||||
let rows;
|
||||
if (filePath.endsWith('.json')) {
|
||||
rows = JSON.parse(raw);
|
||||
} else {
|
||||
rows = parseCsv(raw);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
for (const row of rows) {
|
||||
if (!row.entity_type || !row.entity_id || !row.field_name || row.value == null) continue;
|
||||
const status = publish ? 'published' : (row.status || 'draft');
|
||||
await upsertTranslation({
|
||||
entityType: row.entity_type,
|
||||
entityId: row.entity_id,
|
||||
locale: row.locale || locale,
|
||||
fieldName: row.field_name,
|
||||
value: String(row.value),
|
||||
status,
|
||||
source: row.source || 'import',
|
||||
});
|
||||
count += 1;
|
||||
}
|
||||
|
||||
console.log(`Imported ${count} translation rows from ${filePath}`);
|
||||
await pool.end();
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user