feat(worker): --limit=N i translate-recipes för billig pilot över alla språk

- translate-recipes.ts får en valfri --limit=N som bara köar de N första
  (alfabetiskt) publicerade recepten × målspråk. Syfte: kvalitetskolla en
  handfull recept på ALLA språk innan hela svepet (~496 × 11) körs och betalas.
- Skriver ut vilka recept som valts i pilot-läge så man vet vad man ska granska.
- Oförändrat i övrigt: idempotent, resumbar, draft_ai -> verifiering ->
  publish-translations.ts publicerar bara det som klarat verifieringen.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-08-21 15:42:51 +00:00
parent d22f3d746b
commit b5bfb7cb8b
+21 -4
View File
@@ -11,6 +11,7 @@
* 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
* pnpm --filter @app/worker exec tsx scripts/translate-recipes.ts all --limit=5 # PILOT: 5 recept × alla språk
*
* Kräver DATABASE_URL och REDIS_URL i miljön (samma som API/worker använder).
*/
@@ -37,9 +38,17 @@ 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 positional = process.argv.slice(2).filter((a) => !a.startsWith("--"));
const arg = (positional[0] ?? "all").toLowerCase();
const targets =
arg === "all" ? LANGS : arg.split(",").map((s) => s.trim()).filter(Boolean);
// Valfri pilotgräns: --limit=N översätter bara de N första publicerade recepten
// (alfabetiskt) × målspråk billig kvalitetskoll innan hela svepet.
const limitFlag = process.argv.find((a) => a.startsWith("--limit="));
const limit = limitFlag ? Number(limitFlag.split("=")[1]) : null;
if (limitFlag && (!Number.isInteger(limit) || (limit ?? 0) <= 0)) {
throw new Error("--limit=N kräver ett positivt heltal, t.ex. --limit=5");
}
for (const l of targets) {
if (!LANGS.includes(l)) throw new Error(`Okänt språk: ${l}. Giltiga: ${LANGS.join(", ")}, all`);
}
@@ -50,10 +59,18 @@ async function main() {
});
const queue = new Queue(JOB_QUEUE_NAME, { connection });
const recipes = await db
.select({ id: schema.recipes.id })
const baseQuery = db
.select({ id: schema.recipes.id, title: schema.recipes.titleSv })
.from(schema.recipes)
.where(eq(schema.recipes.status, "published"));
.where(eq(schema.recipes.status, "published"))
.orderBy(schema.recipes.titleSv);
const recipes = limit ? await baseQuery.limit(limit) : await baseQuery;
if (limit) {
console.log(
`PILOT-läge: ${recipes.length} recept (alfabetiskt först) × ${targets.length} språk:`,
);
for (const r of recipes) console.log(`${r.title}`);
}
const existing = await db
.select({ r: schema.recipeTranslations.recipeId, l: schema.recipeTranslations.languageTag })
.from(schema.recipeTranslations);