fix: add frontend/src/lib/ to git (was ignored by /lib/ pattern)
Some checks failed
Deploy to Homelab / Deploy Wordly to 192.168.1.151 (push) Has been cancelled
Deploy to Homelab / Deploy Monitoring (if configured) (push) Has been cancelled

The root .gitignore had `lib/` which matched frontend/src/lib/,
causing Docker build to fail with "Module not found: Can't resolve
'@/lib/utils'" and '@/lib/i18n'.

Changed to `/lib/` so it only ignores the Python lib at repo root.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 15:17:22 +02:00
parent 98d82414bb
commit 05c5dfcbbb
10 changed files with 9115 additions and 2 deletions

4
.gitignore vendored
View File

@@ -10,8 +10,8 @@ dist/
downloads/ downloads/
eggs/ eggs/
.eggs/ .eggs/
lib/ /lib/
lib64/ /lib64/
parts/ parts/
sdist/ sdist/
var/ var/

161
frontend/src/lib/api.ts Normal file
View File

@@ -0,0 +1,161 @@
import { API_BASE } from "./config";
export interface TranslatedText {
id: string;
translated_text: string;
}
export interface Language {
code: string;
name: string;
flag: string;
}
export interface Provider {
id: string;
name: string;
icon?: string;
description?: string;
mode?: "classic" | "llm";
}
export const languages: Language[] = [
{ code: "fr", name: "Français", flag: "🇫🇷" },
{ code: "en", name: "English", flag: "🇬🇧" },
{ code: "de", name: "Deutsch", flag: "🇩🇪" },
{ code: "es", name: "Español", flag: "🇪🇸" },
{ code: "it", name: "Italiano", flag: "🇮🇹" },
{ code: "pt", name: "Português", flag: "🇵🇹" },
{ code: "nl", name: "Nederlands", flag: "🇳🇱" },
{ code: "ru", name: "Русский", flag: "🇷🇺" },
{ code: "zh", name: "中文", flag: "🇨🇳" },
{ code: "ja", name: "日本語", flag: "🇯🇵" },
{ code: "ko", name: "한국어", flag: "🇰🇷" },
{ code: "ar", name: "العربية", flag: "🇸🇦" },
{ code: "fa", name: "فارسی", flag: "🇮🇷" },
{ code: "hi", name: "हिन्दी", flag: "🇮🇳" },
{ code: "tr", name: "Türkçe", flag: "🇹🇷" },
{ code: "pl", name: "Polski", flag: "🇵🇱" },
{ code: "sv", name: "Svenska", flag: "🇸🇪" },
{ code: "da", name: "Dansk", flag: "🇩🇰" },
{ code: "fi", name: "Suomi", flag: "🇫🇮" },
{ code: "no", name: "Norsk", flag: "🇳🇴" },
{ code: "cs", name: "Čeština", flag: "🇨🇿" },
{ code: "ro", name: "Română", flag: "🇷🇴" },
{ code: "hu", name: "Magyar", flag: "🇭🇺" },
{ code: "el", name: "Ελληνικά", flag: "🇬🇷" },
{ code: "he", name: "עברית", flag: "🇮🇱" },
{ code: "th", name: "ไทย", flag: "🇹🇭" },
{ code: "vi", name: "Tiếng Việt", flag: "🇻🇳" },
{ code: "id", name: "Bahasa Indonesia", flag: "🇮🇩" },
{ code: "uk", name: "Українська", flag: "🇺🇦" },
];
export const providers: Provider[] = [
{ id: "google", name: "Google Translate", description: "Fast translation, 130+ languages" },
{ id: "deepl", name: "DeepL", description: "High-quality neural translation" },
{ id: "openai", name: "OpenAI", description: "GPT-powered context-aware translation", mode: "llm" },
{ id: "ollama", name: "Ollama (Local)", description: "Self-hosted LLM translation", mode: "llm" },
{ id: "openrouter", name: "OpenRouter", description: "Multi-model AI translation", mode: "llm" },
];
export async function translateDocument(params: {
file: File;
targetLanguage: string;
provider: string;
ollamaModel?: string;
translateImages?: boolean;
systemPrompt?: string;
glossary?: string;
libreUrl?: string;
openaiApiKey?: string;
openaiModel?: string;
openrouterApiKey?: string;
openrouterModel?: string;
}): Promise<Blob> {
const formData = new FormData();
formData.append("file", params.file);
formData.append("target_language", params.targetLanguage);
formData.append("provider", params.provider);
if (params.ollamaModel) formData.append("ollama_model", params.ollamaModel);
if (params.translateImages !== undefined)
formData.append("translate_images", String(params.translateImages));
if (params.systemPrompt) formData.append("system_prompt", params.systemPrompt);
if (params.glossary) formData.append("glossary", params.glossary);
if (params.libreUrl) formData.append("libre_url", params.libreUrl);
const token =
typeof window !== "undefined" ? localStorage.getItem("token") : null;
const headers: Record<string, string> = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
const res = await fetch(`${API_BASE}/api/v1/translate`, {
method: "POST",
headers,
body: formData,
});
if (!res.ok) {
let msg = `HTTP ${res.status}`;
try {
const j = await res.json();
msg = j.message || j.error || msg;
} catch {}
throw new Error(msg);
}
return res.blob();
}
export async function extractTextsFromDocument(
file: File,
): Promise<{ texts: Array<{ id: string; text: string }>; session_id: string }> {
const formData = new FormData();
formData.append("file", file);
const token =
typeof window !== "undefined" ? localStorage.getItem("token") : null;
const headers: Record<string, string> = {};
if (token) headers["Authorization"] = `Bearer ${token}`;
const res = await fetch(`${API_BASE}/api/v1/extract`, {
method: "POST",
headers,
body: formData,
});
if (!res.ok) {
throw new Error(`Extraction failed: HTTP ${res.status}`);
}
return res.json();
}
export async function reconstructDocument(
sessionId: string,
translations: TranslatedText[],
targetLanguage: string,
): Promise<Blob> {
const token =
typeof window !== "undefined" ? localStorage.getItem("token") : null;
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (token) headers["Authorization"] = `Bearer ${token}`;
const res = await fetch(`${API_BASE}/api/v1/reconstruct`, {
method: "POST",
headers,
body: JSON.stringify({
session_id: sessionId,
translations,
target_language: targetLanguage,
}),
});
if (!res.ok) {
throw new Error(`Reconstruction failed: HTTP ${res.status}`);
}
return res.blob();
}

View File

@@ -0,0 +1,63 @@
import { API_BASE } from "./config";
export class ApiClientError extends Error {
status: number;
code?: string;
constructor(status: number, message: string, code?: string) {
super(message);
this.name = "ApiClientError";
this.status = status;
this.code = code;
}
}
async function request<T>(
method: string,
url: string,
body?: unknown,
): Promise<T> {
const headers: Record<string, string> = {};
const token =
typeof window !== "undefined" ? localStorage.getItem("token") : null;
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
if (body !== undefined) {
headers["Content-Type"] = "application/json";
}
const res = await fetch(`${API_BASE}${url}`, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
let message = `HTTP ${res.status}`;
let code: string | undefined;
try {
const json = await res.json();
message = json.message || json.error || json.detail || message;
code = json.code;
} catch {}
throw new ApiClientError(res.status, message, code);
}
if (res.status === 204) return undefined as T;
const json = await res.json();
return json as T;
}
export const apiClient = {
get: <T>(url: string) => request<T>("GET", url),
post: <T>(url: string, body?: unknown) => request<T>("POST", url, body),
patch: <T>(url: string, body?: unknown) => request<T>("PATCH", url, body),
put: <T>(url: string, body?: unknown) => request<T>("PUT", url, body),
delete: <T>(url: string) => request<T>("DELETE", url),
};
export const API_BASE_URL = API_BASE;

View File

@@ -0,0 +1,4 @@
export const API_BASE =
typeof window !== "undefined"
? (process.env.NEXT_PUBLIC_API_URL || window.location.origin)
: (process.env.BACKEND_URL || "http://localhost:8000");

8687
frontend/src/lib/i18n.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
export const ANNUAL_DISCOUNT_PERCENT = 20;
export const YEARLY_DISCOUNT_FACTOR = (100 - ANNUAL_DISCOUNT_PERCENT) / 100;
export function computeYearlyFromMonthly(monthly: number): number {
return Number((monthly * 12 * YEARLY_DISCOUNT_FACTOR).toFixed(2));
}

135
frontend/src/lib/store.ts Normal file
View File

@@ -0,0 +1,135 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
export const openaiModels = [
{ id: "gpt-4o", name: "GPT-4o" },
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
{ id: "gpt-4-turbo", name: "GPT-4 Turbo" },
];
export const openrouterModels = [
{ id: "deepseek/deepseek-chat-v3-0324", name: "DeepSeek V3" },
{ id: "anthropic/claude-3.5-haiku", name: "Claude 3.5 Haiku" },
{ id: "google/gemini-2.0-flash-001", name: "Gemini 2.0 Flash" },
];
interface TranslationSettings {
defaultTargetLanguage: string;
defaultProvider: string;
translateImages: boolean;
ollamaUrl: string;
ollamaModel: string;
openaiApiKey: string;
openaiModel: string;
openrouterApiKey: string;
openrouterModel: string;
libreTranslateUrl: string;
systemPrompt: string;
glossary: string;
adminToken?: string;
}
interface TranslationState {
settings: TranslationSettings;
updateSettings: (partial: Partial<TranslationSettings>) => void;
setAdminToken: (token: string | undefined) => void;
applyPreset: (preset: string) => void;
clearContext: () => void;
}
const PRESETS: Record<string, { systemPrompt: string; glossary: string }> = {
hvac: {
systemPrompt:
"You are translating technical HVAC (Heating, Ventilation, and Air Conditioning) documents. Use precise engineering terminology as defined in ASHRAE standards and European EN standards. Maintain technical accuracy. Preserve all numerical values, units, and reference codes exactly.",
glossary:
"pression statique=static pressure\nrécupérateur de chaleur=heat recovery unit\nventilo-convecteur=fan coil unit\ngaine de ventilation=ventilation duct\ndiffuseur d'air=air diffuser\nclimatisation=air conditioning\ntraitement d'air=air handling\ncaisson de traitement d'air=air handling unit (AHU)\nréseau de distribution=distribution network\ncharge thermique=thermal load\ndébit d'air=airflow rate\ntaux de brassage=air change rate\nbilan thermique=heat balance\nconductivité thermique=thermal conductivity\ncoefficient de transmission thermique=thermal transmittance (U-value)\nisolation thermique=thermal insulation\nefficacité énergétique=energy efficiency\npuissance frigorifique=cooling capacity\npuissance calorifique=heating capacity\ngroupe d'eau glacée=chiller\n tour de refroidissement=cooling tower\npompes à chaleur=heat pump\n détendeur=expansion valve\névaporateur=evaporator\ncondenseur=condenser\ncompresseur=compressor\ncircuit frigorifique=refrigerant circuit\nfluide frigorigène=refrigerant\nrendement=efficiency\npertes de charge=pressure drop\nboucle d'eau=water loop\nplancher chauffant=underfloor heating\nradiateur=radiator\nboucle de régulation=control loop\nsonde de température=temperature sensor\nvanne trois voies=three-way valve\nregistre=damper\ncaisson d'extraction=exhaust unit\nVMC=mechanical ventilation (MV)\npuits canadien=Canadian well / earth tube\nennoblissement=building envelope\nétanchéité à l'air=airtightness\npont thermique=thermal bridge\ndéperdition thermique=heat loss\nconfort thermique=thermal comfort\nhygrométrie=humidity level\ncondensation=condensation\ninfiltration d'air=air infiltration\nrenouvellement d'air=fresh air supply\nair neuf=fresh air\nair vicié=stale air\nair recyclé=recirculated air\nboucle d'eau glacée=chilled water loop\nréseau aéraulique=air distribution system\nréseau hydraulique=hydraulic system\npertes de charge linéaires=linear pressure losses\npertes de charge singulières=local pressure losses\ndiamètre équivalent=equivalent diameter\ndébit volumique=volumetric flow rate\ndébit massique=mass flow rate",
},
it: {
systemPrompt:
"You are translating IT and software documentation. Use standard technical terminology. Preserve all code snippets, API endpoints, variable names, and technical identifiers unchanged. Follow industry-standard translations for software concepts.",
glossary:
"serveur=server\nbase de données=database\ninterface utilisateur=user interface\npile technologique=tech stack\ndéploiement=deployment\nréseau=network\nstockage=storage\npare-feu=firewall\nbalanceur de charge=load balancer\nmot de passe=password\nauthentification=authentication\nautorisation=authorization\nchiffrement=encryption\ncertificat=certificate\njeton d'accès=access token\nrequête=request\nréponse=response\npoint de terminaison=endpoint\ninterface de programmation=API (Application Programming Interface)\nlogiciel=software\nmatériel=hardware\nmicrologiciel=firmware\nprogramme=program\nscript=script\nalgorithme=algorithm\nbase de code=codebase\ndépôt=repository\nbranche=branch\nfusion=merge\nrequête d'extraction=pull request\nintégration continue=continuous integration (CI)\ndéploiement continu=continuous deployment (CD)\npipeline=pipeline\nconteneur=container\norchestration=orchestration\nmachine virtuelle=virtual machine (VM)\ninstance=instance\ncluster=cluster\nnœud=node\npod=pod\nmicroservice=microservice\narchitecture=architecture\ninfrastructure=infrastructure\nplateforme=platform\nenvironnement=environment\nproduction=production\npréproduction=staging\ndéveloppement=development\ntest=test\nassurance qualité=quality assurance (QA)\nbogue=bug\ncorrection=fix\ncorrectif=patch\nversion=version\npublication=release\njournal=log\nsurveillance=monitoring\nalerte=alert\nmétrique=metric\ntableau de bord=dashboard\nrapport=report\nrapport d'erreur=error report\npile d'appels=stack trace\nexception=exception\ngestion des erreurs=error handling\nfile d'attente=queue\ntampon=buffer\ncache=cache\nperformance=performance\nlatence=latency\ndisponibilité=availability\névolutivité=scalability\nredondance=redundancy\nsauvegarde=backup\nrestauration=restore\nplan de reprise=recovery plan",
},
legal: {
systemPrompt:
"You are translating legal documents, contracts, and regulatory texts. Use formal legal terminology consistent with civil law (French) and common law (English) traditions. Preserve all article references, section numbers, dates, monetary amounts, and legal citations exactly as written. Maintain the formal register.",
glossary:
"contrat=contract\nclause=clause\npartie=party\nrésiliation=termination\nrésiliation unilatérale=unilateral termination\nindemnité=indemnity\nindemnisation=compensation\nresponsabilité=liability\nresponsabilité civile=tort liability\nresponsabilité contractuelle=contractual liability\nlitige=dispute\narbitrage=arbitration\nmédiation=mediation\ntribunal=court\njuridiction=court / jurisdiction\ncompétence=jurisdiction\nappel=appeal\npourvoi en cassation=appeal to supreme court\nforce obligatoire=binding force\nexécution forcée=forced execution\ninexécution=non-performance\ndommages et intérêts=damages\ndommages-intérêts punitifs=punitive damages\nmise en demeure=formal notice\ngarantie=warranty / guarantee\ngarantie légale=statutory warranty\nvice caché=hidden defect\nvice de construction=construction defect\nobligation=obligation\nobligation de moyen=obligation of means\nobligation de résultat=obligation of result\nengagement=commitment\nstipulation=stipulation\ndisposition=provision\narticle=article\nalinéa=paragraph\nannexe=appendix / schedule\navenant=amendment\naddendum=addendum\nprotocole d'accord=memorandum of understanding (MOU)\nlettre d'intention=letter of intent (LOI)\nconditions générales=general terms and conditions\nconditions particulières=specific terms\nforce majeure=force majeure\ncas fortuit=act of God\npréavis=notice period\ndélai=deadline / time limit\nprescription=statute of limitations\nforclusion=forfeiture\ndroit de propriété=property right\ndroit d'auteur=copyright\nbrevet=patent\nmarque déposée=registered trademark\nlicence=license\ncession=assignment / transfer\ncession de créance=assignment of claim\ncession de bail=assignment of lease\nsous-location=sublease\ncaution=guarantor / surety\nhypothèque=mortgage\nnantissement=pledge\ngage=pledge / collateral\nsurendettement=over-indebtedness\nfaillite=bankruptcy\nredressement judiciaire=judicial reorganization\nliquidation=liquidation\nprocuration=power of attorney\nmandat=mandate\nprocès=trial\nassignation=summons\nconclusions=submissions\njugement=judgment\nordonnance=order\narrêt=ruling / decision\nsauf conduite=safe conduct\nastreinte=periodic penalty payment\nexécution provisoire=provisional execution\nappel en garantie=third-party proceedings\nintervention volontaire=voluntary intervention",
},
medical: {
systemPrompt:
"You are translating medical and healthcare documents. Use precise medical terminology consistent with WHO standards and clinical nomenclature. Preserve all dosage information, drug names (use INN where applicable), measurements, and clinical values exactly. Maintain strict accuracy — errors in medical translation can have serious consequences.",
glossary:
"posologie=dosage\ncontre-indication=contraindication\neffet secondaire=side effect / adverse effect\neffet indésirable=adverse drug reaction (ADR)\ntraitement=treatment\ntraitement symptomatique=symptomatic treatment\ntraitement préventif=prophylactic treatment\ntraitement curatif=curative treatment\ndiagnostic=diagnosis\ndiagnostic différentiel=differential diagnosis\npronostic=prognosis\nsymptôme=symptom\nsyndrome=syndrome\nsigne clinique=clinical sign\nexamen clinique=clinical examination\nexamen complémentaire=additional examination\nordonnance=prescription\nprescription=prescription\nmolécule=molecule / active substance\nnom commercial=brand name\ndénomination commune internationale=International Nonproprietary Name (INN)\ngénérique=generic drug\ninteraction médicamenteuse=drug interaction\npharmacocinétique=pharmacokinetics\npharmacodynamie=pharmacodynamics\nbiodisponibilité=bioavailability\ndemi-vie=half-life\nmétabolisme=metabolism\nélimination=elimination\nvoie d'administration=route of administration\nvoie orale=oral route\nvoie intraveineuse=intravenous route\nvoie intramusculaire=intramuscular route\nvoie sous-cutanée=subcutaneous route\nvoie locale=topical route\npatient=patient\npatient ambulatoire=outpatient\npatient hospitalisé=inpatient\nhospitalisation=hospitalization\nconsultation=consultation\nurgences=emergency department\nréanimation=intensive care unit (ICU)\nchirurgie=surgery\nintervention chirurgicale=surgical procedure\nanesthésie=anesthesia\nanesthésie générale=general anesthesia\nanesthésie locale=local anesthesia\nantalgique=analgesic\nanalgésique=analgesic\nanti-inflammatoire=anti-inflammatory\nantibiotique=antibiotic\nantiviral=antiviral\nantifongique=antifungal\nanticoagulant=anticoagulant\nantihypertenseur=antihypertensive\nantidiabétique=antidiabetic\nimmunosuppresseur=immunosuppressant\ncorticostéroïde=corticosteroid\nchimiothérapie=chemotherapy\nradiothérapie=radiotherapy\nimmunothérapie=immunotherapy\nbiopsie=biopsy\néchographie=ultrasound\nradiographie=X-ray\nscanner=CT scan\nIRM=MRI (Magnetic Resonance Imaging)\nscintigraphie=scintigraphy\nendoscopie=endoscopy\nanalyse sanguine=blood test\nnumération formule sanguine=complete blood count (CBC)\nhémoglobine=hemoglobin\nglycémie=blood glucose\npression artérielle=blood pressure\nfréquence cardiaque=heart rate\nfréquence respiratoire=respiratory rate\nsaturation en oxygène=oxygen saturation\ntempérature corporelle=body temperature\nallergie=allergy\nintolérance=intolerance\nimmunisation=immunization\nvaccination=vaccination\nmaladie auto-immune=autoimmune disease\nmaladie chronique=chronic disease\nmaladie aiguë=acute disease\npathologie=pathology\nétiologie=etiology\nanatomopathologie=histopathology\nprévalence=prevalence\nincidence=incidence\népidémiologie=epidemiology\nsanté publique=public health",
},
finance: {
systemPrompt:
"You are translating financial documents, reports, and regulatory filings. Use precise financial terminology consistent with IFRS (International Financial Reporting Standards) and local GAAP. Preserve all numerical values, currency amounts, percentages, and financial ratios exactly. Maintain formal business register.",
glossary:
"bilan=balance sheet\ncompte de résultat=income statement / profit and loss statement\nflux de trésorerie=cash flow\nétat des flux de trésorerie=statement of cash flows\nfonds propres=equity\ncapitaux propres=shareholders' equity\nactif=asset\npassif=liability\nactif circulant=current assets\nactif immobilisé=non-current assets / fixed assets\npassif courant=current liabilities\npassif à long terme=long-term liabilities\nimmobilisations=non-current assets\namortissement=depreciation\nprovision=provision\ndépréciation=impairment\nplus-value=capital gain\nmoins-value=capital loss\nchiffre d'affaires=revenue / turnover\nrésultat d'exploitation=operating income\nrésultat net=net income / net profit\nmarge brute=gross margin\nmarge opérationnelle=operating margin\nexcédent brut d'exploitation=EBITDA\nbénéfice=profit\nperte=loss\ndividende=dividend\naction=share / stock\nactionnaire=shareholder\nobligation=bond\ntaux d'intérêt=interest rate\ntaux d'emprunt=lending rate\nemprunt=loan\ndette=debt\ncrédit=credit\nleasing=lease\nlocation=rent\ninvestissement=investment\nretour sur investissement=return on investment (ROI)\nvaleur nette comptable=net book value\njuste valeur=fair value\nvaleur de marché=market value\ngoodwill=goodwill\nfrais généraux=overhead\ncharge=expense\nproduit=revenue / income\nfacture=invoice\navoir=credit note\nacompte=deposit / advance payment\nsolde=balance\narrêté de comptes=financial year-end closing\nexercice comptable=financial year / fiscal year\nbilan d'ouverture=opening balance sheet\nbilan de clôture=closing balance sheet\nécriture comptable=journal entry\nlettrage=reconciliation\nrapprochement bancaire=bank reconciliation\naudit=audit\ncommissaire aux comptes=statutory auditor\nexpert-comptable=certified public accountant (CPA)\nplan comptable=chart of accounts\nrésultat fiscal=taxable income\nimpôt sur les sociétés=corporate tax\ntaxe sur la valeur ajoutée=value added tax (VAT)\ncharge sociale=social security contribution\nfiscalité=taxation\noptimisation fiscale=tax optimization\nparadis fiscal=tax haven\nfusion-acquisition=merger and acquisition (M&A)\nprise de participation=equity stake\ncession d'actifs=asset disposal\nfiliale=subsidiary\nsociété mère=parent company\nfilialisation=incorporation of a subsidiary\ncession de parts=share transfer\naugmentation de capital=capital increase\nréduction de capital=capital reduction\ndissolution=dissolution\nliquidation=liquidation\nfaillite=bankruptcy\nprocédure collective=insolvency proceedings\ntresorerie=tresury\nfonds de roulement=working capital\nbesoin en fonds de roulement=working capital requirement\nratio d'endettement=debt ratio\nratio de liquidité=liquidity ratio\nrentabilité=profitability\nsolvabilité=solvency",
},
marketing: {
systemPrompt:
"You are translating marketing documents, advertising copy, brand guidelines, and market research reports. Adapt the tone to the target culture while preserving brand voice. Translate idioms and cultural references appropriately — prioritize impact over literal meaning. Preserve all URLs, hashtags, and brand names.",
glossary:
"marché cible=target market\nsegment de marché=market segment\npart de marché=market share\npositionnement=positioning\nciblage=targeting\nstratégie de marque=brand strategy\nidentité visuelle=visual identity\nimage de marque=brand image\nnotoriété=brand awareness\nfidélisation=customer retention\nacquisition de clients=customer acquisition\ntaux de conversion=conversion rate\ntaux d'engagement=engagement rate\nretour sur investissement publicitaire=return on ad spend (ROAS)\ncoste par clic=cost per click (CPC)\ncoste par mille=cost per thousand (CPM)\ncoste par acquisition=cost per acquisition (CPA)\nclick-through rate=taux de clics (CTR)\nbalistique=bounce rate\ntunnel de conversion=conversion funnel\ncall-to-action=call-to-action (CTA)\nlanding page=landing page\nlead=piste / prospect\nprospection=prospecting\nqualification de lead=lead qualification\nCRM=CRM (Customer Relationship Management)\nbase de données=data base\nbase installée=installed base\ncampagne publicitaire=advertising campaign\ncampagne d'emailing=email campaign\nlettre d'information=newsletter\nréseaux sociaux=social media\ncommunity management=community management\ninfluenceur=influencer\npartenariat=partnership\nparrainage=sponsorship\nplacement de produit=product placement\nrelations publiques=public relations (PR)\ncommuniqué de presse=press release\nplan média=media plan\nachat d'espace=media buying\nprogrammatique=programmatic (advertising)\nSEO=référencement naturel (SEO)\nSEA=référencement payant (SEA)\nSEM=référencement sur les moteurs (SEM)\nmots-clés=keywords\ntrafic=traffic\naudience=audience\nreach=portée\nimpressions=impressions\nvues=views\npartages=shares\ncommentaires=comments\nabonnés=followers / subscribers\ntaux d'ouverture=open rate\ntaux de désabonnement=unsubscribe rate\nA/B testing=A/B testing\npersonas=buyer personas\nétude de marché=market research\nsondage=survey / poll\ngroupe focus=focus group\npanel=panel\nenquête de satisfaction=customer satisfaction survey\nNet Promoter Score=Net Promoter Score (NPS)\nexpérience client=customer experience (CX)\nparcours client=customer journey\npoints de contact=touchpoints\npromesse de marque=brand promise\nproposition de valeur=value proposition\navantage concurrentiel=competitive advantage\nSWOT=SWOT analysis\nbenchmark=benchmark / competitive analysis\navant-vente=pre-sales\naprès-vente=after-sales\nservice client=customer service\nsatisfaction client=customer satisfaction\nrecommandation=recommendation / referral\nbouche-à-oreille=word-of-mouth",
},
construction: {
systemPrompt:
"You are translating construction, civil engineering, and architecture documents. Use precise technical terminology consistent with European standards (Eurocodes), building codes, and industry practices. Preserve all dimensions, quantities, material specifications, and reference standards exactly.",
glossary:
"chantier=construction site\n gros œuvre=structural work\nsecond œuvre=finishing work\nfondations=foundations\nfondations superficielles=shallow foundations\nfondations profondes=deep foundations\npieux=piles\nsemelle=footing\nradier=raft foundation\nvoile=wall / shear wall\npoteau=column\npoutre=beam\nplancher=floor slab\ndalle=slab\ndallage=ground floor slab\nmaçonnerie=masonry\nbéton armé=reinforced concrete\nbéton précontraint=prestressed concrete\nbéton préfabriqué=precast concrete\ncoulage=pouring (concrete)\ncoffrage=formwork\narmature=reinforcement\nacier d'armature=rebar\ntreillis soudé=welded mesh\ncadre=stirrup\nenrobage=concrete cover\nferraillage=reinforcement detailing\ncoulage en place=cast-in-place\npréfabrication=precast\nmontage=erection\ncharpente=framework / roof structure\ncharpente métallique=steel frame\ncharpente en bois=timber frame\ncouverture=roofing\nétanchéité=waterproofing\nisolation=insulation\nisolation thermique=thermal insulation\nisolation acoustique=acoustic insulation\nrevêtement de façade=facade cladding\nenduit=plaster / render\ncrépi=render\nbardage=cladding\nmenuiserie=joinery\nmenuiserie extérieure=external joinery\nmenuiserie intérieure=internal joinery\nfenêtre=window\nvitrage=glazing\ndouble vitrage=double glazing\nvitrage isolant=insulating glazing\nporte=door\nportail=gate\nfermeture=shutter / closure\nvolet roulant=roller shutter\nquincaillerie=ironmongery / hardware\nplomberie=plumbing\nsanitaire=sanitary fixtures\nrobinetterie=taps / faucets\ntuyauterie=piping\ntube=pipe\nraccord=fitting / coupling\nvanne=valve\nrobinet=valve / tap\ncoupe-circuit=circuit breaker\ntableau électrique=distribution board\ncâblage=wiring\nconducteur=conductor / wire\ngaine=conduit / duct\nchemin de câbles=cable tray\ndisjoncteur=circuit breaker\ninterrupteur=switch\nprise=socket / outlet\néclairage=lighting\nluminaire=luminaire / light fixture\nascenseur=elevator\nescalier=staircase\nrampe d'accès=access ramp\ncirculation=corridor / circulation area\ncouloir=corridor\ncage d'escalier=stairwell\nhall=lobby\npermis de construire=building permit\ndéclaration de travaux=works declaration\ndossier d'exécution=construction documents\nplans d'exécution=construction drawings\ndossier technique=technical file\nmétré=bill of quantities\navant-projet=preliminary design\nprojet définitif=detailed design\nmarché=contract (public works)\nentreprise générale=general contractor\nsous-traitant=subcontract\nlot=package / trade lot\nconducteur d'opération=project manager\nmaître d'ouvrage=client / project owner\nmaître d'œuvre=project supervisor / lead consultant\narchitecte=architect\nbureau d'études=engineering consultancy\nbureau de contrôle=inspection body\nORB=building technical controller\nDTU=technical specification document (DTU)\nAvis Technique=Technical Assessment\nRE2020=RE2020 (French energy regulation)",
},
automotive: {
systemPrompt:
"You are translating automotive engineering documents, technical specifications, maintenance manuals, and homologation reports. Use precise automotive terminology consistent with SAE, ISO, and European UNECE standards. Preserve all technical specifications, tolerances, torque values, and reference numbers exactly.",
glossary:
"moteur=engine\nmoteur à combustion=internal combustion engine\nmoteur électrique=electric motor\nmoteur hybride=hybrid engine\nbloc moteur=engine block\nculasse=cylinder head\nvilebrequin=crankshaft\narbre à cames=camshaft\npiston=piston\nbielle=connecting rod\nsoupape=valve\nbougie d'allumage=spark plug\ninjecteur=injector\nturbo-compresseur=turbocharger\ncompresseur=supercharger\ncollecteur d'admission=intake manifold\ncollecteur d'échappement=exhaust manifold\nrapport de compression=compression ratio\ncylindrée=engine displacement\npuissance=power\ncouple=torque\nrégime=engine speed (RPM)\nboîte de vitesses=gearbox\nboîte automatique=automatic transmission\nboîte manuelle=manual transmission\nembrayage=clutch\nconvertisseur de couple=torque converter\narbre de transmission=driveshaft\ndifférentiel=differential\npont=axle\ntransmission=transmission / drivetrain\ntraction=front-wheel drive\npropulsion=rear-wheel drive\ntransmission intégrale=all-wheel drive (AWD)\nquatre roues motrices=four-wheel drive (4WD)\nsuspension=suspension\namortisseur=shock absorber\nressort=spring\nbarre stabilisatrice=stabilizer bar / anti-roll bar\nbras de suspension=control arm\ntriangle de suspension=wishbone\nroue=wheel\npneu=tire\njante=rim\npneumatique=tire\npression des pneus=tire pressure\nfrein=brake\nfrein à disque=disc brake\nfrein à tambour=drum brake\nétrier=caliper\ndisque de frein=brake disc\nplaque de frein=brake pad\nservo-frein=brake booster\nABS=ABS (Anti-lock Braking System)\nESP=ESP (Electronic Stability Program)\ndirection=steering\ncrémaillère de direction=rack and pinion\nassistance de direction=power steering\ncarrosserie=bodywork\nchâssis=chassis\npare-choc=bumper\naile=fender\ncapot=hood\ncoffre=trunk / boot\ntoit=roof\ntoit ouvrant=sunroof\nvitres électriques=power windows\nrétroviseur=mirror\ncapot moteur=hood / bonnet\nhabitacle=cabin / passenger compartment\ntableau de bord=dashboard\ncombiné d'instrument=instrument cluster\nvolant=steering wheel\nsiège=seat\nceinture de sécurité=seatbelt\nairbag=airbag\néclairage=lighting\nphare=headlight\nfeu arrière=taillight\nclignotant=turn signal / indicator\nfeu de frein=brake light\nantibrouillard=fog light\némissions=emissions\npollution=pollution\npot catalytique=catalytic converter\nfiltre à particules=particulate filter (DPF)\nNorme Euro=Euro standard\nhomologation=type approval\ncontrôle technique=MOT test / vehicle inspection\nrévision=service / maintenance\nvidange=oil change\ncourroie de distribution=timing belt\nliquide de refroidissement=coolant\nliquide de frein=brake fluid\nhuile moteur=engine oil\nbatterie=battery\nbatterie lithium-ion=lithium-ion battery\nautonomie=range\nrecharge=charging\nborne de recharge=charging station\nvéhicule électrique=electric vehicle (EV)\nvéhicule hybride rechargeable=plug-in hybrid (PHEV)\nvehicule autonome=autonomous vehicle\nADAS=Advanced Driver Assistance Systems\nassistance au maintien dans la voie=lane keeping assist\nrégulateur de vitesse adaptatif=adaptive cruise control\nfreinage d'urgence automatique=automatic emergency braking\nalerte de franchissement=lane departure warning",
},
};
const defaultSettings: TranslationSettings = {
defaultTargetLanguage: "fr",
defaultProvider: "google",
translateImages: false,
ollamaUrl: "http://localhost:11434",
ollamaModel: "",
openaiApiKey: "",
openaiModel: "gpt-4o-mini",
openrouterApiKey: "",
openrouterModel: "deepseek/deepseek-chat-v3-0324",
libreTranslateUrl: "",
systemPrompt: "",
glossary: "",
adminToken: undefined,
};
export const useTranslationStore = create<TranslationState>()(
persist(
(set) => ({
settings: defaultSettings,
updateSettings: (partial) =>
set((state) => ({
settings: { ...state.settings, ...partial },
})),
setAdminToken: (token) =>
set((state) => ({
settings: { ...state.settings, adminToken: token },
})),
applyPreset: (preset) =>
set((state) => ({
settings: {
...state.settings,
...PRESETS[preset],
},
})),
clearContext: () =>
set((state) => ({
settings: { ...state.settings, systemPrompt: "", glossary: "" },
})),
}),
{
name: "translation-settings",
},
),
);

View File

@@ -0,0 +1,5 @@
export interface ApiResponse<T = unknown> {
data?: T;
message?: string;
error?: string;
}

View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -0,0 +1,46 @@
import { useState, useCallback } from "react";
interface WebLLMState {
isLoaded: boolean;
loading: boolean;
error: string | null;
}
export function useWebLLM() {
const [state, setState] = useState<WebLLMState>({
isLoaded: false,
loading: false,
error: null,
});
const isWebGPUSupported = useCallback(() => {
if (typeof navigator === "undefined") return false;
return "gpu" in navigator;
}, []);
const translate = useCallback(
async (
_text: string,
_targetLang: string,
_systemPrompt?: string,
_glossary?: string,
): Promise<string> => {
setState((s) => ({ ...s, loading: true, error: null }));
try {
throw new Error("WebLLM is not available in this environment");
} catch (err) {
const message =
err instanceof Error ? err.message : "WebLLM translation failed";
setState((s) => ({ ...s, loading: false, error: message }));
throw err;
}
},
[],
);
return {
...state,
isWebGPUSupported,
translate,
};
}