Files
Cibello-app/apps/worker/scripts/translate-recipes.ts
T
Claude 16fb29cc12 i18n(innehåll): komplett ingrediensöversättning (362×11) + receptöversättnings-verktyg
Ingredienser: fyllde luckan från 101 till 362 för da/de/es/fi/fr/it/nb/nl/pl/pt
(engelska hade redan alla 362). Nu 3982 översättningar, 11 språk kompletta – en
spansk/tysk/fransk användare ser ingrediensnamn på sitt språk, inte svenska.
Författat av språkexperter per språk, verifierat mot 362-listan + kvalitetskoll.

Recept: 407 recept har 0 översättningar (svensk källa visas). Lade till två
körbara skript för AI-pipelinen (kräver din Gemini-nyckel i workern):
- scripts/translate-recipes.ts [språk|all]  – köar TRANSLATE_RECIPE för alla
  publicerade recept × språk, idempotent (hoppar redan gjorda).
- scripts/publish-translations.ts [språk|all] – publicerar de utkast som klarat
  den inbyggda verifieringen (numbersPreserved/stegantal/confidence); övriga
  stannar som utkast för granskning.

Verifierat mot testdatabas: ingredient_translations 3982 rader/11 språk kompletta;
enqueue köar 405 jobb; processor skapar draft_ai+ok; publish publicerar; steg-
översättningar följer med och serveras.
2026-08-20 18:35:59 +00:00

86 lines
3.1 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 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";
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);
},
);