From 4f3417fcefb47622c975bce2846f7012d6297251 Mon Sep 17 00:00:00 2001 From: ordinarthur <@arthurbarre.js@gmail.com> Date: Sun, 17 May 2026 13:29:57 +0200 Subject: [PATCH] feat(landing): support i18n EN avec routing /en/* (Astro i18n natif) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Active Astro i18n avec `defaultLocale: fr` et `prefixDefaultLocale: false` — les URLs FR restent canoniques à la racine, l'EN vit sous `/en/*` pour ne pas casser le SEO existant. Architecture : - `src/i18n/{types,fr,en,index}.ts` — dico FR fait foi (Dict inféré), EN doit matcher la shape ; helpers `getTranslations(locale)` et `getAlternateUrl()` pour le language switcher. - `Layout.astro` lit `Astro.currentLocale`, propage `locale` aux composants React, set ``, og:locale + alt, hreflang. - `SiteHeader` expose un lien switcher FR↔EN qui préserve la page. - Toutes les sections (Hero, Stats, Promise, HowItWorks, Gamification, AutoBanking, Legal, Pricing, FAQ, FinalCTA, Footnotes, SiteFooter) acceptent une prop `locale` et tirent leurs chaînes du dico. Pages EN créées : - `/en/` — home complète - `/en/blog`, `/en/changelog` — chrome traduit, contenu reste dans la langue de rédaction (les .md changelog + posts API sont FR) - `/en/cgv`, `/en/mentions-legales`, `/en/confidentialite` — résumés courts ; la version juridiquement contraignante reste la FR (droit français, conformité GDPR/LCEN/LME). Sitemap mis à jour avec entrées FR/EN + `xhtml:link rel="alternate"`. Pas de détection auto via Accept-Language pour l'instant — le switcher header suffit en V1. Co-Authored-By: Claude Opus 4.7 --- apps/landing/astro.config.mjs | 9 + apps/landing/src/components/SiteFooter.tsx | 39 +- apps/landing/src/components/SiteHeader.tsx | 50 ++- .../src/components/sections/AutoBanking.tsx | 114 ++--- apps/landing/src/components/sections/FAQ.tsx | 142 +------ .../src/components/sections/FinalCTA.tsx | 20 +- .../src/components/sections/Footnotes.tsx | 24 +- .../src/components/sections/Gamification.tsx | 28 +- apps/landing/src/components/sections/Hero.tsx | 86 ++-- .../src/components/sections/HowItWorks.tsx | 124 +++--- .../landing/src/components/sections/Legal.tsx | 71 ++-- .../src/components/sections/Pricing.tsx | 120 +++--- .../src/components/sections/Promise.tsx | 48 +-- .../landing/src/components/sections/Stats.tsx | 40 +- apps/landing/src/i18n/en.ts | 381 +++++++++++++++++ apps/landing/src/i18n/fr.ts | 377 +++++++++++++++++ apps/landing/src/i18n/index.ts | 43 ++ apps/landing/src/i18n/types.ts | 15 + apps/landing/src/layouts/Layout.astro | 25 +- apps/landing/src/pages/blog/index.astro | 18 +- apps/landing/src/pages/changelog/index.astro | 39 +- apps/landing/src/pages/en/blog/index.astro | 57 +++ apps/landing/src/pages/en/cgv.astro | 76 ++++ .../src/pages/en/changelog/index.astro | 388 ++++++++++++++++++ .../src/pages/en/confidentialite.astro | 86 ++++ apps/landing/src/pages/en/index.astro | 99 +++++ .../src/pages/en/mentions-legales.astro | 91 ++++ apps/landing/src/pages/index.astro | 47 +-- apps/landing/src/pages/sitemap.xml.ts | 36 +- 29 files changed, 2102 insertions(+), 591 deletions(-) create mode 100644 apps/landing/src/i18n/en.ts create mode 100644 apps/landing/src/i18n/fr.ts create mode 100644 apps/landing/src/i18n/index.ts create mode 100644 apps/landing/src/i18n/types.ts create mode 100644 apps/landing/src/pages/en/blog/index.astro create mode 100644 apps/landing/src/pages/en/cgv.astro create mode 100644 apps/landing/src/pages/en/changelog/index.astro create mode 100644 apps/landing/src/pages/en/confidentialite.astro create mode 100644 apps/landing/src/pages/en/index.astro create mode 100644 apps/landing/src/pages/en/mentions-legales.astro diff --git a/apps/landing/astro.config.mjs b/apps/landing/astro.config.mjs index c7d057e..a841fa5 100644 --- a/apps/landing/astro.config.mjs +++ b/apps/landing/astro.config.mjs @@ -23,6 +23,15 @@ export default defineConfig({ adapter: node({ mode: "standalone", }), + i18n: { + // FR par défaut, EN sous /en/* — `prefixDefaultLocale: false` garde + // les URLs FR canoniques à la racine pour ne pas casser le SEO existant. + defaultLocale: "fr", + locales: ["fr", "en"], + routing: { + prefixDefaultLocale: false, + }, + }, integrations: [ react(), ], diff --git a/apps/landing/src/components/SiteFooter.tsx b/apps/landing/src/components/SiteFooter.tsx index 8b1e03e..b95221b 100644 --- a/apps/landing/src/components/SiteFooter.tsx +++ b/apps/landing/src/components/SiteFooter.tsx @@ -1,39 +1,44 @@ import { Brand } from "@rubis/ui"; +import { getTranslations, type Locale } from "../i18n"; const CURRENT_YEAR = new Date().getFullYear(); +type SiteFooterProps = { + locale?: Locale; +}; + /** * Footer public commun à toutes les pages rubis.pro/*. * Liens légaux + tagline. Pas de réseaux sociaux V1. */ -export function SiteFooter() { +export function SiteFooter({ locale = "fr" }: SiteFooterProps) { + const t = getTranslations(locale); + const prefix = locale === "fr" ? "" : "/en"; + return ( diff --git a/apps/landing/src/components/SiteHeader.tsx b/apps/landing/src/components/SiteHeader.tsx index a7fd11a..d46d0d1 100644 --- a/apps/landing/src/components/SiteHeader.tsx +++ b/apps/landing/src/components/SiteHeader.tsx @@ -1,4 +1,5 @@ import { Brand, Button, cn } from "@rubis/ui"; +import { getTranslations, getAlternateUrl, type Locale } from "../i18n"; const APP_URL = "https://app.rubis.pro"; @@ -6,18 +7,29 @@ type SiteHeaderProps = { /** Si true, fond opaque + bordure (utile sur les pages blog où on n'a pas de hero). Sinon transparent + sticky-blur. */ solid?: boolean; className?: string; + locale?: Locale; + /** Path courant — sert à construire le lien switcher vers la locale alternative. */ + currentPath?: string; }; /** - * Header public commun à toutes les pages rubis.pro/* : - * - lockup brand → / - * - liens nav (Tarifs, Blog) - * - CTA "Essai gratuit 30 j" → app.rubis.pro - * - * Sticky avec backdrop-blur quand le header est posé sur un hero (transparent), - * solid+bordure sur les pages secondaires (blog, légal). + * Header public commun à toutes les pages rubis.pro/*. + * Reçoit la locale via Layout.astro, expose un switcher FR/EN qui préserve + * la page courante. */ -export function SiteHeader({ solid = false, className }: SiteHeaderProps) { +export function SiteHeader({ + solid = false, + className, + locale = "fr", + currentPath = "/", +}: SiteHeaderProps) { + const t = getTranslations(locale); + const target: Locale = locale === "fr" ? "en" : "fr"; + const altUrl = getAlternateUrl(currentPath, target); + const homeHref = locale === "fr" ? "/" : "/en/"; + const pricingHref = locale === "fr" ? "/#pricing" : "/en/#pricing"; + const blogHref = locale === "fr" ? "/blog" : "/en/blog"; + return (
- + -
diff --git a/apps/landing/src/components/sections/AutoBanking.tsx b/apps/landing/src/components/sections/AutoBanking.tsx index c19bd24..038b92c 100644 --- a/apps/landing/src/components/sections/AutoBanking.tsx +++ b/apps/landing/src/components/sections/AutoBanking.tsx @@ -1,76 +1,68 @@ import { CheckCircle2, ShieldCheck, Sparkles, Building2 } from "lucide-react"; +import { getTranslations, type Locale } from "../../i18n"; + +type AutoBankingProps = { + locale?: Locale; +}; + +export function AutoBanking({ locale = "fr" }: AutoBankingProps) { + const t = getTranslations(locale).autoBanking; -/** - * Section "Mode automatique — bientôt" : annonce la connexion bancaire - * en lecture seule via Powens (AISP). Désactivée par défaut sur l'app - * en attendant l'agrément KYC Powens prod. La landing communique - * pendant la fenêtre. - * - * Style : carte cream avec accent rubis, illustration mock à droite, - * 4 bénéfices clés, note conformité DSP2 / lecture seule. - */ -export function AutoBanking() { return (
- {/* Colonne texte */}

- Plus jamais besoin de répondre{" "} - « C'est payé ». + {t.title_a} + {t.title_em} + {t.title_b}

-

- Connectez votre compte bancaire en lecture seule. Rubis détecte - automatiquement les virements de vos clients, marque les - factures payées et envoie le mot de remerciement. Vous ne - répondez plus à rien. -

+

{t.body}

    - Détection en temps réel via Powens - (agréé AISP par l'ACPR). + {t.benefit1_bold} + {t.benefit1_rest} - Toutes les banques françaises — - pro ou perso, neo ou traditionnelles. + {t.benefit2_bold} + {t.benefit2_rest} - Mode validation ou auto-pilote{" "} - — vous choisissez si Rubis attend votre OK ou marque - payée tout seul. + {t.benefit3_bold} + {t.benefit3_rest} - Lecture seule. Aucun déplacement - de fonds possible, jamais. Révocable en un clic. + {t.benefit4_bold} + {t.benefit4_rest}

- {/* Colonne illustration : email "Paiement détecté" */}
- +
@@ -92,18 +84,13 @@ function Benefit({ children }: { children: React.ReactNode }) { ); } -/** - * Mock d'email "Paiement détecté" — illustre le résultat concret de la - * feature. Reprend les codes du ThankYouWidget de HowItWorks pour la - * cohérence visuelle (carte blanche, en-tête expéditeur avec gem ◆, - * badge rubis-glow). - */ -function DetectedPaymentMock() { +function DetectedPaymentMock({ locale }: { locale: Locale }) { + const t = getTranslations(locale).autoBanking; + const amount = locale === "fr" ? "4 189,40 €" : "€4,189.40"; return (