Files
LingvAI/utils/replicate-translate.ts
Malin 0799101da3 feat: add admin panel, Replicate AI translation, and document translation
- Admin panel (/admin) with JWT auth: configure Replicate API token,
  JigsawStack API key, model version, enable/disable AI translation,
  change admin password. Settings persisted in data/settings.json.

- Replicate AI translation: POST /api/translate/replicate uses
  JigsawStack text-translate model via Replicate API. Main page
  switches to client-side AI translation when enabled.

- Document translation tab: supports PDF, DOCX, XLSX, XLS, CSV.
  Excel/Word formatting fully preserved (SheetJS + JSZip XML manipulation).
  PDF uses pdf-parse extraction + pdf-lib reconstruction.
  Column selector UI for tabular data (per-sheet, All/None toggles).

- Updated README with full implementation documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 07:43:54 +01:00

84 lines
2.5 KiB
TypeScript

import { readSettings } from "./settings-store";
type ReplicateOutput = string | string[] | { translation?: string; translated_text?: string; output?: string };
export async function replicateTranslate(
text: string,
targetLanguage: string
): Promise<string> {
const settings = readSettings();
if (!settings.replicateApiToken) {
throw new Error("Replicate API token not configured");
}
if (!settings.jigsawApiKey) {
throw new Error("JigsawStack API key not configured");
}
const body = {
version: settings.modelVersion,
input: {
text,
api_key: settings.jigsawApiKey,
target_language: targetLanguage
}
};
const response = await fetch("https://api.replicate.com/v1/predictions", {
method: "POST",
headers: {
"Authorization": `Bearer ${settings.replicateApiToken}`,
"Content-Type": "application/json",
"Prefer": "wait"
},
body: JSON.stringify(body)
});
if (!response.ok) {
const err = await response.text();
throw new Error(`Replicate API error: ${response.status} ${err}`);
}
const data = await response.json();
if (data.error) {
throw new Error(`Replicate model error: ${data.error}`);
}
// Extract translated text from various output formats
const output: ReplicateOutput = data.output;
if (typeof output === "string") return output;
if (Array.isArray(output)) return output.join("");
if (output && typeof output === "object") {
return output.translation ?? output.translated_text ?? output.output ?? String(output);
}
throw new Error("Unexpected output format from Replicate");
}
// Batch translate using separator trick to minimize API calls
const SEPARATOR = "\n{{SEP}}\n";
export async function replicateTranslateBatch(
texts: string[],
targetLanguage: string
): Promise<string[]> {
if (texts.length === 0) return [];
if (texts.length === 1) {
return [await replicateTranslate(texts[0], targetLanguage)];
}
const joined = texts.join(SEPARATOR);
const translated = await replicateTranslate(joined, targetLanguage);
// Try to split on the separator; fall back to individual calls if it got translated
const parts = translated.split(SEPARATOR);
if (parts.length === texts.length) {
return parts;
}
// Fallback: translate individually
return Promise.all(texts.map(t => replicateTranslate(t, targetLanguage)));
}