diff --git a/CHANGELOG.md b/CHANGELOG.md
index cac838e..510e11c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,25 @@ Tudo o que muda de relevante no ESPDocs está aqui. O formato segue o
## [Não lançado]
+### Adicionado
+
+- Internacionalização completa para os idiomas Francês (`fr`) e Alemão (`de`) (Fase 5):
+ - Ativação do francês e do alemão em `ENABLED_LOCALES = ["pt", "en", "es", "fr", "de"]` e no seletor de idiomas do cabeçalho.
+ - Tradução técnica integral e paridade estrutural em `messages/fr.json` e `messages/de.json` (15 namespaces), nos arquivos de dados em `data/{fr,de}/` (`series.json`, `frameworks.json`, `conexoes/*.json`) e `lib/data/{fr,de}/` (`glossario.js`, `diagnostico.js`, `componentes.js`, `topicos.js`).
+ - Tradução técnica natural com tratamento formal respeitado: "vous" em francês e "Sie" em alemão.
+ - 14 exemplos práticos de código com comentários em francês e 14 em alemão em `public/exemplos/{fr,de}/`.
+ - Slugs canônicos ASCII por idioma:
+ - Francês: `/fr/series`, `/fr/catalogue`, `/fr/comparaison`, `/fr/composants`, `/fr/diagnostic`, `/fr/frameworks`, `/fr/glossaire`, `/fr/selecteur`, `/fr/a-propos`.
+ - Alemão: `/de/serien`, `/de/katalog`, `/de/vergleich`, `/de/komponenten`, `/de/diagnose`, `/de/frameworks`, `/de/glossar`, `/de/auswahl`, `/de/ueber-uns`.
+ - Busca dedicada por idioma em `/api/busca/fr` e `/api/busca/de` com normalização de acentos, caracteres alemães (ä, ö, ü, ß) e ligaduras francesas (œ, æ), sem menção ao livro e com links diretos aos slugs canônicos.
+ - Exclusão total e estrita de referências ao livro fora do português, com `/fr/livro` e `/de/livro` respondendo HTTP 404 estrito.
+ - Tabela do catálogo de placas com colunas traduzidas e mapeamento de valores técnicos nos idiomas francês e alemão.
+ - Negociação de idioma na raiz com roteamento para `fr` ou `de` com base nos cabeçalhos Accept-Language ou cookies `NEXT_LOCALE=fr` / `NEXT_LOCALE=de`, e fallback padrão para `en` para navegadores com o idioma inativo restante (`zh-CN`).
+ - Metadados de SEO, Open Graph e Twitter Cards localizados com `og:locale` (`fr_FR`, `de_DE`), `inLanguage` (`fr`, `de`) e `hreflang` cruzado entre `pt-BR`, `en`, `es-419`, `fr-FR`, `de-DE` e `x-default`.
+ - Suporte CSS com hifenação automática (`hyphens: auto`) para alemão e francês via atributo `lang` dinâmico no layout raiz.
+ - Extensão da suíte de verificação: `scripts/check-i18n.mjs` valida `fr` e `de`, `scripts/smoke-imagem.sh` testa as rotas francesas e alemãs e `scripts/verifica-vazamentos.py` varre todas as 36 rotas em francês e 36 em alemão (texto visível e blocos JSON-LD).
+
+
## [1.4.0] - 2026-11-01
### Adicionado
diff --git a/app/[locale]/catalogo/layout.jsx b/app/[locale]/catalogo/layout.jsx
index d3a284b..df49257 100644
--- a/app/[locale]/catalogo/layout.jsx
+++ b/app/[locale]/catalogo/layout.jsx
@@ -4,12 +4,18 @@ export async function generateMetadata({ params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
return paginaMeta({
- titulo: isEn ? "ESP32 Board Catalog" : isEs ? "Catálogo de placas ESP32" : "Catálogo de placas ESP32",
+ titulo: isEn ? "ESP32 Board Catalog" : isEs ? "Catálogo de placas ESP32" : isFr ? "Catalogue de cartes ESP32" : isDe ? "ESP32-Board-Katalog" : "Catálogo de placas ESP32",
descricao: isEn
? "ESP32 development boards and DevKits available in Brazil, with filters by series, connector, and USB-serial converter."
: isEs
? "Placas de desarrollo y DevKits con ESP32, con filtros por serie, conector y conversor USB-serial."
+ : isFr
+ ? "Cartes de développement et DevKits ESP32, avec filtres par série, connecteur et convertisseur USB-série."
+ : isDe
+ ? "ESP32-Entwicklungsboards und DevKits, mit Filtern nach Serie, Anschluss und USB-Seriell-Wandler."
: "Placas de desenvolvimento com ESP32 disponíveis no Brasil, com filtro por série, conector e conversor USB-serial.",
caminho: "/catalogo",
locale,
@@ -17,6 +23,10 @@ export async function generateMetadata({ params }) {
? ["ESP32 boards", "ESP32 DevKit", "buy ESP32", "ESP32 DevKitC"]
: isEs
? ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"]
+ : isFr
+ ? ["cartes ESP32", "DevKit ESP32", "acheter ESP32", "ESP32 DevKitC"]
+ : isDe
+ ? ["ESP32 Boards", "ESP32 DevKit", "ESP32 kaufen", "ESP32 DevKitC"]
: ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"],
});
}
@@ -25,7 +35,9 @@ export default async function Layout({ children, params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
- const canonPath = isEn ? "/en/catalog" : isEs ? "/es/catalogo" : "/catalogo";
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
+ const canonPath = isEn ? "/en/catalog" : isEs ? "/es/catalogo" : isFr ? "/fr/catalogue" : isDe ? "/de/katalog" : "/catalogo";
return (
<>
@@ -34,11 +46,15 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
- nome: isEn ? "ESP32 Development Board Catalog" : isEs ? "Catálogo de placas de desarrollo ESP32" : "Catálogo de Placas ESP32",
+ nome: isEn ? "ESP32 Development Board Catalog" : isEs ? "Catálogo de placas de desarrollo ESP32" : isFr ? "Catalogue de cartes de développement ESP32" : isDe ? "Katalog für ESP32-Entwicklungsboards" : "Catálogo de Placas ESP32",
descricao: isEn
? "Interactive guide and catalog of development boards and DevKits featuring ESP32 microcontrollers."
: isEs
? "Guía y catálogo interactivo de placas de desarrollo y DevKits con microcontroladores ESP32."
+ : isFr
+ ? "Guide interactif et catalogue de cartes de développement et DevKits avec microcontrôleurs ESP32."
+ : isDe
+ ? "Interaktiver Leitfaden und Katalog für Entwicklungsboards und DevKits mit ESP32-Mikrocontrollern."
: "Guia e catálogo interativo de placas de desenvolvimento e DevKits com microcontroladores ESP32.",
caminho: canonPath,
locale,
@@ -51,8 +67,8 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { nome: isEn ? "Home" : isEs ? "Inicio" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : "/" },
- { nome: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : "Catálogo de Placas", caminho: canonPath },
+ { nome: isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/" },
+ { nome: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isDe ? "Board-Katalog" : "Catálogo de Placas", caminho: canonPath },
])
),
}}
diff --git a/app/[locale]/catalogo/page.jsx b/app/[locale]/catalogo/page.jsx
index dcef360..8c25f15 100644
--- a/app/[locale]/catalogo/page.jsx
+++ b/app/[locale]/catalogo/page.jsx
@@ -58,10 +58,62 @@ const VALUE_MAP_ES = {
"Opcional": "Opcional",
};
+const HEADER_MAP_FR = {
+ "Nome": "Nom",
+ "Descrição": "Description",
+ "Série": "Série",
+ "Loja Oficial": "Boutique officielle",
+ "Fabricante": "Fabricant",
+ "Processador": "Processeur",
+ "Memória Flash": "Mémoire Flash",
+ "PSRAM": "PSRAM",
+ "Conector USB": "Connecteur USB",
+ "Interface USB": "Interface USB",
+ "Antena": "Antenne",
+ "Bateria": "Batterie",
+ "Display": "Écran",
+ "Sensores": "Capteurs",
+ "Botões": "Boutons",
+ "LEDs": "LEDs",
+};
+
+const VALUE_MAP_FR = {
+ "Sim": "Oui",
+ "Não": "Non",
+ "Opcional": "Optionnel",
+};
+
+const HEADER_MAP_DE = {
+ "Nome": "Name",
+ "Descrição": "Beschreibung",
+ "Série": "Serie",
+ "Loja Oficial": "Offizieller Store",
+ "Fabricante": "Hersteller",
+ "Processador": "Prozessor",
+ "Memória Flash": "Flash-Speicher",
+ "PSRAM": "PSRAM",
+ "Conector USB": "USB-Anschluss",
+ "Interface USB": "USB-Schnittstelle",
+ "Antena": "Antenne",
+ "Bateria": "Batterie",
+ "Display": "Display",
+ "Sensores": "Sensoren",
+ "Botões": "Tasten",
+ "LEDs": "LEDs",
+};
+
+const VALUE_MAP_DE = {
+ "Sim": "Ja",
+ "Não": "Nein",
+ "Opcional": "Optional",
+};
+
export default function PlacasPage() {
const pathname = usePathname() || "";
const isEn = pathname === "/en" || pathname.startsWith("/en/");
const isEs = pathname === "/es" || pathname.startsWith("/es/");
+ const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
+ const isDe = pathname === "/de" || pathname.startsWith("/de/");
const [boards, setBoards] = useState([]);
const [filteredBoards, setFilteredBoards] = useState([]);
@@ -90,7 +142,7 @@ export default function PlacasPage() {
const response = await fetch("/api/placas");
if (!response.ok) {
- throw new Error(isEn ? "Failed to load board data" : isEs ? "Error al cargar los datos de las placas" : "Erro ao carregar dados das placas");
+ throw new Error(isEn ? "Failed to load board data" : isEs ? "Error al cargar los datos de las placas" : isFr ? "Erreur lors du chargement des données des cartes" : isDe ? "Fehler beim Laden der Board-Daten" : "Erro ao carregar dados das placas");
}
const result = await response.json();
@@ -195,15 +247,21 @@ export default function PlacasPage() {
@@ -215,14 +273,14 @@ export default function PlacasPage() {
{hero}
-
+
{Array.from({ length: 6 }).map((_, i) => (
))}
- {isEn ? "Querying boards database…" : isEs ? "Consultando la base de datos de placas…" : "Consultando o banco de placas…"}
+ {isEn ? "Querying boards database…" : isEs ? "Consultando la base de datos de placas…" : isFr ? "Consultation de la base de cartes…" : isDe ? "Board-Datenbank wird abgefragt…" : "Consultando o banco de placas…"}
@@ -242,14 +300,14 @@ export default function PlacasPage() {
- {isEn ? "Could not load boards" : isEs ? "No se pudieron cargar las placas" : "Não foi possível carregar as placas"}
+ {isEn ? "Could not load boards" : isEs ? "No se pudieron cargar las placas" : isFr ? "Impossible de charger les cartes" : isDe ? "Boards konnten nicht geladen werden" : "Não foi possível carregar as placas"}
- {error}. {isEn ? "Check your connection and try again." : isEs ? "Compruebe la conexión y vuelva a intentarlo." : "Verifique a conexão e tente de novo."}
+ {error}. {isEn ? "Check your connection and try again." : isEs ? "Compruebe la conexión y vuelva a intentarlo." : isFr ? "Vérifiez votre connexion et réessayez." : isDe ? "Prüfen Sie Ihre Verbindung und versuchen Sie es erneut." : "Verifique a conexão e tente de novo."}
- {isEn ? "Try again" : isEs ? "Reintentar" : "Tentar novamente"}
+ {isEn ? "Try again" : isEs ? "Reintentar" : isFr ? "Réessayer" : isDe ? "Erneut versuchen" : "Tentar novamente"}
@@ -264,14 +322,14 @@ export default function PlacasPage() {
- {isEn ? "Search board" : isEs ? "Buscar placa" : "Buscar placa"}
+ {isEn ? "Search board" : isEs ? "Buscar placa" : isFr ? "Rechercher une carte" : isDe ? "Board suchen" : "Buscar placa"}
setSearchTerm(e.target.value)}
className="field pl-10 text-sm"
@@ -282,7 +340,7 @@ export default function PlacasPage() {
{filterableColumns.map((column) => {
const filterData = filterOptions[column];
if (!filterData) return null;
- const colLabel = isEn ? (HEADER_MAP_EN[column] || column) : isEs ? (HEADER_MAP_ES[column] || column) : column;
+ const colLabel = isEn ? (HEADER_MAP_EN[column] || column) : isEs ? (HEADER_MAP_ES[column] || column) : isFr ? (HEADER_MAP_FR[column] || column) : isDe ? (HEADER_MAP_DE[column] || column) : column;
if (filterData.isBinary) {
return (
@@ -306,7 +364,7 @@ export default function PlacasPage() {
{filterData.values.map((option) => {
- const optLabel = isEn ? (VALUE_MAP_EN[option] || option) : isEs ? (VALUE_MAP_ES[option] || option) : option;
+ const optLabel = isEn ? (VALUE_MAP_EN[option] || option) : isEs ? (VALUE_MAP_ES[option] || option) : isFr ? (VALUE_MAP_FR[option] || option) : isDe ? (VALUE_MAP_DE[option] || option) : option;
return (
{hero}
-
+
setFiltrosAbertos(e.currentTarget.open)}>
- {isEn ? "Filters" : isEs ? "Filtros" : "Filtros"}
+ {isEn ? "Filters" : isEs ? "Filtros" : isFr ? "Filtres" : isDe ? "Filter" : "Filtros"}
{activeFilterCount > 0 && {activeFilterCount} }
{activeFilterCount > 0 && (
@@ -352,7 +410,7 @@ export default function PlacasPage() {
}}
className="text-sm font-medium text-accent hover:text-accent-strong"
>
- {isEn ? "Clear" : isEs ? "Limpiar" : "Limpar"}
+ {isEn ? "Clear" : isEs ? "Limpiar" : isFr ? "Effacer" : isDe ? "Zurücksetzen" : "Limpar"}
)}
@@ -371,6 +429,14 @@ export default function PlacasPage() {
? filteredBoards.length === 1
? "placa encontrada"
: "placas encontradas"
+ : isFr
+ ? filteredBoards.length === 1
+ ? "carte trouvée"
+ : "cartes trouvées"
+ : isDe
+ ? filteredBoards.length === 1
+ ? "Board gefunden"
+ : "Boards gefunden"
: filteredBoards.length === 1
? "placa encontrada"
: "placas encontradas"}
@@ -380,17 +446,21 @@ export default function PlacasPage() {
- {isEn ? "No boards match these filters" : isEs ? "Ninguna placa coincide con estos filtros" : "Nenhuma placa com esses filtros"}
+ {isEn ? "No boards match these filters" : isEs ? "Ninguna placa coincide con estos filtros" : isFr ? "Aucune carte ne correspond à ces filtres" : isDe ? "Keine Boards entsprechen diesen Filtern" : "Nenhuma placa com esses filtros"}
{isEn
? "Remove some filters or search by another name."
: isEs
? "Elimine algunos filtros o busque por otro nombre."
+ : isFr
+ ? "Supprimez certains filtres ou recherchez un autre nom."
+ : isDe
+ ? "Entfernen Sie einige Filter oder suchen Sie nach einem anderen Namen."
: "Remova alguns filtros ou busque por outro nome."}
- {isEn ? "Clear filters" : isEs ? "Limpiar filtros" : "Limpar filtros"}
+ {isEn ? "Clear filters" : isEs ? "Limpiar filtros" : isFr ? "Effacer les filtres" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
) : (
diff --git a/app/[locale]/comparacao/layout.jsx b/app/[locale]/comparacao/layout.jsx
index 3b4dc0b..8dc930a 100644
--- a/app/[locale]/comparacao/layout.jsx
+++ b/app/[locale]/comparacao/layout.jsx
@@ -4,12 +4,18 @@ export async function generateMetadata({ params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
return paginaMeta({
- titulo: isEn ? "Compare ESP32 Series: Side-by-Side Table" : isEs ? "Comparar series ESP32: tabla comparativa" : "Comparar séries ESP32",
+ titulo: isEn ? "Compare ESP32 Series: Side-by-Side Table" : isEs ? "Comparar series ESP32: tabla comparativa" : isFr ? "Comparer les séries ESP32 : tableau comparatif" : isDe ? "ESP32-Serien vergleichen: Vergleichstabelle" : "Comparar séries ESP32",
descricao: isEn
? "Compare up to four ESP32 series side by side: processor, Wi-Fi, Bluetooth, memory, GPIO, peripherals, and hardware security, with official datasheet data."
: isEs
? "Compare hasta cuatro series de ESP32 frente a frente: procesador, Wi-Fi, Bluetooth, memoria, GPIO, periféricos y seguridad en hardware, con datos de hojas de datos oficiales."
+ : isFr
+ ? "Comparez jusqu'à quatre séries d'ESP32 côte à côte : processeur, Wi-Fi, Bluetooth, mémoire, GPIO, périphériques et sécurité matérielle, avec les données des fiches techniques officielles."
+ : isDe
+ ? "Vergleichen Sie bis zu vier ESP32-Serien nebeneinander: Prozessor, Wi-Fi, Bluetooth, Speicher, GPIO, Peripherie und Hardware-Sicherheit mit Daten aus offiziellen Datenblättern."
: "Compare até quatro séries de ESP32 lado a lado: processador, Wi-Fi, Bluetooth, memória, GPIO, periféricos e segurança em hardware, com os dados dos datasheets oficiais.",
caminho: "/comparacao",
locale,
@@ -17,6 +23,10 @@ export async function generateMetadata({ params }) {
? ["compare ESP32", "ESP32 vs ESP32-S3", "ESP32 comparison table", "which ESP32 is best"]
: isEs
? ["comparar ESP32", "ESP32 vs ESP32-S3", "tabla comparativa ESP32", "cuál ESP32 es mejor"]
+ : isFr
+ ? ["comparer ESP32", "ESP32 vs ESP32-S3", "tableau comparatif ESP32", "quel ESP32 choisir"]
+ : isDe
+ ? ["ESP32 vergleichen", "ESP32 vs ESP32-S3", "ESP32 Vergleichstabelle", "welcher ESP32 ist der beste"]
: ["comparar ESP32", "ESP32 vs ESP32-S3", "tabela comparativa ESP32", "qual ESP32 é melhor"],
});
}
@@ -66,12 +76,44 @@ const faqsEs = [
},
];
+const faqsFr = [
+ {
+ pergunta: "Comment comparer les microcontrôleurs ESP32 côte à côte ?",
+ resposta: "Le comparateur d'ESPDocs vous permet de sélectionner jusqu'à quatre séries simultanément pour confronter architecture, fréquence d'horloge, mémoire SRAM/PSRAM, normes radio (Wi-Fi 4/6, Bluetooth 5/6, 802.15.4), interfaces de périphériques et blocs de sécurité matérielle.",
+ },
+ {
+ pergunta: "Quelle est la différence entre l'ESP32 classique et l'ESP32-S3 ?",
+ resposta: "L'ESP32 classique utilise des cœurs Xtensa LX6 et le Bluetooth classique/LE. L'ESP32-S3 intègre des cœurs Xtensa LX7 avec instructions vectorielles pour l'intelligence artificielle (TinyML), l'USB natif OTG, davantage de GPIOs (45 contre 36) et le Bluetooth 5.0 LE.",
+ },
+ {
+ pergunta: "Quelle est la différence entre l'ESP32-C3 et l'ESP32-C6 ?",
+ resposta: "L'ESP32-C3 est un microcontrôleur RISC-V avec Wi-Fi 4 et Bluetooth 5 LE. L'ESP32-C6 évolue vers le Wi-Fi 6 (802.11ax), le Bluetooth 5.4 LE, une radio IEEE 802.15.4 pour Zigbee 3.0 et Thread 1.3, ainsi qu'un coprocesseur à ultra-faible consommation.",
+ },
+];
+
+const faqsDe = [
+ {
+ pergunta: "Wie lassen sich ESP32-Mikrocontroller im direkten Vergleich gegenüberstellen?",
+ resposta: "Der ESPDocs-Vergleich ermöglicht es Ihnen, bis zu vier Serien gleichzeitig auszuwählen, um Architektur, Taktfrequenz, SRAM/PSRAM-Speicher, Funkstandards (Wi-Fi 4/6, Bluetooth 5/6, 802.15.4), Peripherieschnittstellen und Hardware-Sicherheitsblöcke zu vergleichen.",
+ },
+ {
+ pergunta: "Was ist der Unterschied zwischen dem klassischen ESP32 und dem ESP32-S3?",
+ resposta: "Der klassische ESP32 nutzt Xtensa-LX6-Kerne und klassisches Bluetooth/LE. Der ESP32-S3 bietet Xtensa-LX7-Kerne mit Vektoranweisungen für künstliche Intelligenz (TinyML), natives USB-OTG, mehr GPIOs (45 gegenüber 36) und Bluetooth 5.0 LE.",
+ },
+ {
+ pergunta: "Was ist der Unterschied zwischen ESP32-C3 und ESP32-C6?",
+ resposta: "Der ESP32-C3 ist ein RISC-V-Mikrocontroller mit Wi-Fi 4 und Bluetooth 5 LE. Der ESP32-C6 erweitert dies um Wi-Fi 6 (802.11ax), Bluetooth 5.4 LE, ein IEEE-802.15.4-Funkmodul für Zigbee 3.0 und Thread 1.3 sowie einen extrem stromsparenden Coprozessor.",
+ },
+];
+
export default async function Layout({ children, params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
- const faqs = isEn ? faqsEn : isEs ? faqsEs : faqsPt;
- const canonPath = isEn ? "/en/compare" : isEs ? "/es/comparacion" : "/comparacao";
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
+ const faqs = isEn ? faqsEn : isEs ? faqsEs : isFr ? faqsFr : isDe ? faqsDe : faqsPt;
+ const canonPath = isEn ? "/en/compare" : isEs ? "/es/comparacion" : isFr ? "/fr/comparaison" : isDe ? "/de/vergleich" : "/comparacao";
return (
<>
@@ -80,11 +122,15 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
- nome: isEn ? "ESP32 Series Comparator" : isEs ? "Comparador de series ESP32" : "Comparador de Séries ESP32",
+ nome: isEn ? "ESP32 Series Comparator" : isEs ? "Comparador de series ESP32" : isFr ? "Comparateur de séries ESP32" : isDe ? "ESP32-Serien-Vergleich" : "Comparador de Séries ESP32",
descricao: isEn
? "Technical comparison table to analyze specifications, pinouts, and features of multiple ESP32 series."
: isEs
? "Tabla comparativa técnica para analizar especificaciones, patillaje y características de múltiples series de ESP32."
+ : isFr
+ ? "Tableau comparatif technique pour analyser les spécifications, le brochage et les fonctionnalités de plusieurs séries ESP32."
+ : isDe
+ ? "Technische Vergleichstabelle zur Analyse von Spezifikationen, Pinbelegungen und Merkmalen mehrerer ESP32-Serien."
: "Tabela comparativa técnica para analisar especificações, pinagens e recursos de múltiplas séries de ESP32.",
caminho: canonPath,
locale,
@@ -97,8 +143,8 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { nome: isEn ? "Home" : isEs ? "Inicio" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : "/" },
- { nome: isEn ? "Compare Series" : isEs ? "Comparar series" : "Comparar Séries", caminho: canonPath },
+ { nome: isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/" },
+ { nome: isEn ? "Compare Series" : isEs ? "Comparar series" : isFr ? "Comparer les séries" : isDe ? "Serien vergleichen" : "Comparar Séries", caminho: canonPath },
])
),
}}
diff --git a/app/[locale]/comparacao/page.jsx b/app/[locale]/comparacao/page.jsx
index 06232db..79eb6c7 100644
--- a/app/[locale]/comparacao/page.jsx
+++ b/app/[locale]/comparacao/page.jsx
@@ -9,6 +9,8 @@ import Link from "next/link";
import seriesDataPt from "@/public/series.json";
import seriesDataEn from "@/data/en/series.json";
import seriesDataEs from "@/data/es/series.json";
+import seriesDataFr from "@/data/fr/series.json";
+import seriesDataDe from "@/data/de/series.json";
import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, Check, ChevronDown, Scale } from "lucide-react";
@@ -257,19 +259,179 @@ const COMPARE_FIELDS_ES = [
]},
];
+const COMPARE_FIELDS_FR = [
+ { category: "Général", fields: [
+ { key: "nome_completo", label: "Nom complet" },
+ { key: "descricao", label: "Description" },
+ ]},
+ { category: "Disponibilité et outils", fields: [
+ { key: "esp_idf_minimo", label: "ESP-IDF minimum" },
+ { key: "arduino_core", label: "Core Arduino" },
+ { key: "depuracao", label: "Débogage / JTAG" },
+ { key: "encapsulamento", label: "Boîtier" },
+ { key: "modulos", label: "Modules disponibles" },
+ ]},
+ { category: "Processeur", fields: [
+ { key: "arquitetura", label: "Architecture" },
+ { key: "nucleos", label: "Cœurs" },
+ { key: "frequencia", label: "Fréquence" },
+ { key: "coprocessador_ulp", label: "Coprocesseur ULP" },
+ { key: "aceleradores_ia", label: "Accélérateurs IA" },
+ ]},
+ { category: "Connectivité", fields: [
+ { key: "wifi", label: "Wi-Fi" },
+ { key: "bluetooth", label: "Bluetooth" },
+ { key: "zigbee_thread", label: "Zigbee/Thread" },
+ { key: "matter", label: "Matter" },
+ { key: "ethernet", label: "Ethernet" },
+ ]},
+ { category: "Mémoire", fields: [
+ { key: "memoria_sram", label: "SRAM" },
+ { key: "memoria_sram_rtc", label: "SRAM RTC" },
+ { key: "memoria_rom", label: "ROM" },
+ { key: "flash_externa", label: "Flash externe" },
+ { key: "psram_externa", label: "PSRAM externe" },
+ ]},
+ { category: "Périphériques numériques", fields: [
+ { key: "gpio", label: "GPIO" },
+ { key: "uart", label: "UART" },
+ { key: "spi", label: "SPI" },
+ { key: "i2c", label: "I²C" },
+ { key: "i2s", label: "I²S" },
+ { key: "pwm", label: "PWM" },
+ { key: "can", label: "CAN" },
+ { key: "usb", label: "USB" },
+ { key: "depuracao", label: "Débogage / JTAG" },
+ { key: "sdio", label: "SDIO" },
+ ]},
+ { category: "Périphériques analogiques", fields: [
+ { key: "adc", label: "ADC" },
+ { key: "dac", label: "DAC" },
+ { key: "touch", label: "Touch capacitif" },
+ { key: "sensor_hall", label: "Capteur Hall" },
+ { key: "sensor_temperatura", label: "Capteur de température" },
+ ]},
+ { category: "Sécurité", fields: [
+ { key: "seguranca.aes", label: "AES" },
+ { key: "seguranca.sha", label: "SHA" },
+ { key: "seguranca.rsa", label: "RSA / MPI" },
+ { key: "seguranca.ecc", label: "ECC (courbes elliptiques)" },
+ { key: "seguranca.ecdsa", label: "ECDSA matériel" },
+ { key: "seguranca.hmac", label: "HMAC" },
+ { key: "seguranca.assinatura_digital", label: "Signature numérique (DS)" },
+ { key: "seguranca.key_manager", label: "Key Manager" },
+ { key: "seguranca.rng", label: "Générateur aléatoire (TRNG)" },
+ { key: "seguranca.criptografia_flash", label: "Chiffrement de flash" },
+ { key: "seguranca.secure_boot", label: "Secure Boot" },
+ { key: "seguranca.protecao_dpa", label: "Protection contre DPA" },
+ ]},
+ { category: "Interfaces spéciales", fields: [
+ { key: "lcd", label: "LCD" },
+ { key: "camera", label: "Caméra" },
+ { key: "mipi_csi", label: "MIPI CSI" },
+ { key: "mipi_dsi", label: "MIPI DSI" },
+ { key: "ethernet_mac", label: "Ethernet MAC" },
+ ]},
+ { category: "Spécifications environnementales", fields: [
+ { key: "temperatura_operacao", label: "Temp. de fonctionnement" },
+ { key: "temperatura_armazenamento", label: "Temp. de stockage" },
+ ]},
+];
+
+const COMPARE_FIELDS_DE = [
+ { category: "Allgemein", fields: [
+ { key: "nome_completo", label: "Vollständiger Name" },
+ { key: "descricao", label: "Beschreibung" },
+ ]},
+ { category: "Verfügbarkeit und Werkzeuge", fields: [
+ { key: "esp_idf_minimo", label: "Mindest-ESP-IDF" },
+ { key: "arduino_core", label: "Arduino-Core" },
+ { key: "depuracao", label: "Debugging / JTAG" },
+ { key: "encapsulamento", label: "Gehäuse" },
+ { key: "modulos", label: "Verfügbare Module" },
+ ]},
+ { category: "Prozessor", fields: [
+ { key: "arquitetura", label: "Architektur" },
+ { key: "nucleos", label: "Kerne" },
+ { key: "frequencia", label: "Taktfrequenz" },
+ { key: "coprocessador_ulp", label: "ULP-Coprozessor" },
+ { key: "aceleradores_ia", label: "KI-Beschleuniger" },
+ ]},
+ { category: "Konnektivität", fields: [
+ { key: "wifi", label: "Wi-Fi" },
+ { key: "bluetooth", label: "Bluetooth" },
+ { key: "zigbee_thread", label: "Zigbee/Thread" },
+ { key: "matter", label: "Matter" },
+ { key: "ethernet", label: "Ethernet" },
+ ]},
+ { category: "Speicher", fields: [
+ { key: "memoria_sram", label: "SRAM" },
+ { key: "memoria_sram_rtc", label: "RTC-SRAM" },
+ { key: "memoria_rom", label: "ROM" },
+ { key: "flash_externa", label: "Externer Flash" },
+ { key: "psram_externa", label: "Externer PSRAM" },
+ ]},
+ { category: "Digitale Peripherie", fields: [
+ { key: "gpio", label: "GPIO" },
+ { key: "uart", label: "UART" },
+ { key: "spi", label: "SPI" },
+ { key: "i2c", label: "I²C" },
+ { key: "i2s", label: "I²S" },
+ { key: "pwm", label: "PWM" },
+ { key: "can", label: "CAN" },
+ { key: "usb", label: "USB" },
+ { key: "depuracao", label: "Debugging / JTAG" },
+ { key: "sdio", label: "SDIO" },
+ ]},
+ { category: "Analoge Peripherie", fields: [
+ { key: "adc", label: "ADC" },
+ { key: "dac", label: "DAC" },
+ { key: "touch", label: "Kapazitiver Touch" },
+ { key: "sensor_hall", label: "Hall-Sensor" },
+ { key: "sensor_temperatura", label: "Temperatursensor" },
+ ]},
+ { category: "Sicherheit", fields: [
+ { key: "seguranca.aes", label: "AES" },
+ { key: "seguranca.sha", label: "SHA" },
+ { key: "seguranca.rsa", label: "RSA / MPI" },
+ { key: "seguranca.ecc", label: "ECC (Elliptische Kurven)" },
+ { key: "seguranca.ecdsa", label: "Hardware-ECDSA" },
+ { key: "seguranca.hmac", label: "HMAC" },
+ { key: "seguranca.assinatura_digital", label: "Digitale Signatur (DS)" },
+ { key: "seguranca.key_manager", label: "Key Manager" },
+ { key: "seguranca.rng", label: "Zufallsgenerator (TRNG)" },
+ { key: "seguranca.criptografia_flash", label: "Flash-Verschlüsselung" },
+ { key: "seguranca.secure_boot", label: "Secure Boot" },
+ { key: "seguranca.protecao_dpa", label: "DPA-Schutz" },
+ ]},
+ { category: "Spezielle Schnittstellen", fields: [
+ { key: "lcd", label: "LCD" },
+ { key: "camera", label: "Kamera" },
+ { key: "mipi_csi", label: "MIPI CSI" },
+ { key: "mipi_dsi", label: "MIPI DSI" },
+ { key: "ethernet_mac", label: "Ethernet MAC" },
+ ]},
+ { category: "Umgebungsbedingungen", fields: [
+ { key: "temperatura_operacao", label: "Betriebstemp." },
+ { key: "temperatura_armazenamento", label: "Lagertemp." },
+ ]},
+];
+
export default function Comparacao() {
const pathname = usePathname();
const isEn = pathname?.startsWith("/en");
const isEs = pathname?.startsWith("/es");
- const currentLocale = isEs ? "es" : isEn ? "en" : "pt";
- const seriesData = isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
- const compareFields = isEs ? COMPARE_FIELDS_ES : isEn ? COMPARE_FIELDS_EN : COMPARE_FIELDS_PT;
+ const isFr = pathname?.startsWith("/fr");
+ const isDe = pathname?.startsWith("/de");
+ const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isDe ? "de" : "pt";
+ const seriesData = isEs ? seriesDataEs : isEn ? seriesDataEn : isFr ? seriesDataFr : isDe ? seriesDataDe : seriesDataPt;
+ const compareFields = isEs ? COMPARE_FIELDS_ES : isEn ? COMPARE_FIELDS_EN : isFr ? COMPARE_FIELDS_FR : isDe ? COMPARE_FIELDS_DE : COMPARE_FIELDS_PT;
const [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
const [soDiferencas, setSoDiferencas] = useState(false);
const [collapsedCategories, setCollapsedCategories] = useState(() => ({
- [isEs ? "Interfaces especiales" : isEn ? "Special Interfaces" : "Interfaces Especiais"]: true,
- [isEs ? "Especificaciones ambientales" : isEn ? "Environmental Specifications" : "Especificações Ambientais"]: true,
+ [isEs ? "Interfaces especiales" : isEn ? "Special Interfaces" : isFr ? "Interfaces spéciales" : isDe ? "Spezielle Schnittstellen" : "Interfaces Especiais"]: true,
+ [isEs ? "Especificaciones ambientales" : isEn ? "Environmental Specifications" : isFr ? "Spécifications environnementales" : isDe ? "Umgebungsbedingungen" : "Especificações Ambientais"]: true,
}));
const series = Object.entries(seriesData);
@@ -295,19 +457,19 @@ export default function Comparacao() {
};
const renderValue = (value) => {
- if (!value || value === "Não" || value === "No") return {isEs || isEn ? "No" : "Não"} ;
- if (value === "Sim" || value === "Yes" || value === "Sí") {
+ if (!value || value === "Não" || value === "No" || value === "Non" || value === "Nein") return {isEs || isEn ? "No" : isFr ? "Non" : isDe ? "Nein" : "Não"} ;
+ if (value === "Sim" || value === "Yes" || value === "Sí" || value === "Oui" || value === "Ja") {
return (
- {isEs ? "Sí" : isEn ? "Yes" : "Sim"}
+ {isEs ? "Sí" : isEn ? "Yes" : isFr ? "Oui" : isDe ? "Ja" : "Sim"}
);
}
return {value} ;
};
- const valores = (key) => selectedSeries.map((k) => String(valorDe(seriesData[k], key) ?? (isEs || isEn ? "No" : "Não")));
+ const valores = (key) => selectedSeries.map((k) => String(valorDe(seriesData[k], key) ?? (isEs || isEn ? "No" : isFr ? "Non" : isDe ? "Nein" : "Não")));
const diferem = (key) => new Set(valores(key)).size > 1;
return (
@@ -318,15 +480,21 @@ export default function Comparacao() {
@@ -340,6 +508,10 @@ export default function Comparacao() {
<>Elija de {MIN_SERIES} a {MAX_SERIES} series>
) : isEn ? (
<>Choose {MIN_SERIES} to {MAX_SERIES} series>
+ ) : isFr ? (
+ <>Choisissez de {MIN_SERIES} à {MAX_SERIES} séries>
+ ) : isDe ? (
+ <>Wählen Sie {MIN_SERIES} bis {MAX_SERIES} Serien>
) : (
<>Escolha de {MIN_SERIES} a {MAX_SERIES} séries>
)}
@@ -355,6 +527,16 @@ export default function Comparacao() {
{selectedSeries.length} of {MAX_SERIES} selected
{selectedSeries.length === MAX_SERIES && ". Remove one to switch."}
>
+ ) : isFr ? (
+ <>
+ {selectedSeries.length} sur {MAX_SERIES} sélectionnées
+ {selectedSeries.length === MAX_SERIES && ". Supprimez-en une pour en changer."}
+ >
+ ) : isDe ? (
+ <>
+ {selectedSeries.length} von {MAX_SERIES} ausgewählt
+ {selectedSeries.length === MAX_SERIES && ". Entfernen Sie eine zum Wechseln."}
+ >
) : (
<>
{selectedSeries.length} de {MAX_SERIES} selecionadas
@@ -389,7 +571,7 @@ export default function Comparacao() {
- {isEs ? "Especificaciones" : isEn ? "Specifications" : "Especificações"}
+ {isEs ? "Especificaciones" : isEn ? "Specifications" : isFr ? "Spécifications" : isDe ? "Spezifikationen" : "Especificações"}
setSoDiferencas(e.target.checked)}
className="h-5 w-5 cursor-pointer accent-[var(--accent)]"
/>
- {isEs ? "Mostrar solo diferencias" : isEn ? "Show differences only" : "Mostrar só as diferenças"}
+ {isEs ? "Mostrar solo diferencias" : isEn ? "Show differences only" : isFr ? "Afficher uniquement les différences" : isDe ? "Nur Unterschiede anzeigen" : "Mostrar só as diferenças"}
@@ -410,6 +592,10 @@ export default function Comparacao() {
<>Comparación de las series {selectedSeries.join(", ")}>
) : isEn ? (
<>Comparison of series {selectedSeries.join(", ")}>
+ ) : isFr ? (
+ <>Comparaison des séries {selectedSeries.join(", ")}>
+ ) : isDe ? (
+ <>Vergleich der Serien {selectedSeries.join(", ")}>
) : (
<>Comparação das séries {selectedSeries.join(", ")}>
)}
@@ -417,7 +603,7 @@ export default function Comparacao() {
- {isEs ? "Especificación" : isEn ? "Specification" : "Especificação"}
+ {isEs ? "Especificación" : isEn ? "Specification" : isFr ? "Spécification" : isDe ? "Spezifikation" : "Especificação"}
{selectedSeries.map((seriesKey) => (
@@ -474,7 +660,7 @@ export default function Comparacao() {
- {isEs ? "Documentación oficial" : isEn ? "Official documentation" : "Documentação oficial"}
+ {isEs ? "Documentación oficial" : isEn ? "Official documentation" : isFr ? "Documentation officielle" : isDe ? "Offizielle Dokumentation" : "Documentação oficial"}
@@ -503,18 +689,22 @@ export default function Comparacao() {
- {isEs ? "¿Desea probar en banco?" : isEn ? "Want to test on the bench?" : "Quer testar na bancada?"}
+ {isEs ? "¿Desea probar en banco?" : isEn ? "Want to test on the bench?" : isFr ? "Envie de tester sur l'établi ?" : isDe ? "Möchten Sie es auf dem Prüfplatz testen ?" : "Quer testar na bancada?"}
{isEs
? "Vea las placas de desarrollo con patillaje, periféricos y enlace a la tienda oficial."
: isEn
? "Explore development boards with pinouts, peripherals, and official store links."
+ : isFr
+ ? "Découvrez les cartes de développement avec brochage, périphériques et lien vers la boutique officielle."
+ : isDe
+ ? "Entdecken Sie die Entwicklungsboards mit Pinbelegung, Peripherie und Link zum offiziellen Store."
: "Veja as placas de desenvolvimento com pinagem, periféricos e link para a loja."}
- {isEs ? "Ver catálogo de placas" : isEn ? "View boards catalog" : "Ver catálogo de placas"}
+ {isEs ? "Ver catálogo de placas" : isEn ? "View boards catalog" : isFr ? "Voir le catalogue de cartes" : isDe ? "Board-Katalog ansehen" : "Ver catálogo de placas"}
diff --git a/app/[locale]/componentes/layout.jsx b/app/[locale]/componentes/layout.jsx
index 83a7b7a..c862788 100644
--- a/app/[locale]/componentes/layout.jsx
+++ b/app/[locale]/componentes/layout.jsx
@@ -4,16 +4,18 @@ export async function generateMetadata({ params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
return paginaMeta({
- titulo: isEs
- ? "ESP Component Registry & IDF Component Manager · ESPDocs"
- : isEn
- ? "ESP Component Registry & IDF Component Manager · ESPDocs"
- : "ESP Component Registry & IDF Component Manager · ESPDocs",
+ titulo: "ESP Component Registry & IDF Component Manager · ESPDocs",
descricao: isEs
? "Catálogo interactivo del ESP Component Registry: controladores de pantalla, LVGL, reconocimiento de voz ESP-SR, RainMaker, BSPs y generador de idf_component.yml."
: isEn
? "Official interactive catalog of the ESP Component Registry: display drivers, LVGL, ESP-SR voice recognition, RainMaker, BSPs, and idf_component.yml generator."
+ : isFr
+ ? "Catalogue interactif du ESP Component Registry : pilotes d'écran, LVGL, reconnaissance vocale ESP-SR, RainMaker, BSPs et générateur d'idf_component.yml."
+ : isDe
+ ? "Interaktiver Katalog der ESP Component Registry: Display-Treiber, LVGL, ESP-SR-Sprachkennung, RainMaker, BSPs und idf_component.yml-Generator."
: "Catálogo oficial e interativo do ESP Component Registry: drivers de displays, LVGL, reconhecimento de voz ESP-SR, RainMaker, BSPs e gerador de idf_component.yml.",
caminho: "/componentes",
locale,
@@ -41,6 +43,30 @@ export async function generateMetadata({ params }) {
"espressif esp-sr",
"ESP-IDF libraries",
]
+ : isFr
+ ? [
+ "ESP Component Registry",
+ "IDF Component Manager",
+ "idf_component.yml",
+ "idf.py add-dependency",
+ "composants ESP32",
+ "espressif esp_lvgl_port",
+ "espressif esp_rainmaker",
+ "espressif esp-sr",
+ "bibliothèques ESP-IDF",
+ ]
+ : isDe
+ ? [
+ "ESP Component Registry",
+ "IDF Component Manager",
+ "idf_component.yml",
+ "idf.py add-dependency",
+ "ESP32 Komponenten",
+ "espressif esp_lvgl_port",
+ "espressif esp_rainmaker",
+ "espressif esp-sr",
+ "ESP-IDF Bibliotheken",
+ ]
: [
"ESP Component Registry",
"IDF Component Manager",
@@ -112,11 +138,52 @@ const faqsEs = [
},
];
+const faqsFr = [
+ {
+ pergunta: "Qu'est-ce que l'ESP Component Registry et l'IDF Component Manager ?",
+ resposta: "L'ESP Component Registry (components.espressif.com) est le dépôt officiel de paquets modulaires d'Espressif. L'IDF Component Manager est l'outil intégré à ESP-IDF (dès la v4.4 et standard en v5.x+) qui télécharge, compile et résout automatiquement les versions des dépendances à partir du manifeste idf_component.yml.",
+ },
+ {
+ pergunta: "Comment ajouter un composant à mon projet ESP-IDF ?",
+ resposta: "Vous pouvez exécuter la commande 'idf.py add-dependency \"namespace/composant^version\"' dans le terminal de votre projet (par exemple : idf.py add-dependency \"espressif/esp_lvgl_port^2.4.0\") ou modifier directement le fichier main/idf_component.yml.",
+ },
+ {
+ pergunta: "Quelle est la différence entre le Component Registry et les sous-modules git ?",
+ resposta: "Le Component Registry élimine la complexité de gestion des sous-modules Git volumineux et des versions manuelles. Il apporte le versionnement sémantique (^ et ~), la résolution automatique des dépendances transitives et la compilation modulaire à la demande.",
+ },
+ {
+ pergunta: "Puis-je créer et publier mes propres composants ?",
+ resposta: "Oui, tout développeur ou entreprise peut publier des composants publics ou privés sur l'ESP Component Registry à l'aide de l'outil officiel Compote CLI (pip install idf-component-manager).",
+ },
+];
+
+const faqsDe = [
+ {
+ pergunta: "Was sind die ESP Component Registry und der IDF Component Manager?",
+ resposta: "Die ESP Component Registry (components.espressif.com) ist das offizielle Repository für modulare Espressif-Pakete. Der IDF Component Manager ist das in ESP-IDF (ab v4.4 und Standard in v5.x+) integrierte Werkzeug, das Abhängigkeiten anhand des idf_component.yml-Manifests automatisch herunterlädt, kompiliert und auflöst.",
+ },
+ {
+ pergunta: "Wie füge ich eine Komponente zu meinem ESP-IDF-Projekt hinzu?",
+ resposta: "Sie können den Befehl 'idf.py add-dependency \"namespace/komponente^version\"' im Projektterminal ausführen (z. B.: idf.py add-dependency \"espressif/esp_lvgl_port^2.4.0\") oder direkt die Datei main/idf_component.yml bearbeiten.",
+ },
+ {
+ pergunta: "Was ist der Unterschied zwischen der Component Registry und Git-Submodulen?",
+ resposta: "Die Component Registry beseitigt die Komplexität schwerfälliger Git-Submodule und manueller Versionsverwaltung. Sie bietet semantische Versionierung (^ und ~), automatische transitive Abhängigkeitsauflösung und modulare On-Demand-Kompilierung.",
+ },
+ {
+ pergunta: "Kann ich eigene Komponenten erstellen und veröffentlichen?",
+ resposta: "Ja, Entwickler und Unternehmen können öffentliche oder private Komponenten mit dem offiziellen Compote-CLI-Tool (pip install idf-component-manager) in der ESP Component Registry veröffentlichen.",
+ },
+];
+
export default async function ComponentesLayout({ children, params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
- const faqs = isEs ? faqsEs : isEn ? faqsEn : faqsPt;
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
+ const faqs = isEs ? faqsEs : isEn ? faqsEn : isFr ? faqsFr : isDe ? faqsDe : faqsPt;
+ const canonPath = isEs ? "/es/componentes" : isEn ? "/en/components" : isFr ? "/fr/composants" : isDe ? "/de/komponenten" : "/componentes";
return (
<>
@@ -125,8 +192,8 @@ export default async function ComponentesLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { nome: isEs ? "Inicio" : isEn ? "Home" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : "/" },
- { nome: "ESP Component Registry", caminho: isEs ? "/es/componentes" : isEn ? "/en/components" : "/componentes" },
+ { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isDe ? "/de" : "/" },
+ { nome: "ESP Component Registry", caminho: canonPath },
])
),
}}
@@ -146,13 +213,21 @@ export default async function ComponentesLayout({ children, params }) {
? "Hub de ESP Component Registry & Generador de Manifiesto"
: isEn
? "ESP Component Registry Hub & Manifest Builder"
+ : isFr
+ ? "Hub de l'ESP Component Registry & Générateur de Manifeste"
+ : isDe
+ ? "ESP Component Registry Hub & Manifest-Generator"
: "Hub do ESP Component Registry & Construtor de Manifesto",
descricao: isEs
? "Catálogo curado y generador de manifiesto idf_component.yml para componentes modulares de ESP-IDF."
: isEn
? "Curated catalog and idf_component.yml manifest generator for modular ESP-IDF components."
+ : isFr
+ ? "Catalogue sélectionné et générateur de manifeste idf_component.yml pour les composants modulaires d'ESP-IDF."
+ : isDe
+ ? "Kuratierter Katalog und idf_component.yml-Manifest-Generator für modulare ESP-IDF-Komponenten."
: "Catálogo curado e gerador de manifesto idf_component.yml para componentes modulares do ESP-IDF.",
- caminho: isEs ? "/es/componentes" : isEn ? "/en/components" : "/componentes",
+ caminho: canonPath,
locale,
})
),
diff --git a/app/[locale]/componentes/page.jsx b/app/[locale]/componentes/page.jsx
index 82bebf1..c3c7a46 100644
--- a/app/[locale]/componentes/page.jsx
+++ b/app/[locale]/componentes/page.jsx
@@ -9,6 +9,8 @@ import SectionHead from "@/components/SectionHead";
import * as componentesPt from "@/lib/componentRegistryData";
import * as componentesEn from "@/lib/data/en/componentes";
import * as componentesEs from "@/lib/data/es/componentes";
+import * as componentesFr from "@/lib/data/fr/componentes";
+import * as componentesDe from "@/lib/data/de/componentes";
import {
ArrowUpRight,
Box,
@@ -46,7 +48,9 @@ export default function ComponentRegistryPage() {
const pathname = usePathname();
const isEn = pathname?.startsWith("/en");
const isEs = pathname?.startsWith("/es");
- const { CATEGORIAS_COMPONENTES, COMPONENTES_REGISTRY, GUIA_COMPONENT_MANAGER } = isEs ? componentesEs : isEn ? componentesEn : componentesPt;
+ const isFr = pathname?.startsWith("/fr");
+ const isDe = pathname?.startsWith("/de");
+ const { CATEGORIAS_COMPONENTES, COMPONENTES_REGISTRY, GUIA_COMPONENT_MANAGER } = isEs ? componentesEs : isEn ? componentesEn : isFr ? componentesFr : isDe ? componentesDe : componentesPt;
const [busca, setBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
@@ -104,6 +108,10 @@ export default function ComponentRegistryPage() {
? `# No hay componentes seleccionados.\n# Haga clic en "+ Agregar al manifiesto" en las tarjetas de abajo.`
: isEn
? `# No components selected.\n# Click "+ Add to Manifest" on the cards below.`
+ : isFr
+ ? `# Aucun composant sélectionné.\n# Cliquez sur "+ Ajouter au manifeste" sur les cartes ci-dessous.`
+ : isDe
+ ? `# Keine Komponenten ausgewählt.\n# Klicken Sie auf den Karten unten auf "+ Zum Manifest hinzufügen".`
: `# Nenhum componente selecionado.\n# Clique em "+ Adicionar ao Manifesto" nos cards abaixo.`;
}
@@ -111,12 +119,16 @@ export default function ComponentRegistryPage() {
? `# main/idf_component.yml\n# Generado mediante ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/es/componentes)\n\ndependencies:\n`
: isEn
? `# main/idf_component.yml\n# Generated via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/en/components)\n\ndependencies:\n`
+ : isFr
+ ? `# main/idf_component.yml\n# Généré via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/fr/composants)\n\ndependencies:\n`
+ : isDe
+ ? `# main/idf_component.yml\n# Erstellt über den ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/de/komponenten)\n\ndependencies:\n`
: `# main/idf_component.yml\n# Gerado via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/componentes)\n\ndependencies:\n`;
selecionados.forEach((c) => {
yaml += ` ${c.namespace}/${c.name}: "${c.versao}"\n`;
});
return yaml;
- }, [componentesSelecionados, COMPONENTES_REGISTRY, isEn, isEs]);
+ }, [componentesSelecionados, COMPONENTES_REGISTRY, isEn, isEs, isFr, isDe]);
const copiarManifesto = () => {
navigator.clipboard.writeText(manifestoYaml);
@@ -152,7 +164,9 @@ export default function ComponentRegistryPage() {
- {isEs ? "Abrir el registro oficial" : isEn ? "Open official registry" : "Abrir o registro oficial"}
+ {isEs ? "Abrir el registro oficial" : isEn ? "Open official registry" : isFr ? "Ouvrir le registre officiel" : isDe ? "Offizielles Repository öffnen" : "Abrir o registro oficial"}
- {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : "(abre em nova aba)"}
+ {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : isFr ? "(ouvre un nouvel onglet)" : isDe ? "(öffnet in neuem Tab)" : "(abre em nova aba)"}
- {isEs ? "Generar idf_component.yml" : isEn ? "Generate idf_component.yml" : "Gerar idf_component.yml"}
+ {isEs ? "Generar idf_component.yml" : isEn ? "Generate idf_component.yml" : isFr ? "Générer idf_component.yml" : isDe ? "idf_component.yml erstellen" : "Gerar idf_component.yml"}
-
+
- {isEs ? "Buscar por nombre, namespace o etiqueta" : isEn ? "Search by name, namespace, or tag" : "Buscar por nome, namespace ou tag"}
+ {isEs ? "Buscar por nombre, namespace o etiqueta" : isEn ? "Search by name, namespace, or tag" : isFr ? "Rechercher par nom, namespace ou tag" : isDe ? "Nach Name, Namespace oder Tag suchen" : "Buscar por nome, namespace ou tag"}
setBusca(e.target.value)}
- placeholder={isEs ? "Buscar por nombre, namespace o etiqueta" : isEn ? "Search by name, namespace, or tag" : "Buscar por nome, namespace ou tag"}
+ placeholder={isEs ? "Buscar por nombre, namespace o etiqueta" : isEn ? "Search by name, namespace, or tag" : isFr ? "Rechercher par nom, namespace ou tag" : isDe ? "Nach Name, Namespace oder Tag suchen" : "Buscar por nome, namespace ou tag"}
autoComplete="off"
className="field pl-12 pr-4 text-base"
/>
-
{isEs ? "Categoría" : isEn ? "Category" : "Categoria"}
+
{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isDe ? "Kategorie" : "Categoria"}
{CATEGORIAS_COMPONENTES.map((cat) => {
const Icone = ICONES_CATEGORIA[cat.id] ?? Layers;
@@ -201,7 +219,7 @@ export default function ComponentRegistryPage() {
return (
setCategoriaAtiva(cat.id)} aria-pressed={categoriaAtiva === cat.id} className="chip-btn">
- {cat.id === "todos" ? (isEs ? "Todos" : isEn ? "All" : "Todos") : cat.nome.replace(/\s*\(.*\)/, "").split(",")[0].split(" &")[0]}
+ {cat.id === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todos") : cat.nome.replace(/\s*\(.*\)/, "").split(",")[0].split(" &")[0]}
{contagem}
);
@@ -210,11 +228,11 @@ export default function ComponentRegistryPage() {
-
{isEs ? "Chip compatible" : isEn ? "Compatible chip" : "Chip compatível"}
+
{isEs ? "Chip compatible" : isEn ? "Compatible chip" : isFr ? "Puce compatible" : isDe ? "Kompatibler Chip" : "Chip compatível"}
{chipsDisponiveis.map((chip) => (
setChipFiltro(chip)} aria-pressed={chipFiltro === chip} className="chip-btn">
- {chip === "todos" ? (isEs ? "Todos" : isEn ? "All" : "Todos") : chip}
+ {chip === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todos") : chip}
))}
@@ -225,17 +243,17 @@ export default function ComponentRegistryPage() {
- {componentesFiltrados.length} {componentesFiltrados.length === 1 ? (isEs ? "componente" : isEn ? "component" : "componente") : (isEs ? "componentes" : isEn ? "components" : "componentes")}
+ {componentesFiltrados.length} {componentesFiltrados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isDe ? "Komponenten" : "componentes")}
@@ -244,17 +262,21 @@ export default function ComponentRegistryPage() {
- {isEs ? "No se encontraron componentes" : isEn ? "No components found" : "Nenhum componente encontrado"}
+ {isEs ? "No se encontraron componentes" : isEn ? "No components found" : isFr ? "Aucun composant trouvé" : isDe ? "Keine Komponenten gefunden" : "Nenhum componente encontrado"}
{isEs
? "Busque por un término más general o regrese a todas las categorías."
: isEn
? "Search for a more general term or return to all categories."
+ : isFr
+ ? "Recherchez un terme plus général ou revenez à toutes les catégories."
+ : isDe
+ ? "Suchen Sie nach einem allgemeineren Begriff oder kehren Sie zu allen Kategorien zurück."
: "Busque por um termo mais geral ou volte para todas as categorias."}
- {isEs ? "Limpiar filtros" : isEn ? "Clear filters" : "Limpar filtros"}
+ {isEs ? "Limpiar filtros" : isEn ? "Clear filters" : isFr ? "Effacer les filtres" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
) : (
@@ -274,7 +296,7 @@ export default function ComponentRegistryPage() {
{comp.oficial && (
- {isEs ? "Oficial" : isEn ? "Official" : "Oficial"}
+ {isEs ? "Oficial" : isEn ? "Official" : isFr ? "Officiel" : isDe ? "Offiziell" : "Oficial"}
)}
@@ -298,12 +320,12 @@ export default function ComponentRegistryPage() {
type="button"
onClick={() => copiarComando(comp.id, cliCmd)}
className="code-panel flex w-full min-h-11 items-center justify-between gap-3 rounded-lg px-3 py-2 text-left font-mono text-xs transition-colors hover:border-white/30"
- aria-label={isEs ? `Copiar comando para agregar ${comp.name}` : isEn ? `Copy command to add ${comp.name}` : `Copiar comando para adicionar ${comp.name}`}
+ aria-label={isEs ? `Copiar comando para agregar ${comp.name}` : isEn ? `Copy command to add ${comp.name}` : isFr ? `Copier la commande pour ajouter ${comp.name}` : isDe ? `Befehl zum Hinzufügen von ${comp.name} kopieren` : `Copiar comando para adicionar ${comp.name}`}
>
{cliCmd}
{foiCopiado ? : }
- {foiCopiado ? (isEs ? "Comando copiado" : isEn ? "Command copied" : "Comando copiado") : ""}
+ {foiCopiado ? (isEs ? "Comando copiado" : isEn ? "Command copied" : isFr ? "Commande copiée" : isDe ? "Befehl kopiert" : "Comando copiado") : ""}
Chips:
@@ -319,14 +341,14 @@ export default function ComponentRegistryPage() {
className={`btn btn-sm ${selecionado ? "btn-primary" : "btn-ghost"}`}
>
{selecionado ? : }
- {selecionado ? (isEs ? "En manifiesto" : isEn ? "In manifest" : "No manifesto") : (isEs ? "Agregar al manifiesto" : isEn ? "Add to manifest" : "Adicionar ao manifesto")}
+ {selecionado ? (isEs ? "En manifiesto" : isEn ? "In manifest" : isFr ? "Dans le manifeste" : isDe ? "Im Manifest" : "No manifesto") : (isEs ? "Agregar al manifiesto" : isEn ? "Add to manifest" : isFr ? "Ajouter au manifeste" : isDe ? "Zum Manifest hinzufügen" : "Adicionar ao manifesto")}
@@ -342,15 +364,19 @@ export default function ComponentRegistryPage() {
-
{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : "Gerador de manifesto"}
+
{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : isFr ? "Générateur de manifeste" : isDe ? "Manifest-Generator" : "Gerador de manifesto"}
- {isEs ? "Su idf_component.yml" : isEn ? "Your idf_component.yml" : "Seu idf_component.yml"}
+ {isEs ? "Su idf_component.yml" : isEn ? "Your idf_component.yml" : isFr ? "Votre idf_component.yml" : isDe ? "Ihre idf_component.yml" : "Seu idf_component.yml"}
{isEs ? (
<>Guarde el archivo en la carpeta main/ del proyecto y ESP-IDF descargará todas las bibliotecas durante la compilación.>
) : isEn ? (
<>Save this file in your project's main/ folder and ESP-IDF will download all dependencies during build.>
+ ) : isFr ? (
+ <>Enregistrez le fichier dans le dossier main/ du projet et ESP-IDF téléchargera toutes les bibliothèques lors de la compilation.>
+ ) : isDe ? (
+ <>Speichern Sie die Datei im Ordner main/ Ihres Projekts, und ESP-IDF lädt beim Build alle Abhängigkeiten herunter.>
) : (
<>Salve o arquivo na pasta main/ do projeto e o ESP-IDF baixa todas as bibliotecas na compilação.>
)}
@@ -359,27 +385,27 @@ export default function ComponentRegistryPage() {
{manifestoCopiado ? : }
- {manifestoCopiado ? (isEs ? "Copiado" : isEn ? "Copied" : "Copiado") : (isEs ? "Copiar YAML" : isEn ? "Copy YAML" : "Copiar YAML")}
+ {manifestoCopiado ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar YAML" : isEn ? "Copy YAML" : isFr ? "Copier le YAML" : isDe ? "YAML kopieren" : "Copiar YAML")}
- {isEs ? "Descargar .yml" : isEn ? "Download .yml" : "Baixar .yml"}
+ {isEs ? "Descargar .yml" : isEn ? "Download .yml" : isFr ? "Télécharger .yml" : isDe ? ".yml herunterladen" : "Baixar .yml"}
{componentesSelecionados.length > 0 && (
setComponentesSelecionados([])} className="btn btn-ghost btn-sm">
- {isEs ? "Limpiar" : isEn ? "Clear" : "Limpar"}
+ {isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isDe ? "Leeren" : "Limpar"}
)}
-
{manifestoYaml}
+
{manifestoYaml}
{componentesSelecionados.length > 0 && (
-
+
{componentesSelecionados.map((id) => {
const c = COMPONENTES_REGISTRY.find((item) => item.id === id);
if (!c) return null;
@@ -390,7 +416,7 @@ export default function ComponentRegistryPage() {
type="button"
onClick={() => toggleSelecaoManifesto(id)}
className="-my-2 grid h-11 w-11 place-items-center rounded-full hover:bg-accent/15"
- aria-label={isEs ? `Eliminar ${c.name} del manifiesto` : isEn ? `Remove ${c.name} from manifest` : `Remover ${c.name} do manifesto`}
+ aria-label={isEs ? `Eliminar ${c.name} del manifiesto` : isEn ? `Remove ${c.name} from manifest` : isFr ? `Retirer ${c.name} du manifeste` : isDe ? `${c.name} aus dem Manifest entfernen` : `Remover ${c.name} do manifesto`}
>
@@ -403,7 +429,7 @@ export default function ComponentRegistryPage() {
-
+
{GUIA_COMPONENT_MANAGER.vantagens.map((v) => (
@@ -416,7 +442,7 @@ export default function ComponentRegistryPage() {
- {isEs ? "Comandos esenciales" : isEn ? "Essential commands" : "Comandos essenciais"}
+ {isEs ? "Comandos esenciales" : isEn ? "Essential commands" : isFr ? "Commandes essentielles" : isDe ? "Wichtige Befehle" : "Comandos essenciais"}
{GUIA_COMPONENT_MANAGER.comandosCli.map((cmd, i) => (
@@ -431,7 +457,7 @@ export default function ComponentRegistryPage() {
className="inline-flex min-h-11 shrink-0 items-center gap-1.5 self-start rounded-full border border-white/15 px-4 text-sm font-medium text-[#e9eef4] transition-colors hover:bg-white/10 md:self-auto"
>
{copiadoId === `cmd-${i}` ? : }
- {copiadoId === `cmd-${i}` ? (isEs ? "Copiado" : isEn ? "Copied" : "Copiado") : (isEs ? "Copiar" : isEn ? "Copy" : "Copiar")}
+ {copiadoId === `cmd-${i}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar" : isEn ? "Copy" : isFr ? "Copier" : isDe ? "Kopieren" : "Copiar")}
))}
@@ -440,7 +466,7 @@ export default function ComponentRegistryPage() {
- {isEs ? "Versionamiento semántico en ESP-IDF" : isEn ? "Semantic versioning in ESP-IDF" : "Versionamento semântico no ESP-IDF"}
+ {isEs ? "Versionamiento semántico en ESP-IDF" : isEn ? "Semantic versioning in ESP-IDF" : isFr ? "Versionnement sémantique dans ESP-IDF" : isDe ? "Semantische Versionierung in ESP-IDF" : "Versionamento semântico no ESP-IDF"}
{isEs ? (
@@ -457,6 +483,20 @@ export default function ComponentRegistryPage() {
1.2.0: locks to this exact version.
*: accepts any version. Avoid in production.
>
+ ) : isFr ? (
+ <>
+ ^1.2.0: accepte les mises à jour mineures et les correctifs (1.2.1, 1.3.0), mais bloque les changements majeurs d'API (2.0.0). Recommandé.
+ ~1.2.0: accepte uniquement les correctifs de bugs (1.2.1, 1.2.5).
+ 1.2.0: verrouille sur cette version exacte.
+ *: accepte n'importe quelle version. À éviter en production.
+ >
+ ) : isDe ? (
+ <>
+ ^1.2.0: akzeptiert Minor-Updates und Bugfixes (1.2.1, 1.3.0), blockiert aber Breaking Changes (2.0.0). Empfohlen.
+ ~1.2.0: akzeptiert nur Patch-Releases (1.2.1, 1.2.5).
+ 1.2.0: bindet fest an diese Version.
+ *: akzeptiert jede Version. In Produktion vermeiden.
+ >
) : (
<>
^1.2.0: aceita atualizações menores e correções (1.2.1, 1.3.0), mas bloqueia mudanças grandes de API (2.0.0). Recomendado.
diff --git a/app/[locale]/diagnostico/layout.jsx b/app/[locale]/diagnostico/layout.jsx
index b3b7c85..fd0bbb9 100644
--- a/app/[locale]/diagnostico/layout.jsx
+++ b/app/[locale]/diagnostico/layout.jsx
@@ -4,16 +4,26 @@ export async function generateMetadata({ params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
return paginaMeta({
titulo: isEs
? "Diagnóstico de errores comunes en ESP32: Guía profesional"
: isEn
? "Common ESP32 Errors & Diagnostics: Professional Guide"
+ : isFr
+ ? "Diagnostic des erreurs courantes sur ESP32 : guide professionnel"
+ : isDe
+ ? "Diagnose häufiger ESP32-Fehler: Professioneller Leitfaden"
: "Diagnóstico de Erros Comuns no ESP32: Guia Profissional",
descricao: isEs
? "Guía técnica definitiva para identificar y solucionar Brownout, Guru Meditation, Task Watchdog, Stack Overflow, fugas de memoria y fallas de arranque en ESP32 (Arduino y ESP-IDF)."
: isEn
? "Definitive technical guide to identify and fix Brownout, Guru Meditation, Task Watchdog, Stack Overflow, memory leaks, and boot issues on ESP32 (Arduino & ESP-IDF)."
+ : isFr
+ ? "Guide technique pour identifier et résoudre Brownout, Guru Meditation, Task Watchdog, Stack Overflow, fuites de mémoire et pannes de démarrage sur ESP32 (Arduino et ESP-IDF)."
+ : isDe
+ ? "Technischer Leitfaden zur Behebung von Brownout, Guru Meditation, Task Watchdog, Stack Overflow, Speicherlecks und Boot-Problemen beim ESP32 (Arduino & ESP-IDF)."
: "Guia técnico definitivo para identificar e resolver Brownout, Guru Meditation, Task Watchdog, Stack Overflow, vazamento de memória e falhas de boot no ESP32 (Arduino e ESP-IDF).",
caminho: "/diagnostico",
locale,
@@ -39,6 +49,28 @@ export async function generateMetadata({ params }) {
"ESP32 bootloop rebooting",
"failed to connect to esp32",
]
+ : isFr
+ ? [
+ "erreurs courantes ESP32",
+ "brownout detector was triggered",
+ "guru meditation error",
+ "task watchdog got triggered",
+ "stack canary watchpoint triggered",
+ "comment déboguer ESP32",
+ "ESP32 redémarre en boucle",
+ "failed to connect to esp32",
+ ]
+ : isDe
+ ? [
+ "häufige ESP32-Fehler",
+ "brownout detector was triggered",
+ "guru meditation error",
+ "task watchdog got triggered",
+ "stack canary watchpoint triggered",
+ "ESP32 debuggen",
+ "ESP32 Bootloop Neustart",
+ "failed to connect to esp32",
+ ]
: [
"erros comuns ESP32",
"brownout detector was triggered",
@@ -109,11 +141,52 @@ const faqsEs = [
},
];
+const faqsFr = [
+ {
+ pergunta: "Quelle est la cause de l'erreur 'Brownout detector was triggered' sur l'ESP32 ?",
+ resposta: "Elle survient lorsque la tension sur la broche 3V3 chute sous 2.8V ~ 2.43V en raison des pics rapides de courant de la radio Wi-Fi/BLE (dépassant 500mA), de câbles USB trop fins ou d'un régulateur LDO sous-dimensionné. La solution d'ingénierie consiste à ajouter des condensateurs Low-ESR de 10µF à 100µF près du VCC et à utiliser un régulateur LDO robuste de 600mA+.",
+ },
+ {
+ pergunta: "Comment résoudre le 'Guru Meditation Error (LoadProhibited)' ?",
+ resposta: "LoadProhibited indique une tentative d'accès à un pointeur nul (NULL) ou invalide en mémoire. La solution consiste à utiliser l'Espressif Exception Decoder ou addr2line pour identifier la ligne exacte du code et à appliquer des vérifications défensives telles que if (pointeur != NULL).",
+ },
+ {
+ pergunta: "Pourquoi le Task Watchdog Timer (TWDT) se déclenche-t-il sous FreeRTOS ?",
+ resposta: "Le TWDT se déclenche lorsqu'une tâche de priorité plus élevée monopolise le CPU dans une boucle bloquante (par exemple : while(1) sans vTaskDelay), empêchant la tâche IDLE de nourrir le watchdog du système d'exploitation.",
+ },
+ {
+ pergunta: "Pourquoi les connexions HTTPS et TLS échouent-elles avec l'erreur -0x2700 sur l'ESP32 ?",
+ resposta: "Au démarrage, l'horloge interne de l'ESP32 démarre à l'année 1970. Le validateur de certificats TLS considère le certificat du serveur invalide car situé dans le 'futur'. Synchroniser l'horloge via SNTP (configTime) avant la connexion résout le problème.",
+ },
+];
+
+const faqsDe = [
+ {
+ pergunta: "Was verursacht den Fehler 'Brownout detector was triggered' beim ESP32?",
+ resposta: "Er tritt auf, wenn die Spannung an der 3V3-Leitung durch schnelle Stromspitzen des Wi-Fi/BLE-Funkmoduls (über 500mA), dünne USB-Kabel oder einen unterdimensionierten LDO-Regler unter 2.8V ~ 2.43V abfällt. Die Lösung besteht darin, Low-ESR-Kondensatoren von 10µF bis 100µF nahe an VCC zu platzieren und robuste LDO-Regler mit 600mA+ einzusetzen.",
+ },
+ {
+ pergunta: "Wie behebt man den 'Guru Meditation Error (LoadProhibited)'?",
+ resposta: "LoadProhibited weist auf den Versuch hin, auf einen Null-Zeiger (NULL) oder eine ungültige Speicheradresse zuzugreifen. Die Lösung besteht darin, den Espressif Exception Decoder oder addr2line zu verwenden, um die genaue Quellcodezeile zu lokalisieren, und defensive Prüfungen wie if (ptr != NULL) einzufügen.",
+ },
+ {
+ pergunta: "Warum löst der Task Watchdog Timer (TWDT) in FreeRTOS aus?",
+ resposta: "Der TWDT löst aus, wenn ein Task mit höherer Priorität die CPU in einer blockierenden Schleife monopolisiert (z. B. while(1) ohne vTaskDelay) und so verhindert, dass der IDLE-Task den Watchdog des Betriebssystems bedient.",
+ },
+ {
+ pergunta: "Warum schlagen HTTPS- und TLS-Verbindungen beim ESP32 mit Fehler -0x2700 fehl?",
+ resposta: "Beim Start beginnt die interne Systemuhr des ESP32 im Jahr 1970. Die TLS-Zertifikatsprüfung stuft das Serverzertifikat als ungültig ein, da es in der fernen 'Zukunft' liegt. Eine Zeitsynchronisation über SNTP (configTime) vor dem Verbindungsaufbau behebt das Problem.",
+ },
+];
+
export default async function DiagnosticoLayout({ children, params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
- const faqs = isEs ? faqsEs : isEn ? faqsEn : faqsPt;
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
+ const faqs = isEs ? faqsEs : isEn ? faqsEn : isFr ? faqsFr : isDe ? faqsDe : faqsPt;
+ const canonPath = isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : isFr ? "/fr/diagnostic" : isDe ? "/de/diagnose" : "/diagnostico";
return (
<>
@@ -122,8 +195,8 @@ export default async function DiagnosticoLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { nome: isEs ? "Inicio" : isEn ? "Home" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : "/" },
- { nome: isEs ? "Diagnóstico y errores comunes" : isEn ? "Diagnostics & Common Errors" : "Diagnóstico & Erros Comuns", caminho: isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : "/diagnostico" },
+ { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isDe ? "/de" : "/" },
+ { nome: isEs ? "Diagnóstico y errores comunes" : isEn ? "Diagnostics & Common Errors" : isFr ? "Diagnostic et erreurs courantes" : isDe ? "Diagnose und häufige Fehler" : "Diagnóstico & Erros Comuns", caminho: canonPath },
])
),
}}
@@ -141,13 +214,17 @@ export default async function DiagnosticoLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
- nome: isEs ? "Guía de diagnóstico y errores en ESP32" : isEn ? "ESP32 Error Diagnostics & Debugging Guide" : "Guia de Diagnóstico e Erros no ESP32",
+ nome: isEs ? "Guía de diagnóstico y errores en ESP32" : isEn ? "ESP32 Error Diagnostics & Debugging Guide" : isFr ? "Guide de diagnostic et erreurs sur ESP32" : isDe ? "ESP32-Fehlerdiagnose und Debugging-Leitfaden" : "Guia de Diagnóstico e Erros no ESP32",
descricao: isEs
? "Herramienta y guía técnica de análisis de fallas, pánicos de CPU y decodificación de registros en ESP32."
: isEn
? "Technical guide and log decoder for troubleshooting CPU panics, crashes, and exceptions on ESP32."
+ : isFr
+ ? "Outil et guide technique d'analyse des pannes, des paniques CPU et du décodage de logs sur ESP32."
+ : isDe
+ ? "Werkzeug und technischer Leitfaden zur Fehleranalyse, Behebung von CPU-Panics und Dekodierung von Logs auf dem ESP32."
: "Ferramenta e guia técnico de análise de falhas, pânicos de CPU e decodificação de logs no ESP32.",
- caminho: isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : "/diagnostico",
+ caminho: canonPath,
locale,
})
),
diff --git a/app/[locale]/diagnostico/page.jsx b/app/[locale]/diagnostico/page.jsx
index 3424bd1..d24b14e 100644
--- a/app/[locale]/diagnostico/page.jsx
+++ b/app/[locale]/diagnostico/page.jsx
@@ -8,6 +8,8 @@ import PageHero from "@/components/PageHero";
import * as diagnosticoPt from "@/lib/diagnosticoData";
import * as diagnosticoEn from "@/lib/data/en/diagnostico";
import * as diagnosticoEs from "@/lib/data/es/diagnostico";
+import * as diagnosticoFr from "@/lib/data/fr/diagnostico";
+import * as diagnosticoDe from "@/lib/data/de/diagnostico";
import {
Activity,
AlertTriangle,
@@ -57,14 +59,34 @@ const EXEMPLOS_LOG_ES = [
["Handshake TLS", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
];
+const EXEMPLOS_LOG_FR = [
+ ["Brownout", "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"],
+ ["LoadProhibited", "Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"],
+ ["Task watchdog", "Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"],
+ ["Handshake TLS", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
+];
+
+const EXEMPLOS_LOG_DE = [
+ ["Brownout", "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"],
+ ["LoadProhibited", "Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"],
+ ["Task watchdog", "Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"],
+ ["TLS-Handshake", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
+];
+
const GRAVIDADE = {
"Crítica": "text-danger",
"Critical": "text-danger",
+ "Critique": "text-danger",
+ "Kritisch": "text-danger",
Alta: "text-gold",
High: "text-gold",
+ "Élevée": "text-gold",
+ Hoch: "text-gold",
"Média": "text-accent",
Medium: "text-accent",
Media: "text-accent",
+ Moyenne: "text-accent",
+ Mittel: "text-accent",
};
const CHECKLISTS_PT = [
@@ -136,13 +158,61 @@ const CHECKLISTS_ES = [
},
];
+const CHECKLISTS_FR = [
+ {
+ icon: Zap,
+ titulo: "Matériel et alimentation",
+ itens: [
+ "Condensateur de 10 µF à 100 µF à faible ESR sur VCC, plus un céramique de 100 nF près de la puce.",
+ "Régulateur LDO dimensionné pour des pointes de 600 mA à 1 A, avec marge thermique.",
+ "Aucun périphérique ne forçant de niveau logique sur les broches de strapping pendant le boot.",
+ "Condensateur de 10 µF ou 1 µF sur la broche EN (RST) pour un auto-reset stable au flashage.",
+ ],
+ },
+ {
+ icon: Layers,
+ titulo: "Firmware et FreeRTOS",
+ itens: [
+ "Chaque tâche FreeRTOS cède la CPU avec vTaskDelay, des files ou des sémaphores.",
+ "Chaque ISR marquée avec IRAM_ATTR et exempte d'appels bloquants.",
+ "Pas d'usage continu de String d'Arduino dans les boucles, afin d'éviter la fragmentation du tas.",
+ "Horloge synchronisée par SNTP avant d'ouvrir des connexions TLS/HTTPS.",
+ ],
+ },
+];
+
+const CHECKLISTS_DE = [
+ {
+ icon: Zap,
+ titulo: "Hardware und Spannungsversorgung",
+ itens: [
+ "10 µF bis 100 µF Low-ESR-Kondensator an VCC, plus ein 100-nF-Keramikkondensator nah am Chip.",
+ "LDO-Regler ausgelegt für 600 mA bis 1 A Stromspitzen mit thermischer Reserve.",
+ "Keine Peripheriegeräte, die beim Booten Logikpegel an Strapping-Pins erzwingen.",
+ "10 µF- oder 1 µF-Kondensator am EN-Pin (RST) für zuverlässigen Auto-Reset beim Flashen.",
+ ],
+ },
+ {
+ icon: Layers,
+ titulo: "Firmware und FreeRTOS",
+ itens: [
+ "Jeder FreeRTOS-Task gibt CPU-Zeit mit vTaskDelay, Queues oder Semaphoren ab.",
+ "Jede ISR mit IRAM_ATTR versehen und frei von blockierenden Aufrufen.",
+ "Keine fortlaufende Verwendung von Arduino-Strings in Schleifen zur Vermeidung von Heap-Fragmentierung.",
+ "Uhrzeit vor dem Aufbau von TLS/HTTPS-Verbindungen per SNTP synchronisiert.",
+ ],
+ },
+];
+
export default function DiagnosticoPage() {
const pathname = usePathname();
const isEn = pathname?.startsWith("/en");
const isEs = pathname?.startsWith("/es");
- const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEs ? diagnosticoEs : isEn ? diagnosticoEn : diagnosticoPt;
- const exemplosLog = isEs ? EXEMPLOS_LOG_ES : isEn ? EXEMPLOS_LOG_EN : EXEMPLOS_LOG_PT;
- const checklists = isEs ? CHECKLISTS_ES : isEn ? CHECKLISTS_EN : CHECKLISTS_PT;
+ const isFr = pathname?.startsWith("/fr");
+ const isDe = pathname?.startsWith("/de");
+ const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEs ? diagnosticoEs : isEn ? diagnosticoEn : isFr ? diagnosticoFr : isDe ? diagnosticoDe : diagnosticoPt;
+ const exemplosLog = isEs ? EXEMPLOS_LOG_ES : isEn ? EXEMPLOS_LOG_EN : isFr ? EXEMPLOS_LOG_FR : isDe ? EXEMPLOS_LOG_DE : EXEMPLOS_LOG_PT;
+ const checklists = isEs ? CHECKLISTS_ES : isEn ? CHECKLISTS_EN : isFr ? CHECKLISTS_FR : isDe ? CHECKLISTS_DE : CHECKLISTS_PT;
const [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
@@ -190,6 +260,24 @@ export default function DiagnosticoPage() {
acaoRecomendada:
"Look for keywords such as 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog', or 'panic' in the serial output.",
}
+ : isFr
+ ? {
+ titulo: "Modèle d'erreur non catégorisé automatiquement",
+ tipo: "Inconnu",
+ explicacao:
+ "Nous n'avons pas identifié de signature classique de Guru Meditation ou Brownout dans cet extrait. Vérifiez que la vitesse en bauds (ex. : 115200) est correcte et si l'en-tête 'rst:' ou 'panic' est présent.",
+ acaoRecomendada:
+ "Recherchez des mots-clés tels que 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' ou 'panic' dans la console série.",
+ }
+ : isDe
+ ? {
+ titulo: "Fehlermuster nicht automatisch kategorisiert",
+ tipo: "Unbekannt",
+ explicacao:
+ "Wir konnten in diesem Ausschnitt keine klassische Guru-Meditation- oder Brownout-Signatur erkennen. Prüfen Sie, ob die Baudrate (z. B. 115200) korrekt ist und ob der Header 'rst:' oder 'panic' vorhanden ist.",
+ acaoRecomendada:
+ "Suchen Sie in der seriellen Konsole nach Begriffen wie 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' oder 'panic'.",
+ }
: {
titulo: "Padrão de erro não categorizado automaticamente",
tipo: "Desconhecido",
@@ -256,15 +344,21 @@ export default function DiagnosticoPage() {
@@ -274,9 +368,9 @@ export default function DiagnosticoPage() {
rel="noopener noreferrer"
className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong"
>
- {isEs ? "Basado en el artículo de Ciência Embarcada" : isEn ? "Based on article by Ciência Embarcada" : "Baseado no artigo do Ciência Embarcada"}
+ {isEs ? "Basado en el artículo de Ciência Embarcada" : isEn ? "Based on article by Ciência Embarcada" : isFr ? "D'après l'article de Ciência Embarcada" : isDe ? "Basierend auf dem Artikel von Ciência Embarcada" : "Baseado no artigo do Ciência Embarcada"}
- {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : "(abre em nova aba)"}
+ {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : isFr ? "(ouvre un nouvel onglet)" : isDe ? "(öffnet in neuem Tab)" : "(abre em nova aba)"}
@@ -285,14 +379,14 @@ export default function DiagnosticoPage() {
- {isEs ? "Pegue el registro y descubra la causa" : isEn ? "Paste the log to see the cause" : "Cole o log e veja a causa"}
+ {isEs ? "Pegue el registro y descubra la causa" : isEn ? "Paste the log to see the cause" : isFr ? "Collez le journal et découvrez la cause" : isDe ? "Log einfügen und Ursache ermitteln" : "Cole o log e veja a causa"}
- {isEs ? "Copie el fragmento del monitor serie. El diagnóstico aparece al instante." : isEn ? "Copy the serial monitor output snippet. Diagnostics appear instantly." : "Copie o trecho do monitor serial. O diagnóstico aparece na hora."}
+ {isEs ? "Copie el fragmento del monitor serie. El diagnóstico aparece al instante." : isEn ? "Copy the serial monitor output snippet. Diagnostics appear instantly." : isFr ? "Copiez l'extrait du moniteur série. Le diagnostic apparaît immédiatement." : isDe ? "Kopieren Sie den Ausschnitt aus dem seriellen Monitor. Die Diagnose erscheint sofort." : "Copie o trecho do monitor serial. O diagnóstico aparece na hora."}
-
{isEs ? "Probar con" : isEn ? "Test with" : "Testar com"}
+
{isEs ? "Probar con" : isEn ? "Test with" : isFr ? "Tester avec" : isDe ? "Testen mit" : "Testar com"}
{exemplosLog.map(([rotulo, texto]) => (
analisarLog(texto)} className="chip-btn">
{rotulo}
@@ -303,21 +397,21 @@ export default function DiagnosticoPage() {
- {isEs ? "Registro del monitor serie" : isEn ? "Serial monitor log" : "Log do monitor serial"}
+ {isEs ? "Registro del monitor serie" : isEn ? "Serial monitor log" : isFr ? "Journal du moniteur série" : isDe ? "Serieller Monitor-Log" : "Log do monitor serial"}
@@ -331,8 +425,8 @@ export default function DiagnosticoPage() {
{resultadoLog.titulo}
{resultadoLog.tipo}
-
{isEs ? "Causa probable:" : isEn ? "Likely cause:" : "Causa provável:"} {resultadoLog.explicacao}
-
{isEs ? "Qué hacer:" : isEn ? "What to do:" : "O que fazer:"} {resultadoLog.acaoRecomendada}
+
{isEs ? "Causa probable:" : isEn ? "Likely cause:" : isFr ? "Cause probable :" : isDe ? "Wahrscheinliche Ursache :" : "Causa provável:"} {resultadoLog.explicacao}
+
{isEs ? "Qué hacer:" : isEn ? "What to do:" : isFr ? "Que faire :" : isDe ? "Was zu tun ist :" : "O que fazer:"} {resultadoLog.acaoRecomendada}
)}
@@ -342,13 +436,13 @@ export default function DiagnosticoPage() {
- {isEs ? "Catálogo de fallas" : isEn ? "Fault catalog" : "Catálogo de falhas"}
+ {isEs ? "Catálogo de fallas" : isEn ? "Fault catalog" : isFr ? "Catalogue de pannes" : isDe ? "Fehlerkatalog" : "Catálogo de falhas"}
- {isEs ? "Buscar por error, síntoma o mensaje serie" : isEn ? "Search by error, symptom, or serial message" : "Buscar por erro, sintoma ou mensagem serial"}
+ {isEs ? "Buscar por error, síntoma o mensaje serie" : isEn ? "Search by error, symptom, or serial message" : isFr ? "Rechercher par erreur, symptôme ou message série" : isDe ? "Nach Fehler, Symptom oder serieller Meldung suchen" : "Buscar por erro, sintoma ou mensagem serial"}
setTermoBusca(e.target.value)}
- placeholder={isEs ? "Buscar error, síntoma o mensaje" : isEn ? "Search error, symptom, or message" : "Buscar erro, sintoma ou mensagem"}
+ placeholder={isEs ? "Buscar error, síntoma o mensaje" : isEn ? "Search error, symptom, or message" : isFr ? "Rechercher une erreur, un symptôme ou un message" : isDe ? "Fehler, Symptom oder Meldung suchen" : "Buscar erro, sintoma ou mensagem"}
autoComplete="off"
className="field pl-12 pr-4 text-base"
/>
-
{isEs ? "Síntoma" : isEn ? "Symptom" : "Sintoma"}
+
{isEs ? "Síntoma" : isEn ? "Symptom" : isFr ? "Symptôme" : isDe ? "Symptom" : "Sintoma"}
{sintomasRapidos.map((sintoma) => (
setSintomaAtivo(sintomaAtivo === sintoma.id ? null : sintoma.id)} aria-pressed={sintomaAtivo === sintoma.id} className="chip-btn">
@@ -375,11 +469,11 @@ export default function DiagnosticoPage() {
-
{isEs ? "Área" : isEn ? "Area" : "Área"}
+
{isEs ? "Área" : isEn ? "Area" : isFr ? "Domaine" : isDe ? "Bereich" : "Área"}
setCategoriaAtiva("todos")} aria-pressed={categoriaAtiva === "todos"} className="chip-btn">
- {isEs ? "Todas" : isEn ? "All" : "Todas"}
+ {isEs ? "Todas" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todas"}
{errosCatalogo.length}
{categoriasDiagnostico.map((cat) => {
@@ -399,13 +493,13 @@ export default function DiagnosticoPage() {
- {errosFiltrados.length} {errosFiltrados.length === 1 ? (isEs ? "falla" : isEn ? "fault" : "falha") : (isEs ? "fallas" : isEn ? "faults" : "falhas")}
- {totalFiltros > 0 && {isEs ? " con los filtros actuales" : isEn ? " with current filters" : " com os filtros atuais"} }
+ {errosFiltrados.length} {errosFiltrados.length === 1 ? (isEs ? "falla" : isEn ? "fault" : isFr ? "panne" : isDe ? "Fehler" : "falha") : (isEs ? "fallas" : isEn ? "faults" : isFr ? "pannes" : isDe ? "Fehler" : "falhas")}
+ {totalFiltros > 0 && {isEs ? " con los filtros actuales" : isEn ? " with current filters" : isFr ? " avec les filtres actuels" : isDe ? " mit aktuellen Filtern" : " com os filtros atuais"} }
{temFiltroAtivo && (
- {isEs ? "Restablecer filtros" : isEn ? "Reset filters" : "Redefinir filtros"}
+ {isEs ? "Restablecer filtros" : isEn ? "Reset filters" : isFr ? "Réinitialiser les filtres" : isDe ? "Filter zurücksetzen" : "Redefinir filtros"}
)}
@@ -415,13 +509,13 @@ export default function DiagnosticoPage() {
- {isEs ? "Ninguna falla coincide con estos filtros" : isEn ? "No faults matching these filters" : "Nenhuma falha com esses filtros"}
+ {isEs ? "Ninguna falla coincide con estos filtros" : isEn ? "No faults matching these filters" : isFr ? "Aucune panne ne correspond à ces filtres" : isDe ? "Keine Fehler entsprechen diesen Filtern" : "Nenhuma falha com esses filtros"}
- {isEs ? "Busque por un término más general o elimine el síntoma y el área seleccionados." : isEn ? "Search for a more general term or remove selected symptom and area." : "Busque por um termo mais geral ou remova o sintoma e a área selecionados."}
+ {isEs ? "Busque por un término más general o elimine el síntoma y el área seleccionados." : isEn ? "Search for a more general term or remove selected symptom and area." : isFr ? "Recherchez un terme plus général ou retirez le symptôme et le domaine sélectionnés." : isDe ? "Suchen Sie nach einem allgemeineren Begriff oder entfernen Sie gewähltes Symptom und Bereich." : "Busque por um termo mais geral ou remova o sintoma e a área selecionados."}
- {isEs ? "Limpiar filtros" : isEn ? "Clear filters" : "Limpar filtros"}
+ {isEs ? "Limpiar filtros" : isEn ? "Clear filters" : isFr ? "Effacer les filtres" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
) : (
@@ -429,7 +523,7 @@ export default function DiagnosticoPage() {
const estaAberto = cardAberto === erro.id;
const lang = linguagemCodigo[erro.id] || "arduino";
const codigoAtual =
- lang === "arduino" ? erro.codigoArduino : lang === "espidf" ? erro.codigoEspIdf : erro.codigoMicroPython || (isEs ? "# Ejemplo no aplicable" : isEn ? "# Example not applicable" : "# Exemplo não aplicável");
+ lang === "arduino" ? erro.codigoArduino : lang === "espidf" ? erro.codigoEspIdf : erro.codigoMicroPython || (isEs ? "# Ejemplo no aplicable" : isEn ? "# Example not applicable" : isFr ? "# Exemple non applicable" : isDe ? "# Beispiel nicht anwendbar" : "# Exemplo não aplicável");
const catInfo = categoriasDiagnostico.find((c) => c.id === erro.categoria);
const IconeCat = ICONES[catInfo?.icone] ?? AlertTriangle;
@@ -454,6 +548,10 @@ export default function DiagnosticoPage() {
<>Severidad {erro.gravidade.toLowerCase()}>
) : isEn ? (
<>Severity: {erro.gravidade.toLowerCase()}>
+ ) : isFr ? (
+ <>Gravité : {erro.gravidade.toLowerCase()}>
+ ) : isDe ? (
+ <>Schweregrad: {erro.gravidade.toLowerCase()}>
) : (
<>Gravidade {erro.gravidade.toLowerCase()}>
)}
@@ -474,11 +572,11 @@ export default function DiagnosticoPage() {
- {isEs ? "Cómo aparece en el monitor serie" : isEn ? "How it appears in serial monitor" : "Como aparece no monitor serial"}
+ {isEs ? "Cómo aparece en el monitor serie" : isEn ? "How it appears in serial monitor" : isFr ? "Apparence dans le moniteur série" : isDe ? "Erscheinungsbild im seriellen Monitor" : "Como aparece no monitor serial"}
copiarParaClipboard(erro.logSerial, `log-${erro.id}`)} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{copiadoId === `log-${erro.id}` ? : }
- {copiadoId === `log-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : "Copiado") : (isEs ? "Copiar registro" : isEn ? "Copy log" : "Copiar log")}
+ {copiadoId === `log-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar registro" : isEn ? "Copy log" : isFr ? "Copier le log" : isDe ? "Log kopieren" : "Copiar log")}
@@ -489,7 +587,7 @@ export default function DiagnosticoPage() {
- {isEs ? "Causa raíz" : isEn ? "Root cause" : "Causa raiz"}
+ {isEs ? "Causa raíz" : isEn ? "Root cause" : isFr ? "Cause racine" : isDe ? "Ursache" : "Causa raiz"}
{erro.causaRaiz}
@@ -498,17 +596,17 @@ export default function DiagnosticoPage() {
- {isEs ? "La solución improvisada común" : isEn ? "The common workaround" : "O remendo comum"}
+ {isEs ? "La solución improvisada común" : isEn ? "The common workaround" : isFr ? "Le bricolage courant" : isDe ? "Der verbreitete Notbehelf" : "O remendo comum"}
{erro.gambiarra.titulo}
{erro.gambiarra.descricao}
-
{isEs ? "Consecuencia:" : isEn ? "Consequence:" : "Consequência:"} {erro.gambiarra.consequencia}
+
{isEs ? "Consecuencia:" : isEn ? "Consequence:" : isFr ? "Conséquence :" : isDe ? "Folge :" : "Consequência:"} {erro.gambiarra.consequencia}
- {isEs ? "La solución de ingeniería" : isEn ? "The engineering solution" : "A solução de engenharia"}
+ {isEs ? "La solución de ingeniería" : isEn ? "The engineering solution" : isFr ? "La solution d'ingénierie" : isDe ? "Die Ingenieurlösung" : "A solução de engenharia"}
{erro.solucaoEngenharia.titulo}
@@ -524,7 +622,7 @@ export default function DiagnosticoPage() {
-
+
{[
["arduino", "Arduino C++"],
["espidf", "ESP-IDF (C)"],
@@ -537,7 +635,7 @@ export default function DiagnosticoPage() {
copiarParaClipboard(codigoAtual, `code-${erro.id}`)} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{copiadoId === `code-${erro.id}` ? : }
- {copiadoId === `code-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : "Copiado") : (isEs ? "Copiar código" : isEn ? "Copy code" : "Copiar código")}
+ {copiadoId === `code-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar código" : isEn ? "Copy code" : isFr ? "Copier le code" : isDe ? "Code kopieren" : "Copiar código")}
@@ -547,7 +645,7 @@ export default function DiagnosticoPage() {
-
{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : "Dica de engenharia:"} {erro.dicaPro}
+
{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : isFr ? "Conseil d'ingénierie :" : isDe ? "Praxistipp :" : "Dica de engenharia:"} {erro.dicaPro}
)}
@@ -560,10 +658,10 @@ export default function DiagnosticoPage() {
- {isEs ? "Lista de verificación previa a campo" : isEn ? "Pre-deployment checklist" : "Checklist antes de ir para campo"}
+ {isEs ? "Lista de verificación previa a campo" : isEn ? "Pre-deployment checklist" : isFr ? "Checklist avant déploiement" : isDe ? "Checkliste vor dem Feldeinsatz" : "Checklist antes de ir para campo"}
- {isEs ? "Elementos esenciales para validar en hardware y firmware antes de entregar el producto." : isEn ? "Essential items to validate on hardware and firmware before shipping a product." : "Itens essenciais para validar no hardware e no firmware antes de entregar o produto."}
+ {isEs ? "Elementos esenciales para validar en hardware y firmware antes de entregar el producto." : isEn ? "Essential items to validate on hardware and firmware before shipping a product." : isFr ? "Éléments essentiels à valider sur le matériel et le firmware avant de livrer le produit." : isDe ? "Wesentliche Punkte zur Prüfung von Hardware und Firmware vor der Auslieferung." : "Itens essenciais para validar no hardware e no firmware antes de entregar o produto."}
{checklists.map(({ icon: Icon, titulo, itens }) => (
diff --git a/app/[locale]/frameworks/[key]/page.jsx b/app/[locale]/frameworks/[key]/page.jsx
index 39f17e4..a3310f2 100644
--- a/app/[locale]/frameworks/[key]/page.jsx
+++ b/app/[locale]/frameworks/[key]/page.jsx
@@ -8,6 +8,8 @@ import CodeExample from "@/components/CodeExample";
import frameworksDataPt from "@/public/frameworks.json";
import frameworksDataEn from "@/data/en/frameworks.json";
import frameworksDataEs from "@/data/es/frameworks.json";
+import frameworksDataFr from "@/data/fr/frameworks.json";
+import frameworksDataDe from "@/data/de/frameworks.json";
import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, BookOpen, Box, Check, Code, Layers } from "lucide-react";
@@ -15,8 +17,10 @@ export default async function FrameworkDetail({ params }) {
const { locale, key } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
- const currentLocale = isEs ? "es" : isEn ? "en" : "pt";
- const frameworksData = isEs ? frameworksDataEs : isEn ? frameworksDataEn : frameworksDataPt;
+ const isFr = locale === "fr";
+ const isDe = locale === "de";
+ const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isDe ? "de" : "pt";
+ const frameworksData = isEs ? frameworksDataEs : isEn ? frameworksDataEn : isFr ? frameworksDataFr : isDe ? frameworksDataDe : frameworksDataPt;
const framework = frameworksData[key];
if (!framework) {
@@ -38,7 +42,7 @@ export default async function FrameworkDetail({ params }) {
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.trilha([
- { nome: isEs ? "Inicio" : isEn ? "Home" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : "/" },
+ { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isDe ? "/de" : "/" },
{ nome: "Frameworks", caminho: getLocalizedHref("/frameworks", currentLocale) },
{ nome: framework.nome, caminho: getLocalizedHref(`/frameworks/${encodeURIComponent(key)}`, currentLocale) },
])),
@@ -51,7 +55,9 @@ export default async function FrameworkDetail({ params }) {
- {isEs ? "Documentación oficial" : isEn ? "Official documentation" : "Documentação oficial"}
+ {isEs ? "Documentación oficial" : isEn ? "Official documentation" : isFr ? "Documentation officielle" : isDe ? "Offizielle Dokumentation" : "Documentação oficial"}
)}
{framework.repositorio && (
- {isEs ? "Repositorio en GitHub" : isEn ? "GitHub repository" : "Repositório no GitHub"}
+ {isEs ? "Repositorio en GitHub" : isEn ? "GitHub repository" : isFr ? "Dépôt sur GitHub" : isDe ? "GitHub-Repository" : "Repositório no GitHub"}
)}
-
+
-
{isEs ? "Lenguaje" : isEn ? "Language" : "Linguagem"}
+ {isEs ? "Lenguaje" : isEn ? "Language" : isFr ? "Langage" : isDe ? "Sprache" : "Linguagem"}
{framework.linguagem}
{framework.nome_completo}
-
{isEs ? "Categoría" : isEn ? "Category" : "Categoria"}
+ {isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isDe ? "Kategorie" : "Categoria"}
{framework.tipo}
-
{isEs ? "Objetivo principal" : isEn ? "Main purpose" : "Objetivo principal"}
+ {isEs ? "Objetivo principal" : isEn ? "Main purpose" : isFr ? "Objectif principal" : isDe ? "Hauptzweck" : "Objetivo principal"}
{framework.funcao_principal}
-
+
- {isEs ? "Funciones destacadas" : isEn ? "Key features" : "Recursos de destaque"}
+ {isEs ? "Funciones destacadas" : isEn ? "Key features" : isFr ? "Fonctionnalités clés" : isDe ? "Wichtigste Features" : "Recursos de destaque"}