From 8584324f690c86c24eb05a4920d952b39e3e094e Mon Sep 17 00:00:00 2001 From: Guillermo Siesto Date: Thu, 7 May 2026 09:04:46 +0200 Subject: [PATCH 1/2] feat: implement full French localization and add internationalized layout support for English, German, Italian, and Portuguese locales --- README.md | 22 +- content/i18n-route-map.json | 36 +- public/llms.txt | 3 +- src/app/(es)/layout.tsx | 2 + src/app/de/layout.tsx | 2 + src/app/en/layout.tsx | 2 + src/app/fr/(operations)/[operation]/page.tsx | 8 + src/app/fr/faq/page.tsx | 12 + src/app/fr/layout.tsx | 77 ++ src/app/fr/page.tsx | 12 + src/app/fr/support/page.tsx | 10 + src/app/it/layout.tsx | 2 + src/app/pt/layout.tsx | 2 + src/app/robots.ts | 7 +- src/components/seo/BreadcrumbSchema.tsx | 56 +- src/i18n/config.ts | 3 +- src/i18n/dictionaries/fr/faqs.ts | 63 ++ src/i18n/dictionaries/fr/index.ts | 715 +++++++++++++++++++ src/i18n/dictionaries/fr/operations.ts | 621 ++++++++++++++++ src/i18n/get-dictionary.ts | 2 + src/i18n/routing.ts | 41 ++ tests/e2e/ui-refinements.spec.ts | 3 + 22 files changed, 1627 insertions(+), 74 deletions(-) create mode 100644 src/app/fr/(operations)/[operation]/page.tsx create mode 100644 src/app/fr/faq/page.tsx create mode 100644 src/app/fr/layout.tsx create mode 100644 src/app/fr/page.tsx create mode 100644 src/app/fr/support/page.tsx create mode 100644 src/i18n/dictionaries/fr/faqs.ts create mode 100644 src/i18n/dictionaries/fr/index.ts create mode 100644 src/i18n/dictionaries/fr/operations.ts diff --git a/README.md b/README.md index d0dc98a..8689d5a 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) [![Build Status](https://github.com/GSiesto/pdflince/actions/workflows/deploy.yml/badge.svg?style=for-the-badge)](https://github.com/GSiesto/pdflince/actions) -| 🇬🇧 English | đŸ‡Ș🇾 Español | đŸ‡©đŸ‡Ș Deutsch | đŸ‡”đŸ‡č PortuguĂȘs | 🇼đŸ‡č Italiano | -|:---:|:---:|:---:|:---:|:---:| -| [pdflince.com](https://pdflince.com/en?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/de?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/pt?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/it?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) +| English | Español | Français | Deutsch | PortuguĂȘs | Italiano | +|:---:|:---:|:---:|:---:|:---:|:---:| +| [pdflince.com](https://pdflince.com/en?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/fr?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/de?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/pt?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) | [pdflince.com](https://pdflince.com/it?utm_source=github&utm_medium=readme&utm_campaign=pdflince_public) -## 🚀 Why PDFLince? +## Why PDFLince? Most online PDF tools require you to upload your sensitive documents to their cloud servers for processing. This creates privacy risks and compliance issues. @@ -22,7 +22,7 @@ Most online PDF tools require you to upload your sensitive documents to their cl - **Offline Capable**: Works even without an internet connection once loaded. - **No File Size Limits**: Bypasses server payload limits since it uses your device's memory. -## ✹ Features +## Features - **Compress**: Smart compression with adaptive image downscaling (WebWorker-powered). - **Merge**: Combine multiple PDFs into one document. @@ -32,9 +32,9 @@ Most online PDF tools require you to upload your sensitive documents to their cl - **Crop**: Crop PDF pages manually with visual selection or by specifying precise values - **Rotate**: Rotate selected pages and preview changes in real-time before exporting. - **Secure**: 100% local processing verifiable via Network tab. -- **Multilingual**: Native support for English, Spanish, German, Portuguese, Italian. +- **Multilingual**: Native support for English, Spanish, French, German, Portuguese, Italian. -## đŸ› ïž Architecture +## Architecture Built with a focus on performance and code quality. The key differentiator is the **Zero-Server Data Flow**: @@ -71,7 +71,7 @@ graph TD - **Styling**: Tailwind CSS with a custom design system. - **Testing**: Playwright End-to-End test suite ensuring reliability across all operations. -## ⚙ Local Development +## Local Development ### Prerequisites - Node.js 18+ @@ -98,17 +98,17 @@ graph TD 4. **Open in browser:** Navigate to [http://localhost:3000](http://localhost:3000). -## đŸ€ Contributing +## Contributing Contributions are welcome! Whether it's fixing bugs, improving documentation, or proposing new features. Please read our [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) before getting started. -## 📄 License +## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## 💡 About the Name +## About the Name "Lince" means **Lynx** in Spanish. The **Iberian Lynx** (*Lynx pardinus*) is a wild cat species native to the Iberian Peninsula (Spain/Portugal). It was once the most endangered feline species in the world, but conservation efforts are helping its population recover. diff --git a/content/i18n-route-map.json b/content/i18n-route-map.json index 50685b4..8d8587c 100644 --- a/content/i18n-route-map.json +++ b/content/i18n-route-map.json @@ -4,21 +4,24 @@ "en": "/en", "pt": "/pt", "de": "/de", - "it": "/it" + "it": "/it", + "fr": "/fr" }, "faq": { "es": "/preguntas-frecuentes", "en": "/en/faq", "pt": "/pt/perguntas-frequentes", "de": "/de/haeufige-fragen", - "it": "/it/faq" + "it": "/it/faq", + "fr": "/fr/faq" }, "support": { "es": "/apoya", "en": "/en/support", "pt": "/pt/apoie", "de": "/de/unterstuetzen", - "it": "/it/supporto" + "it": "/it/supporto", + "fr": "/fr/soutenir" }, "operations": { "compress": { @@ -26,63 +29,72 @@ "en": "/en/compress", "pt": "/pt/comprimir", "de": "/de/komprimieren", - "it": "/it/comprimi" + "it": "/it/comprimi", + "fr": "/fr/compresser" }, "merge": { "es": "/unir", "en": "/en/merge", "pt": "/pt/juntar", "de": "/de/zusammenfuehren", - "it": "/it/unisci" + "it": "/it/unisci", + "fr": "/fr/fusionner" }, "split": { "es": "/dividir", "en": "/en/split", "pt": "/pt/dividir", "de": "/de/teilen", - "it": "/it/dividi" + "it": "/it/dividi", + "fr": "/fr/diviser" }, "extract": { "es": "/extraer", "en": "/en/extract", "pt": "/pt/extrair", "de": "/de/seiten-extrahieren", - "it": "/it/estrai" + "it": "/it/estrai", + "fr": "/fr/extraire" }, "crop": { "es": "/recortar", "en": "/en/crop", "pt": "/pt/recortar", "de": "/de/zuschneiden", - "it": "/it/ritaglia" + "it": "/it/ritaglia", + "fr": "/fr/recadrer" }, "rotate": { "es": "/girar", "en": "/en/rotate", "pt": "/pt/girar", "de": "/de/drehen", - "it": "/it/ruota" + "it": "/it/ruota", + "fr": "/fr/faire-pivoter" }, "reorder": { "es": "/reordenar", "en": "/en/reorder", "pt": "/pt/reordenar", "de": "/de/seiten-sortieren", - "it": "/it/riordina" + "it": "/it/riordina", + "fr": "/fr/reorganiser" }, "pdfToImages": { "es": "/convertir-pdf-a-imagenes", "en": "/en/pdf-to-images", "pt": "/pt/pdf-para-imagens", "de": "/de/pdf-zu-bildern", - "it": "/it/da-pdf-a-immagini" + "it": "/it/da-pdf-a-immagini", + "fr": "/fr/pdf-en-images" }, "imagesToPdf": { "es": "/crear-pdf-desde-imagenes", "en": "/en/images-to-pdf", "pt": "/pt/imagens-para-pdf", "de": "/de/bilder-zu-pdf", - "it": "/it/da-immagini-a-pdf" + "it": "/it/da-immagini-a-pdf", + "fr": "/fr/creer-pdf-depuis-images" } } } diff --git a/public/llms.txt b/public/llms.txt index 45a66c5..8ff5213 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -18,10 +18,11 @@ PDFLince se creĂł para la comunidad hispanohablante, siendo ahora multilingĂŒe, ## Idiomas Disponibles / Available Languages -PDFLince estĂĄ disponible en 5 idiomas. Puedes acceder a la versiĂłn que prefieras: +PDFLince estĂĄ disponible en 6 idiomas. Puedes acceder a la versiĂłn que prefieras: - [Español (Principal)](https://pdflince.com) - [English](https://pdflince.com/en) +- [Français](https://pdflince.com/fr) - [Deutsch](https://pdflince.com/de) - [PortuguĂȘs](https://pdflince.com/pt) - [Italiano](https://pdflince.com/it) diff --git a/src/app/(es)/layout.tsx b/src/app/(es)/layout.tsx index 4a461c0..a63b370 100644 --- a/src/app/(es)/layout.tsx +++ b/src/app/(es)/layout.tsx @@ -34,6 +34,8 @@ export const metadata = { 'es': 'https://pdflince.com', 'en': 'https://pdflince.com/en', 'en-US': 'https://pdflince.com/en', + 'fr': 'https://pdflince.com/fr', + 'fr-FR': 'https://pdflince.com/fr', 'pt': 'https://pdflince.com/pt', 'pt-BR': 'https://pdflince.com/pt', 'de': 'https://pdflince.com/de', diff --git a/src/app/de/layout.tsx b/src/app/de/layout.tsx index a160005..8994c5e 100644 --- a/src/app/de/layout.tsx +++ b/src/app/de/layout.tsx @@ -31,6 +31,8 @@ export const metadata = { 'es': 'https://pdflince.com', 'en': 'https://pdflince.com/en', 'en-US': 'https://pdflince.com/en', + 'fr': 'https://pdflince.com/fr', + 'fr-FR': 'https://pdflince.com/fr', 'pt': 'https://pdflince.com/pt', 'pt-BR': 'https://pdflince.com/pt', 'de': 'https://pdflince.com/de', diff --git a/src/app/en/layout.tsx b/src/app/en/layout.tsx index acbb918..f5977ba 100644 --- a/src/app/en/layout.tsx +++ b/src/app/en/layout.tsx @@ -31,6 +31,8 @@ export const metadata = { 'es': 'https://pdflince.com', 'en': 'https://pdflince.com/en', 'en-US': 'https://pdflince.com/en', + 'fr': 'https://pdflince.com/fr', + 'fr-FR': 'https://pdflince.com/fr', 'pt': 'https://pdflince.com/pt', 'pt-BR': 'https://pdflince.com/pt', 'de': 'https://pdflince.com/de', diff --git a/src/app/fr/(operations)/[operation]/page.tsx b/src/app/fr/(operations)/[operation]/page.tsx new file mode 100644 index 0000000..dfafb1d --- /dev/null +++ b/src/app/fr/(operations)/[operation]/page.tsx @@ -0,0 +1,8 @@ +import { createLocaleOperationHandlers } from "../../../(helpers)/operation-handlers"; + +const LOCALE = "fr" as const; + +const { generateStaticParams, generateMetadata, OperationPage } = createLocaleOperationHandlers(LOCALE); + +export { generateStaticParams, generateMetadata }; +export default OperationPage; diff --git a/src/app/fr/faq/page.tsx b/src/app/fr/faq/page.tsx new file mode 100644 index 0000000..6cddb17 --- /dev/null +++ b/src/app/fr/faq/page.tsx @@ -0,0 +1,12 @@ +import { FaqPageContent } from "../../../components/FaqPageContent"; +import { getDictionary } from "../../../i18n/get-dictionary"; +import { createFaqMetadata } from "../../(helpers)/locale-metadata"; + +const LOCALE = "fr" as const; + +export const generateMetadata = createFaqMetadata(LOCALE); + +export default function FrFaqPage() { + const dictionary = getDictionary(LOCALE); + return ; +} diff --git a/src/app/fr/layout.tsx b/src/app/fr/layout.tsx new file mode 100644 index 0000000..15424f6 --- /dev/null +++ b/src/app/fr/layout.tsx @@ -0,0 +1,77 @@ +import "../../styles/globals.css"; +import { Suspense, type ReactNode } from "react"; +import { GeistSans } from "geist/font/sans"; +import { GeistMono } from "geist/font/mono"; +import NavMenu from "../../components/NavMenu"; +import Footer from "../../components/Footer"; +import FotoLinceBanner from "../../components/FotoLinceBanner"; +import { LocaleProvider } from "../../i18n/LocaleProvider"; +import { GoogleAnalyticsScripts } from "../../components/analytics/GoogleAnalyticsScripts"; +import { GaPageViewTracker } from "../../components/analytics/GaPageViewTracker"; +import { WebVitalsReporter } from "../../components/analytics/WebVitalsReporter"; +import CookieBanner from "../../components/CookieBanner"; +import { SchemaOrg } from "../../components/SchemaOrg"; + +import { METADATA_BASE, SHARED_ICONS, SHARED_OPEN_GRAPH } from "../../lib/metadata-shared"; + +const LOCALE = "fr" as const; + +export const metadata = { + metadataBase: METADATA_BASE, + title: "PDFLince - Fusionner, Compresser, Diviser et Convertir des PDF", + description: "Outils PDF gratuits en ligne pour fusionner, compresser, diviser, extraire et convertir. 100% privĂ©, traitement local dans votre navigateur.", + icons: SHARED_ICONS, + alternates: { + canonical: "https://pdflince.com/fr", + languages: { + 'es-ES': 'https://pdflince.com', + 'es-MX': 'https://pdflince.com', + 'es-CO': 'https://pdflince.com', + 'es-AR': 'https://pdflince.com', + 'es': 'https://pdflince.com', + 'en': 'https://pdflince.com/en', + 'en-US': 'https://pdflince.com/en', + 'fr': 'https://pdflince.com/fr', + 'fr-FR': 'https://pdflince.com/fr', + 'pt': 'https://pdflince.com/pt', + 'pt-BR': 'https://pdflince.com/pt', + 'de': 'https://pdflince.com/de', + 'de-DE': 'https://pdflince.com/de', + 'it': 'https://pdflince.com/it', + 'it-IT': 'https://pdflince.com/it', + 'x-default': 'https://pdflince.com', + }, + }, + openGraph: { + ...SHARED_OPEN_GRAPH, + title: "PDFLince - Outils PDF gratuits et privĂ©s dans votre navigateur", + description: "Fusionnez, compressez, divisez et convertissez des PDF sans tĂ©lĂ©charger de fichiers. SĂ©curisĂ© et 100% privĂ©.", + url: "https://pdflince.com/fr", + locale: "fr_FR", + type: "website", + }, +}; + +export default function FrLayout({ children }: { children: ReactNode }) { + return ( + + + + + + + + + + + + +
{children}
+ +