i18n: fix missing keys and translate all non-admin frontend strings
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m43s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m43s
- Add 12 missing i18n keys (t() was returning the literal key string) to
all 13 locales: dashboard.topbar.premiumAccess,
dashboard.translate.complete.toastOkDesc,
dashboard.translate.progress.{connectionLost,processingFallback},
glossaries.card.{term,created}, glossaries.termEditor.{addTerm,maxReached},
login.google.{connecting,errorFailed,errorGeneric}, login.orContinueWith
- Add 6 FR-drift keys (landing.pricing.{free,enterprise}.{name,desc,cta})
- Add ~120 new i18n keys covering site header/footer, file-uploader,
checkout success, dashboard pages, translate page, provider selector
themes, language selector, translation complete, api-keys, services,
settings, pricing (~1800 new key/locale pairs)
- Wrap hardcoded French/English in components with t() calls
- Convert LLM_THEMES/CLASSIC_THEMES/FALLBACK_PROVIDERS maps from
hardcoded constants to t()-driven factories
- Admin pages intentionally left untouched per request
Files: 15 components/pages + src/lib/i18n.tsx
Typecheck: passes (tsc --noEmit exit 0)
This commit is contained in:
@@ -182,6 +182,12 @@ export default function TranslatePage() {
|
||||
const tgtLangName = config.languages.find(l => l.code === config.targetLang)?.name || config.targetLang;
|
||||
const activeStepIdx = getActiveStepIdx(submit.progress);
|
||||
const qualityLabel = useMemo(() => getQualityLabel(t, config.provider), [t, config.provider]);
|
||||
const fileTypeButtons = [
|
||||
{ label: t('translate.fileType.word'), type: 'word' as const, icon: <FileText size={11} className="text-blue-500" /> },
|
||||
{ label: t('translate.fileType.excel'), type: 'excel' as const, icon: <FileSpreadsheet size={11} className="text-green-500" /> },
|
||||
{ label: t('translate.fileType.slides'), type: 'slides' as const, icon: <Presentation size={11} className="text-orange-500" /> },
|
||||
{ label: t('translate.fileType.pdf'), type: 'pdf' as const, icon: <FileType size={11} className="text-red-500" /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-full p-6 pb-24 lg:pb-8 lg:p-8 dark:bg-[#0a0a0a] selection:bg-brand-accent/10">
|
||||
@@ -191,19 +197,27 @@ export default function TranslatePage() {
|
||||
<div className="mb-12">
|
||||
{showProcessing ? (
|
||||
<>
|
||||
<span className="accent-pill mb-4 block w-fit italic">Traitement en cours</span>
|
||||
<span className="accent-pill mb-4 block w-fit italic">{t('translate.header.processing')}</span>
|
||||
<h1 className="text-4xl md:text-5xl mb-3 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">
|
||||
Analyse IA <span className="italic">Active</span>
|
||||
{(() => {
|
||||
const full = t('translate.header.aiActive');
|
||||
const i = full.lastIndexOf(' ');
|
||||
return <>{i === -1 ? full : <>{full.slice(0, i)} <span className="italic">{full.slice(i + 1)}</span></>}</>;
|
||||
})()}
|
||||
</h1>
|
||||
<p className="text-brand-dark/50 dark:text-white/50 text-sm font-light leading-relaxed">
|
||||
Votre mise en page est en cours de préservation par notre moteur contextuel.
|
||||
{t('translate.header.aiActiveDesc')}
|
||||
</p>
|
||||
</>
|
||||
) : showComplete ? (
|
||||
<>
|
||||
<span className="accent-pill mb-4 block w-fit italic">Complété</span>
|
||||
<span className="accent-pill mb-4 block w-fit italic">{t('translate.header.completed')}</span>
|
||||
<h1 className="text-4xl md:text-5xl mb-3 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">
|
||||
Traduction <span className="italic">terminée</span>
|
||||
{(() => {
|
||||
const full = t('translate.header.completedTitle');
|
||||
const i = full.lastIndexOf(' ');
|
||||
return <>{i === -1 ? full : <>{full.slice(0, i)} <span className="italic">{full.slice(i + 1)}</span></>}</>;
|
||||
})()}
|
||||
</h1>
|
||||
<p className="text-brand-dark/50 dark:text-white/50 text-sm font-light leading-relaxed truncate max-w-xl">
|
||||
{submit.fileName}
|
||||
@@ -211,12 +225,16 @@ export default function TranslatePage() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="accent-pill mb-4 block w-fit">Espace Pro</span>
|
||||
<span className="accent-pill mb-4 block w-fit">{t('translate.header.proSpace')}</span>
|
||||
<h1 className="text-4xl md:text-5xl mb-3 leading-tight text-brand-dark dark:text-white font-serif font-medium tracking-tight">
|
||||
Traduire un <span className="italic">document</span>
|
||||
{(() => {
|
||||
const full = t('translate.header.translateDoc');
|
||||
const i = full.lastIndexOf(' ');
|
||||
return <>{i === -1 ? full : <>{full.slice(0, i)} <span className="italic">{full.slice(i + 1)}</span></>}</>;
|
||||
})()}
|
||||
</h1>
|
||||
<p className="text-brand-dark/50 dark:text-white/50 text-sm font-light leading-relaxed">
|
||||
Conservez la mise en page d'origine grâce au moteur de traduction ultra-haute fidélité.
|
||||
{t('translate.header.translateDocDesc')}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
@@ -240,7 +258,7 @@ export default function TranslatePage() {
|
||||
onClick={() => dropzoneInputRef.current?.click()}
|
||||
>
|
||||
<div className="absolute top-4 right-4 text-[8px] font-bold uppercase tracking-widest text-brand-dark/30 dark:text-white/30 bg-brand-muted dark:bg-white/5 px-3 py-1 rounded-full border border-black/[0.03] dark:border-white/[0.03]">
|
||||
Format natif
|
||||
{t('translate.upload.nativeFormat')}
|
||||
</div>
|
||||
|
||||
<div className="w-16 h-16 bg-brand-muted dark:bg-white/5 rounded-2xl flex items-center justify-center text-brand-accent group-hover:scale-105 group-hover:bg-brand-dark dark:group-hover:bg-brand-accent group-hover:text-white dark:group-hover:text-brand-dark transition-all duration-300 mb-6 shadow-sm">
|
||||
@@ -256,12 +274,7 @@ export default function TranslatePage() {
|
||||
|
||||
{/* Simulated file triggers */}
|
||||
<div className="flex flex-wrap justify-center gap-2.5" onClick={(e) => e.stopPropagation()}>
|
||||
{[
|
||||
{ label: 'Word (.docx)', type: 'word' as const, icon: <FileText size={11} className="text-blue-500" /> },
|
||||
{ label: 'Excel (.xlsx)', type: 'excel' as const, icon: <FileSpreadsheet size={11} className="text-green-500" /> },
|
||||
{ label: 'Slides (.pptx)', type: 'slides' as const, icon: <Presentation size={11} className="text-orange-500" /> },
|
||||
{ label: 'PDF (.pdf)', type: 'pdf' as const, icon: <FileType size={11} className="text-red-500" /> },
|
||||
].map(f => (
|
||||
{fileTypeButtons.map(f => (
|
||||
<button
|
||||
key={f.type}
|
||||
type="button"
|
||||
@@ -310,17 +323,17 @@ export default function TranslatePage() {
|
||||
)}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<><Loader2 className="size-4 animate-spin" /> {t('dashboard.translate.actions.uploading') || 'Soumission...'}</>
|
||||
<><Loader2 className="size-4 animate-spin" /> {t('translate.submit')}</>
|
||||
) : (
|
||||
<>Lancer la traduction <ArrowRight size={13} className={upload.file ? 'text-brand-accent' : 'opacity-20'} /></>
|
||||
<>{t('translate.startTranslation')} <ArrowRight size={13} className={upload.file ? 'text-brand-accent' : 'opacity-20'} /></>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{!upload.file && (
|
||||
<p className="text-center text-[8px] text-brand-dark/30 dark:text-white/30 font-bold uppercase tracking-widest">{t('dashboard.translate.noFile') || 'Veuillez charger un fichier'}</p>
|
||||
<p className="text-center text-[8px] text-brand-dark/30 dark:text-white/30 font-bold uppercase tracking-widest">{t('translate.pleaseLoadFile')}</p>
|
||||
)}
|
||||
{upload.file && !config.targetLang && (
|
||||
<p className="text-center text-[8px] text-brand-dark/30 dark:text-white/30 font-bold uppercase tracking-widest">{t('dashboard.translate.noTargetLang') || 'Veuillez choisir une langue cible'}</p>
|
||||
<p className="text-center text-[8px] text-brand-dark/30 dark:text-white/30 font-bold uppercase tracking-widest">{t('translate.chooseTargetLang')}</p>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between text-[7.5px] font-bold uppercase tracking-[0.15em] text-brand-dark/30 dark:text-white/30 border-t border-black/5 dark:border-white/5 pt-4">
|
||||
@@ -343,7 +356,7 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<h3 className="text-2xl font-serif font-medium text-brand-dark dark:text-white tracking-tight">
|
||||
Moteur contextuel actif
|
||||
{t('translate.contextEngineActive')}
|
||||
</h3>
|
||||
<p className="text-[10px] text-brand-dark/30 dark:text-white/30 font-bold uppercase tracking-widest mt-1">
|
||||
{submit.fileName || upload.file?.name}
|
||||
@@ -376,7 +389,7 @@ export default function TranslatePage() {
|
||||
|
||||
<div className="flex justify-between items-end mt-12 pt-6">
|
||||
<span className="text-[10px] font-bold text-brand-dark/30 dark:text-white/30 uppercase tracking-[0.3em]">
|
||||
{activeStepIdx < 2 ? 'Phase 1: Initialisation' : 'Phase 2: Reconstruction Contextuelle'}
|
||||
{activeStepIdx < 2 ? t('translate.phase1') : t('translate.phase2')}
|
||||
</span>
|
||||
<span className="text-7xl font-serif font-medium text-brand-dark dark:text-white leading-none">
|
||||
{Math.round(submit.progress)}%
|
||||
@@ -384,10 +397,10 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-4 gap-4 pt-12 border-t border-black/5 dark:border-white/5">
|
||||
<StatBox icon={<FileText size={18} />} value={`${Math.round(submit.progress)}%`} label="segments" />
|
||||
<StatBox icon={<Zap size={18} />} value="99.9%" label="précision" />
|
||||
<StatBox icon={<Clock size={18} />} value="Turbo" label="vitesse" />
|
||||
<StatBox icon={<Activity size={18} />} value={formatElapsed(elapsed)} label="temps" />
|
||||
<StatBox icon={<FileText size={18} />} value={`${Math.round(submit.progress)}%`} label={t('translate.stat.segments')} />
|
||||
<StatBox icon={<Zap size={18} />} value="99.9%" label={t('translate.stat.precision')} />
|
||||
<StatBox icon={<Clock size={18} />} value="Turbo" label={t('translate.stat.speedLabel')} />
|
||||
<StatBox icon={<Activity size={18} />} value={formatElapsed(elapsed)} label={t('translate.stat.time')} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -402,7 +415,7 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-[13px] font-bold uppercase tracking-[0.1em] text-brand-dark dark:text-white">
|
||||
Traduction terminée
|
||||
{t('translate.header.completedTitle')}
|
||||
</p>
|
||||
<p className="text-[10px] text-brand-dark/40 dark:text-white/40 font-bold uppercase mt-1 tracking-widest max-w-[300px] truncate">
|
||||
{submit.fileName}
|
||||
@@ -410,7 +423,7 @@ export default function TranslatePage() {
|
||||
</div>
|
||||
</div>
|
||||
<span className="px-5 py-2 bg-white dark:bg-[#1a1a1a] rounded-full text-[9px] font-bold uppercase tracking-widest text-brand-accent border border-brand-accent/20 shadow-sm shrink-0">
|
||||
✓ Qualité Maître
|
||||
{t('translate.complete.masterQuality')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -420,13 +433,13 @@ export default function TranslatePage() {
|
||||
className="premium-button px-24 py-6 text-xl !rounded-full flex items-center gap-6 mb-8 group cursor-pointer hover:scale-[1.02] active:scale-95"
|
||||
>
|
||||
<Download size={28} className="group-hover:translate-y-1 transition-transform" />
|
||||
Télécharger
|
||||
{t('translate.download')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleNewTranslation}
|
||||
className="text-[10px] font-bold uppercase tracking-[0.3em] text-brand-dark/30 hover:text-brand-dark dark:text-white/30 dark:hover:text-white transition-colors"
|
||||
>
|
||||
+ Nouvelle traduction
|
||||
{t('translate.newTranslation')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -441,7 +454,7 @@ export default function TranslatePage() {
|
||||
<AlertTriangle className="size-5 text-red-500" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<p className="text-sm font-bold uppercase tracking-tight text-red-600 dark:text-red-400 mb-2">Erreur lors de la traduction</p>
|
||||
<p className="text-sm font-bold uppercase tracking-tight text-red-600 dark:text-red-400 mb-2">{t('translate.failedTitle')}</p>
|
||||
<p className="text-xs text-red-600/80 dark:text-red-300/80 leading-relaxed font-medium">{humanFriendlyError(submit.error)}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -459,7 +472,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 cursor-pointer hover:scale-[1.01] active:scale-98"
|
||||
>
|
||||
<RotateCcw size={18} />
|
||||
Réessayer
|
||||
{t('translate.retry')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -467,7 +480,7 @@ export default function TranslatePage() {
|
||||
className="w-full py-4 border border-black/10 dark:border-white/10 rounded-2xl text-[10px] font-bold uppercase tracking-[0.25em] text-brand-dark/50 dark:text-white/50 hover:text-brand-dark dark:hover:text-white transition-all flex items-center justify-center gap-3 cursor-pointer hover:bg-brand-muted/30 dark:hover:bg-white/5"
|
||||
>
|
||||
<Upload size={16} />
|
||||
Téléverser un autre fichier
|
||||
{t('translate.uploadAnother')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -556,7 +569,7 @@ export default function TranslatePage() {
|
||||
{config.mode === 'classic' ? (
|
||||
<div className="p-2.5 bg-brand-dark/5 dark:bg-white/5 rounded-lg text-center">
|
||||
<span className="text-[7.5px] font-black uppercase opacity-45 block">
|
||||
Indisponible en mode Standard (IA uniquement)
|
||||
{t('translate.unavailableStandard')}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
@@ -590,7 +603,7 @@ export default function TranslatePage() {
|
||||
{t('dashboard.translate.pdfMode.preserveLayout') || 'Mise en page'}
|
||||
</div>
|
||||
<p className="mt-1.5 text-[8px] text-brand-dark/40 dark:text-white/40 font-bold uppercase tracking-widest leading-relaxed">
|
||||
Conserver la mise en page
|
||||
{t('translate.preserveLayoutDesc')}
|
||||
</p>
|
||||
</button>
|
||||
<button
|
||||
@@ -605,10 +618,10 @@ export default function TranslatePage() {
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[10px] font-bold uppercase tracking-tight text-brand-dark dark:text-white">
|
||||
<Languages className="size-3.5 text-brand-accent" />
|
||||
Texte brut
|
||||
{t('translate.textOnly')}
|
||||
</div>
|
||||
<p className="mt-1.5 text-[8px] text-brand-dark/40 dark:text-white/40 font-bold uppercase tracking-widest leading-relaxed">
|
||||
Traduction rapide du texte uniquement
|
||||
{t('translate.textOnlyDesc')}
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
@@ -624,7 +637,7 @@ export default function TranslatePage() {
|
||||
<div className="editorial-card p-6 bg-white dark:bg-[#141414] border-none shadow-editorial h-full">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] mb-8 flex items-center gap-3 text-brand-dark/45 dark:text-white/45 pb-3 border-b border-black/[0.03] dark:border-white/[0.03]">
|
||||
<div className="w-2 h-2 bg-brand-accent rounded-full animate-ping" />
|
||||
Moniteur IA
|
||||
{t('translate.monitor')}
|
||||
</h4>
|
||||
|
||||
{/* File summary */}
|
||||
@@ -670,8 +683,8 @@ export default function TranslatePage() {
|
||||
{/* Quality progress */}
|
||||
<div className="pt-6 border-t border-black/5 dark:border-white/5 text-left">
|
||||
<div className="flex justify-between text-[9px] font-bold uppercase tracking-[0.2em] mb-3">
|
||||
<span className="text-brand-dark/40 dark:text-white/40">Intégrité Layout</span>
|
||||
<span className="text-brand-accent">100% SECURE</span>
|
||||
<span className="text-brand-dark/40 dark:text-white/40">{t('translate.layoutIntegrity')}</span>
|
||||
<span className="text-brand-accent">{t('translate.secureHundred')}</span>
|
||||
</div>
|
||||
<div className="h-2 bg-brand-muted dark:bg-white/5 rounded-full overflow-hidden p-0.5">
|
||||
<div
|
||||
@@ -685,7 +698,7 @@ export default function TranslatePage() {
|
||||
onClick={handleNewTranslation}
|
||||
className="w-full mt-12 py-4 border border-red-100 text-red-500 rounded-2xl text-[9px] font-bold uppercase tracking-[0.2em] flex items-center justify-center gap-2 hover:bg-red-50 dark:border-red-950/20 dark:hover:bg-red-950/30 transition-all cursor-pointer"
|
||||
>
|
||||
⟳ Annuler le processus
|
||||
{t('translate.cancelProcess')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -695,7 +708,7 @@ export default function TranslatePage() {
|
||||
<div className="editorial-card p-6 bg-white dark:bg-[#141414] border-none shadow-editorial h-full">
|
||||
<h4 className="text-[10px] font-bold uppercase tracking-[0.2em] mb-8 flex items-center gap-3 text-brand-dark/45 dark:text-white/45 pb-3 border-b border-black/[0.03] dark:border-white/[0.03]">
|
||||
<CheckCircle2 size={14} className="text-emerald-500" />
|
||||
Récapitulatif
|
||||
{t('translate.summary')}
|
||||
</h4>
|
||||
|
||||
<div className="space-y-6 mb-8 px-2 text-left">
|
||||
@@ -717,8 +730,8 @@ export default function TranslatePage() {
|
||||
|
||||
<div className="pt-6 border-t border-black/5 dark:border-white/5 text-left">
|
||||
<div className="flex justify-between text-[9px] font-bold uppercase tracking-[0.2em] mb-3">
|
||||
<span className="text-brand-dark/40 dark:text-white/40">Intégrité Layout</span>
|
||||
<span className="text-brand-accent">100% OK</span>
|
||||
<span className="text-brand-dark/40 dark:text-white/40">{t('translate.layoutIntegrity')}</span>
|
||||
<span className="text-brand-accent">{t('translate.okHundred')}</span>
|
||||
</div>
|
||||
<div className="h-2 bg-brand-muted dark:bg-white/5 rounded-full overflow-hidden p-0.5">
|
||||
<div className="h-full bg-brand-accent rounded-full" style={{ width: '100%' }} />
|
||||
@@ -773,9 +786,9 @@ export default function TranslatePage() {
|
||||
)}
|
||||
>
|
||||
{submit.isSubmitting ? (
|
||||
<><Loader2 className="size-4 animate-spin" /> {t('dashboard.translate.actions.uploading') || 'Soumission...'}</>
|
||||
<><Loader2 className="size-4 animate-spin" /> {t('translate.submit')}</>
|
||||
) : (
|
||||
<>Lancer la traduction <ArrowRight size={13} className={upload.file ? 'text-brand-accent' : 'opacity-20'} /></>
|
||||
<>{t('translate.startTranslation')} <ArrowRight size={13} className={upload.file ? 'text-brand-accent' : 'opacity-20'} /></>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user