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

+151 -68
View File
@@ -10,6 +10,7 @@ import seriesDataEn from "@/data/en/series.json";
import seriesDataEs from "@/data/es/series.json";
import seriesDataFr from "@/data/fr/series.json";
import seriesDataDe from "@/data/de/series.json";
import seriesDataZh from "@/data/zh-CN/series.json";
import ConnectionsDiagram from "@/components/ConnectionsDiagram";
import SeriesTabMenu from "@/components/SeriesTabMenu";
import PinRestrictions from "@/components/PinRestrictions";
@@ -41,8 +42,9 @@ export default async function SerieDetail({ params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const seriesData = isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
const isZh = locale === "zh-CN";
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];
if (!serie) {
@@ -51,7 +53,7 @@ export default async function SerieDetail({ params }) {
let conexoes = [];
try {
conexoes = isDe
conexoes = isZh ? require(`@/data/zh-CN/conexoes/${key}.json`) : isDe
? require(`@/data/de/conexoes/${key}.json`)
: isFr
? 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 nextKey = currentIndex < seriesKeys.length - 1 ? seriesKeys[currentIndex + 1] : null;
const nao = 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 nao = isZh ? "否" : isDe ? "Nein" : isFr ? "Non" : isEs || isEn ? "No" : "Não";
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: "Kerne", value: serie.nucleos },
@@ -113,7 +121,20 @@ export default async function SerieDetail({ params }) {
];
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: "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" },
];
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: "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" },
];
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: "Bluetooth", value: serie.bluetooth || nao },
@@ -273,7 +307,13 @@ export default async function SerieDetail({ params }) {
{ 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: "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" },
];
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: "ADC", value: serie.adc },
@@ -380,12 +431,12 @@ export default async function SerieDetail({ params }) {
const interfacesEspeciais = [];
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_dsi) interfacesEspeciais.push({ label: "MIPI DSI", value: serie.mipi_dsi });
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_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_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: isZh ? "温度传感器" : isDe ? "Temperatursensor" : isFr ? "Capteur de température" : isEs ? "Sensor de temperatura" : isEn ? "Temperature Sensor" : "Sensor Temperatura", value: serie.sensor_temperatura });
// Consumo de Energia
const consumoEnergia = [];
@@ -460,7 +511,21 @@ export default async function SerieDetail({ params }) {
'deep_sleep': 'Deep Sleep',
'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]) => {
consumoEnergia.push({
@@ -473,18 +538,18 @@ export default async function SerieDetail({ params }) {
const tabs = [
{
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,
content: (
<div className="card p-5 sm:p-7">
<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>
{/* 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">
<Image
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}
height={800}
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>
<p className="mt-3 text-sm text-muted">
{isDe ? (
{isZh ? ( <>乐鑫面向 {key} 系列的官方开发板。</> ) : isDe ? (
<>Offizielles Espressif-Board für die Serie {key}.</>
) : isFr ? (
<>Carte officielle d'Espressif pour la série {key}.</>
@@ -509,7 +574,7 @@ export default async function SerieDetail({ params }) {
},
{
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,
content: (
<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" />
<div>
<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 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.`
) : 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.`
@@ -545,10 +610,24 @@ export default async function SerieDetail({ params }) {
<div className="callout mt-8">
<p className="flex items-center gap-2 font-semibold text-ink">
<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 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
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 = [
{ id: "processador", titulo: 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: "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: "seguranca", titulo: 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: "processador", titulo: isZh ? "处理器" : isDe ? "Prozessor" : isFr ? "Processeur" : isEs ? "Procesador" : isEn ? "Processor" : "Processador", icon: Cpu, specs: processadorSpecs },
{ id: "conectividade", titulo: isZh ? "无线连接" : isDe ? "Konnektivität" : isFr ? "Connectivité" : isEs ? "Conectividad" : isEn ? "Connectivity" : "Conectividade", icon: Wifi, specs: conectividadeSpecs },
{ 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: isZh ? "安全" : isDe ? "Sicherheit" : isFr ? "Sécurité" : isEs ? "Seguridad" : isEn ? "Security" : "Segurança", icon: ShieldCheck, specs: segurancaSpecs },
{ id: "memoria", titulo: isZh ? "内存" : isDe ? "Speicher" : isFr ? "Mémoire" : isEs ? "Memoria" : isEn ? "Memory" : "Memória", icon: Database, specs: memoriaSpecs },
...(consumoEnergia.length > 0
? [{
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,
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."
: 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."
@@ -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.",
}]
: []),
{ 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
? [{ 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",
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,
specs: [
{ label: 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 ? "Betrieb" : isFr ? "Fonctionnement" : isEs ? "Operación" : isEn ? "Operating" : "Operação", value: serie.temperatura_operacao },
{ label: isZh ? "存储" : isDe ? "Lagerung" : isFr ? "Stockage" : isEs ? "Almacenamiento" : isEn ? "Storage" : "Armazenamento", value: serie.temperatura_armazenamento },
],
},
];
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: "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: "GPIO", value: serie.gpio, detail: 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 ? "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: 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: isZh ? "个可编程引脚" : isDe ? "programmierbare Pins" : isFr ? "broches programmables" : isEs ? "pines programables" : isEn ? "programmable pins" : "pinos programáveis" },
{ 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 (
@@ -689,8 +768,8 @@ export default async function SerieDetail({ params }) {
type="application/ld+json"
dangerouslySetInnerHTML={{
__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: isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32", caminho: getLocalizedHref("/series", currentLocale) },
{ 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: 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) },
])),
}}
@@ -703,8 +782,8 @@ export default async function SerieDetail({ params }) {
isEn={isEn}
isEs={isEs}
isFr={isFr}
isDe={isDe}
trail={[{ label: isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries", href: getLocalizedHref("/series", currentLocale) }, { label: key }]}
isDe={isDe} isZh={isZh}
trail={[{ label: isZh ? "系列" : isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries", href: getLocalizedHref("/series", currentLocale) }, { label: key }]}
icon={Cpu}
label={serie.arquitetura}
title={key}
@@ -713,32 +792,32 @@ export default async function SerieDetail({ params }) {
{serie.datasheet ? (
<a href={serie.datasheet} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
<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" />
</a>
) : (
<span className="btn btn-ghost cursor-not-allowed opacity-70" aria-disabled="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>
)}
{serie.guia_de_programacao && (
<a href={serie.guia_de_programacao} target="_blank" rel="noopener noreferrer" className="btn btn-ghost">
<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" />
</a>
)}
{serie.manual_tecnico && (
<a href={serie.manual_tecnico} target="_blank" rel="noopener noreferrer" className="btn btn-ghost">
<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" />
</a>
)}
</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">
{fatos.map((fato) => (
<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" />
<div>
<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 className="mt-1">{serie.status_documentacao}</p>
</div>
@@ -763,13 +842,13 @@ export default async function SerieDetail({ params }) {
</section>
<div className="shell mt-10">
<SeriesTabMenu tabs={tabs} isEn={isEn} isEs={isEs} isFr={isFr} isDe={isDe} />
<PinRestrictions conexoes={conexoes} serieKey={key} 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} isZh={isZh} />
</div>
<div className="shell mt-12">
<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"
>
{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="max-w-2xl">
<h2 className="section-title">
{isDe ? (
{isZh ? ( <>准备好用 {key} 做原型了吗?</> ) : isDe ? (
<>Bereit zum Prototyping mit {key}?</>
) : isFr ? (
<>Prêt à prototyper avec {key} ?</>
@@ -803,7 +882,7 @@ export default async function SerieDetail({ params }) {
)}
</h2>
<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."
: isFr
? "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>
</div>
<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" />
</Link>
</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 ? (
<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" />
<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>
</Link>
@@ -834,12 +913,12 @@ export default async function SerieDetail({ params }) {
)}
<Link href={getLocalizedHref("/comparacao", currentLocale)} className="btn btn-ghost justify-self-center">
<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>
{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">
<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>
<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 isFr = locale === "fr";
const isDe = locale === "de";
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const seriesData = isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
const isZh = locale === "zh-CN";
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];
if (!serie) {
return {
title: isDe
title: isZh ? "未找到该系列" : isDe
? "Serie nicht gefunden"
: isFr
? "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,
].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.memoria_sram} SRAM und ${serie.gpio} GPIOs` +
(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.`;
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" }];
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} datasheet`, `${serie.nome_completo} Spezifikationen`,
@@ -973,7 +1056,7 @@ export async function generateMetadata({ params }) {
const meta = {
...paginaMeta({
titulo: isDe
titulo: isZh ? `${serie.nome_completo}:规格与引脚图` : isDe
? `${serie.nome_completo}: Spezifikationen und Pinbelegung`
: isFr
? `${serie.nome_completo} : spécifications et brochage`
@@ -989,7 +1072,7 @@ export async function generateMetadata({ params }) {
keywords,
}),
other: {
"article:section": isDe
"article:section": isZh ? "ESP32 系列" : isDe
? "ESP32-Serien"
: isFr
? "Séries ESP32"
+21 -9
View File
@@ -6,8 +6,9 @@ export async function generateMetadata({ params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
const isZh = locale === "zh-CN";
const meta = paginaMeta({
titulo: isDe
titulo: isZh ? "ESP32 系列:技术指南与引脚图" : isDe
? "ESP32-Serien: Technischer Leitfaden und Pinbelegung"
: isFr
? "Séries ESP32 : Guide technique et brochage"
@@ -16,7 +17,7 @@ export async function generateMetadata({ params }) {
: isEn
? "ESP32 Series: Technical Guide and Pinouts"
: "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."
: 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."
@@ -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.",
caminho: "/series",
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"]
: isFr
? ["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"],
});
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",
};
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).",
},
];
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 }) {
const { locale } = await params;
@@ -105,7 +116,8 @@ export default async function Layout({ children, params }) {
const isEs = locale === "es";
const isFr = locale === "fr";
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 (
<>
@@ -115,12 +127,12 @@ export default async function Layout({ children, params }) {
__html: JSON.stringify(
jsonLd.trilha([
{
nome: isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início",
caminho: isDe ? "/de" : isFr ? "/fr" : isEs ? "/es" : isEn ? "/en" : "/",
nome: isZh ? "首页" : isDe ? "Startseite" : isFr ? "Accueil" : isEs ? "Inicio" : isEn ? "Home" : "Início",
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",
caminho: isDe ? "/de/serien" : isFr ? "/fr/series" : isEs ? "/es/series" : isEn ? "/en/series" : "/series",
nome: isZh ? "ESP32 系列" : isDe ? "ESP32-Serien" : isFr ? "Séries ESP32" : isEs ? "Series ESP32" : isEn ? "ESP32 Series" : "Séries ESP32",
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 seriesDataFr from "@/data/fr/series.json";
import seriesDataDe from "@/data/de/series.json";
import seriesDataZh from "@/data/zh-CN/series.json";
import { getLocalizedHref } from "@/i18n/links";
import {
AlertTriangle,
@@ -59,29 +60,37 @@ const FILTROS_DE = [
{ id: "todas", rotulo: "Alle", 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) => 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) },
];
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. */
function nivelSeguranca(serie, isEn, isEs, isFr, isDe) {
function nivelSeguranca(serie, isEn, isEs, isFr, isDe, isZh = false) {
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 (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")) {
return isDe ? "Hardware-ECDSA" : isFr ? "ECDSA matériel" : isEs ? "ECDSA en hardware" : isEn ? "Hardware ECDSA" : "ECDSA em hardware";
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") || String(seg.ecdsa).startsWith("是"))) {
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")) {
return isDe ? "Hardware-ECC" : isFr ? "ECC matériel" : isEs ? "ECC en hardware" : isEn ? "Hardware ECC" : "ECC 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") || String(seg.ecc).startsWith("是"))) {
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";
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 simEn = (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 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() {
const pathname = usePathname();
@@ -89,9 +98,10 @@ export default function Series() {
const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de");
const currentLocale = isDe ? "de" : isFr ? "fr" : isEs ? "es" : isEn ? "en" : "pt";
const seriesData = isDe ? seriesDataDe : isFr ? seriesDataFr : isEs ? seriesDataEs : isEn ? seriesDataEn : seriesDataPt;
const filtros = isDe ? FILTROS_DE : isFr ? FILTROS_FR : isEs ? FILTROS_ES : isEn ? FILTROS_EN : FILTROS_PT;
const isZh = pathname?.startsWith("/zh-CN");
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 filtros = isZh ? FILTROS_ZH : isDe ? FILTROS_DE : isFr ? FILTROS_FR : isEs ? FILTROS_ES : isEn ? FILTROS_EN : FILTROS_PT;
const [filtro, setFiltro] = useState("todas");
const todas = Object.entries(seriesData);
@@ -107,11 +117,11 @@ export default function Series() {
isEn={isEn}
isEs={isEs}
isFr={isFr}
isDe={isDe}
trail={[{ label: isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries" }]}
isDe={isDe} isZh={isZh}
trail={[{ label: isZh ? "系列" : isDe ? "Serien" : isFr ? "Séries" : isEs ? "Series" : isEn ? "Series" : "Séries" }]}
icon={Compass}
label={
isDe
isZh ? `${todas.length} 个乐鑫系列` : isDe
? `${todas.length} Espressif-Familien`
: isFr
? `${todas.length} familles d'Espressif`
@@ -121,9 +131,9 @@ export default function Series() {
? `${todas.length} Espressif families`
: `${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={
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."
: 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é."
@@ -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"}>
<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">
<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={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) => (
<button key={f.id} type="button" aria-pressed={filtro === f.id} onClick={() => setFiltro(f.id)} className="chip-btn">
{f.rotulo}
@@ -146,7 +156,7 @@ export default function Series() {
<p role="status" aria-live="polite" className="mt-4 text-sm text-muted">
{series.length}{" "}
{series.length === 1
? isDe
? isZh ? "个系列" : isDe
? "Serie"
: isFr
? "série"
@@ -155,7 +165,7 @@ export default function Series() {
: isEn
? "series"
: "série"
: isDe
: isZh ? "个系列" : isDe
? "Serien"
: isFr
? "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">
{series.map(([key, serie]) => {
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],
["Taktfrequenz", serie.frequencia],
["SRAM", serie.memoria_sram],
["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,
].filter(Boolean)
: isFr
@@ -225,7 +242,7 @@ export default function Series() {
{fatos.map(([rotulo, valor]) => (
<div key={rotulo} className="flex items-baseline justify-between gap-4 py-2">
<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>
))}
</dl>
@@ -234,14 +251,14 @@ export default function Series() {
{serie.aceleradores_ia && (
<li className="chip chip-accent">
<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 className="chip">
<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>
{(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">
<Home className="h-3 w-3 text-accent" aria-hidden="true" />
Matter
@@ -250,13 +267,13 @@ export default function Series() {
{serie.status_documentacao && (
<li className="chip">
<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>
)}
</ul>
<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" />
</span>
</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="max-w-2xl">
<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>
<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."
: isFr
? "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>
<Link href={getLocalizedHref("/comparacao", currentLocale)} className="btn btn-primary shrink-0">
<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" />
</Link>
</div>