feat(i18n): Simplified Chinese (zh-CN), search answers per locale, fr/de/es data fixes
CI / Lint e auditorias (push) Successful in 1m10s
CI / Trivy (dependências e segredos) (push) Successful in 12s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m39s
Release / Plano (reaproveitar a imagem?) (push) Successful in 6s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 18s
Release / Cria a release no Gitea (push) Successful in 5s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-11-01 23:28:39 -03:00
1 parent ae61416468
commit 5a9fe676fc
61 files changed
+2517 -920

No files matched your search

+34 -1
View File
@@ -6,6 +6,38 @@ Tudo o que muda de relevante no ESPDocs está aqui. O formato segue o
## [Não lançado] ## [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 ## [1.5.0] - 2026-11-02
### Adicionado ### 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 otimização de imagens (GHSA-2464-fhr4-w9xw), e `postcss` fixado acima da cópia
embutida no `next`. 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.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.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 [1.4.1]: https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs/compare/v1.4.0...v1.4.1
+10 -8
View File
@@ -6,15 +6,16 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ 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 descricao: isEn
? "ESP32 development boards and DevKits available in Brazil, with filters by series, connector, and USB-serial converter." ? "ESP32 development boards and DevKits available in Brazil, with filters by series, connector, and USB-serial converter."
: isEs : isEs
? "Placas de desarrollo y DevKits con ESP32, con filtros por serie, conector y conversor USB-serial." ? "Placas de desarrollo y DevKits con ESP32, con filtros por serie, conector y conversor USB-serial."
: isFr : isFr
? "Cartes de développement et DevKits ESP32, avec filtres par série, connecteur et convertisseur USB-série." ? "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." ? "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.", : "Placas de desenvolvimento com ESP32 disponíveis no Brasil, com filtro por série, conector e conversor USB-serial.",
caminho: "/catalogo", caminho: "/catalogo",
@@ -25,7 +26,7 @@ export async function generateMetadata({ params }) {
? ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"] ? ["placas ESP32", "DevKit ESP32", "comprar ESP32", "ESP32 DevKitC"]
: isFr : isFr
? ["cartes ESP32", "DevKit ESP32", "acheter ESP32", "ESP32 DevKitC"] ? ["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"] ? ["ESP32 Boards", "ESP32 DevKit", "ESP32 kaufen", "ESP32 DevKitC"]
: ["placas ESP32", "DevKit ESP32", "comprar ESP32", "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 isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; 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 ( return (
<> <>
@@ -46,14 +48,14 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.aplicacaoWeb({ 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 descricao: isEn
? "Interactive guide and catalog of development boards and DevKits featuring ESP32 microcontrollers." ? "Interactive guide and catalog of development boards and DevKits featuring ESP32 microcontrollers."
: isEs : isEs
? "Guía y catálogo interactivo de placas de desarrollo y DevKits con microcontroladores ESP32." ? "Guía y catálogo interactivo de placas de desarrollo y DevKits con microcontroladores ESP32."
: isFr : isFr
? "Guide interactif et catalogue de cartes de développement et DevKits avec microcontrôleurs ESP32." ? "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." ? "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.", : "Guia e catálogo interativo de placas de desenvolvimento e DevKits com microcontroladores ESP32.",
caminho: canonPath, caminho: canonPath,
@@ -67,8 +69,8 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ jsonLd.trilha([
{ nome: isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/" }, { 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" : isDe ? "Board-Katalog" : "Catálogo de Placas", caminho: canonPath }, { nome: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isZh ? "开发板目录" : isDe ? "Board-Katalog" : "Catálogo de Placas", caminho: canonPath },
]) ])
), ),
}} }}
+46 -22
View File
@@ -101,12 +101,35 @@ const HEADER_MAP_DE = {
"Botões": "Tasten", "Botões": "Tasten",
"LEDs": "LEDs", "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 = { const VALUE_MAP_DE = {
"Sim": "Ja", "Sim": "Ja",
"Não": "Nein", "Não": "Nein",
"Opcional": "Optional", "Opcional": "Optional",
}; };
const VALUE_MAP_ZH = {
"Sim": "是",
"Não": "否",
"Opcional": "可选",
};
export default function PlacasPage() { export default function PlacasPage() {
const pathname = usePathname() || ""; const pathname = usePathname() || "";
@@ -114,6 +137,7 @@ export default function PlacasPage() {
const isEs = pathname === "/es" || pathname.startsWith("/es/"); const isEs = pathname === "/es" || pathname.startsWith("/es/");
const isFr = pathname === "/fr" || pathname.startsWith("/fr/"); const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
const isDe = pathname === "/de" || pathname.startsWith("/de/"); const isDe = pathname === "/de" || pathname.startsWith("/de/");
const isZh = pathname === "/zh-CN" || pathname.startsWith("/zh-CN/");
const [boards, setBoards] = useState([]); const [boards, setBoards] = useState([]);
const [filteredBoards, setFilteredBoards] = useState([]); const [filteredBoards, setFilteredBoards] = useState([]);
@@ -142,7 +166,7 @@ export default function PlacasPage() {
const response = await fetch("/api/placas"); const response = await fetch("/api/placas");
if (!response.ok) { 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(); const result = await response.json();
@@ -248,11 +272,11 @@ export default function PlacasPage() {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isDe ? "Board-Katalog" : "Catálogo de placas" }]} trail={[{ label: isEn ? "Board Catalog" : isEs ? "Catálogo de placas" : isFr ? "Catalogue de cartes" : isZh ? "开发板目录" : isDe ? "Board-Katalog" : "Catálogo de placas" }]}
icon={ShoppingCart} 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"} 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" : isDe ? "ESP32-Board-Katalog" : "Catálogo de placas ESP32"} 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={ lede={
isEn isEn
? "Compare official and third-party boards for your project: USB interface, on-board sensors and direct store links." ? "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." ? "Compare placas oficiales y alternativas para su proyecto: interfaz USB, sensores integrados y enlace directo a la tienda."
: isFr : isFr
? "Comparez les cartes officielles et alternatives pour votre projet : interface USB, capteurs intégrés et lien direct vers la boutique." ? "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." ? "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." : "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() {
<Header /> <Header />
<main id="conteudo"> <main id="conteudo">
{hero} {hero}
<section className="shell section pt-10" aria-busy="true" aria-label={isEn ? "Loading boards" : isEs ? "Cargando placas" : isFr ? "Chargement des cartes" : isDe ? "Boards werden geladen" : "Carregando placas"}> <section className="shell section pt-10" aria-busy="true" aria-label={isEn ? "Loading boards" : isEs ? "Cargando placas" : isFr ? "Chargement des cartes" : isZh ? "正在加载开发板" : isDe ? "Boards werden geladen" : "Carregando placas"}>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3"> <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{Array.from({ length: 6 }).map((_, i) => ( {Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="card h-72 animate-pulse bg-surface-2" /> <div key={i} className="card h-72 animate-pulse bg-surface-2" />
))} ))}
</div> </div>
<p role="status" className="mt-6 text-center text-sm text-muted"> <p role="status" className="mt-6 text-center text-sm text-muted">
{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…"}
</p> </p>
</section> </section>
</main> </main>
@@ -300,14 +324,14 @@ export default function PlacasPage() {
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-danger" aria-hidden="true" /> <AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-danger" aria-hidden="true" />
<div> <div>
<p className="font-semibold text-ink"> <p className="font-semibold text-ink">
{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"}
</p> </p>
<p className="mt-1"> <p className="mt-1">
{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."}
</p> </p>
<button type="button" onClick={fetchBoards} className="btn btn-primary btn-sm mt-4"> <button type="button" onClick={fetchBoards} className="btn btn-primary btn-sm mt-4">
<RefreshCw className="h-4 w-4" aria-hidden="true" /> <RefreshCw className="h-4 w-4" aria-hidden="true" />
{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"}
</button> </button>
</div> </div>
</div> </div>
@@ -322,14 +346,14 @@ export default function PlacasPage() {
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<label htmlFor="busca-placa" className="text-sm font-semibold text-ink"> <label htmlFor="busca-placa" className="text-sm font-semibold text-ink">
{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"}
</label> </label>
<div className="relative mt-2"> <div className="relative mt-2">
<Search className="pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted" aria-hidden="true" /> <Search className="pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted" aria-hidden="true" />
<input <input
id="busca-placa" id="busca-placa"
type="search" type="search"
placeholder={isEn ? "Name or description" : isEs ? "Nombre o descripción" : isFr ? "Nom ou description" : isDe ? "Name oder Beschreibung" : "Nome ou descrição"} placeholder={isEn ? "Name or description" : isEs ? "Nombre o descripción" : isFr ? "Nom ou description" : isZh ? "名称或描述" : isDe ? "Name oder Beschreibung" : "Nome ou descrição"}
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="field pl-10 text-sm" className="field pl-10 text-sm"
@@ -340,7 +364,7 @@ export default function PlacasPage() {
{filterableColumns.map((column) => { {filterableColumns.map((column) => {
const filterData = filterOptions[column]; const filterData = filterOptions[column];
if (!filterData) return null; 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) { if (filterData.isBinary) {
return ( return (
@@ -364,7 +388,7 @@ export default function PlacasPage() {
</legend> </legend>
<div className="mt-2 max-h-44 space-y-0.5 overflow-y-auto pr-1"> <div className="mt-2 max-h-44 space-y-0.5 overflow-y-auto pr-1">
{filterData.values.map((option) => { {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 ( return (
<label key={option} className="flex min-h-10 cursor-pointer items-center gap-3 rounded-lg px-2 text-sm text-muted transition-colors hover:bg-surface-2 hover:text-ink"> <label key={option} className="flex min-h-10 cursor-pointer items-center gap-3 rounded-lg px-2 text-sm text-muted transition-colors hover:bg-surface-2 hover:text-ink">
<input <input
@@ -391,14 +415,14 @@ export default function PlacasPage() {
<main id="conteudo"> <main id="conteudo">
{hero} {hero}
<section className="shell section pt-10 sm:pt-12" aria-label={isEn ? "Boards" : isEs ? "Placas" : isFr ? "Cartes" : isDe ? "Boards" : "Placas"}> <section className="shell section pt-10 sm:pt-12" aria-label={isEn ? "Boards" : isEs ? "Placas" : isFr ? "Cartes" : isZh ? "开发板" : isDe ? "Boards" : "Placas"}>
<div className="grid gap-8 lg:grid-cols-[17rem_1fr] lg:gap-10"> <div className="grid gap-8 lg:grid-cols-[17rem_1fr] lg:gap-10">
<aside className="lg:sticky lg:top-28 lg:self-start"> <aside className="lg:sticky lg:top-28 lg:self-start">
<details className="panel" open={filtrosAbertos} onToggle={(e) => setFiltrosAbertos(e.currentTarget.open)}> <details className="panel" open={filtrosAbertos} onToggle={(e) => setFiltrosAbertos(e.currentTarget.open)}>
<summary className="flex min-h-12 cursor-pointer list-none items-center justify-between gap-3 px-5 py-3 lg:cursor-default"> <summary className="flex min-h-12 cursor-pointer list-none items-center justify-between gap-3 px-5 py-3 lg:cursor-default">
<span className="flex items-center gap-2 text-sm font-semibold text-ink"> <span className="flex items-center gap-2 text-sm font-semibold text-ink">
<SlidersHorizontal className="h-4 w-4 text-accent" aria-hidden="true" /> <SlidersHorizontal className="h-4 w-4 text-accent" aria-hidden="true" />
{isEn ? "Filters" : isEs ? "Filtros" : isFr ? "Filtres" : isDe ? "Filter" : "Filtros"} {isEn ? "Filters" : isEs ? "Filtros" : isFr ? "Filtres" : isZh ? "筛选" : isDe ? "Filter" : "Filtros"}
{activeFilterCount > 0 && <span className="chip chip-accent px-2 py-0">{activeFilterCount}</span>} {activeFilterCount > 0 && <span className="chip chip-accent px-2 py-0">{activeFilterCount}</span>}
</span> </span>
{activeFilterCount > 0 && ( {activeFilterCount > 0 && (
@@ -410,7 +434,7 @@ export default function PlacasPage() {
}} }}
className="text-sm font-medium text-accent hover:text-accent-strong" 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"}
</button> </button>
)} )}
</summary> </summary>
@@ -433,7 +457,7 @@ export default function PlacasPage() {
? filteredBoards.length === 1 ? filteredBoards.length === 1
? "carte trouvée" ? "carte trouvée"
: "cartes trouvées" : "cartes trouvées"
: isDe : isZh ? "款开发板" : isDe
? filteredBoards.length === 1 ? filteredBoards.length === 1
? "Board gefunden" ? "Board gefunden"
: "Boards gefunden" : "Boards gefunden"
@@ -446,7 +470,7 @@ export default function PlacasPage() {
<div className="card mt-4 px-6 py-14 text-center"> <div className="card mt-4 px-6 py-14 text-center">
<Search className="mx-auto h-10 w-10 text-muted" aria-hidden="true" /> <Search className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<h2 className="mt-4 font-display text-xl font-semibold text-ink"> <h2 className="mt-4 font-display text-xl font-semibold text-ink">
{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"}
</h2> </h2>
<p className="mx-auto mt-2 max-w-md text-sm text-muted"> <p className="mx-auto mt-2 max-w-md text-sm text-muted">
{isEn {isEn
@@ -455,12 +479,12 @@ export default function PlacasPage() {
? "Elimine algunos filtros o busque por otro nombre." ? "Elimine algunos filtros o busque por otro nombre."
: isFr : isFr
? "Supprimez certains filtres ou recherchez un autre nom." ? "Supprimez certains filtres ou recherchez un autre nom."
: isDe : isZh ? "请去掉一些筛选条件,或换个名称搜索。" : isDe
? "Entfernen Sie einige Filter oder suchen Sie nach einem anderen Namen." ? "Entfernen Sie einige Filter oder suchen Sie nach einem anderen Namen."
: "Remova alguns filtros ou busque por outro nome."} : "Remova alguns filtros ou busque por outro nome."}
</p> </p>
<button type="button" onClick={clearFilters} className="btn btn-primary mt-6"> <button type="button" onClick={clearFilters} className="btn btn-primary mt-6">
{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"}
</button> </button>
</div> </div>
) : ( ) : (
+25 -9
View File
@@ -6,15 +6,16 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ 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 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." ? "Compare up to four ESP32 series side by side: processor, Wi-Fi, Bluetooth, memory, GPIO, peripherals, and hardware security, with official datasheet data."
: isEs : 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." ? "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 : 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." ? "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." ? "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.", : "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", 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"] ? ["comparar ESP32", "ESP32 vs ESP32-S3", "tabla comparativa ESP32", "cuál ESP32 es mejor"]
: isFr : isFr
? ["comparer ESP32", "ESP32 vs ESP32-S3", "tableau comparatif ESP32", "quel ESP32 choisir"] ? ["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"] ? ["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"], : ["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.", 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 }) { export default async function Layout({ children, params }) {
const { locale } = await params; const { locale } = await params;
@@ -112,8 +127,9 @@ export default async function Layout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const faqs = isEn ? faqsEn : isEs ? faqsEs : isFr ? faqsFr : isDe ? faqsDe : faqsPt; const isZh = locale === "zh-CN";
const canonPath = isEn ? "/en/compare" : isEs ? "/es/comparacion" : isFr ? "/fr/comparaison" : isDe ? "/de/vergleich" : "/comparacao"; 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 ( return (
<> <>
@@ -122,14 +138,14 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.aplicacaoWeb({ 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 descricao: isEn
? "Technical comparison table to analyze specifications, pinouts, and features of multiple ESP32 series." ? "Technical comparison table to analyze specifications, pinouts, and features of multiple ESP32 series."
: isEs : isEs
? "Tabla comparativa técnica para analizar especificaciones, patillaje y características de múltiples series de ESP32." ? "Tabla comparativa técnica para analizar especificaciones, patillaje y características de múltiples series de ESP32."
: isFr : isFr
? "Tableau comparatif technique pour analyser les spécifications, le brochage et les fonctionnalités de plusieurs séries ESP32." ? "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." ? "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.", : "Tabela comparativa técnica para analisar especificações, pinagens e recursos de múltiplas séries de ESP32.",
caminho: canonPath, caminho: canonPath,
@@ -143,8 +159,8 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ jsonLd.trilha([
{ nome: isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início", caminho: isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/" }, { 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" : isDe ? "Serien vergleichen" : "Comparar Séries", caminho: canonPath }, { nome: isEn ? "Compare Series" : isEs ? "Comparar series" : isFr ? "Comparer les séries" : isZh ? "系列对比" : isDe ? "Serien vergleichen" : "Comparar Séries", caminho: canonPath },
]) ])
), ),
}} }}
+109 -27
View File
@@ -11,6 +11,7 @@ import seriesDataEn from "@/data/en/series.json";
import seriesDataEs from "@/data/es/series.json"; import seriesDataEs from "@/data/es/series.json";
import seriesDataFr from "@/data/fr/series.json"; import seriesDataFr from "@/data/fr/series.json";
import seriesDataDe from "@/data/de/series.json"; import seriesDataDe from "@/data/de/series.json";
import seriesDataZh from "@/data/zh-CN/series.json";
import { getLocalizedHref } from "@/i18n/links"; import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, Check, ChevronDown, Scale } from "lucide-react"; import { ArrowRight, ArrowUpRight, Check, ChevronDown, Scale } from "lucide-react";
@@ -416,6 +417,84 @@ const COMPARE_FIELDS_DE = [
{ key: "temperatura_armazenamento", label: "Lagertemp." }, { 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() { export default function Comparacao() {
const pathname = usePathname(); const pathname = usePathname();
@@ -423,15 +502,16 @@ export default function Comparacao() {
const isEs = pathname?.startsWith("/es"); const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr"); const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de"); const isDe = pathname?.startsWith("/de");
const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isDe ? "de" : "pt"; const isZh = pathname?.startsWith("/zh-CN");
const seriesData = isEs ? seriesDataEs : isEn ? seriesDataEn : isFr ? seriesDataFr : isDe ? seriesDataDe : seriesDataPt; const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt";
const compareFields = isEs ? COMPARE_FIELDS_ES : isEn ? COMPARE_FIELDS_EN : isFr ? COMPARE_FIELDS_FR : isDe ? COMPARE_FIELDS_DE : COMPARE_FIELDS_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 [selectedSeries, setSelectedSeries] = useState(["ESP32", "ESP32-S3"]);
const [soDiferencas, setSoDiferencas] = useState(false); const [soDiferencas, setSoDiferencas] = useState(false);
const [collapsedCategories, setCollapsedCategories] = useState(() => ({ const [collapsedCategories, setCollapsedCategories] = useState(() => ({
[isEs ? "Interfaces especiales" : isEn ? "Special Interfaces" : isFr ? "Interfaces spéciales" : isDe ? "Spezielle Schnittstellen" : "Interfaces Especiais"]: 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" : isDe ? "Umgebungsbedingungen" : "Especificações Ambientais"]: true, [isEs ? "Especificaciones ambientales" : isEn ? "Environmental Specifications" : isFr ? "Spécifications environnementales" : isZh ? "环境规格" : isDe ? "Umgebungsbedingungen" : "Especificações Ambientais"]: true,
})); }));
const series = Object.entries(seriesData); const series = Object.entries(seriesData);
@@ -457,19 +537,19 @@ export default function Comparacao() {
}; };
const renderValue = (value) => { const renderValue = (value) => {
if (!value || value === "Não" || value === "No" || value === "Non" || value === "Nein") return <span className="text-muted">{isEs || isEn ? "No" : isFr ? "Non" : isDe ? "Nein" : "Não"}</span>; if (!value || value === "Não" || value === "No" || value === "Non" || (value === "Nein" || value === "否")) return <span className="text-muted">{isEs || isEn ? "No" : isFr ? "Non" : isZh ? "否" : isDe ? "Nein" : "Não"}</span>;
if (value === "Sim" || value === "Yes" || value === "Sí" || value === "Oui" || value === "Ja") { if (value === "Sim" || value === "Yes" || value === "Sí" || value === "Oui" || (value === "Ja" || value === "是")) {
return ( return (
<span className="inline-flex items-center gap-1.5 font-medium text-ok"> <span className="inline-flex items-center gap-1.5 font-medium text-ok">
<Check className="h-4 w-4" aria-hidden="true" /> <Check className="h-4 w-4" aria-hidden="true" />
{isEs ? "Sí" : isEn ? "Yes" : isFr ? "Oui" : isDe ? "Ja" : "Sim"} {isEs ? "Sí" : isEn ? "Yes" : isFr ? "Oui" : isZh ? "是" : isDe ? "Ja" : "Sim"}
</span> </span>
); );
} }
return <span className="font-medium text-ink">{value}</span>; return <span className="font-medium text-ink">{value}</span>;
}; };
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; const diferem = (key) => new Set(valores(key)).size > 1;
return ( return (
@@ -481,11 +561,11 @@ export default function Comparacao() {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isEs ? "Comparador" : isEn ? "Comparator" : isFr ? "Comparateur" : isDe ? "Vergleich" : "Comparador" }]} trail={[{ label: isEs ? "Comparador" : isEn ? "Comparator" : isFr ? "Comparateur" : isZh ? "对比工具" : isDe ? "Vergleich" : "Comparador" }]}
icon={Scale} 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"} 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" : isDe ? "ESP32-Serien-Vergleich" : "Comparador de séries"} 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={ lede={
isEs 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." ? "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." ? "Put cores, radio, memory, peripherals, and security in the same table and see what really changes from chip to chip."
: isFr : 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." ? "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." ? "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." : "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</> <>Choose {MIN_SERIES} to {MAX_SERIES} series</>
) : isFr ? ( ) : isFr ? (
<>Choisissez de {MIN_SERIES} à {MAX_SERIES} séries</> <>Choisissez de {MIN_SERIES} à {MAX_SERIES} séries</>
) : isDe ? ( ) : isZh ? ( <>请选择 {MIN_SERIES} 到 {MAX_SERIES} 个系列</> ) : isDe ? (
<>Wählen Sie {MIN_SERIES} bis {MAX_SERIES} Serien</> <>Wählen Sie {MIN_SERIES} bis {MAX_SERIES} Serien</>
) : ( ) : (
<>Escolha de {MIN_SERIES} a {MAX_SERIES} séries</> <>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} sur {MAX_SERIES} sélectionnées
{selectedSeries.length === MAX_SERIES && ". Supprimez-en une pour en changer."} {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} von {MAX_SERIES} ausgewählt
{selectedSeries.length === MAX_SERIES && ". Entfernen Sie eine zum Wechseln."} {selectedSeries.length === MAX_SERIES && ". Entfernen Sie eine zum Wechseln."}
@@ -571,7 +653,7 @@ export default function Comparacao() {
<section className="shell mt-8" aria-labelledby="tabela-titulo"> <section className="shell mt-8" aria-labelledby="tabela-titulo">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<h2 id="tabela-titulo" className="text-base font-semibold text-ink"> <h2 id="tabela-titulo" className="text-base font-semibold text-ink">
{isEs ? "Especificaciones" : isEn ? "Specifications" : isFr ? "Spécifications" : isDe ? "Spezifikationen" : "Especificações"} {isEs ? "Especificaciones" : isEn ? "Specifications" : isFr ? "Spécifications" : isZh ? "规格" : isDe ? "Spezifikationen" : "Especificações"}
</h2> </h2>
<label className="flex min-h-11 cursor-pointer items-center gap-2.5 text-sm font-medium text-ink-soft"> <label className="flex min-h-11 cursor-pointer items-center gap-2.5 text-sm font-medium text-ink-soft">
<input <input
@@ -580,7 +662,7 @@ export default function Comparacao() {
onChange={(e) => setSoDiferencas(e.target.checked)} onChange={(e) => setSoDiferencas(e.target.checked)}
className="h-5 w-5 cursor-pointer accent-[var(--accent)]" 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"}
</label> </label>
</div> </div>
@@ -594,7 +676,7 @@ export default function Comparacao() {
<>Comparison of series {selectedSeries.join(", ")}</> <>Comparison of series {selectedSeries.join(", ")}</>
) : isFr ? ( ) : isFr ? (
<>Comparaison des séries {selectedSeries.join(", ")}</> <>Comparaison des séries {selectedSeries.join(", ")}</>
) : isDe ? ( ) : isZh ? ( <>系列对比:{selectedSeries.join(", ")}</> ) : isDe ? (
<>Vergleich der Serien {selectedSeries.join(", ")}</> <>Vergleich der Serien {selectedSeries.join(", ")}</>
) : ( ) : (
<>Comparação das séries {selectedSeries.join(", ")}</> <>Comparação das séries {selectedSeries.join(", ")}</>
@@ -603,7 +685,7 @@ export default function Comparacao() {
<thead> <thead>
<tr className="border-b border-line bg-surface-2"> <tr className="border-b border-line bg-surface-2">
<th scope="col" className="sticky left-0 z-20 w-28 bg-surface-2 px-4 py-4 text-left font-semibold text-muted sm:w-52 sm:px-6"> <th scope="col" className="sticky left-0 z-20 w-28 bg-surface-2 px-4 py-4 text-left font-semibold text-muted sm:w-52 sm:px-6">
{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"}
</th> </th>
{selectedSeries.map((seriesKey) => ( {selectedSeries.map((seriesKey) => (
<th key={seriesKey} scope="col" className="min-w-40 px-4 py-4 text-left sm:px-6"> <th key={seriesKey} scope="col" className="min-w-40 px-4 py-4 text-left sm:px-6">
@@ -660,7 +742,7 @@ export default function Comparacao() {
<section className="shell section" aria-labelledby="docs-titulo"> <section className="shell section" aria-labelledby="docs-titulo">
<h2 id="docs-titulo" className="section-title"> <h2 id="docs-titulo" className="section-title">
{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"}
</h2> </h2>
<div className="mt-6 grid gap-3 sm:grid-cols-2 lg:grid-cols-4"> <div className="mt-6 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
{selectedSeries.map((seriesKey) => { {selectedSeries.map((seriesKey) => {
@@ -672,13 +754,13 @@ export default function Comparacao() {
</Link> </Link>
{seriesItem.datasheet ? ( {seriesItem.datasheet ? (
<a href={seriesItem.datasheet} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm"> <a href={seriesItem.datasheet} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm">
{isEs ? "Datasheet oficial" : isEn ? "Official datasheet" : isFr ? "Fiche technique officielle" : isDe ? "Offizielles Datenblatt" : "Datasheet oficial"} {isEs ? "Datasheet oficial" : isEn ? "Official datasheet" : isFr ? "Fiche technique officielle" : isZh ? "官方数据手册" : isDe ? "Offizielles Datenblatt" : "Datasheet oficial"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
<span className="sr-only"> {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)"}</span> <span className="sr-only"> {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)"}</span>
</a> </a>
) : ( ) : (
<span className="text-sm text-muted"> <span className="text-sm text-muted">
{isEs ? "Datasheet aún no publicado" : isEn ? "Datasheet not yet published" : isFr ? "Fiche technique pas encore publiée" : isDe ? "Datenblatt noch nicht veröffentlicht" : "Datasheet ainda não publicado"} {isEs ? "Datasheet aún no publicado" : isEn ? "Datasheet not yet published" : isFr ? "Fiche technique pas encore publiée" : isZh ? "数据手册尚未发布" : isDe ? "Datenblatt noch nicht veröffentlicht" : "Datasheet ainda não publicado"}
</span> </span>
)} )}
</div> </div>
@@ -689,7 +771,7 @@ export default function Comparacao() {
<div className="panel mt-10 flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center"> <div className="panel mt-10 flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center">
<div className="max-w-2xl"> <div className="max-w-2xl">
<h2 className="section-title"> <h2 className="section-title">
{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?"}
</h2> </h2>
<p className="mt-3 text-base text-muted"> <p className="mt-3 text-base text-muted">
{isEs {isEs
@@ -698,13 +780,13 @@ export default function Comparacao() {
? "Explore development boards with pinouts, peripherals, and official store links." ? "Explore development boards with pinouts, peripherals, and official store links."
: isFr : isFr
? "Découvrez les cartes de développement avec brochage, périphériques et lien vers la boutique officielle." ? "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." ? "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."} : "Veja as placas de desenvolvimento com pinagem, periféricos e link para a loja."}
</p> </p>
</div> </div>
<Link href={getLocalizedHref("/catalogo", currentLocale)} className="btn btn-primary shrink-0"> <Link href={getLocalizedHref("/catalogo", currentLocale)} className="btn btn-primary shrink-0">
{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"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
+27 -7
View File
@@ -6,6 +6,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ return paginaMeta({
titulo: "ESP Component Registry & IDF Component Manager · ESPDocs", titulo: "ESP Component Registry & IDF Component Manager · ESPDocs",
descricao: isEs 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." ? "Official interactive catalog of the ESP Component Registry: display drivers, LVGL, ESP-SR voice recognition, RainMaker, BSPs, and idf_component.yml generator."
: isFr : 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." ? "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." ? "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.", : "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", caminho: "/componentes",
@@ -55,7 +56,7 @@ export async function generateMetadata({ params }) {
"espressif esp-sr", "espressif esp-sr",
"bibliothèques ESP-IDF", "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", "ESP Component Registry",
"IDF Component Manager", "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.", 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 }) { export default async function ComponentesLayout({ children, params }) {
const { locale } = await params; const { locale } = await params;
@@ -182,8 +201,9 @@ export default async function ComponentesLayout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const faqs = isEs ? faqsEs : isEn ? faqsEn : isFr ? faqsFr : isDe ? faqsDe : faqsPt; const isZh = locale === "zh-CN";
const canonPath = isEs ? "/es/componentes" : isEn ? "/en/components" : isFr ? "/fr/composants" : isDe ? "/de/komponenten" : "/componentes"; 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 ( return (
<> <>
@@ -192,7 +212,7 @@ export default async function ComponentesLayout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ 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 }, { nome: "ESP Component Registry", caminho: canonPath },
]) ])
), ),
@@ -215,7 +235,7 @@ export default async function ComponentesLayout({ children, params }) {
? "ESP Component Registry Hub & Manifest Builder" ? "ESP Component Registry Hub & Manifest Builder"
: isFr : isFr
? "Hub de l'ESP Component Registry & Générateur de Manifeste" ? "Hub de l'ESP Component Registry & Générateur de Manifeste"
: isDe : isZh ? "ESP Component Registry 中心与清单生成器" : isDe
? "ESP Component Registry Hub & Manifest-Generator" ? "ESP Component Registry Hub & Manifest-Generator"
: "Hub do ESP Component Registry & Construtor de Manifesto", : "Hub do ESP Component Registry & Construtor de Manifesto",
descricao: isEs 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." ? "Curated catalog and idf_component.yml manifest generator for modular ESP-IDF components."
: isFr : isFr
? "Catalogue sélectionné et générateur de manifeste idf_component.yml pour les composants modulaires d'ESP-IDF." ? "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." ? "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.", : "Catálogo curado e gerador de manifesto idf_component.yml para componentes modulares do ESP-IDF.",
caminho: canonPath, caminho: canonPath,
+49 -42
View File
@@ -11,6 +11,7 @@ import * as componentesEn from "@/lib/data/en/componentes";
import * as componentesEs from "@/lib/data/es/componentes"; import * as componentesEs from "@/lib/data/es/componentes";
import * as componentesFr from "@/lib/data/fr/componentes"; import * as componentesFr from "@/lib/data/fr/componentes";
import * as componentesDe from "@/lib/data/de/componentes"; import * as componentesDe from "@/lib/data/de/componentes";
import * as componentesZh from "@/lib/data/zh-CN/componentes";
import { import {
ArrowUpRight, ArrowUpRight,
Box, Box,
@@ -50,7 +51,8 @@ export default function ComponentRegistryPage() {
const isEs = pathname?.startsWith("/es"); const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr"); const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de"); 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 [busca, setBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos"); 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.` ? `# No components selected.\n# Click "+ Add to Manifest" on the cards below.`
: isFr : isFr
? `# Aucun composant sélectionné.\n# Cliquez sur "+ Ajouter au manifeste" sur les cartes ci-dessous.` ? `# 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".` ? `# 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.`; : `# 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` ? `# main/idf_component.yml\n# Generated via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/en/components)\n\ndependencies:\n`
: isFr : isFr
? `# main/idf_component.yml\n# Généré via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/fr/composants)\n\ndependencies:\n` ? `# 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# 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`; : `# main/idf_component.yml\n# Gerado via ESPDocs Component Hub (https://espdocs.cienciaembarcada.com.br/componentes)\n\ndependencies:\n`;
selecionados.forEach((c) => { selecionados.forEach((c) => {
yaml += ` ${c.namespace}/${c.name}: "${c.versao}"\n`; yaml += ` ${c.namespace}/${c.name}: "${c.versao}"\n`;
}); });
return yaml; return yaml;
}, [componentesSelecionados, COMPONENTES_REGISTRY, isEn, isEs, isFr, isDe]); }, [componentesSelecionados, COMPONENTES_REGISTRY, isEn, isEs, isFr, isDe, isZh]);
const copiarManifesto = () => { const copiarManifesto = () => {
navigator.clipboard.writeText(manifestoYaml); navigator.clipboard.writeText(manifestoYaml);
@@ -165,8 +167,8 @@ export default function ComponentRegistryPage() {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isEs ? "Component Registry" : isEn ? "Component Registry" : isFr ? "Component Registry" : isDe ? "Component Registry" : "Component Registry" }]} trail={[{ label: isEs ? "Component Registry" : isEn ? "Component Registry" : isFr ? "Component Registry" : isZh ? "Component Registry" : isDe ? "Component Registry" : "Component Registry" }]}
icon={Box} icon={Box}
label="ESP-IDF Component Manager" label="ESP-IDF Component Manager"
title="ESP Component Registry" 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." ? "Official Espressif catalog of reusable packages for ESP-IDF. Choose drivers, voice stacks, and industrial protocols, copy commands, and generate project manifests."
: isFr : 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." ? "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." ? "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." : "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."
} }
> >
<a href="https://components.espressif.com" target="_blank" rel="noopener noreferrer" className="btn btn-primary"> <a href="https://components.espressif.com" target="_blank" rel="noopener noreferrer" className="btn btn-primary">
{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"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
<span className="sr-only"> {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)"}</span> <span className="sr-only"> {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)"}</span>
</a> </a>
<a href="#manifesto-builder" className="btn btn-ghost"> <a href="#manifesto-builder" className="btn btn-ghost">
{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"}
</a> </a>
</PageHero> </PageHero>
<section className="shell pt-10 sm:pt-12" aria-label={isEs ? "Búsqueda y filtros" : isEn ? "Search and filters" : isFr ? "Recherche et filtres" : isDe ? "Suche und Filter" : "Busca e filtros"}> <section className="shell pt-10 sm:pt-12" aria-label={isEs ? "Búsqueda y filtros" : isEn ? "Search and filters" : isFr ? "Recherche et filtres" : isZh ? "搜索与筛选" : isDe ? "Suche und Filter" : "Busca e filtros"}>
<div className="panel p-5 sm:p-6"> <div className="panel p-5 sm:p-6">
<div className="relative"> <div className="relative">
<label htmlFor="busca-componente" className="sr-only"> <label htmlFor="busca-componente" className="sr-only">
{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"}
</label> </label>
<Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" /> <Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" />
<input <input
@@ -204,14 +206,14 @@ export default function ComponentRegistryPage() {
type="search" type="search"
value={busca} value={busca}
onChange={(e) => setBusca(e.target.value)} onChange={(e) => 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" autoComplete="off"
className="field pl-12 pr-4 text-base" className="field pl-12 pr-4 text-base"
/> />
</div> </div>
<div className="mt-5"> <div className="mt-5">
<h2 className="text-sm font-semibold text-ink">{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isDe ? "Kategorie" : "Categoria"}</h2> <h2 className="text-sm font-semibold text-ink">{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isZh ? "类别" : isDe ? "Kategorie" : "Categoria"}</h2>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
{CATEGORIAS_COMPONENTES.map((cat) => { {CATEGORIAS_COMPONENTES.map((cat) => {
const Icone = ICONES_CATEGORIA[cat.id] ?? Layers; const Icone = ICONES_CATEGORIA[cat.id] ?? Layers;
@@ -219,7 +221,7 @@ export default function ComponentRegistryPage() {
return ( return (
<button key={cat.id} type="button" onClick={() => setCategoriaAtiva(cat.id)} aria-pressed={categoriaAtiva === cat.id} className="chip-btn"> <button key={cat.id} type="button" onClick={() => setCategoriaAtiva(cat.id)} aria-pressed={categoriaAtiva === cat.id} className="chip-btn">
<Icone className="h-4 w-4" aria-hidden="true" /> <Icone className="h-4 w-4" aria-hidden="true" />
{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]}
<span className="tabular-nums opacity-80">{contagem}</span> <span className="tabular-nums opacity-80">{contagem}</span>
</button> </button>
); );
@@ -228,11 +230,11 @@ export default function ComponentRegistryPage() {
</div> </div>
<div className="mt-5 border-t border-line pt-5"> <div className="mt-5 border-t border-line pt-5">
<h2 className="text-sm font-semibold text-ink">{isEs ? "Chip compatible" : isEn ? "Compatible chip" : isFr ? "Puce compatible" : isDe ? "Kompatibler Chip" : "Chip compatível"}</h2> <h2 className="text-sm font-semibold text-ink">{isEs ? "Chip compatible" : isEn ? "Compatible chip" : isFr ? "Puce compatible" : isZh ? "兼容芯片" : isDe ? "Kompatibler Chip" : "Chip compatível"}</h2>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
{chipsDisponiveis.map((chip) => ( {chipsDisponiveis.map((chip) => (
<button key={chip} type="button" onClick={() => setChipFiltro(chip)} aria-pressed={chipFiltro === chip} className="chip-btn"> <button key={chip} type="button" onClick={() => 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}
</button> </button>
))} ))}
</div> </div>
@@ -243,17 +245,17 @@ export default function ComponentRegistryPage() {
<section className="shell section pt-8 sm:pt-10" aria-labelledby="lista-titulo"> <section className="shell section pt-8 sm:pt-10" aria-labelledby="lista-titulo">
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<h2 id="lista-titulo" role="status" aria-live="polite" className="text-base font-semibold text-ink"> <h2 id="lista-titulo" role="status" aria-live="polite" className="text-base font-semibold text-ink">
{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")}
</h2> </h2>
<div className="flex flex-wrap items-center gap-x-5 gap-y-1"> <div className="flex flex-wrap items-center gap-x-5 gap-y-1">
{totalFiltros > 0 && ( {totalFiltros > 0 && (
<button type="button" onClick={limparFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong"> <button type="button" onClick={limparFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
<X className="h-4 w-4" aria-hidden="true" /> <X className="h-4 w-4" aria-hidden="true" />
{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"}
</button> </button>
)} )}
<a href="#manifesto-builder" className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong"> <a href="#manifesto-builder" className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{isEs ? "Manifiesto:" : isEn ? "Manifest:" : isFr ? "Manifeste :" : isDe ? "Manifest :" : "Manifesto:"} {componentesSelecionados.length} {componentesSelecionados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isDe ? "Komponenten" : "componentes")} {isEs ? "Manifiesto:" : isEn ? "Manifest:" : isFr ? "Manifeste :" : isZh ? "清单:" : isDe ? "Manifest :" : "Manifesto:"} {componentesSelecionados.length} {componentesSelecionados.length === 1 ? (isEs ? "componente" : isEn ? "component" : isFr ? "composant" : isZh ? "个组件" : isDe ? "Komponente" : "componente") : (isEs ? "componentes" : isEn ? "components" : isFr ? "composants" : isZh ? "个组件" : isDe ? "Komponenten" : "componentes")}
</a> </a>
</div> </div>
</div> </div>
@@ -262,7 +264,7 @@ export default function ComponentRegistryPage() {
<div className="card mt-6 px-6 py-14 text-center"> <div className="card mt-6 px-6 py-14 text-center">
<Box className="mx-auto h-10 w-10 text-muted" aria-hidden="true" /> <Box className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<h3 className="mt-4 font-display text-xl font-semibold text-ink"> <h3 className="mt-4 font-display text-xl font-semibold text-ink">
{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"}
</h3> </h3>
<p className="mx-auto mt-2 max-w-md text-sm text-muted"> <p className="mx-auto mt-2 max-w-md text-sm text-muted">
{isEs {isEs
@@ -271,12 +273,12 @@ export default function ComponentRegistryPage() {
? "Search for a more general term or return to all categories." ? "Search for a more general term or return to all categories."
: isFr : isFr
? "Recherchez un terme plus général ou revenez à toutes les catégories." ? "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." ? "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."} : "Busque por um termo mais geral ou volte para todas as categorias."}
</p> </p>
<button type="button" onClick={limparFiltros} className="btn btn-primary mt-6"> <button type="button" onClick={limparFiltros} className="btn btn-primary mt-6">
{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"}
</button> </button>
</div> </div>
) : ( ) : (
@@ -296,7 +298,7 @@ export default function ComponentRegistryPage() {
{comp.oficial && ( {comp.oficial && (
<span className="chip chip-accent px-2 py-0"> <span className="chip chip-accent px-2 py-0">
<ShieldCheck className="h-3 w-3" aria-hidden="true" /> <ShieldCheck className="h-3 w-3" aria-hidden="true" />
{isEs ? "Oficial" : isEn ? "Official" : isFr ? "Officiel" : isDe ? "Offiziell" : "Oficial"} {isEs ? "Oficial" : isEn ? "Official" : isFr ? "Officiel" : isZh ? "官方" : isDe ? "Offiziell" : "Oficial"}
</span> </span>
)} )}
</p> </p>
@@ -320,12 +322,12 @@ export default function ComponentRegistryPage() {
type="button" type="button"
onClick={() => copiarComando(comp.id, cliCmd)} 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" 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}`}
> >
<span className="truncate text-[#e9eef4]">{cliCmd}</span> <span className="truncate text-[#e9eef4]">{cliCmd}</span>
{foiCopiado ? <Check className="h-4 w-4 shrink-0 text-[#5fe0a2]" aria-hidden="true" /> : <Copy className="h-4 w-4 shrink-0 text-[#a6afbd]" aria-hidden="true" />} {foiCopiado ? <Check className="h-4 w-4 shrink-0 text-[#5fe0a2]" aria-hidden="true" /> : <Copy className="h-4 w-4 shrink-0 text-[#a6afbd]" aria-hidden="true" />}
</button> </button>
<p role="status" className="sr-only">{foiCopiado ? (isEs ? "Comando copiado" : isEn ? "Command copied" : isFr ? "Commande copiée" : isDe ? "Befehl kopiert" : "Comando copiado") : ""}</p> <p role="status" className="sr-only">{foiCopiado ? (isEs ? "Comando copiado" : isEn ? "Command copied" : isFr ? "Commande copiée" : isZh ? "命令已复制" : isDe ? "Befehl kopiert" : "Comando copiado") : ""}</p>
<p className="mt-3 text-xs text-muted"> <p className="mt-3 text-xs text-muted">
<span className="font-medium text-ink-soft">Chips: </span> <span className="font-medium text-ink-soft">Chips: </span>
@@ -341,14 +343,14 @@ export default function ComponentRegistryPage() {
className={`btn btn-sm ${selecionado ? "btn-primary" : "btn-ghost"}`} className={`btn btn-sm ${selecionado ? "btn-primary" : "btn-ghost"}`}
> >
{selecionado ? <Check className="h-4 w-4" aria-hidden="true" /> : <Plus className="h-4 w-4" aria-hidden="true" />} {selecionado ? <Check className="h-4 w-4" aria-hidden="true" /> : <Plus className="h-4 w-4" aria-hidden="true" />}
{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")}
</button> </button>
<a <a
href={comp.urlRegistry} href={comp.urlRegistry}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="btn-icon inline-flex h-11 w-11 items-center justify-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-accent" className="btn-icon inline-flex h-11 w-11 items-center justify-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-accent"
aria-label={isEs ? `Abrir ${comp.name} en el registro oficial (nueva pestaña)` : isEn ? `Open ${comp.name} in official registry (new tab)` : isFr ? `Ouvrir ${comp.name} sur le registre officiel (nouvel onglet)` : isDe ? `${comp.name} in der offiziellen Registry öffnen (neuer Tab)` : `Abrir ${comp.name} no registro oficial (nova aba)`} aria-label={isEs ? `Abrir ${comp.name} en el registro oficial (nueva pestaña)` : isEn ? `Open ${comp.name} in official registry (new tab)` : isFr ? `Ouvrir ${comp.name} sur le registre officiel (nouvel onglet)` : isZh ? `在官方注册中心打开 ${comp.name}(新标签页)` : isDe ? `${comp.name} in der offiziellen Registry öffnen (neuer Tab)` : `Abrir ${comp.name} no registro oficial (nova aba)`}
> >
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
@@ -364,9 +366,9 @@ export default function ComponentRegistryPage() {
<div className="panel p-6 sm:p-9"> <div className="panel p-6 sm:p-9">
<div className="flex flex-col justify-between gap-5 md:flex-row md:items-end"> <div className="flex flex-col justify-between gap-5 md:flex-row md:items-end">
<div className="max-w-2xl"> <div className="max-w-2xl">
<p className="eyebrow">{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : isFr ? "Générateur de manifeste" : isDe ? "Manifest-Generator" : "Gerador de manifesto"}</p> <p className="eyebrow">{isEs ? "Generador de manifiesto" : isEn ? "Manifest generator" : isFr ? "Générateur de manifeste" : isZh ? "清单生成器" : isDe ? "Manifest-Generator" : "Gerador de manifesto"}</p>
<h2 id="manifesto-titulo" className="section-title mt-3"> <h2 id="manifesto-titulo" className="section-title mt-3">
{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"}
</h2> </h2>
<p className="mt-3 text-sm leading-relaxed text-muted"> <p className="mt-3 text-sm leading-relaxed text-muted">
{isEs ? ( {isEs ? (
@@ -375,7 +377,7 @@ export default function ComponentRegistryPage() {
<>Save this file in your project&apos;s <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> folder and ESP-IDF will download all dependencies during build.</> <>Save this file in your project&apos;s <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> folder and ESP-IDF will download all dependencies during build.</>
) : isFr ? ( ) : isFr ? (
<>Enregistrez le fichier dans le dossier <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> du projet et ESP-IDF téléchargera toutes les bibliothèques lors de la compilation.</> <>Enregistrez le fichier dans le dossier <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> du projet et ESP-IDF téléchargera toutes les bibliothèques lors de la compilation.</>
) : isDe ? ( ) : isZh ? ( <>将文件保存到项目的 <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> 文件夹中,ESP-IDF 会在编译时下载所有库。</> ) : isDe ? (
<>Speichern Sie die Datei im Ordner <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> Ihres Projekts, und ESP-IDF lädt beim Build alle Abhängigkeiten herunter.</> <>Speichern Sie die Datei im Ordner <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> Ihres Projekts, und ESP-IDF lädt beim Build alle Abhängigkeiten herunter.</>
) : ( ) : (
<>Salve o arquivo na pasta <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> do projeto e o ESP-IDF baixa todas as bibliotecas na compilação.</> <>Salve o arquivo na pasta <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono">main/</code> do projeto e o ESP-IDF baixa todas as bibliotecas na compilação.</>
@@ -385,27 +387,27 @@ export default function ComponentRegistryPage() {
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<button type="button" onClick={copiarManifesto} className="btn btn-primary btn-sm"> <button type="button" onClick={copiarManifesto} className="btn btn-primary btn-sm">
{manifestoCopiado ? <Check className="h-4 w-4" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />} {manifestoCopiado ? <Check className="h-4 w-4" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
<span role="status">{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")}</span> <span role="status">{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")}</span>
</button> </button>
<button type="button" onClick={baixarManifesto} className="btn btn-ghost btn-sm"> <button type="button" onClick={baixarManifesto} className="btn btn-ghost btn-sm">
<Download className="h-4 w-4" aria-hidden="true" /> <Download className="h-4 w-4" aria-hidden="true" />
{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"}
</button> </button>
{componentesSelecionados.length > 0 && ( {componentesSelecionados.length > 0 && (
<button type="button" onClick={() => setComponentesSelecionados([])} className="btn btn-ghost btn-sm"> <button type="button" onClick={() => setComponentesSelecionados([])} className="btn btn-ghost btn-sm">
<Trash2 className="h-4 w-4" aria-hidden="true" /> <Trash2 className="h-4 w-4" aria-hidden="true" />
{isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isDe ? "Leeren" : "Limpar"} {isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isZh ? "清除" : isDe ? "Leeren" : "Limpar"}
</button> </button>
)} )}
</div> </div>
</div> </div>
<div className="code-panel mt-6"> <div className="code-panel mt-6">
<pre tabIndex={0} aria-label={isEs ? "Contenido de idf_component.yml" : isEn ? "Content of idf_component.yml" : isFr ? "Contenu de idf_component.yml" : isDe ? "Inhalt von idf_component.yml" : "Conteúdo do idf_component.yml"} className="!text-sm"><code className="text-[#e9eef4]">{manifestoYaml}</code></pre> <pre tabIndex={0} aria-label={isEs ? "Contenido de idf_component.yml" : isEn ? "Content of idf_component.yml" : isFr ? "Contenu de idf_component.yml" : isZh ? "idf_component.yml 的内容" : isDe ? "Inhalt von idf_component.yml" : "Conteúdo do idf_component.yml"} className="!text-sm"><code className="text-[#e9eef4]">{manifestoYaml}</code></pre>
</div> </div>
{componentesSelecionados.length > 0 && ( {componentesSelecionados.length > 0 && (
<ul className="mt-5 flex flex-wrap items-center gap-2" aria-label={isEs ? "Componentes incluidos" : isEn ? "Included components" : isFr ? "Composants inclus" : isDe ? "Enthaltene Komponenten" : "Componentes incluídos"}> <ul className="mt-5 flex flex-wrap items-center gap-2" aria-label={isEs ? "Componentes incluidos" : isEn ? "Included components" : isFr ? "Composants inclus" : isZh ? "已包含的组件" : isDe ? "Enthaltene Komponenten" : "Componentes incluídos"}>
{componentesSelecionados.map((id) => { {componentesSelecionados.map((id) => {
const c = COMPONENTES_REGISTRY.find((item) => item.id === id); const c = COMPONENTES_REGISTRY.find((item) => item.id === id);
if (!c) return null; if (!c) return null;
@@ -416,7 +418,7 @@ export default function ComponentRegistryPage() {
type="button" type="button"
onClick={() => toggleSelecaoManifesto(id)} onClick={() => toggleSelecaoManifesto(id)}
className="-my-2 grid h-11 w-11 place-items-center rounded-full hover:bg-accent/15" 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`}
> >
<X className="h-3.5 w-3.5" aria-hidden="true" /> <X className="h-3.5 w-3.5" aria-hidden="true" />
</button> </button>
@@ -429,7 +431,7 @@ export default function ComponentRegistryPage() {
</section> </section>
<section className="shell section border-t border-line pt-14" aria-labelledby="guia-titulo"> <section className="shell section border-t border-line pt-14" aria-labelledby="guia-titulo">
<SectionHead id="guia-titulo" eyebrow={isEs ? "Guía rápida" : isEn ? "Quick guide" : isFr ? "Guide rapide" : isDe ? "Kurzanleitung" : "Guia rápido"} title={GUIA_COMPONENT_MANAGER.titulo} lede={GUIA_COMPONENT_MANAGER.resumo} /> <SectionHead id="guia-titulo" eyebrow={isEs ? "Guía rápida" : isEn ? "Quick guide" : isFr ? "Guide rapide" : isZh ? "快速指南" : isDe ? "Kurzanleitung" : "Guia rápido"} title={GUIA_COMPONENT_MANAGER.titulo} lede={GUIA_COMPONENT_MANAGER.resumo} />
<ul className="mt-8 grid gap-3 md:grid-cols-2 lg:grid-cols-4"> <ul className="mt-8 grid gap-3 md:grid-cols-2 lg:grid-cols-4">
{GUIA_COMPONENT_MANAGER.vantagens.map((v) => ( {GUIA_COMPONENT_MANAGER.vantagens.map((v) => (
@@ -442,7 +444,7 @@ export default function ComponentRegistryPage() {
<h3 className="mt-12 flex items-center gap-2 font-display text-xl font-semibold tracking-[-0.03em] text-ink"> <h3 className="mt-12 flex items-center gap-2 font-display text-xl font-semibold tracking-[-0.03em] text-ink">
<Terminal className="h-5 w-5 text-accent" aria-hidden="true" /> <Terminal className="h-5 w-5 text-accent" aria-hidden="true" />
{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"}
</h3> </h3>
<ul className="mt-4 space-y-3"> <ul className="mt-4 space-y-3">
{GUIA_COMPONENT_MANAGER.comandosCli.map((cmd, i) => ( {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" 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}` ? <Check className="h-4 w-4 text-[#5fe0a2]" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />} {copiadoId === `cmd-${i}` ? <Check className="h-4 w-4 text-[#5fe0a2]" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
{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")}
</button> </button>
</li> </li>
))} ))}
@@ -466,7 +468,7 @@ export default function ComponentRegistryPage() {
<div className="callout mt-8"> <div className="callout mt-8">
<h3 className="flex items-center gap-2 font-semibold text-ink"> <h3 className="flex items-center gap-2 font-semibold text-ink">
<HelpCircle className="h-4 w-4 text-accent" aria-hidden="true" /> <HelpCircle className="h-4 w-4 text-accent" aria-hidden="true" />
{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"}
</h3> </h3>
<ul className="mt-2 list-disc space-y-1.5 pl-5"> <ul className="mt-2 list-disc space-y-1.5 pl-5">
{isEs ? ( {isEs ? (
@@ -490,7 +492,12 @@ export default function ComponentRegistryPage() {
<li><code>1.2.0</code>: verrouille sur cette version exacte.</li> <li><code>1.2.0</code>: verrouille sur cette version exacte.</li>
<li><code>*</code>: accepte n'importe quelle version. À éviter en production.</li> <li><code>*</code>: accepte n'importe quelle version. À éviter en production.</li>
</> </>
) : isDe ? ( ) : isZh ? ( <>
<li><code>^1.2.0</code>:接受次版本更新和修复(1.2.1、1.3.0),但会阻止重大 API 变更(2.0.0)。<strong>推荐。</strong></li>
<li><code>~1.2.0</code>:只接受缺陷修复(1.2.1、1.2.5)。</li>
<li><code>1.2.0</code>:锁定到精确版本。</li>
<li><code>*</code>:接受任意版本。生产环境请避免。</li>
</> ) : isDe ? (
<> <>
<li><code>^1.2.0</code>: akzeptiert Minor-Updates und Bugfixes (1.2.1, 1.3.0), blockiert aber Breaking Changes (2.0.0). <strong>Empfohlen.</strong></li> <li><code>^1.2.0</code>: akzeptiert Minor-Updates und Bugfixes (1.2.1, 1.3.0), blockiert aber Breaking Changes (2.0.0). <strong>Empfohlen.</strong></li>
<li><code>~1.2.0</code>: akzeptiert nur Patch-Releases (1.2.1, 1.2.5).</li> <li><code>~1.2.0</code>: akzeptiert nur Patch-Releases (1.2.1, 1.2.5).</li>
+29 -9
View File
@@ -6,6 +6,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ return paginaMeta({
titulo: isEs titulo: isEs
? "Diagnóstico de errores comunes en ESP32: Guía profesional" ? "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" ? "Common ESP32 Errors & Diagnostics: Professional Guide"
: isFr : isFr
? "Diagnostic des erreurs courantes sur ESP32 : guide professionnel" ? "Diagnostic des erreurs courantes sur ESP32 : guide professionnel"
: isDe : isZh ? "ESP32 常见故障诊断:专业指南" : isDe
? "Diagnose häufiger ESP32-Fehler: Professioneller Leitfaden" ? "Diagnose häufiger ESP32-Fehler: Professioneller Leitfaden"
: "Diagnóstico de Erros Comuns no ESP32: Guia Profissional", : "Diagnóstico de Erros Comuns no ESP32: Guia Profissional",
descricao: isEs 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)." ? "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 : 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)." ? "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)." ? "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).", : "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", caminho: "/diagnostico",
@@ -60,7 +61,7 @@ export async function generateMetadata({ params }) {
"ESP32 redémarre en boucle", "ESP32 redémarre en boucle",
"failed to connect to esp32", "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", "häufige ESP32-Fehler",
"brownout detector was triggered", "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.", 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 }) { export default async function DiagnosticoLayout({ children, params }) {
const { locale } = await params; const { locale } = await params;
@@ -185,8 +204,9 @@ export default async function DiagnosticoLayout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const faqs = isEs ? faqsEs : isEn ? faqsEn : isFr ? faqsFr : isDe ? faqsDe : faqsPt; const isZh = locale === "zh-CN";
const canonPath = isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : isFr ? "/fr/diagnostic" : isDe ? "/de/diagnose" : "/diagnostico"; 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 ( return (
<> <>
@@ -195,8 +215,8 @@ export default async function DiagnosticoLayout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ 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: 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 ? "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={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.aplicacaoWeb({ 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 descricao: isEs
? "Herramienta y guía técnica de análisis de fallas, pánicos de CPU y decodificación de registros en ESP32." ? "Herramienta y guía técnica de análisis de fallas, pánicos de CPU y decodificación de registros en ESP32."
: isEn : isEn
? "Technical guide and log decoder for troubleshooting CPU panics, crashes, and exceptions on ESP32." ? "Technical guide and log decoder for troubleshooting CPU panics, crashes, and exceptions on ESP32."
: isFr : isFr
? "Outil et guide technique d'analyse des pannes, des paniques CPU et du décodage de logs sur ESP32." ? "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." ? "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.", : "Ferramenta e guia técnico de análise de falhas, pânicos de CPU e decodificação de logs no ESP32.",
caminho: canonPath, caminho: canonPath,
+82 -44
View File
@@ -10,6 +10,7 @@ import * as diagnosticoEn from "@/lib/data/en/diagnostico";
import * as diagnosticoEs from "@/lib/data/es/diagnostico"; import * as diagnosticoEs from "@/lib/data/es/diagnostico";
import * as diagnosticoFr from "@/lib/data/fr/diagnostico"; import * as diagnosticoFr from "@/lib/data/fr/diagnostico";
import * as diagnosticoDe from "@/lib/data/de/diagnostico"; import * as diagnosticoDe from "@/lib/data/de/diagnostico";
import * as diagnosticoZh from "@/lib/data/zh-CN/diagnostico";
import { import {
Activity, Activity,
AlertTriangle, 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"], ["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"], ["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 = { const GRAVIDADE = {
"Crítica": "text-danger", "Crítica": "text-danger",
@@ -87,6 +94,9 @@ const GRAVIDADE = {
Media: "text-accent", Media: "text-accent",
Moyenne: "text-accent", Moyenne: "text-accent",
Mittel: "text-accent", Mittel: "text-accent",
"严重": "text-danger",
"高": "text-gold",
"中": "text-accent",
}; };
const CHECKLISTS_PT = [ 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() { export default function DiagnosticoPage() {
const pathname = usePathname(); const pathname = usePathname();
@@ -210,9 +242,10 @@ export default function DiagnosticoPage() {
const isEs = pathname?.startsWith("/es"); const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr"); const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de"); const isDe = pathname?.startsWith("/de");
const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEs ? diagnosticoEs : isEn ? diagnosticoEn : isFr ? diagnosticoFr : isDe ? diagnosticoDe : diagnosticoPt; const isZh = pathname?.startsWith("/zh-CN");
const exemplosLog = isEs ? EXEMPLOS_LOG_ES : isEn ? EXEMPLOS_LOG_EN : isFr ? EXEMPLOS_LOG_FR : isDe ? EXEMPLOS_LOG_DE : EXEMPLOS_LOG_PT; const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEs ? diagnosticoEs : isEn ? diagnosticoEn : isFr ? diagnosticoFr : isZh ? diagnosticoZh : isDe ? diagnosticoDe : diagnosticoPt;
const checklists = isEs ? CHECKLISTS_ES : isEn ? CHECKLISTS_EN : isFr ? CHECKLISTS_FR : isDe ? CHECKLISTS_DE : CHECKLISTS_PT; 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 [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos"); const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
@@ -269,7 +302,12 @@ export default function DiagnosticoPage() {
acaoRecomendada: acaoRecomendada:
"Recherchez des mots-clés tels que 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' ou 'panic' dans la console série.", "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", titulo: "Fehlermuster nicht automatisch kategorisiert",
tipo: "Unbekannt", tipo: "Unbekannt",
@@ -345,11 +383,11 @@ export default function DiagnosticoPage() {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isEs ? "Diagnóstico" : isEn ? "Diagnostics" : isFr ? "Diagnostic" : isDe ? "Diagnose" : "Diagnóstico" }]} trail={[{ label: isEs ? "Diagnóstico" : isEn ? "Diagnostics" : isFr ? "Diagnostic" : isZh ? "诊断" : isDe ? "Diagnose" : "Diagnóstico" }]}
icon={AlertTriangle} icon={AlertTriangle}
label={isEs ? "Depuración de firmware" : isEn ? "Firmware Debugging" : isFr ? "Débogage du firmware" : isDe ? "Firmware-Debugging" : "Depuração de firmware"} 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" : isDe ? "ESP32-Fehlerdiagnose und Crash-Decoder" : "Diagnóstico de falhas do ESP32"} 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={ lede={
isEs 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." ? "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." ? "Understand the root cause of Brownout, Guru Meditation, Watchdog, and Stack Overflow crashes, and apply engineering solutions instead of workarounds."
: isFr : 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." ? "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." ? "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." : "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" rel="noopener noreferrer"
className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong" 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"}
<ExternalLink className="h-4 w-4" aria-hidden="true" /> <ExternalLink className="h-4 w-4" aria-hidden="true" />
<span className="sr-only"> {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)"}</span> <span className="sr-only"> {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)"}</span>
</a> </a>
</PageHero> </PageHero>
@@ -379,14 +417,14 @@ export default function DiagnosticoPage() {
<div className="flex flex-col justify-between gap-4 md:flex-row md:items-end"> <div className="flex flex-col justify-between gap-4 md:flex-row md:items-end">
<div className="max-w-xl"> <div className="max-w-xl">
<h2 id="decodificador-titulo" className="section-title"> <h2 id="decodificador-titulo" className="section-title">
{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"}
</h2> </h2>
<p className="mt-2 text-sm text-muted"> <p className="mt-2 text-sm text-muted">
{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."}
</p> </p>
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-muted">{isEs ? "Probar con" : isEn ? "Test with" : isFr ? "Tester avec" : isDe ? "Testen mit" : "Testar com"}</span> <span className="text-sm text-muted">{isEs ? "Probar con" : isEn ? "Test with" : isFr ? "Tester avec" : isZh ? "试试这些" : isDe ? "Testen mit" : "Testar com"}</span>
{exemplosLog.map(([rotulo, texto]) => ( {exemplosLog.map(([rotulo, texto]) => (
<button key={rotulo} type="button" onClick={() => analisarLog(texto)} className="chip-btn"> <button key={rotulo} type="button" onClick={() => analisarLog(texto)} className="chip-btn">
{rotulo} {rotulo}
@@ -397,21 +435,21 @@ export default function DiagnosticoPage() {
<div className="relative mt-5"> <div className="relative mt-5">
<label htmlFor="log-serial" className="sr-only"> <label htmlFor="log-serial" className="sr-only">
{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"}
</label> </label>
<textarea <textarea
id="log-serial" id="log-serial"
rows={4} rows={4}
value={logInput} value={logInput}
onChange={(e) => analisarLog(e.target.value)} onChange={(e) => analisarLog(e.target.value)}
placeholder={isEs ? "Pegue el registro del monitor serie aquí" : isEn ? "Paste serial monitor log here" : isFr ? "Collez le journal du moniteur série ici" : isDe ? "Seriellen Monitor-Log hier einfügen" : "Cole o log do monitor serial aqui"} placeholder={isEs ? "Pegue el registro del monitor serie aquí" : isEn ? "Paste serial monitor log here" : isFr ? "Collez le journal du moniteur série ici" : isZh ? "在此粘贴串口监视器日志" : isDe ? "Seriellen Monitor-Log hier einfügen" : "Cole o log do monitor serial aqui"}
spellCheck={false} spellCheck={false}
className="code-panel field !min-h-32 w-full resize-y !rounded-xl !border-white/10 !bg-[var(--code-bg)] p-4 font-mono text-sm !text-[#e9eef4] placeholder:!text-[#a6afbd]" className="code-panel field !min-h-32 w-full resize-y !rounded-xl !border-white/10 !bg-[var(--code-bg)] p-4 font-mono text-sm !text-[#e9eef4] placeholder:!text-[#a6afbd]"
/> />
{logInput && ( {logInput && (
<button type="button" onClick={() => analisarLog("")} className="absolute right-3 top-3 inline-flex min-h-11 items-center gap-1 rounded-full border border-white/15 px-3 text-xs font-medium text-[#e9eef4] hover:bg-white/10"> <button type="button" onClick={() => analisarLog("")} className="absolute right-3 top-3 inline-flex min-h-11 items-center gap-1 rounded-full border border-white/15 px-3 text-xs font-medium text-[#e9eef4] hover:bg-white/10">
<X className="h-3.5 w-3.5" aria-hidden="true" /> <X className="h-3.5 w-3.5" aria-hidden="true" />
{isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isDe ? "Löschen" : "Limpar"} {isEs ? "Limpiar" : isEn ? "Clear" : isFr ? "Effacer" : isZh ? "清除" : isDe ? "Löschen" : "Limpar"}
</button> </button>
)} )}
</div> </div>
@@ -425,8 +463,8 @@ export default function DiagnosticoPage() {
<h3 className="font-semibold text-ink">{resultadoLog.titulo}</h3> <h3 className="font-semibold text-ink">{resultadoLog.titulo}</h3>
<span className="chip">{resultadoLog.tipo}</span> <span className="chip">{resultadoLog.tipo}</span>
</div> </div>
<p><strong className="font-semibold text-ink">{isEs ? "Causa probable:" : isEn ? "Likely cause:" : isFr ? "Cause probable :" : isDe ? "Wahrscheinliche Ursache :" : "Causa provável:"}</strong> {resultadoLog.explicacao}</p> <p><strong className="font-semibold text-ink">{isEs ? "Causa probable:" : isEn ? "Likely cause:" : isFr ? "Cause probable :" : isZh ? "可能的原因:" : isDe ? "Wahrscheinliche Ursache :" : "Causa provável:"}</strong> {resultadoLog.explicacao}</p>
<p><strong className="font-semibold text-ink">{isEs ? "Qué hacer:" : isEn ? "What to do:" : isFr ? "Que faire :" : isDe ? "Was zu tun ist :" : "O que fazer:"}</strong> {resultadoLog.acaoRecomendada}</p> <p><strong className="font-semibold text-ink">{isEs ? "Qué hacer:" : isEn ? "What to do:" : isFr ? "Que faire :" : isZh ? "该怎么做:" : isDe ? "Was zu tun ist :" : "O que fazer:"}</strong> {resultadoLog.acaoRecomendada}</p>
</div> </div>
</div> </div>
)} )}
@@ -436,13 +474,13 @@ export default function DiagnosticoPage() {
<section className="shell section pt-12 sm:pt-14" aria-labelledby="catalogo-titulo"> <section className="shell section pt-12 sm:pt-14" aria-labelledby="catalogo-titulo">
<h2 id="catalogo-titulo" className="section-title"> <h2 id="catalogo-titulo" className="section-title">
{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"}
</h2> </h2>
<div className="panel mt-6 p-5 sm:p-6"> <div className="panel mt-6 p-5 sm:p-6">
<div className="relative"> <div className="relative">
<label htmlFor="busca-erro" className="sr-only"> <label htmlFor="busca-erro" className="sr-only">
{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"}
</label> </label>
<Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" /> <Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" />
<input <input
@@ -451,14 +489,14 @@ export default function DiagnosticoPage() {
type="search" type="search"
value={termoBusca} value={termoBusca}
onChange={(e) => setTermoBusca(e.target.value)} onChange={(e) => 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" autoComplete="off"
className="field pl-12 pr-4 text-base" className="field pl-12 pr-4 text-base"
/> />
</div> </div>
<div className="mt-5"> <div className="mt-5">
<h3 className="text-sm font-semibold text-ink">{isEs ? "Síntoma" : isEn ? "Symptom" : isFr ? "Symptôme" : isDe ? "Symptom" : "Sintoma"}</h3> <h3 className="text-sm font-semibold text-ink">{isEs ? "Síntoma" : isEn ? "Symptom" : isFr ? "Symptôme" : isZh ? "症状" : isDe ? "Symptom" : "Sintoma"}</h3>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
{sintomasRapidos.map((sintoma) => ( {sintomasRapidos.map((sintoma) => (
<button key={sintoma.id} type="button" onClick={() => setSintomaAtivo(sintomaAtivo === sintoma.id ? null : sintoma.id)} aria-pressed={sintomaAtivo === sintoma.id} className="chip-btn"> <button key={sintoma.id} type="button" onClick={() => setSintomaAtivo(sintomaAtivo === sintoma.id ? null : sintoma.id)} aria-pressed={sintomaAtivo === sintoma.id} className="chip-btn">
@@ -469,11 +507,11 @@ export default function DiagnosticoPage() {
</div> </div>
<div className="mt-5 border-t border-line pt-5"> <div className="mt-5 border-t border-line pt-5">
<h3 className="text-sm font-semibold text-ink">{isEs ? "Área" : isEn ? "Area" : isFr ? "Domaine" : isDe ? "Bereich" : "Área"}</h3> <h3 className="text-sm font-semibold text-ink">{isEs ? "Área" : isEn ? "Area" : isFr ? "Domaine" : isZh ? "领域" : isDe ? "Bereich" : "Área"}</h3>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
<button type="button" onClick={() => setCategoriaAtiva("todos")} aria-pressed={categoriaAtiva === "todos"} className="chip-btn"> <button type="button" onClick={() => setCategoriaAtiva("todos")} aria-pressed={categoriaAtiva === "todos"} className="chip-btn">
<LayoutGrid className="h-4 w-4" aria-hidden="true" /> <LayoutGrid className="h-4 w-4" aria-hidden="true" />
{isEs ? "Todas" : isEn ? "All" : isFr ? "Tous" : isDe ? "Alle" : "Todas"} {isEs ? "Todas" : isEn ? "All" : isFr ? "Tous" : isZh ? "全部" : isDe ? "Alle" : "Todas"}
<span className="tabular-nums opacity-80">{errosCatalogo.length}</span> <span className="tabular-nums opacity-80">{errosCatalogo.length}</span>
</button> </button>
{categoriasDiagnostico.map((cat) => { {categoriasDiagnostico.map((cat) => {
@@ -493,13 +531,13 @@ export default function DiagnosticoPage() {
<div className="mt-6 flex flex-wrap items-center justify-between gap-3"> <div className="mt-6 flex flex-wrap items-center justify-between gap-3">
<p role="status" aria-live="polite" className="text-sm font-semibold text-ink"> <p role="status" aria-live="polite" className="text-sm font-semibold text-ink">
{errosFiltrados.length} {errosFiltrados.length === 1 ? (isEs ? "falla" : isEn ? "fault" : isFr ? "panne" : isDe ? "Fehler" : "falha") : (isEs ? "fallas" : isEn ? "faults" : isFr ? "pannes" : isDe ? "Fehler" : "falhas")} {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 && <span className="font-normal text-muted">{isEs ? " con los filtros actuales" : isEn ? " with current filters" : isFr ? " avec les filtres actuels" : isDe ? " mit aktuellen Filtern" : " com os filtros atuais"}</span>} {totalFiltros > 0 && <span className="font-normal text-muted">{isEs ? " con los filtros actuales" : isEn ? " with current filters" : isFr ? " avec les filtres actuels" : isZh ? "(当前筛选下)" : isDe ? " mit aktuellen Filtern" : " com os filtros atuais"}</span>}
</p> </p>
{temFiltroAtivo && ( {temFiltroAtivo && (
<button type="button" onClick={limparTodosFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong"> <button type="button" onClick={limparTodosFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
<RotateCcw className="h-4 w-4" aria-hidden="true" /> <RotateCcw className="h-4 w-4" aria-hidden="true" />
{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"}
</button> </button>
)} )}
</div> </div>
@@ -509,13 +547,13 @@ export default function DiagnosticoPage() {
<div className="card px-6 py-14 text-center"> <div className="card px-6 py-14 text-center">
<HelpCircle className="mx-auto h-10 w-10 text-muted" aria-hidden="true" /> <HelpCircle className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<h3 className="mt-4 font-display text-xl font-semibold text-ink"> <h3 className="mt-4 font-display text-xl font-semibold text-ink">
{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"}
</h3> </h3>
<p className="mx-auto mt-2 max-w-md text-sm text-muted"> <p className="mx-auto mt-2 max-w-md text-sm text-muted">
{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."}
</p> </p>
<button type="button" onClick={limparTodosFiltros} className="btn btn-primary mt-6"> <button type="button" onClick={limparTodosFiltros} className="btn btn-primary mt-6">
{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"}
</button> </button>
</div> </div>
) : ( ) : (
@@ -523,7 +561,7 @@ export default function DiagnosticoPage() {
const estaAberto = cardAberto === erro.id; const estaAberto = cardAberto === erro.id;
const lang = linguagemCodigo[erro.id] || "arduino"; const lang = linguagemCodigo[erro.id] || "arduino";
const codigoAtual = 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 catInfo = categoriasDiagnostico.find((c) => c.id === erro.categoria);
const IconeCat = ICONES[catInfo?.icone] ?? AlertTriangle; const IconeCat = ICONES[catInfo?.icone] ?? AlertTriangle;
@@ -550,7 +588,7 @@ export default function DiagnosticoPage() {
<>Severity: {erro.gravidade.toLowerCase()}</> <>Severity: {erro.gravidade.toLowerCase()}</>
) : isFr ? ( ) : isFr ? (
<>Gravité : {erro.gravidade.toLowerCase()}</> <>Gravité : {erro.gravidade.toLowerCase()}</>
) : isDe ? ( ) : isZh ? ( <>严重程度:{erro.gravidade}</> ) : isDe ? (
<>Schweregrad: {erro.gravidade.toLowerCase()}</> <>Schweregrad: {erro.gravidade.toLowerCase()}</>
) : ( ) : (
<>Gravidade {erro.gravidade.toLowerCase()}</> <>Gravidade {erro.gravidade.toLowerCase()}</>
@@ -572,11 +610,11 @@ export default function DiagnosticoPage() {
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink"> <h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
<Terminal className="h-4 w-4 text-accent" aria-hidden="true" /> <Terminal className="h-4 w-4 text-accent" aria-hidden="true" />
{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"}
</h4> </h4>
<button type="button" onClick={() => 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"> <button type="button" onClick={() => 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}` ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />} {copiadoId === `log-${erro.id}` ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
{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")}
</button> </button>
</div> </div>
<div className="code-panel mt-1"> <div className="code-panel mt-1">
@@ -587,7 +625,7 @@ export default function DiagnosticoPage() {
<div> <div>
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink"> <h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
<Cpu className="h-4 w-4 text-accent" aria-hidden="true" /> <Cpu className="h-4 w-4 text-accent" aria-hidden="true" />
{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"}
</h4> </h4>
<p className="callout mt-2">{erro.causaRaiz}</p> <p className="callout mt-2">{erro.causaRaiz}</p>
</div> </div>
@@ -596,17 +634,17 @@ export default function DiagnosticoPage() {
<div className="callout callout-danger"> <div className="callout callout-danger">
<p className="flex items-center gap-2 font-semibold text-ink"> <p className="flex items-center gap-2 font-semibold text-ink">
<XCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" /> <XCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" />
{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"}
</p> </p>
<p className="mt-2 font-medium text-ink">{erro.gambiarra.titulo}</p> <p className="mt-2 font-medium text-ink">{erro.gambiarra.titulo}</p>
<p className="mt-1">{erro.gambiarra.descricao}</p> <p className="mt-1">{erro.gambiarra.descricao}</p>
<p className="mt-2"><strong className="font-semibold text-danger">{isEs ? "Consecuencia:" : isEn ? "Consequence:" : isFr ? "Conséquence :" : isDe ? "Folge :" : "Consequência:"}</strong> {erro.gambiarra.consequencia}</p> <p className="mt-2"><strong className="font-semibold text-danger">{isEs ? "Consecuencia:" : isEn ? "Consequence:" : isFr ? "Conséquence :" : isZh ? "后果:" : isDe ? "Folge :" : "Consequência:"}</strong> {erro.gambiarra.consequencia}</p>
</div> </div>
<div className="callout callout-ok"> <div className="callout callout-ok">
<p className="flex items-center gap-2 font-semibold text-ink"> <p className="flex items-center gap-2 font-semibold text-ink">
<CheckCircle2 className="h-4 w-4 shrink-0 text-ok" aria-hidden="true" /> <CheckCircle2 className="h-4 w-4 shrink-0 text-ok" aria-hidden="true" />
{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"}
</p> </p>
<p className="mt-2 font-medium text-ink">{erro.solucaoEngenharia.titulo}</p> <p className="mt-2 font-medium text-ink">{erro.solucaoEngenharia.titulo}</p>
<ul className="mt-2 space-y-1.5"> <ul className="mt-2 space-y-1.5">
@@ -622,7 +660,7 @@ export default function DiagnosticoPage() {
<div> <div>
<div className="flex flex-wrap items-center justify-between gap-3"> <div className="flex flex-wrap items-center justify-between gap-3">
<div role="group" aria-label={isEs ? "Lenguaje del ejemplo" : isEn ? "Example language" : isFr ? "Langage de l'exemple" : isDe ? "Sprache des Beispiels" : "Linguagem do exemplo"} className="flex flex-wrap gap-2"> <div role="group" aria-label={isEs ? "Lenguaje del ejemplo" : isEn ? "Example language" : isFr ? "Langage de l'exemple" : isZh ? "示例语言" : isDe ? "Sprache des Beispiels" : "Linguagem do exemplo"} className="flex flex-wrap gap-2">
{[ {[
["arduino", "Arduino C++"], ["arduino", "Arduino C++"],
["espidf", "ESP-IDF (C)"], ["espidf", "ESP-IDF (C)"],
@@ -635,7 +673,7 @@ export default function DiagnosticoPage() {
</div> </div>
<button type="button" onClick={() => 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"> <button type="button" onClick={() => 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}` ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />} {copiadoId === `code-${erro.id}` ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
{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")}
</button> </button>
</div> </div>
<div className="code-panel mt-3"> <div className="code-panel mt-3">
@@ -645,7 +683,7 @@ export default function DiagnosticoPage() {
<div className="callout callout-warn flex items-start gap-3"> <div className="callout callout-warn flex items-start gap-3">
<Wrench className="mt-0.5 h-4 w-4 shrink-0 text-gold" aria-hidden="true" /> <Wrench className="mt-0.5 h-4 w-4 shrink-0 text-gold" aria-hidden="true" />
<p><strong className="font-semibold text-ink">{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : isFr ? "Conseil d'ingénierie :" : isDe ? "Praxistipp :" : "Dica de engenharia:"}</strong> {erro.dicaPro}</p> <p><strong className="font-semibold text-ink">{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : isFr ? "Conseil d'ingénierie :" : isZh ? "工程提示:" : isDe ? "Praxistipp :" : "Dica de engenharia:"}</strong> {erro.dicaPro}</p>
</div> </div>
</div> </div>
)} )}
@@ -658,10 +696,10 @@ export default function DiagnosticoPage() {
<section className="shell pb-20 md:pb-28" aria-labelledby="checklist-titulo"> <section className="shell pb-20 md:pb-28" aria-labelledby="checklist-titulo">
<h2 id="checklist-titulo" className="section-title"> <h2 id="checklist-titulo" className="section-title">
{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"}
</h2> </h2>
<p className="mt-3 max-w-2xl text-base text-muted"> <p className="mt-3 max-w-2xl text-base text-muted">
{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."}
</p> </p>
<div className="mt-8 grid gap-4 md:grid-cols-2"> <div className="mt-8 grid gap-4 md:grid-cols-2">
{checklists.map(({ icon: Icon, titulo, itens }) => ( {checklists.map(({ icon: Icon, titulo, itens }) => (
+34 -25
View File
@@ -10,6 +10,7 @@ import frameworksDataEn from "@/data/en/frameworks.json";
import frameworksDataEs from "@/data/es/frameworks.json"; import frameworksDataEs from "@/data/es/frameworks.json";
import frameworksDataFr from "@/data/fr/frameworks.json"; import frameworksDataFr from "@/data/fr/frameworks.json";
import frameworksDataDe from "@/data/de/frameworks.json"; import frameworksDataDe from "@/data/de/frameworks.json";
import frameworksDataZh from "@/data/zh-CN/frameworks.json";
import { getLocalizedHref } from "@/i18n/links"; import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, BookOpen, Box, Check, Code, Layers } from "lucide-react"; 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 isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isDe ? "de" : "pt"; const isZh = locale === "zh-CN";
const frameworksData = isEs ? frameworksDataEs : isEn ? frameworksDataEn : isFr ? frameworksDataFr : isDe ? frameworksDataDe : frameworksDataPt; 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]; const framework = frameworksData[key];
if (!framework) { if (!framework) {
@@ -42,7 +44,7 @@ export default async function FrameworkDetail({ params }) {
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.trilha([ __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: "Frameworks", caminho: getLocalizedHref("/frameworks", currentLocale) },
{ nome: framework.nome, caminho: getLocalizedHref(`/frameworks/${encodeURIComponent(key)}`, currentLocale) }, { nome: framework.nome, caminho: getLocalizedHref(`/frameworks/${encodeURIComponent(key)}`, currentLocale) },
])), ])),
@@ -56,8 +58,8 @@ export default async function FrameworkDetail({ params }) {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
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 }]} 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} icon={Layers}
label={framework.tipo} label={framework.tipo}
title={framework.nome} title={framework.nome}
@@ -66,41 +68,41 @@ export default async function FrameworkDetail({ params }) {
{framework.documentacao && ( {framework.documentacao && (
<a href={framework.documentacao} target="_blank" rel="noopener noreferrer" className="btn btn-primary"> <a href={framework.documentacao} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<BookOpen className="h-4 w-4" aria-hidden="true" /> <BookOpen className="h-4 w-4" aria-hidden="true" />
{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"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
)} )}
{framework.repositorio && ( {framework.repositorio && (
<a href={framework.repositorio} target="_blank" rel="noopener noreferrer" className="btn btn-ghost"> <a href={framework.repositorio} target="_blank" rel="noopener noreferrer" className="btn btn-ghost">
<Code className="h-4 w-4" aria-hidden="true" /> <Code className="h-4 w-4" aria-hidden="true" />
{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"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
)} )}
</PageHero> </PageHero>
<section className="shell mt-10" aria-label={isEs ? "Resumen" : isEn ? "Summary" : isFr ? "Résumé" : isDe ? "Übersicht" : "Resumo"}> <section className="shell mt-10" aria-label={isEs ? "Resumen" : isEn ? "Summary" : isFr ? "Résumé" : isZh ? "概览" : isDe ? "Übersicht" : "Resumo"}>
<dl className="grid gap-3 md:grid-cols-3"> <dl className="grid gap-3 md:grid-cols-3">
<div className="tile"> <div className="tile">
<dt className="text-xs font-medium text-muted">{isEs ? "Lenguaje" : isEn ? "Language" : isFr ? "Langage" : isDe ? "Sprache" : "Linguagem"}</dt> <dt className="text-xs font-medium text-muted">{isEs ? "Lenguaje" : isEn ? "Language" : isFr ? "Langage" : isZh ? "语言" : isDe ? "Sprache" : "Linguagem"}</dt>
<dd className="mt-1 font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{framework.linguagem}</dd> <dd className="mt-1 font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{framework.linguagem}</dd>
<dd className="mt-0.5 text-xs text-muted">{framework.nome_completo}</dd> <dd className="mt-0.5 text-xs text-muted">{framework.nome_completo}</dd>
</div> </div>
<div className="tile"> <div className="tile">
<dt className="text-xs font-medium text-muted">{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isDe ? "Kategorie" : "Categoria"}</dt> <dt className="text-xs font-medium text-muted">{isEs ? "Categoría" : isEn ? "Category" : isFr ? "Catégorie" : isZh ? "类别" : isDe ? "Kategorie" : "Categoria"}</dt>
<dd className="mt-1 font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{framework.tipo}</dd> <dd className="mt-1 font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{framework.tipo}</dd>
</div> </div>
<div className="tile"> <div className="tile">
<dt className="text-xs font-medium text-muted">{isEs ? "Objetivo principal" : isEn ? "Main purpose" : isFr ? "Objectif principal" : isDe ? "Hauptzweck" : "Objetivo principal"}</dt> <dt className="text-xs font-medium text-muted">{isEs ? "Objetivo principal" : isEn ? "Main purpose" : isFr ? "Objectif principal" : isZh ? "主要目标" : isDe ? "Hauptzweck" : "Objetivo principal"}</dt>
<dd className="mt-1 text-sm font-medium leading-relaxed text-ink">{framework.funcao_principal}</dd> <dd className="mt-1 text-sm font-medium leading-relaxed text-ink">{framework.funcao_principal}</dd>
</div> </div>
</dl> </dl>
</section> </section>
<section className="shell mt-12 grid gap-5 lg:grid-cols-2" aria-label={isEs ? "Funciones y aplicaciones" : isEn ? "Features and applications" : isFr ? "Fonctionnalités et applications" : isDe ? "Features und Anwendungen" : "Recursos e aplicações"}> <section className="shell mt-12 grid gap-5 lg:grid-cols-2" aria-label={isEs ? "Funciones y aplicaciones" : isEn ? "Features and applications" : isFr ? "Fonctionnalités et applications" : isZh ? "功能与应用" : isDe ? "Features und Anwendungen" : "Recursos e aplicações"}>
<div className="card p-5 sm:p-7"> <div className="card p-5 sm:p-7">
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink"> <h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
{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"}
</h2> </h2>
<ul className="mt-5 space-y-3"> <ul className="mt-5 space-y-3">
{features.map((feature) => ( {features.map((feature) => (
@@ -114,7 +116,7 @@ export default async function FrameworkDetail({ params }) {
<div className="card p-5 sm:p-7"> <div className="card p-5 sm:p-7">
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink"> <h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
{isEs ? "Cuándo usar" : isEn ? "When to use" : isFr ? "Quand l'utiliser" : isDe ? "Einsatzbereiche" : "Quando usar"} {isEs ? "Cuándo usar" : isEn ? "When to use" : isFr ? "Quand l'utiliser" : isZh ? "适用场景" : isDe ? "Einsatzbereiche" : "Quando usar"}
</h2> </h2>
<ul className="mt-5 space-y-2.5"> <ul className="mt-5 space-y-2.5">
{useCases.map((useCase) => ( {useCases.map((useCase) => (
@@ -132,10 +134,10 @@ export default async function FrameworkDetail({ params }) {
<div className="max-w-2xl"> <div className="max-w-2xl">
<span className="chip chip-accent"> <span className="chip chip-accent">
<Box className="h-3.5 w-3.5" aria-hidden="true" /> <Box className="h-3.5 w-3.5" aria-hidden="true" />
{isEs ? "ESP-IDF 4.4 o superior" : isEn ? "ESP-IDF 4.4 or higher" : isFr ? "ESP-IDF 4.4 ou supérieur" : isDe ? "ESP-IDF 4.4 oder höher" : "ESP-IDF 4.4 ou superior"} {isEs ? "ESP-IDF 4.4 o superior" : isEn ? "ESP-IDF 4.4 or higher" : isFr ? "ESP-IDF 4.4 ou supérieur" : isZh ? "ESP-IDF 4.4 或更高版本" : isDe ? "ESP-IDF 4.4 oder höher" : "ESP-IDF 4.4 ou superior"}
</span> </span>
<h2 className="section-title mt-4"> <h2 className="section-title mt-4">
{isEs ? "Módulos con ESP Component Registry" : isEn ? "Modules with the ESP Component Registry" : isFr ? "Modules avec l'ESP Component Registry" : isDe ? "Module mit der ESP Component Registry" : "Módulos com o ESP Component Registry"} {isEs ? "Módulos con ESP Component Registry" : isEn ? "Modules with the ESP Component Registry" : isFr ? "Modules avec l'ESP Component Registry" : isZh ? "借助 ESP Component Registry 使用模块" : isDe ? "Module mit der ESP Component Registry" : "Módulos com o ESP Component Registry"}
</h2> </h2>
<p className="mt-3 text-base leading-relaxed text-muted"> <p className="mt-3 text-base leading-relaxed text-muted">
{isEs ? ( {isEs ? (
@@ -156,7 +158,13 @@ export default async function FrameworkDetail({ params }) {
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> et la commande{" "} <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> et la commande{" "}
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf.py add-dependency</code> pour télécharger des pilotes d'écran (LVGL), des piles industrielles (Modbus), RainMaker et bien plus encore. <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf.py add-dependency</code> pour télécharger des pilotes d'écran (LVGL), des piles industrielles (Modbus), RainMaker et bien plus encore.
</> </>
) : isDe ? ( ) : isZh ? (
<>
在 ESP-IDF v4.4+ 和 v5.x 项目中,库不再需要以 Git 子模块的方式引入。使用{" "}
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> 和命令{" "}
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf.py add-dependency</code>,即可下载显示驱动(LVGL)、工业协议栈(Modbus)、RainMaker 等。
</>
) : isDe ? (
<> <>
In Projekten mit ESP-IDF v4.4+ und v5.x müssen Bibliotheken nicht mehr als Git-Submodule eingebunden werden. Nutzen Sie{" "} In Projekten mit ESP-IDF v4.4+ und v5.x müssen Bibliotheken nicht mehr als Git-Submodule eingebunden werden. Nutzen Sie{" "}
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> und den Befehl{" "} <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> und den Befehl{" "}
@@ -172,7 +180,7 @@ export default async function FrameworkDetail({ params }) {
</p> </p>
</div> </div>
<Link href={getLocalizedHref("/componentes", currentLocale)} className="btn btn-primary shrink-0"> <Link href={getLocalizedHref("/componentes", currentLocale)} className="btn btn-primary shrink-0">
{isEs ? "Explorar componentes" : isEn ? "Explore components" : isFr ? "Explorer les composants" : isDe ? "Komponenten erkunden" : "Explorar componentes"} {isEs ? "Explorar componentes" : isEn ? "Explore components" : isFr ? "Explorer les composants" : isZh ? "探索组件" : isDe ? "Komponenten erkunden" : "Explorar componentes"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
@@ -188,7 +196,7 @@ export default async function FrameworkDetail({ params }) {
<div className="shell section pt-10"> <div className="shell section pt-10">
<Link href={getLocalizedHref("/frameworks", currentLocale)} className="inline-flex min-h-11 items-center gap-2 text-sm font-medium text-accent hover:text-accent-strong"> <Link href={getLocalizedHref("/frameworks", currentLocale)} className="inline-flex min-h-11 items-center gap-2 text-sm font-medium text-accent hover:text-accent-strong">
<ArrowRight className="h-4 w-4 rotate-180" aria-hidden="true" /> <ArrowRight className="h-4 w-4 rotate-180" aria-hidden="true" />
{isEs ? "Ver todos los frameworks" : isEn ? "View all frameworks" : isFr ? "Voir tous les frameworks" : isDe ? "Alle Frameworks ansehen" : "Ver todos os frameworks"} {isEs ? "Ver todos los frameworks" : isEn ? "View all frameworks" : isFr ? "Voir tous les frameworks" : isZh ? "查看全部框架" : isDe ? "Alle Frameworks ansehen" : "Ver todos os frameworks"}
</Link> </Link>
</div> </div>
</main> </main>
@@ -204,24 +212,25 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isDe ? "de" : "pt"; const isZh = locale === "zh-CN";
const frameworksData = isEs ? frameworksDataEs : isEn ? frameworksDataEn : isFr ? frameworksDataFr : isDe ? frameworksDataDe : frameworksDataPt; 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]; const framework = frameworksData[key];
if (!framework) { if (!framework) {
return { title: isEs ? "Framework no encontrado" : isEn ? "Framework not found" : isFr ? "Framework introuvable" : isDe ? "Framework nicht gefunden" : "Framework não encontrado", robots: { index: false, follow: false } }; return { title: isEs ? "Framework no encontrado" : isEn ? "Framework not found" : isFr ? "Framework introuvable" : isZh ? "未找到该框架" : isDe ? "Framework nicht gefunden" : "Framework não encontrado", robots: { index: false, follow: false } };
} }
return { return {
...paginaMeta({ ...paginaMeta({
titulo: isEs ? `${framework.nome} para ESP32 · Guía técnica` : isEn ? `${framework.nome} for ESP32 · Technical Guide` : isFr ? `${framework.nome} pour ESP32 · Guide technique` : isDe ? `${framework.nome} für ESP32 · Technischer Leitfaden` : `${framework.nome} para ESP32 · Guia Técnico`, titulo: isEs ? `${framework.nome} para ESP32 · Guía técnica` : isEn ? `${framework.nome} for ESP32 · Technical Guide` : isFr ? `${framework.nome} pour ESP32 · Guide technique` : isZh ? `用于 ESP32 的 ${framework.nome}:技术指南` : isDe ? `${framework.nome} für ESP32 · Technischer Leitfaden` : `${framework.nome} para ESP32 · Guia Técnico`,
descricao: isEs descricao: isEs
? `${framework.nome_completo}: ${framework.descricao} Guía con ejemplos prácticos de código y casos de uso.` ? `${framework.nome_completo}: ${framework.descricao} Guía con ejemplos prácticos de código y casos de uso.`
: isEn : isEn
? `${framework.nome_completo}: ${framework.descricao} Guide with practical code examples and use cases.` ? `${framework.nome_completo}: ${framework.descricao} Guide with practical code examples and use cases.`
: isFr : isFr
? `${framework.nome_completo} : ${framework.descricao} Guide avec exemples pratiques de code et cas d'usage.` ? `${framework.nome_completo} : ${framework.descricao} Guide avec exemples pratiques de code et cas d'usage.`
: isDe : isZh ? `${framework.nome_completo}:${framework.descricao} 附实用代码示例与使用场景的指南。` : isDe
? `${framework.nome_completo}: ${framework.descricao} Leitfaden mit praktischen Codebeispielen und Anwendungsfällen.` ? `${framework.nome_completo}: ${framework.descricao} Leitfaden mit praktischen Codebeispielen und Anwendungsfällen.`
: `${framework.nome_completo}: ${framework.descricao} Guia com exemplos práticos de código e casos de uso.`, : `${framework.nome_completo}: ${framework.descricao} Guia com exemplos práticos de código e casos de uso.`,
caminho: `/frameworks/${encodeURIComponent(key)}`, caminho: `/frameworks/${encodeURIComponent(key)}`,
@@ -245,7 +254,7 @@ export async function generateMetadata({ params }) {
`${framework.nome} ESP32`, `programmer ESP32 avec ${framework.nome}`, `${framework.nome} ESP32`, `programmer ESP32 avec ${framework.nome}`,
"ESP32", "framework ESP32", "ESP32", "framework ESP32",
] ]
: isDe : isZh ? [ framework.nome, framework.nome_completo, `${framework.nome} ESP32`, `用 ${framework.nome} 开发 ESP32`, "ESP32", "ESP32 框架", ] : isDe
? [ ? [
framework.nome, framework.nome_completo, framework.nome, framework.nome_completo,
`${framework.nome} ESP32`, `ESP32 programmieren mit ${framework.nome}`, `${framework.nome} ESP32`, `ESP32 programmieren mit ${framework.nome}`,
+7 -5
View File
@@ -6,6 +6,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return { return {
...paginaMeta({ ...paginaMeta({
titulo: isEs titulo: isEs
@@ -14,7 +15,7 @@ export async function generateMetadata({ params }) {
? "ESP32 Frameworks & SDKs: Complete Guide and Comparison" ? "ESP32 Frameworks & SDKs: Complete Guide and Comparison"
: isFr : isFr
? "Frameworks et SDKs pour ESP32 : guide complet et comparatif" ? "Frameworks et SDKs pour ESP32 : guide complet et comparatif"
: isDe : isZh ? "ESP32 框架与 SDK:完整指南与对比" : isDe
? "ESP32-Frameworks & SDKs: Vollständiger Leitfaden und Vergleich" ? "ESP32-Frameworks & SDKs: Vollständiger Leitfaden und Vergleich"
: "Frameworks & SDKs para ESP32: Guia Completo e Comparativo", : "Frameworks & SDKs para ESP32: Guia Completo e Comparativo",
descricao: isEs descricao: isEs
@@ -23,7 +24,7 @@ export async function generateMetadata({ params }) {
? "Complete development guide: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino, and MicroPython with code examples." ? "Complete development guide: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino, and MicroPython with code examples."
: isFr : isFr
? "Guide complet de développement : ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino et MicroPython avec exemples de code." ? "Guide complet de développement : ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino et MicroPython avec exemples de code."
: isDe : isZh ? "完整开发指南:ESP-IDF、ESP-RainMaker、ESP-Matter、Rust、ESP-ADF、ESP-SR、ESP-WHO、Arduino 和 MicroPython,附代码示例。" : isDe
? "Vollständiger Entwicklungsleitfaden: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino und MicroPython mit Codebeispielen." ? "Vollständiger Entwicklungsleitfaden: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino und MicroPython mit Codebeispielen."
: "Guia completo de desenvolvimento: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino e MicroPython com exemplos de código.", : "Guia completo de desenvolvimento: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino e MicroPython com exemplos de código.",
caminho: "/frameworks", caminho: "/frameworks",
@@ -70,7 +71,7 @@ export async function generateMetadata({ params }) {
"frameworks ESP32", "frameworks ESP32",
"programmer ESP32", "programmer ESP32",
] ]
: isDe : isZh ? ["ESP-IDF", "ESP-RainMaker", "ESP-Matter", "Rust ESP32", "ESP-SR", "ESP-ADF", "ESP-IoT-Solution", "Arduino ESP32", "MicroPython ESP32", "ESP32 框架", "ESP32 编程"] : isDe
? [ ? [
"ESP-IDF", "ESP-IDF",
"ESP-RainMaker", "ESP-RainMaker",
@@ -107,6 +108,7 @@ export default async function FrameworksLayout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return ( return (
<> <>
<script <script
@@ -114,8 +116,8 @@ export default async function FrameworksLayout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ 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: isEs ? "/es/frameworks" : isEn ? "/en/frameworks" : isFr ? "/fr/frameworks" : isDe ? "/de/frameworks" : "/frameworks" }, { nome: "Frameworks", caminho: isEs ? "/es/frameworks" : isEn ? "/en/frameworks" : isFr ? "/fr/frameworks" : isZh ? "/zh-CN/frameworks" : isDe ? "/de/frameworks" : "/frameworks" },
]) ])
), ),
}} }}
+51 -15
View File
@@ -8,6 +8,7 @@ import frameworksDataEn from "@/data/en/frameworks.json";
import frameworksDataEs from "@/data/es/frameworks.json"; import frameworksDataEs from "@/data/es/frameworks.json";
import frameworksDataFr from "@/data/fr/frameworks.json"; import frameworksDataFr from "@/data/fr/frameworks.json";
import frameworksDataDe from "@/data/de/frameworks.json"; import frameworksDataDe from "@/data/de/frameworks.json";
import frameworksDataZh from "@/data/zh-CN/frameworks.json";
import { paginaMeta } from "@/lib/seo"; import { paginaMeta } from "@/lib/seo";
import { getLocalizedHref } from "@/i18n/links"; import { getLocalizedHref } from "@/i18n/links";
import { ArrowRight, ArrowUpRight, Box, Brain, Cloud, GraduationCap, Layers, Mic, Music, Sparkles } from "lucide-react"; import { ArrowRight, ArrowUpRight, Box, Brain, Cloud, GraduationCap, Layers, Mic, Music, Sparkles } from "lucide-react";
@@ -176,6 +177,38 @@ const GUIAS_DE = [
], ],
}, },
]; ];
const GUIAS_ZH = [
{
icon: GraduationCap,
titulo: "快速上手",
texto: "Arduino 或 MicroPython 是测试传感器和 API 最快的途径。量产请选择 ESP-IDF 或 Rust。",
links: [
["Arduino", "/frameworks/Arduino"],
["MicroPython", "/frameworks/MicroPython"],
["Rust", "/frameworks/Rust-ESP"],
],
},
{
icon: Cloud,
titulo: "云与智能家居",
texto: "需要互操作用 ESP-Matter,想要现成的云用 ESP-RainMaker,做低功耗本地 Mesh 网络用 Zigbee。",
links: [
["RainMaker", "/frameworks/ESP-RainMaker"],
["Matter", "/frameworks/ESP-Matter"],
["Zigbee", "/frameworks/ESP-Zigbee-SDK"],
],
},
{
icon: Sparkles,
titulo: "音频、语音与 AI",
texto: "音频处理用 ESP-ADF,离线语音识别用 ESP-SR,加速 TinyML 用 ESP-NN。",
links: [
["音频", "/frameworks/ESP-ADF", Music],
["语音", "/frameworks/ESP-SR", Mic],
["AI", "/frameworks/ESP-NN", Brain],
],
},
];
export default async function Frameworks({ params }) { export default async function Frameworks({ params }) {
const { locale } = await params; const { locale } = await params;
@@ -183,10 +216,11 @@ export default async function Frameworks({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const currentLocale = isEs ? "es" : isEn ? "en" : isFr ? "fr" : isDe ? "de" : "pt"; const isZh = locale === "zh-CN";
const frameworksData = isEs ? frameworksDataEs : isEn ? frameworksDataEn : isFr ? frameworksDataFr : isDe ? frameworksDataDe : frameworksDataPt; 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 frameworks = Object.entries(frameworksData); const frameworks = Object.entries(frameworksData);
const guias = isEs ? GUIAS_ES : isEn ? GUIAS_EN : isFr ? GUIAS_FR : isDe ? GUIAS_DE : GUIAS_PT; const guias = isEs ? GUIAS_ES : isEn ? GUIAS_EN : isFr ? GUIAS_FR : isZh ? GUIAS_ZH : isDe ? GUIAS_DE : GUIAS_PT;
return ( return (
<div className="site-canvas min-h-screen"> <div className="site-canvas min-h-screen">
@@ -197,11 +231,11 @@ export default async function Frameworks({ params }) {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isEs ? "Frameworks y SDKs" : isEn ? "Frameworks & SDKs" : isFr ? "Frameworks et SDKs" : isDe ? "Frameworks & SDKs" : "Frameworks e SDKs" }]} trail={[{ label: isEs ? "Frameworks y SDKs" : isEn ? "Frameworks & SDKs" : isFr ? "Frameworks et SDKs" : isZh ? "框架与 SDK" : isDe ? "Frameworks & SDKs" : "Frameworks e SDKs" }]}
icon={Layers} icon={Layers}
label={isEs ? `${frameworks.length} frameworks y SDKs` : isEn ? `${frameworks.length} frameworks and SDKs` : isFr ? `${frameworks.length} frameworks et SDKs` : isDe ? `${frameworks.length} Frameworks und SDKs` : `${frameworks.length} frameworks e SDKs`} label={isEs ? `${frameworks.length} frameworks y SDKs` : isEn ? `${frameworks.length} frameworks and SDKs` : isFr ? `${frameworks.length} frameworks et SDKs` : isZh ? `${frameworks.length} 个框架与 SDK` : isDe ? `${frameworks.length} Frameworks und SDKs` : `${frameworks.length} frameworks e SDKs`}
title={isEs ? "Frameworks y SDKs de ESP32" : isEn ? "ESP32 Frameworks and SDKs" : isFr ? "Frameworks et SDKs pour ESP32" : isDe ? "ESP32-Frameworks und SDKs" : "Frameworks e SDKs do ESP32"} title={isEs ? "Frameworks y SDKs de ESP32" : isEn ? "ESP32 Frameworks and SDKs" : isFr ? "Frameworks et SDKs pour ESP32" : isZh ? "ESP32 框架与 SDK" : isDe ? "ESP32-Frameworks und SDKs" : "Frameworks e SDKs do ESP32"}
lede={ lede={
isEs isEs
? "Desde C/C++ nativo con FreeRTOS hasta Rust, nube IoT, reconocimiento de voz, audio y Matter. Elija según lo que desee construir." ? "Desde C/C++ nativo con FreeRTOS hasta Rust, nube IoT, reconocimiento de voz, audio y Matter. Elija según lo que desee construir."
@@ -209,13 +243,13 @@ export default async function Frameworks({ params }) {
? "From native C/C++ with FreeRTOS to Rust, IoT cloud, speech recognition, audio, and Matter. Choose by what you want to build." ? "From native C/C++ with FreeRTOS to Rust, IoT cloud, speech recognition, audio, and Matter. Choose by what you want to build."
: isFr : isFr
? "Du C/C++ natif avec FreeRTOS à Rust, en passant par le cloud IoT, la reconnaissance vocale, l'audio et Matter. Choisissez selon ce que vous souhaitez concevoir." ? "Du C/C++ natif avec FreeRTOS à Rust, en passant par le cloud IoT, la reconnaissance vocale, l'audio et Matter. Choisissez selon ce que vous souhaitez concevoir."
: isDe : isZh ? "从原生 C/C++ 与 FreeRTOS,到 Rust,再到物联网云、语音识别、音频和 Matter。按你想构建的东西来选。" : isDe
? "Von nativem C/C++ mit FreeRTOS bis hin zu Rust, IoT-Cloud, Spracherkennung, Audio und Matter. Wählen Sie passend zu Ihrem Projekt." ? "Von nativem C/C++ mit FreeRTOS bis hin zu Rust, IoT-Cloud, Spracherkennung, Audio und Matter. Wählen Sie passend zu Ihrem Projekt."
: "Do C/C++ nativo com FreeRTOS ao Rust, passando por nuvem IoT, reconhecimento de voz, áudio e Matter. Escolha pelo que você quer construir." : "Do C/C++ nativo com FreeRTOS ao Rust, passando por nuvem IoT, reconhecimento de voz, áudio e Matter. Escolha pelo que você quer construir."
} }
/> />
<section className="shell pt-10 sm:pt-12" aria-label={isEs ? "Por dónde empezar" : isEn ? "Where to begin" : isFr ? "Par où commencer" : isDe ? "Wo anfangen" : "Por onde começar"}> <section className="shell pt-10 sm:pt-12" aria-label={isEs ? "Por dónde empezar" : isEn ? "Where to begin" : isFr ? "Par où commencer" : isZh ? "从哪里开始" : isDe ? "Wo anfangen" : "Por onde começar"}>
<div className="grid gap-4 md:grid-cols-3"> <div className="grid gap-4 md:grid-cols-3">
{guias.map(({ icon: Icon, titulo, texto, links }) => ( {guias.map(({ icon: Icon, titulo, texto, links }) => (
<div key={titulo} className="card flex flex-col p-5 sm:p-6"> <div key={titulo} className="card flex flex-col p-5 sm:p-6">
@@ -244,7 +278,7 @@ export default async function Frameworks({ params }) {
<div className="max-w-2xl"> <div className="max-w-2xl">
<span className="chip chip-accent"> <span className="chip chip-accent">
<Box className="h-3.5 w-3.5" aria-hidden="true" /> <Box className="h-3.5 w-3.5" aria-hidden="true" />
{isEs ? "Arquitectura modular" : isEn ? "Modular architecture" : isFr ? "Architecture modulaire" : isDe ? "Modulare Architektur" : "Arquitetura modular"} {isEs ? "Arquitectura modular" : isEn ? "Modular architecture" : isFr ? "Architecture modulaire" : isZh ? "模块化架构" : isDe ? "Modulare Architektur" : "Arquitetura modular"}
</span> </span>
<h2 className="section-title mt-4">ESP Component Registry</h2> <h2 className="section-title mt-4">ESP Component Registry</h2>
<p className="mt-3 text-base leading-relaxed text-muted"> <p className="mt-3 text-base leading-relaxed text-muted">
@@ -263,7 +297,9 @@ export default async function Frameworks({ params }) {
Installez des pilotes d'écran (LVGL), des piles industrielles (Modbus), des fonctionnalités cloud et des paquets de support de cartes Installez des pilotes d'écran (LVGL), des piles industrielles (Modbus), des fonctionnalités cloud et des paquets de support de cartes
(BSPs) avec une seule ligne dans votre manifeste <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code>. (BSPs) avec une seule ligne dans votre manifeste <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code>.
</> </>
) : isDe ? ( ) : isZh ? ( <>
通过 <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> 清单中的一行,即可安装显示驱动(LVGL)、工业协议栈(Modbus)、云功能和开发板包(BSP)。
</> ) : isDe ? (
<> <>
Installieren Sie Display-Treiber (LVGL), Industrieprotokolle (Modbus), Cloud-Funktionen und Board-Support-Pakete Installieren Sie Display-Treiber (LVGL), Industrieprotokolle (Modbus), Cloud-Funktionen und Board-Support-Pakete
(BSPs) mit einer einzigen Zeile in Ihrem <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code>-Manifest. (BSPs) mit einer einzigen Zeile in Ihrem <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code>-Manifest.
@@ -277,7 +313,7 @@ export default async function Frameworks({ params }) {
</p> </p>
</div> </div>
<Link href={getLocalizedHref("/componentes", currentLocale)} className="btn btn-primary shrink-0"> <Link href={getLocalizedHref("/componentes", currentLocale)} className="btn btn-primary shrink-0">
{isEs ? "Explorar componentes" : isEn ? "Explore components" : isFr ? "Explorer les composants" : isDe ? "Komponenten erkunden" : "Explorar componentes"} {isEs ? "Explorar componentes" : isEn ? "Explore components" : isFr ? "Explorer les composants" : isZh ? "探索组件" : isDe ? "Komponenten erkunden" : "Explorar componentes"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
@@ -287,8 +323,8 @@ export default async function Frameworks({ params }) {
<SectionHead <SectionHead
id="todos-titulo" id="todos-titulo"
eyebrow={isEs ? "Todos" : isEn ? "All" : "Todos"} eyebrow={isEs ? "Todos" : isEn ? "All" : "Todos"}
title={isEs ? "Todos los frameworks" : isEn ? "All frameworks" : isFr ? "Tous les frameworks" : isDe ? "Alle Frameworks" : "Todos os frameworks"} title={isEs ? "Todos los frameworks" : isEn ? "All frameworks" : isFr ? "Tous les frameworks" : isZh ? "全部框架" : isDe ? "Alle Frameworks" : "Todos os frameworks"}
lede={isEs ? "Cada ficha ofrece funciones, aplicaciones ideales, ejemplos de código y enlaces oficiales." : isEn ? "Each page provides features, ideal use cases, code examples, and official links." : isFr ? "Chaque fiche présente les fonctionnalités, les cas d'usage idéaux, des exemples de code et les liens officiels." : isDe ? "Jedes Datenblatt bietet Features, ideale Anwendungsbereiche, Codebeispiele und offizielle Links." : "Cada ficha traz recursos, aplicações ideais, exemplo de código e links oficiais."} lede={isEs ? "Cada ficha ofrece funciones, aplicaciones ideales, ejemplos de código y enlaces oficiales." : isEn ? "Each page provides features, ideal use cases, code examples, and official links." : isFr ? "Chaque fiche présente les fonctionnalités, les cas d'usage idéaux, des exemples de code et les liens officiels." : isZh ? "每张卡片包含功能、理想应用场景、代码示例和官方链接。" : isDe ? "Jedes Datenblatt bietet Features, ideale Anwendungsbereiche, Codebeispiele und offizielle Links." : "Cada ficha traz recursos, aplicações ideais, exemplo de código e links oficiais."}
/> />
<div className="mt-8 grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="mt-8 grid gap-4 md:grid-cols-2 lg:grid-cols-3">
@@ -310,7 +346,7 @@ export default async function Frameworks({ params }) {
</ul> </ul>
<span className="mt-5 inline-flex items-center gap-1 border-t border-line pt-4 text-sm font-medium text-accent"> <span className="mt-5 inline-flex items-center gap-1 border-t border-line pt-4 text-sm font-medium text-accent">
{isEs ? "Ver detalles y ejemplo" : isEn ? "View details and example" : isFr ? "Voir les détails et l'exemple" : isDe ? "Details und Beispiel ansehen" : "Ver detalhes e exemplo"} {isEs ? "Ver detalles y ejemplo" : isEn ? "View details and example" : isFr ? "Voir les détails et l'exemple" : isZh ? "查看详情与示例" : isDe ? "Details und Beispiel ansehen" : "Ver detalhes e exemplo"}
<ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
</span> </span>
</Link> </Link>
+12 -9
View File
@@ -4,6 +4,7 @@ import { categoriasGlossario as categoriasGlossarioEn } from "@/lib/data/en/glos
import { categoriasGlossario as categoriasGlossarioEs } from "@/lib/data/es/glossario.js"; import { categoriasGlossario as categoriasGlossarioEs } from "@/lib/data/es/glossario.js";
import { categoriasGlossario as categoriasGlossarioFr } from "@/lib/data/fr/glossario.js"; import { categoriasGlossario as categoriasGlossarioFr } from "@/lib/data/fr/glossario.js";
import { categoriasGlossario as categoriasGlossarioDe } from "@/lib/data/de/glossario.js"; import { categoriasGlossario as categoriasGlossarioDe } from "@/lib/data/de/glossario.js";
import { categoriasGlossario as categoriasGlossarioZh } from "@/lib/data/zh-CN/glossario.js";
export async function generateMetadata({ params }) { export async function generateMetadata({ params }) {
const { locale } = await params; const { locale } = await params;
@@ -11,6 +12,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ return paginaMeta({
titulo: isEs titulo: isEs
? "Glosario técnico de ESP32: Términos y conceptos" ? "Glosario técnico de ESP32: Términos y conceptos"
@@ -18,7 +20,7 @@ export async function generateMetadata({ params }) {
? "ESP32 Technical Glossary: Terms and Concepts" ? "ESP32 Technical Glossary: Terms and Concepts"
: isFr : isFr
? "Glossaire technique de l'ESP32 : termes et concepts" ? "Glossaire technique de l'ESP32 : termes et concepts"
: isDe : isZh ? "ESP32 技术术语表:术语与概念" : isDe
? "Technisches ESP32-Glossar: Begriffe und Konzepte" ? "Technisches ESP32-Glossar: Begriffe und Konzepte"
: "Glossário Técnico do ESP32: Termos e Conceitos", : "Glossário Técnico do ESP32: Termos e Conceitos",
descricao: isEs descricao: isEs
@@ -27,7 +29,7 @@ export async function generateMetadata({ params }) {
? "Over 50 essential ESP32 concepts explained: GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter, and Thread." ? "Over 50 essential ESP32 concepts explained: GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter, and Thread."
: isFr : isFr
? "Plus de 50 concepts essentiels de l'ESP32 expliqués : GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter et Thread." ? "Plus de 50 concepts essentiels de l'ESP32 expliqués : GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter et Thread."
: isDe : isZh ? "50 多个 ESP32 生态的核心术语与概念详解:GPIO、启动配置引脚(Strapping)、ULP、PSRAM、FreeRTOS、eFuse、Deep Sleep、OTA、Secure Boot、ECDSA、Matter 和 Thread。" : isDe
? "Über 50 wichtige ESP32-Konzepte verständlich erklärt: GPIO, Strapping-Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter und Thread." ? "Über 50 wichtige ESP32-Konzepte verständlich erklärt: GPIO, Strapping-Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter und Thread."
: "Mais de 50 termos e conceitos essenciais do ecossistema ESP32 explicados em português: GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter e Thread.", : "Mais de 50 termos e conceitos essenciais do ecossistema ESP32 explicados em português: GPIO, Strapping Pins, ULP, PSRAM, FreeRTOS, eFuse, Deep Sleep, OTA, Secure Boot, ECDSA, Matter e Thread.",
caminho: "/glossario", caminho: "/glossario",
@@ -62,7 +64,7 @@ export async function generateMetadata({ params }) {
"termes techniques microcontrôleur", "termes techniques microcontrôleur",
"concepts matériels ESP32", "concepts matériels ESP32",
] ]
: isDe : isZh ? ["ESP32 术语表", "什么是 GPIO", "什么是启动配置引脚", "什么是 ESP32 ULP", "什么是 ESP32 PSRAM", "微控制器技术术语", "ESP32 概念"] : isDe
? [ ? [
"ESP32 Glossar", "ESP32 Glossar",
"was ist GPIO", "was ist GPIO",
@@ -90,8 +92,9 @@ export default async function GlossarioLayout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const categorias = isEs ? categoriasGlossarioEs : isEn ? categoriasGlossarioEn : isFr ? categoriasGlossarioFr : isDe ? categoriasGlossarioDe : categoriasGlossarioPt; const isZh = locale === "zh-CN";
const canonPath = isEs ? "/es/glosario" : isEn ? "/en/glossary" : isFr ? "/fr/glossaire" : isDe ? "/de/glossar" : "/glossario"; const categorias = isEs ? categoriasGlossarioEs : isEn ? categoriasGlossarioEn : isFr ? categoriasGlossarioFr : isZh ? categoriasGlossarioZh : isDe ? categoriasGlossarioDe : categoriasGlossarioPt;
const canonPath = isEs ? "/es/glosario" : isEn ? "/en/glossary" : isFr ? "/fr/glossaire" : isZh ? "/zh-CN/glossary" : isDe ? "/de/glossar" : "/glossario";
return ( return (
<> <>
@@ -106,8 +109,8 @@ export default async function GlossarioLayout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ 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: isEs ? "Glosario técnico" : isEn ? "Technical Glossary" : isFr ? "Glossaire technique" : isDe ? "Technisches Glossar" : "Glossário Técnico", caminho: canonPath }, { nome: isEs ? "Glosario técnico" : isEn ? "Technical Glossary" : isFr ? "Glossaire technique" : isZh ? "技术术语表" : isDe ? "Technisches Glossar" : "Glossário Técnico", caminho: canonPath },
]) ])
), ),
}} }}
@@ -117,14 +120,14 @@ export default async function GlossarioLayout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.aplicacaoWeb({ jsonLd.aplicacaoWeb({
nome: isEs ? "Glosario técnico de ESP32" : isEn ? "ESP32 Technical Glossary" : isFr ? "Glossaire technique de l'ESP32" : isDe ? "Technisches ESP32-Glossar" : "Glossário Técnico do ESP32", nome: isEs ? "Glosario técnico de ESP32" : isEn ? "ESP32 Technical Glossary" : isFr ? "Glossaire technique de l'ESP32" : isZh ? "ESP32 技术术语表" : isDe ? "Technisches ESP32-Glossar" : "Glossário Técnico do ESP32",
descricao: isEs descricao: isEs
? "Enciclopedia de términos técnicos y conceptos de hardware y software del ecosistema ESP32." ? "Enciclopedia de términos técnicos y conceptos de hardware y software del ecosistema ESP32."
: isEn : isEn
? "Encyclopedia of hardware and software technical terms for the ESP32 ecosystem." ? "Encyclopedia of hardware and software technical terms for the ESP32 ecosystem."
: isFr : isFr
? "Encyclopédie des termes techniques et des concepts matériels et logiciels de l'écosystème ESP32." ? "Encyclopédie des termes techniques et des concepts matériels et logiciels de l'écosystème ESP32."
: isDe : isZh ? "ESP32 生态硬件与软件技术术语和概念的百科。" : isDe
? "Enzyklopädie technischer Begriffe und Hardware-/Softwarekonzepte für das ESP32-Ökosystem." ? "Enzyklopädie technischer Begriffe und Hardware-/Softwarekonzepte für das ESP32-Ökosystem."
: "Enciclopédia de termos técnicos e conceitos de hardware e software do ecossistema ESP32.", : "Enciclopédia de termos técnicos e conceitos de hardware e software do ecossistema ESP32.",
caminho: canonPath, caminho: canonPath,
+35 -30
View File
@@ -11,6 +11,7 @@ import * as glossarioEn from "@/lib/data/en/glossario";
import * as glossarioEs from "@/lib/data/es/glossario"; import * as glossarioEs from "@/lib/data/es/glossario";
import * as glossarioFr from "@/lib/data/fr/glossario"; import * as glossarioFr from "@/lib/data/fr/glossario";
import * as glossarioDe from "@/lib/data/de/glossario"; import * as glossarioDe from "@/lib/data/de/glossario";
import * as glossarioZh from "@/lib/data/zh-CN/glossario";
import { getLocalizedHref } from "@/i18n/links"; import { getLocalizedHref } from "@/i18n/links";
import { import {
ArrowRight, ArrowRight,
@@ -46,8 +47,9 @@ export default function GlossarioPage() {
const isEs = pathname?.startsWith("/es"); const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr"); const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de"); const isDe = pathname?.startsWith("/de");
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt"; const isZh = pathname?.startsWith("/zh-CN");
const { categoriasGlossario } = isDe ? glossarioDe : isFr ? glossarioFr : isEs ? glossarioEs : isEn ? glossarioEn : glossarioPt; const currentLocale = isZh ? "zh-CN" : isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const { categoriasGlossario } = isZh ? glossarioZh : isDe ? glossarioDe : isFr ? glossarioFr : isEs ? glossarioEs : isEn ? glossarioEn : glossarioPt;
const [busca, setBusca] = useState(""); const [busca, setBusca] = useState("");
const [categoriaSelecionada, setCategoriaSelecionada] = useState("todas"); const [categoriaSelecionada, setCategoriaSelecionada] = useState("todas");
@@ -117,11 +119,11 @@ export default function GlossarioPage() {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isDe ? "Glossar" : isFr ? "Glossaire" : isEs ? "Glosario" : isEn ? "Glossary" : "Glossário" }]} trail={[{ label: isZh ? "术语表" : isDe ? "Glossar" : isFr ? "Glossaire" : isEs ? "Glosario" : isEn ? "Glossary" : "Glossário" }]}
icon={BookOpen} icon={BookOpen}
label={ label={
isDe isZh ? `已解释 ${todosOsTermos.length} 个术语` : isDe
? `${todosOsTermos.length} Begriffe erklärt` ? `${todosOsTermos.length} Begriffe erklärt`
: isFr : isFr
? `${todosOsTermos.length} termes expliqués` ? `${todosOsTermos.length} termes expliqués`
@@ -132,7 +134,7 @@ export default function GlossarioPage() {
: `${todosOsTermos.length} termos explicados` : `${todosOsTermos.length} termos explicados`
} }
title={ title={
isDe isZh ? "ESP32 技术术语表" : isDe
? "Technisches Glossar für ESP32" ? "Technisches Glossar für ESP32"
: isFr : isFr
? "Glossaire technique ESP32" ? "Glossaire technique ESP32"
@@ -143,7 +145,7 @@ export default function GlossarioPage() {
: "Glossário técnico do ESP32" : "Glossário técnico do ESP32"
} }
lede={ lede={
isDe isZh ? "为生态中的每个术语提供直接定义、硬件参数和实用示例,从 ADC 到 Secure Boot。" : isDe
? "Präzise Definitionen, Hardware-Parameter und praktische Beispiele für jeden Begriff des Ökosystems – von ADC bis Secure Boot." ? "Präzise Definitionen, Hardware-Parameter und praktische Beispiele für jeden Begriff des Ökosystems – von ADC bis Secure Boot."
: isFr : isFr
? "Définitions précises, paramètres matériels et exemples pratiques pour chaque terme de l'écosystème, de l'ADC au Secure Boot." ? "Définitions précises, paramètres matériels et exemples pratiques pour chaque terme de l'écosystème, de l'ADC au Secure Boot."
@@ -155,11 +157,11 @@ export default function GlossarioPage() {
} }
/> />
<section className="shell pt-10 sm:pt-12" aria-label={isDe ? "Suche und Filter" : isFr ? "Recherche et filtres" : isEs ? "Búsqueda y filtros" : isEn ? "Search and filters" : "Busca e filtros"}> <section className="shell pt-10 sm:pt-12" aria-label={isZh ? "搜索与筛选" : isDe ? "Suche und Filter" : isFr ? "Recherche et filtres" : isEs ? "Búsqueda y filtros" : isEn ? "Search and filters" : "Busca e filtros"}>
<div className="panel p-5 sm:p-7"> <div className="panel p-5 sm:p-7">
<div className="relative"> <div className="relative">
<label htmlFor="busca-glossario" className="sr-only"> <label htmlFor="busca-glossario" className="sr-only">
{isDe ? "Nach Begriff, Abkürzung oder Konzept suchen" : isFr ? "Rechercher un terme, un sigle ou un concept" : isEs ? "Buscar término, sigla o concepto" : isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"} {isZh ? "搜索术语、缩写或概念" : isDe ? "Nach Begriff, Abkürzung oder Konzept suchen" : isFr ? "Rechercher un terme, un sigle ou un concept" : isEs ? "Buscar término, sigla o concepto" : isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"}
</label> </label>
<Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" /> <Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" />
<input <input
@@ -168,7 +170,7 @@ export default function GlossarioPage() {
type="search" type="search"
value={busca} value={busca}
onChange={(e) => setBusca(e.target.value)} onChange={(e) => setBusca(e.target.value)}
placeholder={isDe ? "Nach Begriff, Abkürzung oder Konzept suchen" : isFr ? "Rechercher un terme, un sigle ou un concept" : isEs ? "Buscar término, sigla o concepto" : isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"} placeholder={isZh ? "搜索术语、缩写或概念" : isDe ? "Nach Begriff, Abkürzung oder Konzept suchen" : isFr ? "Rechercher un terme, un sigle ou un concept" : isEs ? "Buscar término, sigla o concepto" : isEn ? "Search term, acronym, or concept" : "Buscar termo, sigla ou conceito"}
autoComplete="off" autoComplete="off"
className={`field pl-12 text-base ${busca ? "pr-28" : "pr-4"}`} className={`field pl-12 text-base ${busca ? "pr-28" : "pr-4"}`}
/> />
@@ -176,14 +178,14 @@ export default function GlossarioPage() {
{busca && ( {busca && (
<button type="button" onClick={() => { setBusca(""); searchInputRef.current?.focus(); }} className="btn btn-ghost btn-sm"> <button type="button" onClick={() => { setBusca(""); searchInputRef.current?.focus(); }} className="btn btn-ghost btn-sm">
<X className="h-3.5 w-3.5" aria-hidden="true" /> <X className="h-3.5 w-3.5" aria-hidden="true" />
{isDe ? "Löschen" : isFr ? "Effacer" : isEs ? "Limpiar" : isEn ? "Clear" : "Limpar"} {isZh ? "清除" : isDe ? "Löschen" : isFr ? "Effacer" : isEs ? "Limpiar" : isEn ? "Clear" : "Limpar"}
</button> </button>
)} )}
</div> </div>
</div> </div>
<div className="mt-4 flex flex-wrap items-center gap-2"> <div className="mt-4 flex flex-wrap items-center gap-2">
<span className="mr-1 text-sm text-muted">{isDe ? "Häufig gesucht" : isFr ? "Plus recherchés" : isEs ? "Más buscados" : isEn ? "Popular searches" : "Mais buscados"}</span> <span className="mr-1 text-sm text-muted">{isZh ? "热门搜索" : isDe ? "Häufig gesucht" : isFr ? "Plus recherchés" : isEs ? "Más buscados" : isEn ? "Popular searches" : "Mais buscados"}</span>
{BUSCAS_POPULARES.map((rotulo) => ( {BUSCAS_POPULARES.map((rotulo) => (
<button <button
key={rotulo} key={rotulo}
@@ -197,7 +199,7 @@ export default function GlossarioPage() {
</div> </div>
<div className="mt-5 border-t border-line pt-5"> <div className="mt-5 border-t border-line pt-5">
<h2 className="text-sm font-semibold text-ink">{isDe ? "Kategorie" : isFr ? "Catégorie" : isEs ? "Categoría" : isEn ? "Category" : "Categoria"}</h2> <h2 className="text-sm font-semibold text-ink">{isZh ? "类别" : isDe ? "Kategorie" : isFr ? "Catégorie" : isEs ? "Categoría" : isEn ? "Category" : "Categoria"}</h2>
<div className="mt-3 flex flex-wrap gap-2"> <div className="mt-3 flex flex-wrap gap-2">
<button <button
type="button" type="button"
@@ -206,7 +208,7 @@ export default function GlossarioPage() {
className="chip-btn" className="chip-btn"
> >
<LayoutGrid className="h-4 w-4" aria-hidden="true" /> <LayoutGrid className="h-4 w-4" aria-hidden="true" />
{isDe ? "Alle" : isFr ? "Toutes" : isEs ? "Todas" : isEn ? "All" : "Todas"} {isZh ? "全部" : isDe ? "Alle" : isFr ? "Toutes" : isEs ? "Todas" : isEn ? "All" : "Todas"}
<span className="tabular-nums opacity-80">{todosOsTermos.length}</span> <span className="tabular-nums opacity-80">{todosOsTermos.length}</span>
</button> </button>
{categoriasGlossario.map((cat) => { {categoriasGlossario.map((cat) => {
@@ -228,11 +230,13 @@ export default function GlossarioPage() {
</div> </div>
</div> </div>
{/* O filtro A–Z não faz sentido em chinês: os termos começam por ideogramas. */}
{!isZh && (
<div className="mt-6 border-t border-line pt-6"> <div className="mt-6 border-t border-line pt-6">
<h2 className="text-sm font-semibold text-ink">{isDe ? "Alphabetische Reihenfolge" : isFr ? "Ordre alphabétique" : isEs ? "Orden alfabético" : isEn ? "Alphabetical order" : "Ordem alfabética"}</h2> <h2 className="text-sm font-semibold text-ink">{isZh ? "按字母顺序" : isDe ? "Alphabetische Reihenfolge" : isFr ? "Ordre alphabétique" : isEs ? "Orden alfabético" : isEn ? "Alphabetical order" : "Ordem alfabética"}</h2>
<div className="mt-3 flex flex-wrap gap-1.5" role="group" aria-label={isDe ? "Nach Anfangsbuchstaben filtern" : isFr ? "Filtrer par lettre initiale" : isEs ? "Filtrar por letra inicial" : isEn ? "Filter by first letter" : "Filtrar pela letra inicial"}> <div className="mt-3 flex flex-wrap gap-1.5" role="group" aria-label={isZh ? "按首字母筛选" : isDe ? "Nach Anfangsbuchstaben filtern" : isFr ? "Filtrer par lettre initiale" : isEs ? "Filtrar por letra inicial" : isEn ? "Filter by first letter" : "Filtrar pela letra inicial"}>
<button type="button" onClick={() => setLetraFiltro(null)} aria-pressed={letraFiltro === null} className="chip-btn"> <button type="button" onClick={() => setLetraFiltro(null)} aria-pressed={letraFiltro === null} className="chip-btn">
{isDe ? "Alle" : isFr ? "Toutes" : isEs ? "Todas" : isEn ? "All" : "Todas"} {isZh ? "全部" : isDe ? "Alle" : isFr ? "Toutes" : isEs ? "Todas" : isEn ? "All" : "Todas"}
</button> </button>
{alfabeto.map(({ letra, temTermos }) => ( {alfabeto.map(({ letra, temTermos }) => (
<button <button
@@ -248,6 +252,7 @@ export default function GlossarioPage() {
))} ))}
</div> </div>
</div> </div>
)}
</div> </div>
</section> </section>
@@ -256,7 +261,7 @@ export default function GlossarioPage() {
<h2 id="resultado-titulo" className="text-base font-semibold text-ink" role="status" aria-live="polite"> <h2 id="resultado-titulo" className="text-base font-semibold text-ink" role="status" aria-live="polite">
{total}{" "} {total}{" "}
{total === 1 {total === 1
? isDe ? isZh ? "个术语" : isDe
? "Begriff" ? "Begriff"
: isFr : isFr
? "terme" ? "terme"
@@ -265,7 +270,7 @@ export default function GlossarioPage() {
: isEn : isEn
? "term" ? "term"
: "termo" : "termo"
: isDe : isZh ? "个术语" : isDe
? "Begriffe" ? "Begriffe"
: isFr : isFr
? "termes" ? "termes"
@@ -276,7 +281,7 @@ export default function GlossarioPage() {
: "termos"} : "termos"}
{temFiltroAtivo && ( {temFiltroAtivo && (
<span className="font-normal text-muted"> <span className="font-normal text-muted">
{isDe {isZh ? "(当前筛选下)" : isDe
? " mit den aktuellen Filtern" ? " mit den aktuellen Filtern"
: isFr : isFr
? " avec les filtres actuels" ? " avec les filtres actuels"
@@ -291,7 +296,7 @@ export default function GlossarioPage() {
{temFiltroAtivo && ( {temFiltroAtivo && (
<button type="button" onClick={limparTodosFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong"> <button type="button" onClick={limparTodosFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
<RotateCcw className="h-4 w-4" aria-hidden="true" /> <RotateCcw className="h-4 w-4" aria-hidden="true" />
{isDe ? "Filter zurücksetzen" : isFr ? "Réinitialiser les filtres" : isEs ? "Restablecer filtros" : isEn ? "Reset filters" : "Redefinir filtros"} {isZh ? "重置筛选" : isDe ? "Filter zurücksetzen" : isFr ? "Réinitialiser les filtres" : isEs ? "Restablecer filtros" : isEn ? "Reset filters" : "Redefinir filtros"}
</button> </button>
)} )}
</div> </div>
@@ -300,10 +305,10 @@ export default function GlossarioPage() {
<div className="card mt-6 px-6 py-14 text-center"> <div className="card mt-6 px-6 py-14 text-center">
<HelpCircle className="mx-auto h-10 w-10 text-muted" aria-hidden="true" /> <HelpCircle className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<h3 className="mt-4 font-display text-xl font-semibold text-ink"> <h3 className="mt-4 font-display text-xl font-semibold text-ink">
{isDe ? "Keine Begriffe gefunden" : isFr ? "Aucun terme trouvé" : isEs ? "No se encontraron términos" : isEn ? "No terms found" : "Nenhum termo encontrado"} {isZh ? "未找到术语" : isDe ? "Keine Begriffe gefunden" : isFr ? "Aucun terme trouvé" : isEs ? "No se encontraron términos" : isEn ? "No terms found" : "Nenhum termo encontrado"}
</h3> </h3>
<p className="mx-auto mt-2 max-w-md text-sm text-muted"> <p className="mx-auto mt-2 max-w-md text-sm text-muted">
{isDe {isZh ? "没有任何术语符合当前的搜索和筛选。请尝试更短的缩写,或重置为全部类别。" : isDe
? "Nichts entspricht Ihrer aktuellen Suche und den Filtern. Versuchen Sie eine kürzere Abkürzung oder wählen Sie alle Kategorien." ? "Nichts entspricht Ihrer aktuellen Suche und den Filtern. Versuchen Sie eine kürzere Abkürzung oder wählen Sie alle Kategorien."
: isFr : isFr
? "Rien ne correspond à votre recherche et à vos filtres actuels. Essayez avec un sigle plus court ou revenez à toutes les catégories." ? "Rien ne correspond à votre recherche et à vos filtres actuels. Essayez avec un sigle plus court ou revenez à toutes les catégories."
@@ -315,7 +320,7 @@ export default function GlossarioPage() {
</p> </p>
<button type="button" onClick={limparTodosFiltros} className="btn btn-primary mt-6"> <button type="button" onClick={limparTodosFiltros} className="btn btn-primary mt-6">
<RotateCcw className="h-4 w-4" aria-hidden="true" /> <RotateCcw className="h-4 w-4" aria-hidden="true" />
{isDe ? "Filter zurücksetzen" : isFr ? "Réinitialiser les filtres" : isEs ? "Limpiar filtros" : isEn ? "Clear filters" : "Limpar filtros"} {isZh ? "清除筛选" : isDe ? "Filter zurücksetzen" : isFr ? "Réinitialiser les filtres" : isEs ? "Limpiar filtros" : isEn ? "Clear filters" : "Limpar filtros"}
</button> </button>
</div> </div>
) : ( ) : (
@@ -333,7 +338,7 @@ export default function GlossarioPage() {
type="button" type="button"
onClick={() => copiarLinkTermo(item.slug)} onClick={() => copiarLinkTermo(item.slug)}
aria-label={ aria-label={
isDe isZh ? `复制 ${item.termo} 的链接` : isDe
? `Link für ${item.termo} kopieren` ? `Link für ${item.termo} kopieren`
: isFr : isFr
? `Copier le lien pour ${item.termo}` ? `Copier le lien pour ${item.termo}`
@@ -357,7 +362,7 @@ export default function GlossarioPage() {
<div className="callout mt-5"> <div className="callout mt-5">
<p className="flex items-center gap-1.5 text-sm font-semibold text-ink"> <p className="flex items-center gap-1.5 text-sm font-semibold text-ink">
<Lightbulb className="h-4 w-4 text-accent" aria-hidden="true" /> <Lightbulb className="h-4 w-4 text-accent" aria-hidden="true" />
{isDe ? "In der Praxis" : isFr ? "En pratique" : isEs ? "En la práctica" : isEn ? "In practice" : "Na prática"} {isZh ? "实际应用" : isDe ? "In der Praxis" : isFr ? "En pratique" : isEs ? "En la práctica" : isEn ? "In practice" : "Na prática"}
</p> </p>
<p className="mt-1"> <p className="mt-1">
<TextoComLinks texto={item.exemplo} currentLocale={currentLocale} /> <TextoComLinks texto={item.exemplo} currentLocale={currentLocale} />
@@ -373,10 +378,10 @@ export default function GlossarioPage() {
<section className="shell pb-20 md:pb-28"> <section className="shell pb-20 md:pb-28">
<div className="panel p-8 text-center sm:p-12"> <div className="panel p-8 text-center sm:p-12">
<h2 className="section-title mx-auto max-w-2xl"> <h2 className="section-title mx-auto max-w-2xl">
{isDe ? "Vom Konzept zum richtigen Chip." : isFr ? "Du concept à la bonne puce." : isEs ? "Del concepto al chip adecuado." : isEn ? "From concept to the right chip." : "Do conceito ao chip certo."} {isZh ? "从概念到合适的芯片。" : isDe ? "Vom Konzept zum richtigen Chip." : isFr ? "Du concept à la bonne puce." : isEs ? "Del concepto al chip adecuado." : isEn ? "From concept to the right chip." : "Do conceito ao chip certo."}
</h2> </h2>
<p className="lede mx-auto mt-4 max-w-2xl"> <p className="lede mx-auto mt-4 max-w-2xl">
{isDe {isZh ? "掌握这些术语后,可以并排对比各系列,或回答选型问题,找到适合你项目的型号。" : isDe
? "Mit den Begriffen zur Hand vergleichen Sie die Serien Seite an Seite oder nutzen Sie die Auswahlhilfe, um das Modell für Ihr Projekt zu finden." ? "Mit den Begriffen zur Hand vergleichen Sie die Serien Seite an Seite oder nutzen Sie die Auswahlhilfe, um das Modell für Ihr Projekt zu finden."
: isFr : isFr
? "Avec les termes sous la main, comparez les séries côte à côte ou répondez au sélecteur pour trouver le modèle adapté à votre projet." ? "Avec les termes sous la main, comparez les séries côte à côte ou répondez au sélecteur pour trouver le modèle adapté à votre projet."
@@ -389,10 +394,10 @@ export default function GlossarioPage() {
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row"> <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Link href={getLocalizedHref("/seletor", currentLocale)} className="btn btn-primary w-full sm:w-auto"> <Link href={getLocalizedHref("/seletor", currentLocale)} className="btn btn-primary w-full sm:w-auto">
<Sliders className="h-4 w-4" aria-hidden="true" /> <Sliders className="h-4 w-4" aria-hidden="true" />
{isDe ? "Meinen Chip wählen" : isFr ? "Choisir ma puce" : isEs ? "Elegir mi chip" : isEn ? "Choose my chip" : "Escolher meu chip"} {isZh ? "选择我的芯片" : isDe ? "Meinen Chip wählen" : isFr ? "Choisir ma puce" : isEs ? "Elegir mi chip" : isEn ? "Choose my chip" : "Escolher meu chip"}
</Link> </Link>
<Link href={getLocalizedHref("/series", currentLocale)} className="btn btn-ghost w-full sm:w-auto"> <Link href={getLocalizedHref("/series", currentLocale)} className="btn btn-ghost w-full sm:w-auto">
{isDe ? "Serien erkunden" : isFr ? "Explorer les séries" : isEs ? "Explorar las series" : isEn ? "Explore series" : "Explorar as séries"} {isZh ? "浏览系列" : isDe ? "Serien erkunden" : isFr ? "Explorer les séries" : isEs ? "Explorar las series" : isEn ? "Explore series" : "Explorar as séries"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
+6 -5
View File
@@ -48,6 +48,7 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
const ogLocaleMap = { const ogLocaleMap = {
pt: "pt_BR", pt: "pt_BR",
@@ -66,7 +67,7 @@ export async function generateMetadata({ params }) {
? "ESPDocs · Documentación abierta de ESP32" ? "ESPDocs · Documentación abierta de ESP32"
: isFr : isFr
? "ESPDocs · Documentation ouverte ESP32" ? "ESPDocs · Documentation ouverte ESP32"
: isDe : isZh ? "ESPDocs · ESP32 中文文档" : isDe
? "ESPDocs · Offene ESP32-Dokumentation" ? "ESPDocs · Offene ESP32-Dokumentation"
: "ESPDocs · Documentação do ESP32 em português"; : "ESPDocs · Documentação do ESP32 em português";
@@ -76,7 +77,7 @@ export async function generateMetadata({ params }) {
? "Especificaciones, patillaje, comparación y selección de las 12 series de microcontroladores ESP32. Hojas de datos, periféricos, seguridad en hardware y diagramas de conexión." ? "Especificaciones, patillaje, comparación y selección de las 12 series de microcontroladores ESP32. Hojas de datos, periféricos, seguridad en hardware y diagramas de conexión."
: isFr : isFr
? "Spécifications, brochages, comparaison et sélection des 12 séries de microcontrôleurs ESP32. Fiches techniques, périphériques, sécurité matérielle et schémas de connexion." ? "Spécifications, brochages, comparaison et sélection des 12 séries de microcontrôleurs ESP32. Fiches techniques, périphériques, sécurité matérielle et schémas de connexion."
: isDe : isZh ? "ESP32 全部 12 个系列微控制器的规格、引脚、对比与选型,使用简体中文。含数据手册、外设、硬件安全与连接图。" : isDe
? "Spezifikationen, Pinbelegungen, Vergleich und Auswahl der 12 ESP32-Mikrocontrollerserien. Datenblätter, Peripheriegeräte, Hardwaresicherheit und Anschlussdiagramme." ? "Spezifikationen, Pinbelegungen, Vergleich und Auswahl der 12 ESP32-Mikrocontrollerserien. Datenblätter, Peripheriegeräte, Hardwaresicherheit und Anschlussdiagramme."
: "Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro. Datasheets, periféricos, segurança em hardware e diagramas de conexão."; : "Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro. Datasheets, periféricos, segurança em hardware e diagramas de conexão.";
@@ -101,7 +102,7 @@ export async function generateMetadata({ params }) {
"comparer ESP32", "quel ESP32 choisir", "datasheet ESP32", "fiche technique ESP32", "comparer ESP32", "quel ESP32 choisir", "datasheet ESP32", "fiche technique ESP32",
"microcontrôleur", "Espressif", "ESP-IDF", "IoT", "systèmes embarqués", "microcontrôleur", "Espressif", "ESP-IDF", "IoT", "systèmes embarqués",
] ]
: isDe : isZh ? ["ESP32", "ESP32 中文", "ESP32 文档", "ESP32 引脚图", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4", "ESP32-H2", "ESP32 对比", "ESP32 怎么选", "ESP32 数据手册", "微控制器", "乐鑫", "ESP-IDF", "物联网", "嵌入式系统"] : isDe
? [ ? [
"ESP32", "ESP32 auf Deutsch", "ESP32 Dokumentation", "ESP32 Pinbelegung", "ESP32 Pinout", "ESP32", "ESP32 auf Deutsch", "ESP32 Dokumentation", "ESP32 Pinbelegung", "ESP32 Pinout",
"ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4", "ESP32-H2", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4", "ESP32-H2",
@@ -152,7 +153,7 @@ export async function generateMetadata({ params }) {
? "Especificaciones, patillaje, comparación y selección de las 12 series de microcontroladores ESP32." ? "Especificaciones, patillaje, comparación y selección de las 12 series de microcontroladores ESP32."
: isFr : isFr
? "Spécifications, brochages, comparaison et sélection des 12 séries de microcontrôleurs ESP32." ? "Spécifications, brochages, comparaison et sélection des 12 séries de microcontrôleurs ESP32."
: isDe : isZh ? "ESP32 全部 12 个系列微控制器的规格、引脚、对比与选型,使用简体中文。" : isDe
? "Spezifikationen, Pinbelegungen, Vergleich und Auswahl der 12 ESP32-Mikrocontrollerserien." ? "Spezifikationen, Pinbelegungen, Vergleich und Auswahl der 12 ESP32-Mikrocontrollerserien."
: "Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro.", : "Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro.",
}, },
@@ -165,7 +166,7 @@ export async function generateMetadata({ params }) {
? "Especificaciones, patillaje, comparación y selección de las 12 series de ESP32." ? "Especificaciones, patillaje, comparación y selección de las 12 series de ESP32."
: isFr : isFr
? "Spécifications, brochages, comparaison et sélection des 12 séries ESP32." ? "Spécifications, brochages, comparaison et sélection des 12 séries ESP32."
: isDe : isZh ? "ESP32 全部 12 个系列的规格、引脚、对比与选型,使用简体中文。" : isDe
? "Spezifikationen, Pinbelegungen, Vergleich und Auswahl der 12 ESP32-Serien." ? "Spezifikationen, Pinbelegungen, Vergleich und Auswahl der 12 ESP32-Serien."
: "Especificações, pinagem, comparação e seleção das 12 séries de ESP32, em português.", : "Especificações, pinagem, comparação e seleção das 12 séries de ESP32, em português.",
}, },
+13 -6
View File
@@ -44,6 +44,12 @@ const CAMINHOS_DE = [
{ href: "/comparacao", icon: Scale, titulo: "Vergleich", texto: "Bis zu vier Serien im direkten Vergleich." }, { href: "/comparacao", icon: Scale, titulo: "Vergleich", texto: "Bis zu vier Serien im direkten Vergleich." },
{ href: "/glossario", icon: BookOpen, titulo: "Glossar", texto: "Hardware-Begriffe verständlich erklärt." }, { href: "/glossario", icon: BookOpen, titulo: "Glossar", texto: "Hardware-Begriffe verständlich erklärt." },
]; ];
const CAMINHOS_ZH = [
{ href: "/series", icon: Compass, titulo: "ESP32 系列", texto: "12 个系列,含引脚与规格。" },
{ href: "/seletor", icon: SlidersHorizontal, titulo: "芯片选型", texto: "回答八个问题,获得推荐型号。" },
{ href: "/comparacao", icon: Scale, titulo: "对比工具", texto: "最多四个系列并排对比。" },
{ href: "/glossario", icon: BookOpen, titulo: "术语表", texto: "清晰解释硬件术语。" },
];
export default async function NotFound() { export default async function NotFound() {
let locale = "pt"; let locale = "pt";
@@ -56,7 +62,8 @@ export default async function NotFound() {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const caminhos = isEn ? CAMINHOS_EN : isEs ? CAMINHOS_ES : isFr ? CAMINHOS_FR : isDe ? CAMINHOS_DE : CAMINHOS_PT; const isZh = locale === "zh-CN";
const caminhos = isEn ? CAMINHOS_EN : isEs ? CAMINHOS_ES : isFr ? CAMINHOS_FR : isZh ? CAMINHOS_ZH : isDe ? CAMINHOS_DE : CAMINHOS_PT;
return ( return (
<div className="site-canvas min-h-screen"> <div className="site-canvas min-h-screen">
@@ -64,8 +71,8 @@ export default async function NotFound() {
<main id="conteudo" className="shell section"> <main id="conteudo" className="shell section">
<div className="mx-auto max-w-2xl text-center"> <div className="mx-auto max-w-2xl text-center">
<p className="eyebrow justify-center">{isEn ? "Error 404" : isFr ? "Erreur 404" : isDe ? "Fehler 404" : "Error 404"}</p> <p className="eyebrow justify-center">{isEn ? "Error 404" : isFr ? "Erreur 404" : isZh ? "错误 404" : isDe ? "Fehler 404" : "Error 404"}</p>
<h1 className="page-title mt-4">{isEn ? "This page does not exist." : isEs ? "Esta página no existe." : isFr ? "Cette page n'existe pas." : isDe ? "Diese Seite existiert nicht." : "Essa página não existe."}</h1> <h1 className="page-title mt-4">{isEn ? "This page does not exist." : isEs ? "Esta página no existe." : isFr ? "Cette page n'existe pas." : isZh ? "该页面不存在。" : isDe ? "Diese Seite existiert nicht." : "Essa página não existe."}</h1>
<p className="lede mx-auto mt-5 max-w-xl"> <p className="lede mx-auto mt-5 max-w-xl">
{isEn {isEn
? "The address may have changed or was mistyped. Choose one of these paths to continue." ? "The address may have changed or was mistyped. Choose one of these paths to continue."
@@ -73,13 +80,13 @@ export default async function NotFound() {
? "La dirección puede haber cambiado o estar mal escrita. Elija uno de estos caminos para continuar." ? "La dirección puede haber cambiado o estar mal escrita. Elija uno de estos caminos para continuar."
: isFr : isFr
? "L'adresse a peut-être changé ou a été mal saisie. Choisissez l'un de ces chemins pour continuer." ? "L'adresse a peut-être changé ou a été mal saisie. Choisissez l'un de ces chemins pour continuer."
: isDe : isZh ? "地址可能已更改或输入有误。请从以下入口继续浏览。" : isDe
? "Die Adresse wurde möglicherweise geändert oder falsch eingegeben. Wählen Sie einen dieser Wege, um fortzufahren." ? "Die Adresse wurde möglicherweise geändert oder falsch eingegeben. Wählen Sie einen dieser Wege, um fortzufahren."
: "O endereço pode ter mudado ou estar digitado errado. Escolha um destes caminhos para continuar."} : "O endereço pode ter mudado ou estar digitado errado. Escolha um destes caminhos para continuar."}
</p> </p>
<div className="mt-8 flex justify-center"> <div className="mt-8 flex justify-center">
<Link href={isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/"} className="btn btn-primary"> <Link href={isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/"} className="btn btn-primary">
{isEn ? "Back to home" : isEs ? "Volver al inicio" : isFr ? "Retour à l'accueil" : isDe ? "Zur Startseite" : "Voltar ao início"} {isEn ? "Back to home" : isEs ? "Volver al inicio" : isFr ? "Retour à l'accueil" : isZh ? "返回首页" : isDe ? "Zur Startseite" : "Voltar ao início"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
+103 -29
View File
@@ -11,7 +11,9 @@ import frameworksDataEs from "@/data/es/frameworks.json";
import seriesDataFr from "@/data/fr/series.json"; import seriesDataFr from "@/data/fr/series.json";
import frameworksDataFr from "@/data/fr/frameworks.json"; import frameworksDataFr from "@/data/fr/frameworks.json";
import seriesDataDe from "@/data/de/series.json"; import seriesDataDe from "@/data/de/series.json";
import seriesDataZh from "@/data/zh-CN/series.json";
import frameworksDataDe from "@/data/de/frameworks.json"; import frameworksDataDe from "@/data/de/frameworks.json";
import frameworksDataZh from "@/data/zh-CN/frameworks.json";
import { SITE } from "@/lib/seo"; import { SITE } from "@/lib/seo";
import { getLocalizedHref } from "@/i18n/links"; import { getLocalizedHref } from "@/i18n/links";
import { import {
@@ -252,6 +254,50 @@ const TOOLS_DE = [
action: "Boards ansehen", action: "Boards ansehen",
}, },
]; ];
const TOOLS_ZH = [
{
href: "/seletor",
title: "智能选型",
desc: "回答八个关于项目的问题,获得推荐芯片及其技术依据。",
icon: SlidersHorizontal,
action: "找到我的芯片",
},
{
href: "/comparacao",
title: "系列对比",
desc: "最多把四个型号并排放在一起,对比内核、无线、内存、外设和安全。",
icon: Scale,
action: "立即对比",
},
{
href: "/diagnostico",
title: "故障诊断",
desc: "针对欠压、启动循环、Guru Meditation、看门狗和闪存错误的根因与解决办法。",
icon: AlertTriangle,
action: "打开诊断",
},
{
href: "/componentes",
title: "Component Registry",
desc: "来自乐鑫官方注册中心的驱动、库和 BSP,附带可直接使用的清单。",
icon: Box,
action: "探索组件",
},
{
href: "/frameworks",
title: "框架与 SDK",
desc: "ESP-IDF、Arduino、Rust、Matter、MicroPython,以及围绕芯片构建的工具。",
icon: Terminal,
action: "查看框架",
},
{
href: "/catalogo",
title: "开发板目录",
desc: "真实的 DevKit 与模组,含 USB 接口、板载传感器和直达商店的链接。",
icon: Cpu,
action: "查看开发板",
},
];
const ERRORS_PT = [ const ERRORS_PT = [
{ {
@@ -367,6 +413,28 @@ const ERRORS_DE = [
fix: "3,3-V-Schiene, Boot-Pull-ups und Flash-Konfiguration in esptool überprüfen.", fix: "3,3-V-Schiene, Boot-Pull-ups und Flash-Konfiguration in esptool überprüfen.",
}, },
]; ];
const ERRORS_ZH = [
{
code: "Brownout detector was triggered",
cause: "射频启动时电压跌落。",
fix: "在芯片附近加 10–100 µF 电容,并使用电流足够的 5 V 电源。",
},
{
code: "Guru Meditation Error",
cause: "无效指针、栈溢出,或 ISR 使用不当。",
fix: "用 addr2line 查看回溯信息,并合理设置 FreeRTOS 任务的栈大小。",
},
{
code: "Wi-Fi 开启时 ADC2 不可用",
cause: "射频控制器工作时会占用 ADC2。",
fix: "与 Wi-Fi 同时使用模拟传感器时,请用 ADC1 引脚。",
},
{
code: "Flash Read err / bootloader",
cause: "闪存电压/模式不正确,或启动时启动配置引脚被拉到错误电平。",
fix: "检查 3.3 V 电源轨、启动上拉,以及 esptool 中的闪存配置。",
},
];
export default async function Home({ params }) { export default async function Home({ params }) {
const { locale } = await params; const { locale } = await params;
@@ -375,12 +443,13 @@ export default async function Home({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
const getLinkHref = (href) => getLocalizedHref(`/${locale}${href}`, locale); const getLinkHref = (href) => getLocalizedHref(`/${locale}${href}`, locale);
const seriesData = isPt ? seriesDataPt : isEs ? seriesDataEs : isFr ? seriesDataFr : isDe ? seriesDataDe : seriesDataEn; const seriesData = isPt ? seriesDataPt : isEs ? seriesDataEs : isFr ? seriesDataFr : isZh ? seriesDataZh : isDe ? seriesDataDe : seriesDataEn;
const frameworksData = isPt ? frameworksDataPt : isEs ? frameworksDataEs : isFr ? frameworksDataFr : isDe ? frameworksDataDe : frameworksDataEn; const frameworksData = isPt ? frameworksDataPt : isEs ? frameworksDataEs : isFr ? frameworksDataFr : isZh ? frameworksDataZh : isDe ? frameworksDataDe : frameworksDataEn;
const tools = isPt ? TOOLS_PT : isEs ? TOOLS_ES : isFr ? TOOLS_FR : isDe ? TOOLS_DE : TOOLS_EN; const tools = isPt ? TOOLS_PT : isEs ? TOOLS_ES : isFr ? TOOLS_FR : isZh ? TOOLS_ZH : isDe ? TOOLS_DE : TOOLS_EN;
const errors = isPt ? ERRORS_PT : isEs ? ERRORS_ES : isFr ? ERRORS_FR : isDe ? ERRORS_DE : ERRORS_EN; const errors = isPt ? ERRORS_PT : isEs ? ERRORS_ES : isFr ? ERRORS_FR : isZh ? ERRORS_ZH : isDe ? ERRORS_DE : ERRORS_EN;
const seriesCount = Object.keys(seriesData).length; const seriesCount = Object.keys(seriesData).length;
const frameworkCount = Object.keys(frameworksData).length; const frameworkCount = Object.keys(frameworksData).length;
@@ -395,7 +464,7 @@ export default async function Home({ params }) {
<div className="mx-auto max-w-4xl text-center"> <div className="mx-auto max-w-4xl text-center">
<span className="chip chip-accent"> <span className="chip chip-accent">
<Cpu className="h-3.5 w-3.5" aria-hidden="true" /> <Cpu className="h-3.5 w-3.5" aria-hidden="true" />
{isPt ? "Documentação aberta do ESP32" : isEs ? "Documentación abierta de ESP32" : isFr ? "Documentation ouverte de l'ESP32" : isDe ? "Offene ESP32-Dokumentation" : "Open ESP32 documentation"} {isPt ? "Documentação aberta do ESP32" : isEs ? "Documentación abierta de ESP32" : isFr ? "Documentation ouverte de l'ESP32" : isZh ? "ESP32 开放文档" : isDe ? "Offene ESP32-Dokumentation" : "Open ESP32 documentation"}
</span> </span>
<h1 className="display mt-6 text-[clamp(2.9rem,7.4vw,5.4rem)]"> <h1 className="display mt-6 text-[clamp(2.9rem,7.4vw,5.4rem)]">
{isPt ? ( {isPt ? (
@@ -413,7 +482,9 @@ export default async function Home({ params }) {
L'ESP32, L'ESP32,
<br className="sm:hidden" /> sans bruit. <br className="sm:hidden" /> sans bruit.
</> </>
) : isDe ? ( ) : isZh ? ( <>
ESP32,<br className="sm:hidden" />去繁就简。
</> ) : isDe ? (
<> <>
Der ESP32, Der ESP32,
<br className="sm:hidden" /> ohne Umwege. <br className="sm:hidden" /> ohne Umwege.
@@ -432,7 +503,7 @@ export default async function Home({ params }) {
? "Especificaciones, patillaje, comparaciones y diagnóstico en una sola lectura. Menos traducción de hojas de datos, más tiempo con el prototipo en el banco de trabajo." ? "Especificaciones, patillaje, comparaciones y diagnóstico en una sola lectura. Menos traducción de hojas de datos, más tiempo con el prototipo en el banco de trabajo."
: isFr : isFr
? "Spécifications, brochage, comparaisons et diagnostics en une seule lecture. Moins de traduction de fiches techniques, plus de temps à l'établi." ? "Spécifications, brochage, comparaisons et diagnostics en une seule lecture. Moins de traduction de fiches techniques, plus de temps à l'établi."
: isDe : isZh ? "规格、引脚、对比与故障诊断,一次读完。少啃数据手册,多花时间在工作台上做原型。" : isDe
? "Spezifikationen, Pinbelegung, Vergleiche und Diagnose auf einen Blick. Weniger Datenblatt-Übersetzung, mehr Zeit am Prüfplatz." ? "Spezifikationen, Pinbelegung, Vergleiche und Diagnose auf einen Blick. Weniger Datenblatt-Übersetzung, mehr Zeit am Prüfplatz."
: "Specifications, pinouts, comparisons and diagnostics in a single read. Less datasheet translation, more workbench time."} : "Specifications, pinouts, comparisons and diagnostics in a single read. Less datasheet translation, more workbench time."}
</p> </p>
@@ -440,7 +511,7 @@ export default async function Home({ params }) {
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row"> <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Link href={getLinkHref("/seletor")} className="btn btn-primary w-full sm:w-auto"> <Link href={getLinkHref("/seletor")} className="btn btn-primary w-full sm:w-auto">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" /> <SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{isPt ? "Escolher meu chip" : isEs ? "Elegir mi chip" : isFr ? "Choisir ma puce" : isDe ? "Meinen Chip wählen" : "Choose my chip"} {isPt ? "Escolher meu chip" : isEs ? "Elegir mi chip" : isFr ? "Choisir ma puce" : isZh ? "选择我的芯片" : isDe ? "Meinen Chip wählen" : "Choose my chip"}
</Link> </Link>
<Link href={getLinkHref("/series")} className="btn btn-ghost w-full sm:w-auto"> <Link href={getLinkHref("/series")} className="btn btn-ghost w-full sm:w-auto">
{isPt ? ( {isPt ? (
@@ -449,7 +520,7 @@ export default async function Home({ params }) {
<>Explorar las {seriesCount} series</> <>Explorar las {seriesCount} series</>
) : isFr ? ( ) : isFr ? (
<>Explorer les {seriesCount} séries</> <>Explorer les {seriesCount} séries</>
) : isDe ? ( ) : isZh ? ( <>浏览全部 {seriesCount} 个系列</> ) : isDe ? (
<>Die {seriesCount} Serien erkunden</> <>Die {seriesCount} Serien erkunden</>
) : ( ) : (
<>Explore the {seriesCount} series</> <>Explore the {seriesCount} series</>
@@ -459,9 +530,9 @@ export default async function Home({ params }) {
</div> </div>
<p className="mt-7 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-sm text-muted"> <p className="mt-7 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-sm text-muted">
<span><strong className="font-semibold text-ink">{seriesCount}</strong> {isPt ? "séries" : isEs ? "series" : isFr ? "séries" : isDe ? "Serien" : "series"}</span> <span><strong className="font-semibold text-ink">{seriesCount}</strong> {isPt ? "séries" : isEs ? "series" : isFr ? "séries" : isZh ? "个系列" : isDe ? "Serien" : "series"}</span>
<span aria-hidden="true" className="text-line-strong">·</span> <span aria-hidden="true" className="text-line-strong">·</span>
<span><strong className="font-semibold text-ink">{frameworkCount}</strong> frameworks</span> <span><strong className="font-semibold text-ink">{frameworkCount}</strong> {isZh ? "个框架" : isDe ? "Frameworks" : "frameworks"}</span>
{isPt ? ( {isPt ? (
<> <>
<span aria-hidden="true" className="text-line-strong">·</span> <span aria-hidden="true" className="text-line-strong">·</span>
@@ -479,7 +550,10 @@ export default async function Home({ params }) {
<span aria-hidden="true" className="text-line-strong">·</span> <span aria-hidden="true" className="text-line-strong">·</span>
<span>100 % en français</span> <span>100 % en français</span>
</> </>
) : isDe ? ( ) : isZh ? ( <>
<span aria-hidden="true" className="text-line-strong">·</span>
<span>100% 中文</span>
</> ) : isDe ? (
<> <>
<span aria-hidden="true" className="text-line-strong">·</span> <span aria-hidden="true" className="text-line-strong">·</span>
<span>100 % auf Deutsch</span> <span>100 % auf Deutsch</span>
@@ -496,13 +570,13 @@ export default async function Home({ params }) {
<div className="panel mt-12 overflow-hidden sm:mt-16"> <div className="panel mt-12 overflow-hidden sm:mt-16">
<div className="flex flex-wrap items-center justify-between gap-3 px-5 py-4 sm:px-7"> <div className="flex flex-wrap items-center justify-between gap-3 px-5 py-4 sm:px-7">
<div> <div>
<h2 className="text-sm font-semibold text-ink">{isPt ? "Séries em destaque" : isEs ? "Series destacadas" : isFr ? "Séries en vedette" : isDe ? "Vorgestellte Serien" : "Featured series"}</h2> <h2 className="text-sm font-semibold text-ink">{isPt ? "Séries em destaque" : isEs ? "Series destacadas" : isFr ? "Séries en vedette" : isZh ? "精选系列" : isDe ? "Vorgestellte Serien" : "Featured series"}</h2>
<p className="mt-0.5 text-xs text-muted"> <p className="mt-0.5 text-xs text-muted">
{isPt ? "O essencial de cada família, antes de abrir o datasheet." : isEs ? "Lo esencial de cada familia, antes de abrir la hoja de datos." : isFr ? "L'essentiel de chaque famille, avant d'ouvrir la fiche technique." : isDe ? "Das Wesentliche jeder Familie, bevor Sie das Datenblatt öffnen." : "The essentials of each family before opening the datasheet."} {isPt ? "O essencial de cada família, antes de abrir o datasheet." : isEs ? "Lo esencial de cada familia, antes de abrir la hoja de datos." : isFr ? "L'essentiel de chaque famille, avant d'ouvrir la fiche technique." : isZh ? "打开数据手册之前,先看每个系列的要点。" : isDe ? "Das Wesentliche jeder Familie, bevor Sie das Datenblatt öffnen." : "The essentials of each family before opening the datasheet."}
</p> </p>
</div> </div>
<Link href={getLinkHref("/series")} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent transition-colors hover:text-accent-strong"> <Link href={getLinkHref("/series")} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent transition-colors hover:text-accent-strong">
{isPt ? "Índice completo" : isEs ? "Índice completo" : isFr ? "Index complet" : isDe ? "Vollständiger Index" : "Full index"} {isPt ? "Índice completo" : isEs ? "Índice completo" : isFr ? "Index complet" : isZh ? "完整索引" : isDe ? "Vollständiger Index" : "Full index"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
@@ -527,7 +601,7 @@ export default async function Home({ params }) {
<div className="mt-5 flex items-center justify-between gap-3 border-t border-line pt-3.5"> <div className="mt-5 flex items-center justify-between gap-3 border-t border-line pt-3.5">
<span className="truncate font-mono text-[0.6875rem] text-ink-soft">{serie.frequencia}</span> <span className="truncate font-mono text-[0.6875rem] text-ink-soft">{serie.frequencia}</span>
<span className="inline-flex shrink-0 items-center gap-1 text-xs font-medium text-accent"> <span className="inline-flex shrink-0 items-center gap-1 text-xs font-medium text-accent">
{isPt ? "Ficha" : isEs ? "Ficha" : isFr ? "Fiche" : isDe ? "Datenblatt" : "Spec sheet"} {isPt ? "Ficha" : isEs ? "Ficha" : isFr ? "Fiche" : isZh ? "规格表" : isDe ? "Datenblatt" : "Spec sheet"}
<ArrowUpRight className="h-3.5 w-3.5 transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" /> <ArrowUpRight className="h-3.5 w-3.5 transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
</span> </span>
</div> </div>
@@ -608,9 +682,9 @@ export default async function Home({ params }) {
<section className="shell section" aria-labelledby="ferramentas-titulo"> <section className="shell section" aria-labelledby="ferramentas-titulo">
<div className="mx-auto max-w-2xl text-center"> <div className="mx-auto max-w-2xl text-center">
<p className="eyebrow">{isPt ? "Ferramentas" : isEs ? "Herramientas" : isFr ? "Outils" : isDe ? "Werkzeuge" : "Tools"}</p> <p className="eyebrow">{isPt ? "Ferramentas" : isEs ? "Herramientas" : isFr ? "Outils" : isZh ? "工具" : isDe ? "Werkzeuge" : "Tools"}</p>
<h2 id="ferramentas-titulo" className="display mt-4 text-[clamp(2rem,4.8vw,3.4rem)]"> <h2 id="ferramentas-titulo" className="display mt-4 text-[clamp(2rem,4.8vw,3.4rem)]">
{isPt ? "Comece pelo problema." : isEs ? "Empiece por el problema." : isFr ? "Commencez par le problème." : isDe ? "Beginnen Sie beim Problem." : "Start with the problem."} {isPt ? "Comece pelo problema." : isEs ? "Empiece por el problema." : isFr ? "Commencez par le problème." : isZh ? "从问题出发。" : isDe ? "Beginnen Sie beim Problem." : "Start with the problem."}
</h2> </h2>
<p className="lede mt-4"> <p className="lede mt-4">
{isPt {isPt
@@ -619,7 +693,7 @@ export default async function Home({ params }) {
? "Cada herramienta resuelve una fase concreta del proyecto, sin registros ni rodeos." ? "Cada herramienta resuelve una fase concreta del proyecto, sin registros ni rodeos."
: isFr : isFr
? "Chaque outil répond à une étape concrète du projet, sans inscription ni détour." ? "Chaque outil répond à une étape concrète du projet, sans inscription ni détour."
: isDe : isZh ? "每个工具对应开发中的一个具体阶段,无需注册,没有废话。" : isDe
? "Jedes Werkzeug löst einen konkreten Projektschritt – ohne Registrierung und ohne Umwege." ? "Jedes Werkzeug löst einen konkreten Projektschritt – ohne Registrierung und ohne Umwege."
: "Each tool addresses a concrete development phase, with no sign-ups and zero fluff."} : "Each tool addresses a concrete development phase, with no sign-ups and zero fluff."}
</p> </p>
@@ -648,9 +722,9 @@ export default async function Home({ params }) {
<section className="shell section pt-0" aria-labelledby="diagnostico-titulo"> <section className="shell section pt-0" aria-labelledby="diagnostico-titulo">
<div className="grid items-start gap-10 lg:grid-cols-[0.78fr_1.22fr] lg:gap-16"> <div className="grid items-start gap-10 lg:grid-cols-[0.78fr_1.22fr] lg:gap-16">
<div className="lg:sticky lg:top-28"> <div className="lg:sticky lg:top-28">
<p className="eyebrow">{isPt ? "Diagnóstico" : isEs ? "Diagnóstico" : isFr ? "Diagnostic" : isDe ? "Diagnose" : "Diagnostics"}</p> <p className="eyebrow">{isPt ? "Diagnóstico" : isEs ? "Diagnóstico" : isFr ? "Diagnostic" : isZh ? "故障诊断" : isDe ? "Diagnose" : "Diagnostics"}</p>
<h2 id="diagnostico-titulo" className="display mt-4 text-[clamp(2rem,4.8vw,3.4rem)]"> <h2 id="diagnostico-titulo" className="display mt-4 text-[clamp(2rem,4.8vw,3.4rem)]">
{isPt ? "O primeiro boot não precisa virar noite." : isEs ? "El primer arranque no tiene por qué desvelarlo." : isFr ? "Le premier démarrage n'a pas à vous gâcher la nuit." : isDe ? "Der erste Boot muss nicht die Nacht kosten." : "First boot doesn't have to take all night."} {isPt ? "O primeiro boot não precisa virar noite." : isEs ? "El primer arranque no tiene por qué desvelarlo." : isFr ? "Le premier démarrage n'a pas à vous gâcher la nuit." : isZh ? "第一次启动,不必折腾一整晚。" : isDe ? "Der erste Boot muss nicht die Nacht kosten." : "First boot doesn't have to take all night."}
</h2> </h2>
<p className="lede mt-4"> <p className="lede mt-4">
{isPt {isPt
@@ -659,13 +733,13 @@ export default async function Home({ params }) {
? "Los errores más comunes tienen una causa conocida. Esta guía parte del mensaje en el terminal y llega al ajuste de hardware o firmware." ? "Los errores más comunes tienen una causa conocida. Esta guía parte del mensaje en el terminal y llega al ajuste de hardware o firmware."
: isFr : isFr
? "Les erreurs les plus courantes ont une cause connue. Ce guide part du message dans le terminal pour arriver au réglage matériel ou logiciel." ? "Les erreurs les plus courantes ont une cause connue. Ce guide part du message dans le terminal pour arriver au réglage matériel ou logiciel."
: isDe : isZh ? "常见错误都有已知的原因。本指南从串口终端日志出发,引导你找到硬件或固件上的调整办法。" : isDe
? "Die häufigsten Fehler haben bekannte Ursachen. Dieser Leitfaden geht von der Terminalmeldung aus und führt zur passenden Hardware- oder Firmware-Anpassung." ? "Die häufigsten Fehler haben bekannte Ursachen. Dieser Leitfaden geht von der Terminalmeldung aus und führt zur passenden Hardware- oder Firmware-Anpassung."
: "Common errors have known causes. This guide starts with the serial terminal log and leads to the hardware or firmware adjustment."} : "Common errors have known causes. This guide starts with the serial terminal log and leads to the hardware or firmware adjustment."}
</p> </p>
<Link href={getLinkHref("/diagnostico")} className="btn btn-accent mt-7"> <Link href={getLinkHref("/diagnostico")} className="btn btn-accent mt-7">
<AlertTriangle className="h-4 w-4" aria-hidden="true" /> <AlertTriangle className="h-4 w-4" aria-hidden="true" />
{isPt ? "Abrir os 30+ diagnósticos" : isEs ? "Abrir los 30+ diagnósticos" : isFr ? "Ouvrir les 30+ diagnostics" : isDe ? "Die 30+ Diagnosen öffnen" : "Open the 30+ diagnostics"} {isPt ? "Abrir os 30+ diagnósticos" : isEs ? "Abrir los 30+ diagnósticos" : isFr ? "Ouvrir les 30+ diagnostics" : isZh ? "查看 30 多项诊断" : isDe ? "Die 30+ Diagnosen öffnen" : "Open the 30+ diagnostics"}
</Link> </Link>
</div> </div>
@@ -675,7 +749,7 @@ export default async function Home({ params }) {
<Terminal className="h-4 w-4" aria-hidden="true" /> <Terminal className="h-4 w-4" aria-hidden="true" />
boot.log boot.log
</span> </span>
<span className="text-xs text-[#a6afbd]">{isPt ? "4 exemplos de 30+" : isEs ? "4 ejemplos de 30+" : isFr ? "4 exemples sur 30+" : isDe ? "4 Beispiele von 30+" : "4 examples of 30+"}</span> <span className="text-xs text-[#a6afbd]">{isPt ? "4 exemplos de 30+" : isEs ? "4 ejemplos de 30+" : isFr ? "4 exemples sur 30+" : isZh ? "30 多项中的 4 个示例" : isDe ? "4 Beispiele von 30+" : "4 examples of 30+"}</span>
</div> </div>
<div className="divide-y divide-white/10"> <div className="divide-y divide-white/10">
{errors.map((item) => ( {errors.map((item) => (
@@ -692,9 +766,9 @@ export default async function Home({ params }) {
<section className="shell pb-20 pt-4 md:pb-28"> <section className="shell pb-20 pt-4 md:pb-28">
<div className="mx-auto max-w-3xl text-center"> <div className="mx-auto max-w-3xl text-center">
<p className="eyebrow">{isPt ? "Curadoria independente" : isEs ? "Curaduría independiente" : isFr ? "Sélection indépendante" : isDe ? "Unabhängige Kuration" : "Independent curation"}</p> <p className="eyebrow">{isPt ? "Curadoria independente" : isEs ? "Curaduría independiente" : isFr ? "Sélection indépendante" : isZh ? "独立整理" : isDe ? "Unabhängige Kuration" : "Independent curation"}</p>
<h2 className="display mt-4 text-[clamp(2rem,4.5vw,3.2rem)]"> <h2 className="display mt-4 text-[clamp(2rem,4.5vw,3.2rem)]">
{isPt ? "Feito para consultar no meio do projeto." : isEs ? "Diseñado para consultar en mitad del proyecto." : isFr ? "Conçu pour être consulté en plein projet." : isDe ? "Gemacht für das Nachschlagen mitten im Projekt." : "Built for quick lookup mid-project."} {isPt ? "Feito para consultar no meio do projeto." : isEs ? "Diseñado para consultar en mitad del proyecto." : isFr ? "Conçu pour être consulté en plein projet." : isZh ? "为项目途中的快速查阅而做。" : isDe ? "Gemacht für das Nachschlagen mitten im Projekt." : "Built for quick lookup mid-project."}
</h2> </h2>
<p className="lede mx-auto mt-4 max-w-2xl"> <p className="lede mx-auto mt-4 max-w-2xl">
{isPt {isPt
@@ -703,17 +777,17 @@ export default async function Home({ params }) {
? "ESPDocs organiza datos públicos de Espressif y apunta siempre a la fuente oficial. Para decisiones críticas, la hoja de datos y el Technical Reference Manual continúan teniendo la última palabra." ? "ESPDocs organiza datos públicos de Espressif y apunta siempre a la fuente oficial. Para decisiones críticas, la hoja de datos y el Technical Reference Manual continúan teniendo la última palabra."
: isFr : isFr
? "ESPDocs organise les données publiques d'Espressif et renvoie systématiquement vers la source officielle. Pour les décisions critiques, la fiche technique et le Technical Reference Manual restent la référence définitive." ? "ESPDocs organise les données publiques d'Espressif et renvoie systématiquement vers la source officielle. Pour les décisions critiques, la fiche technique et le Technical Reference Manual restent la référence définitive."
: isDe : isZh ? "ESPDocs 整理乐鑫的公开数据,并直接指向官方资料来源。对于关键的工程决策,数据手册和技术参考手册仍是最终依据。" : isDe
? "ESPDocs strukturiert öffentliche Espressif-Daten und verweist stets auf die offizielle Quelle. Für kritische Entscheidungen bleiben das Datenblatt und das Technical Reference Manual maßgeblich." ? "ESPDocs strukturiert öffentliche Espressif-Daten und verweist stets auf die offizielle Quelle. Für kritische Entscheidungen bleiben das Datenblatt und das Technical Reference Manual maßgeblich."
: "ESPDocs organizes public Espressif data and points directly to official sources. For critical engineering decisions, the datasheet and Technical Reference Manual remain the definitive authority."} : "ESPDocs organizes public Espressif data and points directly to official sources. For critical engineering decisions, the datasheet and Technical Reference Manual remain the definitive authority."}
</p> </p>
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row"> <div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Link href={getLinkHref("/glossario")} className="btn btn-ghost w-full sm:w-auto"> <Link href={getLinkHref("/glossario")} className="btn btn-ghost w-full sm:w-auto">
<BookOpen className="h-4 w-4" aria-hidden="true" /> <BookOpen className="h-4 w-4" aria-hidden="true" />
{isPt ? "Glossário" : isEs ? "Glosario" : isFr ? "Glossaire" : isDe ? "Glossar" : "Glossary"} {isPt ? "Glossário" : isEs ? "Glosario" : isFr ? "Glossaire" : isZh ? "术语表" : isDe ? "Glossar" : "Glossary"}
</Link> </Link>
<Link href={getLinkHref("/sobre")} className="btn btn-primary w-full sm:w-auto"> <Link href={getLinkHref("/sobre")} className="btn btn-primary w-full sm:w-auto">
{isPt ? "Sobre o projeto" : isEs ? "Acerca del proyecto" : isFr ? "À propos du projet" : isDe ? "Über das Projekt" : "About the project"} {isPt ? "Sobre o projeto" : isEs ? "Acerca del proyecto" : isFr ? "À propos du projet" : isZh ? "关于本项目" : isDe ? "Über das Projekt" : "About the project"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
+31 -11
View File
@@ -6,8 +6,9 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ return paginaMeta({
titulo: isDe titulo: isZh ? "该选哪款 ESP32" : isDe
? "Welchen ESP32 wählen" ? "Welchen ESP32 wählen"
: isFr : isFr
? "Quel ESP32 choisir" ? "Quel ESP32 choisir"
@@ -16,7 +17,7 @@ export async function generateMetadata({ params }) {
: isEn : isEn
? "Which ESP32 to Choose" ? "Which ESP32 to Choose"
: "Qual ESP32 escolher", : "Qual ESP32 escolher",
descricao: isDe descricao: isZh ? "回答几个关于连接(Wi-Fi 6、5GHz、Matter)、功耗、外设和安全的简单问题,或使用参数筛选器,找到最合适的 ESP32。" : isDe
? "Beantworten Sie einfache Fragen zu Konnektivität (Wi-Fi 6, 5 GHz, Matter), Energie, Peripherie und Sicherheit oder nutzen Sie den parametrischen Filter, um den perfekten ESP32 zu finden." ? "Beantworten Sie einfache Fragen zu Konnektivität (Wi-Fi 6, 5 GHz, Matter), Energie, Peripherie und Sicherheit oder nutzen Sie den parametrischen Filter, um den perfekten ESP32 zu finden."
: isFr : isFr
? "Répondez à des questions simples sur la connectivité (Wi-Fi 6, 5 GHz, Matter), l'énergie, les périphériques et la sécurité ou utilisez le filtre paramétrique pour trouver l'ESP32 parfait." ? "Répondez à des questions simples sur la connectivité (Wi-Fi 6, 5 GHz, Matter), l'énergie, les périphériques et la sécurité ou utilisez le filtre paramétrique pour trouver l'ESP32 parfait."
@@ -27,7 +28,7 @@ export async function generateMetadata({ params }) {
: "Responda a perguntas simples sobre conectividade (Wi-Fi 6, 5GHz, Matter), energia, periféricos e segurança ou use o filtro paramétrico para encontrar o ESP32 perfeito.", : "Responda a perguntas simples sobre conectividade (Wi-Fi 6, 5GHz, Matter), energia, periféricos e segurança ou use o filtro paramétrico para encontrar o ESP32 perfeito.",
caminho: "/seletor", caminho: "/seletor",
locale, locale,
keywords: isDe keywords: isZh ? ["该选哪款 ESP32", "该用哪款 ESP32", "微控制器选型", "适合我项目的 ESP32", "ESP32 对比"] : isDe
? ["welchen ESP32 wählen", "welchen ESP32 verwenden", "Mikrocontroller auswählen", "ESP32 für mein Projekt", "ESP32 vergleichen"] ? ["welchen ESP32 wählen", "welchen ESP32 verwenden", "Mikrocontroller auswählen", "ESP32 für mein Projekt", "ESP32 vergleichen"]
: isFr : isFr
? ["quel ESP32 choisir", "quel ESP32 utiliser", "choisir microcontrôleur", "ESP32 pour mon projet", "comparer ESP32"] ? ["quel ESP32 choisir", "quel ESP32 utiliser", "choisir microcontrôleur", "ESP32 pour mon projet", "comparer ESP32"]
@@ -133,6 +134,24 @@ const faqsDe = [
resposta: "Kompakte RISC-V-Mikrocontroller mit ULP-Coprozessor wie der ESP32-C2, ESP32-C3 und ESP32-H2 (dediziert für 802.15.4 ohne Wi-Fi-Funk) bieten extrem niedrige Deep-Sleep-Ströme im Bereich von 5 µA bis 10 µA.", resposta: "Kompakte RISC-V-Mikrocontroller mit ULP-Coprozessor wie der ESP32-C2, ESP32-C3 und ESP32-H2 (dediziert für 802.15.4 ohne Wi-Fi-Funk) bieten extrem niedrige Deep-Sleep-Ströme im Bereich von 5 µA bis 10 µA.",
}, },
]; ];
const faqsZh = [
{
pergunta: "新手该选哪款 ESP32?",
resposta: "对刚入门物联网的创客和开发者来说,经典 ESP32(NodeMCU/DevKitC)或 ESP32-C3 是首选:库支持丰富,Arduino IDE 示例现成,文档也很完整。",
},
{
pergunta: "哪款 ESP32 支持 Wi-Fi 6、蓝牙 6 以及 Thread/Zigbee/Matter?",
resposta: "ESP32-C6 和 ESP32-C5 是面向现代网络的主力系列。ESP32-C6 提供 2.4 GHz Wi-Fi 6 和 802.15.4 射频(Zigbee 3.0 / Thread / Matter)。ESP32-C5 则首次在 2.4 GHz 和 5 GHz 两个频段同时支持双频 Wi-Fi 6。",
},
{
pergunta: "做 AI(TinyML)和显示屏,哪款 ESP32 性能最强?",
resposta: "ESP32-P4(400 MHz 双核 RISC-V,带 AI 向量扩展、MIPI-CSI/DSI 接口,最高 64 MB PSRAM)和 ESP32-S3(240 MHz 双核 Xtensa,带神经网络向量加速并支持 RGB LCD)为边缘 AI 和丰富的图形界面提供最高速度。",
},
{
pergunta: "电池供电的项目,哪款 ESP32 最省电?",
resposta: "带有 ULP 协处理器的紧凑型 RISC-V 微控制器,如 ESP32-C2、ESP32-C3 和 ESP32-H2(专用 802.15.4,无 Wi-Fi),深度睡眠电流可低至 5 µA 到 10 µA。",
},
];
export default async function Layout({ children, params }) { export default async function Layout({ children, params }) {
const { locale } = await params; const { locale } = await params;
@@ -140,7 +159,8 @@ export default async function Layout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const faqs = isDe ? faqsDe : isFr ? faqsFr : isEs ? faqsEs : isEn ? faqsEn : faqsPt; const isZh = locale === "zh-CN";
const faqs = isZh ? faqsZh : isDe ? faqsDe : isFr ? faqsFr : isEs ? faqsEs : isEn ? faqsEn : faqsPt;
return ( return (
<> <>
@@ -149,7 +169,7 @@ export default async function Layout({ children, params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.aplicacaoWeb({ jsonLd.aplicacaoWeb({
nome: isDe nome: isZh ? "智能 ESP32 选型器" : isDe
? "Intelligente ESP32-Auswahl" ? "Intelligente ESP32-Auswahl"
: isFr : isFr
? "Sélecteur intelligent d'ESP32" ? "Sélecteur intelligent d'ESP32"
@@ -158,7 +178,7 @@ export default async function Layout({ children, params }) {
: isEn : isEn
? "Intelligent ESP32 Selector" ? "Intelligent ESP32 Selector"
: "Seletor Inteligente de ESP32", : "Seletor Inteligente de ESP32",
descricao: isDe descricao: isZh ? "交互式工具,分析连接、功耗、外设和安全需求,推荐最合适的 ESP32 系列。" : isDe
? "Interaktives Werkzeug zur Analyse von Konnektivität, Stromverbrauch, Peripherie und Sicherheit zur Ermittlung der idealen ESP32-Serie." ? "Interaktives Werkzeug zur Analyse von Konnektivität, Stromverbrauch, Peripherie und Sicherheit zur Ermittlung der idealen ESP32-Serie."
: isFr : isFr
? "Outil interactif analysant la connectivité, la consommation, les périphériques et la sécurité pour recommander la série idéale d'ESP32." ? "Outil interactif analysant la connectivité, la consommation, les périphériques et la sécurité pour recommander la série idéale d'ESP32."
@@ -167,7 +187,7 @@ export default async function Layout({ children, params }) {
: isEn : isEn
? "Interactive tool analyzing connectivity, power consumption, peripherals, and security to recommend the ideal ESP32 series." ? "Interactive tool analyzing connectivity, power consumption, peripherals, and security to recommend the ideal ESP32 series."
: "Ferramenta interativa que analisa conectividade, consumo, periféricos e segurança para indicar a série ideal de ESP32.", : "Ferramenta interativa que analisa conectividade, consumo, periféricos e segurança para indicar a série ideal de ESP32.",
caminho: isDe caminho: isZh ? "/zh-CN/selector" : isDe
? "/de/auswahl" ? "/de/auswahl"
: isFr : isFr
? "/fr/selecteur" ? "/fr/selecteur"
@@ -187,11 +207,11 @@ export default async function Layout({ children, params }) {
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ jsonLd.trilha([
{ {
nome: isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início", nome: isZh ? "首页" : isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início",
caminho: isDe ? "/de" : isFr ? "/fr" : isEs ? "/es" : isEn ? "/en" : "/", caminho: isZh ? "/zh-CN" : isDe ? "/de" : isFr ? "/fr" : isEs ? "/es" : isEn ? "/en" : "/",
}, },
{ {
nome: isDe nome: isZh ? "ESP32 选型器" : isDe
? "ESP32-Auswahl" ? "ESP32-Auswahl"
: isFr : isFr
? "Sélecteur d'ESP32" ? "Sélecteur d'ESP32"
@@ -200,7 +220,7 @@ export default async function Layout({ children, params }) {
: isEn : isEn
? "ESP32 Selector" ? "ESP32 Selector"
: "Seletor de ESP32", : "Seletor de ESP32",
caminho: isDe caminho: isZh ? "/zh-CN/selector" : isDe
? "/de/auswahl" ? "/de/auswahl"
: isFr : isFr
? "/fr/selecteur" ? "/fr/selecteur"
File diff suppressed because it is too large. Load diff
+151 -68
View File
@@ -10,6 +10,7 @@ import seriesDataEn from "@/data/en/series.json";
import seriesDataEs from "@/data/es/series.json"; import seriesDataEs from "@/data/es/series.json";
import seriesDataFr from "@/data/fr/series.json"; import seriesDataFr from "@/data/fr/series.json";
import seriesDataDe from "@/data/de/series.json"; import seriesDataDe from "@/data/de/series.json";
import seriesDataZh from "@/data/zh-CN/series.json";
import ConnectionsDiagram from "@/components/ConnectionsDiagram"; import ConnectionsDiagram from "@/components/ConnectionsDiagram";
import SeriesTabMenu from "@/components/SeriesTabMenu"; import SeriesTabMenu from "@/components/SeriesTabMenu";
import PinRestrictions from "@/components/PinRestrictions"; import PinRestrictions from "@/components/PinRestrictions";
@@ -41,8 +42,9 @@ export default async function SerieDetail({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt"; const isZh = locale === "zh-CN";
const seriesData = isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt; const currentLocale = isZh ? "zh-CN" : isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const seriesData = isZh ? seriesDataZh : isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
const serie = seriesData[key]; const serie = seriesData[key];
if (!serie) { if (!serie) {
@@ -51,7 +53,7 @@ export default async function SerieDetail({ params }) {
let conexoes = []; let conexoes = [];
try { try {
conexoes = isDe conexoes = isZh ? require(`@/data/zh-CN/conexoes/${key}.json`) : isDe
? require(`@/data/de/conexoes/${key}.json`) ? require(`@/data/de/conexoes/${key}.json`)
: isFr : isFr
? require(`@/data/fr/conexoes/${key}.json`) ? require(`@/data/fr/conexoes/${key}.json`)
@@ -69,10 +71,16 @@ export default async function SerieDetail({ params }) {
const previousKey = currentIndex > 0 ? seriesKeys[currentIndex - 1] : null; const previousKey = currentIndex > 0 ? seriesKeys[currentIndex - 1] : null;
const nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null; const nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null;
const nao = isDe ? "Nein" : isFr ? "Non" : isEs || isEn ? "No" : "Não"; const nao = isZh ? "否" : isDe ? "Nein" : isFr ? "Non" : isEs || isEn ? "No" : "Não";
const naoInformado = isDe ? "Nicht angegeben" : isFr ? "Non communiqué" : isEs ? "No informado" : isEn ? "Not informed" : "Não informado"; const naoInformado = isZh ? "未提供" : isDe ? "Nicht angegeben" : isFr ? "Non communiqué" : isEs ? "No informado" : isEn ? "Not informed" : "Não informado";
const processadorSpecs = isDe const processadorSpecs = isZh ? [
{ label: "架构", value: serie.arquitetura },
{ label: "内核", value: serie.nucleos },
{ label: "频率", value: serie.frequencia },
{ label: "ULP 协处理器", value: serie.coprocessador_ulp || nao },
{ label: "AI 加速器", value: serie.aceleradores_ia || nao },
] : isDe
? [ ? [
{ label: "Architektur", value: serie.arquitetura }, { label: "Architektur", value: serie.arquitetura },
{ label: "Kerne", value: serie.nucleos }, { label: "Kerne", value: serie.nucleos },
@@ -113,7 +121,20 @@ export default async function SerieDetail({ params }) {
]; ];
const seg = serie.seguranca || {}; const seg = serie.seguranca || {};
const segurancaSpecs = isDe const segurancaSpecs = isZh ? [
{ label: "AES", value: seg.aes || nao },
{ label: "SHA", value: seg.sha || nao },
{ label: "RSA / MPI", value: seg.rsa || nao },
{ label: "ECC(椭圆曲线)", value: seg.ecc || nao },
{ label: "硬件 ECDSA", value: seg.ecdsa || nao },
{ label: "HMAC", value: seg.hmac || nao },
{ label: "数字签名(DS)", value: seg.assinatura_digital || nao },
{ label: "密钥管理器", value: seg.key_manager || nao },
{ label: "随机数发生器(TRNG)", value: seg.rng || nao },
{ label: "闪存加密", value: seg.criptografia_flash || nao },
{ label: "安全启动", value: seg.secure_boot || nao },
{ label: "DPA 防护", value: seg.protecao_dpa || nao },
] : isDe
? [ ? [
{ label: "AES", value: seg.aes || nao }, { label: "AES", value: seg.aes || nao },
{ label: "SHA", value: seg.sha || nao }, { label: "SHA", value: seg.sha || nao },
@@ -188,7 +209,13 @@ export default async function SerieDetail({ params }) {
{ label: "Proteção contra DPA", value: seg.protecao_dpa || "Não" }, { label: "Proteção contra DPA", value: seg.protecao_dpa || "Não" },
]; ];
const ferramentasSpecs = isDe const ferramentasSpecs = isZh ? [
{ label: "最低 ESP-IDF 版本", value: serie.esp_idf_minimo || naoInformado },
{ label: "Arduino 核心", value: serie.arduino_core || naoInformado },
{ label: "调试", value: serie.depuracao || naoInformado },
{ label: "封装", value: serie.encapsulamento || naoInformado },
{ label: "可用模组", value: serie.modulos || naoInformado },
] : isDe
? [ ? [
{ label: "Minimales ESP-IDF", value: serie.esp_idf_minimo || naoInformado }, { label: "Minimales ESP-IDF", value: serie.esp_idf_minimo || naoInformado },
{ label: "Arduino-Core", value: serie.arduino_core || naoInformado }, { label: "Arduino-Core", value: serie.arduino_core || naoInformado },
@@ -228,7 +255,14 @@ export default async function SerieDetail({ params }) {
{ label: "Módulos disponíveis", value: serie.modulos || "Não informado" }, { label: "Módulos disponíveis", value: serie.modulos || "Não informado" },
]; ];
const conectividadeSpecs = isDe const conectividadeSpecs = isZh ? [
{ label: "Wi-Fi", value: serie.wifi || nao },
{ label: "蓝牙", value: serie.bluetooth || nao },
{ label: "Zigbee/Thread", value: serie.zigbee_thread || nao },
{ label: "Matter", value: serie.matter || nao },
{ label: "以太网", value: serie.ethernet || serie.ethernet_mac || nao },
{ label: "CAN", value: serie.can || nao },
] : isDe
? [ ? [
{ label: "Wi-Fi", value: serie.wifi || nao }, { label: "Wi-Fi", value: serie.wifi || nao },
{ label: "Bluetooth", value: serie.bluetooth || nao }, { label: "Bluetooth", value: serie.bluetooth || nao },
@@ -273,7 +307,13 @@ export default async function SerieDetail({ params }) {
{ label: "CAN", value: serie.can || "Não" }, { label: "CAN", value: serie.can || "Não" },
]; ];
const memoriaSpecs = isDe const memoriaSpecs = isZh ? [
{ label: "SRAM", value: serie.memoria_sram },
{ label: "RTC SRAM", value: serie.memoria_sram_rtc },
{ label: "ROM", value: serie.memoria_rom },
{ label: "外部闪存", value: serie.flash_externa },
{ label: "外部 PSRAM", value: serie.psram_externa || nao },
] : isDe
? [ ? [
{ label: "SRAM", value: serie.memoria_sram }, { label: "SRAM", value: serie.memoria_sram },
{ label: "RTC-SRAM", value: serie.memoria_sram_rtc }, { label: "RTC-SRAM", value: serie.memoria_sram_rtc },
@@ -313,7 +353,18 @@ export default async function SerieDetail({ params }) {
{ label: "PSRAM Externa", value: serie.psram_externa || "Não" }, { label: "PSRAM Externa", value: serie.psram_externa || "Não" },
]; ];
const perifericos = isDe const perifericos = isZh ? [
{ label: "GPIO", value: serie.gpio },
{ label: "ADC", value: serie.adc },
{ label: "DAC", value: serie.dac || nao },
{ label: "触摸", value: serie.touch || nao },
{ label: "UART", value: serie.uart },
{ label: "SPI", value: serie.spi },
{ label: "I2C", value: serie.i2c },
{ label: "I2S", value: serie.i2s },
{ label: "PWM", value: serie.pwm },
{ label: "USB", value: serie.usb || nao },
] : isDe
? [ ? [
{ label: "GPIO", value: serie.gpio }, { label: "GPIO", value: serie.gpio },
{ label: "ADC", value: serie.adc }, { label: "ADC", value: serie.adc },
@@ -380,12 +431,12 @@ export default async function SerieDetail({ params }) {
const interfacesEspeciais = []; const interfacesEspeciais = [];
if (serie.lcd) interfacesEspeciais.push({ label: "LCD", value: serie.lcd }); if (serie.lcd) interfacesEspeciais.push({ label: "LCD", value: serie.lcd });
if (serie.camera) interfacesEspeciais.push({ label: isDe ? "Kamera" : isFr ? "Caméra" : isEs ? "Cámara" : isEn ? "Camera" : "Câmera", value: serie.camera }); if (serie.camera) interfacesEspeciais.push({ label: isZh ? "摄像头" : isDe ? "Kamera" : isFr ? "Caméra" : isEs ? "Cámara" : isEn ? "Camera" : "Câmera", value: serie.camera });
if (serie.mipi_csi) interfacesEspeciais.push({ label: "MIPI CSI", value: serie.mipi_csi }); if (serie.mipi_csi) interfacesEspeciais.push({ label: "MIPI CSI", value: serie.mipi_csi });
if (serie.mipi_dsi) interfacesEspeciais.push({ label: "MIPI DSI", value: serie.mipi_dsi }); if (serie.mipi_dsi) interfacesEspeciais.push({ label: "MIPI DSI", value: serie.mipi_dsi });
if (serie.sdio) interfacesEspeciais.push({ label: "SDIO", value: serie.sdio }); if (serie.sdio) interfacesEspeciais.push({ label: "SDIO", value: serie.sdio });
if (serie.sensor_hall) interfacesEspeciais.push({ label: isDe ? "Hall-Sensor" : isFr ? "Capteur Hall" : isEs ? "Sensor Hall" : isEn ? "Hall Sensor" : "Sensor Hall", value: serie.sensor_hall }); if (serie.sensor_hall) interfacesEspeciais.push({ label: isZh ? "霍尔传感器" : isDe ? "Hall-Sensor" : isFr ? "Capteur Hall" : isEs ? "Sensor Hall" : isEn ? "Hall Sensor" : "Sensor Hall", value: serie.sensor_hall });
if (serie.sensor_temperatura) interfacesEspeciais.push({ label: isDe ? "Temperatursensor" : isFr ? "Capteur de température" : isEs ? "Sensor de temperatura" : isEn ? "Temperature Sensor" : "Sensor Temperatura", value: serie.sensor_temperatura }); if (serie.sensor_temperatura) interfacesEspeciais.push({ label: isZh ? "温度传感器" : isDe ? "Temperatursensor" : isFr ? "Capteur de température" : isEs ? "Sensor de temperatura" : isEn ? "Temperature Sensor" : "Sensor Temperatura", value: serie.sensor_temperatura });
// Consumo de Energia // Consumo de Energia
const consumoEnergia = []; const consumoEnergia = [];
@@ -460,7 +511,21 @@ export default async function SerieDetail({ params }) {
'deep_sleep': 'Deep Sleep', 'deep_sleep': 'Deep Sleep',
'hibernation': 'Ruhezustand' 'hibernation': 'Ruhezustand'
}; };
const modoLabels = isDe ? modoLabelsDe : isFr ? modoLabelsFr : isEs ? modoLabelsEs : isEn ? modoLabelsEn : modoLabelsPt; const modoLabelsZh = {
'active': '工作模式',
'active_cpu_240mhz': '工作(CPU 240MHz)',
'active_cpu_160mhz': '工作(CPU 160MHz)',
'active_wifi_tx': '工作(Wi-Fi 发射)',
'active_wifi_tx_peak': '工作(Wi-Fi 发射峰值)',
'active_wifi': '工作(Wi-Fi)',
'active_wifi6': '工作(Wi-Fi 6)',
'modem_sleep': 'Modem Sleep',
'modem_sleep_cpu_160mhz': 'Modem Sleep(CPU 160MHz)',
'light_sleep': 'Light Sleep',
'deep_sleep': 'Deep Sleep',
'hibernation': 'Hibernation'
};
const modoLabels = isZh ? modoLabelsZh : isDe ? modoLabelsDe : isFr ? modoLabelsFr : isEs ? modoLabelsEs : isEn ? modoLabelsEn : modoLabelsPt;
Object.entries(serie.consumo_energia).forEach(([modo, consumo]) => { Object.entries(serie.consumo_energia).forEach(([modo, consumo]) => {
consumoEnergia.push({ consumoEnergia.push({
@@ -473,18 +538,18 @@ export default async function SerieDetail({ params }) {
const tabs = [ const tabs = [
{ {
id: "devboard", id: "devboard",
label: isDe ? "Entwicklungsboard" : isFr ? "Carte de développement" : isEs ? "Placa de desarrollo" : isEn ? "Development board" : "Placa de desenvolvimento", label: isZh ? "开发板" : isDe ? "Entwicklungsboard" : isFr ? "Carte de développement" : isEs ? "Placa de desarrollo" : isEn ? "Development board" : "Placa de desenvolvimento",
available: serie.placa, available: serie.placa,
content: ( content: (
<div className="card p-5 sm:p-7"> <div className="card p-5 sm:p-7">
<h2 className="text-lg font-semibold text-ink"> <h2 className="text-lg font-semibold text-ink">
{isDe ? "Offizielles Development Kit" : isFr ? "Kit de développement officiel" : isEs ? "Kit de desarrollo oficial" : isEn ? "Official development kit" : "Kit de desenvolvimento oficial"} {isZh ? "官方开发套件" : isDe ? "Offizielles Development Kit" : isFr ? "Kit de développement officiel" : isEs ? "Kit de desarrollo oficial" : isEn ? "Official development kit" : "Kit de desenvolvimento oficial"}
</h2> </h2>
{/* As fotos oficiais foram feitas para fundo claro: o quadro fica branco nos dois temas. */} {/* As fotos oficiais foram feitas para fundo claro: o quadro fica branco nos dois temas. */}
<div className="mt-5 overflow-hidden rounded-xl border border-line bg-white p-4"> <div className="mt-5 overflow-hidden rounded-xl border border-line bg-white p-4">
<Image <Image
src={serie.placa} src={serie.placa}
alt={isDe ? `Entwicklungsboard ${key}` : isFr ? `Carte de développement ${key}` : isEs ? `Placa de desarrollo ${key}` : isEn ? `Development board ${key}` : `Placa de desenvolvimento ${key}`} alt={isZh ? `开发板 ${key}` : isDe ? `Entwicklungsboard ${key}` : isFr ? `Carte de développement ${key}` : isEs ? `Placa de desarrollo ${key}` : isEn ? `Development board ${key}` : `Placa de desenvolvimento ${key}`}
width={1200} width={1200}
height={800} height={800}
className="mx-auto h-auto max-h-[28rem] w-full rounded-lg object-contain" className="mx-auto h-auto max-h-[28rem] w-full rounded-lg object-contain"
@@ -492,7 +557,7 @@ export default async function SerieDetail({ params }) {
/> />
</div> </div>
<p className="mt-3 text-sm text-muted"> <p className="mt-3 text-sm text-muted">
{isDe ? ( {isZh ? ( <>乐鑫面向 {key} 系列的官方开发板。</> ) : isDe ? (
<>Offizielles Espressif-Board für die Serie {key}.</> <>Offizielles Espressif-Board für die Serie {key}.</>
) : isFr ? ( ) : isFr ? (
<>Carte officielle d'Espressif pour la série {key}.</> <>Carte officielle d'Espressif pour la série {key}.</>
@@ -509,7 +574,7 @@ export default async function SerieDetail({ params }) {
}, },
{ {
id: "connections", id: "connections",
label: isDe ? "Pinbelegungsdiagramm" : isFr ? "Diagramme de brochage" : isEs ? "Diagrama de patillaje" : isEn ? "Pinout diagram" : "Diagrama de conexões", label: isZh ? "引脚图" : isDe ? "Pinbelegungsdiagramm" : isFr ? "Diagramme de brochage" : isEs ? "Diagrama de patillaje" : isEn ? "Pinout diagram" : "Diagrama de conexões",
available: conexoes.length > 0, available: conexoes.length > 0,
content: ( content: (
<div className="card p-5 sm:p-7"> <div className="card p-5 sm:p-7">
@@ -517,10 +582,10 @@ export default async function SerieDetail({ params }) {
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" /> <AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" />
<div> <div>
<p className="font-semibold text-ink"> <p className="font-semibold text-ink">
{isDe ? "Dies ist das Diagramm des Chips, nicht des Entwicklungsboards" : isFr ? "Ceci est le schéma de la puce, pas de la carte de développement" : isEs ? "Este es el diagrama del chip, no de la placa" : isEn ? "This is the chip diagram, not the development board" : "Este é o diagrama do chip, não da placa"} {isZh ? "这是芯片图,不是开发板" : isDe ? "Dies ist das Diagramm des Chips, nicht des Entwicklungsboards" : isFr ? "Ceci est le schéma de la puce, pas de la carte de développement" : isEs ? "Este es el diagrama del chip, no de la placa" : isEn ? "This is the chip diagram, not the development board" : "Este é o diagrama do chip, não da placa"}
</p> </p>
<p className="mt-1"> <p className="mt-1">
{isDe ? ( {isZh ? ( `该图展示了 ${key} 的芯片内部连接。每块开发板只会引出其中一部分,或做不同的走线。` ) : isDe ? (
`Das Diagramm zeigt die Silizium-Anschlüsse des ${key}. Jedes Entwicklungsboard führt nur einen Teil heraus oder leitet sie anders weiter.` `Das Diagramm zeigt die Silizium-Anschlüsse des ${key}. Jedes Entwicklungsboard führt nur einen Teil heraus oder leitet sie anders weiter.`
) : isFr ? ( ) : isFr ? (
`Le schéma montre les connexions du silicium du ${key}. Chaque carte de développement n'en expose qu'une partie ou les route différemment.` `Le schéma montre les connexions du silicium du ${key}. Chaque carte de développement n'en expose qu'une partie ou les route différemment.`
@@ -545,10 +610,24 @@ export default async function SerieDetail({ params }) {
<div className="callout mt-8"> <div className="callout mt-8">
<p className="flex items-center gap-2 font-semibold text-ink"> <p className="flex items-center gap-2 font-semibold text-ink">
<Lightbulb className="h-4 w-4 text-accent" aria-hidden="true" /> <Lightbulb className="h-4 w-4 text-accent" aria-hidden="true" />
{isDe ? "Nahezu jeder Pin ist rekonfigurierbar" : isFr ? "La quasi-totalité des broches est reconfigurable" : isEs ? "Casi todos los pines son reconfigurables" : isEn ? "Almost every pin is reconfigurable" : "Quase todo pino é reconfigurável"} {isZh ? "几乎每个引脚都可重新配置" : isDe ? "Nahezu jeder Pin ist rekonfigurierbar" : isFr ? "La quasi-totalité des broches est reconfigurable" : isEs ? "Casi todos los pines son reconfigurables" : isEn ? "Almost every pin is reconfigurable" : "Quase todo pino é reconfigurável"}
</p> </p>
<p className="mt-1"> <p className="mt-1">
{isDe ? ( {isZh ? (
<>
{key} 的 IO 矩阵可以把 SPI、I2C、UART、PWM 以及摄像头/显示总线移到大多数数字引脚上。关于启动配置引脚的限制和模拟引脚,请查阅{" "}
<a
href={`https://docs.espressif.com/projects/esp-idf/en/latest/${String(key).toLowerCase().replace("-", "")}/api-reference/peripherals/gpio.html`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 font-medium text-accent underline decoration-accent/30 underline-offset-2 hover:decoration-accent"
>
官方 GPIO 指南
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
</a>
。
</>
) : isDe ? (
<> <>
Die IO-Matrix des {key} ermöglicht das Zuweisen von SPI, I2C, UART, PWM und Kamera-/Displaybussen Die IO-Matrix des {key} ermöglicht das Zuweisen von SPI, I2C, UART, PWM und Kamera-/Displaybussen
zu den meisten digitalen Pins. Für Strapping-Einschränkungen und analoge Pins konsultieren Sie den{" "} zu den meisten digitalen Pins. Für Strapping-Einschränkungen und analoge Pins konsultieren Sie den{" "}
@@ -635,18 +714,18 @@ export default async function SerieDetail({ params }) {
]; ];
const secoes = [ const secoes = [
{ id: "processador", titulo: isDe ? "Prozessor" : isFr ? "Processeur" : isEs ? "Procesador" : isEn ? "Processor" : "Processador", icon: Cpu, specs: processadorSpecs }, { id: "processador", titulo: isZh ? "处理器" : isDe ? "Prozessor" : isFr ? "Processeur" : isEs ? "Procesador" : isEn ? "Processor" : "Processador", icon: Cpu, specs: processadorSpecs },
{ id: "conectividade", titulo: isDe ? "Konnektivität" : isFr ? "Connectivité" : isEs ? "Conectividad" : isEn ? "Connectivity" : "Conectividade", icon: Wifi, specs: conectividadeSpecs }, { id: "conectividade", titulo: isZh ? "无线连接" : isDe ? "Konnektivität" : isFr ? "Connectivité" : isEs ? "Conectividad" : isEn ? "Connectivity" : "Conectividade", icon: Wifi, specs: conectividadeSpecs },
{ id: "ferramentas", titulo: isDe ? "Werkzeuge und Verfügbarkeit" : isFr ? "Outils et disponibilité" : isEs ? "Herramientas y disponibilidad" : isEn ? "Tools and availability" : "Ferramentas e disponibilidade", icon: Package, specs: ferramentasSpecs }, { id: "ferramentas", titulo: isZh ? "工具与可用性" : isDe ? "Werkzeuge und Verfügbarkeit" : isFr ? "Outils et disponibilité" : isEs ? "Herramientas y disponibilidad" : isEn ? "Tools and availability" : "Ferramentas e disponibilidade", icon: Package, specs: ferramentasSpecs },
{ id: "seguranca", titulo: isDe ? "Sicherheit" : isFr ? "Sécurité" : isEs ? "Seguridad" : isEn ? "Security" : "Segurança", icon: ShieldCheck, specs: segurancaSpecs }, { id: "seguranca", titulo: isZh ? "安全" : isDe ? "Sicherheit" : isFr ? "Sécurité" : isEs ? "Seguridad" : isEn ? "Security" : "Segurança", icon: ShieldCheck, specs: segurancaSpecs },
{ id: "memoria", titulo: isDe ? "Speicher" : isFr ? "Mémoire" : isEs ? "Memoria" : isEn ? "Memory" : "Memória", icon: Database, specs: memoriaSpecs }, { id: "memoria", titulo: isZh ? "内存" : isDe ? "Speicher" : isFr ? "Mémoire" : isEs ? "Memoria" : isEn ? "Memory" : "Memória", icon: Database, specs: memoriaSpecs },
...(consumoEnergia.length > 0 ...(consumoEnergia.length > 0
? [{ ? [{
id: "energia", id: "energia",
titulo: isDe ? "Stromverbrauch" : isFr ? "Consommation d'énergie" : isEs ? "Consumo de energía" : isEn ? "Power consumption" : "Consumo de energia", titulo: isZh ? "功耗" : isDe ? "Stromverbrauch" : isFr ? "Consommation d'énergie" : isEs ? "Consumo de energía" : isEn ? "Power consumption" : "Consumo de energia",
icon: Battery, icon: Battery,
specs: consumoEnergia, specs: consumoEnergia,
nota: isDe nota: isZh ? "典型值,条件为 3.3 V、25 °C。实际功耗取决于射频占空比和所配置的时钟频率。" : isDe
? "Typische Werte bei 3,3 V und 25 °C. Der tatsächliche Stromverbrauch hängt vom Funk-Tastgrad und der konfigurierten Taktfrequenz ab." ? "Typische Werte bei 3,3 V und 25 °C. Der tatsächliche Stromverbrauch hängt vom Funk-Tastgrad und der konfigurierten Taktfrequenz ab."
: isFr : isFr
? "Valeurs typiques à 3,3 V et 25 °C. La consommation réelle dépend du cycle de travail de la radio et de la fréquence d'horloge configurée." ? "Valeurs typiques à 3,3 V et 25 °C. La consommation réelle dépend du cycle de travail de la radio et de la fréquence d'horloge configurée."
@@ -657,26 +736,26 @@ export default async function SerieDetail({ params }) {
: "Valores típicos a 3,3 V e 25 °C. O consumo real depende do ciclo de trabalho do rádio e da frequência de clock configurada.", : "Valores típicos a 3,3 V e 25 °C. O consumo real depende do ciclo de trabalho do rádio e da frequência de clock configurada.",
}] }]
: []), : []),
{ id: "perifericos", titulo: isDe ? "Peripherie" : isFr ? "Périphériques" : isEs ? "Periféricos" : isEn ? "Peripherals" : "Periféricos", icon: Sliders, specs: perifericos }, { id: "perifericos", titulo: isZh ? "外设" : isDe ? "Peripherie" : isFr ? "Périphériques" : isEs ? "Periféricos" : isEn ? "Peripherals" : "Periféricos", icon: Sliders, specs: perifericos },
...(interfacesEspeciais.length > 0 ...(interfacesEspeciais.length > 0
? [{ id: "interfaces", titulo: isDe ? "Spezielle Schnittstellen" : isFr ? "Interfaces spéciales" : isEs ? "Interfaces especiales" : isEn ? "Special interfaces" : "Interfaces especiais", icon: Sliders, specs: interfacesEspeciais }] ? [{ id: "interfaces", titulo: isZh ? "特殊接口" : isDe ? "Spezielle Schnittstellen" : isFr ? "Interfaces spéciales" : isEs ? "Interfaces especiales" : isEn ? "Special interfaces" : "Interfaces especiais", icon: Sliders, specs: interfacesEspeciais }]
: []), : []),
{ {
id: "temperatura", id: "temperatura",
titulo: isDe ? "Temperaturgrenzwerte" : isFr ? "Limites de température" : isEs ? "Límites de temperatura" : isEn ? "Temperature limits" : "Limites de temperatura", titulo: isZh ? "温度范围" : isDe ? "Temperaturgrenzwerte" : isFr ? "Limites de température" : isEs ? "Límites de temperatura" : isEn ? "Temperature limits" : "Limites de temperatura",
icon: Thermometer, icon: Thermometer,
specs: [ specs: [
{ label: isDe ? "Betrieb" : isFr ? "Fonctionnement" : isEs ? "Operación" : isEn ? "Operating" : "Operação", value: serie.temperatura_operacao }, { label: isZh ? "工作" : isDe ? "Betrieb" : isFr ? "Fonctionnement" : isEs ? "Operación" : isEn ? "Operating" : "Operação", value: serie.temperatura_operacao },
{ label: isDe ? "Lagerung" : isFr ? "Stockage" : isEs ? "Almacenamiento" : isEn ? "Storage" : "Armazenamento", value: serie.temperatura_armazenamento }, { label: isZh ? "存储" : isDe ? "Lagerung" : isFr ? "Stockage" : isEs ? "Almacenamiento" : isEn ? "Storage" : "Armazenamento", value: serie.temperatura_armazenamento },
], ],
}, },
]; ];
const fatos = [ const fatos = [
{ label: isDe ? "Prozessor" : isFr ? "Processeur" : isEs ? "Procesador" : isEn ? "Processor" : "Processador", value: String(serie.nucleos).includes("2") ? "Dual-core" : "Single-core", detail: serie.frequencia }, { label: isZh ? "处理器" : isDe ? "Prozessor" : isFr ? "Processeur" : isEs ? "Procesador" : isEn ? "Processor" : "Processador", value: String(serie.nucleos).includes("2") ? "Dual-core" : "Single-core", detail: serie.frequencia },
{ label: "SRAM", value: serie.memoria_sram, detail: isDe ? `+ ${serie.memoria_sram_rtc} RTC-SRAM` : isFr ? `+ ${serie.memoria_sram_rtc} de SRAM RTC` : isEs ? `+ ${serie.memoria_sram_rtc} de SRAM RTC` : isEn ? `+ ${serie.memoria_sram_rtc} RTC SRAM` : `+ ${serie.memoria_sram_rtc} de SRAM RTC` }, { label: "SRAM", value: serie.memoria_sram, detail: isZh ? `+ ${serie.memoria_sram_rtc} RTC SRAM` : isDe ? `+ ${serie.memoria_sram_rtc} RTC-SRAM` : isFr ? `+ ${serie.memoria_sram_rtc} de SRAM RTC` : isEs ? `+ ${serie.memoria_sram_rtc} de SRAM RTC` : isEn ? `+ ${serie.memoria_sram_rtc} RTC SRAM` : `+ ${serie.memoria_sram_rtc} de SRAM RTC` },
{ label: "GPIO", value: serie.gpio, detail: isDe ? "programmierbare Pins" : isFr ? "broches programmables" : isEs ? "pines programables" : isEn ? "programmable pins" : "pinos programáveis" }, { label: "GPIO", value: serie.gpio, detail: isZh ? "个可编程引脚" : isDe ? "programmierbare Pins" : isFr ? "broches programmables" : isEs ? "pines programables" : isEn ? "programmable pins" : "pinos programáveis" },
{ label: isDe ? "Architektur" : isFr ? "Architecture" : isEs ? "Arquitectura" : isEn ? "Architecture" : "Arquitetura", value: String(serie.arquitetura).includes("RISC-V") ? "RISC-V" : "Xtensa", detail: serie.arquitetura }, { label: isZh ? "架构" : isDe ? "Architektur" : isFr ? "Architecture" : isEs ? "Arquitectura" : isEn ? "Architecture" : "Arquitetura", value: String(serie.arquitetura).includes("RISC-V") ? "RISC-V" : "Xtensa", detail: serie.arquitetura },
]; ];
return ( return (
@@ -689,8 +768,8 @@ export default async function SerieDetail({ params }) {
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd.trilha([ __html: JSON.stringify(jsonLd.trilha([
{ nome: isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início", caminho: isDe ? "/de" : isFr ? "/fr" : isEn ? "/en" : isEs ? "/es" : "/" }, { nome: isZh ? "首页" : isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início", caminho: isZh ? "/zh-CN" : isDe ? "/de" : isFr ? "/fr" : isEn ? "/en" : isEs ? "/es" : "/" },
{ nome: isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32", caminho: getLocalizedHref("/series", currentLocale) }, { nome: isZh ? "ESP32 系列" : isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32", caminho: getLocalizedHref("/series", currentLocale) },
{ nome: serie.nome_completo, caminho: getLocalizedHref(`/series/${key}`, currentLocale) }, { nome: serie.nome_completo, caminho: getLocalizedHref(`/series/${key}`, currentLocale) },
])), ])),
}} }}
@@ -703,8 +782,8 @@ export default async function SerieDetail({ params }) {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries", href: getLocalizedHref("/series", currentLocale) }, { label: key }]} trail={[{ label: isZh ? "系列" : isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries", href: getLocalizedHref("/series", currentLocale) }, { label: key }]}
icon={Cpu} icon={Cpu}
label={serie.arquitetura} label={serie.arquitetura}
title={key} title={key}
@@ -713,32 +792,32 @@ export default async function SerieDetail({ params }) {
{serie.datasheet ? ( {serie.datasheet ? (
<a href={serie.datasheet} target="_blank" rel="noopener noreferrer" className="btn btn-primary"> <a href={serie.datasheet} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<FileText className="h-4 w-4" aria-hidden="true" /> <FileText className="h-4 w-4" aria-hidden="true" />
{isDe ? "Offizielles Datenblatt" : isFr ? "Datasheet officiel" : isEs ? "Datasheet oficial" : isEn ? "Official datasheet" : "Datasheet oficial"} {isZh ? "官方数据手册" : isDe ? "Offizielles Datenblatt" : isFr ? "Datasheet officiel" : isEs ? "Datasheet oficial" : isEn ? "Official datasheet" : "Datasheet oficial"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
) : ( ) : (
<span className="btn btn-ghost cursor-not-allowed opacity-70" aria-disabled="true"> <span className="btn btn-ghost cursor-not-allowed opacity-70" aria-disabled="true">
<FileText className="h-4 w-4" aria-hidden="true" /> <FileText className="h-4 w-4" aria-hidden="true" />
{isDe ? "Datenblatt noch nicht veröffentlicht" : isFr ? "Datasheet pas encore publié" : isEs ? "Datasheet aún no publicado" : isEn ? "Datasheet not yet published" : "Datasheet ainda não publicado"} {isZh ? "数据手册尚未发布" : isDe ? "Datenblatt noch nicht veröffentlicht" : isFr ? "Datasheet pas encore publié" : isEs ? "Datasheet aún no publicado" : isEn ? "Datasheet not yet published" : "Datasheet ainda não publicado"}
</span> </span>
)} )}
{serie.guia_de_programacao && ( {serie.guia_de_programacao && (
<a href={serie.guia_de_programacao} target="_blank" rel="noopener noreferrer" className="btn btn-ghost"> <a href={serie.guia_de_programacao} target="_blank" rel="noopener noreferrer" className="btn btn-ghost">
<Code className="h-4 w-4" aria-hidden="true" /> <Code className="h-4 w-4" aria-hidden="true" />
{isDe ? "Programmierleitfaden" : isFr ? "Guide de programmation" : isEs ? "Guía de programación" : isEn ? "Programming guide" : "Guia de programação"} {isZh ? "编程指南" : isDe ? "Programmierleitfaden" : isFr ? "Guide de programmation" : isEs ? "Guía de programación" : isEn ? "Programming guide" : "Guia de programação"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
)} )}
{serie.manual_tecnico && ( {serie.manual_tecnico && (
<a href={serie.manual_tecnico} target="_blank" rel="noopener noreferrer" className="btn btn-ghost"> <a href={serie.manual_tecnico} target="_blank" rel="noopener noreferrer" className="btn btn-ghost">
<BookOpen className="h-4 w-4" aria-hidden="true" /> <BookOpen className="h-4 w-4" aria-hidden="true" />
{isDe ? "Technisches Referenzhandbuch" : isFr ? "Manuel technique de référence" : isEs ? "Manual de referencia técnica" : isEn ? "Technical reference manual" : "Manual técnico"} {isZh ? "技术参考手册" : isDe ? "Technisches Referenzhandbuch" : isFr ? "Manuel technique de référence" : isEs ? "Manual de referencia técnica" : isEn ? "Technical reference manual" : "Manual técnico"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
)} )}
</PageHero> </PageHero>
<section className="shell mt-10" aria-label={isDe ? "Zusammenfassung" : isFr ? "Résumé" : isEs ? "Resumen" : isEn ? "Summary" : "Resumo"}> <section className="shell mt-10" aria-label={isZh ? "概要" : isDe ? "Zusammenfassung" : isFr ? "Résumé" : isEs ? "Resumen" : isEn ? "Summary" : "Resumo"}>
<dl className="grid grid-cols-2 gap-3 lg:grid-cols-4"> <dl className="grid grid-cols-2 gap-3 lg:grid-cols-4">
{fatos.map((fato) => ( {fatos.map((fato) => (
<div key={fato.label} className="tile"> <div key={fato.label} className="tile">
@@ -754,7 +833,7 @@ export default async function SerieDetail({ params }) {
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" /> <AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" />
<div> <div>
<p className="font-semibold text-ink"> <p className="font-semibold text-ink">
{isDe ? "Vorläufige Dokumentation" : isFr ? "Documentation préliminaire" : isEs ? "Documentación preliminar" : isEn ? "Preliminary documentation" : "Documentação preliminar"} {isZh ? "初步文档" : isDe ? "Vorläufige Dokumentation" : isFr ? "Documentation préliminaire" : isEs ? "Documentación preliminar" : isEn ? "Preliminary documentation" : "Documentação preliminar"}
</p> </p>
<p className="mt-1">{serie.status_documentacao}</p> <p className="mt-1">{serie.status_documentacao}</p>
</div> </div>
@@ -763,13 +842,13 @@ export default async function SerieDetail({ params }) {
</section> </section>
<div className="shell mt-10"> <div className="shell mt-10">
<SeriesTabMenu tabs={tabs} isEn={isEn} isEs={isEs} isFr={isFr} isDe={isDe} /> <SeriesTabMenu tabs={tabs} isEn={isEn} isEs={isEs} isFr={isFr} isDe={isDe} isZh={isZh} />
<PinRestrictions conexoes={conexoes} serieKey={key} isEn={isEn} isEs={isEs} isFr={isFr} isDe={isDe} /> <PinRestrictions conexoes={conexoes} serieKey={key} isEn={isEn} isEs={isEs} isFr={isFr} isDe={isDe} isZh={isZh} />
</div> </div>
<div className="shell mt-12"> <div className="shell mt-12">
<nav <nav
aria-label={isDe ? "Abschnitte des Datenblatts" : isFr ? "Sections de la fiche" : isEs ? "Secciones de la ficha" : isEn ? "Datasheet sections" : "Seções da ficha"} aria-label={isZh ? "数据手册章节" : isDe ? "Abschnitte des Datenblatts" : isFr ? "Sections de la fiche" : isEs ? "Secciones de la ficha" : isEn ? "Datasheet sections" : "Seções da ficha"}
className="nav-pill no-scrollbar sticky top-20 z-30 flex gap-1 overflow-x-auto rounded-full p-1.5" className="nav-pill no-scrollbar sticky top-20 z-30 flex gap-1 overflow-x-auto rounded-full p-1.5"
> >
{secoes.map((secao) => ( {secoes.map((secao) => (
@@ -790,7 +869,7 @@ export default async function SerieDetail({ params }) {
<div className="panel flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center"> <div className="panel flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center">
<div className="max-w-2xl"> <div className="max-w-2xl">
<h2 className="section-title"> <h2 className="section-title">
{isDe ? ( {isZh ? ( <>准备好用 {key} 做原型了吗?</> ) : isDe ? (
<>Bereit zum Prototyping mit {key}?</> <>Bereit zum Prototyping mit {key}?</>
) : isFr ? ( ) : isFr ? (
<>Prêt à prototyper avec {key} ?</> <>Prêt à prototyper avec {key} ?</>
@@ -803,7 +882,7 @@ export default async function SerieDetail({ params }) {
)} )}
</h2> </h2>
<p className="mt-3 text-base text-muted"> <p className="mt-3 text-base text-muted">
{isDe {isZh ? "查看这个系列可用的开发板,含 USB、板载传感器和官方商店链接。" : isDe
? "Entdecken Sie die für diese Serie verfügbaren Entwicklungsboards mit USB, integrierten Sensoren und Links zum offiziellen Shop." ? "Entdecken Sie die für diese Serie verfügbaren Entwicklungsboards mit USB, integrierten Sensoren und Links zum offiziellen Shop."
: isFr : isFr
? "Explorez les cartes de développement disponibles pour cette série, avec USB, capteurs intégrés et liens vers la boutique officielle." ? "Explorez les cartes de développement disponibles pour cette série, avec USB, capteurs intégrés et liens vers la boutique officielle."
@@ -815,17 +894,17 @@ export default async function SerieDetail({ params }) {
</p> </p>
</div> </div>
<Link href={getLocalizedHref("/catalogo", currentLocale)} className="btn btn-primary shrink-0"> <Link href={getLocalizedHref("/catalogo", currentLocale)} className="btn btn-primary shrink-0">
{isDe ? "Boards ansehen" : isFr ? "Voir les cartes" : isEs ? "Ver placas" : isEn ? "View boards" : "Ver placas"} {isZh ? "查看开发板" : isDe ? "Boards ansehen" : isFr ? "Voir les cartes" : isEs ? "Ver placas" : isEn ? "View boards" : "Ver placas"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
<nav aria-label={isDe ? "Andere Serien" : isFr ? "Autres séries" : isEs ? "Otras series" : isEn ? "Other series" : "Outras séries"} className="mt-6 grid gap-3 sm:grid-cols-[1fr_auto_1fr] sm:items-center"> <nav aria-label={isZh ? "其他系列" : isDe ? "Andere Serien" : isFr ? "Autres séries" : isEs ? "Otras series" : isEn ? "Other series" : "Outras séries"} className="mt-6 grid gap-3 sm:grid-cols-[1fr_auto_1fr] sm:items-center">
{previousKey ? ( {previousKey ? (
<Link href={getLocalizedHref(`/series/${previousKey}`, currentLocale)} className="card card-interactive group flex items-center gap-3 px-5 py-4"> <Link href={getLocalizedHref(`/series/${previousKey}`, currentLocale)} className="card card-interactive group flex items-center gap-3 px-5 py-4">
<ArrowLeft className="h-4 w-4 text-muted transition-transform group-hover:-translate-x-1" aria-hidden="true" /> <ArrowLeft className="h-4 w-4 text-muted transition-transform group-hover:-translate-x-1" aria-hidden="true" />
<span> <span>
<span className="block text-xs text-muted">{isDe ? "Vorherige Serie" : isFr ? "Série précédente" : isEs ? "Serie anterior" : isEn ? "Previous series" : "Série anterior"}</span> <span className="block text-xs text-muted">{isZh ? "上一个系列" : isDe ? "Vorherige Serie" : isFr ? "Série précédente" : isEs ? "Serie anterior" : isEn ? "Previous series" : "Série anterior"}</span>
<span className="block font-display text-lg font-semibold text-ink">{previousKey}</span> <span className="block font-display text-lg font-semibold text-ink">{previousKey}</span>
</span> </span>
</Link> </Link>
@@ -834,12 +913,12 @@ export default async function SerieDetail({ params }) {
)} )}
<Link href={getLocalizedHref("/comparacao", currentLocale)} className="btn btn-ghost justify-self-center"> <Link href={getLocalizedHref("/comparacao", currentLocale)} className="btn btn-ghost justify-self-center">
<Scale className="h-4 w-4" aria-hidden="true" /> <Scale className="h-4 w-4" aria-hidden="true" />
{isDe ? "Serien vergleichen" : isFr ? "Comparer les séries" : isEs ? "Comparar series" : isEn ? "Compare series" : "Comparar séries"} {isZh ? "对比系列" : isDe ? "Serien vergleichen" : isFr ? "Comparer les séries" : isEs ? "Comparar series" : isEn ? "Compare series" : "Comparar séries"}
</Link> </Link>
{nextKey ? ( {nextKey ? (
<Link href={getLocalizedHref(`/series/${nextKey}`, currentLocale)} className="card card-interactive group flex items-center justify-end gap-3 px-5 py-4 text-right"> <Link href={getLocalizedHref(`/series/${nextKey}`, currentLocale)} className="card card-interactive group flex items-center justify-end gap-3 px-5 py-4 text-right">
<span> <span>
<span className="block text-xs text-muted">{isDe ? "Nächste Serie" : isFr ? "Série suivante" : isEs ? "Siguiente serie" : isEn ? "Next series" : "Próxima série"}</span> <span className="block text-xs text-muted">{isZh ? "下一个系列" : isDe ? "Nächste Serie" : isFr ? "Série suivante" : isEs ? "Siguiente serie" : isEn ? "Next series" : "Próxima série"}</span>
<span className="block font-display text-lg font-semibold text-ink">{nextKey}</span> <span className="block font-display text-lg font-semibold text-ink">{nextKey}</span>
</span> </span>
<ArrowRight className="h-4 w-4 text-muted transition-transform group-hover:translate-x-1" aria-hidden="true" /> <ArrowRight className="h-4 w-4 text-muted transition-transform group-hover:translate-x-1" aria-hidden="true" />
@@ -887,13 +966,14 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt"; const isZh = locale === "zh-CN";
const seriesData = isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt; const currentLocale = isZh ? "zh-CN" : isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const seriesData = isZh ? seriesDataZh : isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
const serie = seriesData[key]; const serie = seriesData[key];
if (!serie) { if (!serie) {
return { return {
title: isDe title: isZh ? "未找到该系列" : isDe
? "Serie nicht gefunden" ? "Serie nicht gefunden"
: isFr : isFr
? "Série non trouvée" ? "Série non trouvée"
@@ -912,7 +992,10 @@ export async function generateMetadata({ params }) {
serie.zigbee_thread && !String(serie.zigbee_thread).startsWith("Não") && !String(serie.zigbee_thread).startsWith("No") ? "802.15.4" : null, serie.zigbee_thread && !String(serie.zigbee_thread).startsWith("Não") && !String(serie.zigbee_thread).startsWith("No") ? "802.15.4" : null,
].filter(Boolean).join(", "); ].filter(Boolean).join(", ");
const descricao = isDe const descricao = isZh ? `${serie.nome_completo}:${serie.arquitetura},${serie.frequencia},` +
`${serie.memoria_sram} SRAM,${serie.gpio} 个 GPIO` +
(radio ? `。无线:${radio}` : "") +
`。含完整引脚图、外设、硬件安全与受限引脚的简体中文说明。` : isDe
? `${serie.nome_completo}: ${serie.arquitetura}, ${serie.frequencia}, ` + ? `${serie.nome_completo}: ${serie.arquitetura}, ${serie.frequencia}, ` +
`${serie.memoria_sram} SRAM und ${serie.gpio} GPIOs` + `${serie.memoria_sram} SRAM und ${serie.gpio} GPIOs` +
(radio ? `. Funk: ${radio}` : "") + (radio ? `. Funk: ${radio}` : "") +
@@ -938,10 +1021,10 @@ export async function generateMetadata({ params }) {
`. Pinagem completa, periféricos, segurança em hardware e pinos com restrição, em português.`; `. Pinagem completa, periféricos, segurança em hardware e pinos com restrição, em português.`;
const imagem = serie.placa const imagem = serie.placa
? [{ url: serie.placa, width: 1600, height: 1091, alt: isDe ? `Pinbelegung von ${serie.nome_completo}` : isFr ? `Brochage de ${serie.nome_completo}` : isEs ? `Patillaje de ${serie.nome_completo}` : isEn ? `Pinout of ${serie.nome_completo}` : `Pinagem do ${serie.nome_completo}` }] ? [{ url: serie.placa, width: 1600, height: 1091, alt: isZh ? `${serie.nome_completo} 的引脚图` : isDe ? `Pinbelegung von ${serie.nome_completo}` : isFr ? `Brochage de ${serie.nome_completo}` : isEs ? `Patillaje de ${serie.nome_completo}` : isEn ? `Pinout of ${serie.nome_completo}` : `Pinagem do ${serie.nome_completo}` }]
: [{ url: "/opengraph-image.png", width: 1200, height: 630, alt: "ESPDocs" }]; : [{ url: "/opengraph-image.png", width: 1200, height: 630, alt: "ESPDocs" }];
const keywords = isDe const keywords = isZh ? [ serie.nome_completo, `${serie.nome_completo} 引脚图`, `${serie.nome_completo} 数据手册`, `${serie.nome_completo} 规格`, "ESP32", "乐鑫", "微控制器", ] : isDe
? [ ? [
serie.nome_completo, `${serie.nome_completo} pinout`, `${serie.nome_completo} Pinbelegung`, serie.nome_completo, `${serie.nome_completo} pinout`, `${serie.nome_completo} Pinbelegung`,
`${serie.nome_completo} datasheet`, `${serie.nome_completo} Spezifikationen`, `${serie.nome_completo} datasheet`, `${serie.nome_completo} Spezifikationen`,
@@ -973,7 +1056,7 @@ export async function generateMetadata({ params }) {
const meta = { const meta = {
...paginaMeta({ ...paginaMeta({
titulo: isDe titulo: isZh ? `${serie.nome_completo}:规格与引脚图` : isDe
? `${serie.nome_completo}: Spezifikationen und Pinbelegung` ? `${serie.nome_completo}: Spezifikationen und Pinbelegung`
: isFr : isFr
? `${serie.nome_completo} : spécifications et brochage` ? `${serie.nome_completo} : spécifications et brochage`
@@ -989,7 +1072,7 @@ export async function generateMetadata({ params }) {
keywords, keywords,
}), }),
other: { other: {
"article:section": isDe "article:section": isZh ? "ESP32 系列" : isDe
? "ESP32-Serien" ? "ESP32-Serien"
: isFr : isFr
? "Séries ESP32" ? "Séries ESP32"
+21 -9
View File
@@ -6,8 +6,9 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
const meta = paginaMeta({ const meta = paginaMeta({
titulo: isDe titulo: isZh ? "ESP32 系列:技术指南与引脚图" : isDe
? "ESP32-Serien: Technischer Leitfaden und Pinbelegung" ? "ESP32-Serien: Technischer Leitfaden und Pinbelegung"
: isFr : isFr
? "Séries ESP32 : Guide technique et brochage" ? "Séries ESP32 : Guide technique et brochage"
@@ -16,7 +17,7 @@ export async function generateMetadata({ params }) {
: isEn : isEn
? "ESP32 Series: Technical Guide and Pinouts" ? "ESP32 Series: Technical Guide and Pinouts"
: "Séries ESP32", : "Séries ESP32",
descricao: isDe descricao: isZh ? "并排对比全部 12 个 ESP32 系列:ESP32、S2、S3、S31、C2、C3、C5、C6、C61、P4、H2 和 H4。每个系列的架构、内存、无线、外设与引脚图。" : isDe
? "Alle 12 ESP32-Serien Seite an Seite: ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 und H4. Architektur, Speicher, Funk, Peripherie und Pinbelegung jeder Serie." ? "Alle 12 ESP32-Serien Seite an Seite: ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 und H4. Architektur, Speicher, Funk, Peripherie und Pinbelegung jeder Serie."
: isFr : isFr
? "Les 12 séries d'ESP32 côte à côte : ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 et H4. Architecture, mémoire, radio, périphériques et brochage de chacune." ? "Les 12 séries d'ESP32 côte à côte : ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 et H4. Architecture, mémoire, radio, périphériques et brochage de chacune."
@@ -27,7 +28,7 @@ export async function generateMetadata({ params }) {
: "As 12 séries de ESP32 lado a lado: ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 e H4. Arquitetura, memória, rádio, periféricos e pinagem de cada uma, em português.", : "As 12 séries de ESP32 lado a lado: ESP32, S2, S3, S31, C2, C3, C5, C6, C61, P4, H2 e H4. Arquitetura, memória, rádio, periféricos e pinagem de cada uma, em português.",
caminho: "/series", caminho: "/series",
locale, locale,
keywords: isDe keywords: isZh ? ["ESP32 系列", "ESP32 型号", "ESP32 S3", "ESP32 C6", "ESP32 P4", "ESP32 区别"] : isDe
? ["ESP32-Serien", "ESP32-Modelle", "ESP32 S3", "ESP32 C6", "ESP32 P4", "Unterschied zwischen ESP32"] ? ["ESP32-Serien", "ESP32-Modelle", "ESP32 S3", "ESP32 C6", "ESP32 P4", "Unterschied zwischen ESP32"]
: isFr : isFr
? ["séries ESP32", "modèles ESP32", "ESP32 S3", "ESP32 C6", "ESP32 P4", "différence entre ESP32"] ? ["séries ESP32", "modèles ESP32", "ESP32 S3", "ESP32 C6", "ESP32 P4", "différence entre ESP32"]
@@ -38,7 +39,7 @@ export async function generateMetadata({ params }) {
: ["séries ESP32", "modelos de ESP32", "ESP32 S3", "ESP32 C6", "ESP32 P4", "diferença entre ESP32"], : ["séries ESP32", "modelos de ESP32", "ESP32 S3", "ESP32 C6", "ESP32 P4", "diferença entre ESP32"],
}); });
meta.title = { meta.title = {
default: isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32", default: isZh ? "ESP32 系列" : isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32",
template: "%s · ESPDocs", template: "%s · ESPDocs",
}; };
return meta; return meta;
@@ -98,6 +99,16 @@ const faqsDe = [
resposta: "Zu den neuesten Serien gehören der ESP32-P4 (fokussiert auf Hochleistung mit Dual-Core RISC-V bei 400 MHz), der ESP32-C5 (erster Chip mit Dual-Band 2,4/5 GHz Wi-Fi von Espressif) und der ESP32-C61 (kostengünstiges Wi-Fi 6 und Bluetooth 6).", resposta: "Zu den neuesten Serien gehören der ESP32-P4 (fokussiert auf Hochleistung mit Dual-Core RISC-V bei 400 MHz), der ESP32-C5 (erster Chip mit Dual-Band 2,4/5 GHz Wi-Fi von Espressif) und der ESP32-C61 (kostengünstiges Wi-Fi 6 und Bluetooth 6).",
}, },
]; ];
const faqsZh = [
{
pergunta: "目前一共有多少个 ESP32 系列?",
resposta: "目前乐鑫生态有 12 个主要系列:经典 ESP32、ESP32-S2、ESP32-S3、ESP32-S31、ESP32-C2、ESP32-C3、ESP32-C5、ESP32-C6、ESP32-C61、ESP32-P4、ESP32-H2 和 ESP32-H4。",
},
{
pergunta: "乐鑫最新的系列有哪些?",
resposta: "最新的系列包括 ESP32-P4(400 MHz 的高性能双核 RISC-V)、ESP32-C5(乐鑫首款 2.4/5 GHz 双频 Wi-Fi 芯片)以及 ESP32-C61(高性价比的 Wi-Fi 6 与蓝牙 6)。",
},
];
export default async function Layout({ children, params }) { export default async function Layout({ children, params }) {
const { locale } = await params; const { locale } = await params;
@@ -105,7 +116,8 @@ export default async function Layout({ children, params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const faqs = isDe ? faqsDe : isFr ? faqsFr : isEs ? faqsEs : isEn ? faqsEn : faqsPt; const isZh = locale === "zh-CN";
const faqs = isZh ? faqsZh : isDe ? faqsDe : isFr ? faqsFr : isEs ? faqsEs : isEn ? faqsEn : faqsPt;
return ( return (
<> <>
@@ -115,12 +127,12 @@ export default async function Layout({ children, params }) {
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ jsonLd.trilha([
{ {
nome: isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início", nome: isZh ? "首页" : isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início",
caminho: isDe ? "/de" : isFr ? "/fr" : isEs ? "/es" : isEn ? "/en" : "/", caminho: isZh ? "/zh-CN" : isDe ? "/de" : isFr ? "/fr" : isEs ? "/es" : isEn ? "/en" : "/",
}, },
{ {
nome: isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32", nome: isZh ? "ESP32 系列" : isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32",
caminho: isDe ? "/de/serien" : isFr ? "/fr/series" : isEs ? "/es/series" : isEn ? "/en/series" : "/series", caminho: isZh ? "/zh-CN/series" : isDe ? "/de/serien" : isFr ? "/fr/series" : isEs ? "/es/series" : isEn ? "/en/series" : "/series",
}, },
]) ])
), ),
+49 -32
View File
@@ -11,6 +11,7 @@ import seriesDataEn from "@/data/en/series.json";
import seriesDataEs from "@/data/es/series.json"; import seriesDataEs from "@/data/es/series.json";
import seriesDataFr from "@/data/fr/series.json"; import seriesDataFr from "@/data/fr/series.json";
import seriesDataDe from "@/data/de/series.json"; import seriesDataDe from "@/data/de/series.json";
import seriesDataZh from "@/data/zh-CN/series.json";
import { getLocalizedHref } from "@/i18n/links"; import { getLocalizedHref } from "@/i18n/links";
import { import {
AlertTriangle, AlertTriangle,
@@ -59,29 +60,37 @@ const FILTROS_DE = [
{ id: "todas", rotulo: "Alle", testa: () => true }, { id: "todas", rotulo: "Alle", testa: () => true },
{ id: "riscv", rotulo: "RISC-V", testa: (s) => s.arquitetura.includes("RISC-V") }, { id: "riscv", rotulo: "RISC-V", testa: (s) => s.arquitetura.includes("RISC-V") },
{ id: "xtensa", rotulo: "Xtensa", testa: (s) => !s.arquitetura.includes("RISC-V") }, { id: "xtensa", rotulo: "Xtensa", testa: (s) => !s.arquitetura.includes("RISC-V") },
{ id: "matter", rotulo: "Matter", testa: (s) => s.matter === "Ja" || s.matter === "Yes" || s.matter === "Sim" || s.matter === "Sí" }, { id: "matter", rotulo: "Matter", testa: (s) => (s.matter === "Ja" || s.matter === "是") || s.matter === "Yes" || s.matter === "Sim" || s.matter === "Sí" },
{ id: "ia", rotulo: "KI-Beschleunigung", testa: (s) => Boolean(s.aceleradores_ia) }, { id: "ia", rotulo: "KI-Beschleunigung", testa: (s) => Boolean(s.aceleradores_ia) },
]; ];
const FILTROS_ZH = [
{ id: "todas", rotulo: "全部", testa: () => true },
{ id: "riscv", rotulo: "RISC-V", testa: (s) => s.arquitetura.includes("RISC-V") },
{ id: "xtensa", rotulo: "Xtensa", testa: (s) => !s.arquitetura.includes("RISC-V") },
{ id: "matter", rotulo: "Matter", testa: (s) => String(s.matter).startsWith("是") },
{ id: "ia", rotulo: "AI 加速", testa: (s) => Boolean(s.aceleradores_ia) },
];
/** Marcador de segurança: o recurso que diferencia a série, não o que todas têm. */ /** Marcador de segurança: o recurso que diferencia a série, não o que todas têm. */
function nivelSeguranca(serie, isEn, isEs, isFr, isDe) { function nivelSeguranca(serie, isEn, isEs, isFr, isDe, isZh = false) {
const seg = serie.seguranca || {}; const seg = serie.seguranca || {};
if (seg.key_manager === "Sim" || seg.key_manager === "Yes" || seg.key_manager === "Sí" || seg.key_manager === "Oui" || seg.key_manager === "Ja") return "Key Manager"; if (seg.key_manager === "Sim" || seg.key_manager === "Yes" || seg.key_manager === "Sí" || seg.key_manager === "Oui" || (seg.key_manager === "Ja" || seg.key_manager === "是")) return "Key Manager";
if (String(seg.ecdsa).startsWith("Sim") || String(seg.ecdsa).startsWith("Yes") || String(seg.ecdsa).startsWith("Sí") || String(seg.ecdsa).startsWith("Oui") || String(seg.ecdsa).startsWith("Ja")) { if (String(seg.ecdsa).startsWith("Sim") || String(seg.ecdsa).startsWith("Yes") || String(seg.ecdsa).startsWith("Sí") || String(seg.ecdsa).startsWith("Oui") || (String(seg.ecdsa).startsWith("Ja") || String(seg.ecdsa).startsWith("是"))) {
return isDe ? "Hardware-ECDSA" : isFr ? "ECDSA matériel" : isEs ? "ECDSA en hardware" : isEn ? "Hardware ECDSA" : "ECDSA em hardware"; return isZh ? "硬件 ECDSA" : isDe ? "Hardware-ECDSA" : isFr ? "ECDSA matériel" : isEs ? "ECDSA en hardware" : isEn ? "Hardware ECDSA" : "ECDSA em hardware";
} }
if (String(seg.ecc).startsWith("Sim") || String(seg.ecc).startsWith("Yes") || String(seg.ecc).startsWith("Sí") || String(seg.ecc).startsWith("Oui") || String(seg.ecc).startsWith("Ja")) { if (String(seg.ecc).startsWith("Sim") || String(seg.ecc).startsWith("Yes") || String(seg.ecc).startsWith("Sí") || String(seg.ecc).startsWith("Oui") || (String(seg.ecc).startsWith("Ja") || String(seg.ecc).startsWith("是"))) {
return isDe ? "Hardware-ECC" : isFr ? "ECC matériel" : isEs ? "ECC en hardware" : isEn ? "Hardware ECC" : "ECC em hardware"; return isZh ? "硬件 ECC" : isDe ? "Hardware-ECC" : isFr ? "ECC matériel" : isEs ? "ECC en hardware" : isEn ? "Hardware ECC" : "ECC em hardware";
} }
if (String(seg.aes).startsWith("AES")) return "AES + SHA"; if (String(seg.aes).startsWith("AES")) return "AES + SHA";
return isDe ? "Nur SHA" : isFr ? "SHA uniquement" : isEs ? "Solo SHA" : isEn ? "SHA only" : "Só SHA"; return isZh ? "仅 SHA" : isDe ? "Nur SHA" : isFr ? "SHA uniquement" : isEs ? "Solo SHA" : isEn ? "SHA only" : "Só SHA";
} }
const simPt = (v) => !String(v).includes("Não"); const simPt = (v) => !String(v).includes("Não");
const simEn = (v) => !String(v).includes("No") && !String(v).includes("Não"); const simEn = (v) => !String(v).includes("No") && !String(v).includes("Não");
const simEs = (v) => !String(v).includes("No") && !String(v).includes("Não"); const simEs = (v) => !String(v).includes("No") && !String(v).includes("Não");
const simFr = (v) => !String(v).includes("Non") && !String(v).includes("No") && !String(v).includes("Não"); const simFr = (v) => !String(v).includes("Non") && !String(v).includes("No") && !String(v).includes("Não");
const simDe = (v) => !String(v).includes("Nein") && !String(v).includes("No") && !String(v).includes("Não"); const simDe = (v) => !(String(v).includes("Nein") || String(v).includes("否")) && !String(v).includes("No") && !String(v).includes("Não");
const simZh = (v) => !String(v).includes("否");
export default function Series() { export default function Series() {
const pathname = usePathname(); const pathname = usePathname();
@@ -89,9 +98,10 @@ export default function Series() {
const isEs = pathname?.startsWith("/es"); const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr"); const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de"); const isDe = pathname?.startsWith("/de");
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt"; const isZh = pathname?.startsWith("/zh-CN");
const seriesData = isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt; const currentLocale = isZh ? "zh-CN" : isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const filtros = isDe ? FILTROS_DE : isFr ? FILTROS_FR : isEs ? FILTROS_ES : isEn ? FILTROS_EN : FILTROS_PT; const seriesData = isZh ? seriesDataZh : isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
const filtros = isZh ? FILTROS_ZH : isDe ? FILTROS_DE : isFr ? FILTROS_FR : isEs ? FILTROS_ES : isEn ? FILTROS_EN : FILTROS_PT;
const [filtro, setFiltro] = useState("todas"); const [filtro, setFiltro] = useState("todas");
const todas = Object.entries(seriesData); const todas = Object.entries(seriesData);
@@ -107,11 +117,11 @@ export default function Series() {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries" }]} trail={[{ label: isZh ? "系列" : isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries" }]}
icon={Compass} icon={Compass}
label={ label={
isDe isZh ? `${todas.length} 个乐鑫系列` : isDe
? `${todas.length} Espressif-Familien` ? `${todas.length} Espressif-Familien`
: isFr : isFr
? `${todas.length} familles d'Espressif` ? `${todas.length} familles d'Espressif`
@@ -121,9 +131,9 @@ export default function Series() {
? `${todas.length} Espressif families` ? `${todas.length} Espressif families`
: `${todas.length} famílias da Espressif` : `${todas.length} famílias da Espressif`
} }
title={isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32"} title={isZh ? "ESP32 系列" : isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32"}
lede={ lede={
isDe isZh ? "每个系列都为特定类型的项目设计,从电池供电的传感器到 AI 加速器。展开卡片可查看引脚、外设与安全特性。" : isDe
? "Jede Serie wurde für einen bestimmten Projekttyp entwickelt, von Batteriesensoren bis hin zu KI-Beschleunigern. Öffnen Sie das Datenblatt für Pinbelegung, Peripherie und Sicherheit." ? "Jede Serie wurde für einen bestimmten Projekttyp entwickelt, von Batteriesensoren bis hin zu KI-Beschleunigern. Öffnen Sie das Datenblatt für Pinbelegung, Peripherie und Sicherheit."
: isFr : isFr
? "Chaque série a été conçue pour un type de projet spécifique, des capteurs sur batterie aux accélérateurs IA. Ouvrez la fiche pour consulter le brochage, les périphériques et la sécurité." ? "Chaque série a été conçue pour un type de projet spécifique, des capteurs sur batterie aux accélérateurs IA. Ouvrez la fiche pour consulter le brochage, les périphériques et la sécurité."
@@ -135,8 +145,8 @@ export default function Series() {
} }
/> />
<section className="shell pt-10 sm:pt-12" aria-label={isDe ? "Serienliste" : isFr ? "Liste des séries" : isEs ? "Lista de series" : isEn ? "Series list" : "Lista de séries"}> <section className="shell pt-10 sm:pt-12" aria-label={isZh ? "系列列表" : isDe ? "Serienliste" : isFr ? "Liste des séries" : isEs ? "Lista de series" : isEn ? "Series list" : "Lista de séries"}>
<div role="group" aria-label={isDe ? "Serien filtern" : isFr ? "Filtrer les séries" : isEs ? "Filtrar series" : isEn ? "Filter series" : "Filtrar séries"} className="flex flex-wrap gap-2"> <div role="group" aria-label={isZh ? "筛选系列" : isDe ? "Serien filtern" : isFr ? "Filtrer les séries" : isEs ? "Filtrar series" : isEn ? "Filter series" : "Filtrar séries"} className="flex flex-wrap gap-2">
{filtros.map((f) => ( {filtros.map((f) => (
<button key={f.id} type="button" aria-pressed={filtro === f.id} onClick={() => setFiltro(f.id)} className="chip-btn"> <button key={f.id} type="button" aria-pressed={filtro === f.id} onClick={() => setFiltro(f.id)} className="chip-btn">
{f.rotulo} {f.rotulo}
@@ -146,7 +156,7 @@ export default function Series() {
<p role="status" aria-live="polite" className="mt-4 text-sm text-muted"> <p role="status" aria-live="polite" className="mt-4 text-sm text-muted">
{series.length}{" "} {series.length}{" "}
{series.length === 1 {series.length === 1
? isDe ? isZh ? "个系列" : isDe
? "Serie" ? "Serie"
: isFr : isFr
? "série" ? "série"
@@ -155,7 +165,7 @@ export default function Series() {
: isEn : isEn
? "series" ? "series"
: "série" : "série"
: isDe : isZh ? "个系列" : isDe
? "Serien" ? "Serien"
: isFr : isFr
? "séries" ? "séries"
@@ -169,13 +179,20 @@ export default function Series() {
<div className="mt-4 grid gap-4 md:grid-cols-2 lg:grid-cols-3"> <div className="mt-4 grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{series.map(([key, serie]) => { {series.map(([key, serie]) => {
const riscv = serie.arquitetura.includes("RISC-V"); const riscv = serie.arquitetura.includes("RISC-V");
const fatos = isDe const fatos = isZh ? [
["内核", serie.nucleos],
["频率", serie.frequencia],
["SRAM", serie.memoria_sram],
["Wi-Fi", simZh(serie.wifi) ? "是" : "否"],
["蓝牙", String(serie.bluetooth).includes("否") ? "否" : "是"],
serie.consumo_energia?.deep_sleep ? ["深度睡眠", serie.consumo_energia.deep_sleep] : null,
].filter(Boolean) : isDe
? [ ? [
["Kerne", serie.nucleos], ["Kerne", serie.nucleos],
["Taktfrequenz", serie.frequencia], ["Taktfrequenz", serie.frequencia],
["SRAM", serie.memoria_sram], ["SRAM", serie.memoria_sram],
["Wi-Fi", simDe(serie.wifi) ? "Ja" : "Nein"], ["Wi-Fi", simDe(serie.wifi) ? "Ja" : "Nein"],
["Bluetooth", String(serie.bluetooth).includes("Nein") || String(serie.bluetooth).includes("No") || String(serie.bluetooth).includes("Não") ? "Nein" : "Ja"], ["Bluetooth", (String(serie.bluetooth).includes("Nein") || String(serie.bluetooth).includes("否")) || String(serie.bluetooth).includes("No") || String(serie.bluetooth).includes("Não") ? "Nein" : "Ja"],
serie.consumo_energia?.deep_sleep ? ["Deep sleep", serie.consumo_energia.deep_sleep] : null, serie.consumo_energia?.deep_sleep ? ["Deep sleep", serie.consumo_energia.deep_sleep] : null,
].filter(Boolean) ].filter(Boolean)
: isFr : isFr
@@ -225,7 +242,7 @@ export default function Series() {
{fatos.map(([rotulo, valor]) => ( {fatos.map(([rotulo, valor]) => (
<div key={rotulo} className="flex items-baseline justify-between gap-4 py-2"> <div key={rotulo} className="flex items-baseline justify-between gap-4 py-2">
<dt className="text-muted">{rotulo}</dt> <dt className="text-muted">{rotulo}</dt>
<dd className={`text-right font-medium ${valor === "Não" || valor === "No" || valor === "Non" || valor === "Nein" ? "text-muted" : "text-ink"}`}>{valor}</dd> <dd className={`text-right font-medium ${valor === "Não" || valor === "No" || valor === "Non" || (valor === "Nein" || valor === "否") ? "text-muted" : "text-ink"}`}>{valor}</dd>
</div> </div>
))} ))}
</dl> </dl>
@@ -234,14 +251,14 @@ export default function Series() {
{serie.aceleradores_ia && ( {serie.aceleradores_ia && (
<li className="chip chip-accent"> <li className="chip chip-accent">
<Sparkles className="h-3 w-3" aria-hidden="true" /> <Sparkles className="h-3 w-3" aria-hidden="true" />
{isDe ? "KI-Beschleuniger" : isFr ? "Accélérateurs IA" : isEs ? "Aceleradores de IA" : isEn ? "AI Accelerators" : "Aceleradores de IA"} {isZh ? "AI 加速器" : isDe ? "KI-Beschleuniger" : isFr ? "Accélérateurs IA" : isEs ? "Aceleradores de IA" : isEn ? "AI Accelerators" : "Aceleradores de IA"}
</li> </li>
)} )}
<li className="chip"> <li className="chip">
<ShieldCheck className="h-3 w-3 text-accent" aria-hidden="true" /> <ShieldCheck className="h-3 w-3 text-accent" aria-hidden="true" />
{nivelSeguranca(serie, isEn, isEs, isFr, isDe)} {nivelSeguranca(serie, isEn, isEs, isFr, isDe, isZh)}
</li> </li>
{(serie.matter === "Sim" || serie.matter === "Yes" || serie.matter === "Sí" || serie.matter === "Oui" || serie.matter === "Ja") && ( {(serie.matter === "Sim" || serie.matter === "Yes" || serie.matter === "Sí" || serie.matter === "Oui" || (serie.matter === "Ja" || serie.matter === "是")) && (
<li className="chip"> <li className="chip">
<Home className="h-3 w-3 text-accent" aria-hidden="true" /> <Home className="h-3 w-3 text-accent" aria-hidden="true" />
Matter Matter
@@ -250,13 +267,13 @@ export default function Series() {
{serie.status_documentacao && ( {serie.status_documentacao && (
<li className="chip"> <li className="chip">
<AlertTriangle className="h-3 w-3 text-gold" aria-hidden="true" /> <AlertTriangle className="h-3 w-3 text-gold" aria-hidden="true" />
{isDe ? "Vorläufige Dokumentation" : isFr ? "Documentation préliminaire" : isEs ? "Documentación preliminar" : isEn ? "Preliminary documentation" : "Documentação preliminar"} {isZh ? "初步文档" : isDe ? "Vorläufige Dokumentation" : isFr ? "Documentation préliminaire" : isEs ? "Documentación preliminar" : isEn ? "Preliminary documentation" : "Documentação preliminar"}
</li> </li>
)} )}
</ul> </ul>
<span className="mt-5 inline-flex items-center gap-1 text-sm font-medium text-accent"> <span className="mt-5 inline-flex items-center gap-1 text-sm font-medium text-accent">
{isDe ? "Datenblatt öffnen" : isFr ? "Ouvrir la fiche" : isEs ? "Abrir ficha" : isEn ? "Open datasheet" : "Abrir ficha"} {isZh ? "打开数据手册" : isDe ? "Datenblatt öffnen" : isFr ? "Ouvrir la fiche" : isEs ? "Abrir ficha" : isEn ? "Open datasheet" : "Abrir ficha"}
<ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
</span> </span>
</Link> </Link>
@@ -269,10 +286,10 @@ export default function Series() {
<div className="panel flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center"> <div className="panel flex flex-col items-start justify-between gap-6 p-7 sm:p-10 md:flex-row md:items-center">
<div className="max-w-2xl"> <div className="max-w-2xl">
<h2 className="section-title"> <h2 className="section-title">
{isDe ? "Unsicher zwischen zwei Serien?" : isFr ? "Vous hésitez entre deux séries ?" : isEs ? "¿Tiene dudas entre dos series?" : isEn ? "Unsure between two series?" : "Ficou em dúvida entre duas séries?"} {isZh ? "在两个系列之间犹豫?" : isDe ? "Unsicher zwischen zwei Serien?" : isFr ? "Vous hésitez entre deux séries ?" : isEs ? "¿Tiene dudas entre dos series?" : isEn ? "Unsure between two series?" : "Ficou em dúvida entre duas séries?"}
</h2> </h2>
<p className="mt-3 text-base text-muted"> <p className="mt-3 text-base text-muted">
{isDe {isZh ? "对比工具可把最多四个型号并排放在一起:内核、无线、内存、外设和安全。" : isDe
? "Der Vergleicher stellt bis zu vier Modelle gegenüber: Kerne, Funk, Speicher, Peripherie und Sicherheit." ? "Der Vergleicher stellt bis zu vier Modelle gegenüber: Kerne, Funk, Speicher, Peripherie und Sicherheit."
: isFr : isFr
? "Le comparateur place jusqu'à quatre modèles côte à côte : cœurs, radio, mémoire, périphériques et sécurité." ? "Le comparateur place jusqu'à quatre modèles côte à côte : cœurs, radio, mémoire, périphériques et sécurité."
@@ -285,7 +302,7 @@ export default function Series() {
</div> </div>
<Link href={getLocalizedHref("/comparacao", currentLocale)} className="btn btn-primary shrink-0"> <Link href={getLocalizedHref("/comparacao", currentLocale)} className="btn btn-primary shrink-0">
<Scale className="h-4 w-4" aria-hidden="true" /> <Scale className="h-4 w-4" aria-hidden="true" />
{isDe ? "Serien vergleichen" : isFr ? "Comparer les séries" : isEs ? "Comparar series" : isEn ? "Compare series" : "Comparar séries"} {isZh ? "对比系列" : isDe ? "Serien vergleichen" : isFr ? "Comparer les séries" : isEs ? "Comparar series" : isEn ? "Compare series" : "Comparar séries"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
+44 -27
View File
@@ -14,8 +14,9 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
return paginaMeta({ return paginaMeta({
titulo: isDe titulo: isZh ? "关于本项目" : isDe
? "Über das Projekt" ? "Über das Projekt"
: isFr : isFr
? "À propos du projet" ? "À propos du projet"
@@ -24,7 +25,7 @@ export async function generateMetadata({ params }) {
: isEn : isEn
? "About the Project" ? "About the Project"
: "Sobre o projeto", : "Sobre o projeto",
descricao: isDe descricao: isZh ? "ESPDocs 是一个独立平台,整理 ESP32 生态的技术文档。了解本项目以及如何参与贡献。" : isDe
? "ESPDocs ist eine unabhängige Plattform, die die technische Dokumentation des ESP32-Ökosystems strukturiert. Erfahren Sie mehr über das Projekt und wie Sie beitragen können." ? "ESPDocs ist eine unabhängige Plattform, die die technische Dokumentation des ESP32-Ökosystems strukturiert. Erfahren Sie mehr über das Projekt und wie Sie beitragen können."
: isFr : isFr
? "ESPDocs est une plateforme indépendante qui organise la documentation technique de l'écosystème ESP32. Découvrez le projet et comment contribuer." ? "ESPDocs est une plateforme indépendante qui organise la documentation technique de l'écosystème ESP32. Découvrez le projet et comment contribuer."
@@ -34,7 +35,7 @@ export async function generateMetadata({ params }) {
? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem. Learn about the project and how to contribute." ? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem. Learn about the project and how to contribute."
: "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro. Saiba como o projeto nasceu e como contribuir.", : "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro. Saiba como o projeto nasceu e como contribuir.",
caminho: "/sobre", caminho: "/sobre",
keywords: isDe keywords: isZh ? ["ESPDocs", "ESP32 文档", "开源 ESP32 项目"] : isDe
? ["ESPDocs", "ESP32 Dokumentation", "ESP32 Open-Source-Projekt", "Open Source"] ? ["ESPDocs", "ESP32 Dokumentation", "ESP32 Open-Source-Projekt", "Open Source"]
: isFr : isFr
? ["ESPDocs", "documentation ESP32", "projet open source ESP32", "open source"] ? ["ESPDocs", "documentation ESP32", "projet open source ESP32", "open source"]
@@ -54,6 +55,7 @@ export default async function Sobre({ params }) {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
const featuresPt = [ const featuresPt = [
{ icon: Languages, title: "Em português", desc: "Dados técnicos organizados e apresentados em português do Brasil." }, { icon: Languages, title: "Em português", desc: "Dados técnicos organizados e apresentados em português do Brasil." },
@@ -89,8 +91,14 @@ export default async function Sobre({ params }) {
{ icon: Globe, title: "Zentraler Zugang", desc: "Direkte Links zu offizieller Dokumentation, Repositories und Beispielen." }, { icon: Globe, title: "Zentraler Zugang", desc: "Direkte Links zu offizieller Dokumentation, Repositories und Beispielen." },
{ icon: Code, title: "Open Source", desc: "Beiträge sind auf Gitea herzlich willkommen." }, { icon: Code, title: "Open Source", desc: "Beiträge sind auf Gitea herzlich willkommen." },
]; ];
const featuresZh = [
{ icon: Languages, title: "精心整理的内容", desc: "结构化的技术数据,清晰呈现,没有数据手册的冗余。" },
{ icon: GitCompare, title: "可视化对比", desc: "用表格和图示选出合适的系列,无需翻阅十份数据手册。" },
{ icon: Globe, title: "集中入口", desc: "直达官方文档、代码仓库与示例的链接。" },
{ icon: Code, title: "开源", desc: "欢迎在 Gitea 上贡献。" },
];
const features = isDe ? featuresDe : isFr ? featuresFr : isEs ? featuresEs : isEn ? featuresEn : featuresPt; const features = isZh ? featuresZh : isDe ? featuresDe : isFr ? featuresFr : isEs ? featuresEs : isEn ? featuresEn : featuresPt;
return ( return (
<div className="site-canvas min-h-screen"> <div className="site-canvas min-h-screen">
@@ -104,8 +112,8 @@ export default async function Sobre({ params }) {
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: JSON.stringify( __html: JSON.stringify(
jsonLd.trilha([ jsonLd.trilha([
{ nome: isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isPt ? "Início" : "Home", caminho: isDe ? "/de" : isFr ? "/fr" : isEn ? "/en" : isEs ? "/es" : "/" }, { nome: isZh ? "首页" : isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isPt ? "Início" : "Home", caminho: isZh ? "/zh-CN" : isDe ? "/de" : isFr ? "/fr" : isEn ? "/en" : isEs ? "/es" : "/" },
{ nome: isDe ? "Über das Projekt" : isFr ? "À propos du projet" : isEs ? "Acerca del proyecto" : isPt ? "Sobre o Projeto" : "About the Project", caminho: isDe ? "/de/ueber-uns" : isFr ? "/fr/a-propos" : isEn ? "/en/about" : isEs ? "/es/acerca" : "/sobre" }, { nome: isZh ? "关于本项目" : isDe ? "Über das Projekt" : isFr ? "À propos du projet" : isEs ? "Acerca del proyecto" : isPt ? "Sobre o Projeto" : "About the Project", caminho: isZh ? "/zh-CN/about" : isDe ? "/de/ueber-uns" : isFr ? "/fr/a-propos" : isEn ? "/en/about" : isEs ? "/es/acerca" : "/sobre" },
]) ])
), ),
}} }}
@@ -118,13 +126,13 @@ export default async function Sobre({ params }) {
isEn={isEn} isEn={isEn}
isEs={isEs} isEs={isEs}
isFr={isFr} isFr={isFr}
isDe={isDe} isDe={isDe} isZh={isZh}
trail={[{ label: isDe ? "Über uns" : isFr ? "À propos" : isEs ? "Acerca" : isPt ? "Sobre" : "About" }]} trail={[{ label: isZh ? "关于" : isDe ? "Über uns" : isFr ? "À propos" : isEs ? "Acerca" : isPt ? "Sobre" : "About" }]}
icon={Info} icon={Info}
label={isDe ? "Unabhängiges Projekt" : isFr ? "Projet indépendant" : isEs ? "Proyecto independiente" : isPt ? "Projeto independente" : "Independent project"} label={isZh ? "独立项目" : isDe ? "Unabhängiges Projekt" : isFr ? "Projet indépendant" : isEs ? "Proyecto independiente" : isPt ? "Projeto independente" : "Independent project"}
title={isDe ? "Über ESPDocs" : isFr ? "À propos d'ESPDocs" : isEs ? "Acerca de ESPDocs" : isPt ? "Sobre o ESPDocs" : "About ESPDocs"} title={isZh ? "关于 ESPDocs" : isDe ? "Über ESPDocs" : isFr ? "À propos d'ESPDocs" : isEs ? "Acerca de ESPDocs" : isPt ? "Sobre o ESPDocs" : "About ESPDocs"}
lede={ lede={
isDe isZh ? "一个非官方平台,整理 ESP32 的技术信息,并直接链接到乐鑫官方文档。为开源硬件社区而做。" : isDe
? "Eine inoffizielle deutschsprachige Plattform, die technische Informationen zum ESP32 strukturiert und Sie mit der offiziellen Dokumentation von Espressif verbindet. Entwickelt für die Open-Hardware-Community." ? "Eine inoffizielle deutschsprachige Plattform, die technische Informationen zum ESP32 strukturiert und Sie mit der offiziellen Dokumentation von Espressif verbindet. Entwickelt für die Open-Hardware-Community."
: isFr : isFr
? "Une plateforme non officielle en français qui organise les informations techniques de l'ESP32 et vous oriente vers la documentation officielle d'Espressif. Conçue pour la communauté du matériel libre." ? "Une plateforme non officielle en français qui organise les informations techniques de l'ESP32 et vous oriente vers la documentation officielle d'Espressif. Conçue pour la communauté du matériel libre."
@@ -137,7 +145,7 @@ export default async function Sobre({ params }) {
> >
<a href={REPOSITORIO} target="_blank" rel="noopener noreferrer" className="btn btn-primary"> <a href={REPOSITORIO} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<GiteaMark className="h-4 w-4" /> <GiteaMark className="h-4 w-4" />
{isDe ? "Repository ansehen" : isFr ? "Voir le dépôt" : isEs ? "Ver el repositorio" : isPt ? "Ver o repositório" : "View repository"} {isZh ? "查看代码仓库" : isDe ? "Repository ansehen" : isFr ? "Voir le dépôt" : isEs ? "Ver el repositorio" : isPt ? "Ver o repositório" : "View repository"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
</PageHero> </PageHero>
@@ -145,9 +153,9 @@ export default async function Sobre({ params }) {
<div className="shell section grid grid-cols-1 gap-10 pt-12 sm:pt-16 lg:grid-cols-[1fr_22rem] lg:gap-14"> <div className="shell section grid grid-cols-1 gap-10 pt-12 sm:pt-16 lg:grid-cols-[1fr_22rem] lg:gap-14">
<div className="min-w-0 max-w-3xl space-y-12"> <div className="min-w-0 max-w-3xl space-y-12">
<section aria-labelledby="missao"> <section aria-labelledby="missao">
<h2 id="missao" className="section-title">{isDe ? "Mission" : isFr ? "Mission" : isEs ? "Misión" : isPt ? "Missão" : "Mission"}</h2> <h2 id="missao" className="section-title">{isZh ? "使命" : isDe ? "Mission" : isFr ? "Mission" : isEs ? "Misión" : isPt ? "Missão" : "Mission"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted"> <p className="mt-4 text-base leading-relaxed text-muted">
{isDe {isZh ? "通过一个整理规格、对比系列、介绍框架并集中官方文档链接的界面,让 ESP32 生态更易获取、更易理解。目标是节省固件与物联网项目的开发时间。" : isDe
? "Den Zugang zum und das Verständnis für das ESP32-Ökosystem durch eine deutsche Oberfläche erleichtern, die Spezifikationen bündelt, Serien vergleicht, Frameworks vorstellt und offizielle Dokumentationslinks zentralisiert. Ziel ist es, Entwicklungszeit bei Firmware und IoT-Projekten zu sparen." ? "Den Zugang zum und das Verständnis für das ESP32-Ökosystem durch eine deutsche Oberfläche erleichtern, die Spezifikationen bündelt, Serien vergleicht, Frameworks vorstellt und offizielle Dokumentationslinks zentralisiert. Ziel ist es, Entwicklungszeit bei Firmware und IoT-Projekten zu sparen."
: isFr : isFr
? "Faciliter l'accès et la compréhension de l'écosystème ESP32 avec une interface en français qui organise les spécifications, compare les séries, présente les frameworks et centralise les liens vers la documentation officielle. L'objectif est de vous faire gagner du temps dans le développement de firmware et d'IoT." ? "Faciliter l'accès et la compréhension de l'écosystème ESP32 avec une interface en français qui organise les spécifications, compare les séries, présente les frameworks et centralise les liens vers la documentation officielle. L'objectif est de vous faire gagner du temps dans le développement de firmware et d'IoT."
@@ -160,9 +168,9 @@ export default async function Sobre({ params }) {
</section> </section>
<section aria-labelledby="porque"> <section aria-labelledby="porque">
<h2 id="porque" className="section-title">{isDe ? "Warum es ESPDocs gibt" : isFr ? "Pourquoi ESPDocs existe" : isEs ? "¿Por qué existe ESPDocs?" : isPt ? "Por que o ESPDocs existe" : "Why ESPDocs exists"}</h2> <h2 id="porque" className="section-title">{isZh ? "ESPDocs 为什么存在" : isDe ? "Warum es ESPDocs gibt" : isFr ? "Pourquoi ESPDocs existe" : isEs ? "¿Por qué existe ESPDocs?" : isPt ? "Por que o ESPDocs existe" : "Why ESPDocs exists"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted"> <p className="mt-4 text-base leading-relaxed text-muted">
{isDe {isZh ? "ESP32 的信息常常分散在冗长的数据手册、技术参考手册(TRM)和零散的代码仓库中。ESPDocs 把这些数据浓缩并结构化,便于在深入工程文档之前快速评估。" : isDe
? "Informationen über den ESP32 sind oft über dichte Datenblätter, technische Referenzhandbücher (TRM) und sekundäre Repositories verstreut. ESPDocs verdichtet und strukturiert diese Daten für eine schnelle Vorabauswahl, bevor Sie tiefer in die offizielle Technik einsteigen." ? "Informationen über den ESP32 sind oft über dichte Datenblätter, technische Referenzhandbücher (TRM) und sekundäre Repositories verstreut. ESPDocs verdichtet und strukturiert diese Daten für eine schnelle Vorabauswahl, bevor Sie tiefer in die offizielle Technik einsteigen."
: isFr : isFr
? "Les informations sur l'ESP32 sont souvent dispersées entre des fiches techniques denses, des manuels de référence technique (TRM) et des dépôts secondaires. ESPDocs condense et structure ces données pour une évaluation rapide avant d'approfondir l'ingénierie officielle." ? "Les informations sur l'ESP32 sont souvent dispersées entre des fiches techniques denses, des manuels de référence technique (TRM) et des dépôts secondaires. ESPDocs condense et structure ces données pour une évaluation rapide avant d'approfondir l'ingénierie officielle."
@@ -188,9 +196,9 @@ export default async function Sobre({ params }) {
</section> </section>
<section aria-labelledby="contribuir"> <section aria-labelledby="contribuir">
<h2 id="contribuir" className="section-title">{isDe ? "Wie Sie beitragen können" : isFr ? "Comment contribuer" : isEs ? "Cómo contribuir" : isPt ? "Como contribuir" : "How to contribute"}</h2> <h2 id="contribuir" className="section-title">{isZh ? "如何参与贡献" : isDe ? "Wie Sie beitragen können" : isFr ? "Comment contribuer" : isEs ? "Cómo contribuir" : isPt ? "Como contribuir" : "How to contribute"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted"> <p className="mt-4 text-base leading-relaxed text-muted">
{isDe {isZh ? "欢迎提交翻译修正、表格改进和新的代码示例。项目与社区一同成长。" : isDe
? "Übersetzungskorrekturen, Tabellenanpassungen und neue Codebeispiele sind herzlich willkommen. Das Projekt wächst gemeinsam mit der Community." ? "Übersetzungskorrekturen, Tabellenanpassungen und neue Codebeispiele sind herzlich willkommen. Das Projekt wächst gemeinsam mit der Community."
: isFr : isFr
? "Les corrections de traduction, les ajustements de tableaux et les nouveaux exemples de code sont très appréciés. Le projet grandit avec la communauté." ? "Les corrections de traduction, les ajustements de tableaux et les nouveaux exemples de code sont très appréciés. Le projet grandit avec la communauté."
@@ -201,7 +209,7 @@ export default async function Sobre({ params }) {
: "Translation fixes, table refinements and new code examples are welcome. The project grows with the community."} : "Translation fixes, table refinements and new code examples are welcome. The project grows with the community."}
</p> </p>
<ol className="mt-6 space-y-3"> <ol className="mt-6 space-y-3">
{(isDe {(isZh ? ["在 Gitea 上 Fork 本仓库。", "实现改进,并核对官方技术资料。", "发起 Pull Request 并说明改动。"] : isDe
? [ ? [
"Erstellen Sie einen Fork des Repositories auf Gitea.", "Erstellen Sie einen Fork des Repositories auf Gitea.",
"Implementieren Sie die Verbesserungen und prüfen Sie die offiziellen technischen Referenzen.", "Implementieren Sie die Verbesserungen und prüfen Sie die offiziellen technischen Referenzen.",
@@ -242,9 +250,9 @@ export default async function Sobre({ params }) {
</section> </section>
<section aria-labelledby="acessibilidade"> <section aria-labelledby="acessibilidade">
<h2 id="acessibilidade" className="section-title">{isDe ? "Barrierefreiheit" : isFr ? "Accessibilité" : isEs ? "Accesibilidad" : isPt ? "Acessibilidade" : "Accessibility"}</h2> <h2 id="acessibilidade" className="section-title">{isZh ? "无障碍" : isDe ? "Barrierefreiheit" : isFr ? "Accessibilité" : isEs ? "Accesibilidad" : isPt ? "Acessibilidade" : "Accessibility"}</h2>
<p className="mt-4 text-base leading-relaxed text-muted"> <p className="mt-4 text-base leading-relaxed text-muted">
{isDe {isZh ? "ESPDocs 重视可读性、高对比度(WCAG AAA 级)和完整的键盘导航。如果你遇到任何无障碍或导航上的障碍,请在代码仓库中提交 issue。" : isDe
? "ESPDocs legt großen Wert auf Lesbarkeit, hohen Kontrast (WCAG-Stufe AAA) und vollständige Tastaturnavigation. Wenn Sie auf Lese- oder Navigationsbarrieren stoßen, eröffnen Sie bitte ein Issue im Repository." ? "ESPDocs legt großen Wert auf Lesbarkeit, hohen Kontrast (WCAG-Stufe AAA) und vollständige Tastaturnavigation. Wenn Sie auf Lese- oder Navigationsbarrieren stoßen, eröffnen Sie bitte ein Issue im Repository."
: isFr : isFr
? "ESPDocs privilégie la lisibilité, un contraste élevé (niveau AAA du WCAG) et une navigation complète au clavier. Si vous rencontrez un obstacle de lecture ou de navigation, ouvrez un ticket dans le dépôt." ? "ESPDocs privilégie la lisibilité, un contraste élevé (niveau AAA du WCAG) et une navigation complète au clavier. Si vous rencontrez un obstacle de lecture ou de navigation, ouvrez un ticket dans le dépôt."
@@ -257,7 +265,7 @@ export default async function Sobre({ params }) {
</section> </section>
</div> </div>
<aside className="space-y-5 lg:sticky lg:top-28 lg:self-start" aria-label={isDe ? "Autor und rechtlicher Hinweis" : isFr ? "Auteur et mentions légales" : isEs ? "Autor y aviso legal" : isPt ? "Autor e aviso legal" : "Author and legal notice"}> <aside className="space-y-5 lg:sticky lg:top-28 lg:self-start" aria-label={isZh ? "作者与法律声明" : isDe ? "Autor und rechtlicher Hinweis" : isFr ? "Auteur et mentions légales" : isEs ? "Autor y aviso legal" : isPt ? "Autor e aviso legal" : "Author and legal notice"}>
<div className="panel p-6 sm:p-7"> <div className="panel p-6 sm:p-7">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<span className="grid h-16 w-16 shrink-0 place-items-center rounded-2xl border border-line bg-white"> <span className="grid h-16 w-16 shrink-0 place-items-center rounded-2xl border border-line bg-white">
@@ -265,11 +273,20 @@ export default async function Sobre({ params }) {
</span> </span>
<div> <div>
<h2 className="font-display text-lg font-semibold text-ink">Lucas Rayan Guerra</h2> <h2 className="font-display text-lg font-semibold text-ink">Lucas Rayan Guerra</h2>
<p className="text-sm text-muted">{isDe ? "Gründer von Ciência Embarcada" : isFr ? "Créateur de Ciência Embarcada" : isEs ? "Creador de Ciência Embarcada" : isPt ? "Criador do Ciência Embarcada" : "Creator of Ciência Embarcada"}</p> <p className="text-sm text-muted">{isZh ? "Ciência Embarcada 创始人" : isDe ? "Gründer von Ciência Embarcada" : isFr ? "Créateur de Ciência Embarcada" : isEs ? "Creador de Ciência Embarcada" : isPt ? "Criador do Ciência Embarcada" : "Creator of Ciência Embarcada"}</p>
</div> </div>
</div> </div>
<div className="mt-5 space-y-3 text-sm leading-relaxed text-muted"> <div className="mt-5 space-y-3 text-sm leading-relaxed text-muted">
{isDe ? ( {isZh ? (
<>
<p>
系统开发技术员,UFRPE 计算机科学在读,区块链与密码学研究者。
</p>
<p>
Semine AgriTech 软件总监,并在 REC&apos;n&apos;Play 等活动中作演讲,分享硬件、物联网和网络安全方面的见解。
</p>
</>
) : isDe ? (
<> <>
<p> <p>
Systementwicklungstechniker, Informatikstudent an der UFRPE und Forscher im Bereich Blockchain und Kryptographie. Systementwicklungstechniker, Informatikstudent an der UFRPE und Forscher im Bereich Blockchain und Kryptographie.
@@ -319,15 +336,15 @@ export default async function Sobre({ params }) {
)} )}
</div> </div>
<a href="https://cienciaembarcada.com.br" target="_blank" rel="noopener noreferrer" className="btn btn-ghost mt-6 w-full"> <a href="https://cienciaembarcada.com.br" target="_blank" rel="noopener noreferrer" className="btn btn-ghost mt-6 w-full">
{isDe ? "Ciência Embarcada besuchen" : isFr ? "Visiter Ciência Embarcada" : isEs ? "Visitar Ciência Embarcada" : isPt ? "Visitar o Ciência Embarcada" : "Visit Ciência Embarcada"} {isZh ? "访问 Ciência Embarcada" : isDe ? "Ciência Embarcada besuchen" : isFr ? "Visiter Ciência Embarcada" : isEs ? "Visitar Ciência Embarcada" : isPt ? "Visitar o Ciência Embarcada" : "Visit Ciência Embarcada"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
</a> </a>
</div> </div>
<div className="callout callout-warn"> <div className="callout callout-warn">
<p className="font-semibold text-ink">{isDe ? "Rechtlicher Hinweis" : isFr ? "Mentions légales" : isEs ? "Aviso legal" : isPt ? "Aviso legal" : "Legal notice"}</p> <p className="font-semibold text-ink">{isZh ? "法律声明" : isDe ? "Rechtlicher Hinweis" : isFr ? "Mentions légales" : isEs ? "Aviso legal" : isPt ? "Aviso legal" : "Legal notice"}</p>
<p className="mt-1"> <p className="mt-1">
{isDe {isZh ? "ESPDocs 是一个聚合与整理平台。所引用的手册和代码仓库归乐鑫系统(Espressif Systems)所有。在关键任务或商业设计中,请始终查阅官方文档。" : isDe
? "ESPDocs ist ein Aggregator und Kurator. Die referenzierten Handbücher und Repositories sind Eigentum von Espressif Systems. Konsultieren Sie bei geschäftskritischen oder kommerziellen Projekten stets die offizielle Dokumentation." ? "ESPDocs ist ein Aggregator und Kurator. Die referenzierten Handbücher und Repositories sind Eigentum von Espressif Systems. Konsultieren Sie bei geschäftskritischen oder kommerziellen Projekten stets die offizielle Dokumentation."
: isFr : isFr
? "ESPDocs est une plateforme d'agrégation et de synthèse. Les manuels et dépôts référencés appartiennent à Espressif Systems. Pour les projets critiques ou commerciaux, consultez systématiquement la documentation officielle." ? "ESPDocs est une plateforme d'agrégation et de synthèse. Les manuels et dépôts référencés appartiennent à Espressif Systems. Pour les projets critiques ou commerciaux, consultez systématiquement la documentation officielle."
+2 -2
View File
@@ -3,12 +3,12 @@ import { construirIndiceBusca } from "@/lib/searchIndex";
export const dynamic = "force-static"; export const dynamic = "force-static";
export function generateStaticParams() { export function generateStaticParams() {
return [{ locale: "pt" }, { locale: "en" }, { locale: "es" }, { locale: "fr" }, { locale: "de" }]; return [{ locale: "pt" }, { locale: "en" }, { locale: "es" }, { locale: "fr" }, { locale: "de" }, { locale: "zh-CN" }];
} }
export async function GET(request, { params }) { export async function GET(request, { params }) {
const { locale } = await params; const { locale } = await params;
const loc = ["en", "es", "fr", "de"].includes(locale) ? locale : "pt"; const loc = ["en", "es", "fr", "de", "zh-CN"].includes(locale) ? locale : "pt";
return Response.json(construirIndiceBusca(loc), { return Response.json(construirIndiceBusca(loc), {
headers: { headers: {
"Cache-Control": "no-cache, no-store, must-revalidate", "Cache-Control": "no-cache, no-store, must-revalidate",
+26 -7
View File
@@ -78,6 +78,24 @@ const HEADER_MAP_DE = {
"Botões": "Taster", "Botões": "Taster",
"LEDs": "LEDs", "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": "LED",
};
export default function BoardCard({ board }) { export default function BoardCard({ board }) {
const pathname = usePathname() || ""; const pathname = usePathname() || "";
@@ -85,6 +103,7 @@ export default function BoardCard({ board }) {
const isEs = pathname === "/es" || pathname.startsWith("/es/"); const isEs = pathname === "/es" || pathname.startsWith("/es/");
const isFr = pathname === "/fr" || pathname.startsWith("/fr/"); const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
const isDe = pathname === "/de" || pathname.startsWith("/de/"); const isDe = pathname === "/de" || pathname.startsWith("/de/");
const isZh = pathname === "/zh-CN" || pathname.startsWith("/zh-CN/");
const mainFields = ["Nome", "Link", "Imagem", "Loja Oficial"]; const mainFields = ["Nome", "Link", "Imagem", "Loja Oficial"];
@@ -107,18 +126,18 @@ export default function BoardCard({ board }) {
{lojaOficial && ( {lojaOficial && (
<span className="chip chip-accent mb-3 w-fit"> <span className="chip chip-accent mb-3 w-fit">
<Award className="h-3.5 w-3.5" aria-hidden="true" /> <Award className="h-3.5 w-3.5" aria-hidden="true" />
{isEn ? "Official store" : isEs ? "Tienda oficial" : isFr ? "Boutique officielle" : isDe ? "Offizieller Shop" : "Loja oficial"} {isEn ? "Official store" : isEs ? "Tienda oficial" : isFr ? "Boutique officielle" : isZh ? "官方商店" : isDe ? "Offizieller Shop" : "Loja oficial"}
</span> </span>
)} )}
<h3 className="line-clamp-2 font-display text-lg font-semibold leading-snug tracking-[-0.02em] text-ink"> <h3 className="line-clamp-2 font-display text-lg font-semibold leading-snug tracking-[-0.02em] text-ink">
{board.Nome || (isEn ? "Generic board" : isEs ? "Placa genérica" : isFr ? "Carte générique" : isDe ? "Generisches Board" : "Placa genérica")} {board.Nome || (isEn ? "Generic board" : isEs ? "Placa genérica" : isFr ? "Carte générique" : isZh ? "通用开发板" : isDe ? "Generisches Board" : "Placa genérica")}
</h3> </h3>
{recursos.length > 0 && ( {recursos.length > 0 && (
<div className="mt-4"> <div className="mt-4">
<p className="text-xs font-medium text-muted"> <p className="text-xs font-medium text-muted">
{isEn ? "On-board features" : isEs ? "Recursos integrados" : isFr ? "Fonctionnalités intégrées" : isDe ? "Integrierte Funktionen" : "Recursos embarcados"} {isEn ? "On-board features" : isEs ? "Recursos integrados" : isFr ? "Fonctionnalités intégrées" : isZh ? "板载资源" : isDe ? "Integrierte Funktionen" : "Recursos embarcados"}
</p> </p>
<ul className="mt-2 flex flex-wrap gap-1.5"> <ul className="mt-2 flex flex-wrap gap-1.5">
{recursos.map((campo) => { {recursos.map((campo) => {
@@ -129,7 +148,7 @@ export default function BoardCard({ board }) {
? (HEADER_MAP_ES[campo] || campo) ? (HEADER_MAP_ES[campo] || campo)
: isFr : isFr
? (HEADER_MAP_FR[campo] || campo) ? (HEADER_MAP_FR[campo] || campo)
: isDe : isZh ? (HEADER_MAP_ZH[campo] || campo) : isDe
? (HEADER_MAP_DE[campo] || campo) ? (HEADER_MAP_DE[campo] || campo)
: campo; : campo;
return ( return (
@@ -140,7 +159,7 @@ export default function BoardCard({ board }) {
<Check className="h-3 w-3 text-ok" aria-hidden="true" /> <Check className="h-3 w-3 text-ok" aria-hidden="true" />
)} )}
{label} {label}
{opcional && <span className="sr-only">{isEn ? " (optional)" : isEs ? " (opcional)" : isFr ? " (optionnel)" : isDe ? " (optional)" : " (opcional)"}</span>} {opcional && <span className="sr-only">{isEn ? " (optional)" : isEs ? " (opcional)" : isFr ? " (optionnel)" : isZh ? "(可选)" : isDe ? " (optional)" : " (opcional)"}</span>}
</li> </li>
); );
})} })}
@@ -150,10 +169,10 @@ export default function BoardCard({ board }) {
{board.Link && ( {board.Link && (
<a href={board.Link} target="_blank" rel="noopener noreferrer" className="btn btn-primary mt-6 w-full"> <a href={board.Link} target="_blank" rel="noopener noreferrer" className="btn btn-primary mt-6 w-full">
{isEn ? "View on AliExpress" : isEs ? "Ver en AliExpress" : isFr ? "Voir sur AliExpress" : isDe ? "Auf AliExpress ansehen" : "Ver no AliExpress"} {isEn ? "View on AliExpress" : isEs ? "Ver en AliExpress" : isFr ? "Voir sur AliExpress" : isZh ? "在 AliExpress 查看" : isDe ? "Auf AliExpress ansehen" : "Ver no AliExpress"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" /> <ArrowUpRight className="h-4 w-4" aria-hidden="true" />
<span className="sr-only"> <span className="sr-only">
{isEn ? " (opens in new tab)" : isEs ? " (abre en una pestaña nueva)" : isFr ? " (ouvre un nouvel onglet)" : isDe ? " (öffnet in neuem Tab)" : " (abre em nova aba)"} {isEn ? " (opens in new tab)" : isEs ? " (abre en una pestaña nueva)" : isFr ? " (ouvre un nouvel onglet)" : isZh ? "(在新标签页打开)" : isDe ? " (öffnet in neuem Tab)" : " (abre em nova aba)"}
</span> </span>
</a> </a>
)} )}
+16 -15
View File
@@ -10,7 +10,8 @@ export default function CodeExample({ framework }) {
const isEs = pathname === "/es" || pathname.startsWith("/es/"); const isEs = pathname === "/es" || pathname.startsWith("/es/");
const isFr = pathname === "/fr" || pathname.startsWith("/fr/"); const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
const isDe = pathname === "/de" || pathname.startsWith("/de/"); const isDe = pathname === "/de" || pathname.startsWith("/de/");
const loc = isEn ? "en" : isEs ? "es" : isFr ? "fr" : isDe ? "de" : "pt"; const isZh = pathname === "/zh-CN" || pathname.startsWith("/zh-CN/");
const loc = isEn ? "en" : isEs ? "es" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt";
const examplesUrl = framework?.exemplos; const examplesUrl = framework?.exemplos;
@@ -35,7 +36,7 @@ export default function CodeExample({ framework }) {
? `// Ejemplos completos: ${examplesUrl || "ver repositorio"}\n` ? `// Ejemplos completos: ${examplesUrl || "ver repositorio"}\n`
: isFr : isFr
? `// Exemples complets : ${examplesUrl || "voir le dépôt"}\n` ? `// Exemples complets : ${examplesUrl || "voir le dépôt"}\n`
: isDe : isZh ? `// 完整示例:${examplesUrl || "见代码仓库"}\n` : isDe
? `// Vollständige Beispiele: ${examplesUrl || "Repository ansehen"}\n` ? `// Vollständige Beispiele: ${examplesUrl || "Repository ansehen"}\n`
: `// Exemplos completos: ${examplesUrl || "ver repositório"}\n` : `// Exemplos completos: ${examplesUrl || "ver repositório"}\n`
); );
@@ -78,7 +79,7 @@ export default function CodeExample({ framework }) {
? "// El archivo de ejemplo está vacío. Consulte el repositorio oficial.\n" ? "// El archivo de ejemplo está vacío. Consulte el repositorio oficial.\n"
: isFr : isFr
? "// Le fichier d'exemple est vide. Consultez le dépôt officiel.\n" ? "// Le fichier d'exemple est vide. Consultez le dépôt officiel.\n"
: isDe : isZh ? "// 示例文件为空,请查阅官方代码仓库。\n" : isDe
? "// Die Beispieldatei ist leer. Bitte konsultieren Sie das offizielle Repository.\n" ? "// Die Beispieldatei ist leer. Bitte konsultieren Sie das offizielle Repository.\n"
: "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n" : "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n"
); );
@@ -89,7 +90,7 @@ export default function CodeExample({ framework }) {
? "archivo vacío" ? "archivo vacío"
: isFr : isFr
? "fichier vide" ? "fichier vide"
: isDe : isZh ? "文件为空" : isDe
? "leere Datei" ? "leere Datei"
: "arquivo vazio" : "arquivo vazio"
); );
@@ -104,7 +105,7 @@ export default function CodeExample({ framework }) {
? "// No fue posible cargar el ejemplo.\n// Consulte el repositorio oficial para ver los ejemplos completos.\n" ? "// No fue posible cargar el ejemplo.\n// Consulte el repositorio oficial para ver los ejemplos completos.\n"
: isFr : isFr
? "// Impossible de charger l'exemple.\n// Consultez le dépôt officiel pour des exemples complets.\n" ? "// Impossible de charger l'exemple.\n// Consultez le dépôt officiel pour des exemples complets.\n"
: isDe : isZh ? "// 无法加载示例。\n// 完整示例请查阅官方代码仓库。\n" : isDe
? "// Beispiel konnte nicht geladen werden.\n// Vollständige Beispiele finden Sie im offiziellen Repository.\n" ? "// Beispiel konnte nicht geladen werden.\n// Vollständige Beispiele finden Sie im offiziellen Repository.\n"
: "// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n" : "// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n"
); );
@@ -116,7 +117,7 @@ export default function CodeExample({ framework }) {
? "fallo al cargar" ? "fallo al cargar"
: isFr : isFr
? "échec du chargement" ? "échec du chargement"
: isDe : isZh ? "加载失败" : isDe
? "Ladefehler" ? "Ladefehler"
: "falha ao carregar") : "falha ao carregar")
); );
@@ -151,7 +152,7 @@ export default function CodeExample({ framework }) {
? "Ejemplo de código" ? "Ejemplo de código"
: isFr : isFr
? "Exemple de code" ? "Exemple de code"
: isDe : isZh ? "代码示例" : isDe
? "Codebeispiel" ? "Codebeispiel"
: "Exemplo de código"} : "Exemplo de código"}
</h2> </h2>
@@ -162,7 +163,7 @@ export default function CodeExample({ framework }) {
? `Fragmento de inicio en ${framework?.linguagem}. Ajuste los pines y la configuración para su placa.` ? `Fragmento de inicio en ${framework?.linguagem}. Ajuste los pines y la configuración para su placa.`
: isFr : isFr
? `Extrait de départ en ${framework?.linguagem}. Adaptez les broches et la configuration à votre carte.` ? `Extrait de départ en ${framework?.linguagem}. Adaptez les broches et la configuration à votre carte.`
: isDe : isZh ? `${framework?.linguagem} 的起始代码片段,请根据你的开发板调整引脚和配置。` : isDe
? `Startbeispiel in ${framework?.linguagem}. Passen Sie Pins und Konfiguration an Ihr Board an.` ? `Startbeispiel in ${framework?.linguagem}. Passen Sie Pins und Konfiguration an Ihr Board an.`
: `Trecho de partida em ${framework?.linguagem}. Ajuste pinos e configuração à sua placa.`} : `Trecho de partida em ${framework?.linguagem}. Ajuste pinos e configuração à sua placa.`}
</p> </p>
@@ -173,13 +174,13 @@ export default function CodeExample({ framework }) {
{copied ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />} {copied ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
<span role="status"> <span role="status">
{copied {copied
? (isEn ? "Copied" : isEs ? "Copiado" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado") ? (isEn ? "Copied" : isEs ? "Copiado" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado")
: (isEn ? "Copy" : isEs ? "Copiar" : isFr ? "Copier" : isDe ? "Kopieren" : "Copiar")} : (isEn ? "Copy" : isEs ? "Copiar" : isFr ? "Copier" : isZh ? "复制" : isDe ? "Kopieren" : "Copiar")}
</span> </span>
</button> </button>
{examplesUrl && ( {examplesUrl && (
<a href={examplesUrl} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm"> <a href={examplesUrl} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm">
{isEn ? "More examples" : isEs ? "Más ejemplos" : isFr ? "Plus d'exemples" : isDe ? "Weitere Beispiele" : "Mais exemplos"} {isEn ? "More examples" : isEs ? "Más ejemplos" : isFr ? "Plus d'exemples" : isZh ? "更多示例" : isDe ? "Weitere Beispiele" : "Mais exemplos"}
<ExternalLink className="h-4 w-4" aria-hidden="true" /> <ExternalLink className="h-4 w-4" aria-hidden="true" />
<span className="sr-only"> <span className="sr-only">
{isEn {isEn
@@ -188,7 +189,7 @@ export default function CodeExample({ framework }) {
? " (abre en una pestaña nueva)" ? " (abre en una pestaña nueva)"
: isFr : isFr
? " (ouvre un nouvel onglet)" ? " (ouvre un nouvel onglet)"
: isDe : isZh ? "(在新标签页打开)" : isDe
? " (öffnet in neuem Tab)" ? " (öffnet in neuem Tab)"
: " (abre em nova aba)"} : " (abre em nova aba)"}
</span> </span>
@@ -208,7 +209,7 @@ export default function CodeExample({ framework }) {
? "Cargando ejemplo…" ? "Cargando ejemplo…"
: isFr : isFr
? "Chargement de l'exemple…" ? "Chargement de l'exemple…"
: isDe : isZh ? "正在加载示例…" : isDe
? "Beispiel wird geladen…" ? "Beispiel wird geladen…"
: "Carregando exemplo…") : "Carregando exemplo…")
: framework?.linguagem} : framework?.linguagem}
@@ -223,7 +224,7 @@ export default function CodeExample({ framework }) {
? `Código de ejemplo en ${framework?.linguagem}` ? `Código de ejemplo en ${framework?.linguagem}`
: isFr : isFr
? `Exemple de code en ${framework?.linguagem}` ? `Exemple de code en ${framework?.linguagem}`
: isDe : isZh ? `${framework?.linguagem} 示例代码` : isDe
? `Codebeispiel in ${framework?.linguagem}` ? `Codebeispiel in ${framework?.linguagem}`
: `Código de exemplo em ${framework?.linguagem}` : `Código de exemplo em ${framework?.linguagem}`
} }
@@ -247,7 +248,7 @@ export default function CodeExample({ framework }) {
? `No se pudo cargar el ejemplo local (${error}). Utilice “Más ejemplos” para ver el código oficial.` ? `No se pudo cargar el ejemplo local (${error}). Utilice “Más ejemplos” para ver el código oficial.`
: isFr : isFr
? `Impossible de charger l'exemple local (${error}). Utilisez « Plus d'exemples » pour consulter le code officiel.` ? `Impossible de charger l'exemple local (${error}). Utilisez « Plus d'exemples » pour consulter le code officiel.`
: isDe : isZh ? `无法加载本地示例(${error})。请使用“更多示例”查看官方代码。` : isDe
? `Lokales Beispiel konnte nicht geladen werden (${error}). Nutzen Sie „Weitere Beispiele“, um den offiziellen Code anzusehen.` ? `Lokales Beispiel konnte nicht geladen werden (${error}). Nutzen Sie „Weitere Beispiele“, um den offiziellen Code anzusehen.`
: `Não foi possível carregar o exemplo local (${error}). Use “Mais exemplos” para ver o código oficial.`} : `Não foi possível carregar o exemplo local (${error}). Use “Mais exemplos” para ver o código oficial.`}
</p> </p>
+70 -40
View File
@@ -124,6 +124,28 @@ const FILTER_CATEGORIES_DE = [
{ id: "I2C", name: "I2C", color: "#f59e0b" }, { id: "I2C", name: "I2C", color: "#f59e0b" },
{ id: "Antenne", name: "Antenne", color: "#06b6d4" }, { id: "Antenne", name: "Antenne", color: "#06b6d4" },
]; ];
const FILTER_CATEGORIES_ZH = [
{ id: "All", name: "全部", color: "#8b5cf6" },
{ id: "GND", name: "接地(GND)", color: "#64748b" },
{ id: "Power", name: "电源", color: "#ef4444" },
{ id: "GPIO", name: "GPIO", color: "#22c55e" },
{ id: "Low Power", name: "低功耗 GPIO", color: "#f59e0b" },
{ id: "ADC", name: "ADC", color: "#ec4899" },
{ id: "DAC", name: "DAC", color: "#a855f7" },
{ id: "Touch", name: "触摸", color: "#06b6d4" },
{ id: "UART", name: "UART", color: "#3b82f6" },
{ id: "SPI", name: "SPI", color: "#f43f5e" },
{ id: "USB", name: "USB", color: "#a855f7" },
{ id: "Clock", name: "时钟", color: "#eab308" },
{ id: "JTAG", name: "JTAG", color: "#6366f1" },
{ id: "Strapping", name: "启动配置", color: "#dc2626" },
{ id: "DSI", name: "显示(DSI)", color: "#f97316" },
{ id: "CSI", name: "摄像头(CSI)", color: "#14b8a6" },
{ id: "Ethernet", name: "以太网", color: "#0284c7" },
{ id: "SD", name: "SD 卡", color: "#10b981" },
{ id: "I2C", name: "I2C", color: "#f59e0b" },
{ id: "Antena", name: "天线", color: "#06b6d4" },
];
const type_colors = { const type_colors = {
power: "#ef4444", // high-voltage red power: "#ef4444", // high-voltage red
@@ -154,6 +176,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
const isEs = pathname?.startsWith("/es"); const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr"); const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de"); const isDe = pathname?.startsWith("/de");
const isZh = pathname?.startsWith("/zh-CN");
const router = useRouter(); const router = useRouter();
const pinosFiltro = useMemo(() => new Set((params.get("pinos") ?? "").split(",").filter(Boolean)), [params]); const pinosFiltro = useMemo(() => new Set((params.get("pinos") ?? "").split(",").filter(Boolean)), [params]);
const motivoFiltro = params.get("motivo") ?? ""; const motivoFiltro = params.get("motivo") ?? "";
@@ -171,7 +194,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? FILTER_CATEGORIES_ES ? FILTER_CATEGORIES_ES
: isFr : isFr
? FILTER_CATEGORIES_FR ? FILTER_CATEGORIES_FR
: isDe : isZh ? FILTER_CATEGORIES_ZH : isDe
? FILTER_CATEGORIES_DE ? FILTER_CATEGORIES_DE
: FILTER_CATEGORIES; : FILTER_CATEGORIES;
@@ -196,7 +219,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
const nomeCategoria = (cat) => { const nomeCategoria = (cat) => {
if (cat === "Strapping") { if (cat === "Strapping") {
return isEn ? "Boot mode" : isEs ? "Arranque (Boot)" : isFr ? "Démarrage (Boot)" : isDe ? "Bootmodus" : "Inicialização"; return isEn ? "Boot mode" : isEs ? "Arranque (Boot)" : isFr ? "Démarrage (Boot)" : isZh ? "启动配置" : isDe ? "Bootmodus" : "Inicialização";
} }
if (isEn) { if (isEn) {
const found = FILTER_CATEGORIES_EN.find((f) => f.id === cat); const found = FILTER_CATEGORIES_EN.find((f) => f.id === cat);
@@ -214,6 +237,10 @@ function ConnectionsDiagramInner({ connections, serie }) {
const found = FILTER_CATEGORIES_DE.find((f) => f.id === cat); const found = FILTER_CATEGORIES_DE.find((f) => f.id === cat);
if (found) return found.name; if (found) return found.name;
} }
if (isZh) {
const found = FILTER_CATEGORIES_ZH.find((f) => f.id === cat);
if (found) return found.name;
}
return cat; return cat;
}; };
@@ -270,7 +297,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="px-4 py-14 text-center"> <div className="px-4 py-14 text-center">
<Sliders className="mx-auto h-10 w-10 text-muted" aria-hidden="true" /> <Sliders className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<p className="mt-4 font-display text-xl font-semibold text-ink"> <p className="mt-4 font-display text-xl font-semibold text-ink">
{isEn ? "Pinout diagram unavailable" : isEs ? "Diagrama de conexiones no disponible" : isFr ? "Brochage non disponible" : isDe ? "Pinbelegungsdiagramm nicht verfügbar" : "Diagrama de conexões indisponível"} {isEn ? "Pinout diagram unavailable" : isEs ? "Diagrama de conexiones no disponible" : isFr ? "Brochage non disponible" : isZh ? "连接图暂不可用" : isDe ? "Pinbelegungsdiagramm nicht verfügbar" : "Diagrama de conexões indisponível"}
</p> </p>
<p className="mt-1 text-sm text-muted"> <p className="mt-1 text-sm text-muted">
{isEn {isEn
@@ -279,7 +306,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? "Este modelo todavía no dispone de un mapa detallado de conexiones y buses." ? "Este modelo todavía no dispone de un mapa detallado de conexiones y buses."
: isFr : isFr
? "Ce modèle ne dispose pas encore d'un plan détaillé des broches et des bus." ? "Ce modèle ne dispose pas encore d'un plan détaillé des broches et des bus."
: isDe : isZh ? "该型号尚无详细的连接与总线图。" : isDe
? "Dieses Modell verfügt noch über keinen detaillierten Pinbelegungs- und Busplan." ? "Dieses Modell verfügt noch über keinen detaillierten Pinbelegungs- und Busplan."
: "Este modelo ainda não tem um mapa detalhado de conexões e barramentos."} : "Este modelo ainda não tem um mapa detalhado de conexões e barramentos."}
</p> </p>
@@ -293,7 +320,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink"> <h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
{isEn ? "Interactive pinout diagram" : isEs ? "Diagrama de conexiones interactivo" : isFr ? "Brochage interactif" : isDe ? "Interaktives Pinbelegungsdiagramm" : "Diagrama de conexões interativo"} {isEn ? "Interactive pinout diagram" : isEs ? "Diagrama de conexiones interactivo" : isFr ? "Brochage interactif" : isZh ? "交互式连接图" : isDe ? "Interaktives Pinbelegungsdiagramm" : "Diagrama de conexões interativo"}
</h2> </h2>
<p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted"> <p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted">
{isEn ? ( {isEn ? (
@@ -302,7 +329,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
`Todas las conexiones y la multiplexación del ${serie.nome_completo}. Busque por nombre o número, filtre por función y abra un pin para ver los detalles.` `Todas las conexiones y la multiplexación del ${serie.nome_completo}. Busque por nombre o número, filtre por función y abra un pin para ver los detalles.`
) : isFr ? ( ) : isFr ? (
`Toutes les connexions et le multiplexage de l'${serie.nome_completo}. Recherchez par nom ou numéro, filtrez par fonction et ouvrez une broche pour voir les détails.` `Toutes les connexions et le multiplexage de l'${serie.nome_completo}. Recherchez par nom ou numéro, filtrez par fonction et ouvrez une broche pour voir les détails.`
) : isDe ? ( ) : isZh ? ( `${serie.nome_completo} 的全部连接与复用。可按名称或编号搜索、按功能筛选,并展开引脚查看详情。` ) : isDe ? (
`Alle Anschlüsse und das Multiplexing des ${serie.nome_completo}. Suchen Sie nach Name oder Nummer, filtern Sie nach Funktion und öffnen Sie einen Pin für Details.` `Alle Anschlüsse und das Multiplexing des ${serie.nome_completo}. Suchen Sie nach Name oder Nummer, filtern Sie nach Funktion und öffnen Sie einen Pin für Details.`
) : ( ) : (
`Todas as conexões e a multiplexação do ${serie.nome_completo}. Busque por nome ou número, filtre por função e abra um pino para ver os detalhes.` `Todas as conexões e a multiplexação do ${serie.nome_completo}. Busque por nome ou número, filtre por função e abra um pino para ver os detalhes.`
@@ -314,15 +341,15 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div role="status" className="callout flex flex-wrap items-center justify-between gap-3"> <div role="status" className="callout flex flex-wrap items-center justify-between gap-3">
<p className="text-sm text-ink"> <p className="text-sm text-ink">
<strong className="font-semibold"> <strong className="font-semibold">
{isEn ? "Filtered by search:" : isEs ? "Filtrado por la búsqueda:" : isFr ? "Filtré par la recherche :" : isDe ? "Nach Suche gefiltert:" : "Filtrado pela busca:"} {isEn ? "Filtered by search:" : isEs ? "Filtrado por la búsqueda:" : isFr ? "Filtré par la recherche :" : isZh ? "按搜索筛选:" : isDe ? "Nach Suche gefiltert:" : "Filtrado pela busca:"}
</strong>{" "} </strong>{" "}
{motivoFiltro || (isEn ? "selected pins" : isEs ? "pines seleccionados" : isFr ? "broches sélectionnées" : isDe ? "ausgewählte Pins" : "pinos selecionados")} ({pinosFiltro.size}{" "} {motivoFiltro || (isEn ? "selected pins" : isEs ? "pines seleccionados" : isFr ? "broches sélectionnées" : isZh ? "个引脚已选中" : isDe ? "ausgewählte Pins" : "pinos selecionados")} ({pinosFiltro.size}{" "}
{pinosFiltro.size === 1 {pinosFiltro.size === 1
? (isEn ? "pin" : isEs ? "pin" : isFr ? "broche" : isDe ? "Pin" : "pino") ? (isEn ? "pin" : isEs ? "pin" : isFr ? "broche" : isZh ? "个引脚" : isDe ? "Pin" : "pino")
: (isEn ? "pins" : isEs ? "pines" : isFr ? "broches" : isDe ? "Pins" : "pinos")}) : (isEn ? "pins" : isEs ? "pines" : isFr ? "broches" : isZh ? "个引脚" : isDe ? "Pins" : "pinos")})
</p> </p>
<button type="button" onClick={mostrarTodos} className="btn btn-ghost btn-sm"> <button type="button" onClick={mostrarTodos} className="btn btn-ghost btn-sm">
{isEn ? "Show all pins" : isEs ? "Mostrar todos los pines" : isFr ? "Afficher toutes les broches" : isDe ? "Alle Pins anzeigen" : "Mostrar todos os pinos"} {isEn ? "Show all pins" : isEs ? "Mostrar todos los pines" : isFr ? "Afficher toutes les broches" : isZh ? "显示全部引脚" : isDe ? "Alle Pins anzeigen" : "Mostrar todos os pinos"}
</button> </button>
</div> </div>
)} )}
@@ -330,7 +357,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="flex flex-col gap-3 sm:flex-row"> <div className="flex flex-col gap-3 sm:flex-row">
<div className="relative flex-1"> <div className="relative flex-1">
<label htmlFor="busca-pino" className="sr-only"> <label htmlFor="busca-pino" className="sr-only">
{isEn ? "Search connection by name or number" : isEs ? "Buscar conexión por nombre o número" : isFr ? "Rechercher une connexion par nom ou numéro" : isDe ? "Anschluss nach Name oder Nummer suchen" : "Buscar conexão por nome ou número"} {isEn ? "Search connection by name or number" : isEs ? "Buscar conexión por nombre o número" : isFr ? "Rechercher une connexion par nom ou numéro" : isZh ? "按名称或编号搜索连接" : isDe ? "Anschluss nach Name oder Nummer suchen" : "Buscar conexão por nome ou número"}
</label> </label>
<Search className="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted" aria-hidden="true" /> <Search className="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted" aria-hidden="true" />
<input <input
@@ -343,7 +370,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? "Buscar pin (ej.: GPIO1, TXD, XTAL)" ? "Buscar pin (ej.: GPIO1, TXD, XTAL)"
: isFr : isFr
? "Rechercher une broche (ex. : GPIO1, TXD, XTAL)" ? "Rechercher une broche (ex. : GPIO1, TXD, XTAL)"
: isDe : isZh ? "搜索引脚(例如:GPIO1、TXD、XTAL)" : isDe
? "Pin suchen (z. B.: GPIO1, TXD, XTAL)" ? "Pin suchen (z. B.: GPIO1, TXD, XTAL)"
: "Buscar pino (ex.: GPIO1, TXD, XTAL)" : "Buscar pino (ex.: GPIO1, TXD, XTAL)"
} }
@@ -353,21 +380,21 @@ function ConnectionsDiagramInner({ connections, serie }) {
className="field pl-11 pr-4 text-sm" className="field pl-11 pr-4 text-sm"
/> />
</div> </div>
<div role="group" aria-label={isEn ? "Display mode" : isEs ? "Modo de visualización" : isFr ? "Mode d'affichage" : isDe ? "Anzeigemodus" : "Modo de exibição"} className="flex gap-2"> <div role="group" aria-label={isEn ? "Display mode" : isEs ? "Modo de visualización" : isFr ? "Mode d'affichage" : isZh ? "显示模式" : isDe ? "Anzeigemodus" : "Modo de exibição"} className="flex gap-2">
<button type="button" onClick={() => setViewMode("grid")} aria-pressed={viewMode === "grid"} className="chip-btn"> <button type="button" onClick={() => setViewMode("grid")} aria-pressed={viewMode === "grid"} className="chip-btn">
<Grid className="h-4 w-4" aria-hidden="true" /> <Grid className="h-4 w-4" aria-hidden="true" />
{isEn ? "Grid" : isEs ? "Cuadrícula" : isFr ? "Grille" : isDe ? "Raster" : "Grade"} {isEn ? "Grid" : isEs ? "Cuadrícula" : isFr ? "Grille" : isZh ? "网格" : isDe ? "Raster" : "Grade"}
</button> </button>
<button type="button" onClick={() => setViewMode("list")} aria-pressed={viewMode === "list"} className="chip-btn"> <button type="button" onClick={() => setViewMode("list")} aria-pressed={viewMode === "list"} className="chip-btn">
<List className="h-4 w-4" aria-hidden="true" /> <List className="h-4 w-4" aria-hidden="true" />
{isEn ? "List" : isEs ? "Lista" : isFr ? "Liste" : isDe ? "Liste" : "Lista"} {isEn ? "List" : isEs ? "Lista" : isFr ? "Liste" : isZh ? "列表" : isDe ? "Liste" : "Lista"}
</button> </button>
</div> </div>
</div> </div>
<div> <div>
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<h3 className="text-sm font-semibold text-ink">{isEn ? "Pin function" : isEs ? "Función del pin" : isFr ? "Fonction de la broche" : isDe ? "Pin-Funktion" : "Função do pino"}</h3> <h3 className="text-sm font-semibold text-ink">{isEn ? "Pin function" : isEs ? "Función del pin" : isFr ? "Fonction de la broche" : isZh ? "引脚功能" : isDe ? "Pin-Funktion" : "Função do pino"}</h3>
<p role="status" aria-live="polite" className="text-sm text-muted"> <p role="status" aria-live="polite" className="text-sm text-muted">
{isEn ? ( {isEn ? (
`${visibleConnections.length} of ${connections.length} pins` `${visibleConnections.length} of ${connections.length} pins`
@@ -375,14 +402,14 @@ function ConnectionsDiagramInner({ connections, serie }) {
`${visibleConnections.length} de ${connections.length} pines` `${visibleConnections.length} de ${connections.length} pines`
) : isFr ? ( ) : isFr ? (
`${visibleConnections.length} sur ${connections.length} broches` `${visibleConnections.length} sur ${connections.length} broches`
) : isDe ? ( ) : isZh ? ( `共 ${connections.length} 个引脚,显示 ${visibleConnections.length} 个` ) : isDe ? (
`${visibleConnections.length} von ${connections.length} Pins` `${visibleConnections.length} von ${connections.length} Pins`
) : ( ) : (
`${visibleConnections.length} de ${connections.length} pinos` `${visibleConnections.length} de ${connections.length} pinos`
)} )}
</p> </p>
</div> </div>
<div role="group" aria-label={isEn ? "Filter by function" : isEs ? "Filtrar por función" : isFr ? "Filtrer par fonction" : isDe ? "Nach Funktion filtern" : "Filtrar por função"} className="mt-3 flex flex-wrap gap-2"> <div role="group" aria-label={isEn ? "Filter by function" : isEs ? "Filtrar por función" : isFr ? "Filtrer par fonction" : isZh ? "按功能筛选" : isDe ? "Nach Funktion filtern" : "Filtrar por função"} className="mt-3 flex flex-wrap gap-2">
{filterCategories.map((filter) => { {filterCategories.map((filter) => {
const isActive = activeFilter === filter.id; const isActive = activeFilter === filter.id;
const count = connections.filter((c) => (filter.id === "All" ? true : c.categorias.includes(filter.id))).length; const count = connections.filter((c) => (filter.id === "All" ? true : c.categorias.includes(filter.id))).length;
@@ -401,7 +428,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
{visibleConnections.length === 0 ? ( {visibleConnections.length === 0 ? (
<div className="rounded-2xl border border-dashed border-line-strong px-6 py-12 text-center"> <div className="rounded-2xl border border-dashed border-line-strong px-6 py-12 text-center">
<Search className="mx-auto h-8 w-8 text-muted" aria-hidden="true" /> <Search className="mx-auto h-8 w-8 text-muted" aria-hidden="true" />
<p className="mt-3 font-semibold text-ink">{isEn ? "No connections found" : isEs ? "No se encontraron conexiones" : isFr ? "Aucune connexion trouvée" : isDe ? "Keine Anschlüsse gefunden" : "Nenhuma conexão encontrada"}</p> <p className="mt-3 font-semibold text-ink">{isEn ? "No connections found" : isEs ? "No se encontraron conexiones" : isFr ? "Aucune connexion trouvée" : isZh ? "未找到连接" : isDe ? "Keine Anschlüsse gefunden" : "Nenhuma conexão encontrada"}</p>
<p className="mt-1 text-sm text-muted"> <p className="mt-1 text-sm text-muted">
{isEn {isEn
? "Adjust your search or return to all functions." ? "Adjust your search or return to all functions."
@@ -409,12 +436,12 @@ function ConnectionsDiagramInner({ connections, serie }) {
? "Ajuste la búsqueda o vuelva a todas las funciones." ? "Ajuste la búsqueda o vuelva a todas las funciones."
: isFr : isFr
? "Ajustez votre recherche ou revenez à toutes les fonctions." ? "Ajustez votre recherche ou revenez à toutes les fonctions."
: isDe : isZh ? "请调整搜索,或返回全部功能。" : isDe
? "Passen Sie Ihre Suche an oder kehren Sie zu allen Funktionen zurück." ? "Passen Sie Ihre Suche an oder kehren Sie zu allen Funktionen zurück."
: "Ajuste a busca ou volte para todas as funções."} : "Ajuste a busca ou volte para todas as funções."}
</p> </p>
<button type="button" onClick={() => { setActiveFilter("All"); setSearchQuery(""); if (pinosFiltro.size) mostrarTodos(); }} className="btn btn-primary btn-sm mt-5"> <button type="button" onClick={() => { setActiveFilter("All"); setSearchQuery(""); if (pinosFiltro.size) mostrarTodos(); }} className="btn btn-primary btn-sm mt-5">
{isEn ? "Clear search and filters" : isEs ? "Limpiar búsqueda y filtros" : isFr ? "Effacer la recherche et les filtres" : isDe ? "Suche und Filter zurücksetzen" : "Limpar busca e filtros"} {isEn ? "Clear search and filters" : isEs ? "Limpiar búsqueda y filtros" : isFr ? "Effacer la recherche et les filtres" : isZh ? "清除搜索和筛选" : isDe ? "Suche und Filter zurücksetzen" : "Limpar busca e filtros"}
</button> </button>
</div> </div>
) : viewMode === "grid" ? ( ) : viewMode === "grid" ? (
@@ -434,7 +461,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Pin ${connection.nome}, número ${connection.numero}${connection.avisos ? ", con restricciones de uso" : ""}` ? `Pin ${connection.nome}, número ${connection.numero}${connection.avisos ? ", con restricciones de uso" : ""}`
: isFr : isFr
? `Broche ${connection.nome}, numéro ${connection.numero}${connection.avisos ? ", avec restrictions d'utilisation" : ""}` ? `Broche ${connection.nome}, numéro ${connection.numero}${connection.avisos ? ", avec restrictions d'utilisation" : ""}`
: isDe : isZh ? `引脚 ${connection.nome},编号 ${connection.numero}${connection.avisos ? ",有使用限制" : ""}` : isDe
? `Pin ${connection.nome}, Nummer ${connection.numero}${connection.avisos ? ", mit Nutzungseinschränkungen" : ""}` ? `Pin ${connection.nome}, Nummer ${connection.numero}${connection.avisos ? ", mit Nutzungseinschränkungen" : ""}`
: `Pino ${connection.nome}, número ${connection.numero}${connection.avisos ? ", com restrições de uso" : ""}` : `Pino ${connection.nome}, número ${connection.numero}${connection.avisos ? ", com restrições de uso" : ""}`
} }
@@ -472,16 +499,16 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Conexiones del ${serie.nome_completo}` ? `Conexiones del ${serie.nome_completo}`
: isFr : isFr
? `Connexions de l'${serie.nome_completo}` ? `Connexions de l'${serie.nome_completo}`
: isDe : isZh ? `${serie.nome_completo} 的连接` : isDe
? `Anschlüsse des ${serie.nome_completo}` ? `Anschlüsse des ${serie.nome_completo}`
: `Conexões do ${serie.nome_completo}`} : `Conexões do ${serie.nome_completo}`}
</caption> </caption>
<thead> <thead>
<tr className="border-b border-line bg-surface-2 text-muted"> <tr className="border-b border-line bg-surface-2 text-muted">
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Pin" : isEs ? "Pin" : isFr ? "Broche" : isDe ? "Pin" : "Pino"}</th> <th scope="col" className="px-4 py-3 font-medium">{isEn ? "Pin" : isEs ? "Pin" : isFr ? "Broche" : isZh ? "引脚" : isDe ? "Pin" : "Pino"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Name" : isEs ? "Nombre" : isFr ? "Nom" : isDe ? "Name" : "Nome"}</th> <th scope="col" className="px-4 py-3 font-medium">{isEn ? "Name" : isEs ? "Nombre" : isFr ? "Nom" : isZh ? "名称" : isDe ? "Name" : "Nome"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Functions" : isEs ? "Funciones" : isFr ? "Fonctions" : isDe ? "Funktionen" : "Funções"}</th> <th scope="col" className="px-4 py-3 font-medium">{isEn ? "Functions" : isEs ? "Funciones" : isFr ? "Fonctions" : isZh ? "功能" : isDe ? "Funktionen" : "Funções"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Usage" : isEs ? "Uso" : isFr ? "Utilisation" : isDe ? "Nutzung" : "Uso"}</th> <th scope="col" className="px-4 py-3 font-medium">{isEn ? "Usage" : isEs ? "Uso" : isFr ? "Utilisation" : isZh ? "用途" : isDe ? "Nutzung" : "Uso"}</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-line"> <tbody className="divide-y divide-line">
@@ -501,7 +528,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Abrir detalles del pin ${connection.nome}, número ${connection.numero}` ? `Abrir detalles del pin ${connection.nome}, número ${connection.numero}`
: isFr : isFr
? `Ouvrir les détails de la broche ${connection.nome}, numéro ${connection.numero}` ? `Ouvrir les détails de la broche ${connection.nome}, numéro ${connection.numero}`
: isDe : isZh ? `展开引脚 ${connection.nome} 的详情,编号 ${connection.numero}` : isDe
? `Pindetails für ${connection.nome}, Nummer ${connection.numero} öffnen` ? `Pindetails für ${connection.nome}, Nummer ${connection.numero} öffnen`
: `Abrir detalhes do pino ${connection.nome}, número ${connection.numero}` : `Abrir detalhes do pino ${connection.nome}, número ${connection.numero}`
} }
@@ -519,12 +546,12 @@ function ConnectionsDiagramInner({ connections, serie }) {
{connection.avisos ? ( {connection.avisos ? (
<span className="inline-flex items-center gap-1.5 font-medium text-danger"> <span className="inline-flex items-center gap-1.5 font-medium text-danger">
<AlertTriangle className="h-4 w-4" aria-hidden="true" /> <AlertTriangle className="h-4 w-4" aria-hidden="true" />
{isEn ? "Restricted" : isEs ? "Restringido" : isFr ? "Restreint" : isDe ? "Eingeschränkt" : "Restrito"} {isEn ? "Restricted" : isEs ? "Restringido" : isFr ? "Restreint" : isZh ? "受限" : isDe ? "Eingeschränkt" : "Restrito"}
</span> </span>
) : ( ) : (
<span className="inline-flex items-center gap-1.5 text-ok"> <span className="inline-flex items-center gap-1.5 text-ok">
<CheckCircle className="h-4 w-4" aria-hidden="true" /> <CheckCircle className="h-4 w-4" aria-hidden="true" />
{isEn ? "Free" : isEs ? "Libre" : isFr ? "Libre" : isDe ? "Frei" : "Livre"} {isEn ? "Free" : isEs ? "Libre" : isFr ? "Libre" : isZh ? "可用" : isDe ? "Frei" : "Livre"}
</span> </span>
)} )}
</td> </td>
@@ -540,7 +567,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="callout callout-warn flex items-start gap-3"> <div className="callout callout-warn flex items-start gap-3">
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" /> <AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" />
<div> <div>
<p className="font-semibold text-ink">{isEn ? "Pinout notes" : isEs ? "Notas sobre el patillaje" : isFr ? "Remarques sur le brochage" : isDe ? "Hinweise zur Pinbelegung" : "Notas sobre a pinagem"}</p> <p className="font-semibold text-ink">{isEn ? "Pinout notes" : isEs ? "Notas sobre el patillaje" : isFr ? "Remarques sur le brochage" : isZh ? "引脚说明" : isDe ? "Hinweise zur Pinbelegung" : "Notas sobre a pinagem"}</p>
<ul className="mt-1 list-disc space-y-1 pl-5"> <ul className="mt-1 list-disc space-y-1 pl-5">
{isEn ? ( {isEn ? (
<> <>
@@ -557,7 +584,10 @@ function ConnectionsDiagramInner({ connections, serie }) {
<li>Les broches signalées par une icône d'avertissement ont un rôle spécial au démarrage ou des restrictions analogiques. Ouvrez la broche pour voir les détails.</li> <li>Les broches signalées par une icône d'avertissement ont un rôle spécial au démarrage ou des restrictions analogiques. Ouvrez la broche pour voir les détails.</li>
<li>Isolez les pistes tactiles capacitives des bruits RF couplés.</li> <li>Isolez les pistes tactiles capacitives des bruits RF couplés.</li>
</> </>
) : isDe ? ( ) : isZh ? ( <>
<li>带警告图标的引脚在启动时有特殊作用,或存在模拟功能限制。展开该引脚查看详情。</li>
<li>请将电容触摸电路与耦合了射频噪声的引脚隔离开。</li>
</> ) : isDe ? (
<> <>
<li>Pins mit einem Warnsymbol spielen eine besondere Rolle beim Booten oder unterliegen analogen Einschränkungen. Öffnen Sie den Pin für Details.</li> <li>Pins mit einem Warnsymbol spielen eine besondere Rolle beim Booten oder unterliegen analogen Einschränkungen. Öffnen Sie den Pin für Details.</li>
<li>Isolieren Sie kapazitive Touch-Leitungen von eingekoppeltem HF-Rauschen.</li> <li>Isolieren Sie kapazitive Touch-Leitungen von eingekoppeltem HF-Rauschen.</li>
@@ -602,7 +632,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Pin físico ${selectedConnection.numero}` ? `Pin físico ${selectedConnection.numero}`
: isFr : isFr
? `Broche physique ${selectedConnection.numero}` ? `Broche physique ${selectedConnection.numero}`
: isDe : isZh ? `物理引脚 ${selectedConnection.numero}` : isDe
? `Physischer Pin ${selectedConnection.numero}` ? `Physischer Pin ${selectedConnection.numero}`
: `Pino físico ${selectedConnection.numero}`} : `Pino físico ${selectedConnection.numero}`}
</p> </p>
@@ -611,7 +641,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<button <button
type="button" type="button"
onClick={fechar} onClick={fechar}
aria-label={isEn ? "Close pin details" : isEs ? "Cerrar detalles del pin" : isFr ? "Fermer les détails de la broche" : isDe ? "Pindetails schließen" : "Fechar detalhes do pino"} aria-label={isEn ? "Close pin details" : isEs ? "Cerrar detalles del pin" : isFr ? "Fermer les détails de la broche" : isZh ? "关闭引脚详情" : isDe ? "Pindetails schließen" : "Fechar detalhes do pino"}
className="grid h-11 w-11 shrink-0 place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink" className="grid h-11 w-11 shrink-0 place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink"
> >
<X className="h-5 w-5" aria-hidden="true" /> <X className="h-5 w-5" aria-hidden="true" />
@@ -621,7 +651,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="flex-1 space-y-6 overflow-y-auto p-5 sm:p-7"> <div className="flex-1 space-y-6 overflow-y-auto p-5 sm:p-7">
{selectedConnection.categorias.length > 0 && ( {selectedConnection.categorias.length > 0 && (
<section> <section>
<h4 className="text-sm font-semibold text-ink">{isEn ? "Categories" : isEs ? "Categorías" : isFr ? "Catégories" : isDe ? "Kategorien" : "Categorias"}</h4> <h4 className="text-sm font-semibold text-ink">{isEn ? "Categories" : isEs ? "Categorías" : isFr ? "Catégories" : isZh ? "类别" : isDe ? "Kategorien" : "Categorias"}</h4>
<ul className="mt-3 flex flex-wrap gap-2"> <ul className="mt-3 flex flex-wrap gap-2">
{selectedConnection.categorias.map((cat) => ( {selectedConnection.categorias.map((cat) => (
<li key={cat} className="chip"> <li key={cat} className="chip">
@@ -637,7 +667,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<section> <section>
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink"> <h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
<Zap className="h-4 w-4 text-danger" aria-hidden="true" /> <Zap className="h-4 w-4 text-danger" aria-hidden="true" />
{isEn ? "Power domain" : isEs ? "Dominio de alimentación" : isFr ? "Domaine d'alimentation" : isDe ? "Spannungsbereich" : "Domínio de alimentação"} {isEn ? "Power domain" : isEs ? "Dominio de alimentación" : isFr ? "Domaine d'alimentation" : isZh ? "供电域" : isDe ? "Spannungsbereich" : "Domínio de alimentação"}
</h4> </h4>
<ul className="tile mt-3 space-y-2"> <ul className="tile mt-3 space-y-2">
{selectedConnection.alimentacao.map((source) => ( {selectedConnection.alimentacao.map((source) => (
@@ -648,7 +678,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
)} )}
<section> <section>
<h4 className="text-sm font-semibold text-ink">{isEn ? "Available functions" : isEs ? "Funciones disponibles" : isFr ? "Fonctions disponibles" : isDe ? "Verfügbare Funktionen" : "Funções disponíveis"}</h4> <h4 className="text-sm font-semibold text-ink">{isEn ? "Available functions" : isEs ? "Funciones disponibles" : isFr ? "Fonctions disponibles" : isZh ? "可用功能" : isDe ? "Verfügbare Funktionen" : "Funções disponíveis"}</h4>
<ul className="tile mt-3 grid gap-2.5 sm:grid-cols-2"> <ul className="tile mt-3 grid gap-2.5 sm:grid-cols-2">
{selectedConnection.funcoes.map((func) => ( {selectedConnection.funcoes.map((func) => (
<li key={func} className="flex items-center gap-2.5 text-sm text-ink-soft"> <li key={func} className="flex items-center gap-2.5 text-sm text-ink-soft">
@@ -663,7 +693,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<section className="callout callout-danger"> <section className="callout callout-danger">
<h4 className="flex items-center gap-2 font-semibold text-ink"> <h4 className="flex items-center gap-2 font-semibold text-ink">
<AlertTriangle className="h-4 w-4 text-danger" aria-hidden="true" /> <AlertTriangle className="h-4 w-4 text-danger" aria-hidden="true" />
{isEn ? "Engineering notes" : isEs ? "Notas de ingeniería" : isFr ? "Notes d'ingénierie" : isDe ? "Entwicklungshinweise" : "Avisos de engenharia"} {isEn ? "Engineering notes" : isEs ? "Notas de ingeniería" : isFr ? "Notes d'ingénierie" : isZh ? "工程警告" : isDe ? "Entwicklungshinweise" : "Avisos de engenharia"}
</h4> </h4>
<ul className="mt-2 list-disc space-y-1.5 pl-5"> <ul className="mt-2 list-disc space-y-1.5 pl-5">
{selectedConnection.avisos.map((aviso) => ( {selectedConnection.avisos.map((aviso) => (
@@ -676,7 +706,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="border-t border-line p-4 sm:px-7"> <div className="border-t border-line p-4 sm:px-7">
<button type="button" onClick={fechar} className="btn btn-primary w-full"> <button type="button" onClick={fechar} className="btn btn-primary w-full">
{isEn ? "Close" : isEs ? "Cerrar" : isFr ? "Fermer" : isDe ? "Schließen" : "Fechar"} {isEn ? "Close" : isEs ? "Cerrar" : isFr ? "Fermer" : isZh ? "关闭" : isDe ? "Schließen" : "Fechar"}
</button> </button>
</div> </div>
</div> </div>
+38 -9
View File
@@ -153,6 +153,34 @@ const COLUMNS_DE = [
], ],
}, },
]; ];
const COLUMNS_ZH = [
{
title: "探索",
links: [
{ href: "/series", label: "ESP32 系列" },
{ href: "/comparacao", label: "对比工具" },
{ href: "/catalogo", label: "开发板目录" },
],
},
{
title: "工具",
links: [
{ href: "/seletor", label: "智能选型" },
{ href: "/diagnostico", label: "故障诊断" },
{ href: "/componentes", label: "Component Registry" },
{ href: "/frameworks", label: "框架与 SDK" },
],
},
{
title: "项目",
links: [
{ href: "/sobre", label: "关于 ESPDocs" },
{ href: "/glossario", label: "术语表" },
{ href: SITE.repositorio, label: "代码仓库", external: true },
{ href: `${SITE.repositorio}/issues`, label: "反馈问题", external: true },
],
},
];
export default function Footer() { export default function Footer() {
const pathname = usePathname() || ""; const pathname = usePathname() || "";
@@ -165,6 +193,7 @@ export default function Footer() {
const isEs = currentLocale === "es"; const isEs = currentLocale === "es";
const isFr = currentLocale === "fr"; const isFr = currentLocale === "fr";
const isDe = currentLocale === "de"; const isDe = currentLocale === "de";
const isZh = currentLocale === "zh-CN";
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale); const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -177,7 +206,7 @@ export default function Footer() {
? COLUMNS_ES ? COLUMNS_ES
: isFr : isFr
? COLUMNS_FR ? COLUMNS_FR
: isDe : isZh ? COLUMNS_ZH : isDe
? COLUMNS_DE ? COLUMNS_DE
: COLUMNS_EN; : COLUMNS_EN;
@@ -191,7 +220,7 @@ export default function Footer() {
? "Pie de página de ESPDocs" ? "Pie de página de ESPDocs"
: isFr : isFr
? "Pied de page d'ESPDocs" ? "Pied de page d'ESPDocs"
: isDe : isZh ? "ESPDocs 页脚" : isDe
? "ESPDocs-Fußzeile" ? "ESPDocs-Fußzeile"
: "ESPDocs footer" : "ESPDocs footer"
} }
@@ -209,7 +238,7 @@ export default function Footer() {
? "ESPDocs, página de inicio" ? "ESPDocs, página de inicio"
: isFr : isFr
? "ESPDocs, page d'accueil" ? "ESPDocs, page d'accueil"
: isDe : isZh ? "ESPDocs,首页" : isDe
? "ESPDocs, Startseite" ? "ESPDocs, Startseite"
: "ESPDocs, homepage" : "ESPDocs, homepage"
} }
@@ -233,7 +262,7 @@ export default function Footer() {
Documentation indépendante de l&apos;écosystème ESP32. Documentation indépendante de l&apos;écosystème ESP32.
Données publiques, sélection éditoriale et liens vers les sources officielles. Données publiques, sélection éditoriale et liens vers les sources officielles.
</> </>
) : isDe ? ( ) : isZh ? ( <>ESP32 生态的独立文档。公开数据、编辑整理,并链接到官方资料来源。</> ) : isDe ? (
<> <>
Unabhängige Dokumentation des ESP32-Ökosystems. Unabhängige Dokumentation des ESP32-Ökosystems.
Öffentliche Daten, redaktionelle Betreuung und Links zu offiziellen Quellen. Öffentliche Daten, redaktionelle Betreuung und Links zu offiziellen Quellen.
@@ -312,7 +341,7 @@ export default function Footer() {
))} ))}
<div> <div>
<h2 className="text-sm font-semibold text-ink">{isPt ? "Autor" : isEs ? "Autor" : isFr ? "Auteur" : isDe ? "Autor" : "Author"}</h2> <h2 className="text-sm font-semibold text-ink">{isPt ? "Autor" : isEs ? "Autor" : isFr ? "Auteur" : isZh ? "作者" : isDe ? "Autor" : "Author"}</h2>
<p className="mt-4 text-sm leading-relaxed text-muted"> <p className="mt-4 text-sm leading-relaxed text-muted">
Lucas Rayan Guerra Lucas Rayan Guerra
<br /> <br />
@@ -330,7 +359,7 @@ export default function Footer() {
? "Gitea del proyecto (abre en una pestaña nueva)" ? "Gitea del proyecto (abre en una pestaña nueva)"
: isFr : isFr
? "Gitea du projet (ouvre un nouvel onglet)" ? "Gitea du projet (ouvre un nouvel onglet)"
: isDe : isZh ? "项目 Gitea(在新标签页打开)" : isDe
? "Projekt-Gitea (öffnet in neuem Tab)" ? "Projekt-Gitea (öffnet in neuem Tab)"
: "Project Gitea (opens in new tab)" : "Project Gitea (opens in new tab)"
} }
@@ -349,7 +378,7 @@ export default function Footer() {
? "LinkedIn del autor (abre en una pestaña nueva)" ? "LinkedIn del autor (abre en una pestaña nueva)"
: isFr : isFr
? "LinkedIn de l'auteur (ouvre un nouvel onglet)" ? "LinkedIn de l'auteur (ouvre un nouvel onglet)"
: isDe : isZh ? "作者 LinkedIn(在新标签页打开)" : isDe
? "LinkedIn des Autors (öffnet in neuem Tab)" ? "LinkedIn des Autors (öffnet in neuem Tab)"
: "Author LinkedIn (opens in new tab)" : "Author LinkedIn (opens in new tab)"
} }
@@ -373,7 +402,7 @@ export default function Footer() {
? "ESPDocs. Proyecto independiente, sin afiliación con Espressif Systems." ? "ESPDocs. Proyecto independiente, sin afiliación con Espressif Systems."
: isFr : isFr
? "ESPDocs. Projet indépendant, sans affiliation avec Espressif Systems." ? "ESPDocs. Projet indépendant, sans affiliation avec Espressif Systems."
: isDe : isZh ? "ESPDocs。独立项目,与乐鑫(Espressif Systems)无任何关联。" : isDe
? "ESPDocs. Unabhängiges Projekt, nicht verbunden mit Espressif Systems." ? "ESPDocs. Unabhängiges Projekt, nicht verbunden mit Espressif Systems."
: "ESPDocs. Independent project, not affiliated with Espressif Systems."} : "ESPDocs. Independent project, not affiliated with Espressif Systems."}
</p> </p>
@@ -393,7 +422,7 @@ export default function Footer() {
? "Fuentes oficiales: Espressif Systems." ? "Fuentes oficiales: Espressif Systems."
: isFr : isFr
? "Sources officielles : Espressif Systems." ? "Sources officielles : Espressif Systems."
: isDe : isZh ? "官方资料来源:乐鑫(Espressif Systems)。" : isDe
? "Offizielle Quellen: Espressif Systems." ? "Offizielle Quellen: Espressif Systems."
: "Official sources: Espressif Systems."} : "Official sources: Espressif Systems."}
</span> </span>
+60 -19
View File
@@ -177,6 +177,35 @@ const MENUS_DE = [
], ],
}, },
]; ];
const MENUS_ZH = [
{
id: "hardware",
label: "硬件",
items: [
{ href: "/series", label: "ESP32 系列", desc: "12 款芯片的引脚与规格", icon: Cpu },
{ href: "/comparacao", label: "对比工具", desc: "最多四个系列并排对比", icon: Scale },
{ href: "/catalogo", label: "开发板目录", desc: "DevKit、引脚与购买渠道", icon: Box },
],
},
{
id: "tools",
label: "工具",
items: [
{ href: "/seletor", label: "智能选型", desc: "为你的项目找到合适的芯片", icon: SlidersHorizontal },
{ href: "/diagnostico", label: "故障诊断", desc: "欠压、启动循环、看门狗等问题", icon: AlertTriangle },
{ href: "/componentes", label: "Component Registry", desc: "驱动与 idf_component.yml 清单", icon: Search },
],
},
{
id: "library",
label: "资料库",
items: [
{ href: "/frameworks", label: "框架与 SDK", desc: "ESP-IDF、Arduino、Rust 与 Matter", icon: BookOpen },
{ href: "/glossario", label: "技术术语表", desc: "数据手册中出现的术语", icon: BookA },
{ href: "/sobre", label: "关于本项目", desc: "作者、方法与开放性", icon: Sparkles },
],
},
];
const MOBILE_LINKS_PT = [ const MOBILE_LINKS_PT = [
{ href: "/series", label: "Séries ESP32" }, { href: "/series", label: "Séries ESP32" },
@@ -238,6 +267,17 @@ const MOBILE_LINKS_DE = [
{ href: "/catalogo", label: "Board-Katalog" }, { href: "/catalogo", label: "Board-Katalog" },
{ href: "/sobre", label: "Über uns" }, { href: "/sobre", label: "Über uns" },
]; ];
const MOBILE_LINKS_ZH = [
{ href: "/series", label: "ESP32 系列" },
{ href: "/comparacao", label: "对比工具" },
{ href: "/seletor", label: "智能选型" },
{ href: "/diagnostico", label: "故障诊断" },
{ href: "/frameworks", label: "框架与 SDK" },
{ href: "/componentes", label: "Component Registry" },
{ href: "/glossario", label: "术语表" },
{ href: "/catalogo", label: "开发板目录" },
{ href: "/sobre", label: "关于" },
];
function isActivePath(pathname, href, currentLocale) { function isActivePath(pathname, href, currentLocale) {
const localized = getLocalizedHref(`/${currentLocale}${href}`, currentLocale); const localized = getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -263,6 +303,7 @@ export default function Header() {
const isEs = currentLocale === "es"; const isEs = currentLocale === "es";
const isFr = currentLocale === "fr"; const isFr = currentLocale === "fr";
const isDe = currentLocale === "de"; const isDe = currentLocale === "de";
const isZh = currentLocale === "zh-CN";
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale); const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -308,10 +349,10 @@ export default function Header() {
? MOBILE_LINKS_ES ? MOBILE_LINKS_ES
: isFr : isFr
? MOBILE_LINKS_FR ? MOBILE_LINKS_FR
: isDe : isZh ? MOBILE_LINKS_ZH : isDe
? MOBILE_LINKS_DE ? MOBILE_LINKS_DE
: MOBILE_LINKS_EN; : MOBILE_LINKS_EN;
const MENUS = isPt ? MENUS_PT : isEs ? MENUS_ES : isFr ? MENUS_FR : isDe ? MENUS_DE : MENUS_EN; const MENUS = isPt ? MENUS_PT : isEs ? MENUS_ES : isFr ? MENUS_FR : isZh ? MENUS_ZH : isDe ? MENUS_DE : MENUS_EN;
return ( return (
<header className="sticky top-0 z-50 pt-3 sm:pt-4"> <header className="sticky top-0 z-50 pt-3 sm:pt-4">
@@ -327,7 +368,7 @@ export default function Header() {
? "ESPDocs, página de inicio" ? "ESPDocs, página de inicio"
: isFr : isFr
? "ESPDocs, page d'accueil" ? "ESPDocs, page d'accueil"
: isDe : isZh ? "ESPDocs,首页" : isDe
? "ESPDocs, Startseite" ? "ESPDocs, Startseite"
: "ESPDocs, homepage" : "ESPDocs, homepage"
} }
@@ -346,7 +387,7 @@ export default function Header() {
? "Navegación principal" ? "Navegación principal"
: isFr : isFr
? "Navigation principale" ? "Navigation principale"
: isDe : isZh ? "主导航" : isDe
? "Hauptnavigation" ? "Hauptnavigation"
: "Main navigation" : "Main navigation"
} }
@@ -403,18 +444,18 @@ export default function Header() {
</Link> </Link>
)} )}
<Link href={getLinkHref("/sobre")} className={`nav-link ${isActivePath(pathname, "/sobre", currentLocale) ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/sobre", currentLocale) ? "page" : undefined}> <Link href={getLinkHref("/sobre")} className={`nav-link ${isActivePath(pathname, "/sobre", currentLocale) ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/sobre", currentLocale) ? "page" : undefined}>
{isPt ? "Sobre" : isEs ? "Acerca de" : isFr ? "À propos" : isDe ? "Über uns" : "About"} {isPt ? "Sobre" : isEs ? "Acerca de" : isFr ? "À propos" : isZh ? "关于" : isDe ? "Über uns" : "About"}
</Link> </Link>
</nav> </nav>
<div className="flex items-center gap-1 sm:gap-1.5"> <div className="flex items-center gap-1 sm:gap-1.5">
<Link href={getLinkHref("/seletor")} className="btn btn-primary btn-sm hidden lg:inline-flex"> <Link href={getLinkHref("/seletor")} className="btn btn-primary btn-sm hidden lg:inline-flex">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" /> <SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{isPt ? "Selecionar chip" : isEs ? "Seleccionar chip" : isFr ? "Choisir une puce" : isDe ? "Chip auswählen" : "Select chip"} {isPt ? "Selecionar chip" : isEs ? "Seleccionar chip" : isFr ? "Choisir une puce" : isZh ? "选择芯片" : isDe ? "Chip auswählen" : "Select chip"}
</Link> </Link>
<SearchDialog /> <SearchDialog />
<LanguageSelector /> <LanguageSelector />
<ThemeToggle isPt={isPt} isEs={isEs} isFr={isFr} isDe={isDe} /> <ThemeToggle isPt={isPt} isEs={isEs} isFr={isFr} isDe={isDe} isZh={isZh} />
<button <button
ref={menuButtonRef} ref={menuButtonRef}
type="button" type="button"
@@ -426,7 +467,7 @@ export default function Header() {
? "Abrir menú de navegación" ? "Abrir menú de navegación"
: isFr : isFr
? "Ouvrir le menu de navigation" ? "Ouvrir le menu de navigation"
: isDe : isZh ? "打开导航菜单" : isDe
? "Navigationsmenü öffnen" ? "Navigationsmenü öffnen"
: "Open navigation menu" : "Open navigation menu"
} }
@@ -459,7 +500,7 @@ export default function Header() {
? "Menú de navegación" ? "Menú de navegación"
: isFr : isFr
? "Menu de navigation" ? "Menu de navigation"
: isDe : isZh ? "导航菜单" : isDe
? "Navigationsmenü" ? "Navigationsmenü"
: "Navigation menu" : "Navigation menu"
} }
@@ -472,10 +513,10 @@ export default function Header() {
<div className="flex items-start justify-between px-5 py-5"> <div className="flex items-start justify-between px-5 py-5">
<div> <div>
<p className="text-xs font-semibold text-muted"> <p className="text-xs font-semibold text-muted">
{isPt ? "Navegação" : isEs ? "Navegación" : isFr ? "Navigation" : isDe ? "Navigation" : "Navigation"} {isPt ? "Navegação" : isEs ? "Navegación" : isFr ? "Navigation" : isZh ? "导航" : isDe ? "Navigation" : "Navigation"}
</p> </p>
<p className="mt-1 font-display text-2xl font-semibold tracking-[-0.04em] text-ink"> <p className="mt-1 font-display text-2xl font-semibold tracking-[-0.04em] text-ink">
{isPt ? "Para onde ir" : isEs ? "A dónde ir" : isFr ? "Où aller" : isDe ? "Wohin möchten Sie" : "Where to go"} {isPt ? "Para onde ir" : isEs ? "A dónde ir" : isFr ? "Où aller" : isZh ? "前往" : isDe ? "Wohin möchten Sie" : "Where to go"}
</p> </p>
</div> </div>
<button <button
@@ -483,7 +524,7 @@ export default function Header() {
type="button" type="button"
onClick={() => setDrawerOpen(false)} onClick={() => setDrawerOpen(false)}
className="grid h-10 w-10 place-items-center rounded-full border border-line text-ink transition-colors hover:bg-surface-2" className="grid h-10 w-10 place-items-center rounded-full border border-line text-ink transition-colors hover:bg-surface-2"
aria-label={isPt ? "Fechar menu" : isEs ? "Cerrar menú" : isFr ? "Fermer le menu" : isDe ? "Menü schließen" : "Close menu"} aria-label={isPt ? "Fechar menu" : isEs ? "Cerrar menú" : isFr ? "Fermer le menu" : isZh ? "关闭菜单" : isDe ? "Menü schließen" : "Close menu"}
> >
<X className="h-5 w-5" aria-hidden="true" /> <X className="h-5 w-5" aria-hidden="true" />
</button> </button>
@@ -517,7 +558,7 @@ export default function Header() {
<nav <nav
className="flex-1 overflow-y-auto px-3 py-2" className="flex-1 overflow-y-auto px-3 py-2"
aria-label={isPt ? "Navegação móvel" : isEs ? "Navegación móvil" : isFr ? "Navigation mobile" : isDe ? "Mobile Navigation" : "Mobile navigation"} aria-label={isPt ? "Navegação móvel" : isEs ? "Navegación móvil" : isFr ? "Navigation mobile" : isZh ? "移动端导航" : isDe ? "Mobile Navigation" : "Mobile navigation"}
> >
{visibleMobileLinks.map((link) => ( {visibleMobileLinks.map((link) => (
<Link <Link
@@ -536,7 +577,7 @@ export default function Header() {
<div className="p-4 pt-2"> <div className="p-4 pt-2">
<Link href={getLinkHref("/seletor")} onClick={() => setDrawerOpen(false)} className="btn btn-primary w-full"> <Link href={getLinkHref("/seletor")} onClick={() => setDrawerOpen(false)} className="btn btn-primary w-full">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" /> <SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{isPt ? "Selecionar meu chip" : isEs ? "Seleccionar mi chip" : isFr ? "Choisir ma puce" : isDe ? "Meinen Chip auswählen" : "Select my chip"} {isPt ? "Selecionar meu chip" : isEs ? "Seleccionar mi chip" : isFr ? "Choisir ma puce" : isZh ? "选择我的芯片" : isDe ? "Meinen Chip auswählen" : "Select my chip"}
</Link> </Link>
</div> </div>
</div> </div>
@@ -547,7 +588,7 @@ export default function Header() {
); );
} }
function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false }) { function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false, isZh = false }) {
const { resolvedTheme, setTheme } = useTheme(); const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
@@ -572,7 +613,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Activar tema claro" ? "Activar tema claro"
: isFr : isFr
? "Activer le thème clair" ? "Activer le thème clair"
: isDe : isZh ? "切换到浅色主题" : isDe
? "Helles Design aktivieren" ? "Helles Design aktivieren"
: "Switch to light theme" : "Switch to light theme"
: isPt : isPt
@@ -581,7 +622,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Activar tema oscuro" ? "Activar tema oscuro"
: isFr : isFr
? "Activer le thème sombre" ? "Activer le thème sombre"
: isDe : isZh ? "切换到深色主题" : isDe
? "Dunkles Design aktivieren" ? "Dunkles Design aktivieren"
: "Switch to dark theme" : "Switch to dark theme"
} }
@@ -593,7 +634,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Tema claro" ? "Tema claro"
: isFr : isFr
? "Thème clair" ? "Thème clair"
: isDe : isZh ? "浅色主题" : isDe
? "Helles Design" ? "Helles Design"
: "Light theme" : "Light theme"
: isPt : isPt
@@ -602,7 +643,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Tema oscuro" ? "Tema oscuro"
: isFr : isFr
? "Thème sombre" ? "Thème sombre"
: isDe : isZh ? "深色主题" : isDe
? "Dunkles Design" ? "Dunkles Design"
: "Dark theme" : "Dark theme"
} }
+10 -3
View File
@@ -17,6 +17,7 @@ export default function PageHero({
isEs: isEsProp, isEs: isEsProp,
isFr: isFrProp, isFr: isFrProp,
isDe: isDeProp, isDe: isDeProp,
isZh: isZhProp,
locale: localeProp, locale: localeProp,
}) { }) {
const center = align === "center"; const center = align === "center";
@@ -44,16 +45,22 @@ export default function PageHero({
: localeProp : localeProp
? localeProp === "de" ? localeProp === "de"
: trail.some((t) => t.isDe || (t.href && t.href.startsWith("/de"))); : trail.some((t) => t.isDe || (t.href && t.href.startsWith("/de")));
const isZh =
isZhProp !== undefined
? isZhProp
: localeProp
? localeProp === "zh-CN"
: trail.some((t) => t.isZh || (t.href && t.href.startsWith("/zh-CN")));
const homeHref = isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/"; const homeHref = isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/";
const homeLabel = isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início"; const homeLabel = isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início";
const breadcrumbAria = isEn const breadcrumbAria = isEn
? "Breadcrumb navigation" ? "Breadcrumb navigation"
: isEs : isEs
? "Navegación por migas de pan" ? "Navegación por migas de pan"
: isFr : isFr
? "Fil d'Ariane" ? "Fil d'Ariane"
: isDe : isZh ? "面包屑导航" : isDe
? "Brotkrumennavigation" ? "Brotkrumennavigation"
: "Trilha de navegação"; : "Trilha de navegação";
+73 -10
View File
@@ -322,6 +322,66 @@ const GRUPOS_DE = [
combina: (avisos) => /unbeschaltet|floating/i.test(avisos), combina: (avisos) => /unbeschaltet|floating/i.test(avisos),
}, },
]; ];
const GRUPOS_ZH = [
{
id: "entrada",
titulo: "仅输入",
icone: ArrowDownToLine,
cor: "text-accent",
explicacao:
"仅能作输入的引脚。它们不能配置为输出,也没有内部上拉或下拉电阻。如有需要,请在外部电路中添加。",
combina: (avisos) => /仅为输入|仅输入|input[- ]only/i.test(avisos),
},
{
id: "flash",
titulo: "连接到闪存/PSRAM",
icone: HardDrive,
cor: "text-copper",
explicacao:
"已连接到存放并执行固件的存储器。把它们当作通用 GPIO 使用,通常会使芯片崩溃或破坏程序执行。在带内置闪存的模组中,这些引脚不会引出到封装引脚上。",
combina: (avisos) =>
/(flash|psram)/i.test(avisos) &&
/(专用于|存储使用|不得用于|不建议|dedicated to)/i.test(avisos),
},
{
id: "strapping",
titulo: "启动配置引脚(Strapping,启动模式)",
icone: Power,
cor: "text-gold",
explicacao:
"芯片在复位期间会对这些引脚的逻辑电平采样,以决定启动模式。启动完成后可以使用它们,但外部电路在复位期间不能把它们强制到错误的电平,否则开发板将无法启动,或意外进入下载模式。",
combina: (avisos, cats) =>
cats.includes("Strapping") ||
/strapping|启动模式|启动配置|boot mode/i.test(avisos),
},
{
id: "debug",
titulo: "烧录与调试",
icone: Bug,
cor: "text-accent",
explicacao:
"这些是用于烧录的 UART 和 JTAG 线。它们仍可用作 GPIO,但一旦复用,就必须先断开所接外设,才能对开发板烧录和调试。",
combina: (avisos, cats) => /调试|烧录|debugging/i.test(avisos) || cats.includes("JTAG"),
},
{
id: "usb",
titulo: "原生 USB",
icone: Usb,
cor: "text-accent",
explicacao:
"默认连接到内部的 USB Serial/JTAG 控制器。要把它们用作 GPIO,就必须禁用该外设,同时也会失去直接通过 USB 烧录的能力。",
combina: (avisos) => /usb serial\/jtag|usb_d[+-]/i.test(avisos),
},
{
id: "eletrico",
titulo: "不能悬空",
icone: Zap,
cor: "text-gold",
explicacao:
"需要确定的硬件逻辑电平:外部上拉或 RC 延时电路。悬空会导致随机复位或启动不稳定。",
combina: (avisos) => /浮空|悬空|floating/i.test(avisos),
},
];
/** Nome do pino + o GPIO correspondente, quando o nome é uma função (ex.: SENSOR_VP). */ /** Nome do pino + o GPIO correspondente, quando o nome é uma função (ex.: SENSOR_VP). */
function rotuloDoPino(pino) { function rotuloDoPino(pino) {
@@ -329,14 +389,14 @@ function rotuloDoPino(pino) {
return gpio && gpio !== pino.nome ? `${pino.nome} · ${gpio}` : pino.nome; return gpio && gpio !== pino.nome ? `${pino.nome} · ${gpio}` : pino.nome;
} }
function derivarGrupos(conexoes, isEn, isEs, isFr, isDe) { function derivarGrupos(conexoes, isEn, isEs, isFr, isDe, isZh = false) {
const listaGrupos = isEn const listaGrupos = isEn
? GRUPOS_EN ? GRUPOS_EN
: isEs : isEs
? GRUPOS_ES ? GRUPOS_ES
: isFr : isFr
? GRUPOS_FR ? GRUPOS_FR
: isDe : isZh ? GRUPOS_ZH : isDe
? GRUPOS_DE ? GRUPOS_DE
: GRUPOS_PT; : GRUPOS_PT;
return listaGrupos.map((grupo) => { return listaGrupos.map((grupo) => {
@@ -354,15 +414,16 @@ function derivarGrupos(conexoes, isEn, isEs, isFr, isDe) {
}).filter((g) => g.pinos.length > 0); }).filter((g) => g.pinos.length > 0);
} }
export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs = false, isFr = false, isDe = false, locale }) { export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs = false, isFr = false, isDe = false, isZh = false, locale }) {
if (!conexoes?.length) return null; if (!conexoes?.length) return null;
const effectiveIsEn = isEn || locale === "en"; const effectiveIsEn = isEn || locale === "en";
const effectiveIsEs = isEs || locale === "es"; const effectiveIsEs = isEs || locale === "es";
const effectiveIsFr = isFr || locale === "fr"; const effectiveIsFr = isFr || locale === "fr";
const effectiveIsDe = isDe || locale === "de"; const effectiveIsDe = isDe || locale === "de";
const effectiveIsZh = isZh || locale === "zh-CN";
const grupos = derivarGrupos(conexoes, effectiveIsEn, effectiveIsEs, effectiveIsFr, effectiveIsDe); const grupos = derivarGrupos(conexoes, effectiveIsEn, effectiveIsEs, effectiveIsFr, effectiveIsDe, effectiveIsZh);
if (!grupos.length) return null; if (!grupos.length) return null;
const totalPinos = new Set(grupos.flatMap((g) => g.pinos)).size; const totalPinos = new Set(grupos.flatMap((g) => g.pinos)).size;
@@ -379,7 +440,7 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
? "Pines con restricción de uso" ? "Pines con restricción de uso"
: effectiveIsFr : effectiveIsFr
? "Broches avec restrictions d'utilisation" ? "Broches avec restrictions d'utilisation"
: effectiveIsDe : effectiveIsZh ? "有使用限制的引脚" : effectiveIsDe
? "Pins mit Nutzungseinschränkungen" ? "Pins mit Nutzungseinschränkungen"
: "Pinos com restrição de uso"} : "Pinos com restrição de uso"}
</h2> </h2>
@@ -397,7 +458,9 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
<> <>
Toutes les broches de l&apos;{serieKey} ne sont pas libres pour un usage général. {totalPinos} d&apos;entre elles ont des conditions : occupées par la mémoire, lues pendant le démarrage ou fonctionnent uniquement en entrée. Pensez à vérifier avant de finaliser votre schéma. Toutes les broches de l&apos;{serieKey} ne sont pas libres pour un usage général. {totalPinos} d&apos;entre elles ont des conditions : occupées par la mémoire, lues pendant le démarrage ou fonctionnent uniquement en entrée. Pensez à vérifier avant de finaliser votre schéma.
</> </>
) : effectiveIsDe ? ( ) : effectiveIsZh ? ( <>
{serieKey} 上并非每个引脚都可以随意用作通用 IO。其中有 {totalPinos} 个带有使用条件:被存储器占用、在启动时被读取,或只能作输入。在定稿原理图之前,建议先核对一遍。
</> ) : effectiveIsDe ? (
<> <>
Nicht jeder Pin des {serieKey} steht für allgemeine Zwecke frei zur Verfügung. {totalPinos} davon unterliegen Bedingungen: durch Speicher belegt, beim Booten abgefragt oder reine Eingangspins. Prüfen Sie dies vor Abschluss Ihres Schaltplans. Nicht jeder Pin des {serieKey} steht für allgemeine Zwecke frei zur Verfügung. {totalPinos} davon unterliegen Bedingungen: durch Speicher belegt, beim Booten abgefragt oder reine Eingangspins. Prüfen Sie dies vor Abschluss Ihres Schaltplans.
</> </>
@@ -425,7 +488,7 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
? "pin" ? "pin"
: effectiveIsFr : effectiveIsFr
? "broche" ? "broche"
: effectiveIsDe : effectiveIsZh ? "个引脚" : effectiveIsDe
? "Pin" ? "Pin"
: "pino" : "pino"
: effectiveIsEn : effectiveIsEn
@@ -434,7 +497,7 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
? "pines" ? "pines"
: effectiveIsFr : effectiveIsFr
? "broches" ? "broches"
: effectiveIsDe : effectiveIsZh ? "个引脚" : effectiveIsDe
? "Pins" ? "Pins"
: "pinos"} : "pinos"}
</span> </span>
@@ -461,14 +524,14 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
"Estos grupos proceden de los mismos datos que el diagrama de conexiones. Allí podrá consultar pino a pino todas las funciones, el dominio de alimentación y los avisos completos." "Estos grupos proceden de los mismos datos que el diagrama de conexiones. Allí podrá consultar pino a pino todas las funciones, el dominio de alimentación y los avisos completos."
) : effectiveIsFr ? ( ) : effectiveIsFr ? (
"Ces groupes sont issus des mêmes données que le schéma de brochage. Vous pouvez y consulter broche par broche l'ensemble des fonctions, le domaine d'alimentation et les remarques complètes." "Ces groupes sont issus des mêmes données que le schéma de brochage. Vous pouvez y consulter broche par broche l'ensemble des fonctions, le domaine d'alimentation et les remarques complètes."
) : effectiveIsDe ? ( ) : effectiveIsZh ? ( "这些分组来自与连接图相同的数据。在那里可以逐个引脚查看全部功能、供电域和完整的警告。" ) : effectiveIsDe ? (
"Diese Gruppen stammen aus denselben Daten wie das Pinbelegungsdiagramm. Dort finden Sie für jeden Pin alle Funktionen, den Spannungsbereich und die vollständigen Entwicklungshinweise." "Diese Gruppen stammen aus denselben Daten wie das Pinbelegungsdiagramm. Dort finden Sie für jeden Pin alle Funktionen, den Spannungsbereich und die vollständigen Entwicklungshinweise."
) : ( ) : (
"Estes grupos vêm dos mesmos dados do diagrama de conexões. Lá você vê pino a pino todas as funções, o domínio de alimentação e os avisos completos." "Estes grupos vêm dos mesmos dados do diagrama de conexões. Lá você vê pino a pino todas as funções, o domínio de alimentação e os avisos completos."
)} )}
</p> </p>
<Link href="#connections" className="btn btn-ghost shrink-0"> <Link href="#connections" className="btn btn-ghost shrink-0">
{effectiveIsEn ? "Open pinout diagram" : effectiveIsEs ? "Abrir diagrama de pines" : effectiveIsFr ? "Ouvrir le brochage" : effectiveIsDe ? "Pinbelegung öffnen" : "Abrir diagrama de pinos"} {effectiveIsEn ? "Open pinout diagram" : effectiveIsEs ? "Abrir diagrama de pines" : effectiveIsFr ? "Ouvrir le brochage" : effectiveIsZh ? "打开引脚图" : effectiveIsDe ? "Pinbelegung öffnen" : "Abrir diagrama de pinos"}
<ArrowRight className="h-4 w-4" aria-hidden="true" /> <ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link> </Link>
</div> </div>
+40 -26
View File
@@ -75,6 +75,16 @@ const TIPOS_DE = {
erro: { label: "Fehler", Icon: AlertTriangle, cor: "bg-gold-soft text-gold" }, erro: { label: "Fehler", Icon: AlertTriangle, cor: "bg-gold-soft text-gold" },
componente: { label: "Komponente", Icon: Box, cor: "bg-surface-2 text-ink-soft" }, componente: { label: "Komponente", Icon: Box, cor: "bg-surface-2 text-ink-soft" },
}; };
const TIPOS_ZH = {
resposta: { label: "答案", Icon: Sparkles, cor: "bg-accent text-accent-contrast" },
pino: { label: "引脚", Icon: Plug, cor: "bg-accent text-accent-contrast" },
pagina: { label: "页面", Icon: FileText, cor: "bg-surface-2 text-ink-soft" },
serie: { label: "系列", Icon: Cpu, cor: "bg-accent-soft text-accent" },
framework: { label: "框架", Icon: Terminal, cor: "bg-teal-soft text-teal" },
glossario: { label: "术语", Icon: BookOpen, cor: "bg-copper-soft text-copper" },
erro: { label: "错误", Icon: AlertTriangle, cor: "bg-gold-soft text-gold" },
componente: { label: "组件", Icon: Box, cor: "bg-surface-2 text-ink-soft" },
};
const INTELIGENTES = new Set(["resposta", "pino"]); const INTELIGENTES = new Set(["resposta", "pino"]);
@@ -82,6 +92,7 @@ const EXEMPLOS_EN = ["flash s3", "usb pins c6", "gpio0 esp32", "brownout", "LVGL
const EXEMPLOS_ES = ["flash s3", "pines usb c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"]; const EXEMPLOS_ES = ["flash s3", "pines usb c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"];
const EXEMPLOS_FR = ["flash s3", "broches usb c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"]; const EXEMPLOS_FR = ["flash s3", "broches usb c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"];
const EXEMPLOS_DE = ["flash s3", "usb pins c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"]; const EXEMPLOS_DE = ["flash s3", "usb pins c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"];
const EXEMPLOS_ZH = ["s3 闪存", "c6 usb 引脚", "esp32 gpio0", "brownout", "LVGL", "FreeRTOS"];
function Destaque({ texto, termos }) { function Destaque({ texto, termos }) {
if (!termos.length) return texto; if (!termos.length) return texto;
@@ -142,14 +153,17 @@ export default function SearchDialog() {
? "fr" ? "fr"
: pathname === "/de" || pathname.startsWith("/de/") : pathname === "/de" || pathname.startsWith("/de/")
? "de" ? "de"
: pathname === "/zh-CN" || pathname.startsWith("/zh-CN/")
? "zh-CN"
: "pt"; : "pt";
const isEn = locale === "en"; const isEn = locale === "en";
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
const TIPOS = isEn ? TIPOS_EN : isEs ? TIPOS_ES : isFr ? TIPOS_FR : isDe ? TIPOS_DE : TIPOS_PT; const TIPOS = isEn ? TIPOS_EN : isEs ? TIPOS_ES : isFr ? TIPOS_FR : isZh ? TIPOS_ZH : isDe ? TIPOS_DE : TIPOS_PT;
const EXEMPLOS = isEn ? EXEMPLOS_EN : isEs ? EXEMPLOS_ES : isFr ? EXEMPLOS_FR : isDe ? EXEMPLOS_DE : EXEMPLOS_DE_BUSCA; const EXEMPLOS = isEn ? EXEMPLOS_EN : isEs ? EXEMPLOS_ES : isFr ? EXEMPLOS_FR : isZh ? EXEMPLOS_ZH : isDe ? EXEMPLOS_DE : EXEMPLOS_DE_BUSCA;
const [aberto, setAberto] = useState(false); const [aberto, setAberto] = useState(false);
const [montado, setMontado] = useState(false); const [montado, setMontado] = useState(false);
@@ -196,7 +210,7 @@ export default function SearchDialog() {
? "/api/busca/es?v=2" ? "/api/busca/es?v=2"
: isFr : isFr
? "/api/busca/fr?v=2" ? "/api/busca/fr?v=2"
: isDe : isZh ? "/api/busca/zh-CN?v=2" : isDe
? "/api/busca/de?v=2" ? "/api/busca/de?v=2"
: "/api/busca?v=2"; : "/api/busca?v=2";
fetch(url, { cache: "no-cache" }) fetch(url, { cache: "no-cache" })
@@ -222,7 +236,7 @@ export default function SearchDialog() {
return () => { return () => {
cancelado = true; cancelado = true;
}; };
}, [isEn, isEs, isFr, isDe]); }, [isEn, isEs, isFr, isDe, isZh]);
// Carrega o índice (textos + especificações + pinos) na primeira abertura. // Carrega o índice (textos + especificações + pinos) na primeira abertura.
useEffect(() => { useEffect(() => {
@@ -293,17 +307,17 @@ export default function SearchDialog() {
id="botao-busca" id="botao-busca"
onClick={() => setAberto(true)} onClick={() => setAberto(true)}
className="flex h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full border border-line px-3 text-ink transition-colors hover:bg-surface-2 xl:px-4" className="flex h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full border border-line px-3 text-ink transition-colors hover:bg-surface-2 xl:px-4"
aria-label={isEn ? "Search ESPDocs (Ctrl+K)" : isEs ? "Buscar en ESPDocs (Ctrl+K)" : isFr ? "Rechercher sur ESPDocs (Ctrl+K)" : isDe ? "ESPDocs durchsuchen (Strg+K)" : "Buscar no ESPDocs (Ctrl+K)"} aria-label={isEn ? "Search ESPDocs (Ctrl+K)" : isEs ? "Buscar en ESPDocs (Ctrl+K)" : isFr ? "Rechercher sur ESPDocs (Ctrl+K)" : isZh ? "在 ESPDocs 中搜索(Ctrl+K)" : isDe ? "ESPDocs durchsuchen (Strg+K)" : "Buscar no ESPDocs (Ctrl+K)"}
> >
<Search className="h-4 w-4" aria-hidden="true" /> <Search className="h-4 w-4" aria-hidden="true" />
<span className="hidden text-sm font-medium xl:inline">{isEn ? "Search" : isEs ? "Buscar" : isFr ? "Rechercher" : isDe ? "Suchen" : "Buscar"}</span> <span className="hidden text-sm font-medium xl:inline">{isEn ? "Search" : isEs ? "Buscar" : isFr ? "Rechercher" : isZh ? "搜索" : isDe ? "Suchen" : "Buscar"}</span>
<kbd className="hidden rounded-md border border-line bg-surface-2 px-1.5 py-0.5 font-mono text-xs font-semibold text-muted xl:inline">{isDe ? "Strg K" : "Ctrl K"}</kbd> <kbd className="hidden rounded-md border border-line bg-surface-2 px-1.5 py-0.5 font-mono text-xs font-semibold text-muted xl:inline">{isZh ? "Ctrl K" : isDe ? "Strg K" : "Ctrl K"}</kbd>
</button> </button>
{montado && {montado &&
aberto && aberto &&
createPortal( createPortal(
<div className="fixed inset-0 z-[120] flex items-start justify-center px-4 pt-[8vh]" role="dialog" aria-modal="true" aria-label={isEn ? "Search ESPDocs" : isEs ? "Buscar en ESPDocs" : isFr ? "Rechercher sur ESPDocs" : isDe ? "ESPDocs durchsuchen" : "Buscar no ESPDocs"}> <div className="fixed inset-0 z-[120] flex items-start justify-center px-4 pt-[8vh]" role="dialog" aria-modal="true" aria-label={isEn ? "Search ESPDocs" : isEs ? "Buscar en ESPDocs" : isFr ? "Rechercher sur ESPDocs" : isZh ? "在 ESPDocs 中搜索" : isDe ? "ESPDocs durchsuchen" : "Buscar no ESPDocs"}>
<div className="dialog-scrim absolute inset-0 bg-black/45 backdrop-blur-sm" onClick={fechar} /> <div className="dialog-scrim absolute inset-0 bg-black/45 backdrop-blur-sm" onClick={fechar} />
<div className="dialog-panel relative flex max-h-[80vh] w-full max-w-2xl flex-col overflow-hidden rounded-3xl border border-line bg-canvas shadow-2xl"> <div className="dialog-panel relative flex max-h-[80vh] w-full max-w-2xl flex-col overflow-hidden rounded-3xl border border-line bg-canvas shadow-2xl">
<div className="flex items-center gap-3 border-b border-line px-5"> <div className="flex items-center gap-3 border-b border-line px-5">
@@ -322,20 +336,20 @@ export default function SearchDialog() {
? "Pregunte a ESPDocs: “flash s3”, “pines usb c6”, “gpio0 esp32”…" ? "Pregunte a ESPDocs: “flash s3”, “pines usb c6”, “gpio0 esp32”…"
: isFr : isFr
? "Demandez à ESPDocs : « flash s3 », « broches usb c6 », « gpio0 esp32 »…" ? "Demandez à ESPDocs : « flash s3 », « broches usb c6 », « gpio0 esp32 »…"
: isDe : isZh ? "向 ESPDocs 提问:“s3 闪存”、“c6 usb 引脚”、“esp32 gpio0”…" : isDe
? "ESPDocs fragen: „flash s3“, „usb pins c6“, „gpio0 esp32“… " ? "ESPDocs fragen: „flash s3“, „usb pins c6“, „gpio0 esp32“… "
: "Pergunte ao ESPDocs: “flash s3”, “pinos usb c6”, “gpio0 esp32”…" : "Pergunte ao ESPDocs: “flash s3”, “pinos usb c6”, “gpio0 esp32”…"
} }
className="flex-1 bg-transparent py-5 text-base text-ink outline-none placeholder:text-muted" className="flex-1 bg-transparent py-5 text-base text-ink outline-none placeholder:text-muted"
autoComplete="off" autoComplete="off"
spellCheck={false} spellCheck={false}
aria-label={isEn ? "Search query" : isEs ? "Término de búsqueda" : isFr ? "Requête de recherche" : isDe ? "Suchbegriff" : "Termo de busca"} aria-label={isEn ? "Search query" : isEs ? "Término de búsqueda" : isFr ? "Requête de recherche" : isZh ? "搜索词" : isDe ? "Suchbegriff" : "Termo de busca"}
/> />
<button <button
type="button" type="button"
onClick={fechar} onClick={fechar}
className="grid h-9 w-9 cursor-pointer place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink" className="grid h-9 w-9 cursor-pointer place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink"
aria-label={isEn ? "Close search" : isEs ? "Cerrar búsqueda" : isFr ? "Fermer la recherche" : isDe ? "Suche schließen" : "Fechar busca"} aria-label={isEn ? "Close search" : isEs ? "Cerrar búsqueda" : isFr ? "Fermer la recherche" : isZh ? "关闭搜索" : isDe ? "Suche schließen" : "Fechar busca"}
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</button> </button>
@@ -343,7 +357,7 @@ export default function SearchDialog() {
{entendeu && ( {entendeu && (
<div className="flex flex-wrap items-center gap-2 border-b border-line bg-surface px-5 py-2.5"> <div className="flex flex-wrap items-center gap-2 border-b border-line bg-surface px-5 py-2.5">
<span className="text-xs font-semibold text-muted">{isEn ? "Understood:" : isEs ? "Entendido:" : isFr ? "Compris :" : isDe ? "Erkannt:" : "Entendi:"}</span> <span className="text-xs font-semibold text-muted">{isEn ? "Understood:" : isEs ? "Entendido:" : isFr ? "Compris :" : isZh ? "理解为:" : isDe ? "Erkannt:" : "Entendi:"}</span>
{interpretacao.chips.map((c) => ( {interpretacao.chips.map((c) => (
<Pilula key={c.chave} forte>{c.nome}</Pilula> <Pilula key={c.chave} forte>{c.nome}</Pilula>
))} ))}
@@ -357,8 +371,8 @@ export default function SearchDialog() {
<div className="flex flex-wrap items-center gap-2 border-b border-line px-5 py-2.5"> <div className="flex flex-wrap items-center gap-2 border-b border-line px-5 py-2.5">
<span className="text-xs font-semibold text-muted"> <span className="text-xs font-semibold text-muted">
{interpretacao.chips.length {interpretacao.chips.length
? (isEn ? "Ask about:" : isEs ? "Preguntar sobre:" : isFr ? "Poser une question sur :" : isDe ? "Frage zu:" : "Perguntar sobre:") ? (isEn ? "Ask about:" : isEs ? "Preguntar sobre:" : isFr ? "Poser une question sur :" : isZh ? "查询:" : isDe ? "Frage zu:" : "Perguntar sobre:")
: (isEn ? "Which chip?" : isEs ? "¿Para qué chip?" : isFr ? "Pour quelle puce ?" : isDe ? "Für welchen Chip?" : "Para qual chip?")} : (isEn ? "Which chip?" : isEs ? "¿Para qué chip?" : isFr ? "Pour quelle puce ?" : isZh ? "针对哪款芯片?" : isDe ? "Für welchen Chip?" : "Para qual chip?")}
</span> </span>
{sugestoes.map((s) => ( {sugestoes.map((s) => (
<Pilula key={s.rotulo} onClick={() => anexar(s.anexar)}> <Pilula key={s.rotulo} onClick={() => anexar(s.anexar)}>
@@ -378,7 +392,7 @@ export default function SearchDialog() {
? "No se pudo cargar el índice de búsqueda." ? "No se pudo cargar el índice de búsqueda."
: isFr : isFr
? "Impossible de charger l'index de recherche." ? "Impossible de charger l'index de recherche."
: isDe : isZh ? "无法加载搜索索引。" : isDe
? "Suchindex konnte nicht geladen werden." ? "Suchindex konnte nicht geladen werden."
: "Não foi possível carregar o índice de busca."} : "Não foi possível carregar o índice de busca."}
</p> </p>
@@ -387,20 +401,20 @@ export default function SearchDialog() {
onClick={carregarIndice} onClick={carregarIndice}
className="btn btn-primary btn-sm mt-3" className="btn btn-primary btn-sm mt-3"
> >
{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"}
</button> </button>
</div> </div>
)} )}
{!erro && !indice && ( {!erro && !indice && (
<p className="p-6 text-center text-sm text-muted"> <p className="p-6 text-center text-sm text-muted">
{isEn ? "Loading…" : isEs ? "Cargando…" : isFr ? "Chargement…" : isDe ? "Wird geladen…" : "Carregando…"} {isEn ? "Loading…" : isEs ? "Cargando…" : isFr ? "Chargement…" : isZh ? "加载中…" : isDe ? "Wird geladen…" : "Carregando…"}
</p> </p>
)} )}
{indice && vazia && ( {indice && vazia && (
<div className="px-3 pb-3 pt-1"> <div className="px-3 pb-3 pt-1">
<p className="text-xs font-semibold text-muted"> <p className="text-xs font-semibold text-muted">
{isEn ? "Try asking" : isEs ? "Pruebe a preguntar" : isFr ? "Essayez de demander" : isDe ? "Versuchen Sie" : "Experimente perguntar"} {isEn ? "Try asking" : isEs ? "Pruebe a preguntar" : isFr ? "Essayez de demander" : isZh ? "试着问问" : isDe ? "Versuchen Sie" : "Experimente perguntar"}
</p> </p>
<div className="mt-2 flex flex-wrap gap-2"> <div className="mt-2 flex flex-wrap gap-2">
{EXEMPLOS.map((ex) => ( {EXEMPLOS.map((ex) => (
@@ -410,7 +424,7 @@ export default function SearchDialog() {
))} ))}
</div> </div>
<p className="mt-4 text-xs font-semibold text-muted"> <p className="mt-4 text-xs font-semibold text-muted">
{isEn ? "Go to" : isEs ? "Ir a" : isFr ? "Aller à" : isDe ? "Gehe zu" : "Ir para"} {isEn ? "Go to" : isEs ? "Ir a" : isFr ? "Aller à" : isZh ? "前往" : isDe ? "Gehe zu" : "Ir para"}
</p> </p>
</div> </div>
)} )}
@@ -424,7 +438,7 @@ export default function SearchDialog() {
? `Sin resultados para “${consulta}”` ? `Sin resultados para “${consulta}”`
: isFr : isFr
? `Aucun résultat pour « ${consulta} »` ? `Aucun résultat pour « ${consulta} »`
: isDe : isZh ? `未找到“${consulta}”的结果` : isDe
? `Keine Ergebnisse für „${consulta}“` ? `Keine Ergebnisse für „${consulta}“`
: `Nenhum resultado para “${consulta}”`} : `Nenhum resultado para “${consulta}”`}
</p> </p>
@@ -435,7 +449,7 @@ export default function SearchDialog() {
? "Pruebe “brownout”, “flash s3”, “pines usb c6” o “LVGL”." ? "Pruebe “brownout”, “flash s3”, “pines usb c6” o “LVGL”."
: isFr : isFr
? "Essayez « brownout », « flash s3 », « broches usb c6 » ou « LVGL »." ? "Essayez « brownout », « flash s3 », « broches usb c6 » ou « LVGL »."
: isDe : isZh ? "试试“brownout”、“s3 闪存”、“c6 usb 引脚”或“LVGL”。" : isDe
? "Versuchen Sie „brownout“, „flash s3“, „usb pins c6“ oder „LVGL“." ? "Versuchen Sie „brownout“, „flash s3“, „usb pins c6“ oder „LVGL“."
: "Tente “brownout”, “flash s3”, “pinos usb c6” ou “LVGL”."} : "Tente “brownout”, “flash s3”, “pinos usb c6” ou “LVGL”."}
</p> </p>
@@ -501,9 +515,9 @@ export default function SearchDialog() {
</div> </div>
<div className="hidden items-center gap-4 border-t border-line bg-surface px-4 py-2.5 text-xs text-muted md:flex"> <div className="hidden items-center gap-4 border-t border-line bg-surface px-4 py-2.5 text-xs text-muted md:flex">
<span className="flex items-center gap-1"><ArrowUp className="h-3 w-3" /><ArrowDown className="h-3 w-3" /> {isEn ? "navigate" : isEs ? "navegar" : isFr ? "naviguer" : isDe ? "navigieren" : "navegar"}</span> <span className="flex items-center gap-1"><ArrowUp className="h-3 w-3" /><ArrowDown className="h-3 w-3" /> {isEn ? "navigate" : isEs ? "navegar" : isFr ? "naviguer" : isZh ? "导航" : isDe ? "navigieren" : "navegar"}</span>
<span className="flex items-center gap-1"><CornerDownLeft className="h-3 w-3" /> {isEn ? "open" : isEs ? "abrir" : isFr ? "ouvrir" : isDe ? "öffnen" : "abrir"}</span> <span className="flex items-center gap-1"><CornerDownLeft className="h-3 w-3" /> {isEn ? "open" : isEs ? "abrir" : isFr ? "ouvrir" : isZh ? "打开" : isDe ? "öffnen" : "abrir"}</span>
<span><kbd className="font-mono font-semibold">Esc</kbd> {isEn ? "close" : isEs ? "cerrar" : isFr ? "fermer" : isDe ? "schließen" : "fechar"}</span> <span><kbd className="font-mono font-semibold">Esc</kbd> {isEn ? "close" : isEs ? "cerrar" : isFr ? "fermer" : isZh ? "关闭" : isDe ? "schließen" : "fechar"}</span>
<span className="ml-auto"> <span className="ml-auto">
{vazia {vazia
? (isEn ? (isEn
@@ -512,7 +526,7 @@ export default function SearchDialog() {
? "Consejo: presione / en cualquier página" ? "Consejo: presione / en cualquier página"
: isFr : isFr
? "Astuce : appuyez sur / sur n'importe quelle page" ? "Astuce : appuyez sur / sur n'importe quelle page"
: isDe : isZh ? "提示:在任意页面按 / 键" : isDe
? "Tipp: Drücken Sie / auf einer beliebigen Seite" ? "Tipp: Drücken Sie / auf einer beliebigen Seite"
: "Dica: pressione / em qualquer página") : "Dica: pressione / em qualquer página")
: `${resultados.length} ${ : `${resultados.length} ${
@@ -522,7 +536,7 @@ export default function SearchDialog() {
? "resultado(s)" ? "resultado(s)"
: isFr : isFr
? "résultat(s)" ? "résultat(s)"
: isDe : isZh ? "条结果" : isDe
? "Ergebnis(se)" ? "Ergebnis(se)"
: "resultado(s)" : "resultado(s)"
}`} }`}
+5 -2
View File
@@ -2,11 +2,12 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
export default function SeriesTabMenu({ tabs, isEn = false, isEs = false, isFr = false, isDe = false, locale }) { export default function SeriesTabMenu({ tabs, isEn = false, isEs = false, isFr = false, isDe = false, isZh = false, locale }) {
const effectiveIsEn = isEn || locale === "en"; const effectiveIsEn = isEn || locale === "en";
const effectiveIsEs = isEs || locale === "es"; const effectiveIsEs = isEs || locale === "es";
const effectiveIsFr = isFr || locale === "fr"; const effectiveIsFr = isFr || locale === "fr";
const effectiveIsDe = isDe || locale === "de"; const effectiveIsDe = isDe || locale === "de";
const effectiveIsZh = isZh || locale === "zh-CN";
const availableTabs = tabs.filter((tab) => tab.available); const availableTabs = tabs.filter((tab) => tab.available);
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id); const [activeTab, setActiveTab] = useState(availableTabs[0]?.id);
const refs = useRef({}); const refs = useRef({});
@@ -66,7 +67,9 @@ export default function SeriesTabMenu({ tabs, isEn = false, isEs = false, isFr =
<div <div
role="tablist" role="tablist"
aria-label={ aria-label={
effectiveIsEn effectiveIsZh
? "开发板与图示"
: effectiveIsEn
? "Board and diagram" ? "Board and diagram"
: effectiveIsEs : effectiveIsEs
? "Placa y diagrama" ? "Placa y diagrama"
+40 -40
View File
@@ -23,18 +23,18 @@
}, },
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, bis zu 150 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, bis zu 150 Mbps)",
"bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) und Bluetooth LE (dual mode)", "bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) und Bluetooth LE (dual mode)",
"ethernet": "Ja (Ethernet MAC integrado)", "ethernet": "Ja (Ethernet-MAC integriert)",
"memoria_sram": "520 KB", "memoria_sram": "520 KB",
"memoria_sram_rtc": "16 KB (dividida em RTC FAST e RTC SLOW)", "memoria_sram_rtc": "16 KB (dividida em RTC FAST e RTC SLOW)",
"memoria_rom": "448 KB (für Boot- und Core-Funktionen)", "memoria_rom": "448 KB (für Boot- und Core-Funktionen)",
"flash_externa": "Bis zu 16 MB über QSPI", "flash_externa": "Bis zu 16 MB über QSPI",
"psram_externa": "Bis zu 8 MB über QSPI (in spezifischen Modulen wie WROVER)", "psram_externa": "Bis zu 8 MB über QSPI (in spezifischen Modulen wie WROVER)",
"gpio": "34", "gpio": "34",
"adc": "2x SAR ADC de 12 bits, até 18 canais", "adc": "2x SAR-ADC mit 12 Bit, bis zu 18 Kanäle",
"dac": "2x DAC de 8 bits", "dac": "2x DAC de 8 bits",
"touch": "10 canais capacitivos", "touch": "10 kapazitive Kanäle",
"uart": "3", "uart": "3",
"spi": "4 (SPI, HSPI, VSPI - 2 podem ser usados para Flash/PSRAM)", "spi": "4 (SPI, HSPI, VSPI - 2 für Flash/PSRAM nutzbar)",
"i2c": "2", "i2c": "2",
"i2s": "2", "i2s": "2",
"pwm": "LED-PWM (bis zu 16 Kanäle), Motor-PWM", "pwm": "LED-PWM (bis zu 16 Kanäle), Motor-PWM",
@@ -94,11 +94,11 @@
"flash_externa": "Bis zu 1 GB über QSPI", "flash_externa": "Bis zu 1 GB über QSPI",
"psram_externa": "Bis zu 1 GB über QSPI/Octal SPI", "psram_externa": "Bis zu 1 GB über QSPI/Octal SPI",
"gpio": "43", "gpio": "43",
"adc": "2x SAR ADC, até 20 canais: saída de 13 bits (0–8191, única da família), resolução efetiva de 12 bits a partir da revisão v1.0 (errata ADC-112)", "adc": "2x SAR-ADC, bis zu 20 Kanäle: 13-Bit-Ausgabe (0–8191, einzigartig in der Familie), effektive Auflösung von 12 Bit ab Revision v1.0 (Erratum ADC-112)",
"dac": "2x DAC de 8 bits", "dac": "2x DAC de 8 bits",
"touch": "14 canais capacitivos", "touch": "14 kapazitive Kanäle",
"uart": "2", "uart": "2",
"spi": "4 (SPI0/1 para Flash, SPI2/3 para uso geral)", "spi": "4 (SPI0/1 für Flash, SPI2/3 für allgemeine Zwecke)",
"i2c": "2", "i2c": "2",
"i2s": "1", "i2s": "1",
"pwm": "LED-PWM (bis zu 8 Kanäle)", "pwm": "LED-PWM (bis zu 8 Kanäle)",
@@ -133,7 +133,7 @@
"nucleos": "2 (Dual-core)", "nucleos": "2 (Dual-core)",
"frequencia": "Bis zu 240 MHz", "frequencia": "Bis zu 240 MHz",
"coprocessador_ulp": "Ja (RISC-V)", "coprocessador_ulp": "Ja (RISC-V)",
"aceleradores_ia": "Sim (Vector Instructions para IA)", "aceleradores_ia": "Ja (Vektorbefehle für KI)",
"seguranca": { "seguranca": {
"aes": "AES-128/256", "aes": "AES-128/256",
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
@@ -156,11 +156,11 @@
"flash_externa": "Bis zu 1 GB über QSPI/Octal SPI", "flash_externa": "Bis zu 1 GB über QSPI/Octal SPI",
"psram_externa": "Bis zu 1 GB über QSPI/Octal SPI", "psram_externa": "Bis zu 1 GB über QSPI/Octal SPI",
"gpio": "45", "gpio": "45",
"adc": "2x SAR ADC de 12 bits, até 20 canais", "adc": "2x SAR-ADC mit 12 Bit, bis zu 20 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "14 canais capacitivos", "touch": "14 kapazitive Kanäle",
"uart": "3", "uart": "3",
"spi": "4 (SPI0/1 para Flash, SPI2/3 para uso geral)", "spi": "4 (SPI0/1 für Flash, SPI2/3 für allgemeine Zwecke)",
"i2c": "2", "i2c": "2",
"i2s": "2", "i2s": "2",
"pwm": "LED-PWM (bis zu 8 Kanäle), Motor-PWM (6 Kanäle)", "pwm": "LED-PWM (bis zu 8 Kanäle), Motor-PWM (6 Kanäle)",
@@ -171,7 +171,7 @@
"sensor_temperatura": "1 (interno)", "sensor_temperatura": "1 (interno)",
"consumo_energia": { "consumo_energia": {
"active": "78 mW (~24 mA @3.3V)", "active": "78 mW (~24 mA @3.3V)",
"modem_sleep": "13-42 mA (40-80 MHz, conforme a carga)", "modem_sleep": "13-42 mA (40-80 MHz, je nach Last)",
"light_sleep": "240 µA (+40 a 200 µA für PSRAM)", "light_sleep": "240 µA (+40 a 200 µA für PSRAM)",
"deep_sleep": "7 µA" "deep_sleep": "7 µA"
}, },
@@ -202,7 +202,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Ja (RSA/MPI bis zu 4096 bits)", "rsa": "Ja (RSA/MPI bis zu 4096 bits)",
"ecc": "Ja (P-192, P-256, P-384)", "ecc": "Ja (P-192, P-256, P-384)",
"ecdsa": "Ja (modo determinístico, RFC 6979)", "ecdsa": "Ja (deterministischer Modus, RFC 6979)",
"hmac": "Ja", "hmac": "Ja",
"assinatura_digital": "Ja", "assinatura_digital": "Ja",
"key_manager": "Ja", "key_manager": "Ja",
@@ -221,9 +221,9 @@
"flash_externa": "Bis zu 256 MB über QSPI", "flash_externa": "Bis zu 256 MB über QSPI",
"psram_externa": "16 MB oder 32 MB Octal-DDR, nur im Gehäuse integriert (NRV16, NRV32)", "psram_externa": "16 MB oder 32 MB Octal-DDR, nur im Gehäuse integriert (NRV16, NRV32)",
"gpio": "60", "gpio": "60",
"adc": "2x SAR ADC de 12 bits, até 16 canais", "adc": "2x SAR-ADC mit 12 Bit, bis zu 16 Kanäle",
"dac": "Ja (2x DAC de 10 bits e 2x DAC de 12 bits)", "dac": "Ja (2x DAC de 10 bits e 2x DAC de 12 bits)",
"touch": "14 canais capacitivos", "touch": "14 kapazitive Kanäle",
"uart": "5 (4 UART + 1 LP UART)", "uart": "5 (4 UART + 1 LP UART)",
"spi": "7 (6 SPI + 1 LP SPI)", "spi": "7 (6 SPI + 1 LP SPI)",
"i2c": "3 (2 I2C + 1 LP I2C)", "i2c": "3 (2 I2C + 1 LP I2C)",
@@ -274,17 +274,17 @@
}, },
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, bis zu 72.2 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, bis zu 72.2 Mbps)",
"bluetooth": "Bluetooth 5.3 LE (certificado)", "bluetooth": "Bluetooth 5.3 LE (certificado)",
"memoria_sram": "272 KB (16 KB para cache)", "memoria_sram": "272 KB (davon 16 KB Cache)",
"memoria_sram_rtc": "Nein", "memoria_sram_rtc": "Nein",
"memoria_rom": "576 KB", "memoria_rom": "576 KB",
"flash_externa": "Bis zu 16 MB über QSPI, jedoch bei im Handel erhältlichen Varianten nicht verfügbar (ESP8684H2X/H4X haben 2 MB oder 4 MB im Gehäuse und führen keine Flash-Pins heraus)", "flash_externa": "Bis zu 16 MB über QSPI, jedoch bei im Handel erhältlichen Varianten nicht verfügbar (ESP8684H2X/H4X haben 2 MB oder 4 MB im Gehäuse und führen keine Flash-Pins heraus)",
"psram_externa": "Nein", "psram_externa": "Nein",
"gpio": "14", "gpio": "14",
"adc": "1x SAR ADC de 12 bits, até 5 canais", "adc": "1x SAR-ADC mit 12 Bit, bis zu 5 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Nein", "touch": "Nein",
"uart": "2", "uart": "2",
"spi": "3 (SPI0/1 para Flash interna, SPI2 para uso geral)", "spi": "3 (SPI0/1 für interne Flash, SPI2 für allgemeine Zwecke)",
"i2c": "1 (Master-only)", "i2c": "1 (Master-only)",
"i2s": "Nein", "i2s": "Nein",
"pwm": "LED-PWM (bis zu 6 Kanäle)", "pwm": "LED-PWM (bis zu 6 Kanäle)",
@@ -336,11 +336,11 @@
"flash_externa": "Bis zu 16 MB über QSPI", "flash_externa": "Bis zu 16 MB über QSPI",
"psram_externa": "Nein", "psram_externa": "Nein",
"gpio": "22", "gpio": "22",
"adc": "2x SAR ADC de 12 bits, até 6 canais", "adc": "2x SAR-ADC mit 12 Bit, bis zu 6 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Nein", "touch": "Nein",
"uart": "2", "uart": "2",
"spi": "3 (SPI0/1 para Flash, SPI2 para uso geral)", "spi": "3 (SPI0/1 für Flash, SPI2 für allgemeine Zwecke)",
"i2c": "1", "i2c": "1",
"i2s": "1", "i2s": "1",
"pwm": "LED-PWM (bis zu 6 Kanäle)", "pwm": "LED-PWM (bis zu 6 Kanäle)",
@@ -379,7 +379,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Ja (RSA/MPI bis zu 3072 bits)", "rsa": "Ja (RSA/MPI bis zu 3072 bits)",
"ecc": "Ja (P-192, P-256, P-384)", "ecc": "Ja (P-192, P-256, P-384)",
"ecdsa": "Ja (modo determinístico, RFC 6979)", "ecdsa": "Ja (deterministischer Modus, RFC 6979)",
"hmac": "Ja", "hmac": "Ja",
"assinatura_digital": "Ja", "assinatura_digital": "Ja",
"key_manager": "Ja", "key_manager": "Ja",
@@ -398,7 +398,7 @@
"flash_externa": "Bis zu 16 MB über QSPI", "flash_externa": "Bis zu 16 MB über QSPI",
"psram_externa": "Ja, via SPI (controlador de flash/PSRAM com cache)", "psram_externa": "Ja, via SPI (controlador de flash/PSRAM com cache)",
"gpio": "29", "gpio": "29",
"adc": "1x SAR ADC de 12 bits, até 6 canais", "adc": "1x SAR-ADC mit 12 Bit, bis zu 6 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Nein", "touch": "Nein",
"uart": "3 (2 UART + 1 LP UART)", "uart": "3 (2 UART + 1 LP UART)",
@@ -460,7 +460,7 @@
"flash_externa": "Bis zu 16 MB über QSPI", "flash_externa": "Bis zu 16 MB über QSPI",
"psram_externa": "Nein", "psram_externa": "Nein",
"gpio": "30", "gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 7 canais", "adc": "1x SAR-ADC mit 12 Bit, bis zu 7 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Nein", "touch": "Nein",
"uart": "3 (2 UART + 1 LP UART)", "uart": "3 (2 UART + 1 LP UART)",
@@ -501,7 +501,7 @@
"sha": "SHA-1/224/256", "sha": "SHA-1/224/256",
"rsa": "Nein", "rsa": "Nein",
"ecc": "Ja (P-192, P-256)", "ecc": "Ja (P-192, P-256)",
"ecdsa": "Ja (modo determinístico, RFC 6979)", "ecdsa": "Ja (deterministischer Modus, RFC 6979)",
"hmac": "Nein", "hmac": "Nein",
"assinatura_digital": "Nein", "assinatura_digital": "Nein",
"key_manager": "Nein", "key_manager": "Nein",
@@ -520,11 +520,11 @@
"flash_externa": "Bis zu 16 MB über QSPI (Modelle mit 4 MB oder 8 MB im Gehäuse integriert)", "flash_externa": "Bis zu 16 MB über QSPI (Modelle mit 4 MB oder 8 MB im Gehäuse integriert)",
"psram_externa": "Bis zu 16 MB über QSPI (Modelle mit 2 MB oder 8 MB im Gehäuse integriert)", "psram_externa": "Bis zu 16 MB über QSPI (Modelle mit 2 MB oder 8 MB im Gehäuse integriert)",
"gpio": "30", "gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 4 canais", "adc": "1x SAR-ADC mit 12 Bit, bis zu 4 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Nein", "touch": "Nein",
"uart": "3", "uart": "3",
"spi": "3 (2 para Flash/PSRAM, 1 para uso geral)", "spi": "3 (2 für Flash/PSRAM, 1 für allgemeine Zwecke)",
"i2c": "1", "i2c": "1",
"i2s": "1", "i2s": "1",
"pwm": "LED-PWM (bis zu 6 Kanäle)", "pwm": "LED-PWM (bis zu 6 Kanäle)",
@@ -555,7 +555,7 @@
"descricao": "Hochleistungs-Mikrocontroller ohne integriertes Funkmodul mit 400-MHz-RISC-V-Dual-Core, MIPI-CSI/DSI-Schnittstellen und erweiterter Sicherheit für anspruchsvolle HMIs und Gateways.", "descricao": "Hochleistungs-Mikrocontroller ohne integriertes Funkmodul mit 400-MHz-RISC-V-Dual-Core, MIPI-CSI/DSI-Schnittstellen und erweiterter Sicherheit für anspruchsvolle HMIs und Gateways.",
"arquitetura": "RISC-V de 32 bits dual-core de alto desempenho (HP) + single-core de baixo consumo (LP)", "arquitetura": "RISC-V de 32 bits dual-core de alto desempenho (HP) + single-core de baixo consumo (LP)",
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)", "nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
"frequencia": "Bis zu 400 MHz (HP, revisão v3.x; 360 MHz na v1.x); LP até 40 MHz", "frequencia": "Bis zu 400 MHz (HP, Revision v3.x; 360 MHz bei v1.x); LP bis zu 40 MHz",
"coprocessador_ulp": "Ja (RISC-V, LP CPU de 40 MHz)", "coprocessador_ulp": "Ja (RISC-V, LP CPU de 40 MHz)",
"aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)", "aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)",
"seguranca": { "seguranca": {
@@ -563,7 +563,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Ja (RSA/MPI bis zu 4096 bits)", "rsa": "Ja (RSA/MPI bis zu 4096 bits)",
"ecc": "Ja (P-192, P-256, P-384)", "ecc": "Ja (P-192, P-256, P-384)",
"ecdsa": "Ja (modo determinístico, RFC 6979)", "ecdsa": "Ja (deterministischer Modus, RFC 6979)",
"hmac": "Ja", "hmac": "Ja",
"assinatura_digital": "Ja", "assinatura_digital": "Ja",
"key_manager": "Ja", "key_manager": "Ja",
@@ -574,14 +574,14 @@
}, },
"wifi": "Nein", "wifi": "Nein",
"bluetooth": "Nein", "bluetooth": "Nein",
"ethernet": "Ja (Ethernet MAC integrado)", "ethernet": "Ja (Ethernet-MAC integriert)",
"memoria_sram": "768 KB", "memoria_sram": "768 KB",
"memoria_sram_rtc": "32 KB", "memoria_sram_rtc": "32 KB",
"memoria_rom": "128 KB (HP ROM) + 16 KB (LP ROM)", "memoria_rom": "128 KB (HP ROM) + 16 KB (LP ROM)",
"flash_externa": "Bis zu 128 MB über QSPI/Octal SPI", "flash_externa": "Bis zu 128 MB über QSPI/Octal SPI",
"psram_externa": "16 MB oder 32 MB im Gehäuse integriert (OPI/HPI, 1,8 V)", "psram_externa": "16 MB oder 32 MB im Gehäuse integriert (OPI/HPI, 1,8 V)",
"gpio": "55", "gpio": "55",
"adc": "2x SAR ADC de 12 bits, até 14 canais", "adc": "2x SAR-ADC mit 12 Bit, bis zu 14 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Ja (sensor de toque capacitivo)", "touch": "Ja (sensor de toque capacitivo)",
"uart": "6 (5 UART + 1 LP UART)", "uart": "6 (5 UART + 1 LP UART)",
@@ -591,13 +591,13 @@
"pwm": "LED-PWM (bis zu 8 Kanäle), Motor-PWM (6 Kanäle)", "pwm": "LED-PWM (bis zu 8 Kanäle), Motor-PWM (6 Kanäle)",
"usb": "USB 2.0 High-Speed OTG + USB 2.0 Full-Speed OTG + USB Serial/JTAG", "usb": "USB 2.0 High-Speed OTG + USB 2.0 Full-Speed OTG + USB Serial/JTAG",
"mipi_csi": "MIPI-CSI-Kameraschnittstelle", "mipi_csi": "MIPI-CSI-Kameraschnittstelle",
"mipi_dsi": "Interface MIPI DSI para display", "mipi_dsi": "MIPI-DSI-Schnittstelle für Displays",
"lcd": "8/16/24-Bit-LCD-Schnittstelle", "lcd": "8/16/24-Bit-LCD-Schnittstelle",
"camera": "8/16-Bit-DVP-Kameraschnittstelle", "camera": "8/16-Bit-DVP-Kameraschnittstelle",
"sdio": "1 SD/MMC-Host-Controller (SDHOST)", "sdio": "1 SD/MMC-Host-Controller (SDHOST)",
"sensor_temperatura": "1 (interno)", "sensor_temperatura": "1 (interno)",
"consumo_energia": { "consumo_energia": {
"active": "15-150 mA (40 a 400 MHz, conforme a carga)", "active": "15-150 mA (40 bis 400 MHz, je nach Last)",
"light_sleep": "75-800 µA", "light_sleep": "75-800 µA",
"deep_sleep": "12 µA" "deep_sleep": "12 µA"
}, },
@@ -626,7 +626,7 @@
"sha": "SHA-1/224/256", "sha": "SHA-1/224/256",
"rsa": "Ja (RSA/MPI bis zu 3072 bits)", "rsa": "Ja (RSA/MPI bis zu 3072 bits)",
"ecc": "Ja (P-192, P-256)", "ecc": "Ja (P-192, P-256)",
"ecdsa": "Ja (modo determinístico, RFC 6979)", "ecdsa": "Ja (deterministischer Modus, RFC 6979)",
"hmac": "Ja", "hmac": "Ja",
"assinatura_digital": "Ja", "assinatura_digital": "Ja",
"key_manager": "Nein", "key_manager": "Nein",
@@ -645,7 +645,7 @@
"flash_externa": "Bis zu 16 MB über QSPI", "flash_externa": "Bis zu 16 MB über QSPI",
"psram_externa": "Nein", "psram_externa": "Nein",
"gpio": "19", "gpio": "19",
"adc": "1x SAR ADC de 12 bits, até 5 canais", "adc": "1x SAR-ADC mit 12 Bit, bis zu 5 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "Nein", "touch": "Nein",
"uart": "2", "uart": "2",
@@ -686,7 +686,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Nein", "rsa": "Nein",
"ecc": "Ja (P-192, P-256, P-384)", "ecc": "Ja (P-192, P-256, P-384)",
"ecdsa": "Ja (modo determinístico, RFC 6979)", "ecdsa": "Ja (deterministischer Modus, RFC 6979)",
"hmac": "Ja", "hmac": "Ja",
"assinatura_digital": "Nein", "assinatura_digital": "Nein",
"key_manager": "Nein", "key_manager": "Nein",
@@ -696,18 +696,18 @@
"protecao_dpa": "Nein" "protecao_dpa": "Nein"
}, },
"wifi": "Nein", "wifi": "Nein",
"bluetooth": "Bluetooth 5.4 LE (certificado Bluetooth 6.0; LE Audio, canais isócronos BIS/CIS, PAwR, Connection Subrating und Direction Finding AoA/AoD)", "bluetooth": "Bluetooth 5.4 LE (Bluetooth-6.0-zertifiziert; LE Audio, isochrone Kanäle BIS/CIS, PAwR, Connection Subrating und Direction Finding AoA/AoD)",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
"matter": "Ja", "matter": "Ja",
"memoria_sram": "384 KB", "memoria_sram": "384 KB",
"memoria_sram_rtc": "Sim (LP SRAM)", "memoria_sram_rtc": "Sim (LP SRAM)",
"memoria_rom": "128 KB", "memoria_rom": "128 KB",
"flash_externa": "Sim, via QSPI (4 MB integrada no encapsulamento no ESP32-H4HF4)", "flash_externa": "Ja, über QSPI (4 MB im Gehäuse des ESP32-H4HF4 integriert)",
"psram_externa": "Ja (2 MB integrada no encapsulamento no ESP32-H4HR2)", "psram_externa": "Ja (2 MB im Gehäuse des ESP32-H4HR2 integriert)",
"gpio": "40", "gpio": "40",
"adc": "1x SAR ADC de 12 bits, até 5 canais", "adc": "1x SAR-ADC mit 12 Bit, bis zu 5 Kanäle",
"dac": "Nein", "dac": "Nein",
"touch": "15 canais capacitivos (versão 3, com waterproof e proximidade)", "touch": "15 kapazitive Kanäle (Version 3, mit Wasserfestigkeit und Näherungserkennung)",
"uart": "2", "uart": "2",
"spi": "3", "spi": "3",
"i2c": "2", "i2c": "2",
+2 -2
View File
@@ -174,7 +174,7 @@
"sensor_temperatura": "Sí (1 sensor interno)", "sensor_temperatura": "Sí (1 sensor interno)",
"consumo_energia": { "consumo_energia": {
"active": "78 mW (~24 mA @3.3V)", "active": "78 mW (~24 mA @3.3V)",
"modem_sleep": "13-42 mA (40-80 MHz, conforme a carga)", "modem_sleep": "13-42 mA (40-80 MHz, según la carga)",
"light_sleep": "240 µA (+40 a 200 µA de la PSRAM)", "light_sleep": "240 µA (+40 a 200 µA de la PSRAM)",
"deep_sleep": "7 µA" "deep_sleep": "7 µA"
}, },
@@ -629,7 +629,7 @@
"sdio": "1 controlador host SD/MMC (SDHOST)", "sdio": "1 controlador host SD/MMC (SDHOST)",
"sensor_temperatura": "Sí (1 sensor interno)", "sensor_temperatura": "Sí (1 sensor interno)",
"consumo_energia": { "consumo_energia": {
"active": "15-150 mA (40 a 400 MHz, conforme a carga)", "active": "15-150 mA (40 a 400 MHz, según la carga)",
"light_sleep": "75-800 µA", "light_sleep": "75-800 µA",
"deep_sleep": "12 µA" "deep_sleep": "12 µA"
}, },
+40 -40
View File
@@ -23,18 +23,18 @@
}, },
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, jusqu'à 150 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, jusqu'à 150 Mbps)",
"bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) et Bluetooth LE (dual mode)", "bluetooth": "Bluetooth 4.2 BR/EDR (Clássico) et Bluetooth LE (dual mode)",
"ethernet": "Oui (Ethernet MAC integrado)", "ethernet": "Oui (Ethernet MAC intégré)",
"memoria_sram": "520 KB", "memoria_sram": "520 KB",
"memoria_sram_rtc": "16 KB (dividida em RTC FAST e RTC SLOW)", "memoria_sram_rtc": "16 KB (dividida em RTC FAST e RTC SLOW)",
"memoria_rom": "448 KB (pour le démarrage et les fonctions du noyau)", "memoria_rom": "448 KB (pour le démarrage et les fonctions du noyau)",
"flash_externa": "Jusqu'à 16 MB via QSPI", "flash_externa": "Jusqu'à 16 MB via QSPI",
"psram_externa": "Jusqu'à 8 MB via QSPI (dans des modules spécifiques comme WROVER)", "psram_externa": "Jusqu'à 8 MB via QSPI (dans des modules spécifiques comme WROVER)",
"gpio": "34", "gpio": "34",
"adc": "2x SAR ADC de 12 bits, até 18 canais", "adc": "2x ADC SAR 12 bits, jusqu'à 18 canaux",
"dac": "2x DAC de 8 bits", "dac": "2x DAC de 8 bits",
"touch": "10 canais capacitivos", "touch": "10 canaux capacitifs",
"uart": "3", "uart": "3",
"spi": "4 (SPI, HSPI, VSPI - 2 podem ser usados para Flash/PSRAM)", "spi": "4 (SPI, HSPI, VSPI - 2 utilisables pour Flash/PSRAM)",
"i2c": "2", "i2c": "2",
"i2s": "2", "i2s": "2",
"pwm": "LED PWM (jusqu'à 16 canaux), Motor PWM", "pwm": "LED PWM (jusqu'à 16 canaux), Motor PWM",
@@ -94,11 +94,11 @@
"flash_externa": "Jusqu'à 1 GB via QSPI", "flash_externa": "Jusqu'à 1 GB via QSPI",
"psram_externa": "Jusqu'à 1 GB via QSPI/Octal SPI", "psram_externa": "Jusqu'à 1 GB via QSPI/Octal SPI",
"gpio": "43", "gpio": "43",
"adc": "2x SAR ADC, até 20 canais: saída de 13 bits (0–8191, única da família), resolução efetiva de 12 bits a partir da revisão v1.0 (errata ADC-112)", "adc": "2x ADC SAR, jusqu'à 20 canaux : sortie 13 bits (0–8191, unique dans la famille), résolution effective de 12 bits à partir de la révision v1.0 (erratum ADC-112)",
"dac": "2x DAC de 8 bits", "dac": "2x DAC de 8 bits",
"touch": "14 canais capacitivos", "touch": "14 canaux capacitifs",
"uart": "2", "uart": "2",
"spi": "4 (SPI0/1 para Flash, SPI2/3 para uso geral)", "spi": "4 (SPI0/1 pour la Flash, SPI2/3 pour usage général)",
"i2c": "2", "i2c": "2",
"i2s": "1", "i2s": "1",
"pwm": "LED PWM (jusqu'à 8 canaux)", "pwm": "LED PWM (jusqu'à 8 canaux)",
@@ -133,7 +133,7 @@
"nucleos": "2 (Dual-core)", "nucleos": "2 (Dual-core)",
"frequencia": "Jusqu'à 240 MHz", "frequencia": "Jusqu'à 240 MHz",
"coprocessador_ulp": "Oui (RISC-V)", "coprocessador_ulp": "Oui (RISC-V)",
"aceleradores_ia": "Sim (Vector Instructions para IA)", "aceleradores_ia": "Oui (instructions vectorielles pour l'IA)",
"seguranca": { "seguranca": {
"aes": "AES-128/256", "aes": "AES-128/256",
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
@@ -156,11 +156,11 @@
"flash_externa": "Jusqu'à 1 GB via QSPI/Octal SPI", "flash_externa": "Jusqu'à 1 GB via QSPI/Octal SPI",
"psram_externa": "Jusqu'à 1 GB via QSPI/Octal SPI", "psram_externa": "Jusqu'à 1 GB via QSPI/Octal SPI",
"gpio": "45", "gpio": "45",
"adc": "2x SAR ADC de 12 bits, até 20 canais", "adc": "2x ADC SAR 12 bits, jusqu'à 20 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "14 canais capacitivos", "touch": "14 canaux capacitifs",
"uart": "3", "uart": "3",
"spi": "4 (SPI0/1 para Flash, SPI2/3 para uso geral)", "spi": "4 (SPI0/1 pour la Flash, SPI2/3 pour usage général)",
"i2c": "2", "i2c": "2",
"i2s": "2", "i2s": "2",
"pwm": "LED PWM (jusqu'à 8 canaux), Motor PWM (6 canaux)", "pwm": "LED PWM (jusqu'à 8 canaux), Motor PWM (6 canaux)",
@@ -171,7 +171,7 @@
"sensor_temperatura": "1 (interno)", "sensor_temperatura": "1 (interno)",
"consumo_energia": { "consumo_energia": {
"active": "78 mW (~24 mA @3.3V)", "active": "78 mW (~24 mA @3.3V)",
"modem_sleep": "13-42 mA (40-80 MHz, conforme a carga)", "modem_sleep": "13-42 mA (40-80 MHz, selon la charge)",
"light_sleep": "240 µA (+40 a 200 µA pour la PSRAM)", "light_sleep": "240 µA (+40 a 200 µA pour la PSRAM)",
"deep_sleep": "7 µA" "deep_sleep": "7 µA"
}, },
@@ -202,7 +202,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Oui (RSA/MPI jusqu'à 4096 bits)", "rsa": "Oui (RSA/MPI jusqu'à 4096 bits)",
"ecc": "Oui (P-192, P-256, P-384)", "ecc": "Oui (P-192, P-256, P-384)",
"ecdsa": "Oui (modo determinístico, RFC 6979)", "ecdsa": "Oui (mode déterministe, RFC 6979)",
"hmac": "Oui", "hmac": "Oui",
"assinatura_digital": "Oui", "assinatura_digital": "Oui",
"key_manager": "Oui", "key_manager": "Oui",
@@ -221,9 +221,9 @@
"flash_externa": "Jusqu'à 256 MB via QSPI", "flash_externa": "Jusqu'à 256 MB via QSPI",
"psram_externa": "16 MB ou 32 MB octal DDR, uniquement dans le boîtier (NRV16, NRV32)", "psram_externa": "16 MB ou 32 MB octal DDR, uniquement dans le boîtier (NRV16, NRV32)",
"gpio": "60", "gpio": "60",
"adc": "2x SAR ADC de 12 bits, até 16 canais", "adc": "2x ADC SAR 12 bits, jusqu'à 16 canaux",
"dac": "Oui (2x DAC de 10 bits e 2x DAC de 12 bits)", "dac": "Oui (2x DAC de 10 bits e 2x DAC de 12 bits)",
"touch": "14 canais capacitivos", "touch": "14 canaux capacitifs",
"uart": "5 (4 UART + 1 LP UART)", "uart": "5 (4 UART + 1 LP UART)",
"spi": "7 (6 SPI + 1 LP SPI)", "spi": "7 (6 SPI + 1 LP SPI)",
"i2c": "3 (2 I2C + 1 LP I2C)", "i2c": "3 (2 I2C + 1 LP I2C)",
@@ -274,17 +274,17 @@
}, },
"wifi": "IEEE 802.11 b/g/n (2.4 GHz, jusqu'à 72.2 Mbps)", "wifi": "IEEE 802.11 b/g/n (2.4 GHz, jusqu'à 72.2 Mbps)",
"bluetooth": "Bluetooth 5.3 LE (certificado)", "bluetooth": "Bluetooth 5.3 LE (certificado)",
"memoria_sram": "272 KB (16 KB para cache)", "memoria_sram": "272 KB (dont 16 KB de cache)",
"memoria_sram_rtc": "Non", "memoria_sram_rtc": "Non",
"memoria_rom": "576 KB", "memoria_rom": "576 KB",
"flash_externa": "Jusqu'à 16 MB via QSPI, mais indisponible sur les variantes vendues (ESP8684H2X/H4X ont 2 MB ou 4 MB dans le boîtier et n'exposent pas les broches de flash)", "flash_externa": "Jusqu'à 16 MB via QSPI, mais indisponible sur les variantes vendues (ESP8684H2X/H4X ont 2 MB ou 4 MB dans le boîtier et n'exposent pas les broches de flash)",
"psram_externa": "Nonn", "psram_externa": "Nonn",
"gpio": "14", "gpio": "14",
"adc": "1x SAR ADC de 12 bits, até 5 canais", "adc": "1x ADC SAR 12 bits, jusqu'à 5 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Nonn", "touch": "Nonn",
"uart": "2", "uart": "2",
"spi": "3 (SPI0/1 para Flash interna, SPI2 para uso geral)", "spi": "3 (SPI0/1 pour la Flash interne, SPI2 pour usage général)",
"i2c": "1 (Master-only)", "i2c": "1 (Master-only)",
"i2s": "Non", "i2s": "Non",
"pwm": "LED PWM (jusqu'à 6 canaux)", "pwm": "LED PWM (jusqu'à 6 canaux)",
@@ -336,11 +336,11 @@
"flash_externa": "Jusqu'à 16 MB via QSPI", "flash_externa": "Jusqu'à 16 MB via QSPI",
"psram_externa": "Nonn", "psram_externa": "Nonn",
"gpio": "22", "gpio": "22",
"adc": "2x SAR ADC de 12 bits, até 6 canais", "adc": "2x ADC SAR 12 bits, jusqu'à 6 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Nonn", "touch": "Nonn",
"uart": "2", "uart": "2",
"spi": "3 (SPI0/1 para Flash, SPI2 para uso geral)", "spi": "3 (SPI0/1 pour la Flash, SPI2 pour usage général)",
"i2c": "1", "i2c": "1",
"i2s": "1", "i2s": "1",
"pwm": "LED PWM (jusqu'à 6 canaux)", "pwm": "LED PWM (jusqu'à 6 canaux)",
@@ -379,7 +379,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Oui (RSA/MPI jusqu'à 3072 bits)", "rsa": "Oui (RSA/MPI jusqu'à 3072 bits)",
"ecc": "Oui (P-192, P-256, P-384)", "ecc": "Oui (P-192, P-256, P-384)",
"ecdsa": "Oui (modo determinístico, RFC 6979)", "ecdsa": "Oui (mode déterministe, RFC 6979)",
"hmac": "Oui", "hmac": "Oui",
"assinatura_digital": "Oui", "assinatura_digital": "Oui",
"key_manager": "Oui", "key_manager": "Oui",
@@ -398,7 +398,7 @@
"flash_externa": "Jusqu'à 16 MB via QSPI", "flash_externa": "Jusqu'à 16 MB via QSPI",
"psram_externa": "Oui, via SPI (controlador de flash/PSRAM com cache)", "psram_externa": "Oui, via SPI (controlador de flash/PSRAM com cache)",
"gpio": "29", "gpio": "29",
"adc": "1x SAR ADC de 12 bits, até 6 canais", "adc": "1x ADC SAR 12 bits, jusqu'à 6 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Nonn", "touch": "Nonn",
"uart": "3 (2 UART + 1 LP UART)", "uart": "3 (2 UART + 1 LP UART)",
@@ -460,7 +460,7 @@
"flash_externa": "Jusqu'à 16 MB via QSPI", "flash_externa": "Jusqu'à 16 MB via QSPI",
"psram_externa": "Nonn", "psram_externa": "Nonn",
"gpio": "30", "gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 7 canais", "adc": "1x ADC SAR 12 bits, jusqu'à 7 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Nonn", "touch": "Nonn",
"uart": "3 (2 UART + 1 LP UART)", "uart": "3 (2 UART + 1 LP UART)",
@@ -501,7 +501,7 @@
"sha": "SHA-1/224/256", "sha": "SHA-1/224/256",
"rsa": "Nonn", "rsa": "Nonn",
"ecc": "Oui (P-192, P-256)", "ecc": "Oui (P-192, P-256)",
"ecdsa": "Oui (modo determinístico, RFC 6979)", "ecdsa": "Oui (mode déterministe, RFC 6979)",
"hmac": "Nonn", "hmac": "Nonn",
"assinatura_digital": "Nonn", "assinatura_digital": "Nonn",
"key_manager": "Nonn", "key_manager": "Nonn",
@@ -520,11 +520,11 @@
"flash_externa": "Jusqu'à 16 MB via QSPI (modèles avec 4 MB ou 8 MB intégrés dans le boîtier)", "flash_externa": "Jusqu'à 16 MB via QSPI (modèles avec 4 MB ou 8 MB intégrés dans le boîtier)",
"psram_externa": "Jusqu'à 16 MB via QSPI (modèles avec 2 MB ou 8 MB intégrés dans le boîtier)", "psram_externa": "Jusqu'à 16 MB via QSPI (modèles avec 2 MB ou 8 MB intégrés dans le boîtier)",
"gpio": "30", "gpio": "30",
"adc": "1x SAR ADC de 12 bits, até 4 canais", "adc": "1x ADC SAR 12 bits, jusqu'à 4 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Nonn", "touch": "Nonn",
"uart": "3", "uart": "3",
"spi": "3 (2 para Flash/PSRAM, 1 para uso geral)", "spi": "3 (2 pour Flash/PSRAM, 1 pour usage général)",
"i2c": "1", "i2c": "1",
"i2s": "1", "i2s": "1",
"pwm": "LED PWM (jusqu'à 6 canaux)", "pwm": "LED PWM (jusqu'à 6 canaux)",
@@ -555,7 +555,7 @@
"descricao": "Microcontrôleur haute performance sans radio intégrée, doté d'un double cœur RISC-V à 400 MHz, d'interfaces vidéo MIPI-CSI/DSI et d'une sécurité renforcée pour les écrans complexes et les passerelles.", "descricao": "Microcontrôleur haute performance sans radio intégrée, doté d'un double cœur RISC-V à 400 MHz, d'interfaces vidéo MIPI-CSI/DSI et d'une sécurité renforcée pour les écrans complexes et les passerelles.",
"arquitetura": "RISC-V 32 bits bicœur de alto desempenho (HP) + monocœur de baixo consumo (LP)", "arquitetura": "RISC-V 32 bits bicœur de alto desempenho (HP) + monocœur de baixo consumo (LP)",
"nucleos": "2 (Dual-core HP) + 1 (Single-core LP)", "nucleos": "2 (Dual-core HP) + 1 (Single-core LP)",
"frequencia": "Jusqu'à 400 MHz (HP, revisão v3.x; 360 MHz na v1.x); LP até 40 MHz", "frequencia": "Jusqu'à 400 MHz (HP, révision v3.x ; 360 MHz en v1.x) ; LP jusqu'à 40 MHz",
"coprocessador_ulp": "Oui (RISC-V, LP CPU de 40 MHz)", "coprocessador_ulp": "Oui (RISC-V, LP CPU de 40 MHz)",
"aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)", "aceleradores_ia": "Sim (Vector Instructions, aceleradores de IA)",
"seguranca": { "seguranca": {
@@ -563,7 +563,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Oui (RSA/MPI jusqu'à 4096 bits)", "rsa": "Oui (RSA/MPI jusqu'à 4096 bits)",
"ecc": "Oui (P-192, P-256, P-384)", "ecc": "Oui (P-192, P-256, P-384)",
"ecdsa": "Oui (modo determinístico, RFC 6979)", "ecdsa": "Oui (mode déterministe, RFC 6979)",
"hmac": "Oui", "hmac": "Oui",
"assinatura_digital": "Oui", "assinatura_digital": "Oui",
"key_manager": "Oui", "key_manager": "Oui",
@@ -574,14 +574,14 @@
}, },
"wifi": "Nonn", "wifi": "Nonn",
"bluetooth": "Nonn", "bluetooth": "Nonn",
"ethernet": "Oui (Ethernet MAC integrado)", "ethernet": "Oui (Ethernet MAC intégré)",
"memoria_sram": "768 KB", "memoria_sram": "768 KB",
"memoria_sram_rtc": "32 KB", "memoria_sram_rtc": "32 KB",
"memoria_rom": "128 KB (HP ROM) + 16 KB (LP ROM)", "memoria_rom": "128 KB (HP ROM) + 16 KB (LP ROM)",
"flash_externa": "Jusqu'à 128 MB via QSPI/Octal SPI", "flash_externa": "Jusqu'à 128 MB via QSPI/Octal SPI",
"psram_externa": "16 MB ou 32 MB intégrés dans le boîtier (OPI/HPI, 1,8 V)", "psram_externa": "16 MB ou 32 MB intégrés dans le boîtier (OPI/HPI, 1,8 V)",
"gpio": "55", "gpio": "55",
"adc": "2x SAR ADC de 12 bits, até 14 canais", "adc": "2x ADC SAR 12 bits, jusqu'à 14 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Oui (sensor de toque capacitivo)", "touch": "Oui (sensor de toque capacitivo)",
"uart": "6 (5 UART + 1 LP UART)", "uart": "6 (5 UART + 1 LP UART)",
@@ -591,13 +591,13 @@
"pwm": "LED PWM (jusqu'à 8 canaux), Motor PWM (6 canaux)", "pwm": "LED PWM (jusqu'à 8 canaux), Motor PWM (6 canaux)",
"usb": "USB 2.0 High-Speed OTG + USB 2.0 Full-Speed OTG + USB Serial/JTAG", "usb": "USB 2.0 High-Speed OTG + USB 2.0 Full-Speed OTG + USB Serial/JTAG",
"mipi_csi": "Interface de caméra MIPI CSI", "mipi_csi": "Interface de caméra MIPI CSI",
"mipi_dsi": "Interface MIPI DSI para display", "mipi_dsi": "Interface MIPI DSI pour écran",
"lcd": "Interface LCD 8/16/24 bits", "lcd": "Interface LCD 8/16/24 bits",
"camera": "Interface DVP 8/16 bits", "camera": "Interface DVP 8/16 bits",
"sdio": "1 contrôleur hôte SD/MMC (SDHOST)", "sdio": "1 contrôleur hôte SD/MMC (SDHOST)",
"sensor_temperatura": "1 (interno)", "sensor_temperatura": "1 (interno)",
"consumo_energia": { "consumo_energia": {
"active": "15-150 mA (40 a 400 MHz, conforme a carga)", "active": "15-150 mA (40 à 400 MHz, selon la charge)",
"light_sleep": "75-800 µA", "light_sleep": "75-800 µA",
"deep_sleep": "12 µA" "deep_sleep": "12 µA"
}, },
@@ -626,7 +626,7 @@
"sha": "SHA-1/224/256", "sha": "SHA-1/224/256",
"rsa": "Oui (RSA/MPI jusqu'à 3072 bits)", "rsa": "Oui (RSA/MPI jusqu'à 3072 bits)",
"ecc": "Oui (P-192, P-256)", "ecc": "Oui (P-192, P-256)",
"ecdsa": "Oui (modo determinístico, RFC 6979)", "ecdsa": "Oui (mode déterministe, RFC 6979)",
"hmac": "Oui", "hmac": "Oui",
"assinatura_digital": "Oui", "assinatura_digital": "Oui",
"key_manager": "Nonn", "key_manager": "Nonn",
@@ -645,7 +645,7 @@
"flash_externa": "Jusqu'à 16 MB via QSPI", "flash_externa": "Jusqu'à 16 MB via QSPI",
"psram_externa": "Nonn", "psram_externa": "Nonn",
"gpio": "19", "gpio": "19",
"adc": "1x SAR ADC de 12 bits, até 5 canais", "adc": "1x ADC SAR 12 bits, jusqu'à 5 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "Nonn", "touch": "Nonn",
"uart": "2", "uart": "2",
@@ -686,7 +686,7 @@
"sha": "SHA-1/224/256/384/512", "sha": "SHA-1/224/256/384/512",
"rsa": "Nonn", "rsa": "Nonn",
"ecc": "Oui (P-192, P-256, P-384)", "ecc": "Oui (P-192, P-256, P-384)",
"ecdsa": "Oui (modo determinístico, RFC 6979)", "ecdsa": "Oui (mode déterministe, RFC 6979)",
"hmac": "Oui", "hmac": "Oui",
"assinatura_digital": "Nonn", "assinatura_digital": "Nonn",
"key_manager": "Nonn", "key_manager": "Nonn",
@@ -696,18 +696,18 @@
"protecao_dpa": "Nonn" "protecao_dpa": "Nonn"
}, },
"wifi": "Nonn", "wifi": "Nonn",
"bluetooth": "Bluetooth 5.4 LE (certificado Bluetooth 6.0; LE Audio, canais isócronos BIS/CIS, PAwR, Connection Subrating et Direction Finding AoA/AoD)", "bluetooth": "Bluetooth 5.4 LE (certifié Bluetooth 6.0 ; LE Audio, canaux isochrones BIS/CIS, PAwR, Connection Subrating et Direction Finding AoA/AoD)",
"zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)", "zigbee_thread": "IEEE 802.15.4 (Zigbee 3.0, Thread 1.4)",
"matter": "Oui", "matter": "Oui",
"memoria_sram": "384 KB", "memoria_sram": "384 KB",
"memoria_sram_rtc": "Sim (LP SRAM)", "memoria_sram_rtc": "Sim (LP SRAM)",
"memoria_rom": "128 KB", "memoria_rom": "128 KB",
"flash_externa": "Sim, via QSPI (4 MB integrada no encapsulamento no ESP32-H4HF4)", "flash_externa": "Oui, via QSPI (4 MB intégrée au boîtier de l'ESP32-H4HF4)",
"psram_externa": "Oui (2 MB integrada no encapsulamento no ESP32-H4HR2)", "psram_externa": "Oui (2 MB intégrée au boîtier de l'ESP32-H4HR2)",
"gpio": "40", "gpio": "40",
"adc": "1x SAR ADC de 12 bits, até 5 canais", "adc": "1x ADC SAR 12 bits, jusqu'à 5 canaux",
"dac": "Nonn", "dac": "Nonn",
"touch": "15 canais capacitivos (versão 3, com waterproof e proximidade)", "touch": "15 canaux capacitifs (version 3, avec étanchéité et détection de proximité)",
"uart": "2", "uart": "2",
"spi": "3", "spi": "3",
"i2c": "2", "i2c": "2",
+2 -2
View File
@@ -41,7 +41,7 @@
"caracteristicas": [ "caracteristicas": [
"通过官方应用辅助进行 Wi-Fi 和 BLE 配网", "通过官方应用辅助进行 Wi-Fi 和 BLE 配网",
"根据固件参数在应用中动态生成界面", "根据固件参数在应用中动态生成界面",
"原生集成 Amazon Alexa 和 Google Assistant", "原生集成 Alexa 和 Google Assistant",
"同时支持本地控制(通过 mDNS/HTTP 的 Local Control)和云端", "同时支持本地控制(通过 mDNS/HTTP 的 Local Control)和云端",
"用于设备群和 OTA 管理的管理面板(RainMaker Dashboard)", "用于设备群和 OTA 管理的管理面板(RainMaker Dashboard)",
"原生兼容 Matter 节点和设备桥接", "原生兼容 Matter 节点和设备桥接",
@@ -63,7 +63,7 @@
"nome": "ESP-Matter", "nome": "ESP-Matter",
"icone": "🏠", "icone": "🏠",
"nome_completo": "ESP Matter SDK", "nome_completo": "ESP Matter SDK",
"descricao": "乐鑫面向 Matter 通用智能家居标准(原 Project CHIP)的官方 SDK。让你的设备可与 Apple HomeKit、Google Home、Amazon Alexa 和 Home Assistant 互操作。", "descricao": "乐鑫面向 Matter 通用智能家居标准(原 Project CHIP)的官方 SDK。让你的设备可与 Apple HomeKit、Google Home、Alexa 和 Home Assistant 互操作。",
"linguagem": "C++", "linguagem": "C++",
"tipo": "智能家居框架", "tipo": "智能家居框架",
"funcao_principal": "开发兼容 Wi-Fi 和 Thread 上统一 Matter 标准的智能设备和桥接", "funcao_principal": "开发兼容 Wi-Fi 和 Thread 上统一 Matter 标准的智能设备和桥接",
+1 -1
View File
@@ -5,7 +5,7 @@
// Ponto único de configuração de idiomas ATIVOS. // Ponto único de configuração de idiomas ATIVOS.
// Para ativar um novo idioma no site, basta adicionar seu código aqui (ex.: ["pt", "en", "es"]). // Para ativar um novo idioma no site, basta adicionar seu código aqui (ex.: ["pt", "en", "es"]).
export const ENABLED_LOCALES = ["pt", "en", "es", "fr", "de"]; export const ENABLED_LOCALES = ["pt", "en", "es", "fr", "de", "zh-CN"];
export const ALL_LOCALES = [ export const ALL_LOCALES = [
{ code: "pt", label: "Português", short: "PT", lang: "pt-BR", og: "pt_BR" }, { code: "pt", label: "Português", short: "PT", lang: "pt-BR", og: "pt_BR" },
+1 -1
View File
@@ -166,7 +166,7 @@ export function montarConhecimento(series, conexoes) {
for (const [modo, valor] of Object.entries(s.consumo_energia ?? {})) { for (const [modo, valor] of Object.entries(s.consumo_energia ?? {})) {
const base = modo.replace(/_cpu.*|_wifi.*/, ""); const base = modo.replace(/_cpu.*|_wifi.*/, "");
const label = ROTULOS_ENERGIA[modo] ?? ROTULOS_ENERGIA[base] ?? modo.replace(/_/g, " "); const label = ROTULOS_ENERGIA[modo] ?? ROTULOS_ENERGIA[base] ?? modo.replace(/_/g, " ");
specs.push({ label: `Consumo: ${label}`, valor: String(valor), secao: "energia", topicos: ["consumo"] }); specs.push({ label: `Power: ${label}`, valor: String(valor), secao: "energia", topicos: ["consumo"] });
} }
saida[chave] = { saida[chave] = {
nome: s.nome_completo ?? chave, nome: s.nome_completo ?? chave,
+5 -5
View File
@@ -278,7 +278,7 @@ export const COMPONENTES_REGISTRY = [
categoria: "cloud_conectividade", categoria: "cloud_conectividade",
oficial: true, oficial: true,
popular: true, popular: true,
titulo: "ESP-NOW Enhanced & Provisioning", titulo: "ESP-NOW 增强版与配网",
descricao: "ESP-NOW 协议的扩展,提供遥控配对、AES-128 安全以及多设备控制等高级功能。", descricao: "ESP-NOW 协议的扩展,提供遥控配对、AES-128 安全以及多设备控制等高级功能。",
tags: ["ESP-NOW", "无线", "低延迟", "广播", "遥控"], tags: ["ESP-NOW", "无线", "低延迟", "广播", "遥控"],
chips: ["ESP32", "ESP32-S2", "ESP32-S3", "ESP32-C3", "ESP32-C6"], chips: ["ESP32", "ESP32-S2", "ESP32-S3", "ESP32-C3", "ESP32-C6"],
@@ -406,7 +406,7 @@ export const COMPONENTES_REGISTRY = [
oficial: true, oficial: true,
popular: true, popular: true,
titulo: "ESP Matter SDK", titulo: "ESP Matter SDK",
descricao: "官方 SDK,用于创建可在 Wi-Fi 和 Thread 上与 Apple Home、Google Home、Amazon Alexa 和 Home Assistant 互操作的设备。", descricao: "官方 SDK,用于创建可在 Wi-Fi 和 Thread 上与 Apple Home、Google Home、Alexa 和 Home Assistant 互操作的设备。",
tags: ["Matter", "智能家居", "Apple HomeKit", "Google Home", "Thread", "CHIP"], tags: ["Matter", "智能家居", "Apple HomeKit", "Google Home", "Thread", "CHIP"],
chips: ["ESP32", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-H2"], chips: ["ESP32", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-H2"],
urlRegistry: "https://components.espressif.com/components/espressif/esp-matter", urlRegistry: "https://components.espressif.com/components/espressif/esp-matter",
@@ -494,7 +494,7 @@ export const COMPONENTES_REGISTRY = [
categoria: "drivers_perifericos", categoria: "drivers_perifericos",
oficial: true, oficial: true,
popular: true, popular: true,
titulo: "Addressable LED Strip (WS2812/SK6812)", titulo: "可寻址 LED 灯带(WS2812/SK6812)",
descricao: "高性能驱动,使用 RMT 或 SPI 外设控制 WS2812、WS2813 和 SK6812 可寻址 LED 灯带与灯阵。", descricao: "高性能驱动,使用 RMT 或 SPI 外设控制 WS2812、WS2813 和 SK6812 可寻址 LED 灯带与灯阵。",
tags: ["WS2812B", "Neopixel", "SK6812", "RMT", "LED 灯带", "RGB"], tags: ["WS2812B", "Neopixel", "SK6812", "RMT", "LED 灯带", "RGB"],
chips: ["ESP32", "ESP32-S2", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4"], chips: ["ESP32", "ESP32-S2", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4"],
@@ -719,7 +719,7 @@ export const COMPONENTES_REGISTRY = [
categoria: "bsp_placas", categoria: "bsp_placas",
oficial: false, oficial: false,
popular: true, popular: true,
titulo: "BSP Waveshare ESP32-P4 Touch LCD 7\"", titulo: "BSP Waveshare ESP32-P4 7\" 触摸 LCD",
descricao: "面向高性能 Waveshare ESP32-P4 开发板的支持包,配备 7 英寸电容触摸屏(MIPI-DSI 1024x600)和 Wi-Fi 6 协处理器。", descricao: "面向高性能 Waveshare ESP32-P4 开发板的支持包,配备 7 英寸电容触摸屏(MIPI-DSI 1024x600)和 Wi-Fi 6 协处理器。",
tags: ["BSP", "ESP32-P4", "MIPI-DSI", "Waveshare", "7 英寸屏", "触摸"], tags: ["BSP", "ESP32-P4", "MIPI-DSI", "Waveshare", "7 英寸屏", "触摸"],
chips: ["ESP32-P4"], chips: ["ESP32-P4"],
@@ -757,7 +757,7 @@ export const COMPONENTES_REGISTRY = [
categoria: "sistema_utilitarios", categoria: "sistema_utilitarios",
oficial: true, oficial: true,
popular: true, popular: true,
titulo: "ESP-IDF C++ Idiomatic Wrappers", titulo: "ESP-IDF C++ 现代封装",
descricao: "现代 C++(C++17/C++20/C++23)类与封装集合,采用 RAII 语义,覆盖 GPIO、Wi-Fi、定时器、FreeRTOS 队列和信号量。", descricao: "现代 C++(C++17/C++20/C++23)类与封装集合,采用 RAII 语义,覆盖 GPIO、Wi-Fi、定时器、FreeRTOS 队列和信号量。",
tags: ["C++", "C++20", "RAII", "Modern C++", "面向对象", "FreeRTOS"], tags: ["C++", "C++20", "RAII", "Modern C++", "面向对象", "FreeRTOS"],
chips: ["ESP32", "ESP32-S2", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-H2", "ESP32-P4"], chips: ["ESP32", "ESP32-S2", "ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-H2", "ESP32-P4"],
+184
View File
@@ -0,0 +1,184 @@
/**
* Conhecimento de domínio da busca inteligente (zh-CN).
*
* Sem dependências nem alias de import: roda no servidor (montando o índice),
* no navegador (interpretando a consulta) e em scripts de teste no Node.
*
* Todos os textos de `termos` já estão normalizados (minúsculos, sem acento).
*/
export const normalizar = (s = "") =>
String(s)
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "");
/**
* Reconhece pinos ocupados por flash/PSRAM com base no que o diagrama diz.
* "forte": o pino é consumido pela memória (ou é a alimentação dela).
* "fraco": só é afetado em certos modos (ex.: SPI de 8 linhas/octal).
* Avisos genéricos de tensão mínima ("para chips com flash integrada...") não contam.
*/
const nivelMemoria = (palavra) => (p) => {
const citam = p._avs.filter((a) => a.includes(palavra) && !/^对于集成/.test(a));
if (citam.some((a) => /(存储使用|专用于|连接到|用于存储)/.test(a) && !/除非/.test(a))) return "forte";
if (p._fn.some((f) => /(供电|alimentacao|vdd)/.test(f) && f.includes(palavra))) return "forte";
if (citam.length) return "fraco";
return null;
};
/**
* Assuntos que o usuário pode citar. Cada um sabe:
* - termos: como as pessoas escrevem (sinônimos, siglas, plurais comuns);
* - pinoCats / pinoRe: como reconhecer os pinos relacionados no diagrama;
* - pinoRotulo: frase usada no título do resultado de pinos.
* Os dados de especificação ligam-se aos assuntos por SPEC_CAMPOS.
*/
export const TOPICOS = [
{ id: "flash", nome: "闪存", termos: ["flash", "memoria flash", "spi flash", "armazenamento", "firmware", "rom externa", "闪存", "外部闪存", "程序存储", "固件存储"], pinoNivel: nivelMemoria("flash"), pinoRotulo: "连接到闪存" },
{ id: "psram", nome: "PSRAM", termos: ["psram", "ram externa", "spiram", "外部内存", "伪静态"], pinoNivel: nivelMemoria("psram"), pinoRotulo: "连接到 PSRAM" },
{ id: "memoria", nome: "内存", termos: ["memoria", "sram", "heap", "rtc ram", "kb de ram", "memoria interna", "ram", "memory", "内存", "内部内存", "堆", "存储器", "容量"], pinoRotulo: "" },
{ id: "wifi", nome: "Wi-Fi", termos: ["wifi", "wi-fi", "wireless", "sem fio", "802.11", "wifi6", "wifi 6", "wi-fi 6", "无线网", "无线", "无线网络"], pinoCats: ["Antena"], pinoRotulo: "天线与射频" },
{ id: "bluetooth", nome: "蓝牙", termos: ["bluetooth", "ble", "bt", "bluetooth le", "mesh", "蓝牙", "低功耗蓝牙", "经典蓝牙"], pinoCats: ["Antena"], pinoRotulo: "天线与射频" },
{ id: "zigbee", nome: "Zigbee/Thread", termos: ["zigbee", "thread", "802.15.4", "ieee 802.15.4", "组网"], pinoRotulo: "" },
{ id: "matter", nome: "Matter", termos: ["matter", "casa inteligente", "smart home", "智能家居", "家居"], pinoRotulo: "" },
{ id: "ethernet", nome: "以太网", termos: ["ethernet", "rmii", "cabo de rede", "lan", "以太网", "网口", "有线网络", "网线"], pinoCats: ["Ethernet"], pinoRotulo: "以太网" },
{ id: "can", nome: "CAN / TWAI", termos: ["can", "twai", "can bus", "canbus", "can fd", "总线", "车载总线"], pinoRe: /twai|\bcan\b/, pinoRotulo: "CAN/TWAI" },
{ id: "usb", nome: "USB", termos: ["usb", "otg", "usb serial", "usb otg", "hid", "cdc", "原生usb"], pinoCats: ["USB"], pinoRotulo: "USB" },
{ id: "jtag", nome: "JTAG / 调试", termos: ["jtag", "debug", "depuracao", "depurar", "openocd", "gdb", "调试", "调试器", "断点"], pinoCats: ["JTAG"], pinoRotulo: "JTAG" },
{ id: "adc", nome: "ADC", termos: ["adc", "analogico", "entrada analogica", "conversor analogico", "analogread", "saradc", "analog", "模数转换", "模拟输入", "模拟量", "模拟"], pinoCats: ["ADC"], pinoRotulo: "带 ADC 功能" },
{ id: "dac", nome: "DAC", termos: ["dac", "saida analogica", "conversor digital", "数模转换", "模拟输出"], pinoCats: ["DAC"], pinoRotulo: "带 DAC 功能" },
{ id: "touch", nome: "电容触摸", termos: ["touch", "capacitivo", "toque", "touchpad", "sensor de toque", "触摸", "触控", "电容"], pinoCats: ["Touch"], pinoRotulo: "电容触摸" },
{ id: "uart", nome: "UART", termos: ["uart", "serial", "tx", "rx", "txd", "rxd", "porta serial", "串口", "串行", "串行通信"], pinoCats: ["UART"], pinoRotulo: "UART" },
{ id: "spi", nome: "SPI", termos: ["spi", "mosi", "miso", "sclk", "barramento spi", "spi总线"], pinoCats: ["SPI"], pinoRotulo: "SPI" },
{ id: "i2c", nome: "I2C", termos: ["i2c", "sda", "scl", "twi", "iic", "i2c总线"], pinoCats: ["I2C"], pinoRotulo: "I2C" },
{ id: "i2s", nome: "I2S", termos: ["i2s", "audio", "som", "microfone", "pdm", "codec", "音频", "声音", "麦克风", "编解码"], pinoRe: /i2s|\bpdm\b/, pinoRotulo: "I2S / 音频" },
{ id: "pwm", nome: "PWM", termos: ["pwm", "ledc", "mcpwm", "servo", "dimmer", "脉宽", "舵机", "调光", "电机"], pinoRe: /ledc|pwm/, pinoRotulo: "PWM" },
{ id: "sd", nome: "SD 卡 / SDIO", termos: ["sd", "sdio", "sd card", "sdcard", "cartao sd", "microsd", "sdmmc", "mmc", "sd卡", "存储卡", "内存卡"], pinoCats: ["SD"], pinoRe: /sdio|sdmmc|\bsd\b/, pinoRotulo: "SD 卡 / SDIO" },
{ id: "camera", nome: "摄像头", termos: ["camera", "cam", "dvp", "csi", "mipi csi", "ov2640", "video", "摄像头", "相机", "视频", "拍照"], pinoCats: ["CSI"], pinoRe: /\bcam\b|dvp|cam_/, pinoRotulo: "摄像头" },
{ id: "lcd", nome: "显示屏 / LCD", termos: ["lcd", "display", "tela", "dsi", "mipi dsi", "oled", "tft", "lvgl", "rgb", "显示屏", "屏幕", "液晶", "显示"], pinoCats: ["DSI"], pinoRe: /lcd/, pinoRotulo: "显示屏/LCD" },
{ id: "strapping", nome: "启动配置", termos: ["strapping", "boot", "bootstrap", "modo de boot", "download mode", "modo de gravacao", "inicializacao", "启动配置", "启动模式", "下载模式", "烧录模式"], pinoCats: ["Strapping"], pinoRotulo: "启动配置" },
{ id: "energia_pinos", nome: "电源", termos: ["alimentacao", "vdd", "3v3", "3.3v", "tensao", "regulador", "ldo", "fonte", "power", "电源", "供电", "电压", "稳压", "稳压器"], pinoCats: ["Power"], pinoRotulo: "电源" },
{ id: "clock", nome: "时钟 / 晶振", termos: ["clock", "xtal", "cristal", "oscilador", "32khz", "40mhz", "crystal", "时钟", "晶振", "晶体", "振荡器"], pinoCats: ["Clock"], pinoRotulo: "时钟/晶振" },
{ id: "gpio", nome: "GPIO", termos: ["gpio", "entradas e saidas", "通用io", "输入输出"], pinoCats: ["GPIO"], pinoRotulo: "GPIO" },
{ id: "baixo_consumo", nome: "低功耗 / ULP", termos: ["ulp", "lp core", "lp cpu", "coprocessador", "lp gpio", "low power", "rtc gpio", "协处理器", "超低功耗"], pinoCats: ["Low Power"], pinoRotulo: "低功耗(LP/RTC)" },
{ id: "antena", nome: "天线 / 射频", termos: ["antena", "rf", "radiofrequencia", "ipex", "antenna", "天线", "射频"], pinoCats: ["Antena"], pinoRotulo: "天线/射频" },
{ id: "seguranca", nome: "安全", termos: ["seguranca", "secure boot", "criptografia", "encriptacao", "aes", "sha", "rsa", "hmac", "ecdsa", "efuse", "tee", "trng", "rng", "hsm", "protecao", "security", "安全", "加密", "安全启动", "密钥", "熔丝", "可信"], pinoRotulo: "" },
{ id: "consumo", nome: "功耗", termos: ["consumo", "bateria", "deep sleep", "light sleep", "sleep", "modem sleep", "corrente", "miliamperes", "autonomia", "economia de energia", "low power mode", "power consumption", "功耗", "电池", "深度睡眠", "浅睡眠", "电流", "待机", "续航", "省电", "耗电"], pinoRotulo: "" },
{ id: "temperatura", nome: "温度", termos: ["temperatura", "calor", "graus", "faixa de temperatura", "industrial", "temperature", "温度", "工作温度", "高温", "低温"], pinoRotulo: "" },
{ id: "ia", nome: "AI / 加速", termos: ["ia", "ai", "machine learning", "ml", "tinyml", "vetorial", "inteligencia artificial", "rede neural", "esp-nn", "esp-dl", "人工智能", "机器学习", "神经网络", "边缘ai", "向量"], pinoRotulo: "" },
{ id: "processador", nome: "处理器", termos: ["processador", "cpu", "nucleo", "nucleos", "core", "dual core", "single core", "arquitetura", "xtensa", "risc-v", "riscv", "processor", "处理器", "内核", "核心", "双核", "单核", "架构", "主频"], pinoRotulo: "" },
{ id: "frequencia", nome: "频率", termos: ["frequencia", "mhz", "velocidade", "desempenho", "performance", "frequency", "频率", "主频", "速度", "性能"], pinoRotulo: "" },
{ id: "ferramentas", nome: "工具 / SDK", termos: ["esp-idf", "idf", "arduino", "sdk", "toolchain", "versao do idf", "tools", "开发环境", "工具链", "开发框架"], pinoRotulo: "" },
{ id: "encapsulamento", nome: "模组与封装", termos: ["modulo", "modulos", "encapsulamento", "package", "qfn", "wroom", "wrover", "mini-1", "modules", "模组", "封装", "型号"], pinoRotulo: "" },
];
/**
* Campos de public/series.json que respondem a cada assunto.
* `campo` aceita caminho com ponto (seguranca.aes). `secao` é o id da âncora
* na página /series/[key].
*/
export const SPEC_CAMPOS = [
{ campo: "arquitetura", label: "架构", secao: "processador", topicos: ["processador"] },
{ campo: "nucleos", label: "内核", secao: "processador", topicos: ["processador"] },
{ campo: "frequencia", label: "频率", secao: "processador", topicos: ["frequencia", "processador"] },
{ campo: "coprocessador_ulp", label: "ULP 协处理器", secao: "processador", topicos: ["baixo_consumo"] },
{ campo: "aceleradores_ia", label: "AI 加速器", secao: "processador", topicos: ["ia"] },
{ campo: "wifi", label: "Wi-Fi", secao: "conectividade", topicos: ["wifi"] },
{ campo: "bluetooth", label: "蓝牙", secao: "conectividade", topicos: ["bluetooth"] },
{ campo: "zigbee_thread", label: "Zigbee/Thread", secao: "conectividade", topicos: ["zigbee"] },
{ campo: "matter", label: "Matter", secao: "conectividade", topicos: ["matter"] },
{ campo: "ethernet", label: "以太网", secao: "conectividade", topicos: ["ethernet"] },
{ campo: "ethernet_mac", label: "以太网 MAC", secao: "conectividade", topicos: ["ethernet"] },
{ campo: "can", label: "CAN", secao: "conectividade", topicos: ["can"] },
{ campo: "memoria_sram", label: "SRAM", secao: "memoria", topicos: ["memoria"] },
{ campo: "memoria_sram_rtc", label: "RTC SRAM", secao: "memoria", topicos: ["memoria", "baixo_consumo"] },
{ campo: "memoria_rom", label: "ROM", secao: "memoria", topicos: ["memoria"] },
{ campo: "flash_externa", label: "外部闪存", secao: "memoria", topicos: ["flash", "memoria"] },
{ campo: "psram_externa", label: "外部 PSRAM", secao: "memoria", topicos: ["psram", "memoria"] },
{ campo: "gpio", label: "GPIO", secao: "perifericos", topicos: ["gpio"] },
{ campo: "adc", label: "ADC", secao: "perifericos", topicos: ["adc"] },
{ campo: "dac", label: "DAC", secao: "perifericos", topicos: ["dac"] },
{ campo: "touch", label: "触摸", secao: "perifericos", topicos: ["touch"] },
{ campo: "uart", label: "UART", secao: "perifericos", topicos: ["uart"] },
{ campo: "spi", label: "SPI", secao: "perifericos", topicos: ["spi", "flash"] },
{ campo: "i2c", label: "I2C", secao: "perifericos", topicos: ["i2c"] },
{ campo: "i2s", label: "I2S", secao: "perifericos", topicos: ["i2s"] },
{ campo: "pwm", label: "PWM", secao: "perifericos", topicos: ["pwm"] },
{ campo: "usb", label: "USB", secao: "perifericos", topicos: ["usb"] },
{ campo: "lcd", label: "LCD", secao: "interfaces", topicos: ["lcd"] },
{ campo: "camera", label: "摄像头", secao: "interfaces", topicos: ["camera"] },
{ campo: "mipi_csi", label: "MIPI CSI", secao: "interfaces", topicos: ["camera"] },
{ campo: "mipi_dsi", label: "MIPI DSI", secao: "interfaces", topicos: ["lcd"] },
{ campo: "sdio", label: "SDIO", secao: "interfaces", topicos: ["sd"] },
{ campo: "sensor_temperatura", label: "温度传感器", secao: "interfaces", topicos: ["temperatura"] },
{ campo: "seguranca.aes", label: "AES", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.sha", label: "SHA", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.rsa", label: "RSA", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.ecc", label: "ECC", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.ecdsa", label: "ECDSA", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.hmac", label: "HMAC", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.assinatura_digital", label: "数字签名", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.rng", label: "随机数发生器", secao: "seguranca", topicos: ["seguranca"] },
{ campo: "seguranca.criptografia_flash", label: "闪存加密", secao: "seguranca", topicos: ["seguranca", "flash"] },
{ campo: "seguranca.secure_boot", label: "安全启动", secao: "seguranca", topicos: ["seguranca", "strapping"] },
{ campo: "temperatura_operacao", label: "工作温度", secao: "temperatura", topicos: ["temperatura"] },
{ campo: "temperatura_armazenamento", label: "存储温度", secao: "temperatura", topicos: ["temperatura"] },
{ campo: "esp_idf_minimo", label: "最低 ESP-IDF 版本", secao: "ferramentas", topicos: ["ferramentas"] },
{ campo: "arduino_core", label: "Arduino 核心", secao: "ferramentas", topicos: ["ferramentas"] },
{ campo: "depuracao", label: "调试", secao: "ferramentas", topicos: ["jtag", "ferramentas"] },
{ campo: "encapsulamento", label: "封装", secao: "ferramentas", topicos: ["encapsulamento"] },
{ campo: "modulos", label: "模组", secao: "ferramentas", topicos: ["encapsulamento"] },
];
const ROTULOS_ENERGIA = {
active: "工作",
modem_sleep: "Modem Sleep",
light_sleep: "Light Sleep",
deep_sleep: "Deep Sleep",
hibernation: "Hibernation",
};
const pegar = (obj, caminho) => caminho.split(".").reduce((o, k) => (o == null ? o : o[k]), obj);
/**
* Monta, para cada série, a lista plana de especificações (com os assuntos a
* que respondem) e os pinos do diagrama. É o que o navegador baixa.
*
* @param {Record<string, object>} series conteúdo de public/series.json
* @param {Record<string, object[]>} conexoes pinos por série (public/conexoes)
*/
export function montarConhecimento(series, conexoes) {
const saida = {};
for (const [chave, s] of Object.entries(series)) {
const specs = [];
for (const c of SPEC_CAMPOS) {
const valor = pegar(s, c.campo);
if (valor == null || valor === "") continue;
specs.push({ label: c.label, valor: String(valor), secao: c.secao, topicos: c.topicos });
}
for (const [modo, valor] of Object.entries(s.consumo_energia ?? {})) {
const base = modo.replace(/_cpu.*|_wifi.*/, "");
const label = ROTULOS_ENERGIA[modo] ?? ROTULOS_ENERGIA[base] ?? modo.replace(/_/g, " ");
specs.push({ label: `功耗:${label}`, valor: String(valor), secao: "energia", topicos: ["consumo"] });
}
saida[chave] = {
nome: s.nome_completo ?? chave,
specs,
pinos: (conexoes[chave] ?? []).map((p) => ({
n: String(p.numero),
nome: p.nome,
f: p.funcoes ?? [],
c: p.categorias ?? [],
a: p.avisos ?? [],
})),
};
}
return saida;
}
+79 -24
View File
@@ -1,4 +1,5 @@
import { TOPICOS, normalizar } from "./searchTopics.js"; import { TOPICOS, normalizar } from "./searchTopics.js";
import { BUSCA_I18N } from "./searchI18n.js";
/** /**
* Motor da busca inteligente (puro: sem React nem DOM). * Motor da busca inteligente (puro: sem React nem DOM).
@@ -21,6 +22,26 @@ const INTENCAO_DADOS = new Set([
"quanto", "quantos", "quantas", "capacidade", "limite", "limites", "tem", "possui", "suporta", "quanto", "quantos", "quantas", "capacidade", "limite", "limites", "tem", "possui", "suporta",
]); ]);
const CJK = /[\u3400-\u9fff]/;
const INTENCAO_PINOS_ZH = ["引脚", "管脚", "针脚", "脚位"];
const INTENCAO_DADOS_ZH = ["规格", "参数", "容量", "多少", "支持吗", "是否支持", "有吗"];
const STOP_ZH = new Set(["的", "吗", "呢", "啊", "吧", "了", "是", "什么", "哪个", "哪些", "怎么", "如何", "有", "和", "与", "在", "要", "想", "我", "请", "能", "可以"]);
/** Palavras de um trecho em chinês (Intl.Segmenter; sem ele, bigramas de caracteres). */
function segmentar(texto) {
let palavras = [];
try {
const seg = new Intl.Segmenter("zh", { granularity: "word" });
palavras = [...seg.segment(texto)].filter((s) => s.isWordLike).map((s) => s.segment);
} catch {
const cs = [...texto];
palavras = cs.length <= 2 ? [texto] : cs.slice(0, -1).map((c, i) => c + cs[i + 1]);
}
return palavras.filter((w) => !STOP_ZH.has(w));
}
const fmt = (modelo, v) => modelo.replace(/\{(\w+)\}/g, (_, k) => String(v[k] ?? ""));
const EXEMPLOS_TOPICOS = ["flash", "psram", "usb", "adc", "i2c", "deep sleep", "wifi", "strapping"]; const EXEMPLOS_TOPICOS = ["flash", "psram", "usb", "adc", "i2c", "deep sleep", "wifi", "strapping"];
/** Distância de edição com corte: devolve > max assim que passa do limite. */ /** Distância de edição com corte: devolve > max assim que passa do limite. */
@@ -45,7 +66,7 @@ const palavrasDe = (texto) => texto.split(/[^a-z0-9]+/).filter((w) => w.length >
/** Normaliza o payload do servidor uma única vez, na abertura da busca. */ /** Normaliza o payload do servidor uma única vez, na abertura da busca. */
export function preparar(dados) { export function preparar(dados) {
if (!dados) return { itens: [], series: {}, porFuncao: new Map(), aliases: new Map(), chaves: [] }; if (!dados) return { itens: [], series: {}, porFuncao: new Map(), aliases: new Map(), chaves: [], locale: "pt", topicosLoc: null };
const itensRaw = Array.isArray(dados) ? dados : (dados.itens ?? []); const itensRaw = Array.isArray(dados) ? dados : (dados.itens ?? []);
const seriesRaw = Array.isArray(dados) ? {} : (dados.series ?? {}); const seriesRaw = Array.isArray(dados) ? {} : (dados.series ?? {});
@@ -90,11 +111,15 @@ export function preparar(dados) {
if (k.startsWith("esp32-")) aliases.set(k.slice(6), chave); if (k.startsWith("esp32-")) aliases.set(k.slice(6), chave);
} }
return { itens, series, porFuncao, aliases, chaves: Object.keys(series) }; return { itens, series, porFuncao, aliases, chaves: Object.keys(series), locale: Array.isArray(dados) ? "pt" : (dados.locale ?? "pt"), topicosLoc: Array.isArray(dados) ? null : (dados.topicos ?? null) };
} }
/** Tenta casar um termo do dicionário com os tokens livres. Devolve { idx[], qualidade } ou null. */ /** Tenta casar um termo do dicionário com os tokens livres. Devolve { idx[], qualidade } ou null. */
function casarTermo(termo, livres) { function casarTermo(termo, livres) {
if (CJK.test(termo)) {
for (const { t, i } of livres) if (CJK.test(t) && t.includes(termo)) return { idx: [i], qualidade: 3 + termo.length / 10, cjk: true };
return null;
}
const partes = termo.split(" "); const partes = termo.split(" ");
if (partes.length > 1) { if (partes.length > 1) {
for (let i = 0; i + partes.length <= livres.length; i++) { for (let i = 0; i + partes.length <= livres.length; i++) {
@@ -115,7 +140,9 @@ function casarTermo(termo, livres) {
export function interpretar(indice, consulta) { export function interpretar(indice, consulta) {
const bruto = normalizar(consulta) const bruto = normalizar(consulta)
.replace(/gpio\s*-?\s*(\d+)/g, "gpio$1") .replace(/gpio\s*-?\s*(\d+)/g, "gpio$1")
.replace(/[^a-z0-9.+\-_ ]/g, " "); .replace(/([\u3400-\u9fff])([a-z0-9])/g, "$1 $2")
.replace(/([a-z0-9])([\u3400-\u9fff])/g, "$1 $2")
.replace(/[^a-z0-9.+\-_ \u3400-\u9fff]/g, " ");
const tokens = bruto.split(/\s+/).filter(Boolean); const tokens = bruto.split(/\s+/).filter(Boolean);
const usados = new Set(); const usados = new Set();
@@ -138,6 +165,17 @@ export function interpretar(indice, consulta) {
const gpios = []; const gpios = [];
let querPinos = false; let querPinos = false;
let querDados = false; let querDados = false;
// Chinês não separa palavras por espaço: tira do trecho as palavras de intenção.
tokens.forEach((t, i) => {
if (!CJK.test(t)) return;
let resto = t;
for (const w of INTENCAO_PINOS_ZH) if (resto.includes(w)) { querPinos = true; resto = resto.split(w).join(""); }
for (const w of INTENCAO_DADOS_ZH) if (resto.includes(w)) { querDados = true; resto = resto.split(w).join(""); }
if (resto !== t) {
tokens[i] = resto;
if (!resto) usados.add(i);
}
});
tokens.forEach((t, i) => { tokens.forEach((t, i) => {
if (/^gpio\d+$/.test(t)) { if (/^gpio\d+$/.test(t)) {
gpios.push(t); gpios.push(t);
@@ -158,7 +196,8 @@ export function interpretar(indice, consulta) {
const candidatos = []; const candidatos = [];
for (const topico of TOPICOS) { for (const topico of TOPICOS) {
let melhor = null; let melhor = null;
for (const termo of topico.termos) { const extras = indice.topicosLoc?.[topico.id]?.termos;
for (const termo of extras ? [...topico.termos, ...extras] : topico.termos) {
const m = casarTermo(normalizar(termo), livres); const m = casarTermo(normalizar(termo), livres);
if (m && (!melhor || m.qualidade > melhor.qualidade)) melhor = m; if (m && (!melhor || m.qualidade > melhor.qualidade)) melhor = m;
} }
@@ -166,8 +205,15 @@ export function interpretar(indice, consulta) {
} }
candidatos.sort((a, b) => b.qualidade - a.qualidade); candidatos.sort((a, b) => b.qualidade - a.qualidade);
const topicos = []; const topicos = [];
const cjkUsados = [];
for (const c of candidatos) { for (const c of candidatos) {
if (topicos.length >= 3) break; if (topicos.length >= 3) break;
if (c.cjk) {
// Um mesmo trecho em chinês pode citar vários assuntos.
topicos.push(c.topico);
cjkUsados.push(...c.idx);
continue;
}
if (c.idx.some((i) => usados.has(i))) continue; if (c.idx.some((i) => usados.has(i))) continue;
c.idx.forEach((i) => usados.add(i)); c.idx.forEach((i) => usados.add(i));
topicos.push(c.topico); topicos.push(c.topico);
@@ -183,7 +229,8 @@ export function interpretar(indice, consulta) {
} }
}); });
const restantes = tokens.filter((_, i) => !usados.has(i)); cjkUsados.forEach((i) => usados.add(i));
const restantes = tokens.filter((_, i) => !usados.has(i)).flatMap((t) => (CJK.test(t) ? segmentar(t) : [t]));
return { tokens, chips, topicos, gpios, funcoes, restantes, querPinos, querDados }; return { tokens, chips, topicos, gpios, funcoes, restantes, querPinos, querDados };
} }
@@ -206,11 +253,17 @@ function pinosDoTopico(serie, topico) {
return { forte, fraco }; return { forte, fraco };
} }
const hrefPinos = (chave, pinos, motivo) => const hrefPinos = (I, chave, pinos, motivo) =>
`/series/${chave}?pinos=${pinos.map((p) => p.n).join(",")}&motivo=${encodeURIComponent(motivo)}#connections`; `${I.rotas.series}/${chave}?pinos=${pinos.map((p) => p.n).join(",")}&motivo=${encodeURIComponent(motivo)}#connections`;
function respostas(indice, it) { function respostas(indice, it) {
const saida = []; const saida = [];
const I = BUSCA_I18N[indice.locale] ?? BUSCA_I18N.pt;
const T = I.t;
const loc = (topico) => ({
nome: indice.topicosLoc?.[topico.id]?.nome ?? topico.nome,
rotulo: indice.topicosLoc?.[topico.id]?.pinoRotulo ?? topico.pinoRotulo,
});
const alvos = it.chips.slice(0, 2); const alvos = it.chips.slice(0, 2);
for (const chave of alvos) { for (const chave of alvos) {
@@ -218,14 +271,15 @@ function respostas(indice, it) {
if (!serie) continue; if (!serie) continue;
for (const topico of it.topicos.slice(0, 2)) { for (const topico of it.topicos.slice(0, 2)) {
const tn = loc(topico);
const specs = serie.specs.filter((s) => s.topicos.includes(topico.id)); const specs = serie.specs.filter((s) => s.topicos.includes(topico.id));
const cartaoSpec = specs.length const cartaoSpec = specs.length
? { ? {
tipo: "resposta", tipo: "resposta",
titulo: `${topico.nome} no ${serie.nome}`, titulo: fmt(T.specTitulo, { topico: tn.nome, serie: serie.nome }),
descricao: "Dados da série", descricao: T.specDesc,
linhas: specs.slice(0, 5).map((s) => ({ k: s.label, v: s.valor })), linhas: specs.slice(0, 5).map((s) => ({ k: s.label, v: s.valor })),
href: `/series/${chave}#${specs[0].secao}`, href: `${I.rotas.series}/${chave}#${specs[0].secao}`,
} }
: null; : null;
@@ -234,19 +288,19 @@ function respostas(indice, it) {
const cartaoPinos = principais.length const cartaoPinos = principais.length
? { ? {
tipo: "pino", tipo: "pino",
titulo: `Pinos do ${serie.nome} ${topico.pinoRotulo}`, titulo: fmt(T.pinosTitulo, { serie: serie.nome, rotulo: tn.rotulo }),
descricao: descricao:
forte.length && fraco.length forte.length && fraco.length
? `${forte.length} ocupados + ${fraco.length} que dependem do modo de uso (ex.: SPI de 8 linhas) · abre o diagrama filtrado` ? fmt(T.pinosMistos, { forte: forte.length, fraco: fraco.length })
: forte.length : forte.length
? `${forte.length} ${forte.length === 1 ? "pino" : "pinos"} · abre o diagrama já filtrado` ? fmt(forte.length === 1 ? T.pinoUm : T.pinosVarios, { n: forte.length })
: `${fraco.length} pinos que só são afetados em certos modos de uso · abre o diagrama filtrado`, : fmt(T.pinosFraco, { n: fraco.length }),
chips: forte chips: forte
.slice(0, 12) .slice(0, 12)
.map(rotuloPino) .map(rotuloPino)
.concat(forte.length > 12 ? [`+${forte.length - 12}`] : []) .concat(forte.length > 12 ? [`+${forte.length - 12}`] : [])
.concat(fraco.length && forte.length ? [`+${fraco.length} condicionais`] : forte.length ? [] : fraco.slice(0, 10).map(rotuloPino)), .concat(fraco.length && forte.length ? [fmt(T.condicionais, { n: fraco.length })] : forte.length ? [] : fraco.slice(0, 10).map(rotuloPino)),
href: hrefPinos(chave, [...forte, ...fraco], `${topico.nome}: pinos ${topico.pinoRotulo}`), href: hrefPinos(I, chave, [...forte, ...fraco], fmt(T.motivoTopico, { topico: tn.nome, rotulo: tn.rotulo })),
} }
: null; : null;
@@ -257,13 +311,13 @@ function respostas(indice, it) {
if (it.querPinos && serie.pinos.length) { if (it.querPinos && serie.pinos.length) {
saida.push({ saida.push({
tipo: "pino", tipo: "pino",
titulo: `Diagrama de pinos do ${serie.nome}`, titulo: fmt(T.diagTitulo, { serie: serie.nome }),
descricao: `${serie.pinos.length} pinos com funções, restrições e avisos`, descricao: fmt(T.diagDesc, { n: serie.pinos.length }),
href: `/series/${chave}#connections`, href: `${I.rotas.series}/${chave}#connections`,
}); });
} }
if (it.querDados) { if (it.querDados) {
saida.push({ tipo: "resposta", titulo: `Especificações do ${serie.nome}`, descricao: "Processador, memória, conectividade, periféricos e mais", href: `/series/${chave}` }); saida.push({ tipo: "resposta", titulo: fmt(T.especTitulo, { serie: serie.nome }), descricao: T.especDesc, href: `${I.rotas.series}/${chave}` });
} }
} }
} }
@@ -271,6 +325,7 @@ function respostas(indice, it) {
// Assunto sem chip: panorama por série ("qual série tem PSRAM?") // Assunto sem chip: panorama por série ("qual série tem PSRAM?")
if (!alvos.length && it.topicos.length) { if (!alvos.length && it.topicos.length) {
const topico = it.topicos[0]; const topico = it.topicos[0];
const tn = loc(topico);
const linhas = []; const linhas = [];
for (const chave of indice.chaves) { for (const chave of indice.chaves) {
const serie = indice.series[chave]; const serie = indice.series[chave];
@@ -278,7 +333,7 @@ function respostas(indice, it) {
if (spec) linhas.push({ k: serie.nome, v: spec.valor.length > 46 ? `${spec.valor.slice(0, 45)}…` : spec.valor }); if (spec) linhas.push({ k: serie.nome, v: spec.valor.length > 46 ? `${spec.valor.slice(0, 45)}…` : spec.valor });
} }
if (linhas.length > 1) { if (linhas.length > 1) {
saida.push({ tipo: "resposta", titulo: `${topico.nome} em cada série`, descricao: "Comparativo rápido · adicione o chip na busca para detalhar", linhas, href: "/comparacao" }); saida.push({ tipo: "resposta", titulo: fmt(T.emCada, { topico: tn.nome }), descricao: T.compDesc, linhas, href: I.rotas.comparacao });
} }
} }
@@ -291,9 +346,9 @@ function respostas(indice, it) {
const serie = indice.series[chave]; const serie = indice.series[chave];
saida.push({ saida.push({
tipo: "pino", tipo: "pino",
titulo: `${rotuloPino(pino)} no ${serie.nome}`, titulo: fmt(T.pinoNo, { pino: rotuloPino(pino), serie: serie.nome }),
descricao: [pino.f.join(" · "), pino.a[0]].filter(Boolean).join(" — "), descricao: [pino.f.join(" · "), pino.a[0]].filter(Boolean).join(" — "),
href: hrefPinos(chave, [pino], `Pino ${rotuloPino(pino)}`), href: hrefPinos(I, chave, [pino], fmt(T.motivoPino, { pino: rotuloPino(pino) })),
}); });
} }
} }
@@ -331,7 +386,7 @@ export function buscar(indice, consulta) {
const it = interpretar(indice, consulta); const it = interpretar(indice, consulta);
const interpretacao = { const interpretacao = {
chips: it.chips.map((c) => ({ chave: c, nome: indice.series[c]?.nome ?? c })), chips: it.chips.map((c) => ({ chave: c, nome: indice.series[c]?.nome ?? c })),
topicos: it.topicos.map((t) => ({ id: t.id, nome: t.nome })), topicos: it.topicos.map((t) => ({ id: t.id, nome: indice.topicosLoc?.[t.id]?.nome ?? t.nome })),
}; };
const smart = respostas(indice, it).map((r, i) => ({ id: `r${i}`, keywords: "", ...r })); const smart = respostas(indice, it).map((r, i) => ({ id: `r${i}`, keywords: "", ...r }));
+146
View File
@@ -0,0 +1,146 @@
/**
* Textos e rotas das respostas diretas da busca, por idioma.
*
* Módulo puro (sem alias de import): o servidor monta o índice com ele e o
* motor (`searchEngine.js`) só preenche os {placeholders}. Idioma sem entrada
* cai no português.
*/
const base = (loc, series, comparacao) => ({ series: `/${loc}/${series}`, comparacao: `/${loc}/${comparacao}` });
export const BUSCA_I18N = {
pt: {
rotas: base("pt", "series", "comparacao"),
t: {
specTitulo: "{topico} no {serie}",
specDesc: "Dados da série",
pinosTitulo: "Pinos do {serie} {rotulo}",
pinosMistos: "{forte} ocupados + {fraco} que dependem do modo de uso (ex.: SPI de 8 linhas) · abre o diagrama filtrado",
pinoUm: "{n} pino · abre o diagrama já filtrado",
pinosVarios: "{n} pinos · abre o diagrama já filtrado",
pinosFraco: "{n} pinos que só são afetados em certos modos de uso · abre o diagrama filtrado",
condicionais: "+{n} condicionais",
motivoTopico: "{topico}: pinos {rotulo}",
diagTitulo: "Diagrama de pinos do {serie}",
diagDesc: "{n} pinos com funções, restrições e avisos",
especTitulo: "Especificações do {serie}",
especDesc: "Processador, memória, conectividade, periféricos e mais",
emCada: "{topico} em cada série",
compDesc: "Comparativo rápido · adicione o chip na busca para detalhar",
pinoNo: "{pino} no {serie}",
motivoPino: "Pino {pino}",
},
},
en: {
rotas: base("en", "series", "compare"),
t: {
specTitulo: "{topico} on the {serie}",
specDesc: "Series data",
pinosTitulo: "{serie}: {rotulo}",
pinosMistos: "{forte} occupied + {fraco} that depend on the usage mode (e.g. 8-line SPI) · opens the filtered diagram",
pinoUm: "{n} pin · opens the already filtered diagram",
pinosVarios: "{n} pins · opens the already filtered diagram",
pinosFraco: "{n} pins only affected in certain usage modes · opens the filtered diagram",
condicionais: "+{n} conditional",
motivoTopico: "{topico}: {rotulo}",
diagTitulo: "{serie} pinout diagram",
diagDesc: "{n} pins with functions, restrictions and warnings",
especTitulo: "{serie} specifications",
especDesc: "Processor, memory, connectivity, peripherals and more",
emCada: "{topico} across all series",
compDesc: "Quick comparison · add the chip to your search for details",
pinoNo: "{pino} on the {serie}",
motivoPino: "Pin {pino}",
},
},
es: {
rotas: base("es", "series", "comparacion"),
t: {
specTitulo: "{topico} en el {serie}",
specDesc: "Datos de la serie",
pinosTitulo: "Pines del {serie} {rotulo}",
pinosMistos: "{forte} ocupados + {fraco} que dependen del modo de uso (p. ej., SPI de 8 líneas) · abre el diagrama filtrado",
pinoUm: "{n} pin · abre el diagrama ya filtrado",
pinosVarios: "{n} pines · abre el diagrama ya filtrado",
pinosFraco: "{n} pines que solo se ven afectados en ciertos modos de uso · abre el diagrama filtrado",
condicionais: "+{n} condicionales",
motivoTopico: "{topico}: pines {rotulo}",
diagTitulo: "Diagrama de pines del {serie}",
diagDesc: "{n} pines con funciones, restricciones y avisos",
especTitulo: "Especificaciones del {serie}",
especDesc: "Procesador, memoria, conectividad, periféricos y más",
emCada: "{topico} en cada serie",
compDesc: "Comparativa rápida · añade el chip a la búsqueda para ver el detalle",
pinoNo: "{pino} en el {serie}",
motivoPino: "Pin {pino}",
},
},
fr: {
rotas: base("fr", "series", "comparaison"),
t: {
specTitulo: "{topico} sur le {serie}",
specDesc: "Données de la série",
pinosTitulo: "Broches du {serie} {rotulo}",
pinosMistos: "{forte} occupées + {fraco} qui dépendent du mode d'utilisation (ex. : SPI 8 lignes) · ouvre le schéma filtré",
pinoUm: "{n} broche · ouvre le schéma déjà filtré",
pinosVarios: "{n} broches · ouvre le schéma déjà filtré",
pinosFraco: "{n} broches concernées seulement dans certains modes d'utilisation · ouvre le schéma filtré",
condicionais: "+{n} conditionnelles",
motivoTopico: "{topico} : broches {rotulo}",
diagTitulo: "Schéma des broches du {serie}",
diagDesc: "{n} broches avec fonctions, restrictions et avertissements",
especTitulo: "Caractéristiques du {serie}",
especDesc: "Processeur, mémoire, connectivité, périphériques et plus",
emCada: "{topico} dans chaque série",
compDesc: "Comparatif rapide · ajoutez la puce à la recherche pour détailler",
pinoNo: "{pino} sur le {serie}",
motivoPino: "Broche {pino}",
},
},
de: {
rotas: base("de", "serien", "vergleich"),
t: {
specTitulo: "{topico} beim {serie}",
specDesc: "Seriendaten",
pinosTitulo: "{serie}: {rotulo}",
pinosMistos: "{forte} belegt + {fraco} abhängig vom Nutzungsmodus (z. B. 8-Bit-SPI) · öffnet das gefilterte Diagramm",
pinoUm: "{n} Pin · öffnet das bereits gefilterte Diagramm",
pinosVarios: "{n} Pins · öffnet das bereits gefilterte Diagramm",
pinosFraco: "{n} Pins, die nur in bestimmten Nutzungsmodi betroffen sind · öffnet das gefilterte Diagramm",
condicionais: "+{n} bedingte",
motivoTopico: "{topico}: {rotulo}",
diagTitulo: "Pin-Diagramm des {serie}",
diagDesc: "{n} Pins mit Funktionen, Einschränkungen und Hinweisen",
especTitulo: "Spezifikationen des {serie}",
especDesc: "Prozessor, Speicher, Konnektivität, Peripherie und mehr",
emCada: "{topico} in jeder Serie",
compDesc: "Schnellvergleich · fügen Sie den Chip zur Suche hinzu, um Details zu sehen",
pinoNo: "{pino} beim {serie}",
motivoPino: "Pin {pino}",
},
},
"zh-CN": {
rotas: base("zh-CN", "series", "compare"),
t: {
specTitulo: "{serie}:{topico}",
specDesc: "系列数据",
pinosTitulo: "{serie} 的引脚:{rotulo}",
pinosMistos: "{forte} 个被占用 + {fraco} 个取决于使用模式(例如八线 SPI)· 打开已筛选的引脚图",
pinoUm: "{n} 个引脚 · 打开已筛选的引脚图",
pinosVarios: "{n} 个引脚 · 打开已筛选的引脚图",
pinosFraco: "{n} 个引脚仅在某些使用模式下受影响 · 打开已筛选的引脚图",
condicionais: "另有 {n} 个有条件",
motivoTopico: "{topico}:{rotulo}",
diagTitulo: "{serie} 引脚图",
diagDesc: "{n} 个引脚,含功能、限制与警告",
especTitulo: "{serie} 规格",
especDesc: "处理器、内存、无线连接、外设等",
emCada: "{topico}:各系列对比",
compDesc: "快速对比 · 在搜索中加上芯片可查看详情",
pinoNo: "{serie} 的 {pino}",
motivoPino: "引脚 {pino}",
},
},
};
export const buscaI18n = (locale) => BUSCA_I18N[locale] ?? BUSCA_I18N.pt;
+47 -6
View File
@@ -33,6 +33,20 @@ import { errosCatalogo as errosDe } from "@/lib/data/de/diagnostico";
import { COMPONENTES_REGISTRY as componentesDe } from "@/lib/data/de/componentes"; import { COMPONENTES_REGISTRY as componentesDe } from "@/lib/data/de/componentes";
import { montarConhecimento as montarConhecimentoDe } from "@/lib/data/de/topicos"; import { montarConhecimento as montarConhecimentoDe } from "@/lib/data/de/topicos";
import seriesDataZh from "@/data/zh-CN/series.json";
import frameworksDataZh from "@/data/zh-CN/frameworks.json";
import { categoriasGlossario as glossarioZh } from "@/lib/data/zh-CN/glossario";
import { errosCatalogo as errosZh } from "@/lib/data/zh-CN/diagnostico";
import { COMPONENTES_REGISTRY as componentesZh } from "@/lib/data/zh-CN/componentes";
import { montarConhecimento as montarConhecimentoZh } from "@/lib/data/zh-CN/topicos";
// Nomes, rótulos e sinônimos dos assuntos no idioma da página (o motor só traz os em português).
import { TOPICOS as TOPICOS_EN } from "@/lib/data/en/topicos";
import { TOPICOS as TOPICOS_ES } from "@/lib/data/es/topicos";
import { TOPICOS as TOPICOS_FR } from "@/lib/data/fr/topicos";
import { TOPICOS as TOPICOS_DE } from "@/lib/data/de/topicos";
import { TOPICOS as TOPICOS_ZH } from "@/lib/data/zh-CN/topicos";
import { readFileSync, existsSync } from "node:fs"; import { readFileSync, existsSync } from "node:fs";
import path from "node:path"; import path from "node:path";
@@ -97,6 +111,19 @@ const PAGINAS_FR = [
{ titulo: "À Propos du Projet", descricao: "Histoire, auteur et open source", href: "/fr/a-propos", keywords: "auteur contact open source" }, { titulo: "À Propos du Projet", descricao: "Histoire, auteur et open source", href: "/fr/a-propos", keywords: "auteur contact open source" },
]; ];
const PAGINAS_ZH = [
{ titulo: "首页", descricao: "ESPDocs 首页", href: "/zh-CN", keywords: "首页 主页 home" },
{ titulo: "全部系列", descricao: "每个 ESP32 系列的引脚、规格与限制", href: "/zh-CN/series", keywords: "芯片 引脚 规格 pinout" },
{ titulo: "系列对比", descricao: "最多并排对比 4 个型号", href: "/zh-CN/compare", keywords: "对比 比较 差异" },
{ titulo: "开发板目录", descricao: "开发板、引脚与购买渠道", href: "/zh-CN/catalog", keywords: "开发板 devkit 购买" },
{ titulo: "智能选型", descricao: "为你的项目找到合适的芯片", href: "/zh-CN/selector", keywords: "选型 推荐 测验" },
{ titulo: "故障诊断", descricao: "排查 Brownout、启动循环、看门狗和崩溃", href: "/zh-CN/diagnostics", keywords: "故障 错误 崩溃 问题 bug" },
{ titulo: "框架与 SDK", descricao: "ESP-IDF、Arduino、Rust、RainMaker 等", href: "/zh-CN/frameworks", keywords: "sdk 开发框架" },
{ titulo: "Component Registry", descricao: "驱动目录与 idf_component.yml 生成器", href: "/zh-CN/components", keywords: "库 驱动 组件 yml" },
{ titulo: "技术术语表", descricao: "关键的硬件与软件概念", href: "/zh-CN/glossary", keywords: "术语 词典 定义 含义" },
{ titulo: "关于本项目", descricao: "历史、作者与开源", href: "/zh-CN/about", keywords: "作者 联系 开源" },
];
const PAGINAS_DE = [ const PAGINAS_DE = [
{ titulo: "Startseite", descricao: "ESPDocs Startseite", href: "/de", keywords: "startseite home" }, { titulo: "Startseite", descricao: "ESPDocs Startseite", href: "/de", keywords: "startseite home" },
{ titulo: "Alle Serien", descricao: "Pinbelegungen, Spezifikationen und Einschränkungen für jede ESP32-Serie", href: "/de/serien", keywords: "chips pinbelegung spezifikationen pinout" }, { titulo: "Alle Serien", descricao: "Pinbelegungen, Spezifikationen und Einschränkungen für jede ESP32-Serie", href: "/de/serien", keywords: "chips pinbelegung spezifikationen pinout" },
@@ -117,6 +144,7 @@ export function construirIndiceBusca(locale = "pt") {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
let paginas = PAGINAS_PT; let paginas = PAGINAS_PT;
let seriesData = seriesDataPt; let seriesData = seriesDataPt;
@@ -158,13 +186,21 @@ export function construirIndiceBusca(locale = "pt") {
errosCatalogo = errosDe; errosCatalogo = errosDe;
componentes = componentesDe; componentes = componentesDe;
montarConhecimento = montarConhecimentoDe; montarConhecimento = montarConhecimentoDe;
} else if (isZh) {
paginas = PAGINAS_ZH;
seriesData = seriesDataZh;
frameworksData = frameworksDataZh;
categoriasGlossario = glossarioZh;
errosCatalogo = errosZh;
componentes = componentesZh;
montarConhecimento = montarConhecimentoZh;
} }
const slugSeries = isDe ? "/de/serien" : isFr ? "/fr/series" : isEn ? "/en/series" : isEs ? "/es/series" : "/series"; const slugSeries = isZh ? "/zh-CN/series" : isDe ? "/de/serien" : isFr ? "/fr/series" : isEn ? "/en/series" : isEs ? "/es/series" : "/series";
const slugFrameworks = isDe ? "/de/frameworks" : isFr ? "/fr/frameworks" : isEn ? "/en/frameworks" : isEs ? "/es/frameworks" : "/frameworks"; const slugFrameworks = isZh ? "/zh-CN/frameworks" : isDe ? "/de/frameworks" : isFr ? "/fr/frameworks" : isEn ? "/en/frameworks" : isEs ? "/es/frameworks" : "/frameworks";
const slugGlossario = isDe ? "/de/glossar" : isFr ? "/fr/glossaire" : isEn ? "/en/glossary" : isEs ? "/es/glosario" : "/glossario"; const slugGlossario = isZh ? "/zh-CN/glossary" : isDe ? "/de/glossar" : isFr ? "/fr/glossaire" : isEn ? "/en/glossary" : isEs ? "/es/glosario" : "/glossario";
const slugDiagnostico = isDe ? "/de/diagnose" : isFr ? "/fr/diagnostic" : isEn ? "/en/diagnostics" : isEs ? "/es/diagnostico" : "/diagnostico"; const slugDiagnostico = isZh ? "/zh-CN/diagnostics" : isDe ? "/de/diagnose" : isFr ? "/fr/diagnostic" : isEn ? "/en/diagnostics" : isEs ? "/es/diagnostico" : "/diagnostico";
const slugComponentes = isDe ? "/de/komponenten" : isFr ? "/fr/composants" : isEn ? "/en/components" : isEs ? "/es/componentes" : "/componentes"; const slugComponentes = isZh ? "/zh-CN/components" : isDe ? "/de/komponenten" : isFr ? "/fr/composants" : isEn ? "/en/components" : isEs ? "/es/componentes" : "/componentes";
const itens = []; const itens = [];
@@ -233,7 +269,7 @@ export function construirIndiceBusca(locale = "pt") {
? path.join(process.cwd(), "data", "es", "conexoes") ? path.join(process.cwd(), "data", "es", "conexoes")
: isFr : isFr
? path.join(process.cwd(), "data", "fr", "conexoes") ? path.join(process.cwd(), "data", "fr", "conexoes")
: isDe : isZh ? path.join(process.cwd(), "data", "zh-CN", "conexoes") : isDe
? path.join(process.cwd(), "data", "de", "conexoes") ? path.join(process.cwd(), "data", "de", "conexoes")
: path.join(process.cwd(), "public", "conexoes"); : path.join(process.cwd(), "public", "conexoes");
const arquivo = path.join(pastaConexoes, `${chave}.json`); const arquivo = path.join(pastaConexoes, `${chave}.json`);
@@ -245,7 +281,12 @@ export function construirIndiceBusca(locale = "pt") {
} }
} }
const topicosLoc = { en: TOPICOS_EN, es: TOPICOS_ES, fr: TOPICOS_FR, de: TOPICOS_DE, "zh-CN": TOPICOS_ZH }[locale];
return { return {
locale,
topicos: topicosLoc
? Object.fromEntries(topicosLoc.map((tp) => [tp.id, { nome: tp.nome, pinoRotulo: tp.pinoRotulo, termos: tp.termos }]))
: null,
itens: itens.map((item, i) => ({ id: i, ...item })), itens: itens.map((item, i) => ({ id: i, ...item })),
series: montarConhecimento(seriesData, conexoes), series: montarConhecimento(seriesData, conexoes),
}; };
+37 -31
View File
@@ -97,9 +97,10 @@ export const jsonLd = {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const orgAnchor = isEn ? "organization" : isEs ? "organizacion" : (isFr || isDe) ? "organisation" : "organizacao"; const isZh = locale === "zh-CN";
const orgAnchor = isZh ? "organization" : isEn ? "organization" : isEs ? "organizacion" : (isFr || isDe) ? "organisation" : "organizacao";
const authorAnchor = isEn ? "author" : isFr ? "auteur" : "autor"; const authorAnchor = isEn ? "author" : isFr ? "auteur" : "autor";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt-BR";
return { return {
"@context": "https://schema.org", "@context": "https://schema.org",
"@graph": [ "@graph": [
@@ -115,7 +116,7 @@ export const jsonLd = {
? "Documentación del ecosistema ESP32: especificaciones, patillaje, comparación y selección de series." ? "Documentación del ecosistema ESP32: especificaciones, patillaje, comparación y selección de series."
: isFr : isFr
? "Documentation de l'écosystème ESP32 : spécifications, brochages, comparaison et sélection de séries." ? "Documentation de l'écosystème ESP32 : spécifications, brochages, comparaison et sélection de séries."
: isDe : isZh ? "ESP32 生态文档(简体中文):规格、引脚、对比与系列选型。" : isDe
? "Dokumentation des ESP32-Ökosystems: Spezifikationen, Pinbelegung, Vergleich und Serienauswahl." ? "Dokumentation des ESP32-Ökosystems: Spezifikationen, Pinbelegung, Vergleich und Serienauswahl."
: "Documentação do ecossistema ESP32 em português brasileiro: especificações, pinagem, comparação e seleção de séries.", : "Documentação do ecossistema ESP32 em português brasileiro: especificações, pinagem, comparação e seleção de séries.",
publisher: { "@id": `${SITE.url}/#${orgAnchor}` }, publisher: { "@id": `${SITE.url}/#${orgAnchor}` },
@@ -166,33 +167,34 @@ export const jsonLd = {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const isZh = locale === "zh-CN";
const prop = (nome, valor) => const prop = (nome, valor) =>
valor ? { "@type": "PropertyValue", name: nome, value: String(valor) } : null; valor ? { "@type": "PropertyValue", name: nome, value: String(valor) } : null;
const propriedades = [ const propriedades = [
prop(isEn ? "Manufacturer" : isEs ? "Fabricante" : isFr ? "Fabricant" : isDe ? "Hersteller" : "Fabricante", "Espressif Systems"), prop(isEn ? "Manufacturer" : isEs ? "Fabricante" : isFr ? "Fabricant" : isZh ? "制造商" : isDe ? "Hersteller" : "Fabricante", "Espressif Systems"),
prop(isEn ? "Architecture" : isEs ? "Arquitectura" : isFr ? "Architecture" : isDe ? "Architektur" : "Arquitetura"), prop(isEn ? "Architecture" : isEs ? "Arquitectura" : isFr ? "Architecture" : isZh ? "架构" : isDe ? "Architektur" : "Arquitetura"),
prop(isEn ? "Cores" : isEs ? "Núcleos" : isFr ? "Cœurs" : isDe ? "Kerne" : "Núcleos", serie.nucleos), prop(isEn ? "Cores" : isEs ? "Núcleos" : isFr ? "Cœurs" : isZh ? "内核数" : isDe ? "Kerne" : "Núcleos", serie.nucleos),
prop(isEn ? "Max frequency" : isEs ? "Frecuencia máxima" : isFr ? "Fréquence maximale" : isDe ? "Maximale Frequenz" : "Frequência máxima", serie.frequencia), prop(isEn ? "Max frequency" : isEs ? "Frecuencia máxima" : isFr ? "Fréquence maximale" : isZh ? "最高频率" : isDe ? "Maximale Frequenz" : "Frequência máxima", serie.frequencia),
prop("SRAM", serie.memoria_sram), prop("SRAM", serie.memoria_sram),
prop("RTC SRAM", serie.memoria_sram_rtc), prop("RTC SRAM", serie.memoria_sram_rtc),
prop(isEn ? "Supported Flash" : isEs ? "Flash soportada" : isFr ? "Flash prise en charge" : isDe ? "Unterstützter Flash" : "Flash suportada", serie.flash_suportada), prop(isEn ? "Supported Flash" : isEs ? "Flash soportada" : isFr ? "Flash prise en charge" : isZh ? "支持的闪存" : isDe ? "Unterstützter Flash" : "Flash suportada", serie.flash_suportada),
prop(isEn ? "Supported PSRAM" : isEs ? "PSRAM soportada" : isFr ? "PSRAM prise en charge" : isDe ? "Unterstütztes PSRAM" : "PSRAM suportada", serie.psram_suportada), prop(isEn ? "Supported PSRAM" : isEs ? "PSRAM soportada" : isFr ? "PSRAM prise en charge" : isZh ? "支持的 PSRAM" : isDe ? "Unterstütztes PSRAM" : "PSRAM suportada", serie.psram_suportada),
prop("GPIO", serie.gpio), prop("GPIO", serie.gpio),
prop("Wi-Fi", serie.wifi), prop("Wi-Fi", serie.wifi),
prop("Bluetooth", serie.bluetooth), prop("Bluetooth", serie.bluetooth),
prop("Zigbee / Thread / 802.15.4", serie.zigbee_thread), prop("Zigbee / Thread / 802.15.4", serie.zigbee_thread),
prop(isEn ? "Package" : isEs ? "Encapsulado" : isFr ? "Boîtier" : isDe ? "Gehäuse" : "Encapsulamento", serie.encapsulamento), prop(isEn ? "Package" : isEs ? "Encapsulado" : isFr ? "Boîtier" : isZh ? "封装" : isDe ? "Gehäuse" : "Encapsulamento", serie.encapsulamento),
prop(isEn ? "Minimum ESP-IDF" : isEs ? "ESP-IDF mínimo" : isFr ? "ESP-IDF minimum" : isDe ? "Mindestens ESP-IDF" : "ESP-IDF mínimo", serie.esp_idf_minimo), prop(isEn ? "Minimum ESP-IDF" : isEs ? "ESP-IDF mínimo" : isFr ? "ESP-IDF minimum" : isZh ? "最低 ESP-IDF 版本" : isDe ? "Mindestens ESP-IDF" : "ESP-IDF mínimo", serie.esp_idf_minimo),
].filter(Boolean); ].filter(Boolean);
const imagem = serie.placa const imagem = serie.placa
? `${SITE.url}${serie.placa}` ? `${SITE.url}${serie.placa}`
: `${SITE.url}/opengraph-image.png`; : `${SITE.url}/opengraph-image.png`;
const basePath = isDe ? `/de/serien/${chave}` : isFr ? `/fr/series/${chave}` : isEn ? `/en/series/${chave}` : isEs ? `/es/series/${chave}` : `/series/${chave}`; const basePath = isZh ? `/zh-CN/series/${chave}` : isDe ? `/de/serien/${chave}` : isFr ? `/fr/series/${chave}` : isEn ? `/en/series/${chave}` : isEs ? `/es/series/${chave}` : `/series/${chave}`;
const anchor = isEn ? "article" : isEs ? "articulo" : isFr ? "article" : isDe ? "artikel" : "artigo"; const anchor = isEn ? "article" : isEs ? "articulo" : isFr ? "article" : isZh ? "article" : isDe ? "artikel" : "artigo";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt-BR";
return { return {
"@context": "https://schema.org", "@context": "https://schema.org",
@@ -204,7 +206,7 @@ export const jsonLd = {
? `${serie.nome_completo || chave}: especificaciones y patillaje` ? `${serie.nome_completo || chave}: especificaciones y patillaje`
: isFr : isFr
? `${serie.nome_completo || chave} : spécifications et brochage` ? `${serie.nome_completo || chave} : spécifications et brochage`
: isDe : isZh ? `${serie.nome_completo || chave}:规格与引脚图` : isDe
? `${serie.nome_completo || chave}: Spezifikationen und Pinbelegung` ? `${serie.nome_completo || chave}: Spezifikationen und Pinbelegung`
: `${serie.nome_completo || chave}: especificações e pinagem`, : `${serie.nome_completo || chave}: especificações e pinagem`,
name: isEn name: isEn
@@ -213,7 +215,7 @@ export const jsonLd = {
? `${serie.nome_completo || chave} - Documentación técnica y especificaciones` ? `${serie.nome_completo || chave} - Documentación técnica y especificaciones`
: isFr : isFr
? `${serie.nome_completo || chave} - Documentation technique et spécifications` ? `${serie.nome_completo || chave} - Documentation technique et spécifications`
: isDe : isZh ? `${serie.nome_completo || chave} - 技术文档与规格` : isDe
? `${serie.nome_completo || chave} - Technische Dokumentation und Spezifikationen` ? `${serie.nome_completo || chave} - Technische Dokumentation und Spezifikationen`
: `${serie.nome_completo || chave} - Documentação Técnica e Especificações`, : `${serie.nome_completo || chave} - Documentação Técnica e Especificações`,
description: serie.descricao, description: serie.descricao,
@@ -254,9 +256,10 @@ export const jsonLd = {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const basePath = isDe ? `/de/frameworks/${encodeURIComponent(chave)}` : isFr ? `/fr/frameworks/${encodeURIComponent(chave)}` : isEn ? `/en/frameworks/${encodeURIComponent(chave)}` : isEs ? `/es/frameworks/${encodeURIComponent(chave)}` : `/frameworks/${encodeURIComponent(chave)}`; const isZh = locale === "zh-CN";
const anchor = isEn ? "article" : isEs ? "articulo" : isFr ? "article" : isDe ? "artikel" : "artigo"; const basePath = isZh ? `/zh-CN/frameworks/${encodeURIComponent(chave)}` : isDe ? `/de/frameworks/${encodeURIComponent(chave)}` : isFr ? `/fr/frameworks/${encodeURIComponent(chave)}` : isEn ? `/en/frameworks/${encodeURIComponent(chave)}` : isEs ? `/es/frameworks/${encodeURIComponent(chave)}` : `/frameworks/${encodeURIComponent(chave)}`;
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; const anchor = isEn ? "article" : isEs ? "articulo" : isFr ? "article" : isZh ? "article" : isDe ? "artikel" : "artigo";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt-BR";
return { return {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "TechArticle", "@type": "TechArticle",
@@ -267,14 +270,14 @@ export const jsonLd = {
? `${framework.nome} para ESP32: Guía y documentación` ? `${framework.nome} para ESP32: Guía y documentación`
: isFr : isFr
? `${framework.nome} pour ESP32 : Guide et documentation` ? `${framework.nome} pour ESP32 : Guide et documentation`
: isDe : isZh ? `用于 ESP32 的 ${framework.nome}:指南与文档` : isDe
? `${framework.nome} für ESP32: Leitfaden und Dokumentation` ? `${framework.nome} für ESP32: Leitfaden und Dokumentation`
: `${framework.nome} para ESP32: Guia e Documentação`, : `${framework.nome} para ESP32: Guia e Documentação`,
name: isEn name: isEn
? `${framework.nome_completo || framework.nome} for ESP32` ? `${framework.nome_completo || framework.nome} for ESP32`
: isFr : isFr
? `${framework.nome_completo || framework.nome} pour ESP32` ? `${framework.nome_completo || framework.nome} pour ESP32`
: isDe : isZh ? `用于 ESP32 的 ${framework.nome_completo || framework.nome}` : isDe
? `${framework.nome_completo || framework.nome} für ESP32` ? `${framework.nome_completo || framework.nome} für ESP32`
: `${framework.nome_completo || framework.nome} para ESP32`, : `${framework.nome_completo || framework.nome} para ESP32`,
description: framework.descricao, description: framework.descricao,
@@ -317,14 +320,15 @@ export const jsonLd = {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const basePath = isDe ? "/de/glossar" : isFr ? "/fr/glossaire" : isEn ? "/en/glossary" : isEs ? "/es/glosario" : "/glossario"; const isZh = locale === "zh-CN";
const anchor = isDe ? "glossar" : isFr ? "glossaire" : isEn ? "glossary" : isEs ? "glosario" : "glossario"; const basePath = isZh ? "/zh-CN/glossary" : isDe ? "/de/glossar" : isFr ? "/fr/glossaire" : isEn ? "/en/glossary" : isEs ? "/es/glosario" : "/glossario";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; const anchor = isZh ? "glossary" : isDe ? "glossar" : isFr ? "glossaire" : isEn ? "glossary" : isEs ? "glosario" : "glossario";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt-BR";
return { return {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "DefinedTermSet", "@type": "DefinedTermSet",
"@id": `${SITE.url}${basePath}#${anchor}`, "@id": `${SITE.url}${basePath}#${anchor}`,
name: isEn ? "ESP32 Technical Glossary" : isEs ? "Glosario técnico de ESP32" : isFr ? "Glossaire technique de l'ESP32" : isDe ? "Technisches Glossar für ESP32" : "Glossário Técnico do ESP32", name: isEn ? "ESP32 Technical Glossary" : isEs ? "Glosario técnico de ESP32" : isFr ? "Glossaire technique de l'ESP32" : isZh ? "ESP32 技术术语表" : isDe ? "Technisches Glossar für ESP32" : "Glossário Técnico do ESP32",
inLanguage: inLang, inLanguage: inLang,
hasDefinedTerm: categorias.flatMap((cat) => hasDefinedTerm: categorias.flatMap((cat) =>
cat.termos.map((t) => ({ cat.termos.map((t) => ({
@@ -356,7 +360,8 @@ export const jsonLd = {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; const isZh = locale === "zh-CN";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt-BR";
return { return {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "WebApplication", "@type": "WebApplication",
@@ -452,21 +457,22 @@ export const jsonLd = {
const isEs = locale === "es"; const isEs = locale === "es";
const isFr = locale === "fr"; const isFr = locale === "fr";
const isDe = locale === "de"; const isDe = locale === "de";
const basePath = isDe ? "/de/ueber-uns" : isFr ? "/fr/a-propos" : isEn ? "/en/about" : isEs ? "/es/acerca" : "/sobre"; const isZh = locale === "zh-CN";
const anchor = isDe ? "ueber-uns" : isFr ? "a-propos" : isEn ? "about" : isEs ? "acerca" : "sobre"; const basePath = isZh ? "/zh-CN/about" : isDe ? "/de/ueber-uns" : isFr ? "/fr/a-propos" : isEn ? "/en/about" : isEs ? "/es/acerca" : "/sobre";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; const anchor = isZh ? "about" : isDe ? "ueber-uns" : isFr ? "a-propos" : isEn ? "about" : isEs ? "acerca" : "sobre";
const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt-BR";
return { return {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "AboutPage", "@type": "AboutPage",
"@id": `${SITE.url}${basePath}#${anchor}`, "@id": `${SITE.url}${basePath}#${anchor}`,
name: isEn ? "About ESPDocs" : isEs ? "Acerca de ESPDocs" : isFr ? "À propos d'ESPDocs" : isDe ? "Über ESPDocs" : "Sobre o ESPDocs", name: isEn ? "About ESPDocs" : isEs ? "Acerca de ESPDocs" : isFr ? "À propos d'ESPDocs" : isZh ? "关于 ESPDocs" : isDe ? "Über ESPDocs" : "Sobre o ESPDocs",
description: isEn description: isEn
? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem." ? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem."
: isEs : isEs
? "ESPDocs es una plataforma independiente que reúne la documentación técnica del ecosistema ESP32." ? "ESPDocs es una plataforma independiente que reúne la documentación técnica del ecosistema ESP32."
: isFr : isFr
? "ESPDocs est une plateforme indépendante qui rassemble la documentation technique de l'écosystème ESP32." ? "ESPDocs est une plateforme indépendante qui rassemble la documentation technique de l'écosystème ESP32."
: isDe : isZh ? "ESPDocs 是一个独立平台,汇集 ESP32 生态的文档,使用简体中文。" : isDe
? "ESPDocs ist eine unabhängige Plattform, die die technische Dokumentation des ESP32-Ökosystems zusammenführt." ? "ESPDocs ist eine unabhängige Plattform, die die technische Dokumentation des ESP32-Ökosystems zusammenführt."
: "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro.", : "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro.",
url: `${SITE.url}${basePath}`, url: `${SITE.url}${basePath}`,
+1 -1
View File
@@ -8,7 +8,7 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
delay(1000); delay(1000);
Serial.println("Conectando ao Wi-Fi..."); Serial.println("Connecting to Wi-Fi...");
WiFi.begin(ssid, password); WiFi.begin(ssid, password);
// 等待连接 // 等待连接
+1 -1
View File
@@ -25,7 +25,7 @@ void app_main(void)
// 等待 SD 卡挂载 // 等待 SD 卡挂载
periph_sdcard_wait_for_ready(sdcard_handle, portMAX_DELAY); periph_sdcard_wait_for_ready(sdcard_handle, portMAX_DELAY);
ESP_LOGI(TAG, "SD card montado!"); ESP_LOGI(TAG, "SD card mounted!");
// 创建流水线 // 创建流水线
audio_pipeline_handle_t pipeline; audio_pipeline_handle_t pipeline;
+2 -2
View File
@@ -16,7 +16,7 @@ void app_main(void)
return; return;
} }
printf("Sinal de entrada:\n"); printf("Input signal:\n");
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
printf("%f ", x[i]); printf("%f ", x[i]);
} }
@@ -33,5 +33,5 @@ void app_main(void)
// 计算幅值(平方和的平方根) // 计算幅值(平方和的平方根)
float magnitude = sqrtf(soma); float magnitude = sqrtf(soma);
printf("Magnitude do vetor: %f\n", magnitude); printf("Vector magnitude: %f\n", magnitude);
} }
+2 -2
View File
@@ -31,8 +31,8 @@ static void blink_task(void *pvParameters)
led_state = !led_state; led_state = !led_state;
gpio_set_level(BLINK_GPIO, led_state); gpio_set_level(BLINK_GPIO, led_state);
ESP_LOGI(TAG, "LED %s | Heap livre: %lu bytes", ESP_LOGI(TAG, "LED %s | Free heap: %lu bytes",
led_state ? "LIGADO" : "DESLIGADO", led_state ? "ON" : "OFF",
(unsigned long)esp_get_free_heap_size()); (unsigned long)esp_get_free_heap_size());
vTaskDelay(pdMS_TO_TICKS(1000)); vTaskDelay(pdMS_TO_TICKS(1000));
+1 -1
View File
@@ -24,7 +24,7 @@ static led_indicator_handle_t led_handle = NULL;
/* 按键单击回调 */ /* 按键单击回调 */
static void button_single_click_cb(void *arg, void *data) static void button_single_click_cb(void *arg, void *data)
{ {
ESP_LOGI(TAG, "Evento: Clique Único detectado!"); ESP_LOGI(TAG, "Event: single click detected!");
led_indicator_start(led_handle, BLINK_DOUBLE_FAST); led_indicator_start(led_handle, BLINK_DOUBLE_FAST);
} }
+2 -2
View File
@@ -35,10 +35,10 @@ void app_main(void)
esp_nn_mul_mat_16x8_16(weights, input, bias, output, esp_nn_mul_mat_16x8_16(weights, input, bias, output,
OUTPUT_SIZE, INPUT_SIZE, /*shift=*/0); OUTPUT_SIZE, INPUT_SIZE, /*shift=*/0);
print_array("Antes da ReLU", output, OUTPUT_SIZE); print_array("Before ReLU", output, OUTPUT_SIZE);
// 应用 ReLU 激活函数 // 应用 ReLU 激活函数
esp_nn_relu_16(output, OUTPUT_SIZE); esp_nn_relu_16(output, OUTPUT_SIZE);
print_array("Depois da ReLU", output, OUTPUT_SIZE); print_array("After ReLU", output, OUTPUT_SIZE);
} }
+1 -1
View File
@@ -38,7 +38,7 @@ static void on_data_recv(const esp_now_recv_info_t *recv_info, const uint8_t *da
{ {
if (len == sizeof(dados_pacote_t)) { if (len == sizeof(dados_pacote_t)) {
dados_pacote_t *pct = (dados_pacote_t *)data; dados_pacote_t *pct = (dados_pacote_t *)data;
ESP_LOGI(TAG, "Pacote recebido de [%02X:%02X:%02X:%02X:%02X:%02X]: Sensor #%ld | Temp: %.1f C | Umid: %.1f %%", ESP_LOGI(TAG, "Packet received from [%02X:%02X:%02X:%02X:%02X:%02X]: Sensor #%ld | Temp: %.1f C | Humidity: %.1f %%",
recv_info->src_addr[0], recv_info->src_addr[1], recv_info->src_addr[2], recv_info->src_addr[0], recv_info->src_addr[1], recv_info->src_addr[2],
recv_info->src_addr[3], recv_info->src_addr[4], recv_info->src_addr[5], recv_info->src_addr[3], recv_info->src_addr[4], recv_info->src_addr[5],
pct->sensor_id, pct->temperatura, pct->umidade); pct->sensor_id, pct->temperatura, pct->umidade);
+2 -2
View File
@@ -25,11 +25,11 @@ static esp_rmaker_device_t *switch_device;
static esp_err_t write_cb(const esp_rmaker_device_t *dev, const esp_rmaker_param_val_t val, void *priv_data, esp_rmaker_write_ctx_t *ctx) static esp_err_t write_cb(const esp_rmaker_device_t *dev, const esp_rmaker_param_val_t val, void *priv_data, esp_rmaker_write_ctx_t *ctx)
{ {
if (ctx) { if (ctx) {
ESP_LOGI(TAG, "Comando recebido via %s", esp_rmaker_device_cb_src_to_str(ctx->src)); ESP_LOGI(TAG, "Command received via %s", esp_rmaker_device_cb_src_to_str(ctx->src));
} }
bool state = val.val.b; bool state = val.val.b;
ESP_LOGI(TAG, "Estado do interruptor alterado para: %s", state ? "LIGADO" : "DESLIGADO"); ESP_LOGI(TAG, "Switch state changed to: %s", state ? "ON" : "OFF");
// 更新云端和应用中的参数 // 更新云端和应用中的参数
esp_rmaker_param_update_and_report(priv_data, val); esp_rmaker_param_update_and_report(priv_data, val);
+3 -3
View File
@@ -56,7 +56,7 @@ void speech_recognition_task(void *arg)
// 1. 唤醒词(Wake Word)检测 // 1. 唤醒词(Wake Word)检测
if (res->wake_word_detected == WAKENET_DETECTED) { if (res->wake_word_detected == WAKENET_DETECTED) {
ESP_LOGW(TAG, ">>> PALAVRA-CHAVE DETECTADA! Canal de escuta aberto."); ESP_LOGW(TAG, ">>> WAKE WORD DETECTED! Listening channel open.");
} }
// 2. 本地命令识别 // 2. 本地命令识别
@@ -65,7 +65,7 @@ void speech_recognition_task(void *arg)
if (mn_state == ESP_MN_STATE_DETECTED) { if (mn_state == ESP_MN_STATE_DETECTED) {
esp_mn_results_t *mn_res = multinet->get_results(model_data); esp_mn_results_t *mn_res = multinet->get_results(model_data);
ESP_LOGI(TAG, ">>> COMANDO RECONHECIDO: ID #%d (Frase correspondente)", mn_res->command_id[0]); ESP_LOGI(TAG, ">>> COMMAND RECOGNIZED: ID #%d (matching phrase)", mn_res->command_id[0]);
// 操作示例 // 操作示例
if (mn_res->command_id[0] == 1) { if (mn_res->command_id[0] == 1) {
@@ -74,7 +74,7 @@ void speech_recognition_task(void *arg)
ESP_LOGI(TAG, "Action: turn off the living room light"); ESP_LOGI(TAG, "Action: turn off the living room light");
} }
} else if (mn_state == ESP_MN_STATE_TIMEOUT) { } else if (mn_state == ESP_MN_STATE_TIMEOUT) {
ESP_LOGI(TAG, "Tempo esgotado. Retornando ao modo de escuta de Wake Word."); ESP_LOGI(TAG, "Timeout. Returning to Wake Word listening mode.");
} }
} }
} }
+2 -2
View File
@@ -55,7 +55,7 @@ void loop() {
// 检测图像中的人脸 // 检测图像中的人脸
box_array_t *faces = face_detect_msr01(fb->buf, fb->width, fb->height, fb->format); box_array_t *faces = face_detect_msr01(fb->buf, fb->width, fb->height, fb->format);
if (faces) { if (faces) {
Serial.printf("Rostos detectados: %d\n", faces->len); Serial.printf("Faces detected: %d\n", faces->len);
for (int i = 0; i < faces->len; i++) { for (int i = 0; i < faces->len; i++) {
box_t *box = &faces->box[i]; box_t *box = &faces->box[i];
Serial.printf("Face %d - x:%d y:%d w:%d h:%d\n", i, box->box_p[0], box->box_p[1], Serial.printf("Face %d - x:%d y:%d w:%d h:%d\n", i, box->box_p[0], box->box_p[1],
@@ -63,7 +63,7 @@ void loop() {
} }
dl_lib_free(faces); dl_lib_free(faces);
} else { } else {
Serial.println("Nenhum rosto detectado."); Serial.println("No face detected.");
} }
esp_camera_fb_return(fb); esp_camera_fb_return(fb);
+2 -2
View File
@@ -25,7 +25,7 @@ static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t
if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID && if (message->attribute.id == ESP_ZB_ZCL_ATTR_ON_OFF_ON_OFF_ID &&
message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) { message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_BOOL) {
bool on_off_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : false; bool on_off_state = message->attribute.data.value ? *(bool *)message->attribute.data.value : false;
ESP_LOGW(TAG, ">>> COMANDO ZIGBEE RECEBIDO: Lamp %s", on_off_state ? "ON" : "OFF"); ESP_LOGW(TAG, ">>> ZIGBEE COMMAND RECEIVED: Lamp %s", on_off_state ? "ON" : "OFF");
} }
} }
return ESP_OK; return ESP_OK;
@@ -47,7 +47,7 @@ void esp_zb_app_signal_handler(esp_zb_app_signal_msg_t *signal_msg)
if (err_status == ESP_OK) { if (err_status == ESP_OK) {
ESP_LOGI(TAG, "Connected to the Zigbee 3.0 network!"); ESP_LOGI(TAG, "Connected to the Zigbee 3.0 network!");
} else { } else {
ESP_LOGW(TAG, "Buscando coordenador Zigbee novamente..."); ESP_LOGW(TAG, "Searching for the Zigbee coordinator again...");
esp_zb_scheduler_alarm((esp_zb_callback_t)esp_zb_bdb_start_top_level_commissioning, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000); esp_zb_scheduler_alarm((esp_zb_callback_t)esp_zb_bdb_start_top_level_commissioning, ESP_ZB_BDB_MODE_NETWORK_STEERING, 1000);
} }
break; break;
+1 -1
View File
@@ -33,7 +33,7 @@ fn main() -> ! {
let delay = Delay::new(&clocks); let delay = Delay::new(&clocks);
println!("🦀 Firmware Rust Bare-Metal inicializado no ESP32!"); println!("🦀 Rust bare-metal firmware initialized on ESP32!");
let mut counter: u32 = 0; let mut counter: u32 = 0;
+38
View File
@@ -620,6 +620,43 @@ function verificarRegrasLivro() {
} }
} }
// ---------------------------------------------------------------------------
// 5. Português dentro dos dados de outros idiomas
// ---------------------------------------------------------------------------
// Valores que começam com número ("4 (SPI0/1 para Flash...)") passam pela allowlist
// técnica; esta varredura pega o texto em português que sobra dentro deles.
const PALAVRAS_SO_PORTUGUESAS = /\b(até|canais|uso geral|conforme a carga|podem ser|saída|resolução|integrada no|encapsulamento|memória|não|única|isócronos|proximidade e)\b/i;
function percorrer(valor, caminho, aoAchar) {
if (typeof valor === "string") {
if (PALAVRAS_SO_PORTUGUESAS.test(valor)) aoAchar(caminho, valor);
} else if (Array.isArray(valor)) {
valor.forEach((v, i) => percorrer(v, `${caminho}[${i}]`, aoAchar));
} else if (valor && typeof valor === "object") {
for (const [k, v] of Object.entries(valor)) percorrer(v, `${caminho}.${k}`, aoAchar);
}
}
function verificarPortuguesNosDados() {
console.log(`\n${CORES.negrito}${CORES.ciano}[5/5] Português dentro dos dados de outros idiomas${CORES.reset}`);
let achados = 0;
for (const loc of LOCALES) {
if (loc === "pt") continue;
const arquivos = [`data/${loc}/series.json`, `data/${loc}/frameworks.json`];
const dirCon = `data/${loc}/conexoes`;
if (existsSync(dirCon)) for (const a of readdirSync(dirCon)) arquivos.push(join(dirCon, a));
for (const arq of arquivos) {
if (!existsSync(arq)) continue;
percorrer(JSON.parse(readFileSync(arq, "utf8")), arq, (caminho, valor) => {
if (achados < 10) falha(`'${loc}': texto em português em ${caminho}: "${valor.slice(0, 80)}"`);
achados++;
});
}
}
if (achados === 0) sucesso("Nenhum texto em português nos dados dos outros idiomas.");
else if (achados > 10) falha(`... e mais ${achados - 10} ocorrência(s).`);
}
async function main() { async function main() {
console.log(`${CORES.negrito}${CORES.azul}======================================================${CORES.reset}`); console.log(`${CORES.negrito}${CORES.azul}======================================================${CORES.reset}`);
console.log(`${CORES.negrito}${CORES.azul} ESPDocs · Verificação de Internacionalização (i18n) ${CORES.reset}`); console.log(`${CORES.negrito}${CORES.azul} ESPDocs · Verificação de Internacionalização (i18n) ${CORES.reset}`);
@@ -629,6 +666,7 @@ async function main() {
verificarOutrosIdiomas(ptMessages); verificarOutrosIdiomas(ptMessages);
await verificarDadosPorIdioma(); await verificarDadosPorIdioma();
verificarRegrasLivro(); verificarRegrasLivro();
verificarPortuguesNosDados();
console.log(`\n${CORES.negrito}------------------------------------------------------${CORES.reset}`); console.log(`\n${CORES.negrito}------------------------------------------------------${CORES.reset}`);
if (totalErros === 0 && totalAvisos === 0) { if (totalErros === 0 && totalAvisos === 0) {
+22
View File
@@ -67,6 +67,12 @@ if [[ " $ENABLED_LOCALES " =~ " de " ]]; then
done done
fi fi
if [[ " $ENABLED_LOCALES " =~ " zh-CN " ]]; then
for p in /series /catalog /compare /components /diagnostics /frameworks /glossary /selector /about; do
status 200 "/zh-CN$p"
done
fi
# 2. Idiomas INATIVOS: redirecionam com 307 para o idioma fallback (en se ativo, senão pt) # 2. Idiomas INATIVOS: redirecionam com 307 para o idioma fallback (en se ativo, senão pt)
for l in $ALL_LOCALES; do for l in $ALL_LOCALES; do
if [[ ! " $ENABLED_LOCALES " =~ " $l " ]]; then if [[ ! " $ENABLED_LOCALES " =~ " $l " ]]; then
@@ -120,6 +126,11 @@ else
fi fi
if [[ ! " $ENABLED_LOCALES " =~ " zh-CN " ]]; then if [[ ! " $ENABLED_LOCALES " =~ " zh-CN " ]]; then
destino 307 / /en 'Accept-Language: zh-CN,zh;q=0.9' destino 307 / /en 'Accept-Language: zh-CN,zh;q=0.9'
else
destino 307 / /zh-CN 'Accept-Language: zh-CN,zh;q=0.9'
destino 307 / /zh-CN 'Accept-Language: zh-Hans-CN'
destino 307 / /zh-CN 'Accept-Language: zh-TW,zh;q=0.9'
destino 307 / /zh-CN 'Cookie: NEXT_LOCALE=zh-CN'
fi fi
fi fi
@@ -176,4 +187,15 @@ if [[ " $ENABLED_LOCALES " =~ " de " ]]; then
done done
fi fi
if [[ " $ENABLED_LOCALES " =~ " zh-CN " ]]; then
html_zh=$(curl -s -A 'Mozilla/5.0 Chrome/130' "$base/zh-CN/about")
head_zh=${html_zh%%</head>*}
for marca in '<title>' 'rel="canonical"'; do
case "$head_zh" in *"$marca"*) ;; *) echo "FALHOU /zh-CN/about: $marca fora do <head>"; falhas=$((falhas + 1));; esac
done
fi
# 8. Respostas da busca no idioma da página (o motor roda no navegador)
LOCALES="$ENABLED_LOCALES" node scripts/verifica-busca.mjs "$base" || falhas=$((falhas + 1))
exit $((falhas > 0)) exit $((falhas > 0))
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env node
// Respostas da busca por idioma: texto no idioma da página e links dentro dele.
//
// node scripts/verifica-busca.mjs http://127.0.0.1:3998
//
// O texto das "respostas diretas" é montado no navegador pelo motor; as varreduras
// de página não o enxergam. Aqui rodamos o mesmo motor sobre o índice servido.
import { preparar, buscar } from "../lib/searchEngine.js";
const base = process.argv[2];
if (!base) {
console.error("uso: verifica-busca.mjs <url-base>");
process.exit(2);
}
const idiomas = (process.env.LOCALES || "pt en es fr de zh-CN").split(/\s+/);
const consultas = {
pt: ["flash s3", "gpio26 s3", "pinos usb c6", "deep sleep p4", "psram"],
en: ["flash s3", "gpio26 s3", "usb pins c6", "deep sleep p4", "psram"],
es: ["flash s3", "gpio26 s3", "usb c6", "deep sleep p4", "psram"],
fr: ["flash s3", "gpio26 s3", "usb c6", "deep sleep p4", "psram"],
de: ["flash s3", "gpio26 s3", "usb c6", "deep sleep p4", "psram"],
"zh-CN": ["s3 闪存", "gpio26 s3", "c6 usb 引脚", "低功耗 p4", "哪款 esp32 有 psram", "安全启动"],
};
// Palavras que só existem em português (as respostas dos outros idiomas não podem ter).
const SO_PT = /(ligados à|dados da série|diagrama de pinos|que dependem do modo|já filtrado|em cada série|especificações do|\bcondicionais\b|\bpinos\b)/i;
const strings = (r) => [r.titulo, r.descricao, ...(r.linhas ?? []).flatMap((l) => [l.k, l.v]), ...(r.chips ?? [])].filter(Boolean);
let falhas = 0;
for (const loc of idiomas) {
const res = await fetch(`${base}/api/busca/${loc}`);
if (!res.ok) {
console.log(`FALHOU /api/busca/${loc}: ${res.status}`);
falhas++;
continue;
}
const indice = preparar(await res.json());
for (const q of consultas[loc] ?? consultas.en) {
const { resultados } = buscar(indice, q);
if (!resultados.length) {
console.log(`FALHOU [${loc}] "${q}": sem resultados`);
falhas++;
continue;
}
for (const r of resultados.filter((x) => x.tipo === "resposta" || x.tipo === "pino")) {
if (loc !== "pt" && !r.href.startsWith(`/${loc}/`)) {
console.log(`FALHOU [${loc}] "${q}": link fora do idioma: ${r.href}`);
falhas++;
}
if (loc !== "pt") {
for (const s of strings(r)) {
if (SO_PT.test(s)) {
console.log(`FALHOU [${loc}] "${q}": português em "${s.slice(0, 80)}"`);
falhas++;
}
}
}
}
}
}
if (falhas === 0) console.log(` ✔ Busca: respostas e links corretos em ${idiomas.join(", ")}.`);
process.exit(falhas > 0 ? 1 : 0);
+65 -1
View File
@@ -235,8 +235,72 @@ for u in urls_de:
print(f" ✖ Texto visível vazamento em {u}: \"{h[:120]}\"") print(f" ✖ Texto visível vazamento em {u}: \"{h[:120]}\"")
falhas += 1 falhas += 1
# Varredura das rotas em chinês simplificado: português OU inglês solto (fora de blocos de código)
pt_in_zh_regex = re.compile(
r"\b(não|você|para o|para a|com o|com a|são|também|então|pinagem|série|séries|livro|"
r"ção|ções|muito|já|estão|é um|é uma|pelo|pela|especificações|especificação|início|artigo|sobre|seletor)\b",
re.I
)
# Frase em inglês solta: palavra funcional entre palavras minúsculas, ou expressões de interface.
# Nomes de produto em Title Case ("Arduino Core for ESP32", "Google Home") não casam.
en_in_zh_regex = re.compile(
r"\b[a-z]{2,}\s+(?:the|and|with|for|your|this|that|from|are|you|will|not|how|what)\s+[a-z]{2,}\b"
r"|(?i:\b(?:skip to content|click here|learn more|read more|back to|please)\b)"
)
fixas_zh = [
"/zh-CN", "/zh-CN/series", "/zh-CN/compare", "/zh-CN/diagnostics", "/zh-CN/selector",
"/zh-CN/frameworks", "/zh-CN/components", "/zh-CN/glossary", "/zh-CN/catalog", "/zh-CN/about"
]
urls_zh = fixas_zh + [f"/zh-CN/series/{s}" for s in series] + [f"/zh-CN/frameworks/{f}" for f in frameworks]
print(f"\nESPDocs · Varredura de i18n nas {len(urls_zh)} rotas em chinês ({base})")
# Mensagens reais de log/erro da ferramenta: aparecem iguais em qualquer idioma.
ASSINATURAS_DE_LOG = {"Failed to connect to ESP32: Timed out waiting for packet header"}
def vazou_zh(texto):
if texto.strip() in ASSINATURAS_DE_LOG:
return None
return pt_in_zh_regex.search(texto) or en_in_zh_regex.search(texto)
for u in urls_zh:
try:
req = urllib.request.Request(base + u, headers={"User-Agent": "ESPDocs-SmokeTest/1.0"})
raw = urllib.request.urlopen(req, timeout=10).read().decode("utf-8")
except Exception as e:
print(f" ✖ ERRO ao acessar {u}: {e}")
falhas += 1
continue
# <html lang> correto
if not re.search(r"<html[^>]*\blang=\"zh-CN\"", raw):
print(f" ✖ <html lang> diferente de zh-CN em {u}")
falhas += 1
# 1. Verifica blocos ld+json (strings de texto, não URLs/ids)
scripts = re.findall(r"<script[^>]*type=[\"']application/ld\+json[\"'][^>]*>(.*?)</script>", raw, re.DOTALL)
for i, s in enumerate(scripts):
try:
data = json.loads(s)
issues = check_jsonld(data, f"script[{i}]", pt_in_zh_regex, True)
for p_, v in issues:
print(f" ✖ JSON-LD vazamento em {u} -> {p_}: \"{v}\"")
falhas += 1
except Exception as e:
print(f" ✖ Erro no parse de JSON-LD em {u}: {e}")
falhas += 1
# 2. Texto visível, sem blocos de código (exemplos têm logs e comentários em inglês por desenho)
clean = re.sub(r"<(script|style|pre|code)[\s\S]*?</\1>", "", raw)
clean = html.unescape(re.sub(r"<[^>]+>", "\n", clean))
hits = [l.strip() for l in clean.split("\n") if l.strip() and vazou_zh(l)]
for h in hits:
print(f" ✖ Texto visível vazamento em {u}: \"{h[:120]}\"")
falhas += 1
if falhas == 0: if falhas == 0:
print(f" ✔ Todas as {len(urls)} rotas en, {len(urls_es)} rotas es, {len(urls_fr)} rotas fr e {len(urls_de)} rotas de verificadas com ZERO vazamentos (texto e JSON-LD).") print(f" ✔ Todas as {len(urls)} rotas en, {len(urls_es)} rotas es, {len(urls_fr)} rotas fr, {len(urls_de)} rotas de e {len(urls_zh)} rotas zh verificadas com ZERO vazamentos (texto e JSON-LD).")
sys.exit(0) sys.exit(0)
else: else:
print(f" ✖ Total de falhas detectadas: {falhas}") print(f" ✖ Total de falhas detectadas: {falhas}")