Files
Cibello-app/apps/worker/scripts/translate-recipes.ts
T
Claude 5974e8740f fix(scripts): ladda .env i translate/publish-skripten (annars saknades DATABASE_URL)
De fristandiga skripten laste bara process.env och kunde inte hitta DATABASE_URL
(till skillnad fran worker/seed som laddar .env). Lade till samma dotenv-sokning
(.env / ../.env / ../../.env) sa de funkar med  utan att man
exporterar variabler manuellt. Verifierat: bada kor nu utan export och laser .env.
2026-08-20 19:27:14 +00:00

99 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Köa AI-översättning (TRANSLATE_RECIPE) för ALLA publicerade recept × målspråk.
* Hoppar över (recept, språk) som redan har en översättningsrad idempotent och
* resumbar, så du kan köra om utan att betala för samma jobb två gånger.
*
* Workern (Fönster C) MÅSTE köra med en RIKTIG AI-nyckel (inte mock) för att
* jobben ska bli riktiga översättningar. Jobben sparas som utkast (draft_ai)
* publicera dem sedan med publish-translations.ts.
*
* Kör från repo-roten:
* pnpm --filter @app/worker exec tsx scripts/translate-recipes.ts es # bara spanska (testa först!)
* pnpm --filter @app/worker exec tsx scripts/translate-recipes.ts es,de # flera
* pnpm --filter @app/worker exec tsx scripts/translate-recipes.ts all # alla 11 språk
*
* Kräver DATABASE_URL och REDIS_URL i miljön (samma som API/worker använder).
*/
import { config as loadDotenv } from "dotenv";
import { existsSync } from "node:fs";
import path from "node:path";
import IORedis from "ioredis";
import { Queue } from "bullmq";
import { eq } from "drizzle-orm";
import { createDatabase, schema } from "@app/database";
import { JOB_QUEUE_NAME } from "@app/shared-types";
// Ladda .env (samma sökning som worker/seed) så DATABASE_URL/REDIS_URL finns
// även när skriptet körs fristående via `pnpm exec tsx`.
for (const candidate of [".env", "../.env", "../../.env"]) {
const p = path.resolve(process.cwd(), candidate);
if (existsSync(p)) {
loadDotenv({ path: p });
break;
}
}
const LANGS = ["en", "da", "de", "es", "fi", "fr", "it", "nb", "nl", "pl", "pt"];
async function main() {
if (!process.env.DATABASE_URL) throw new Error("Sätt DATABASE_URL.");
const arg = (process.argv[2] ?? "all").toLowerCase();
const targets =
arg === "all" ? LANGS : arg.split(",").map((s) => s.trim()).filter(Boolean);
for (const l of targets) {
if (!LANGS.includes(l)) throw new Error(`Okänt språk: ${l}. Giltiga: ${LANGS.join(", ")}, all`);
}
const { db, pool } = createDatabase(process.env.DATABASE_URL);
const connection = new IORedis(process.env.REDIS_URL ?? "redis://localhost:6379", {
maxRetriesPerRequest: null,
});
const queue = new Queue(JOB_QUEUE_NAME, { connection });
const recipes = await db
.select({ id: schema.recipes.id })
.from(schema.recipes)
.where(eq(schema.recipes.status, "published"));
const existing = await db
.select({ r: schema.recipeTranslations.recipeId, l: schema.recipeTranslations.languageTag })
.from(schema.recipeTranslations);
const have = new Set(existing.map((e) => `${e.r}|${e.l}`));
const jobs: { name: string; data: unknown }[] = [];
let skip = 0;
for (const r of recipes) {
for (const lang of targets) {
if (have.has(`${r.id}|${lang}`)) {
skip++;
continue;
}
jobs.push({
name: "TRANSLATE_RECIPE",
data: { jobType: "TRANSLATE_RECIPE", recipeId: r.id, targetLanguageTag: lang },
});
}
}
// Köa i klumpar (snabbare än en add per jobb).
for (let i = 0; i < jobs.length; i += 500) {
await queue.addBulk(jobs.slice(i, i + 500) as never);
}
console.log(
`Köade ${jobs.length} översättningsjobb (hoppade ${skip} som redan finns). ` +
`Recept: ${recipes.length}, språk: ${targets.join(", ")}.`,
);
console.log(
"Workern (med riktig AI-nyckel) processar dem nu. När kön tömts: kör publish-translations.ts.",
);
await queue.close();
await connection.quit();
await pool.end();
}
main().then(
() => process.exit(0),
(e) => {
console.error(e);
process.exit(1);
},
);