From 50047ea8a27c924d61d30dea66d2e084502aa9c9 Mon Sep 17 00:00:00 2001 From: sepehr Date: Sun, 30 Aug 2026 21:44:53 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20design=20critique=20overhaul=20?= =?UTF-8?q?=E2=80=94=20a11y,=20honest=20metrics,=20i18n=20repair=20(critiq?= =?UTF-8?q?ue=202026-08-30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0 a11y: keyboard-accessible dropzone (role/tabIndex/Enter-Space), ARIA combobox + listbox pattern for language selector, role=switch on glossary toggle, role=status live region on notifications, aria-labels on password toggles, visible-on-focus close buttons, RTL logical positioning (start-*). Trust: third-party Memento promo removed from translate page, sidebar and all 13 locales; fabricated stats (99.9%, Turbo, computed layout-integrity bars) replaced with real measurements incl. API estimated remaining time; silent download failure now surfaces an error notification. Honesty: fake 100-byte file injections removed (format chips are now informational); cancel-that-doesn't renamed 'Back to start' with hint; Enterprise contact placeholder replaced with contact@wordly.art. i18n: t() no longer returns raw keys (empty string + defaultValue support, ~30 dead || fallbacks now work); ~170 new keys EN+FR across new reviews/ teams namespaces, glossaries context tab, translate monitor, settings, services, pricing, landing, fileUploader; split-key italic titles replace lastIndexOf() surgery (zh/ja-safe); key-audit script added 0 missing. Flow: active job persisted across refresh with polling resume (24h TTL); client-side recent-jobs history with review links; review page linked from complete state; settings/services added to dashboard nav; Business/ Enterprise regain glossary access (tier gate unified). Typeset (sober-tool direction): 7.5-9px labels raised to 10-12px, /30 opacity to /45-/55, uppercase tracking reduced, trust footer legible, country flags removed from language switcher, localized dates. Cleanup: 5 orphaned translate components, dead site header/footer, fossil tailwind.config.js, PipelineStepper, duplicate pill+H1 titles, two-step confirm for cache clear, dead landing footer links. Verified: next build exit 0, vitest 9/9, eslint 64 errors = HEAD (no regression, -3 warnings), detector 4 -> 3 findings. --- .../2026-08-30T19-00-41Z__frontend-src.md | 84 ++++ frontend/src/app/auth/login/LoginForm.tsx | 2 + .../src/app/auth/register/RegisterForm.tsx | 7 +- .../app/dashboard/DashboardLayoutClient.tsx | 6 +- .../src/app/dashboard/DashboardSidebar.tsx | 26 +- frontend/src/app/dashboard/constants.ts | 4 +- .../src/app/dashboard/glossaries/page.tsx | 96 ++--- frontend/src/app/dashboard/profile/page.tsx | 2 +- .../app/dashboard/reviews/[jobId]/page.tsx | 77 ++-- frontend/src/app/dashboard/services/page.tsx | 26 +- frontend/src/app/dashboard/settings/page.tsx | 57 ++- frontend/src/app/dashboard/teams/page.tsx | 48 ++- .../app/dashboard/translate/FileDropZone.tsx | 92 ---- .../app/dashboard/translate/FilePreview.tsx | 53 --- .../dashboard/translate/GlossarySelector.tsx | 7 +- .../dashboard/translate/LanguageSelector.tsx | 40 +- .../translate/TranslationComplete.tsx | 187 -------- .../translate/TranslationModeToggle.tsx | 89 ---- .../translate/TranslationProgress.tsx | 358 --------------- frontend/src/app/dashboard/translate/page.tsx | 408 ++++++++---------- frontend/src/app/dashboard/translate/types.ts | 2 +- .../app/dashboard/translate/useFileUpload.ts | 31 +- .../translate/useTranslationSubmit.ts | 68 +++ frontend/src/app/pricing/page.tsx | 15 +- .../src/components/landing/landing-page.tsx | 21 +- .../src/components/layout/site-footer.tsx | 24 -- .../src/components/layout/site-header.tsx | 43 -- .../src/components/ui/language-switcher.tsx | 35 +- frontend/src/components/ui/notification.tsx | 6 +- frontend/src/lib/i18n.tsx | 13 +- frontend/src/lib/i18n/messages/ar/index.ts | 2 - .../src/lib/i18n/messages/ar/memento.json | 7 - frontend/src/lib/i18n/messages/de/index.ts | 2 - .../src/lib/i18n/messages/de/memento.json | 7 - .../src/lib/i18n/messages/en/dashboard.json | 3 +- .../lib/i18n/messages/en/fileUploader.json | 4 +- .../src/lib/i18n/messages/en/glossaries.json | 33 +- frontend/src/lib/i18n/messages/en/index.ts | 6 +- .../src/lib/i18n/messages/en/landing.json | 3 +- .../lib/i18n/messages/en/langSelector.json | 3 +- .../src/lib/i18n/messages/en/memento.json | 7 - .../src/lib/i18n/messages/en/pricing.json | 7 +- .../src/lib/i18n/messages/en/reviews.json | 32 ++ .../src/lib/i18n/messages/en/services.json | 3 +- .../src/lib/i18n/messages/en/settings.json | 5 +- frontend/src/lib/i18n/messages/en/teams.json | 26 ++ .../src/lib/i18n/messages/en/translate.json | 22 +- frontend/src/lib/i18n/messages/es/index.ts | 2 - .../src/lib/i18n/messages/es/memento.json | 7 - frontend/src/lib/i18n/messages/fa/index.ts | 2 - .../src/lib/i18n/messages/fa/memento.json | 7 - .../src/lib/i18n/messages/fr/dashboard.json | 3 +- .../lib/i18n/messages/fr/fileUploader.json | 4 +- .../src/lib/i18n/messages/fr/glossaries.json | 33 +- frontend/src/lib/i18n/messages/fr/index.ts | 6 +- .../src/lib/i18n/messages/fr/landing.json | 3 +- .../lib/i18n/messages/fr/langSelector.json | 3 +- .../src/lib/i18n/messages/fr/memento.json | 7 - .../src/lib/i18n/messages/fr/pricing.json | 7 +- .../src/lib/i18n/messages/fr/reviews.json | 32 ++ .../src/lib/i18n/messages/fr/services.json | 3 +- .../src/lib/i18n/messages/fr/settings.json | 5 +- frontend/src/lib/i18n/messages/fr/teams.json | 26 ++ .../src/lib/i18n/messages/fr/translate.json | 22 +- frontend/src/lib/i18n/messages/it/index.ts | 2 - .../src/lib/i18n/messages/it/memento.json | 7 - frontend/src/lib/i18n/messages/ja/index.ts | 2 - .../src/lib/i18n/messages/ja/memento.json | 7 - frontend/src/lib/i18n/messages/ko/index.ts | 2 - .../src/lib/i18n/messages/ko/memento.json | 7 - frontend/src/lib/i18n/messages/nl/index.ts | 2 - .../src/lib/i18n/messages/nl/memento.json | 7 - frontend/src/lib/i18n/messages/pt/index.ts | 2 - .../src/lib/i18n/messages/pt/memento.json | 7 - frontend/src/lib/i18n/messages/ru/index.ts | 2 - .../src/lib/i18n/messages/ru/memento.json | 7 - frontend/src/lib/i18n/messages/zh/index.ts | 2 - .../src/lib/i18n/messages/zh/memento.json | 7 - frontend/tailwind.config.js | 134 ------ 79 files changed, 878 insertions(+), 1590 deletions(-) create mode 100644 .impeccable/critique/2026-08-30T19-00-41Z__frontend-src.md delete mode 100644 frontend/src/app/dashboard/translate/FileDropZone.tsx delete mode 100644 frontend/src/app/dashboard/translate/FilePreview.tsx delete mode 100644 frontend/src/app/dashboard/translate/TranslationComplete.tsx delete mode 100644 frontend/src/app/dashboard/translate/TranslationModeToggle.tsx delete mode 100644 frontend/src/app/dashboard/translate/TranslationProgress.tsx delete mode 100644 frontend/src/components/layout/site-footer.tsx delete mode 100644 frontend/src/components/layout/site-header.tsx delete mode 100644 frontend/src/lib/i18n/messages/ar/memento.json delete mode 100644 frontend/src/lib/i18n/messages/de/memento.json delete mode 100644 frontend/src/lib/i18n/messages/en/memento.json create mode 100644 frontend/src/lib/i18n/messages/en/reviews.json create mode 100644 frontend/src/lib/i18n/messages/en/teams.json delete mode 100644 frontend/src/lib/i18n/messages/es/memento.json delete mode 100644 frontend/src/lib/i18n/messages/fa/memento.json delete mode 100644 frontend/src/lib/i18n/messages/fr/memento.json create mode 100644 frontend/src/lib/i18n/messages/fr/reviews.json create mode 100644 frontend/src/lib/i18n/messages/fr/teams.json delete mode 100644 frontend/src/lib/i18n/messages/it/memento.json delete mode 100644 frontend/src/lib/i18n/messages/ja/memento.json delete mode 100644 frontend/src/lib/i18n/messages/ko/memento.json delete mode 100644 frontend/src/lib/i18n/messages/nl/memento.json delete mode 100644 frontend/src/lib/i18n/messages/pt/memento.json delete mode 100644 frontend/src/lib/i18n/messages/ru/memento.json delete mode 100644 frontend/src/lib/i18n/messages/zh/memento.json delete mode 100644 frontend/tailwind.config.js diff --git a/.impeccable/critique/2026-08-30T19-00-41Z__frontend-src.md b/.impeccable/critique/2026-08-30T19-00-41Z__frontend-src.md new file mode 100644 index 0000000..ef0effa --- /dev/null +++ b/.impeccable/critique/2026-08-30T19-00-41Z__frontend-src.md @@ -0,0 +1,84 @@ +--- +target: frontend/src (UI complète Wordly.art) +total_score: 20 +max_score: 40 +na_heuristics: +p0_count: 1 +p1_count: 4 +timestamp: 2026-08-30T19-00-41Z +slug: frontend-src +--- +# Critique de design — Wordly.art (`frontend/src`) + +Method: dual-agent (A: agent_0921d3fd-00f8-424c-a025-1e69fe2d49ff · B: agent_44a564e1-9137-48cf-a6b5-7d78e1042ead) + +## Design Health Score + +| # | Heuristique | Score | Problème clé | +|---|-------------|-------|--------------| +| 1 | Visibility of System Status | 2 | Stats fabriquées pendant le traitement (« 99.9% » `translate/page.tsx:404`, « Intégrité Layout » = `40 + progress*0.55` `translate/page.tsx:695`) ; `estimatedRemaining_seconds` récupéré mais jamais affiché (`useTranslationSubmit.ts:78`) ; échec de téléchargement **silencieux** (`translate/page.tsx:152`) ; notifications sans `role="status"` (`notification.tsx:118`). | +| 2 | Match System / Real World | 2 | Produit annoncé 12 locales, mais « Source/Cible/Moteur » codés en dur FR (`translate/page.tsx:671-728`), page reviews 100% FR (`reviews/[jobId]/page.tsx`), erreurs upload FR (`useFileUpload.ts:8-9`), erreurs polling EN (`useTranslationSubmit.ts:62,102`), « Loading… » EN (`DashboardLayoutClient.tsx:29`). | +| 3 | User Control and Freedom | 2 | « Annuler le processus » (`translate/page.tsx:700-705`) ne fait qu'un reset local — le job continue côté serveur, crédits consommés ; aucun historique : rafraîchir pendant une traduction perd tout (`useTranslationSubmit.ts` sans persistance) ; page review inatteignable (unique lien dans un composant mort, `TranslationComplete.tsx:168`). | +| 4 | Consistency and Standards | 1 | Trois systèmes visuels (éditorial / shadcn auth / admin violet `admin/login/page.tsx:52`) ; pricing landing 3 plans 7/15/39€ (`landing-page.tsx:330-332`) vs /pricing 5 plans (`pricing/page.tsx:56-192`) ; deux systèmes de toasts (`useNotification` + `useToast`) ; 5 composants translate/ orphelins jamais importés ; `tailwind.config.js:28-35` décrit un thème dark/bleu qui n'existe plus. | +| 5 | Error Prevention | 2 | Boutons Word/Excel/Slides/PDF du dropzone injectent un **faux fichier** de 100 octets à taille truquée (`useFileUpload.ts:83-90`) ; « Effacer le cache » sans confirmation (`settings/page.tsx:13-28`) ; gating incohérent : glossaires réservés à `tier === 'pro'` strict (`glossaries/page.tsx:56`) alors que la sidebar traite business/enterprise comme Pro (`DashboardSidebar.tsx:20`) — clients Business privés de la feature. | +| 6 | Recognition Rather Than Recall | 3 | Nav icône+texte (`DashboardSidebar.tsx:42-54`), résumé de config visible pendant le processing (`translate/page.tsx:647-684`). Perd : pages settings/services/reviews orphelines hors nav (`constants.ts:10-17`), badge tier `text-[7px]` illisible (`DashboardSidebar.tsx:113`). | +| 7 | Flexibility and Efficiency | 1 | Aucun raccourci clavier dans le code vivant (le seul handler Enter/Espace est dans le composant mort `FileDropZone.tsx:35`) ; un fichier à la fois, aucun batch ; pas d'historique ni rejeu de config. | +| 8 | Aesthetic and Minimalist Design | 2 | Micro-typographie : `text-[7.5px]` (`translate/page.tsx:342`), `text-[8px]` (`translate/page.tsx:263,336`), texte à `/30` opacité ; tout en `uppercase tracking-widest` jusqu'au CTA primaire (`translate/page.tsx:322`) ; deux blocs promo Memento concurrents sur l'écran de travail. | +| 9 | Error Recovery | 3 | `humanFriendlyError()` mappe les erreurs brutes vers des messages clairs localisés (`translate/page.tsx:90-111`) ; état failed conserve le fichier + Réessayer (`translate/page.tsx:452-489`). Gaps : téléchargement silencieux, messages backend bruts sur register (`RegisterForm.tsx:221`). | +| 10 | Help and Documentation | 2 | Aide contextuelle bonne sur moteurs/glossaires (`ProviderSelector.tsx:276-306`, `GlossarySelector.tsx:332-334`) mais aucune doc vivante : liens footer morts `href="#"` (`landing-page.tsx:436-445`), contact Enterprise = `mailto:contact@votre-domaine.com` (`pricing/page.tsx:362`) — un placeholder de domaine. | +| **Total** | | **20/40** | **Acceptable — améliorations significatives nécessaires** | + +## Verdict de spécificité design + +**LLM assessment** : Landing — spécifique, parfois remarquable (copy métier : « CTA restera "Air Handling Unit", jamais "Call To Action" » `landing.json:35` ; identité crème/Playfair/or `globals.css:14-17,112`) mais le hero illustre le produit avec une photo Unsplash générique (`landing-page.tsx:139`) au lieu de montrer l'argument n°1 — la mise en page préservée. Dashboard — l'ossature du flux traduction est écrite pour CE produit, mais rendue interchangeable par la bannière publicitaire tierce « Memento » (`translate/page.tsx:748-781`, `DashboardSidebar.tsx:73-93`) et des stats décoratives fausses. Auth — interchangeable (Register `variant="premium"` `RegisterForm.tsx:337` vs Login default `LoginForm.tsx:179`). Admin — littéralement un autre produit (sidebar compacte EN codée en dur, accent violet, `AdminSidebar.tsx:64-84`). + +**Deterministic scan** : 4 findings (exit 2). Deux `overused-font` sur Inter (`globals.css:141,147`) — le détecteur confirme le diagnostic de la revue par l'autre bout : l'app est portée par Inter, la police par défaut de l'ère IA, tandis que l'identité réelle du produit (Playfair + crème + or) ne vit que sur la landing. Un `side-tab` (`border-l-4` `dashboard/profile/page.tsx:469`, visible pour les comptes payants) que la revue n'avait pas isolé. Un `border-accent-on-rounded` (`globals.css:215`) : **faux positif** — c'est le pattern canonique d'un spinner de chargement, pas une carte. + +**Visual overlays** : aucun overlay disponible — l'injection mutable s'est heurtée à « Browser is not available in subagent » après deux tentatives réelles. Signal de fallback : scan CLI complet + vérification HTTP statique (/, /login, /pricing → 200, avec réserve : un port-forward de l'éditeurCursor sur 127.0.0.1:3000 a probablement répondu à la place du serveur local). + +## Impression générale + +La fondation est saine — machine à états propre, gestion d'erreurs humaine, copy métier différenciante — mais l'exécution sabote la confiance au moment exact où un professionnel en a besoin : publicité tierce dans l'écran de travail, statistiques fabriquées, parcours primaire inaccessible au clavier, i18n de façade. La plus grande opportunité : transformer la fin de job — déjà le meilleur moment émotionnel du produit — en preuve produit (lien vers la review segment par segment qui existe déjà), et évacuer le bruit qui la noie. + +## Ce qui fonctionne + +1. **La machine à états du flux de traduction** (`translate/page.tsx:172-181`) : upload → config → processing → complete/failed se relaient proprement dans un seul écran, une intention à la fois, zéro navigation. C'est la bonne architecture pour ce produit. +2. **`humanFriendlyError()`** (`translate/page.tsx:90-111`) : pattern-matching des erreurs brutes (« quota », « 429 », « timed out ») vers des messages clairs localisés, avec état failed qui conserve le fichier et propose Réessayer. La majorité des SaaS affichent le message brut — ici c'est pensé. +3. **La copy landing FR comme différenciateur** (« Les autres traducteurs détruisent votre mise en page. Pas nous. » `landing.json:43`) : c'est du domaine, pas du template. + +## Problèmes prioritaires + +1. **[P0] Le dépôt de fichier est impossible au clavier — et le composant accessible est du code mort.** Le dropzone vivant est un `div` avec `onClick` sans `tabIndex`, `role` ni handler clavier (`translate/page.tsx:256-261`) ; l'utilisateur clavier seul ne peut pas faire la tâche primaire. Le handler Enter/Espace correct existe dans `FileDropZone.tsx:35`, jamais importé. Le combobox de langue custom n'a ni `role="combobox"` ni flèches ni Échap (`LanguageSelector.tsx:66-113`) ; le switch glossaire n'a pas de `role="switch"` (`GlossarySelector.tsx:286-312`) ; contrastes massivement sous 4.5:1 (`text-brand-dark/30` sur crème, `translate/page.tsx:263,336,342`) ; actions révélées au survol uniquement (`GlossaryCard.tsx:65,75`). → Fix : brancher `FileDropZone.tsx` ou rendre le dropzone focusable, `Select` shadcn pour les langues, `role="switch"`/`role="status"` manquants. **Commandes : `$impeccable audit` (quantifier a11y) puis `$impeccable harden` (corriger).** + +2. **[P1] Publicité tierce « Memento » greffée dans l'outil de travail.** Deux blocs promo vers un produit externe (sidebar `DashboardSidebar.tsx:73-93` + bannière pleine largeur à double CTA `translate/page.tsx:748-781`) sur l'écran du cœur de métier, pour un SaaS qui vend la confidentialité documentaire — le message « Rétention zéro » en 7.5px juste au-dessus (`translate/page.tsx:342-349`) est contredit par la promo en dessous. Pill « Ecosystème Wordly » codé en dur non traduit (`translate/page.tsx:764`). → Fix : sortir la promo du flux de traduction ; au maximum un emplacement unique post-tâche, discret, désactivable, traduit. **Commande : `$impeccable distill`.** + +3. **[P1] Les boutons « Word / Excel / Slides / PDF » injectent de faux fichiers.** 4 boutons ressemblant à des filtres de format chargent un blob de 100 octets avec taille falsifiée (`useFileUpload.ts:83-90`, noms factices `useFileUpload.ts:74-77`). L'utilisateur croit sélectionner un type ; il obtient un fichier qui échouera à la soumission — piège à erreurs garanti. → Fix : supprimer, ou transformer en démo explicite (« Essayer avec un exemple ») sur un flux séparé. **Commandes : `$impeccable harden` + `$impeccable clarify`.** + +4. **[P1] L'i18n est une façade : la moitié des strings visibles sont codées en dur, et le pattern de fallback est mort.** Un utilisateur EN voit « Source/Cible » (`translate/page.tsx:671-681`), des pages entières FR (reviews, teams, glossaries), des erreurs FR (« Erreur serveur » `pricing/page.tsx:390,434`) et EN (« Loading... »). Pire : `t()` retourne la clé brute si absente (`i18n.tsx:161`), donc tous les `t('key') || 'fallback'` (~30 occurrences, ex. `GlossarySelector.tsx:277`) n'appliquent **jamais** leur fallback — clé manquante = `translate.glossary.title` affiché à l'écran. Les découpes `lastIndexOf(' ')` pour l'italique du dernier mot (`translate/page.tsx:206-208`) casseront en zh/ja. → Fix : `t()` retourne une chaîne vide sur clé manquante, auditer les `||`, extraire les strings codées en dur. **Commande : `$impeccable harden`.** + +5. **[P1] Features fantômes et perte de contrôle : review inatteignable, pages orphelines, annulation factice, pas d'historique.** La page de review segment par segment (`reviews/[jobId]/page.tsx`) n'est liée nulle part dans l'UI vivante (unique lien dans `TranslationComplete.tsx:168`, composant jamais importé) ; settings et services absents de la nav (`constants.ts:10-17`) ; « Annuler » ne fait qu'un reset local pendant que crédits partent en consommation ; rafraîchir pendant un traitement détruit tout état (aucun jobId persisté) ; clients Business/Enterprise privés de glossaires (`glossaries/page.tsx:56`). → Fix : persister le jobId avec reprise du polling, historique des jobs récents, lien complet → `/dashboard/reviews/[jobId]`, vraie API d'annulation ou renommage honnête, unifier `isPro`. **Commande : `$impeccable shape`.** + +## Red flags personas + +**Alex (utilisateur expert pressé)** : aucun raccourci clavier vivant ; un fichier à la fois, aucun batch (rédhibitoire pour 20 docs/semaine) ; aucun historique/rejeu — chaque session repart de zéro ; les stats « 99.9% précision / Turbo » (`translate/page.tsx:404-405`) détruisent sa confiance ; « Annuler » qui n'annule pas — il le découvrira à la facture. + +**Sam (accessibilité)** : bloqué dès l'étape 1 — dropzone inaccessible clavier (`translate/page.tsx:256-261`) ; contraste ~1.8:1 sur des dizaines de libellés (`text-brand-dark/30`) ; tailles 7.5-9px `uppercase tracking-widest` illisibles ; notifications muettes pour lecteur d'écran (`notification.tsx:118`) ; échec de téléchargement sans aucun rendu (`translate/page.tsx:152`) ; actions invisibles hors hover (`GlossaryCard.tsx:65,75`, `notification.tsx:181`) ; RTL : `dir` bien posé mais barres/badges en `left-0`/`right-0` physiques (`translate/page.tsx:387`). + +**Jordan (premier usage)** : premier écran correct (dropzone central « Glissez-déposez ici ») puis 4 boutons formats qui ne font pas ce qu'ils annoncent ; jargon non défini (« Standard » vs « IA contextuelle », « Intégrité Layout 100% SÉCURISÉ » `translate.json:85-86`) ; aucun doc vivante (footer « Documentation » mort `landing-page.tsx:436`) ; le premier paiement peut aboutir sur `mailto:contact@votre-domaine.com` (`pricing/page.tsx:362`). + +## Observations mineures + +- Titre dupliqué pill + H1 sur settings et services (`settings/page.tsx:40-42`, `services/page.tsx:56-58`). +- Dates glossaires `fr-FR` en dur (`glossaries/page.tsx:412`) contre `formatDate()` locale existante (`i18n.tsx:108`). +- Prix IA en `$` sur une page facturée en `€` (`pricing/page.tsx:870,885`). +- `site-header.tsx`/`site-footer.tsx` importés nulle part ; le header fantôme pointe « Connexion » vers `/dashboard` (`site-header.tsx:31`). +- Mention CGU non cliquable (`RegisterForm.tsx:366-370`). +- Drapeaux pays pour des langues (ar→🇸🇦, zh→🇨🇳 `language-switcher.tsx:24-25`) — discutable pour un outil pro multilingue ; 13 locales à plat dans le switcher. +- Bon réflexe rare : `prefers-reduced-motion` et `prefers-contrast` gérés (`globals.css:237-254`) ; skeleton pricing anti-flash (`pricing/page.tsx:221-259`). +- Le switch « Traduire les images » désactivé rend toute la carte grisée sans explication avant interaction (`translate/page.tsx:556-585`). + +## Questions à considérer + +1. Si un prospect demande « prouvez-moi que ma mise en page survit », quelle partie de l'écran répond ? Aucune aujourd'hui — le hero montre une photo Unsplash de bureau (`landing-page.tsx:139`) alors que le moteur produit déjà un avant/après à chaque job. Un seul DOCX tri-langue côte à côte vaudrait plus que les 6 cartes de LayoutFeatures. +2. Pourquoi l'état « traduction terminée » — le meilleur moment émotionnel — se termine-t-il sur un bouton fantôme de 10px alors que la page de review segment par segment existe déjà ? +3. Qu'est-ce qui coûte le plus : les revenus Memento, ou la crédibilité « documents confidentiels » que la promo détruit à chaque écran ? diff --git a/frontend/src/app/auth/login/LoginForm.tsx b/frontend/src/app/auth/login/LoginForm.tsx index b2531c0..ac62531 100644 --- a/frontend/src/app/auth/login/LoginForm.tsx +++ b/frontend/src/app/auth/login/LoginForm.tsx @@ -169,6 +169,8 @@ export function LoginForm() { @@ -171,7 +171,7 @@ export function RegisterForm() { - Wordly + {t('auth.brandName')} @@ -334,7 +334,6 @@ export function RegisterForm() { - - - )} - {/* User section */}
{!isLoading && user && ( @@ -110,7 +86,7 @@ export function DashboardSidebar() {
- + {translateTier(t, user.tier)}
diff --git a/frontend/src/app/dashboard/constants.ts b/frontend/src/app/dashboard/constants.ts index 2b43d95..b401975 100644 --- a/frontend/src/app/dashboard/constants.ts +++ b/frontend/src/app/dashboard/constants.ts @@ -1,4 +1,4 @@ -import { FileText, BookText, User, Users, type LucideIcon } from 'lucide-react'; +import { FileText, BookText, User, Users, Settings, Wrench, type LucideIcon } from 'lucide-react'; export interface NavItem { labelKey: string; @@ -11,6 +11,8 @@ export const baseNavItems: NavItem[] = [ { labelKey: 'dashboard.nav.translate', href: '/dashboard/translate', icon: FileText }, { labelKey: 'dashboard.nav.glossaries', href: '/dashboard/glossaries', icon: BookText, proOnly: true }, { labelKey: 'dashboard.nav.teams', href: '/dashboard/teams', icon: Users, proOnly: true }, + { labelKey: 'dashboard.nav.settings', href: '/dashboard/settings', icon: Settings }, + { labelKey: 'dashboard.nav.services', href: '/dashboard/services', icon: Wrench }, { labelKey: 'dashboard.nav.profile', href: '/dashboard/profile', icon: User }, // API Keys nav item temporarily removed per request — uncomment to restore. // { labelKey: 'dashboard.nav.apiKeys', href: '/dashboard/api-keys', icon: Key, proOnly: true }, diff --git a/frontend/src/app/dashboard/glossaries/page.tsx b/frontend/src/app/dashboard/glossaries/page.tsx index c529e3e..2634a6e 100644 --- a/frontend/src/app/dashboard/glossaries/page.tsx +++ b/frontend/src/app/dashboard/glossaries/page.tsx @@ -10,7 +10,7 @@ import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { cn } from '@/lib/utils'; import { useUser } from '@/app/dashboard/useUser'; -import { useI18n } from '@/lib/i18n'; +import { useI18n, formatDate } from '@/lib/i18n'; import { useGlossaries } from './useGlossaries'; import type { GlossaryListItem } from './types'; import { ProUpgradePrompt } from './ProUpgradePrompt'; @@ -28,19 +28,8 @@ const CONTEXT_SUGGESTIONS = [ { label: 'Style concis', value: 'Préfère des formulations courtes et directes. Évite les périphrases.' }, ]; -function renderTitle(title: string) { - const lastSpaceIndex = title.lastIndexOf(' '); - if (lastSpaceIndex === -1) return title; - return ( - <> - {title.substring(0, lastSpaceIndex)}{' '} - {title.substring(lastSpaceIndex + 1)} - - ); -} - export default function GlossariesPage() { - const { t } = useI18n(); + const { t, locale } = useI18n(); const router = useRouter(); const { data: user, isLoading: isLoadingUser } = useUser(); const { glossaries, isLoading: isLoadingGlossaries } = useGlossaries(); @@ -53,7 +42,8 @@ export default function GlossariesPage() { const [searchQuery, setSearchQuery] = useState(''); const [activeTab, setActiveTab] = useState<'glossaries' | 'context'>('glossaries'); - const isPro = user?.tier === 'pro'; + // Same tier logic as the sidebar: business/enterprise are Pro-grade plans. + const isPro = ['pro', 'business', 'enterprise'].includes(user?.tier ?? ''); const isLoading = isLoadingUser || isLoadingGlossaries; const currentTargetLang = settings.defaultTargetLanguage; @@ -122,7 +112,7 @@ export default function GlossariesPage() { {t('glossaries.yourGlossaries') || 'Vos Glossaires'}

- {renderTitle(t('glossaries.title') || 'Glossaires & Contexte')} + {t('glossaries.titleBase')} {t('glossaries.titleAccent')}

{t('glossaries.description') || 'Gérez vos glossaires et instructions de contexte pour des traductions plus précises.'} @@ -134,7 +124,7 @@ export default function GlossariesPage() { className="shrink-0 flex items-center gap-2 px-5 py-2.5 rounded-xl bg-[#1A1A1A] hover:bg-[#333333] text-white text-xs font-bold uppercase tracking-widest transition-all" > - Nouveau glossaire + {t('glossaries.new')} @@ -144,15 +134,14 @@ export default function GlossariesPage() {

- Pour utiliser un glossaire dans une traduction :{' '} - cliquez sur « Utiliser » sur la carte souhaitée. - Vous serez redirigé sur la page Traduire avec ce glossaire déjà sélectionné. + {t('glossaries.info.title')}{' '} + {t('glossaries.info.body')}

- Aller traduire + {t('glossaries.info.cta')} @@ -160,6 +149,8 @@ export default function GlossariesPage() {

- Consignes de contexte + {t('glossaries.context.title')}

- Ces instructions sont envoyées automatiquement à l'IA avant chaque traduction en mode Pro LLM. + {t('glossaries.context.desc')}

@@ -207,16 +200,16 @@ export default function GlossariesPage() { {promptHasUnsavedChanges ? ( - Non sauvegardé + {t('glossaries.context.unsaved')} ) : promptIsActive ? ( - Actif + {t('glossaries.context.active')} ) : ( - Inactif + {t('glossaries.context.inactive')} )} @@ -226,7 +219,7 @@ export default function GlossariesPage() {
- Suggestions rapides — cliquez pour ajouter + {t('glossaries.context.suggestions')}
@@ -246,7 +239,8 @@ export default function GlossariesPage() {