fix: remove DeepSeek/MiniMax branding, fix glossary visibility, improve translate page UX
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m7s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m7s
- US1: Replace exposed provider names (DeepSeek V3 → IA Express, MiniMax → IA Avancée) in legacy_routes.py, remove 52 DeepSeek references from i18n pricing/landing keys, add generic modelName key across all 13 languages - US2: Fix glossary selector filtering (fallback to all glossaries when source lang filter returns empty), show templates by default, fix 3 broken presets (hvac/construction/automotive → hr/scientific/ecommerce) - US3: Replace 15 hardcoded French strings with i18n t() calls, increase font sizes from 8-9px to 11px, improve text contrast (opacity /20→/40, /25→/45, /30→/50) - US4: Add missing provider labels in admin ProviderStatus (deepseek, minimax, zai, google_cloud, openrouter, openrouter_premium) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,10 +15,15 @@ interface ProviderStatusProps {
|
||||
|
||||
const PROVIDER_LABELS: Record<string, string> = {
|
||||
google: "Google Translate",
|
||||
google_cloud: "Google Cloud",
|
||||
deepl: "DeepL",
|
||||
ollama: "Ollama (Local)",
|
||||
openai: "OpenAI",
|
||||
openrouter: "OpenRouter",
|
||||
openrouter: "IA Essentielle",
|
||||
openrouter_premium: "IA Premium",
|
||||
deepseek: "IA Express",
|
||||
minimax: "IA Avancée",
|
||||
zai: "Grok (xAI)",
|
||||
};
|
||||
|
||||
const STATUS_CONFIG = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
BookText, Plus, Library, Calendar, Hash,
|
||||
Zap, Save, Trash2, MessageSquare, Loader2,
|
||||
Wrench, HardHat, Monitor, Scale, Stethoscope, BarChart3,
|
||||
Megaphone, Car,
|
||||
Megaphone, Car, ShoppingCart, FlaskConical, Users,
|
||||
} from 'lucide-react';
|
||||
import { useUser } from '@/app/dashboard/useUser';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
@@ -21,14 +21,14 @@ import { useTranslationStore } from '@/lib/store';
|
||||
import { API_BASE } from '@/lib/config';
|
||||
|
||||
const PRESETS = [
|
||||
{ key: 'hvac', title: 'HVAC / Génie climatique', desc: 'Thermique, ventilation, climatisation', icon: Wrench, templateId: 'hvac' },
|
||||
{ key: 'construction', title: 'BTP / Construction', desc: 'Gros œuvre, second œuvre, normes', icon: HardHat, templateId: 'construction' },
|
||||
{ key: 'it', title: 'IT / Logiciel', desc: 'Développement, infrastructure, DevOps', icon: Monitor, templateId: 'technology' },
|
||||
{ key: 'legal', title: 'Juridique / Contrats', desc: 'Droit des affaires, contentieux', icon: Scale, templateId: 'legal' },
|
||||
{ key: 'medical', title: 'Médical / Santé', desc: 'Pharmacologie, chirurgie, diagnostic', icon: Stethoscope, templateId: 'medical' },
|
||||
{ key: 'finance', title: 'Finance / Comptabilité', desc: 'IFRS, bilans, fiscalité', icon: BarChart3, templateId: 'finance' },
|
||||
{ key: 'marketing', title: 'Marketing / Publicité', desc: 'Digital, branding, analytics', icon: Megaphone, templateId: 'marketing' },
|
||||
{ key: 'automotive', title: 'Automobile', desc: 'Motorisation, ADAS, homologation', icon: Car, templateId: 'automotive' },
|
||||
{ key: 'hr', title: 'RH / Ressources Humaines', desc: 'Contrats, politiques, recrutement', icon: Users, templateId: 'hr' },
|
||||
{ key: 'scientific', title: 'Scientifique / Recherche', desc: 'Publications, thèses, articles', icon: FlaskConical, templateId: 'scientific' },
|
||||
{ key: 'ecommerce', title: 'E-commerce / Vente', desc: 'Boutiques en ligne, catalogues, CRM', icon: ShoppingCart, templateId: 'ecommerce' },
|
||||
];
|
||||
|
||||
export default function GlossariesPage() {
|
||||
|
||||
@@ -40,7 +40,7 @@ export function GlossarySelector({ sourceLang, targetLang, isPro, glossaryId, on
|
||||
const [importingId, setImportingId] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [showTemplates, setShowTemplates] = useState(false);
|
||||
const [showTemplates, setShowTemplates] = useState(true);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
@@ -125,17 +125,20 @@ export function GlossarySelector({ sourceLang, targetLang, isPro, glossaryId, on
|
||||
const sourceFlag = SUPPORTED_LANGUAGES.find(l => l.code === sourceLang)?.flag ?? '';
|
||||
const targetFlag = SUPPORTED_LANGUAGES.find(l => l.code === targetLang)?.flag ?? '';
|
||||
|
||||
const filteredGlossaries = sourceLang === 'auto'
|
||||
const langFiltered = sourceLang === 'auto'
|
||||
? glossaries
|
||||
: glossaries.filter(g => g.source_language === sourceLang);
|
||||
|
||||
// If filtering by source language yields nothing, show all glossaries
|
||||
const filteredGlossaries = langFiltered.length > 0 ? langFiltered : glossaries;
|
||||
|
||||
const selected = glossaries.find(g => g.id === glossaryId);
|
||||
|
||||
return (
|
||||
<div className="space-y-2 relative" ref={containerRef}>
|
||||
<label className="text-[9px] font-black text-brand-dark/40 dark:text-white/40 uppercase tracking-[0.2em] block">
|
||||
<BookText size={10} className="inline mr-1.5 text-brand-accent" />
|
||||
{t('translate.glossary.title')} <span className="normal-case tracking-normal font-normal text-brand-dark/25 dark:text-white/25">({sourceFlag}→{targetFlag})</span>
|
||||
<label className="text-[11px] font-black text-brand-dark/50 dark:text-white/50 uppercase tracking-[0.15em] block">
|
||||
<BookText size={12} className="inline mr-1.5 text-brand-accent" />
|
||||
{t('translate.glossary.title')} <span className="normal-case tracking-normal font-normal text-brand-dark/40 dark:text-white/40">({sourceFlag}→{targetFlag})</span>
|
||||
</label>
|
||||
|
||||
{/* Dropdown trigger */}
|
||||
@@ -158,12 +161,12 @@ export function GlossarySelector({ sourceLang, targetLang, isPro, glossaryId, on
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[11px] font-bold truncate text-brand-accent">{selected.name}</div>
|
||||
<div className="text-[9px] text-brand-dark/30 dark:text-white/30">{selected.terms_count} {t('translate.glossary.terms')}</div>
|
||||
<div className="text-[11px] text-brand-dark/50 dark:text-white/50">{selected.terms_count} {t('translate.glossary.terms')}</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => { e.stopPropagation(); onChange(null); }}
|
||||
className="shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-brand-dark/20 hover:text-brand-dark hover:bg-brand-muted dark:text-white/20 dark:hover:text-white dark:hover:bg-white/10 transition-colors"
|
||||
className="shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-brand-dark/40 hover:text-brand-dark hover:bg-brand-muted dark:text-white/40 dark:hover:text-white dark:hover:bg-white/10 transition-colors"
|
||||
>
|
||||
<X size={11} />
|
||||
</button>
|
||||
@@ -182,12 +185,12 @@ export function GlossarySelector({ sourceLang, targetLang, isPro, glossaryId, on
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<ChevronDown size={14} className={cn("shrink-0 text-brand-dark/20 dark:text-white/20 transition-transform", isOpen && "rotate-180")} />
|
||||
<ChevronDown size={14} className={cn("shrink-0 text-brand-dark/40 dark:text-white/40 transition-transform", isOpen && "rotate-180")} />
|
||||
</button>
|
||||
|
||||
{/* Error */}
|
||||
{error && (
|
||||
<p className="text-[10px] text-red-500 pl-1">{error}</p>
|
||||
<p className="text-[11px] text-red-500 pl-1">{error}</p>
|
||||
)}
|
||||
|
||||
{/* Dropdown panel */}
|
||||
@@ -221,7 +224,7 @@ export function GlossarySelector({ sourceLang, targetLang, isPro, glossaryId, on
|
||||
{g.name}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-[9px] text-brand-dark/25 dark:text-white/25">{g.terms_count} {t('translate.glossary.terms')}</span>
|
||||
<span className="text-[10px] text-brand-dark/45 dark:text-white/45">{g.terms_count} {t('translate.glossary.terms')}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -83,32 +83,32 @@ export default function TranslatePage() {
|
||||
|
||||
/* ── Human-friendly error messages ──────────────────────────── */
|
||||
const humanFriendlyError = (raw: string | null): string => {
|
||||
if (!raw) return 'Une erreur inattendue est survenue. Réessayez.';
|
||||
if (!raw) return t('dashboard.translate.error.unexpected');
|
||||
const r = raw.toLowerCase();
|
||||
if (r.includes('0 out of') || r.includes('0 textes') || r.includes('0 texts'))
|
||||
return "La traduction n'a produit aucun résultat. Vérifiez que le document contient du texte, puis réessayez ou choisissez un autre moteur.";
|
||||
return t('dashboard.translate.error.noResult');
|
||||
if (r.includes('api key') || r.includes('api_key') || r.includes('unauthorized') || r.includes('401'))
|
||||
return "Clé API invalide ou manquante. Contactez l'administrateur pour configurer les clés.";
|
||||
return t('dashboard.translate.error.apiKey');
|
||||
if (r.includes('quota') || r.includes('rate limit') || r.includes('429'))
|
||||
return "Limite d'utilisation atteinte. Réessayez dans quelques minutes ou choisissez un autre moteur.";
|
||||
return t('dashboard.translate.error.quota');
|
||||
if (r.includes('timeout') || r.includes('timed out') || r.includes('connexion'))
|
||||
return "La connexion au service de traduction a expiré. Vérifiez votre réseau et réessayez.";
|
||||
return t('dashboard.translate.error.timeout');
|
||||
if (r.includes('not found') || r.includes('404'))
|
||||
return "La session a expiré. Cliquez sur Réessayer pour relancer la traduction.";
|
||||
return t('dashboard.translate.error.sessionExpired');
|
||||
if (r.includes('empty') || r.includes('vide') || r.includes('no translatable'))
|
||||
return "Le document semble vide ou ne contient pas de texte traduisible (PDF image ?).";
|
||||
return t('dashboard.translate.error.empty');
|
||||
if (r.includes('unsupported') || r.includes('format'))
|
||||
return "Format de fichier non supporté ou fichier corrompu.";
|
||||
return t('dashboard.translate.error.unsupported');
|
||||
if (r.includes('lost connection') || r.includes('internet'))
|
||||
return "Connexion perdue. Vérifiez votre réseau et réessayez.";
|
||||
return t('dashboard.translate.error.connection');
|
||||
// Generic fallback — show raw but readable
|
||||
return `Traduction échouée : ${raw.charAt(0).toUpperCase() + raw.slice(1)}`;
|
||||
return t('dashboard.translate.error.generic', { detail: raw.charAt(0).toUpperCase() + raw.slice(1) });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (submit.error && submit.error !== lastErrorRef.current) {
|
||||
lastErrorRef.current = submit.error;
|
||||
showError({ title: 'Traduction échouée', description: humanFriendlyError(submit.error) });
|
||||
showError({ title: t('dashboard.translate.error.title'), description: humanFriendlyError(submit.error) });
|
||||
}
|
||||
}, [submit.error, showError]);
|
||||
|
||||
@@ -275,7 +275,7 @@ export default function TranslatePage() {
|
||||
{/* ── CONFIGURING STATE: File strip ─────────────────── */}
|
||||
{showConfiguring && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial dark:bg-[#141414]">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/30 border-b border-black/5 pb-6 dark:text-white/30 dark:border-white/5">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/50 border-b border-black/5 pb-6 dark:text-white/50 dark:border-white/5">
|
||||
{t('landing.translate.sourceDocument')}
|
||||
</h4>
|
||||
<FileStrip file={upload.file!} onRemove={upload.removeFile} onReplace={() => replaceInputRef.current?.click()} t={t} />
|
||||
@@ -295,7 +295,7 @@ export default function TranslatePage() {
|
||||
<h3 className="text-2xl font-black uppercase tracking-tight mb-2 text-brand-dark dark:text-white">
|
||||
{t('dashboard.translate.translating')}
|
||||
</h3>
|
||||
<p className="text-[10px] text-brand-dark/30 font-black uppercase tracking-widest dark:text-white/30">
|
||||
<p className="text-[10px] text-brand-dark/50 font-black uppercase tracking-widest dark:text-white/50">
|
||||
{submit.fileName || upload.file?.name}
|
||||
</p>
|
||||
</div>
|
||||
@@ -311,7 +311,7 @@ export default function TranslatePage() {
|
||||
'w-12 h-12 rounded-2xl border-4 border-white dark:border-[#141414] shadow-xl flex items-center justify-center z-10 transition-all duration-500',
|
||||
submit.progress > (i * 25)
|
||||
? 'bg-brand-dark text-white scale-110 dark:bg-brand-accent'
|
||||
: 'bg-brand-muted text-brand-dark/20 dark:bg-white/10 dark:text-white/20'
|
||||
: 'bg-brand-muted text-brand-dark/40 dark:bg-white/10 dark:text-white/40'
|
||||
)}
|
||||
>
|
||||
<Icon size={18} />
|
||||
@@ -325,7 +325,7 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-end mb-8">
|
||||
<span className="text-[10px] font-black text-brand-dark/30 uppercase tracking-[0.3em] dark:text-white/30">
|
||||
<span className="text-[10px] font-black text-brand-dark/50 uppercase tracking-[0.3em] dark:text-white/50">
|
||||
{activeStepIdx < 2 ? t('dashboard.translate.steps.uploading') : t('dashboard.translate.steps.starting')}
|
||||
</span>
|
||||
<span className="text-7xl font-black text-brand-dark dark:text-white">
|
||||
@@ -360,7 +360,7 @@ export default function TranslatePage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="px-5 py-2 bg-white dark:bg-[#1a1a1a] rounded-full text-[9px] font-black uppercase tracking-widest text-brand-accent border border-brand-accent/20 shadow-sm">
|
||||
<span className="px-5 py-2 bg-white dark:bg-[#1a1a1a] rounded-full text-[11px] font-black uppercase tracking-widest text-brand-accent border border-brand-accent/20 shadow-sm">
|
||||
{qualityLabel}
|
||||
</span>
|
||||
</div>
|
||||
@@ -375,7 +375,7 @@ export default function TranslatePage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNewTranslation}
|
||||
className="text-[10px] font-black uppercase tracking-[0.3em] text-brand-dark/20 hover:text-brand-dark transition-colors dark:text-white/20 dark:hover:text-white"
|
||||
className="text-[10px] font-black uppercase tracking-[0.3em] text-brand-dark/40 hover:text-brand-dark transition-colors dark:text-white/40 dark:hover:text-white"
|
||||
>
|
||||
+ {t('dashboard.translate.complete.newTranslation')}
|
||||
</button>
|
||||
@@ -394,7 +394,7 @@ export default function TranslatePage() {
|
||||
<AlertTriangle className="size-5 text-red-500" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-black uppercase tracking-tight text-red-600 dark:text-red-400 mb-2">Traduction échouée</p>
|
||||
<p className="text-sm font-black uppercase tracking-tight text-red-600 dark:text-red-400 mb-2">{t('dashboard.translate.error.title')}</p>
|
||||
<p className="text-sm text-red-600/80 dark:text-red-300/80 leading-relaxed">{humanFriendlyError(submit.error)}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -416,7 +416,7 @@ export default function TranslatePage() {
|
||||
className="premium-button w-full py-5 text-[12px] uppercase tracking-[0.25em] flex items-center justify-center gap-3 !rounded-2xl"
|
||||
>
|
||||
<RotateCcw size={18} />
|
||||
Réessayer la traduction
|
||||
{t('dashboard.translate.retry')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -424,7 +424,7 @@ export default function TranslatePage() {
|
||||
className="w-full py-4 border-2 border-black/10 dark:border-white/10 rounded-2xl text-[11px] font-black uppercase tracking-[0.25em] text-brand-dark/50 dark:text-white/50 hover:text-brand-dark dark:hover:text-white hover:border-brand-dark/20 dark:hover:border-white/20 transition-all flex items-center justify-center gap-3"
|
||||
>
|
||||
<Upload size={16} />
|
||||
Nouveau fichier
|
||||
{t('dashboard.translate.newFile')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -441,7 +441,7 @@ export default function TranslatePage() {
|
||||
<div className="editorial-card bg-white border-none shadow-editorial dark:bg-[#141414] overflow-hidden flex flex-col lg:sticky lg:top-8 lg:max-h-[calc(100vh-6rem)]">
|
||||
{/* Scrollable config content */}
|
||||
<div className="flex-1 overflow-y-auto p-10 pb-6">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/30 border-b border-black/5 pb-6 dark:text-white/30 dark:border-white/5">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.3em] mb-10 text-brand-dark/50 border-b border-black/5 pb-6 dark:text-white/50 dark:border-white/5">
|
||||
{t('landing.translate.configuration')}
|
||||
</h4>
|
||||
|
||||
@@ -463,20 +463,20 @@ export default function TranslatePage() {
|
||||
{config.provider && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn(
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[9px] font-black uppercase tracking-[0.15em]",
|
||||
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[10px] font-black uppercase tracking-[0.15em]",
|
||||
config.mode === 'llm'
|
||||
? "bg-brand-accent/10 text-brand-accent border border-brand-accent/20"
|
||||
: "bg-brand-muted/50 text-brand-dark/40 dark:bg-white/5 dark:text-white/40 border border-transparent"
|
||||
)}>
|
||||
{config.mode === 'llm' ? (
|
||||
<><Zap size={10} /> Mode IA</>
|
||||
<><Zap size={10} /> {t('dashboard.translate.modeAI')}</>
|
||||
) : (
|
||||
<><Languages size={10} /> Mode Classique</>
|
||||
<><Languages size={10} /> {t('dashboard.translate.modeClassic')}</>
|
||||
)}
|
||||
</span>
|
||||
{config.mode === 'classic' && config.isPro && (
|
||||
<span className="text-[9px] text-brand-dark/20 dark:text-white/20 italic">
|
||||
Glossaires disponibles en mode IA
|
||||
<span className="text-[11px] text-brand-dark/40 dark:text-white/40 italic">
|
||||
{t('dashboard.translate.glossaryLLMHint')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -497,7 +497,7 @@ export default function TranslatePage() {
|
||||
{/* PDF mode selector */}
|
||||
{isPdf && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-[9px] font-black text-brand-dark/40 uppercase tracking-[0.2em] block mb-3 dark:text-white/40">
|
||||
<label className="text-[11px] font-black text-brand-dark/50 uppercase tracking-[0.15em] block mb-3 dark:text-white/50">
|
||||
{t('dashboard.translate.pdfMode.title')}
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
@@ -515,7 +515,7 @@ export default function TranslatePage() {
|
||||
<FileText className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.preserveLayout')}
|
||||
</div>
|
||||
<p className="mt-1 text-[9px] text-brand-dark/50 font-bold uppercase tracking-widest leading-relaxed dark:text-white/40">
|
||||
<p className="mt-1 text-[11px] text-brand-dark/55 font-bold uppercase tracking-widest leading-relaxed dark:text-white/50">
|
||||
{t('dashboard.translate.pdfMode.preserveLayoutDesc')}
|
||||
</p>
|
||||
</button>
|
||||
@@ -533,7 +533,7 @@ export default function TranslatePage() {
|
||||
<Languages className="size-4 text-brand-accent" />
|
||||
{t('dashboard.translate.pdfMode.textOnly')}
|
||||
</div>
|
||||
<p className="mt-1 text-[9px] text-brand-dark/50 font-bold uppercase tracking-widest leading-relaxed dark:text-white/40">
|
||||
<p className="mt-1 text-[11px] text-brand-dark/55 font-bold uppercase tracking-widest leading-relaxed dark:text-white/50">
|
||||
{t('dashboard.translate.pdfMode.textOnlyDesc')}
|
||||
</p>
|
||||
</button>
|
||||
@@ -552,24 +552,24 @@ export default function TranslatePage() {
|
||||
'w-full py-5 rounded-2xl text-[13px] font-black uppercase tracking-[0.2em] flex items-center justify-center gap-3 transition-all duration-200',
|
||||
config.isConfigValid && upload.file && !submit.isSubmitting
|
||||
? 'bg-brand-dark text-white hover:bg-brand-dark/90 shadow-lg shadow-brand-dark/20 dark:bg-brand-accent dark:text-brand-dark dark:hover:bg-brand-accent/90'
|
||||
: 'bg-black/5 dark:bg-white/5 text-brand-dark/25 dark:text-white/25 cursor-not-allowed'
|
||||
: 'bg-black/5 dark:bg-white/5 text-brand-dark/45 dark:text-white/45 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<><Loader2 className="size-5 animate-spin" />Envoi en cours...</>
|
||||
<><Loader2 className="size-5 animate-spin" />{t('dashboard.translate.submitting')}</>
|
||||
) : (
|
||||
<><ArrowRight size={20} />Lancer la traduction</>
|
||||
<><ArrowRight size={20} />{t('dashboard.translate.submit')}</>
|
||||
)}
|
||||
</button>
|
||||
{!upload.file && (
|
||||
<p className="text-center text-[10px] text-brand-dark/30 dark:text-white/30 mt-2 font-bold uppercase tracking-widest">↑ Déposez d'abord un fichier</p>
|
||||
<p className="text-center text-[11px] text-brand-dark/40 dark:text-white/40 mt-2 font-bold uppercase tracking-widest">↑ {t('dashboard.translate.noFile')}</p>
|
||||
)}
|
||||
{upload.file && !config.targetLang && (
|
||||
<p className="text-center text-[10px] text-brand-dark/30 dark:text-white/30 mt-2 font-bold uppercase tracking-widest">↑ Sélectionnez une langue cible</p>
|
||||
<p className="text-center text-[11px] text-brand-dark/40 dark:text-white/40 mt-2 font-bold uppercase tracking-widest">↑ {t('dashboard.translate.noTargetLang')}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 flex justify-between px-6 pb-5 pt-1 text-[9px] font-black uppercase tracking-[0.2em] text-brand-dark/20 dark:text-white/20">
|
||||
<div className="shrink-0 flex justify-between px-6 pb-5 pt-1 text-[10px] font-black uppercase tracking-[0.15em] text-brand-dark/40 dark:text-white/40">
|
||||
<span className="flex items-center gap-2">
|
||||
<ShieldCheck size={12} /> {t('landing.translate.zeroRetention')}
|
||||
</span>
|
||||
@@ -583,7 +583,7 @@ export default function TranslatePage() {
|
||||
{/* ── MONITOR (processing) ────────────────────────────── */}
|
||||
{showProcessing && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial h-full dark:bg-[#141414]">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.4em] mb-12 flex items-center gap-3 text-brand-dark/30 dark:text-white/30">
|
||||
<h4 className="text-[11px] font-black uppercase tracking-[0.3em] mb-12 flex items-center gap-3 text-brand-dark/45 dark:text-white/45">
|
||||
<div className="w-2 h-2 bg-brand-accent rounded-full animate-ping" />
|
||||
{t('dashboard.translate.liveMonitor')}
|
||||
</h4>
|
||||
@@ -603,7 +603,7 @@ export default function TranslatePage() {
|
||||
<p className="text-[11px] font-black uppercase tracking-tight truncate text-brand-dark dark:text-white">
|
||||
{submit.fileName || upload.file?.name}
|
||||
</p>
|
||||
<p className="text-[9px] text-brand-dark/40 font-bold uppercase tracking-widest mt-1 dark:text-white/40">
|
||||
<p className="text-[11px] text-brand-dark/45 font-bold uppercase tracking-widest mt-1 dark:text-white/45">
|
||||
{upload.file ? `${fmt(upload.file.size)} ` : ''}{(submit.fileName || upload.file?.name || '').split('.').pop()?.toUpperCase()}
|
||||
</p>
|
||||
</div>
|
||||
@@ -612,16 +612,16 @@ export default function TranslatePage() {
|
||||
|
||||
{/* Config summary */}
|
||||
<div className="space-y-8 mb-16 px-2">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.source')}</span>
|
||||
<span className="text-brand-dark dark:text-white">{srcLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.target')}</span>
|
||||
<span className="text-brand-accent">{tgtLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
{currentProvider && (
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.engine')}</span>
|
||||
<span className="text-brand-dark dark:text-white">{currentProvider.label.toUpperCase()}</span>
|
||||
</div>
|
||||
@@ -631,7 +631,7 @@ export default function TranslatePage() {
|
||||
{/* Quality progress */}
|
||||
<div className="pt-10 border-t border-black/10 dark:border-white/10">
|
||||
<div className="flex justify-between text-[10px] font-black uppercase tracking-[0.4em] mb-4">
|
||||
<span className="text-brand-dark/30 dark:text-white/30">{t('dashboard.translate.quality')}</span>
|
||||
<span className="text-brand-dark/50 dark:text-white/50">{t('dashboard.translate.quality')}</span>
|
||||
<span className="text-brand-accent">{qualityLabel}</span>
|
||||
</div>
|
||||
<div className="h-2 bg-brand-muted rounded-full overflow-hidden p-0.5 dark:bg-white/10">
|
||||
@@ -654,22 +654,22 @@ export default function TranslatePage() {
|
||||
{/* ── SUMMARY (complete) ──────────────────────────────── */}
|
||||
{showComplete && (
|
||||
<div className="editorial-card p-10 bg-white border-none shadow-editorial h-full dark:bg-[#141414]">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-[0.4em] mb-12 flex items-center gap-3 text-brand-dark/30 dark:text-white/30">
|
||||
<h4 className="text-[11px] font-black uppercase tracking-[0.3em] mb-12 flex items-center gap-3 text-brand-dark/45 dark:text-white/45">
|
||||
<CheckCircle2 size={14} className="text-emerald-500" />
|
||||
{t('dashboard.translate.summary')}
|
||||
</h4>
|
||||
|
||||
<div className="space-y-8 mb-16 px-2">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.source')}</span>
|
||||
<span className="text-brand-dark dark:text-white">{srcLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.language.target')}</span>
|
||||
<span className="text-brand-accent">{tgtLangName.toUpperCase()}</span>
|
||||
</div>
|
||||
{currentProvider && (
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/30 dark:text-white/30">
|
||||
<div className="flex justify-between items-center text-[10px] font-black uppercase tracking-[0.4em] text-brand-dark/50 dark:text-white/50">
|
||||
<span>{t('dashboard.translate.engine')}</span>
|
||||
<span className="text-brand-dark dark:text-white">{currentProvider.label.toUpperCase()}</span>
|
||||
</div>
|
||||
@@ -678,7 +678,7 @@ export default function TranslatePage() {
|
||||
|
||||
<div className="pt-10 border-t border-black/10 dark:border-white/10">
|
||||
<div className="flex justify-between text-[10px] font-black uppercase tracking-[0.4em] mb-4">
|
||||
<span className="text-brand-dark/30 dark:text-white/30">{t('dashboard.translate.quality')}</span>
|
||||
<span className="text-brand-dark/50 dark:text-white/50">{t('dashboard.translate.quality')}</span>
|
||||
<span className="text-brand-accent">{qualityLabel}</span>
|
||||
</div>
|
||||
<div className="h-2 bg-brand-muted rounded-full overflow-hidden p-0.5 dark:bg-white/10">
|
||||
@@ -701,7 +701,7 @@ export default function TranslatePage() {
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<span className="accent-pill !px-3 !py-1 text-[9px] italic">{t('memento.title')}</span>
|
||||
<span className="accent-pill !px-3 !py-1 text-[11px] italic">{t('memento.title')}</span>
|
||||
<h3 className="text-2xl font-black uppercase tracking-tight text-brand-dark dark:text-white">{t('memento.title')}</h3>
|
||||
</div>
|
||||
<p className="text-sm text-brand-dark/40 font-medium leading-relaxed max-w-2xl dark:text-white/40">
|
||||
@@ -736,12 +736,12 @@ function FileStrip({ file, onRemove, onReplace, t }: { file: File; onRemove: ()
|
||||
<FileIcon className={`size-5 shrink-0 ${color}`} />
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<span className="truncate text-[11px] font-black uppercase tracking-tight text-brand-dark dark:text-white">{file.name}</span>
|
||||
<span className="text-[9px] text-brand-dark/40 font-bold uppercase tracking-widest dark:text-white/40">{fmt(file.size)} .{ext.toUpperCase()}</span>
|
||||
<span className="text-[10px] text-brand-dark/45 font-bold uppercase tracking-widest dark:text-white/45">{fmt(file.size)} .{ext.toUpperCase()}</span>
|
||||
</div>
|
||||
<button type="button" onClick={onReplace} className="flex shrink-0 items-center gap-1 rounded-xl px-2 py-1 text-[10px] font-black uppercase tracking-widest text-brand-dark/30 transition hover:bg-brand-muted hover:text-brand-dark dark:text-white/30 dark:hover:bg-white/10 dark:hover:text-white">
|
||||
<button type="button" onClick={onReplace} className="flex shrink-0 items-center gap-1 rounded-xl px-2 py-1 text-[10px] font-black uppercase tracking-widest text-brand-dark/50 transition hover:bg-brand-muted hover:text-brand-dark dark:text-white/50 dark:hover:bg-white/10 dark:hover:text-white">
|
||||
<Upload className="size-3.5" />{t('dashboard.translate.replace')}
|
||||
</button>
|
||||
<button type="button" aria-label="Remove" onClick={onRemove} className="flex size-7 shrink-0 items-center justify-center rounded-xl text-brand-dark/20 transition hover:bg-brand-muted hover:text-brand-dark dark:text-white/20 dark:hover:bg-white/10 dark:hover:text-white">
|
||||
<button type="button" aria-label="Remove" onClick={onRemove} className="flex size-7 shrink-0 items-center justify-center rounded-xl text-brand-dark/40 transition hover:bg-brand-muted hover:text-brand-dark dark:text-white/40 dark:hover:bg-white/10 dark:hover:text-white">
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -789,7 +789,7 @@ function StatBox({ icon, value, label }: { icon: React.ReactNode; value: string;
|
||||
<div className="p-6 bg-brand-muted/30 rounded-3xl text-center border border-transparent hover:border-brand-accent/10 transition-all dark:bg-white/5 dark:border-white/5">
|
||||
<div className="text-brand-accent flex justify-center mb-4">{icon}</div>
|
||||
<p className="text-[12px] font-black text-brand-dark mb-1 uppercase tracking-tight dark:text-white">{value}</p>
|
||||
<p className="text-[8px] font-black text-brand-dark/30 uppercase tracking-[0.2em] dark:text-white/30">{label}</p>
|
||||
<p className="text-[11px] font-black text-brand-dark/50 uppercase tracking-[0.15em] dark:text-white/50">{label}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -814,7 +814,7 @@ export default function PricingPage() {
|
||||
<Badge className="ml-auto text-xs bg-blue-500/10 text-blue-600 border-blue-500/30 dark:text-blue-300">{t('pricing.aiModels.essential.plan')}</Badge>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground mb-3">
|
||||
{t('pricing.aiModels.essential.descPrefix')} <strong className="text-foreground">DeepSeek V3.2</strong> {t('pricing.aiModels.essential.descSuffix')}
|
||||
{t('pricing.aiModels.essential.descPrefix')} <strong className="text-foreground">{t('pricing.aiModels.essential.modelName')}</strong> {t('pricing.aiModels.essential.descSuffix')}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 text-xs">
|
||||
<span className="px-2 py-1 bg-muted rounded">{t('pricing.aiModels.essential.context')}</span>
|
||||
|
||||
Reference in New Issue
Block a user