Files
Cibello-app/apps/worker/scripts/retry-failed-translations.ts
T
Claude 52bd6e74bd feat: retry-failed-translations + kok-läget på rätt språk (titel, ingredienser, valfritt)
- retry-failed-translations.ts: kör om de receptöversättningar som föll på
  verifieringen (skriver ut vilka + varför, köar TRANSLATE_RECIPE på nytt).
  Idempotent; kör tills listan är tom, publicera sedan.
- Kok-läget (cooking/[id].tsx) visade svensk titel + svenska ingrediensnamn fast
  detalj-API:t redan levererar översatta fält. Nu title/displayName med svensk
  fallback (stegen var redan översatta).
- "(valfritt)" var hårdkodat i kok-läget + recept-detaljen -> ny nyckel
  recipe.optional i alla 12 språk. Vakt + typecheck gröna.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 18:49:34 +00:00

98 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ör om de receptöversättningar som föll på den deterministiska verifieringen
* (verification.ok = false) och därför ligger kvar som svenska. Skriver ut vilka
* + varför, och köar TRANSLATE_RECIPE på nytt. Workern översätter om (upsert med
* ny verifiering) → kör sedan publish-translations.ts all för att publicera de
* som nu klarar. Idempotent: kör om tills listan är tom.
*
* pnpm --filter @app/worker exec tsx scripts/retry-failed-translations.ts # kör om
* pnpm --filter @app/worker exec tsx scripts/retry-failed-translations.ts --dry-run # visa bara
*
* Fönster C (workern) måste vara igång med riktig AI-nyckel.
* Kräver DATABASE_URL + REDIS_URL (laddas från .env).
*/
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 { and, eq, sql } from "drizzle-orm";
import { createDatabase, schema } from "@app/database";
import { JOB_QUEUE_NAME } from "@app/shared-types";
for (const candidate of [".env", "../.env", "../../.env"]) {
const p = path.resolve(process.cwd(), candidate);
if (existsSync(p)) {
loadDotenv({ path: p });
break;
}
}
async function main() {
if (!process.env.DATABASE_URL) throw new Error("Sätt DATABASE_URL.");
const dryRun = process.argv.includes("--dry-run");
const { db, pool } = createDatabase(process.env.DATABASE_URL);
const failed = await db
.select({
recipeId: schema.recipeTranslations.recipeId,
lang: schema.recipeTranslations.languageTag,
title: schema.recipes.titleSv,
verification: schema.recipeTranslations.verification,
})
.from(schema.recipeTranslations)
.innerJoin(schema.recipes, eq(schema.recipes.id, schema.recipeTranslations.recipeId))
.where(
and(
eq(schema.recipeTranslations.status, "draft_ai"),
sql`${schema.recipeTranslations.verification}->>'ok' = 'false'`,
),
)
.orderBy(schema.recipes.titleSv);
if (failed.length === 0) {
console.log("✓ Inga misslyckade översättningar allt är verifierat och klart.");
await pool.end();
return;
}
console.log(`${failed.length} översättning(ar) föll på verifieringen:\n`);
for (const f of failed) {
const notes = f.verification?.notes?.length ? f.verification.notes.join("; ") : "okänd orsak";
console.log(` [${f.lang}] ${f.title}`);
console.log(`${notes}`);
}
if (dryRun) {
console.log("\n(dry-run: köade inget. Kör utan --dry-run för att översätta om.)");
await pool.end();
return;
}
const connection = new IORedis(process.env.REDIS_URL ?? "redis://localhost:6379", {
maxRetriesPerRequest: null,
});
const queue = new Queue(JOB_QUEUE_NAME, { connection });
await queue.addBulk(
failed.map((f) => ({
name: "TRANSLATE_RECIPE",
data: { jobType: "TRANSLATE_RECIPE", recipeId: f.recipeId, targetLanguageTag: f.lang },
})) as never,
);
console.log(`\nKöade ${failed.length} omförsök. Workern (Fönster C) översätter om nu.`);
console.log(
"När kön tömts: pnpm --filter @app/worker exec tsx scripts/publish-translations.ts all",
);
console.log("Kör det här scriptet igen för att se om något fortfarande faller (då kan det vara receptet självt som behöver en manuell titt).");
await queue.close();
await connection.quit();
await pool.end();
}
main().then(
() => process.exit(0),
(e) => {
console.error(e);
process.exit(1);
},
);