diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4176e25..deffcf1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,38 @@ Tudo o que muda de relevante no ESPDocs está aqui. O formato segue o
## [Não lançado]
+## [1.6.0] - 2026-11-02
+
+### Adicionado
+
+- Chinês simplificado (`/zh-CN`), o sexto idioma do site, com o mesmo conteúdo dos
+ demais, menos o livro (`/zh-CN/livro` responde 404):
+ - Interface, séries, frameworks, diagramas de pinos, glossário, diagnóstico,
+ componentes e exemplos de código com comentários em chinês (mensagens de log
+ dos exemplos ficam em inglês, para não aparecerem quebradas no monitor serial).
+ - Busca em chinês: o motor entende consultas como `s3 闪存`, `c6 usb 引脚` ou
+ `哪款 esp32 有 psram`, segmentando o texto com `Intl.Segmenter`.
+ - SEO próprio (`hreflang`, sitemap, JSON-LD em `zh-CN`) e negociação da raiz:
+ `Accept-Language` com `zh`, `zh-Hans` ou `zh-TW` leva a `/zh-CN`.
+ - O filtro alfabético A–Z do glossário não aparece em chinês.
+- `scripts/verifica-busca.mjs`, chamado pelo teste de fumaça: roda o motor de busca
+ sobre o índice de cada idioma e confere que as respostas e os links estão no
+ idioma da página.
+- `check-i18n` passa a procurar português dentro dos dados dos outros idiomas.
+
+### Corrigido
+
+- Na busca, as respostas diretas (por exemplo "Flash no ESP32-S3", "Dados da série",
+ "Pinos do ESP32-S3 ligados à memória flash") saíam em português em inglês, espanhol,
+ francês e alemão, e levavam a `/series/...` em vez do idioma da página. Agora
+ textos e links seguem o idioma.
+- Em francês e alemão, valores de ADC, SPI, toque capacitivo, consumo e outros ainda
+ estavam em português (por exemplo "até 18 canais", "conforme a carga"); em espanhol,
+ dois valores de consumo. Todos traduzidos.
+- O seletor de chip reconhecia "Sim"/"Yes" para Matter, Arduino e segurança, mas não
+ "Sí", "Oui" nem "Ja"; agora reconhece os seis idiomas.
+- A home em alemão mostrava "14 frameworks" em minúscula.
+
## [1.5.0] - 2026-11-02
### Adicionado
@@ -194,7 +226,8 @@ entrada resume tudo desde o primeiro commit, em 2025-10-21, até a imagem Docker
otimização de imagens (GHSA-2464-fhr4-w9xw), e `postcss` fixado acima da cópia
embutida no `next`.
-[Não lançado]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.5.0...HEAD
+[Não lançado]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.6.0...HEAD
+[1.6.0]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.5.0...v1.6.0
[1.5.0]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.4.2...v1.5.0
[1.4.2]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.4.1...v1.4.2
[1.4.1]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.4.0...v1.4.1
diff --git a/app/[locale]/catalogo/layout.jsx b/app/[locale]/catalogo/layout.jsx
index df49257..d8e5a51 100644
--- a/app/[locale]/catalogo/layout.jsx
+++ b/app/[locale]/catalogo/layout.jsx
@@ -6,15 +6,16 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
+ const isZh = locale === "zh-CN";
return paginaMeta({
- 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",
+ titulo: isEn ? "ESP32 Board Catalog" : isEs ? "Catálogo de placas ESP32" : isFr ? "Catalogue de cartes ESP32" : isZh ? "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
+ : isZh ? "带有 ESP32 的开发板,可按系列、接口和 USB 转串口芯片筛选。" : 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",
@@ -25,7 +26,7 @@ export async function generateMetadata({ params }) {
? ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"]
: isFr
? ["cartes ESP32", "DevKit ESP32", "acheter ESP32", "ESP32 DevKitC"]
- : isDe
+ : isZh ? ["ESP32 开发板", "ESP32 DevKit", "购买 ESP32", "ESP32 DevKitC"] : isDe
? ["ESP32 Boards", "ESP32 DevKit", "ESP32 kaufen", "ESP32 DevKitC"]
: ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"],
});
@@ -37,7 +38,8 @@ export default async function Layout({ children, params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
- const canonPath = isEn ? "/en/catalog" : isEs ? "/es/catalogo" : isFr ? "/fr/catalogue" : isDe ? "/de/katalog" : "/catalogo";
+ const isZh = locale === "zh-CN";
+ const canonPath = isEn ? "/en/catalog" : isEs ? "/es/catalogo" : isFr ? "/fr/catalogue" : isZh ? "/zh-CN/catalog" : isDe ? "/de/katalog" : "/catalogo";
return (
<>
@@ -46,14 +48,14 @@ 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" : isFr ? "Catalogue de cartes de développement ESP32" : isDe ? "Katalog für ESP32-Entwicklungsboards" : "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" : isZh ? "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
+ : isZh ? "带有 ESP32 微控制器的开发板与 DevKit 的交互式指南与目录。" : 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,
@@ -67,8 +69,8 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { 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 },
+ { nome: isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/" },
+ { nome: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isZh ? "开发板目录" : isDe ? "Board-Katalog" : "Catálogo de Placas", caminho: canonPath },
])
),
}}
diff --git a/app/[locale]/catalogo/page.jsx b/app/[locale]/catalogo/page.jsx
index 8c25f15..31c1363 100644
--- a/app/[locale]/catalogo/page.jsx
+++ b/app/[locale]/catalogo/page.jsx
@@ -101,12 +101,35 @@ const HEADER_MAP_DE = {
"Botões": "Tasten",
"LEDs": "LEDs",
};
+const HEADER_MAP_ZH = {
+ "Nome": "名称",
+ "Descrição": "描述",
+ "Série": "系列",
+ "Loja Oficial": "官方商店",
+ "Fabricante": "制造商",
+ "Processador": "处理器",
+ "Memória Flash": "闪存",
+ "PSRAM": "PSRAM",
+ "Conector USB": "USB 接口",
+ "Interface USB": "USB 接口类型",
+ "Antena": "天线",
+ "Bateria": "电池",
+ "Display": "显示屏",
+ "Sensores": "传感器",
+ "Botões": "按键",
+ "LEDs": "LEDs",
+};
const VALUE_MAP_DE = {
"Sim": "Ja",
"Não": "Nein",
"Opcional": "Optional",
};
+const VALUE_MAP_ZH = {
+ "Sim": "是",
+ "Não": "否",
+ "Opcional": "可选",
+};
export default function PlacasPage() {
const pathname = usePathname() || "";
@@ -114,6 +137,7 @@ export default function PlacasPage() {
const isEs = pathname === "/es" || pathname.startsWith("/es/");
const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
const isDe = pathname === "/de" || pathname.startsWith("/de/");
+ const isZh = pathname === "/zh-CN" || pathname.startsWith("/zh-CN/");
const [boards, setBoards] = useState([]);
const [filteredBoards, setFilteredBoards] = useState([]);
@@ -142,7 +166,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" : isFr ? "Erreur lors du chargement des données des cartes" : isDe ? "Fehler beim Laden der Board-Daten" : "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" : isZh ? "加载开发板数据出错" : isDe ? "Fehler beim Laden der Board-Daten" : "Erro ao carregar dados das placas");
}
const result = await response.json();
@@ -248,11 +272,11 @@ export default function PlacasPage() {
isEn={isEn}
isEs={isEs}
isFr={isFr}
- isDe={isDe}
- trail={[{ label: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isDe ? "Board-Katalog" : "Catálogo de placas" }]}
+ isDe={isDe} isZh={isZh}
+ trail={[{ label: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isZh ? "开发板目录" : isDe ? "Board-Katalog" : "Catálogo de placas" }]}
icon={ShoppingCart}
- label={isEn ? "Real DevKits and modules" : isEs ? "DevKits y módulos reales" : isFr ? "DevKits et modules réels" : isDe ? "Echte DevKits und Module" : "DevKits e módulos reais"}
- title={isEn ? "ESP32 Board Catalog" : isEs ? "Catálogo de placas ESP32" : isFr ? "Catalogue de cartes ESP32" : isDe ? "ESP32-Board-Katalog" : "Catálogo de placas ESP32"}
+ label={isEn ? "Real DevKits and modules" : isEs ? "DevKits y módulos reales" : isFr ? "DevKits et modules réels" : isZh ? "真实的 DevKit 与模组" : isDe ? "Echte DevKits und Module" : "DevKits e módulos reais"}
+ title={isEn ? "ESP32 Board Catalog" : isEs ? "Catálogo de placas ESP32" : isFr ? "Catalogue de cartes ESP32" : isZh ? "ESP32 开发板目录" : isDe ? "ESP32-Board-Katalog" : "Catálogo de placas ESP32"}
lede={
isEn
? "Compare official and third-party boards for your project: USB interface, on-board sensors and direct store links."
@@ -260,7 +284,7 @@ export default function PlacasPage() {
? "Compare placas oficiales y alternativas para su proyecto: interfaz USB, sensores integrados y enlace directo a la tienda."
: isFr
? "Comparez les cartes officielles et alternatives pour votre projet : interface USB, capteurs intégrés et lien direct vers la boutique."
- : isDe
+ : isZh ? "对比官方与替代开发板,为你的项目选型:USB 接口、板载传感器,以及直达商店的链接。" : isDe
? "Vergleichen Sie offizielle und Drittanbieter-Boards für Ihr Projekt: USB-Schnittstelle, integrierte Sensoren und direkter Store-Link."
: "Compare placas oficiais e alternativas para o seu projeto: interface USB, sensores integrados e link direto para a loja."
}
@@ -273,14 +297,14 @@ export default function PlacasPage() {
{hero}
-
+
{Array.from({ length: 6 }).map((_, i) => (
))}
- {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…"}
+ {isEn ? "Querying boards database…" : isEs ? "Consultando la base de datos de placas…" : isFr ? "Consultation de la base de cartes…" : isZh ? "正在查询开发板数据库…" : isDe ? "Board-Datenbank wird abgefragt…" : "Consultando o banco de placas…"}
@@ -300,14 +324,14 @@ export default function PlacasPage() {
- {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"}
+ {isEn ? "Could not load boards" : isEs ? "No se pudieron cargar las placas" : isFr ? "Impossible de charger les cartes" : isZh ? "无法加载开发板" : 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." : 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."}
+ {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." : isZh ? "请检查网络连接后重试。" : isDe ? "Prüfen Sie Ihre Verbindung und versuchen Sie es erneut." : "Verifique a conexão e tente de novo."}
- {isEn ? "Try again" : isEs ? "Reintentar" : isFr ? "Réessayer" : isDe ? "Erneut versuchen" : "Tentar novamente"}
+ {isEn ? "Try again" : isEs ? "Reintentar" : isFr ? "Réessayer" : isZh ? "重试" : isDe ? "Erneut versuchen" : "Tentar novamente"}
@@ -322,14 +346,14 @@ export default function PlacasPage() {
- {isEn ? "Search board" : isEs ? "Buscar placa" : isFr ? "Rechercher une carte" : isDe ? "Board suchen" : "Buscar placa"}
+ {isEn ? "Search board" : isEs ? "Buscar placa" : isFr ? "Rechercher une carte" : isZh ? "搜索开发板" : isDe ? "Board suchen" : "Buscar placa"}
setSearchTerm(e.target.value)}
className="field pl-10 text-sm"
@@ -340,7 +364,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) : isFr ? (HEADER_MAP_FR[column] || column) : isDe ? (HEADER_MAP_DE[column] || column) : column;
+ const colLabel = isEn ? (HEADER_MAP_EN[column] || column) : isEs ? (HEADER_MAP_ES[column] || column) : isFr ? (HEADER_MAP_FR[column] || column) : isZh ? (HEADER_MAP_ZH[column] || column) : isDe ? (HEADER_MAP_DE[column] || column) : column;
if (filterData.isBinary) {
return (
@@ -364,7 +388,7 @@ export default function PlacasPage() {
{filterData.values.map((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;
+ const optLabel = isEn ? (VALUE_MAP_EN[option] || option) : isEs ? (VALUE_MAP_ES[option] || option) : isFr ? (VALUE_MAP_FR[option] || option) : isZh ? (VALUE_MAP_ZH[option] || option) : isDe ? (VALUE_MAP_DE[option] || option) : option;
return (
{hero}
-
+
setFiltrosAbertos(e.currentTarget.open)}>
- {isEn ? "Filters" : isEs ? "Filtros" : isFr ? "Filtres" : isDe ? "Filter" : "Filtros"}
+ {isEn ? "Filters" : isEs ? "Filtros" : isFr ? "Filtres" : isZh ? "筛选" : isDe ? "Filter" : "Filtros"}
{activeFilterCount > 0 && {activeFilterCount} }
{activeFilterCount > 0 && (
@@ -410,7 +434,7 @@ export default function PlacasPage() {
}}
className="text-sm font-medium text-accent hover:text-accent-strong"
>
- {isEn ? "Clear" : isEs ? "Limpiar" : isFr ? "Effacer" : isDe ? "Zurücksetzen" : "Limpar"}
+ {isEn ? "Clear" : isEs ? "Limpiar" : isFr ? "Effacer" : isZh ? "清除" : isDe ? "Zurücksetzen" : "Limpar"}
)}
@@ -433,7 +457,7 @@ export default function PlacasPage() {
? filteredBoards.length === 1
? "carte trouvée"
: "cartes trouvées"
- : isDe
+ : isZh ? "款开发板" : isDe
? filteredBoards.length === 1
? "Board gefunden"
: "Boards gefunden"
@@ -446,7 +470,7 @@ export default function PlacasPage() {
- {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 ? "No boards match these filters" : isEs ? "Ninguna placa coincide con estos filtros" : isFr ? "Aucune carte ne correspond à ces filtres" : isZh ? "没有符合这些筛选条件的开发板" : isDe ? "Keine Boards entsprechen diesen Filtern" : "Nenhuma placa com esses filtros"}
{isEn
@@ -455,12 +479,12 @@ export default function PlacasPage() {
? "Elimine algunos filtros o busque por otro nombre."
: isFr
? "Supprimez certains filtres ou recherchez un autre nom."
- : isDe
+ : isZh ? "请去掉一些筛选条件,或换个名称搜索。" : 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" : isFr ? "Effacer les filtres" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
+ {isEn ? "Clear filters" : isEs ? "Limpiar filtros" : isFr ? "Effacer les filtres" : isZh ? "清除筛选" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
) : (
diff --git a/app/[locale]/comparacao/layout.jsx b/app/[locale]/comparacao/layout.jsx
index 8dc930a..40d232d 100644
--- a/app/[locale]/comparacao/layout.jsx
+++ b/app/[locale]/comparacao/layout.jsx
@@ -6,15 +6,16 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
+ const isZh = locale === "zh-CN";
return paginaMeta({
- 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",
+ titulo: isEn ? "Compare ESP32 Series: Side-by-Side Table" : isEs ? "Comparar series ESP32: tabla comparativa" : isFr ? "Comparer les séries ESP32 : tableau comparatif" : isZh ? "ESP32 系列对比" : 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
+ : isZh ? "最多并排对比四个 ESP32 系列:处理器、Wi-Fi、蓝牙、内存、GPIO、外设和硬件安全,数据来自官方数据手册。" : 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",
@@ -25,7 +26,7 @@ export async function generateMetadata({ params }) {
? ["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
+ : isZh ? ["ESP32 对比", "ESP32 vs ESP32-S3", "ESP32 对比表", "哪款 ESP32 最好"] : 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"],
});
@@ -105,6 +106,20 @@ const faqsDe = [
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.",
},
];
+const faqsZh = [
+ {
+ pergunta: "如何并排对比 ESP32 微控制器?",
+ resposta: "ESPDocs 对比工具可同时选择最多四个系列,对比架构、主频、SRAM/PSRAM 内存、无线标准(Wi-Fi 4/6、蓝牙 5/6、802.15.4)、外设接口以及硬件安全模块。",
+ },
+ {
+ pergunta: "经典 ESP32 与 ESP32-S3 有什么区别?",
+ resposta: "经典 ESP32 使用 Xtensa LX6 内核和传统蓝牙/BLE。ESP32-S3 采用带 AI(TinyML)向量指令的 Xtensa LX7 内核,提供原生 USB OTG、更多 GPIO(45 对 36)以及蓝牙 5.0 LE。",
+ },
+ {
+ pergunta: "ESP32-C3 与 ESP32-C6 有什么区别?",
+ resposta: "ESP32-C3 是支持 Wi-Fi 4 和蓝牙 5 LE 的 RISC-V 微控制器。ESP32-C6 升级为 Wi-Fi 6(802.11ax)、蓝牙 5.4 LE,并带有用于 Zigbee 3.0 和 Thread 1.3 的 IEEE 802.15.4 射频,以及一颗超低功耗协处理器。",
+ },
+];
export default async function Layout({ children, params }) {
const { locale } = await params;
@@ -112,8 +127,9 @@ export default async function Layout({ children, params }) {
const isEs = locale === "es";
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";
+ const isZh = locale === "zh-CN";
+ const faqs = isEn ? faqsEn : isEs ? faqsEs : isFr ? faqsFr : isZh ? faqsZh : isDe ? faqsDe : faqsPt;
+ const canonPath = isEn ? "/en/compare" : isEs ? "/es/comparacion" : isFr ? "/fr/comparaison" : isZh ? "/zh-CN/compare" : isDe ? "/de/vergleich" : "/comparacao";
return (
<>
@@ -122,14 +138,14 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
- 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",
+ nome: isEn ? "ESP32 Series Comparator" : isEs ? "Comparador de series ESP32" : isFr ? "Comparateur de séries ESP32" : isZh ? "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
+ : isZh ? "用于分析多个 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,
@@ -143,8 +159,8 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { 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 },
+ { nome: isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/" },
+ { nome: isEn ? "Compare Series" : isEs ? "Comparar series" : isFr ? "Comparer les séries" : isZh ? "系列对比" : isDe ? "Serien vergleichen" : "Comparar Séries", caminho: canonPath },
])
),
}}
diff --git a/app/[locale]/comparacao/page.jsx b/app/[locale]/comparacao/page.jsx
index 79eb6c7..a9b6c61 100644
--- a/app/[locale]/comparacao/page.jsx
+++ b/app/[locale]/comparacao/page.jsx
@@ -11,6 +11,7 @@ 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 seriesDataZh from "@/data/zh-CN/series.json";
import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, Check, ChevronDown, Scale } from "lucide-react";
@@ -416,6 +417,84 @@ const COMPARE_FIELDS_DE = [
{ key: "temperatura_armazenamento", label: "Lagertemp." },
]},
];
+const COMPARE_FIELDS_ZH = [
+ { category: "概览", fields: [
+ { key: "nome_completo", label: "完整名称" },
+ { key: "descricao", label: "描述" },
+ ]},
+ { category: "可用性与工具", fields: [
+ { key: "esp_idf_minimo", label: "最低 ESP-IDF 版本" },
+ { key: "arduino_core", label: "Arduino 核心" },
+ { key: "depuracao", label: "调试 / JTAG" },
+ { key: "encapsulamento", label: "封装" },
+ { key: "modulos", label: "可用模组" },
+ ]},
+ { category: "处理器", fields: [
+ { key: "arquitetura", label: "架构" },
+ { key: "nucleos", label: "内核" },
+ { key: "frequencia", label: "频率" },
+ { key: "coprocessador_ulp", label: "ULP 协处理器" },
+ { key: "aceleradores_ia", label: "AI 加速器" },
+ ]},
+ { category: "无线连接", fields: [
+ { key: "wifi", label: "Wi-Fi" },
+ { key: "bluetooth", label: "蓝牙" },
+ { key: "zigbee_thread", label: "Zigbee/Thread" },
+ { key: "matter", label: "Matter" },
+ { key: "ethernet", label: "以太网" },
+ ]},
+ { category: "内存", fields: [
+ { key: "memoria_sram", label: "SRAM" },
+ { key: "memoria_sram_rtc", label: "RTC SRAM" },
+ { key: "memoria_rom", label: "ROM" },
+ { key: "flash_externa", label: "外部闪存" },
+ { key: "psram_externa", label: "外部 PSRAM" },
+ ]},
+ { category: "数字外设", 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: "调试 / JTAG" },
+ { key: "sdio", label: "SDIO" },
+ ]},
+ { category: "模拟外设", fields: [
+ { key: "adc", label: "ADC" },
+ { key: "dac", label: "DAC" },
+ { key: "touch", label: "电容触摸" },
+ { key: "sensor_hall", label: "霍尔传感器" },
+ { key: "sensor_temperatura", label: "温度传感器" },
+ ]},
+ { category: "安全", fields: [
+ { key: "seguranca.aes", label: "AES" },
+ { key: "seguranca.sha", label: "SHA" },
+ { key: "seguranca.rsa", label: "RSA / MPI" },
+ { key: "seguranca.ecc", label: "ECC(椭圆曲线)" },
+ { key: "seguranca.ecdsa", label: "硬件 ECDSA" },
+ { key: "seguranca.hmac", label: "HMAC" },
+ { key: "seguranca.assinatura_digital", label: "数字签名(DS)" },
+ { key: "seguranca.key_manager", label: "密钥管理器" },
+ { key: "seguranca.rng", label: "随机数发生器(TRNG)" },
+ { key: "seguranca.criptografia_flash", label: "闪存加密" },
+ { key: "seguranca.secure_boot", label: "安全启动" },
+ { key: "seguranca.protecao_dpa", label: "DPA 防护" },
+ ]},
+ { category: "特殊接口", fields: [
+ { key: "lcd", label: "LCD" },
+ { key: "camera", label: "摄像头" },
+ { key: "mipi_csi", label: "MIPI CSI" },
+ { key: "mipi_dsi", label: "MIPI DSI" },
+ { key: "ethernet_mac", label: "以太网 MAC" },
+ ]},
+ { category: "环境规格", fields: [
+ { key: "temperatura_operacao", label: "工作温度" },
+ { key: "temperatura_armazenamento", label: "存储温度" },
+ ]},
+];
export default function Comparacao() {
const pathname = usePathname();
@@ -423,15 +502,16 @@ export default function Comparacao() {
const isEs = pathname?.startsWith("/es");
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 isZh = pathname?.startsWith("/zh-CN");
+ const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt";
+ const seriesData = isEs ? seriesDataEs : isEn ? seriesDataEn : isFr ? seriesDataFr : isZh ? seriesDataZh : isDe ? seriesDataDe : seriesDataPt;
+ const compareFields = isEs ? COMPARE_FIELDS_ES : isEn ? COMPARE_FIELDS_EN : isFr ? COMPARE_FIELDS_FR : isZh ? COMPARE_FIELDS_ZH : 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" : 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,
+ [isEs ? "Interfaces especiales" : isEn ? "Special Interfaces" : isFr ? "Interfaces spéciales" : isZh ? "特殊接口" : isDe ? "Spezielle Schnittstellen" : "Interfaces Especiais"]: true,
+ [isEs ? "Especificaciones ambientales" : isEn ? "Environmental Specifications" : isFr ? "Spécifications environnementales" : isZh ? "环境规格" : isDe ? "Umgebungsbedingungen" : "Especificações Ambientais"]: true,
}));
const series = Object.entries(seriesData);
@@ -457,19 +537,19 @@ export default function Comparacao() {
};
const renderValue = (value) => {
- 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") {
+ if (!value || value === "Não" || value === "No" || value === "Non" || (value === "Nein" || value === "否")) return {isEs || isEn ? "No" : isFr ? "Non" : isZh ? "否" : isDe ? "Nein" : "Não"} ;
+ if (value === "Sim" || value === "Yes" || value === "Sí" || value === "Oui" || (value === "Ja" || value === "是")) {
return (
- {isEs ? "Sí" : isEn ? "Yes" : isFr ? "Oui" : isDe ? "Ja" : "Sim"}
+ {isEs ? "Sí" : isEn ? "Yes" : isFr ? "Oui" : isZh ? "是" : isDe ? "Ja" : "Sim"}
);
}
return {value} ;
};
- const valores = (key) => selectedSeries.map((k) => String(valorDe(seriesData[k], key) ?? (isEs || isEn ? "No" : isFr ? "Non" : isDe ? "Nein" : "Não")));
+ const valores = (key) => selectedSeries.map((k) => String(valorDe(seriesData[k], key) ?? (isEs || isEn ? "No" : isFr ? "Non" : isZh ? "否" : isDe ? "Nein" : "Não")));
const diferem = (key) => new Set(valores(key)).size > 1;
return (
@@ -481,11 +561,11 @@ export default function Comparacao() {
isEn={isEn}
isEs={isEs}
isFr={isFr}
- isDe={isDe}
- trail={[{ label: isEs ? "Comparador" : isEn ? "Comparator" : isFr ? "Comparateur" : isDe ? "Vergleich" : "Comparador" }]}
+ isDe={isDe} isZh={isZh}
+ trail={[{ label: isEs ? "Comparador" : isEn ? "Comparator" : isFr ? "Comparateur" : isZh ? "对比工具" : isDe ? "Vergleich" : "Comparador" }]}
icon={Scale}
- label={isEs ? "Hasta cuatro series lado a lado" : isEn ? "Up to four series side by side" : isFr ? "Jusqu'à quatre séries côte à côte" : isDe ? "Bis zu vier Serien im Vergleich" : "Até quatro séries lado a lado"}
- title={isEs ? "Comparador de series ESP32" : isEn ? "ESP32 Series Comparator" : isFr ? "Comparateur de séries ESP32" : isDe ? "ESP32-Serien-Vergleich" : "Comparador de séries"}
+ label={isEs ? "Hasta cuatro series lado a lado" : isEn ? "Up to four series side by side" : isFr ? "Jusqu'à quatre séries côte à côte" : isZh ? "最多四个系列并排对比" : isDe ? "Bis zu vier Serien im Vergleich" : "Até quatro séries lado a lado"}
+ title={isEs ? "Comparador de series ESP32" : isEn ? "ESP32 Series Comparator" : isFr ? "Comparateur de séries ESP32" : isZh ? "系列对比工具" : isDe ? "ESP32-Serien-Vergleich" : "Comparador de séries"}
lede={
isEs
? "Ponga núcleos, radio, memoria, periféricos y seguridad en la misma tabla y vea lo que realmente cambia de un chip a otro."
@@ -493,7 +573,7 @@ export default function Comparacao() {
? "Put cores, radio, memory, peripherals, and security in the same table and see what really changes from chip to chip."
: isFr
? "Placez les cœurs, la radio, la mémoire, les périphériques et la sécurité dans le même tableau et voyez ce qui change réellement d'une puce à l'autre."
- : isDe
+ : isZh ? "把内核、无线、内存、外设和安全放进同一张表,看看芯片之间到底有什么不同。" : isDe
? "Stellen Sie Kerne, Funk, Speicher, Peripherie und Sicherheit in derselben Tabelle gegenüber und sehen Sie, was sich wirklich von Chip zu Chip ändert."
: "Coloque núcleos, rádio, memória, periféricos e segurança na mesma tabela e veja o que realmente muda de um chip para outro."
}
@@ -510,7 +590,7 @@ export default function Comparacao() {
<>Choose {MIN_SERIES} to {MAX_SERIES} series>
) : isFr ? (
<>Choisissez de {MIN_SERIES} à {MAX_SERIES} séries>
- ) : isDe ? (
+ ) : isZh ? ( <>请选择 {MIN_SERIES} 到 {MAX_SERIES} 个系列> ) : isDe ? (
<>Wählen Sie {MIN_SERIES} bis {MAX_SERIES} Serien>
) : (
<>Escolha de {MIN_SERIES} a {MAX_SERIES} séries>
@@ -532,7 +612,9 @@ export default function Comparacao() {
{selectedSeries.length} sur {MAX_SERIES} sélectionnées
{selectedSeries.length === MAX_SERIES && ". Supprimez-en une pour en changer."}
>
- ) : isDe ? (
+ ) : isZh ? ( <>
+已选 {selectedSeries.length} / {MAX_SERIES} 个{selectedSeries.length === MAX_SERIES && "。请先移除一个再更换。"}
+> ) : isDe ? (
<>
{selectedSeries.length} von {MAX_SERIES} ausgewählt
{selectedSeries.length === MAX_SERIES && ". Entfernen Sie eine zum Wechseln."}
@@ -571,7 +653,7 @@ export default function Comparacao() {
- {isEs ? "Especificaciones" : isEn ? "Specifications" : isFr ? "Spécifications" : isDe ? "Spezifikationen" : "Especificações"}
+ {isEs ? "Especificaciones" : isEn ? "Specifications" : isFr ? "Spécifications" : isZh ? "规格" : 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" : isFr ? "Afficher uniquement les différences" : isDe ? "Nur Unterschiede anzeigen" : "Mostrar só as diferenças"}
+ {isEs ? "Mostrar solo diferencias" : isEn ? "Show differences only" : isFr ? "Afficher uniquement les différences" : isZh ? "仅显示差异" : isDe ? "Nur Unterschiede anzeigen" : "Mostrar só as diferenças"}
@@ -594,7 +676,7 @@ export default function Comparacao() {
<>Comparison of series {selectedSeries.join(", ")}>
) : isFr ? (
<>Comparaison des séries {selectedSeries.join(", ")}>
- ) : isDe ? (
+ ) : isZh ? ( <>系列对比:{selectedSeries.join(", ")}> ) : isDe ? (
<>Vergleich der Serien {selectedSeries.join(", ")}>
) : (
<>Comparação das séries {selectedSeries.join(", ")}>
@@ -603,7 +685,7 @@ export default function Comparacao() {
- {isEs ? "Especificación" : isEn ? "Specification" : isFr ? "Spécification" : isDe ? "Spezifikation" : "Especificação"}
+ {isEs ? "Especificación" : isEn ? "Specification" : isFr ? "Spécification" : isZh ? "规格项" : isDe ? "Spezifikation" : "Especificação"}
{selectedSeries.map((seriesKey) => (
@@ -660,7 +742,7 @@ export default function Comparacao() {
- {isEs ? "Documentación oficial" : isEn ? "Official documentation" : isFr ? "Documentation officielle" : isDe ? "Offizielle Dokumentation" : "Documentação oficial"}
+ {isEs ? "Documentación oficial" : isEn ? "Official documentation" : isFr ? "Documentation officielle" : isZh ? "官方文档" : isDe ? "Offizielle Dokumentation" : "Documentação oficial"}
@@ -689,7 +771,7 @@ export default function Comparacao() {
- {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 ? "¿Desea probar en banco?" : isEn ? "Want to test on the bench?" : isFr ? "Envie de tester sur l'établi ?" : isZh ? "想在工作台上试试?" : isDe ? "Möchten Sie es auf dem Prüfplatz testen ?" : "Quer testar na bancada?"}
{isEs
@@ -698,13 +780,13 @@ export default function Comparacao() {
? "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
+ : isZh ? "查看带引脚、外设和商店链接的开发板。" : 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" : isFr ? "Voir le catalogue de cartes" : isDe ? "Board-Katalog ansehen" : "Ver catálogo de placas"}
+ {isEs ? "Ver catálogo de placas" : isEn ? "View boards catalog" : isFr ? "Voir le catalogue de cartes" : isZh ? "查看开发板目录" : isDe ? "Board-Katalog ansehen" : "Ver catálogo de placas"}
diff --git a/app/[locale]/componentes/layout.jsx b/app/[locale]/componentes/layout.jsx
index c862788..1703771 100644
--- a/app/[locale]/componentes/layout.jsx
+++ b/app/[locale]/componentes/layout.jsx
@@ -6,6 +6,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
+ const isZh = locale === "zh-CN";
return paginaMeta({
titulo: "ESP Component Registry & IDF Component Manager · ESPDocs",
descricao: isEs
@@ -14,7 +15,7 @@ export async function generateMetadata({ params }) {
? "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
+ : isZh ? "官方交互式 ESP Component Registry 目录:显示屏驱动、LVGL、ESP-SR 语音识别、RainMaker、BSP 以及 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",
@@ -55,7 +56,7 @@ export async function generateMetadata({ params }) {
"espressif esp-sr",
"bibliothèques ESP-IDF",
]
- : isDe
+ : isZh ? ["ESP Component Registry", "IDF Component Manager", "idf_component.yml", "idf.py add-dependency", "ESP32 组件", "espressif esp_lvgl_port", "espressif esp_rainmaker", "espressif esp-sr", "ESP-IDF 库"] : isDe
? [
"ESP Component Registry",
"IDF Component Manager",
@@ -175,6 +176,24 @@ const faqsDe = [
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.",
},
];
+const faqsZh = [
+ {
+ pergunta: "什么是 ESP Component Registry 和 IDF Component Manager?",
+ resposta: "ESP Component Registry(components.espressif.com)是乐鑫模块化软件包的官方中央仓库。IDF Component Manager 集成在 ESP-IDF 中(v4.4+,v5.x+ 起默认启用),可根据 idf_component.yml 清单自动下载、编译并解析依赖版本。",
+ },
+ {
+ pergunta: "如何向 ESP-IDF 项目中添加组件?",
+ resposta: "可以在项目终端运行 'idf.py add-dependency \"namespace/component^version\"'(例如:idf.py add-dependency \"espressif/esp_lvgl_port^2.4.0\"),也可以直接编辑 main/idf_component.yml。",
+ },
+ {
+ pergunta: "使用 Component Registry 与 git 子模块有什么区别?",
+ resposta: "Component Registry 省去了管理笨重 Git 子模块和手动跟踪的麻烦。它提供语义化版本(^ 和 ~)、自动解析传递依赖,以及按需的模块化编译。",
+ },
+ {
+ pergunta: "我可以创建并发布自己的组件吗?",
+ resposta: "可以,任何开发者或公司都可以使用官方的 Compote CLI 工具(pip install idf-component-manager)向 ESP Component Registry 发布公开或私有组件。",
+ },
+];
export default async function ComponentesLayout({ children, params }) {
const { locale } = await params;
@@ -182,8 +201,9 @@ export default async function ComponentesLayout({ children, params }) {
const isEs = locale === "es";
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";
+ const isZh = locale === "zh-CN";
+ const faqs = isEs ? faqsEs : isEn ? faqsEn : isFr ? faqsFr : isZh ? faqsZh : isDe ? faqsDe : faqsPt;
+ const canonPath = isEs ? "/es/componentes" : isEn ? "/en/components" : isFr ? "/fr/composants" : isZh ? "/zh-CN/components" : isDe ? "/de/komponenten" : "/componentes";
return (
<>
@@ -192,7 +212,7 @@ export default async function ComponentesLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isDe ? "/de" : "/" },
+ { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/" },
{ nome: "ESP Component Registry", caminho: canonPath },
])
),
@@ -215,7 +235,7 @@ export default async function ComponentesLayout({ children, params }) {
? "ESP Component Registry Hub & Manifest Builder"
: isFr
? "Hub de l'ESP Component Registry & Générateur de Manifeste"
- : isDe
+ : isZh ? "ESP Component Registry 中心与清单生成器" : isDe
? "ESP Component Registry Hub & Manifest-Generator"
: "Hub do ESP Component Registry & Construtor de Manifesto",
descricao: isEs
@@ -224,7 +244,7 @@ export default async function ComponentesLayout({ children, params }) {
? "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
+ : isZh ? "ESP-IDF 模块化组件的精选目录与 idf_component.yml 清单生成器。" : 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: canonPath,
diff --git a/app/[locale]/componentes/page.jsx b/app/[locale]/componentes/page.jsx
index c3c7a46..40916dd 100644
--- a/app/[locale]/componentes/page.jsx
+++ b/app/[locale]/componentes/page.jsx
@@ -11,6 +11,7 @@ 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 * as componentesZh from "@/lib/data/zh-CN/componentes";
import {
ArrowUpRight,
Box,
@@ -50,7 +51,8 @@ export default function ComponentRegistryPage() {
const isEs = pathname?.startsWith("/es");
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 isZh = pathname?.startsWith("/zh-CN");
+ const { CATEGORIAS_COMPONENTES, COMPONENTES_REGISTRY, GUIA_COMPONENT_MANAGER } = isEs ? componentesEs : isEn ? componentesEn : isFr ? componentesFr : isZh ? componentesZh : isDe ? componentesDe : componentesPt;
const [busca, setBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
@@ -110,7 +112,7 @@ export default function ComponentRegistryPage() {
? `# 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
+ : isZh ? `# 尚未选择组件。\n# 点击下方卡片中的“+ 添加到清单”。` : 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.`;
}
@@ -121,14 +123,14 @@ export default function ComponentRegistryPage() {
? `# 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
+ : isZh ? `# main/idf_component.yml\n# 由 ESPDocs Component Hub 生成(https://espdocs.cienciaembarcada.com.br/zh-CN/components)\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, isFr, isDe]);
+ }, [componentesSelecionados, COMPONENTES_REGISTRY, isEn, isEs, isFr, isDe, isZh]);
const copiarManifesto = () => {
navigator.clipboard.writeText(manifestoYaml);
@@ -165,8 +167,8 @@ export default function ComponentRegistryPage() {
isEn={isEn}
isEs={isEs}
isFr={isFr}
- isDe={isDe}
- trail={[{ label: isEs ? "Component Registry" : isEn ? "Component Registry" : isFr ? "Component Registry" : isDe ? "Component Registry" : "Component Registry" }]}
+ isDe={isDe} isZh={isZh}
+ trail={[{ label: isEs ? "Component Registry" : isEn ? "Component Registry" : isFr ? "Component Registry" : isZh ? "Component Registry" : isDe ? "Component Registry" : "Component Registry" }]}
icon={Box}
label="ESP-IDF Component Manager"
title="ESP Component Registry"
@@ -177,26 +179,26 @@ export default function ComponentRegistryPage() {
? "Official Espressif catalog of reusable packages for ESP-IDF. Choose drivers, voice stacks, and industrial protocols, copy commands, and generate project manifests."
: isFr
? "Le catalogue officiel de paquets réutilisables d'Espressif pour ESP-IDF. Choisissez vos pilotes, piles vocales et protocoles industriels, copiez la commande et générez le manifeste du projet."
- : isDe
+ : isZh ? "乐鑫面向 ESP-IDF 的官方可复用软件包目录。选择驱动、语音和工业协议栈,复制命令并生成项目清单。" : isDe
? "Der offizielle Espressif-Katalog für wiederverwendbare Pakete für ESP-IDF. Wählen Sie Treiber, Sprach- und Industrieprotokolle, kopieren Sie den Befehl und erstellen Sie das Projektmanifest."
: "O catálogo oficial de pacotes reutilizáveis da Espressif para o ESP-IDF. Escolha drivers, voz e pilhas industriais, copie o comando e gere o manifesto do projeto."
}
>
- {isEs ? "Abrir el registro oficial" : isEn ? "Open official registry" : isFr ? "Ouvrir le registre officiel" : isDe ? "Offizielles Repository öffnen" : "Abrir o registro oficial"}
+ {isEs ? "Abrir el registro oficial" : isEn ? "Open official registry" : isFr ? "Ouvrir le registre officiel" : isZh ? "打开官方注册中心" : isDe ? "Offizielles Repository öffnen" : "Abrir o registro oficial"}
- {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 ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : isFr ? "(ouvre un nouvel onglet)" : isZh ? "(在新标签页打开)" : isDe ? "(öffnet in neuem Tab)" : "(abre em nova aba)"}
- {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 ? "Generar idf_component.yml" : isEn ? "Generate idf_component.yml" : isFr ? "Générer idf_component.yml" : isZh ? "生成 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" : isFr ? "Rechercher par nom, namespace ou tag" : isDe ? "Nach Name, Namespace oder Tag suchen" : "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" : isZh ? "按名称、命名空间或标签搜索" : 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" : isFr ? "Rechercher par nom, namespace ou tag" : isDe ? "Nach Name, Namespace oder Tag suchen" : "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" : isZh ? "按名称、命名空间或标签搜索" : 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" : isFr ? "Catégorie" : isDe ? "Kategorie" : "Categoria"}
+
{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isZh ? "类别" : isDe ? "Kategorie" : "Categoria"}
{CATEGORIAS_COMPONENTES.map((cat) => {
const Icone = ICONES_CATEGORIA[cat.id] ?? Layers;
@@ -219,7 +221,7 @@ export default function ComponentRegistryPage() {
return (
setCategoriaAtiva(cat.id)} aria-pressed={categoriaAtiva === cat.id} className="chip-btn">
- {cat.id === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todos") : cat.nome.replace(/\s*\(.*\)/, "").split(",")[0].split(" &")[0]}
+ {cat.id === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isZh ? "全部" : isDe ? "Alle" : "Todos") : cat.nome.replace(/\s*\(.*\)/, "").split(",")[0].split(" &")[0]}
{contagem}
);
@@ -228,11 +230,11 @@ export default function ComponentRegistryPage() {
-
{isEs ? "Chip compatible" : isEn ? "Compatible chip" : isFr ? "Puce compatible" : isDe ? "Kompatibler Chip" : "Chip compatível"}
+
{isEs ? "Chip compatible" : isEn ? "Compatible chip" : isFr ? "Puce compatible" : isZh ? "兼容芯片" : isDe ? "Kompatibler Chip" : "Chip compatível"}
{chipsDisponiveis.map((chip) => (
setChipFiltro(chip)} aria-pressed={chipFiltro === chip} className="chip-btn">
- {chip === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todos") : chip}
+ {chip === "todos" ? (isEs ? "Todos" : isEn ? "All" : isFr ? "Tous" : isZh ? "全部" : isDe ? "Alle" : "Todos") : chip}
))}
@@ -243,17 +245,17 @@ export default function ComponentRegistryPage() {
- {componentesFiltrados.length} {componentesFiltrados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isDe ? "Komponenten" : "componentes")}
+ {componentesFiltrados.length} {componentesFiltrados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isZh ? "个组件" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isZh ? "个组件" : isDe ? "Komponenten" : "componentes")}
@@ -262,7 +264,7 @@ export default function ComponentRegistryPage() {
- {isEs ? "No se encontraron componentes" : isEn ? "No components found" : isFr ? "Aucun composant trouvé" : isDe ? "Keine Komponenten gefunden" : "Nenhum componente encontrado"}
+ {isEs ? "No se encontraron componentes" : isEn ? "No components found" : isFr ? "Aucun composant trouvé" : isZh ? "未找到组件" : isDe ? "Keine Komponenten gefunden" : "Nenhum componente encontrado"}
{isEs
@@ -271,12 +273,12 @@ export default function ComponentRegistryPage() {
? "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
+ : isZh ? "请搜索更通用的词,或返回全部类别。" : 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" : isFr ? "Effacer les filtres" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
+ {isEs ? "Limpiar filtros" : isEn ? "Clear filters" : isFr ? "Effacer les filtres" : isZh ? "清除筛选" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
) : (
@@ -296,7 +298,7 @@ export default function ComponentRegistryPage() {
{comp.oficial && (
- {isEs ? "Oficial" : isEn ? "Official" : isFr ? "Officiel" : isDe ? "Offiziell" : "Oficial"}
+ {isEs ? "Oficial" : isEn ? "Official" : isFr ? "Officiel" : isZh ? "官方" : isDe ? "Offiziell" : "Oficial"}
)}
@@ -320,12 +322,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}` : isFr ? `Copier la commande pour ajouter ${comp.name}` : isDe ? `Befehl zum Hinzufügen von ${comp.name} kopieren` : `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}` : isZh ? `复制添加 ${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" : isFr ? "Commande copiée" : isDe ? "Befehl kopiert" : "Comando copiado") : ""}
+ {foiCopiado ? (isEs ? "Comando copiado" : isEn ? "Command copied" : isFr ? "Commande copiée" : isZh ? "命令已复制" : isDe ? "Befehl kopiert" : "Comando copiado") : ""}
Chips:
@@ -341,14 +343,14 @@ export default function ComponentRegistryPage() {
className={`btn btn-sm ${selecionado ? "btn-primary" : "btn-ghost"}`}
>
{selecionado ? : }
- {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")}
+ {selecionado ? (isEs ? "En manifiesto" : isEn ? "In manifest" : isFr ? "Dans le manifeste" : isZh ? "已在清单中" : isDe ? "Im Manifest" : "No manifesto") : (isEs ? "Agregar al manifiesto" : isEn ? "Add to manifest" : isFr ? "Ajouter au manifeste" : isZh ? "添加到清单" : isDe ? "Zum Manifest hinzufügen" : "Adicionar ao manifesto")}
@@ -364,9 +366,9 @@ export default function ComponentRegistryPage() {
-
{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : isFr ? "Générateur de manifeste" : isDe ? "Manifest-Generator" : "Gerador de manifesto"}
+
{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : isFr ? "Générateur de manifeste" : isZh ? "清单生成器" : isDe ? "Manifest-Generator" : "Gerador de manifesto"}
- {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 ? "Su idf_component.yml" : isEn ? "Your idf_component.yml" : isFr ? "Votre idf_component.yml" : isZh ? "你的 idf_component.yml" : isDe ? "Ihre idf_component.yml" : "Seu idf_component.yml"}
{isEs ? (
@@ -375,7 +377,7 @@ export default function ComponentRegistryPage() {
<>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 ? (
+ ) : isZh ? ( <>将文件保存到项目的 main/ 文件夹中,ESP-IDF 会在编译时下载所有库。> ) : 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.>
@@ -385,27 +387,27 @@ export default function ComponentRegistryPage() {
{manifestoCopiado ? : }
- {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")}
+ {manifestoCopiado ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar YAML" : isEn ? "Copy YAML" : isFr ? "Copier le YAML" : isZh ? "复制 YAML" : isDe ? "YAML kopieren" : "Copiar YAML")}
- {isEs ? "Descargar .yml" : isEn ? "Download .yml" : isFr ? "Télécharger .yml" : isDe ? ".yml herunterladen" : "Baixar .yml"}
+ {isEs ? "Descargar .yml" : isEn ? "Download .yml" : isFr ? "Télécharger .yml" : isZh ? "下载 .yml" : isDe ? ".yml herunterladen" : "Baixar .yml"}
{componentesSelecionados.length > 0 && (
setComponentesSelecionados([])} className="btn btn-ghost btn-sm">
- {isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isDe ? "Leeren" : "Limpar"}
+ {isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isZh ? "清除" : isDe ? "Leeren" : "Limpar"}
)}
-
{manifestoYaml}
+
{manifestoYaml}
{componentesSelecionados.length > 0 && (
-
+
{componentesSelecionados.map((id) => {
const c = COMPONENTES_REGISTRY.find((item) => item.id === id);
if (!c) return null;
@@ -416,7 +418,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` : isFr ? `Retirer ${c.name} du manifeste` : isDe ? `${c.name} aus dem Manifest entfernen` : `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` : isZh ? `从清单中移除 ${c.name}` : isDe ? `${c.name} aus dem Manifest entfernen` : `Remover ${c.name} do manifesto`}
>
@@ -429,7 +431,7 @@ export default function ComponentRegistryPage() {
-
+
{GUIA_COMPONENT_MANAGER.vantagens.map((v) => (
@@ -442,7 +444,7 @@ export default function ComponentRegistryPage() {
- {isEs ? "Comandos esenciales" : isEn ? "Essential commands" : isFr ? "Commandes essentielles" : isDe ? "Wichtige Befehle" : "Comandos essenciais"}
+ {isEs ? "Comandos esenciales" : isEn ? "Essential commands" : isFr ? "Commandes essentielles" : isZh ? "基本命令" : isDe ? "Wichtige Befehle" : "Comandos essenciais"}
{GUIA_COMPONENT_MANAGER.comandosCli.map((cmd, i) => (
@@ -457,7 +459,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" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar" : isEn ? "Copy" : isFr ? "Copier" : isDe ? "Kopieren" : "Copiar")}
+ {copiadoId === `cmd-${i}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar" : isEn ? "Copy" : isFr ? "Copier" : isZh ? "复制" : isDe ? "Kopieren" : "Copiar")}
))}
@@ -466,7 +468,7 @@ export default function ComponentRegistryPage() {
- {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 ? "Versionamiento semántico en ESP-IDF" : isEn ? "Semantic versioning in ESP-IDF" : isFr ? "Versionnement sémantique dans ESP-IDF" : isZh ? "ESP-IDF 中的语义化版本" : isDe ? "Semantische Versionierung in ESP-IDF" : "Versionamento semântico no ESP-IDF"}
{isEs ? (
@@ -490,7 +492,12 @@ export default function ComponentRegistryPage() {
1.2.0: verrouille sur cette version exacte.
*: accepte n'importe quelle version. À éviter en production.
>
- ) : isDe ? (
+ ) : isZh ? ( <>
+^1.2.0:接受次版本更新和修复(1.2.1、1.3.0),但会阻止重大 API 变更(2.0.0)。推荐。
+~1.2.0:只接受缺陷修复(1.2.1、1.2.5)。
+1.2.0:锁定到精确版本。
+*:接受任意版本。生产环境请避免。
+> ) : 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).
diff --git a/app/[locale]/diagnostico/layout.jsx b/app/[locale]/diagnostico/layout.jsx
index fd0bbb9..631c99a 100644
--- a/app/[locale]/diagnostico/layout.jsx
+++ b/app/[locale]/diagnostico/layout.jsx
@@ -6,6 +6,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
+ const isZh = locale === "zh-CN";
return paginaMeta({
titulo: isEs
? "Diagnóstico de errores comunes en ESP32: Guía profesional"
@@ -13,7 +14,7 @@ export async function generateMetadata({ params }) {
? "Common ESP32 Errors & Diagnostics: Professional Guide"
: isFr
? "Diagnostic des erreurs courantes sur ESP32 : guide professionnel"
- : isDe
+ : isZh ? "ESP32 常见故障诊断:专业指南" : isDe
? "Diagnose häufiger ESP32-Fehler: Professioneller Leitfaden"
: "Diagnóstico de Erros Comuns no ESP32: Guia Profissional",
descricao: isEs
@@ -22,7 +23,7 @@ export async function generateMetadata({ params }) {
? "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
+ : isZh ? "权威技术指南,用于识别并解决 ESP32(Arduino 与 ESP-IDF)上的 Brownout、Guru Meditation、Task Watchdog、栈溢出、内存泄漏和启动故障。" : 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",
@@ -60,7 +61,7 @@ export async function generateMetadata({ params }) {
"ESP32 redémarre en boucle",
"failed to connect to esp32",
]
- : isDe
+ : isZh ? ["ESP32 常见错误", "brownout detector was triggered", "guru meditation error", "task watchdog got triggered", "stack canary watchpoint triggered", "如何调试 ESP32", "ESP32 反复重启", "failed to connect to esp32"] : isDe
? [
"häufige ESP32-Fehler",
"brownout detector was triggered",
@@ -178,6 +179,24 @@ const faqsDe = [
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.",
},
];
+const faqsZh = [
+ {
+ pergunta: "ESP32 上出现“Brownout detector was triggered”是什么原因?",
+ resposta: "当 Wi-Fi/BLE 射频产生快速电流峰值(常超过 500mA)时,3V3 电源轨跌到 2.8V ~ 2.43V 以下,就会出现这个错误,通常由过细的 USB 线或容量不足的 LDO 稳压器引起。工程上的解决办法是在 VCC 附近放置 10µF 到 100µF 的低 ESR 旁路电容,并使用 600mA 以上的可靠 LDO 稳压器。",
+ },
+ {
+ pergunta: "如何解决“Guru Meditation Error (LoadProhibited)”?",
+ resposta: "LoadProhibited 表示试图读取或写入空(NULL)或无效的内存地址。解决办法是用 Espressif Exception Decoder 或 addr2line 定位源代码行,并加入 if (ptr != NULL) 这类防御式检查。",
+ },
+ {
+ pergunta: "为什么 FreeRTOS 中会触发任务看门狗定时器(TWDT)?",
+ resposta: "当高优先级任务在紧凑的阻塞循环中独占 CPU(例如没有 vTaskDelay 的 while(1)),使负责喂操作系统看门狗的低优先级 IDLE 任务得不到运行时,TWDT 就会触发。",
+ },
+ {
+ pergunta: "为什么 ESP32 上 HTTPS 和 TLS 连接会出现错误 -0x2700?",
+ resposta: "ESP32 启动时,内部系统时钟从 1970 年纪元开始。TLS 证书校验器会因服务器证书的有效期落在遥远的“未来”而拒绝该证书。在发起 TLS 连接之前,通过 SNTP(configTime)同步时间即可解决。",
+ },
+];
export default async function DiagnosticoLayout({ children, params }) {
const { locale } = await params;
@@ -185,8 +204,9 @@ export default async function DiagnosticoLayout({ children, params }) {
const isEs = locale === "es";
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";
+ const isZh = locale === "zh-CN";
+ const faqs = isEs ? faqsEs : isEn ? faqsEn : isFr ? faqsFr : isZh ? faqsZh : isDe ? faqsDe : faqsPt;
+ const canonPath = isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : isFr ? "/fr/diagnostic" : isZh ? "/zh-CN/diagnostics" : isDe ? "/de/diagnose" : "/diagnostico";
return (
<>
@@ -195,8 +215,8 @@ export default async function DiagnosticoLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
- { 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 },
+ { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/" },
+ { nome: isEs ? "Diagnóstico y errores comunes" : isEn ? "Diagnostics & Common Errors" : isFr ? "Diagnostic et erreurs courantes" : isZh ? "诊断与常见错误" : isDe ? "Diagnose und häufige Fehler" : "Diagnóstico & Erros Comuns", caminho: canonPath },
])
),
}}
@@ -214,14 +234,14 @@ 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" : isFr ? "Guide de diagnostic et erreurs sur ESP32" : isDe ? "ESP32-Fehlerdiagnose und Debugging-Leitfaden" : "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" : isZh ? "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
+ : isZh ? "用于分析故障、CPU 异常和解码 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: canonPath,
diff --git a/app/[locale]/diagnostico/page.jsx b/app/[locale]/diagnostico/page.jsx
index d24b14e..99173fa 100644
--- a/app/[locale]/diagnostico/page.jsx
+++ b/app/[locale]/diagnostico/page.jsx
@@ -10,6 +10,7 @@ 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 * as diagnosticoZh from "@/lib/data/zh-CN/diagnostico";
import {
Activity,
AlertTriangle,
@@ -72,6 +73,12 @@ const EXEMPLOS_LOG_DE = [
["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 EXEMPLOS_LOG_ZH = [
+ ["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 got triggered. The following tasks/users did not reset the watchdog: - IDLE0"],
+ ["TLS 握手", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
+];
const GRAVIDADE = {
"Crítica": "text-danger",
@@ -87,6 +94,9 @@ const GRAVIDADE = {
Media: "text-accent",
Moyenne: "text-accent",
Mittel: "text-accent",
+ "严重": "text-danger",
+ "高": "text-gold",
+ "中": "text-accent",
};
const CHECKLISTS_PT = [
@@ -203,6 +213,28 @@ const CHECKLISTS_DE = [
],
},
];
+const CHECKLISTS_ZH = [
+ {
+ icon: Zap,
+ titulo: "硬件与供电",
+ itens: [
+ "VCC 上接 10 µF 到 100 µF 的低 ESR 电容,并在靠近芯片处再加一个 100 nF 陶瓷电容。",
+ "LDO 稳压器按 600 mA 到 1 A 的电流峰值选型,并留有足够的散热余量。",
+ "复位和启动期间,没有外部外设在启动配置引脚上强制逻辑电平。",
+ "EN(RST)引脚上接 10 µF 或 1 µF 电容,使自动复位烧录更稳定。",
+ ],
+ },
+ {
+ icon: Layers,
+ titulo: "固件与 FreeRTOS",
+ itens: [
+ "每个 FreeRTOS 任务都通过 vTaskDelay、队列或信号量让出 CPU 时间。",
+ "每个 ISR 都标记了 IRAM_ATTR,且不包含任何阻塞调用。",
+ "避免在紧凑循环中使用 Arduino String 造成持续的堆重新分配。",
+ "在尝试 TLS/HTTPS 握手之前,先通过 SNTP 同步时钟。",
+ ],
+ },
+];
export default function DiagnosticoPage() {
const pathname = usePathname();
@@ -210,9 +242,10 @@ export default function DiagnosticoPage() {
const isEs = pathname?.startsWith("/es");
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 isZh = pathname?.startsWith("/zh-CN");
+ const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEs ? diagnosticoEs : isEn ? diagnosticoEn : isFr ? diagnosticoFr : isZh ? diagnosticoZh : isDe ? diagnosticoDe : diagnosticoPt;
+ const exemplosLog = isEs ? EXEMPLOS_LOG_ES : isEn ? EXEMPLOS_LOG_EN : isFr ? EXEMPLOS_LOG_FR : isZh ? EXEMPLOS_LOG_ZH : isDe ? EXEMPLOS_LOG_DE : EXEMPLOS_LOG_PT;
+ const checklists = isEs ? CHECKLISTS_ES : isEn ? CHECKLISTS_EN : isFr ? CHECKLISTS_FR : isZh ? CHECKLISTS_ZH : isDe ? CHECKLISTS_DE : CHECKLISTS_PT;
const [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
@@ -269,7 +302,12 @@ export default function DiagnosticoPage() {
acaoRecomendada:
"Recherchez des mots-clés tels que 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' ou 'panic' dans la console série.",
}
- : isDe
+ : isZh ? {
+titulo: "未能自动归类的错误模式",
+tipo: "未知",
+explicacao: "在这段内容中没有识别出典型的 Guru Meditation 或 Brownout 特征。请检查波特率(例如 115200)是否正确,以及是否包含以 'rst:' 或 'panic' 开头的信息。",
+acaoRecomendada: "在串口控制台中查找 'Guru Meditation'、'rst:'、'LoadProhibited'、'watchdog' 或 'panic' 等关键字。",
+} : isDe
? {
titulo: "Fehlermuster nicht automatisch kategorisiert",
tipo: "Unbekannt",
@@ -345,11 +383,11 @@ export default function DiagnosticoPage() {
isEn={isEn}
isEs={isEs}
isFr={isFr}
- isDe={isDe}
- trail={[{ label: isEs ? "Diagnóstico" : isEn ? "Diagnostics" : isFr ? "Diagnostic" : isDe ? "Diagnose" : "Diagnóstico" }]}
+ isDe={isDe} isZh={isZh}
+ trail={[{ label: isEs ? "Diagnóstico" : isEn ? "Diagnostics" : isFr ? "Diagnostic" : isZh ? "诊断" : isDe ? "Diagnose" : "Diagnóstico" }]}
icon={AlertTriangle}
- label={isEs ? "Depuración de firmware" : isEn ? "Firmware Debugging" : isFr ? "Débogage du firmware" : isDe ? "Firmware-Debugging" : "Depuração de firmware"}
- title={isEs ? "Diagnóstico de fallas del ESP32" : isEn ? "ESP32 Diagnostics and Crash Decoder" : isFr ? "Diagnostic des pannes de l'ESP32" : isDe ? "ESP32-Fehlerdiagnose und Crash-Decoder" : "Diagnóstico de falhas do ESP32"}
+ label={isEs ? "Depuración de firmware" : isEn ? "Firmware Debugging" : isFr ? "Débogage du firmware" : isZh ? "固件调试" : isDe ? "Firmware-Debugging" : "Depuração de firmware"}
+ title={isEs ? "Diagnóstico de fallas del ESP32" : isEn ? "ESP32 Diagnostics and Crash Decoder" : isFr ? "Diagnostic des pannes de l'ESP32" : isZh ? "ESP32 故障诊断" : isDe ? "ESP32-Fehlerdiagnose und Crash-Decoder" : "Diagnóstico de falhas do ESP32"}
lede={
isEs
? "Comprenda la causa raíz de las fallas por Brownout, Guru Meditation, Watchdog y Stack Overflow, y aplique la corrección de ingeniería en lugar de soluciones improvisadas."
@@ -357,7 +395,7 @@ export default function DiagnosticoPage() {
? "Understand the root cause of Brownout, Guru Meditation, Watchdog, and Stack Overflow crashes, and apply engineering solutions instead of workarounds."
: isFr
? "Comprenez la cause racine des pannes de Brownout, Guru Meditation, Watchdog et Stack Overflow, et appliquez la correction d'ingénierie plutôt qu'un bricolage."
- : isDe
+ : isZh ? "弄清 Brownout、Guru Meditation、Watchdog 和栈溢出的成因,并采用工程上的正确修复,而不是临时补救。" : isDe
? "Verstehen Sie die Ursache von Brownout, Guru Meditation, Watchdog und Stack Overflow und wenden Sie saubere Ingenieurlösungen statt Notbehelfen an."
: "Entenda a causa de Brownout, Guru Meditation, Watchdog e Stack Overflow, e aplique a correção de engenharia, não o remendo."
}
@@ -368,9 +406,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" : 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 ? "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" : isZh ? "基于 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)" : isFr ? "(ouvre un nouvel onglet)" : isDe ? "(öffnet in neuem Tab)" : "(abre em nova aba)"}
+ {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : isFr ? "(ouvre un nouvel onglet)" : isZh ? "(在新标签页打开)" : isDe ? "(öffnet in neuem Tab)" : "(abre em nova aba)"}
@@ -379,14 +417,14 @@ export default function DiagnosticoPage() {
- {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 ? "Pegue el registro y descubra la causa" : isEn ? "Paste the log to see the cause" : isFr ? "Collez le journal et découvrez la cause" : isZh ? "粘贴日志,查看原因" : 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." : 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 ? "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." : isZh ? "复制串口监视器中的内容,诊断结果立即显示。" : 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" : isFr ? "Tester avec" : isDe ? "Testen mit" : "Testar com"}
+
{isEs ? "Probar con" : isEn ? "Test with" : isFr ? "Tester avec" : isZh ? "试试这些" : isDe ? "Testen mit" : "Testar com"}
{exemplosLog.map(([rotulo, texto]) => (
analisarLog(texto)} className="chip-btn">
{rotulo}
@@ -397,21 +435,21 @@ export default function DiagnosticoPage() {
- {isEs ? "Registro del monitor serie" : isEn ? "Serial monitor log" : isFr ? "Journal du moniteur série" : isDe ? "Serieller Monitor-Log" : "Log do monitor serial"}
+ {isEs ? "Registro del monitor serie" : isEn ? "Serial monitor log" : isFr ? "Journal du moniteur série" : isZh ? "串口监视器日志" : isDe ? "Serieller Monitor-Log" : "Log do monitor serial"}
@@ -425,8 +463,8 @@ export default function DiagnosticoPage() {
{resultadoLog.titulo}
{resultadoLog.tipo}
-
{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}
+
{isEs ? "Causa probable:" : isEn ? "Likely cause:" : isFr ? "Cause probable :" : isZh ? "可能的原因:" : isDe ? "Wahrscheinliche Ursache :" : "Causa provável:"} {resultadoLog.explicacao}
+
{isEs ? "Qué hacer:" : isEn ? "What to do:" : isFr ? "Que faire :" : isZh ? "该怎么做:" : isDe ? "Was zu tun ist :" : "O que fazer:"} {resultadoLog.acaoRecomendada}
)}
@@ -436,13 +474,13 @@ export default function DiagnosticoPage() {
- {isEs ? "Catálogo de fallas" : isEn ? "Fault catalog" : isFr ? "Catalogue de pannes" : isDe ? "Fehlerkatalog" : "Catálogo de falhas"}
+ {isEs ? "Catálogo de fallas" : isEn ? "Fault catalog" : isFr ? "Catalogue de pannes" : isZh ? "故障目录" : isDe ? "Fehlerkatalog" : "Catálogo de falhas"}
- {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"}
+ {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" : isZh ? "按错误、症状或串口信息搜索" : 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" : isFr ? "Rechercher une erreur, un symptôme ou un message" : isDe ? "Fehler, Symptom oder Meldung suchen" : "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" : isZh ? "搜索错误、症状或信息" : 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" : isFr ? "Symptôme" : isDe ? "Symptom" : "Sintoma"}
+
{isEs ? "Síntoma" : isEn ? "Symptom" : isFr ? "Symptôme" : isZh ? "症状" : isDe ? "Symptom" : "Sintoma"}
{sintomasRapidos.map((sintoma) => (
setSintomaAtivo(sintomaAtivo === sintoma.id ? null : sintoma.id)} aria-pressed={sintomaAtivo === sintoma.id} className="chip-btn">
@@ -469,11 +507,11 @@ export default function DiagnosticoPage() {
-
{isEs ? "Área" : isEn ? "Area" : isFr ? "Domaine" : isDe ? "Bereich" : "Área"}
+
{isEs ? "Área" : isEn ? "Area" : isFr ? "Domaine" : isZh ? "领域" : isDe ? "Bereich" : "Área"}
setCategoriaAtiva("todos")} aria-pressed={categoriaAtiva === "todos"} className="chip-btn">
- {isEs ? "Todas" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todas"}
+ {isEs ? "Todas" : isEn ? "All" : isFr ? "Tous" : isZh ? "全部" : isDe ? "Alle" : "Todas"}
{errosCatalogo.length}
{categoriasDiagnostico.map((cat) => {
@@ -493,13 +531,13 @@ export default function DiagnosticoPage() {
- {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"} }
+ {errosFiltrados.length} {errosFiltrados.length === 1 ? (isEs ? "falla" : isEn ? "fault" : isFr ? "panne" : isZh ? "个故障" : isDe ? "Fehler" : "falha") : (isEs ? "fallas" : isEn ? "faults" : isFr ? "pannes" : isZh ? "个故障" : isDe ? "Fehler" : "falhas")}
+ {totalFiltros > 0 && {isEs ? " con los filtros actuales" : isEn ? " with current filters" : isFr ? " avec les filtres actuels" : isZh ? "(当前筛选下)" : isDe ? " mit aktuellen Filtern" : " com os filtros atuais"} }
{temFiltroAtivo && (
- {isEs ? "Restablecer filtros" : isEn ? "Reset filters" : isFr ? "Réinitialiser les filtres" : isDe ? "Filter zurücksetzen" : "Redefinir filtros"}
+ {isEs ? "Restablecer filtros" : isEn ? "Reset filters" : isFr ? "Réinitialiser les filtres" : isZh ? "重置筛选" : isDe ? "Filter zurücksetzen" : "Redefinir filtros"}
)}
@@ -509,13 +547,13 @@ export default function DiagnosticoPage() {
- {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 ? "Ninguna falla coincide con estos filtros" : isEn ? "No faults matching these filters" : isFr ? "Aucune panne ne correspond à ces filtres" : isZh ? "没有符合这些筛选条件的故障" : 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." : 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 ? "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." : isZh ? "请搜索更通用的词,或去掉已选的症状和领域。" : 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" : isFr ? "Effacer les filtres" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
+ {isEs ? "Limpiar filtros" : isEn ? "Clear filters" : isFr ? "Effacer les filtres" : isZh ? "清除筛选" : isDe ? "Filter zurücksetzen" : "Limpar filtros"}
) : (
@@ -523,7 +561,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" : isFr ? "# Exemple non applicable" : isDe ? "# Beispiel nicht anwendbar" : "# 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" : isZh ? "# 示例不适用" : isDe ? "# Beispiel nicht anwendbar" : "# Exemplo não aplicável");
const catInfo = categoriasDiagnostico.find((c) => c.id === erro.categoria);
const IconeCat = ICONES[catInfo?.icone] ?? AlertTriangle;
@@ -550,7 +588,7 @@ export default function DiagnosticoPage() {
<>Severity: {erro.gravidade.toLowerCase()}>
) : isFr ? (
<>Gravité : {erro.gravidade.toLowerCase()}>
- ) : isDe ? (
+ ) : isZh ? ( <>严重程度:{erro.gravidade}> ) : isDe ? (
<>Schweregrad: {erro.gravidade.toLowerCase()}>
) : (
<>Gravidade {erro.gravidade.toLowerCase()}>
@@ -572,11 +610,11 @@ export default function DiagnosticoPage() {
- {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"}
+ {isEs ? "Cómo aparece en el monitor serie" : isEn ? "How it appears in serial monitor" : isFr ? "Apparence dans le moniteur série" : isZh ? "在串口监视器中的样子" : 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" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar registro" : isEn ? "Copy log" : isFr ? "Copier le log" : isDe ? "Log kopieren" : "Copiar log")}
+ {copiadoId === `log-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar registro" : isEn ? "Copy log" : isFr ? "Copier le log" : isZh ? "复制日志" : isDe ? "Log kopieren" : "Copiar log")}
@@ -587,7 +625,7 @@ export default function DiagnosticoPage() {
- {isEs ? "Causa raíz" : isEn ? "Root cause" : isFr ? "Cause racine" : isDe ? "Ursache" : "Causa raiz"}
+ {isEs ? "Causa raíz" : isEn ? "Root cause" : isFr ? "Cause racine" : isZh ? "根本原因" : isDe ? "Ursache" : "Causa raiz"}
{erro.causaRaiz}
@@ -596,17 +634,17 @@ export default function DiagnosticoPage() {
- {isEs ? "La solución improvisada común" : isEn ? "The common workaround" : isFr ? "Le bricolage courant" : isDe ? "Der verbreitete Notbehelf" : "O remendo comum"}
+ {isEs ? "La solución improvisada común" : isEn ? "The common workaround" : isFr ? "Le bricolage courant" : isZh ? "常见的临时补救" : isDe ? "Der verbreitete Notbehelf" : "O remendo comum"}
{erro.gambiarra.titulo}
{erro.gambiarra.descricao}
-
{isEs ? "Consecuencia:" : isEn ? "Consequence:" : isFr ? "Conséquence :" : isDe ? "Folge :" : "Consequência:"} {erro.gambiarra.consequencia}
+
{isEs ? "Consecuencia:" : isEn ? "Consequence:" : isFr ? "Conséquence :" : isZh ? "后果:" : isDe ? "Folge :" : "Consequência:"} {erro.gambiarra.consequencia}
- {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"}
+ {isEs ? "La solución de ingeniería" : isEn ? "The engineering solution" : isFr ? "La solution d'ingénierie" : isZh ? "工程上的解决方案" : isDe ? "Die Ingenieurlösung" : "A solução de engenharia"}
{erro.solucaoEngenharia.titulo}
@@ -622,7 +660,7 @@ export default function DiagnosticoPage() {
-
+
{[
["arduino", "Arduino C++"],
["espidf", "ESP-IDF (C)"],
@@ -635,7 +673,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" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar código" : isEn ? "Copy code" : isFr ? "Copier le code" : isDe ? "Code kopieren" : "Copiar código")}
+ {copiadoId === `code-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado") : (isEs ? "Copiar código" : isEn ? "Copy code" : isFr ? "Copier le code" : isZh ? "复制代码" : isDe ? "Code kopieren" : "Copiar código")}
@@ -645,7 +683,7 @@ export default function DiagnosticoPage() {
-
{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : isFr ? "Conseil d'ingénierie :" : isDe ? "Praxistipp :" : "Dica de engenharia:"} {erro.dicaPro}
+
{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : isFr ? "Conseil d'ingénierie :" : isZh ? "工程提示:" : isDe ? "Praxistipp :" : "Dica de engenharia:"} {erro.dicaPro}
)}
@@ -658,10 +696,10 @@ export default function DiagnosticoPage() {
- {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 ? "Lista de verificación previa a campo" : isEn ? "Pre-deployment checklist" : isFr ? "Checklist avant déploiement" : isZh ? "上线前的检查清单" : 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." : 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."}
+ {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." : isZh ? "在交付产品之前,需要在硬件和固件上验证的关键项目。" : 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 a3310f2..628ca56 100644
--- a/app/[locale]/frameworks/[key]/page.jsx
+++ b/app/[locale]/frameworks/[key]/page.jsx
@@ -10,6 +10,7 @@ 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 frameworksDataZh from "@/data/zh-CN/frameworks.json";
import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, BookOpen, Box, Check, Code, Layers } from "lucide-react";
@@ -19,8 +20,9 @@ export default async function FrameworkDetail({ params }) {
const isEs = locale === "es";
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 isZh = locale === "zh-CN";
+ const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt";
+ const frameworksData = isEs ? frameworksDataEs : isEn ? frameworksDataEn : isFr ? frameworksDataFr : isZh ? frameworksDataZh : isDe ? frameworksDataDe : frameworksDataPt;
const framework = frameworksData[key];
if (!framework) {
@@ -42,7 +44,7 @@ export default async function FrameworkDetail({ params }) {
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.trilha([
- { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isDe ? "/de" : "/" },
+ { nome: isEs ? "Inicio" : isEn ? "Home" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/" },
{ nome: "Frameworks", caminho: getLocalizedHref("/frameworks", currentLocale) },
{ nome: framework.nome, caminho: getLocalizedHref(`/frameworks/${encodeURIComponent(key)}`, currentLocale) },
])),
@@ -56,8 +58,8 @@ export default async function FrameworkDetail({ params }) {
isEn={isEn}
isEs={isEs}
isFr={isFr}
- isDe={isDe}
- trail={[{ label: isEs ? "Frameworks y SDKs" : isEn ? "Frameworks & SDKs" : isFr ? "Frameworks et SDKs" : isDe ? "Frameworks & SDKs" : "Frameworks e SDKs", href: getLocalizedHref("/frameworks", currentLocale) }, { label: framework.nome }]}
+ isDe={isDe} isZh={isZh}
+ trail={[{ label: isEs ? "Frameworks y SDKs" : isEn ? "Frameworks & SDKs" : isFr ? "Frameworks et SDKs" : isZh ? "框架与 SDK" : isDe ? "Frameworks & SDKs" : "Frameworks e SDKs", href: getLocalizedHref("/frameworks", currentLocale) }, { label: framework.nome }]}
icon={Layers}
label={framework.tipo}
title={framework.nome}
@@ -66,41 +68,41 @@ export default async function FrameworkDetail({ params }) {
{framework.documentacao && (
- {isEs ? "Documentación oficial" : isEn ? "Official documentation" : isFr ? "Documentation officielle" : isDe ? "Offizielle Dokumentation" : "Documentação oficial"}
+ {isEs ? "Documentación oficial" : isEn ? "Official documentation" : isFr ? "Documentation officielle" : isZh ? "官方文档" : isDe ? "Offizielle Dokumentation" : "Documentação oficial"}
)}
{framework.repositorio && (
- {isEs ? "Repositorio en GitHub" : isEn ? "GitHub repository" : isFr ? "Dépôt sur GitHub" : isDe ? "GitHub-Repository" : "Repositório no GitHub"}
+ {isEs ? "Repositorio en GitHub" : isEn ? "GitHub repository" : isFr ? "Dépôt sur GitHub" : isZh ? "GitHub 仓库" : isDe ? "GitHub-Repository" : "Repositório no GitHub"}
)}
-
+
-
{isEs ? "Lenguaje" : isEn ? "Language" : isFr ? "Langage" : isDe ? "Sprache" : "Linguagem"}
+ {isEs ? "Lenguaje" : isEn ? "Language" : isFr ? "Langage" : isZh ? "语言" : isDe ? "Sprache" : "Linguagem"}
{framework.linguagem}
{framework.nome_completo}
-
{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isDe ? "Kategorie" : "Categoria"}
+ {isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isZh ? "类别" : isDe ? "Kategorie" : "Categoria"}
{framework.tipo}
-
{isEs ? "Objetivo principal" : isEn ? "Main purpose" : isFr ? "Objectif principal" : isDe ? "Hauptzweck" : "Objetivo principal"}
+ {isEs ? "Objetivo principal" : isEn ? "Main purpose" : isFr ? "Objectif principal" : isZh ? "主要目标" : isDe ? "Hauptzweck" : "Objetivo principal"}
{framework.funcao_principal}
-
+
- {isEs ? "Funciones destacadas" : isEn ? "Key features" : isFr ? "Fonctionnalités clés" : isDe ? "Wichtigste Features" : "Recursos de destaque"}
+ {isEs ? "Funciones destacadas" : isEn ? "Key features" : isFr ? "Fonctionnalités clés" : isZh ? "亮点功能" : isDe ? "Wichtigste Features" : "Recursos de destaque"}