Files
Termix/scripts/crowdin-pretranslate.cjs
T

122 lines
3.3 KiB
JavaScript
Raw Normal View History

+2
2026-06-16 15:59:53 -05:00
const API = "https://api.crowdin.com/api/v2";
const ENGINE_ID = 649248; // Google Translate MT engine
const PROJECT_NAME = "termix-ssh";
const SOURCE_FILE = "en.json";
function token() {
const t = process.env.CROWDIN_API_KEY;
if (!t) throw new Error("CROWDIN_API_KEY is not set");
return t;
}
async function request(method, pathname, body) {
const res = await fetch(`${API}${pathname}`, {
method,
headers: {
Authorization: `Bearer ${token()}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const text = await res.text();
throw new Error(`${method} ${pathname} -> ${res.status}: ${text}`);
}
return res.json();
}
async function paged(pathname) {
const items = [];
let offset = 0;
const limit = 500;
for (;;) {
const sep = pathname.includes("?") ? "&" : "?";
const page = await request(
"GET",
`${pathname}${sep}limit=${limit}&offset=${offset}`,
);
const batch = (page.data || []).map((row) => row.data);
items.push(...batch);
if (batch.length < limit) break;
offset += limit;
}
return items;
}
async function resolveProjectId() {
const projects = await paged("/projects");
const match = projects.find(
(p) => p.identifier === PROJECT_NAME || p.name === PROJECT_NAME,
);
if (!match) throw new Error(`project "${PROJECT_NAME}" not found`);
return { id: match.id, targetLanguageIds: match.targetLanguageIds || [] };
}
async function resolveFileId(projectId) {
const files = await paged(`/projects/${projectId}/files`);
const match = files.find(
(f) => f.name === SOURCE_FILE || f.path === `/${SOURCE_FILE}`,
);
if (!match)
throw new Error(`source file "${SOURCE_FILE}" not found in project`);
return match.id;
}
async function pollPreTranslation(projectId, preTranslationId) {
for (let i = 0; i < 120; i++) {
const { data } = await request(
"GET",
`/projects/${projectId}/pre-translations/${preTranslationId}`,
);
if (data.status === "finished") return data;
if (data.status === "failed" || data.status === "canceled") {
throw new Error(
`pre-translation ${data.status} (progress ${data.progress}%)`,
);
}
await new Promise((r) => setTimeout(r, 5000));
}
throw new Error("pre-translation timed out after 10 minutes");
}
async function main() {
const { id: projectId, targetLanguageIds } = await resolveProjectId();
if (targetLanguageIds.length === 0) {
throw new Error("project has no target languages configured");
}
const fileId = await resolveFileId(projectId);
console.log(
`Pre-translating project ${projectId}, file ${fileId}, ${targetLanguageIds.length} languages via MT engine ${ENGINE_ID}`,
);
const { data } = await request(
"POST",
`/projects/${projectId}/pre-translations`,
{
languageIds: targetLanguageIds,
fileIds: [fileId],
method: "mt",
engineId: ENGINE_ID,
scope: "untranslated",
},
);
const result = await pollPreTranslation(projectId, data.identifier);
console.log(`Pre-translation finished (${result.progress}%)`);
}
module.exports = {
resolveProjectId,
resolveFileId,
pollPreTranslation,
};
if (require.main === module) {
main().catch((err) => {
console.error(`crowdin-pretranslate: ${err.message}`);
process.exit(1);
});
}