feat(i18n): English localization (phase 3)
- Translate content to English across messages/en.json, data/en, lib/data/en, and public/exemplos/en - Implement localized routing, slugs, and language negotiation with fallback to en - Build dedicated search index for English with strict book exclusion - Update components and pages to dynamically render localized strings and datasets - Verify full structural parity and exact 1:1 match on Portuguese pages via comparator Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
1 parent
22b1f26aed
commit
c1bd2c7e11
61 files changed
+18182
-1225
No files matched your search
+311
-171
@@ -5,10 +5,12 @@ import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import { notFound } from "next/navigation";
|
||||
import seriesData from "@/public/series.json";
|
||||
import seriesDataPt from "@/public/series.json";
|
||||
import seriesDataEn from "@/data/en/series.json";
|
||||
import ConnectionsDiagram from "@/components/ConnectionsDiagram";
|
||||
import SeriesTabMenu from "@/components/SeriesTabMenu";
|
||||
import PinRestrictions from "@/components/PinRestrictions";
|
||||
import { getLocalizedHref } from "@/i18n/links";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowLeft,
|
||||
@@ -31,7 +33,10 @@ import {
|
||||
} from "lucide-react";
|
||||
|
||||
export default async function SerieDetail({ params }) {
|
||||
const { key } = await params;
|
||||
const { locale, key } = await params;
|
||||
const isEn = locale === "en";
|
||||
const currentLocale = isEn ? "en" : "pt";
|
||||
const seriesData = isEn ? seriesDataEn : seriesDataPt;
|
||||
const serie = seriesData[key];
|
||||
|
||||
if (!serie) {
|
||||
@@ -40,7 +45,7 @@ export default async function SerieDetail({ params }) {
|
||||
|
||||
let conexoes = [];
|
||||
try {
|
||||
conexoes = require(`@/public/conexoes/${key}.json`);
|
||||
conexoes = isEn ? require(`@/data/en/conexoes/${key}.json`) : require(`@/public/conexoes/${key}.json`);
|
||||
} catch (error) {
|
||||
console.log(`Arquivo de conexões não encontrado para ${key}`);
|
||||
}
|
||||
@@ -50,98 +55,177 @@ 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 processadorSpecs = [
|
||||
{ label: "Arquitetura", value: serie.arquitetura },
|
||||
{ label: "Núcleos", value: serie.nucleos },
|
||||
{ label: "Frequência", value: serie.frequencia },
|
||||
{ label: "Coprocessador ULP", value: serie.coprocessador_ulp || "Não" },
|
||||
{ label: "Aceleradores IA", value: serie.aceleradores_ia || "Não" },
|
||||
];
|
||||
const nao = isEn ? "No" : "Não";
|
||||
const naoInformado = isEn ? "Not informed" : "Não informado";
|
||||
|
||||
const processadorSpecs = isEn
|
||||
? [
|
||||
{ label: "Architecture", value: serie.arquitetura },
|
||||
{ label: "Cores", value: serie.nucleos },
|
||||
{ label: "Frequency", value: serie.frequencia },
|
||||
{ label: "ULP Coprocessor", value: serie.coprocessador_ulp || nao },
|
||||
{ label: "AI Accelerators", value: serie.aceleradores_ia || nao },
|
||||
]
|
||||
: [
|
||||
{ label: "Arquitetura", value: serie.arquitetura },
|
||||
{ label: "Núcleos", value: serie.nucleos },
|
||||
{ label: "Frequência", value: serie.frequencia },
|
||||
{ label: "Coprocessador ULP", value: serie.coprocessador_ulp || "Não" },
|
||||
{ label: "Aceleradores IA", value: serie.aceleradores_ia || "Não" },
|
||||
];
|
||||
|
||||
const seg = serie.seguranca || {};
|
||||
const segurancaSpecs = [
|
||||
{ label: "AES", value: seg.aes || "Não" },
|
||||
{ label: "SHA", value: seg.sha || "Não" },
|
||||
{ label: "RSA / MPI", value: seg.rsa || "Não" },
|
||||
{ label: "ECC (curvas elípticas)", value: seg.ecc || "Não" },
|
||||
{ label: "ECDSA em hardware", value: seg.ecdsa || "Não" },
|
||||
{ label: "HMAC", value: seg.hmac || "Não" },
|
||||
{ label: "Assinatura Digital (DS)", value: seg.assinatura_digital || "Não" },
|
||||
{ label: "Key Manager", value: seg.key_manager || "Não" },
|
||||
{ label: "Gerador aleatório (RNG)", value: seg.rng || "Não" },
|
||||
{ label: "Criptografia de flash", value: seg.criptografia_flash || "Não" },
|
||||
{ label: "Secure Boot", value: seg.secure_boot || "Não" },
|
||||
{ label: "Proteção contra DPA", value: seg.protecao_dpa || "Não" },
|
||||
];
|
||||
const segurancaSpecs = isEn
|
||||
? [
|
||||
{ label: "AES", value: seg.aes || nao },
|
||||
{ label: "SHA", value: seg.sha || nao },
|
||||
{ label: "RSA / MPI", value: seg.rsa || nao },
|
||||
{ label: "ECC (elliptic curves)", value: seg.ecc || nao },
|
||||
{ label: "Hardware ECDSA", value: seg.ecdsa || nao },
|
||||
{ label: "HMAC", value: seg.hmac || nao },
|
||||
{ label: "Digital Signature (DS)", value: seg.assinatura_digital || nao },
|
||||
{ label: "Key Manager", value: seg.key_manager || nao },
|
||||
{ label: "Random Generator (TRNG)", value: seg.rng || nao },
|
||||
{ label: "Flash Encryption", value: seg.criptografia_flash || nao },
|
||||
{ label: "Secure Boot", value: seg.secure_boot || nao },
|
||||
{ label: "DPA Protection", value: seg.protecao_dpa || nao },
|
||||
]
|
||||
: [
|
||||
{ label: "AES", value: seg.aes || "Não" },
|
||||
{ label: "SHA", value: seg.sha || "Não" },
|
||||
{ label: "RSA / MPI", value: seg.rsa || "Não" },
|
||||
{ label: "ECC (curvas elípticas)", value: seg.ecc || "Não" },
|
||||
{ label: "ECDSA em hardware", value: seg.ecdsa || "Não" },
|
||||
{ label: "HMAC", value: seg.hmac || "Não" },
|
||||
{ label: "Assinatura Digital (DS)", value: seg.assinatura_digital || "Não" },
|
||||
{ label: "Key Manager", value: seg.key_manager || "Não" },
|
||||
{ label: "Gerador aleatório (RNG)", value: seg.rng || "Não" },
|
||||
{ label: "Criptografia de flash", value: seg.criptografia_flash || "Não" },
|
||||
{ label: "Secure Boot", value: seg.secure_boot || "Não" },
|
||||
{ label: "Proteção contra DPA", value: seg.protecao_dpa || "Não" },
|
||||
];
|
||||
|
||||
const ferramentasSpecs = [
|
||||
{ label: "ESP-IDF mínimo", value: serie.esp_idf_minimo || "Não informado" },
|
||||
{ label: "Core Arduino", value: serie.arduino_core || "Não informado" },
|
||||
{ label: "Depuração", value: serie.depuracao || "Não informado" },
|
||||
{ label: "Encapsulamento", value: serie.encapsulamento || "Não informado" },
|
||||
{ label: "Módulos disponíveis", value: serie.modulos || "Não informado" },
|
||||
];
|
||||
const ferramentasSpecs = isEn
|
||||
? [
|
||||
{ label: "Minimum ESP-IDF", value: serie.esp_idf_minimo || naoInformado },
|
||||
{ label: "Arduino Core", value: serie.arduino_core || naoInformado },
|
||||
{ label: "Debugging", value: serie.depuracao || naoInformado },
|
||||
{ label: "Package", value: serie.encapsulamento || naoInformado },
|
||||
{ label: "Available Modules", value: serie.modulos || naoInformado },
|
||||
]
|
||||
: [
|
||||
{ label: "ESP-IDF mínimo", value: serie.esp_idf_minimo || "Não informado" },
|
||||
{ label: "Core Arduino", value: serie.arduino_core || "Não informado" },
|
||||
{ label: "Depuração", value: serie.depuracao || "Não informado" },
|
||||
{ label: "Encapsulamento", value: serie.encapsulamento || "Não informado" },
|
||||
{ label: "Módulos disponíveis", value: serie.modulos || "Não informado" },
|
||||
];
|
||||
|
||||
const conectividadeSpecs = [
|
||||
{ label: "Wi-Fi", value: serie.wifi || "Não" },
|
||||
{ label: "Bluetooth", value: serie.bluetooth || "Não" },
|
||||
{ label: "Zigbee/Thread", value: serie.zigbee_thread || "Não" },
|
||||
{ label: "Matter", value: serie.matter || "Não" },
|
||||
{ label: "Ethernet", value: serie.ethernet || serie.ethernet_mac || "Não" },
|
||||
{ label: "CAN", value: serie.can || "Não" },
|
||||
];
|
||||
const conectividadeSpecs = isEn
|
||||
? [
|
||||
{ label: "Wi-Fi", value: serie.wifi || nao },
|
||||
{ label: "Bluetooth", value: serie.bluetooth || nao },
|
||||
{ label: "Zigbee/Thread", value: serie.zigbee_thread || nao },
|
||||
{ label: "Matter", value: serie.matter || nao },
|
||||
{ label: "Ethernet", value: serie.ethernet || serie.ethernet_mac || nao },
|
||||
{ label: "CAN", value: serie.can || nao },
|
||||
]
|
||||
: [
|
||||
{ label: "Wi-Fi", value: serie.wifi || "Não" },
|
||||
{ label: "Bluetooth", value: serie.bluetooth || "Não" },
|
||||
{ label: "Zigbee/Thread", value: serie.zigbee_thread || "Não" },
|
||||
{ label: "Matter", value: serie.matter || "Não" },
|
||||
{ label: "Ethernet", value: serie.ethernet || serie.ethernet_mac || "Não" },
|
||||
{ label: "CAN", value: serie.can || "Não" },
|
||||
];
|
||||
|
||||
const memoriaSpecs = [
|
||||
{ label: "SRAM", value: serie.memoria_sram },
|
||||
{ label: "SRAM RTC", value: serie.memoria_sram_rtc },
|
||||
{ label: "ROM", value: serie.memoria_rom },
|
||||
{ label: "Flash Externa", value: serie.flash_externa },
|
||||
{ label: "PSRAM Externa", value: serie.psram_externa || "Não" },
|
||||
];
|
||||
const memoriaSpecs = isEn
|
||||
? [
|
||||
{ label: "SRAM", value: serie.memoria_sram },
|
||||
{ label: "RTC SRAM", value: serie.memoria_sram_rtc },
|
||||
{ label: "ROM", value: serie.memoria_rom },
|
||||
{ label: "External Flash", value: serie.flash_externa },
|
||||
{ label: "External PSRAM", value: serie.psram_externa || nao },
|
||||
]
|
||||
: [
|
||||
{ label: "SRAM", value: serie.memoria_sram },
|
||||
{ label: "SRAM RTC", value: serie.memoria_sram_rtc },
|
||||
{ label: "ROM", value: serie.memoria_rom },
|
||||
{ label: "Flash Externa", value: serie.flash_externa },
|
||||
{ label: "PSRAM Externa", value: serie.psram_externa || "Não" },
|
||||
];
|
||||
|
||||
const perifericos = [
|
||||
{ label: "GPIO", value: serie.gpio },
|
||||
{ label: "ADC", value: serie.adc },
|
||||
{ label: "DAC", value: serie.dac || "Não" },
|
||||
{ label: "Touch", value: serie.touch || "Não" },
|
||||
{ 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 || "Não" },
|
||||
];
|
||||
const perifericos = isEn
|
||||
? [
|
||||
{ label: "GPIO", value: serie.gpio },
|
||||
{ label: "ADC", value: serie.adc },
|
||||
{ label: "DAC", value: serie.dac || nao },
|
||||
{ label: "Touch", 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 },
|
||||
]
|
||||
: [
|
||||
{ label: "GPIO", value: serie.gpio },
|
||||
{ label: "ADC", value: serie.adc },
|
||||
{ label: "DAC", value: serie.dac || "Não" },
|
||||
{ label: "Touch", value: serie.touch || "Não" },
|
||||
{ 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 || "Não" },
|
||||
];
|
||||
|
||||
const interfacesEspeciais = [];
|
||||
if (serie.lcd) interfacesEspeciais.push({ label: "LCD", value: serie.lcd });
|
||||
if (serie.camera) interfacesEspeciais.push({ label: "Câmera", value: serie.camera });
|
||||
if (serie.camera) interfacesEspeciais.push({ label: 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: "Sensor Hall", value: serie.sensor_hall });
|
||||
if (serie.sensor_temperatura) interfacesEspeciais.push({ label: "Sensor Temperatura", value: serie.sensor_temperatura });
|
||||
if (serie.sensor_hall) interfacesEspeciais.push({ label: isEn ? "Hall Sensor" : "Sensor Hall", value: serie.sensor_hall });
|
||||
if (serie.sensor_temperatura) interfacesEspeciais.push({ label: isEn ? "Temperature Sensor" : "Sensor Temperatura", value: serie.sensor_temperatura });
|
||||
|
||||
// Consumo de Energia
|
||||
const consumoEnergia = [];
|
||||
if (serie.consumo_energia) {
|
||||
const modoLabelsPt = {
|
||||
'active': 'Modo Ativo',
|
||||
'active_cpu_240mhz': 'Ativo (CPU 240MHz)',
|
||||
'active_cpu_160mhz': 'Ativo (CPU 160MHz)',
|
||||
'active_wifi_tx': 'Ativo (Wi-Fi TX)',
|
||||
'active_wifi_tx_peak': 'Ativo (Wi-Fi TX Pico)',
|
||||
'active_wifi': 'Ativo (Wi-Fi)',
|
||||
'active_wifi6': 'Ativo (Wi-Fi 6)',
|
||||
'modem_sleep': 'Modem Sleep',
|
||||
'modem_sleep_cpu_160mhz': 'Modem Sleep (CPU 160MHz)',
|
||||
'light_sleep': 'Light Sleep',
|
||||
'deep_sleep': 'Deep Sleep',
|
||||
'hibernation': 'Hibernação'
|
||||
};
|
||||
const modoLabelsEn = {
|
||||
'active': 'Active Mode',
|
||||
'active_cpu_240mhz': 'Active (CPU 240MHz)',
|
||||
'active_cpu_160mhz': 'Active (CPU 160MHz)',
|
||||
'active_wifi_tx': 'Active (Wi-Fi TX)',
|
||||
'active_wifi_tx_peak': 'Active (Wi-Fi TX Peak)',
|
||||
'active_wifi': 'Active (Wi-Fi)',
|
||||
'active_wifi6': 'Active (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 = isEn ? modoLabelsEn : modoLabelsPt;
|
||||
|
||||
Object.entries(serie.consumo_energia).forEach(([modo, consumo]) => {
|
||||
const modoLabels = {
|
||||
'active': 'Modo Ativo',
|
||||
'active_cpu_240mhz': 'Ativo (CPU 240MHz)',
|
||||
'active_cpu_160mhz': 'Ativo (CPU 160MHz)',
|
||||
'active_wifi_tx': 'Ativo (Wi-Fi TX)',
|
||||
'active_wifi_tx_peak': 'Ativo (Wi-Fi TX Pico)',
|
||||
'active_wifi': 'Ativo (Wi-Fi)',
|
||||
'active_wifi6': 'Ativo (Wi-Fi 6)',
|
||||
'modem_sleep': 'Modem Sleep',
|
||||
'modem_sleep_cpu_160mhz': 'Modem Sleep (CPU 160MHz)',
|
||||
'light_sleep': 'Light Sleep',
|
||||
'deep_sleep': 'Deep Sleep',
|
||||
'hibernation': 'Hibernação'
|
||||
};
|
||||
|
||||
consumoEnergia.push({
|
||||
label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
||||
label: modoLabels[modo] || modo.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
value: consumo
|
||||
});
|
||||
});
|
||||
@@ -150,38 +234,54 @@ export default async function SerieDetail({ params }) {
|
||||
const tabs = [
|
||||
{
|
||||
id: "devboard",
|
||||
label: "Placa de desenvolvimento",
|
||||
label: 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">Kit de desenvolvimento oficial</h2>
|
||||
<h2 className="text-lg font-semibold text-ink">
|
||||
{isEn ? "Official development kit" : "Kit de desenvolvimento oficial"}
|
||||
</h2>
|
||||
<div className="mt-5 overflow-hidden rounded-xl border border-line bg-surface-2 p-4">
|
||||
<Image
|
||||
src={serie.placa}
|
||||
alt={`Placa de desenvolvimento ${key}`}
|
||||
alt={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"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-3 text-sm text-muted">Placa padrão da Espressif para a série {key}.</p>
|
||||
<p className="mt-3 text-sm text-muted">
|
||||
{isEn ? (
|
||||
<>Official Espressif board for the {key} series.</>
|
||||
) : (
|
||||
<>Placa padrão da Espressif para a série {key}.</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "connections",
|
||||
label: "Diagrama de conexões",
|
||||
label: isEn ? "Pinout diagram" : "Diagrama de conexões",
|
||||
available: conexoes.length > 0,
|
||||
content: (
|
||||
<div className="card p-5 sm:p-7">
|
||||
<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" />
|
||||
<div>
|
||||
<p className="font-semibold text-ink">Este é o diagrama do chip, não da placa</p>
|
||||
<p className="font-semibold text-ink">
|
||||
{isEn ? "This is the chip diagram, not the development board" : "Este é o diagrama do chip, não da placa"}
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
O mapa mostra as conexões do silício do <strong>{key}</strong>. Cada placa de desenvolvimento
|
||||
expõe só parte delas, ou as roteia de outra forma.
|
||||
{isEn ? (
|
||||
`The map shows the silicon connections for the ${key}. Each development board exposes only a subset or routes them differently.`
|
||||
) : (
|
||||
<>
|
||||
O mapa mostra as conexões do silício do <strong>{key}</strong>. Cada placa de desenvolvimento
|
||||
expõe só parte delas, ou as roteia de outra forma.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,22 +293,41 @@ 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" />
|
||||
Quase todo pino é reconfigurável
|
||||
{isEn ? "Almost every pin is reconfigurable" : "Quase todo pino é reconfigurável"}
|
||||
</p>
|
||||
<p className="mt-1">
|
||||
A matriz de IO do {key} permite mover SPI, I2C, UART, PWM e barramentos de câmera ou display
|
||||
para a maioria dos pinos digitais. Para as restrições de strapping e dos pinos analógicos,
|
||||
consulte o{" "}
|
||||
<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"
|
||||
>
|
||||
guia oficial de GPIO
|
||||
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
.
|
||||
{isEn ? (
|
||||
<>
|
||||
The {key} IO matrix allows moving SPI, I2C, UART, PWM, and camera/display buses
|
||||
to most digital pins. For strapping restrictions and analog pins, consult the{" "}
|
||||
<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"
|
||||
>
|
||||
official GPIO guide
|
||||
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
A matriz de IO do {key} permite mover SPI, I2C, UART, PWM e barramentos de câmera ou display
|
||||
para a maioria dos pinos digitais. Para as restrições de strapping e dos pinos analógicos,
|
||||
consulte o{" "}
|
||||
<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"
|
||||
>
|
||||
guia oficial de GPIO
|
||||
<ExternalLink className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</a>
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,40 +336,42 @@ export default async function SerieDetail({ params }) {
|
||||
];
|
||||
|
||||
const secoes = [
|
||||
{ id: "processador", titulo: "Processador", icon: Cpu, specs: processadorSpecs },
|
||||
{ id: "conectividade", titulo: "Conectividade", icon: Wifi, specs: conectividadeSpecs },
|
||||
{ id: "ferramentas", titulo: "Ferramentas e disponibilidade", icon: Package, specs: ferramentasSpecs },
|
||||
{ id: "seguranca", titulo: "Segurança", icon: ShieldCheck, specs: segurancaSpecs },
|
||||
{ id: "memoria", titulo: "Memória", icon: Database, specs: memoriaSpecs },
|
||||
{ id: "processador", titulo: isEn ? "Processor" : "Processador", icon: Cpu, specs: processadorSpecs },
|
||||
{ id: "conectividade", titulo: isEn ? "Connectivity" : "Conectividade", icon: Wifi, specs: conectividadeSpecs },
|
||||
{ id: "ferramentas", titulo: isEn ? "Tools and availability" : "Ferramentas e disponibilidade", icon: Package, specs: ferramentasSpecs },
|
||||
{ id: "seguranca", titulo: isEn ? "Security" : "Segurança", icon: ShieldCheck, specs: segurancaSpecs },
|
||||
{ id: "memoria", titulo: isEn ? "Memory" : "Memória", icon: Database, specs: memoriaSpecs },
|
||||
...(consumoEnergia.length > 0
|
||||
? [{
|
||||
id: "energia",
|
||||
titulo: "Consumo de energia",
|
||||
icon: Battery,
|
||||
specs: consumoEnergia,
|
||||
nota: "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: "energia",
|
||||
titulo: isEn ? "Power consumption" : "Consumo de energia",
|
||||
icon: Battery,
|
||||
specs: consumoEnergia,
|
||||
nota: isEn
|
||||
? "Typical values at 3.3 V and 25 °C. Actual power consumption depends on radio duty cycle and configured clock frequency."
|
||||
: "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: "Periféricos", icon: Sliders, specs: perifericos },
|
||||
{ id: "perifericos", titulo: isEn ? "Peripherals" : "Periféricos", icon: Sliders, specs: perifericos },
|
||||
...(interfacesEspeciais.length > 0
|
||||
? [{ id: "interfaces", titulo: "Interfaces especiais", icon: Sliders, specs: interfacesEspeciais }]
|
||||
? [{ id: "interfaces", titulo: isEn ? "Special interfaces" : "Interfaces especiais", icon: Sliders, specs: interfacesEspeciais }]
|
||||
: []),
|
||||
{
|
||||
id: "temperatura",
|
||||
titulo: "Limites de temperatura",
|
||||
titulo: isEn ? "Temperature limits" : "Limites de temperatura",
|
||||
icon: Thermometer,
|
||||
specs: [
|
||||
{ label: "Operação", value: serie.temperatura_operacao },
|
||||
{ label: "Armazenamento", value: serie.temperatura_armazenamento },
|
||||
{ label: isEn ? "Operating" : "Operação", value: serie.temperatura_operacao },
|
||||
{ label: isEn ? "Storage" : "Armazenamento", value: serie.temperatura_armazenamento },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const fatos = [
|
||||
{ label: "Processador", value: String(serie.nucleos).includes("2") ? "Dual-core" : "Single-core", detail: serie.frequencia },
|
||||
{ label: "SRAM", value: serie.memoria_sram, detail: `+ ${serie.memoria_sram_rtc} de SRAM RTC` },
|
||||
{ label: "GPIO", value: serie.gpio, detail: "pinos programáveis" },
|
||||
{ label: "Arquitetura", value: String(serie.arquitetura).includes("RISC-V") ? "RISC-V" : "Xtensa", detail: serie.arquitetura },
|
||||
{ label: isEn ? "Processor" : "Processador", value: String(serie.nucleos).includes("2") ? "Dual-core" : "Single-core", detail: serie.frequencia },
|
||||
{ label: "SRAM", value: serie.memoria_sram, detail: isEn ? `+ ${serie.memoria_sram_rtc} RTC SRAM` : `+ ${serie.memoria_sram_rtc} de SRAM RTC` },
|
||||
{ label: "GPIO", value: serie.gpio, detail: isEn ? "programmable pins" : "pinos programáveis" },
|
||||
{ label: isEn ? "Architecture" : "Arquitetura", value: String(serie.arquitetura).includes("RISC-V") ? "RISC-V" : "Xtensa", detail: serie.arquitetura },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -263,9 +384,9 @@ export default async function SerieDetail({ params }) {
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(jsonLd.trilha([
|
||||
{ nome: "Início", caminho: "/" },
|
||||
{ nome: "Séries", caminho: "/series" },
|
||||
{ nome: serie.nome_completo, caminho: `/series/${key}` },
|
||||
{ nome: isEn ? "Home" : "Início", caminho: isEn ? "/en" : "/" },
|
||||
{ nome: isEn ? "Series" : "Séries", caminho: getLocalizedHref("/series", currentLocale) },
|
||||
{ nome: serie.nome_completo, caminho: getLocalizedHref(`/series/${key}`, currentLocale) },
|
||||
])),
|
||||
}}
|
||||
/>
|
||||
@@ -274,7 +395,7 @@ export default async function SerieDetail({ params }) {
|
||||
|
||||
<main id="conteudo">
|
||||
<PageHero
|
||||
trail={[{ label: "Séries", href: "/series" }, { label: key }]}
|
||||
trail={[{ label: isEn ? "Series" : "Séries", href: getLocalizedHref("/series", currentLocale) }, { label: key }]}
|
||||
icon={Cpu}
|
||||
label={serie.arquitetura}
|
||||
title={key}
|
||||
@@ -283,32 +404,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" />
|
||||
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" />
|
||||
Datasheet ainda não 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" />
|
||||
Guia de programação
|
||||
{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" />
|
||||
Manual técnico
|
||||
{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="Resumo">
|
||||
<section className="shell mt-10" aria-label={isEn ? "Summary" : "Resumo"}>
|
||||
<dl className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
||||
{fatos.map((fato) => (
|
||||
<div key={fato.label} className="tile">
|
||||
@@ -323,7 +444,9 @@ export default async function SerieDetail({ params }) {
|
||||
<div className="callout callout-warn mt-4 flex items-start gap-3">
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" />
|
||||
<div>
|
||||
<p className="font-semibold text-ink">Documentação preliminar</p>
|
||||
<p className="font-semibold text-ink">
|
||||
{isEn ? "Preliminary documentation" : "Documentação preliminar"}
|
||||
</p>
|
||||
<p className="mt-1">{serie.status_documentacao}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -332,12 +455,12 @@ export default async function SerieDetail({ params }) {
|
||||
|
||||
<div className="shell mt-10">
|
||||
<SeriesTabMenu tabs={tabs} />
|
||||
<PinRestrictions conexoes={conexoes} serieKey={key} />
|
||||
<PinRestrictions conexoes={conexoes} serieKey={key} isEn={isEn} />
|
||||
</div>
|
||||
|
||||
<div className="shell mt-12">
|
||||
<nav
|
||||
aria-label="Seções da ficha"
|
||||
aria-label={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) => (
|
||||
@@ -357,37 +480,45 @@ export default async function SerieDetail({ params }) {
|
||||
<section className="shell section">
|
||||
<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">Pronto para prototipar o {key}?</h2>
|
||||
<h2 className="section-title">
|
||||
{isEn ? (
|
||||
<>Ready to prototype with {key}?</>
|
||||
) : (
|
||||
<>Pronto para prototipar o {key}?</>
|
||||
)}
|
||||
</h2>
|
||||
<p className="mt-3 text-base text-muted">
|
||||
Veja as placas de desenvolvimento disponíveis para a série, com USB, sensores integrados e link para a loja oficial.
|
||||
{isEn
|
||||
? "Explore development boards available for this series, with USB, onboard sensors, and official store links."
|
||||
: "Veja as placas de desenvolvimento disponíveis para a série, com USB, sensores integrados e link para a loja oficial."}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/catalogo" className="btn btn-primary shrink-0">
|
||||
Ver placas
|
||||
<Link href={getLocalizedHref("/catalogo", currentLocale)} className="btn btn-primary shrink-0">
|
||||
{isEn ? "View boards" : "Ver placas"}
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<nav aria-label="Outras séries" className="mt-6 grid gap-3 sm:grid-cols-[1fr_auto_1fr] sm:items-center">
|
||||
<nav aria-label={isEn ? "Other series" : "Outras séries"} className="mt-6 grid gap-3 sm:grid-cols-[1fr_auto_1fr] sm:items-center">
|
||||
{previousKey ? (
|
||||
<Link href={`/series/${previousKey}`} 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" />
|
||||
<span>
|
||||
<span className="block text-xs text-muted">Série anterior</span>
|
||||
<span className="block text-xs text-muted">{isEn ? "Previous series" : "Série anterior"}</span>
|
||||
<span className="block font-display text-lg font-semibold text-ink">{previousKey}</span>
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span className="hidden sm:block" />
|
||||
)}
|
||||
<Link href="/comparacao" 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" />
|
||||
Comparar séries
|
||||
{isEn ? "Compare series" : "Comparar séries"}
|
||||
</Link>
|
||||
{nextKey ? (
|
||||
<Link href={`/series/${nextKey}`} 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 className="block text-xs text-muted">Próxima série</span>
|
||||
<span className="block text-xs text-muted">{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" />
|
||||
@@ -416,7 +547,7 @@ function SpecSection({ id, titulo, icon: Icon, specs, nota }) {
|
||||
{nota && <p className="mt-3 text-sm text-muted">{nota}</p>}
|
||||
<dl className="mt-5 grid grid-cols-2 gap-3 lg:grid-cols-3">
|
||||
{specs.map((spec) => {
|
||||
const negativo = /^n[ãa]o\b/i.test(String(spec.value));
|
||||
const negativo = /^n[ãa]o\b|^no\b/i.test(String(spec.value));
|
||||
return (
|
||||
<div key={spec.label} className="tile">
|
||||
<dt className="text-xs font-medium text-muted">{spec.label}</dt>
|
||||
@@ -430,49 +561,58 @@ function SpecSection({ id, titulo, icon: Icon, specs, nota }) {
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { key } = await params;
|
||||
const { locale, key } = await params;
|
||||
const isEn = locale === "en";
|
||||
const currentLocale = isEn ? "en" : "pt";
|
||||
const seriesData = isEn ? seriesDataEn : seriesDataPt;
|
||||
const serie = seriesData[key];
|
||||
|
||||
if (!serie) {
|
||||
return { title: "Série não encontrada", robots: { index: false, follow: false } };
|
||||
return { title: isEn ? "Series not found" : "Série não encontrada", robots: { index: false, follow: false } };
|
||||
}
|
||||
|
||||
// A descrição usa as especificações reais em vez de texto genérico: é o que
|
||||
// aparece no resultado de busca e o que faz alguém clicar.
|
||||
const radio = [
|
||||
String(serie.wifi).startsWith("Não") ? null : serie.wifi.split(" (")[0],
|
||||
String(serie.bluetooth).startsWith("Não") ? null : serie.bluetooth.split(" (")[0],
|
||||
serie.zigbee_thread && !String(serie.zigbee_thread).startsWith("Não") ? "802.15.4" : null,
|
||||
String(serie.wifi).startsWith("Não") || String(serie.wifi).startsWith("No") ? null : serie.wifi.split(" (")[0],
|
||||
String(serie.bluetooth).startsWith("Não") || String(serie.bluetooth).startsWith("No") ? null : serie.bluetooth.split(" (")[0],
|
||||
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 =
|
||||
`${serie.nome_completo}: ${serie.arquitetura}, ${serie.frequencia.replace("Até ", "até ")}, ` +
|
||||
`${serie.memoria_sram} de SRAM e ${serie.gpio} GPIOs` +
|
||||
(radio ? `. Rádio: ${radio}` : "") +
|
||||
`. Pinagem completa, periféricos, segurança em hardware e pinos com restrição, em português.`;
|
||||
const descricao = isEn
|
||||
? `${serie.nome_completo}: ${serie.arquitetura}, ${serie.frequencia}, ` +
|
||||
`${serie.memoria_sram} SRAM, and ${serie.gpio} GPIOs` +
|
||||
(radio ? `. Radio: ${radio}` : "") +
|
||||
`. Complete pinout, peripherals, hardware security, and restricted pins in English.`
|
||||
: `${serie.nome_completo}: ${serie.arquitetura}, ${serie.frequencia.replace("Até ", "até ")}, ` +
|
||||
`${serie.memoria_sram} de SRAM e ${serie.gpio} GPIOs` +
|
||||
(radio ? `. Rádio: ${radio}` : "") +
|
||||
`. Pinagem completa, periféricos, segurança em hardware e pinos com restrição, em português.`;
|
||||
|
||||
// A pinagem da placa é a melhor prévia possível para uma série: quem
|
||||
// compartilha o link já mostra o diagrama. Séries sem placa publicada caem
|
||||
// na imagem padrão do site.
|
||||
// Definir openGraph explicitamente impede a herança automática da imagem do
|
||||
// site, então séries sem placa publicada (o H4) precisam do fallback aqui.
|
||||
const imagem = serie.placa
|
||||
? [{ url: serie.placa, width: 1600, height: 1091, alt: `Pinagem do ${serie.nome_completo}` }]
|
||||
? [{ url: serie.placa, width: 1600, height: 1091, alt: isEn ? `Pinout of ${serie.nome_completo}` : `Pinagem do ${serie.nome_completo}` }]
|
||||
: [{ url: "/opengraph-image.png", width: 1200, height: 630, alt: "ESPDocs" }];
|
||||
|
||||
const meta = {
|
||||
...paginaMeta({
|
||||
titulo: `${serie.nome_completo}: especificações e pinagem`,
|
||||
descricao,
|
||||
caminho: `/series/${key}`,
|
||||
tipo: "article",
|
||||
keywords: [
|
||||
const keywords = isEn
|
||||
? [
|
||||
serie.nome_completo, `${serie.nome_completo} pinout`,
|
||||
`${serie.nome_completo} datasheet`, `${serie.nome_completo} specifications`,
|
||||
"ESP32", "Espressif", "microcontroller",
|
||||
]
|
||||
: [
|
||||
serie.nome_completo, `${serie.nome_completo} pinout`,
|
||||
`${serie.nome_completo} datasheet`, `${serie.nome_completo} especificações`,
|
||||
`${serie.nome_completo} português`, "ESP32", "Espressif", "microcontrolador",
|
||||
],
|
||||
];
|
||||
|
||||
const meta = {
|
||||
...paginaMeta({
|
||||
titulo: isEn ? `${serie.nome_completo}: specifications and pinout` : `${serie.nome_completo}: especificações e pinagem`,
|
||||
descricao,
|
||||
caminho: `/series/${key}`,
|
||||
tipo: "article",
|
||||
locale: currentLocale,
|
||||
keywords,
|
||||
}),
|
||||
other: { "article:section": "Séries ESP32" },
|
||||
other: { "article:section": isEn ? "ESP32 Series" : "Séries ESP32" },
|
||||
};
|
||||
|
||||
meta.openGraph = { ...meta.openGraph, images: imagem };
|
||||
@@ -482,7 +622,7 @@ export async function generateMetadata({ params }) {
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return Object.keys(seriesData).map((key) => ({
|
||||
return Object.keys(seriesDataPt).map((key) => ({
|
||||
key: key,
|
||||
}));
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import Link from "next/link";
|
||||
import seriesData from "@/public/series.json";
|
||||
import seriesDataPt from "@/public/series.json";
|
||||
import seriesDataEn from "@/data/en/series.json";
|
||||
import { getLocalizedHref } from "@/i18n/links";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ArrowRight,
|
||||
@@ -17,7 +20,7 @@ import {
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
|
||||
const FILTROS = [
|
||||
const FILTROS_PT = [
|
||||
{ id: "todas", rotulo: "Todas", 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") },
|
||||
@@ -25,22 +28,41 @@ const FILTROS = [
|
||||
{ id: "ia", rotulo: "Aceleração de IA", testa: (s) => Boolean(s.aceleradores_ia) },
|
||||
];
|
||||
|
||||
const FILTROS_EN = [
|
||||
{ id: "todas", rotulo: "All", 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 === "Yes" || s.matter === "Sim" },
|
||||
{ id: "ia", rotulo: "AI Acceleration", 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) {
|
||||
function nivelSeguranca(serie, isEn) {
|
||||
const seg = serie.seguranca || {};
|
||||
if (seg.key_manager === "Sim") return "Key Manager";
|
||||
if (String(seg.ecdsa).startsWith("Sim")) return "ECDSA em hardware";
|
||||
if (String(seg.ecc).startsWith("Sim")) return "ECC em hardware";
|
||||
if (seg.key_manager === "Sim" || seg.key_manager === "Yes") return "Key Manager";
|
||||
if (String(seg.ecdsa).startsWith("Sim") || String(seg.ecdsa).startsWith("Yes")) {
|
||||
return isEn ? "Hardware ECDSA" : "ECDSA em hardware";
|
||||
}
|
||||
if (String(seg.ecc).startsWith("Sim") || String(seg.ecc).startsWith("Yes")) {
|
||||
return isEn ? "Hardware ECC" : "ECC em hardware";
|
||||
}
|
||||
if (String(seg.aes).startsWith("AES")) return "AES + SHA";
|
||||
return "Só SHA";
|
||||
return isEn ? "SHA only" : "Só SHA";
|
||||
}
|
||||
|
||||
const sim = (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");
|
||||
|
||||
export default function Series() {
|
||||
const pathname = usePathname();
|
||||
const isEn = pathname?.startsWith("/en");
|
||||
const currentLocale = isEn ? "en" : "pt";
|
||||
const seriesData = isEn ? seriesDataEn : seriesDataPt;
|
||||
const filtros = isEn ? FILTROS_EN : FILTROS_PT;
|
||||
|
||||
const [filtro, setFiltro] = useState("todas");
|
||||
const todas = Object.entries(seriesData);
|
||||
const ativo = FILTROS.find((f) => f.id === filtro);
|
||||
const ativo = filtros.find((f) => f.id === filtro) || filtros[0];
|
||||
const series = useMemo(() => todas.filter(([, s]) => ativo.testa(s)), [todas, ativo]);
|
||||
|
||||
return (
|
||||
@@ -49,40 +71,53 @@ export default function Series() {
|
||||
|
||||
<main id="conteudo">
|
||||
<PageHero
|
||||
trail={[{ label: "Séries" }]}
|
||||
trail={[{ label: isEn ? "Series" : "Séries" }]}
|
||||
icon={Compass}
|
||||
label={`${todas.length} famílias da Espressif`}
|
||||
title="Séries ESP32"
|
||||
lede="Cada série foi desenhada para um tipo de projeto, do sensor a bateria ao acelerador de IA. Abra a ficha para ver pinagem, periféricos e segurança."
|
||||
label={isEn ? `${todas.length} Espressif families` : `${todas.length} famílias da Espressif`}
|
||||
title={isEn ? "ESP32 Series" : "Séries ESP32"}
|
||||
lede={
|
||||
isEn
|
||||
? "Each series was designed for a specific project type, from battery-powered sensors to AI accelerators. Open the sheet to see pinout, peripherals, and security."
|
||||
: "Cada série foi desenhada para um tipo de projeto, do sensor a bateria ao acelerador de IA. Abra a ficha para ver pinagem, periféricos e segurança."
|
||||
}
|
||||
/>
|
||||
|
||||
<section className="shell pt-10 sm:pt-12" aria-label="Lista de séries">
|
||||
<div role="group" aria-label="Filtrar séries" className="flex flex-wrap gap-2">
|
||||
{FILTROS.map((f) => (
|
||||
<section className="shell pt-10 sm:pt-12" aria-label={isEn ? "Series list" : "Lista de séries"}>
|
||||
<div role="group" aria-label={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}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<p role="status" aria-live="polite" className="mt-4 text-sm text-muted">
|
||||
{series.length} {series.length === 1 ? "série" : "séries"}
|
||||
{series.length} {series.length === 1 ? (isEn ? "series" : "série") : (isEn ? "series" : "séries")}
|
||||
</p>
|
||||
|
||||
<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 = [
|
||||
["Núcleos", serie.nucleos],
|
||||
["Frequência", serie.frequencia],
|
||||
["SRAM", serie.memoria_sram],
|
||||
["Wi-Fi", sim(serie.wifi) ? "Sim" : "Não"],
|
||||
["Bluetooth", serie.bluetooth === "Não" ? "Não" : "Sim"],
|
||||
serie.consumo_energia?.deep_sleep ? ["Deep sleep", serie.consumo_energia.deep_sleep] : null,
|
||||
].filter(Boolean);
|
||||
const fatos = isEn
|
||||
? [
|
||||
["Cores", serie.nucleos],
|
||||
["Frequency", serie.frequencia],
|
||||
["SRAM", serie.memoria_sram],
|
||||
["Wi-Fi", simEn(serie.wifi) ? "Yes" : "No"],
|
||||
["Bluetooth", String(serie.bluetooth).includes("No") || String(serie.bluetooth).includes("Não") ? "No" : "Yes"],
|
||||
serie.consumo_energia?.deep_sleep ? ["Deep sleep", serie.consumo_energia.deep_sleep] : null,
|
||||
].filter(Boolean)
|
||||
: [
|
||||
["Núcleos", serie.nucleos],
|
||||
["Frequência", serie.frequencia],
|
||||
["SRAM", serie.memoria_sram],
|
||||
["Wi-Fi", simPt(serie.wifi) ? "Sim" : "Não"],
|
||||
["Bluetooth", serie.bluetooth === "Não" ? "Não" : "Sim"],
|
||||
serie.consumo_energia?.deep_sleep ? ["Deep sleep", serie.consumo_energia.deep_sleep] : null,
|
||||
].filter(Boolean);
|
||||
return (
|
||||
<Link key={key} href={`/series/${key}`} className="card card-interactive group flex flex-col p-5 sm:p-6">
|
||||
<Link key={key} href={getLocalizedHref(`/series/${key}`, currentLocale)} className="card card-interactive group flex flex-col p-5 sm:p-6">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<h2 className="font-display text-2xl font-semibold tracking-[-0.035em] text-ink">{key}</h2>
|
||||
<h2 className="font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{key}</h2>
|
||||
<span className="chip">{riscv ? "RISC-V" : "Xtensa"}</span>
|
||||
</div>
|
||||
<p className="mt-2 line-clamp-2 min-h-[2.9rem] text-sm leading-relaxed text-muted">{serie.descricao}</p>
|
||||
@@ -91,7 +126,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" ? "text-muted" : "text-ink"}`}>{valor}</dd>
|
||||
<dd className={`text-right font-medium ${valor === "Não" || valor === "No" ? "text-muted" : "text-ink"}`}>{valor}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
@@ -100,14 +135,14 @@ export default function Series() {
|
||||
{serie.aceleradores_ia && (
|
||||
<li className="chip chip-accent">
|
||||
<Sparkles className="h-3 w-3" aria-hidden="true" />
|
||||
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)}
|
||||
{nivelSeguranca(serie, isEn)}
|
||||
</li>
|
||||
{serie.matter === "Sim" && (
|
||||
{(serie.matter === "Sim" || serie.matter === "Yes") && (
|
||||
<li className="chip">
|
||||
<Home className="h-3 w-3 text-accent" aria-hidden="true" />
|
||||
Matter
|
||||
@@ -116,13 +151,13 @@ export default function Series() {
|
||||
{serie.status_documentacao && (
|
||||
<li className="chip">
|
||||
<AlertTriangle className="h-3 w-3 text-gold" aria-hidden="true" />
|
||||
Documentação 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">
|
||||
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>
|
||||
@@ -134,14 +169,18 @@ export default function Series() {
|
||||
<section className="shell section">
|
||||
<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">Ficou em dúvida entre duas séries?</h2>
|
||||
<h2 className="section-title">
|
||||
{isEn ? "Unsure between two series?" : "Ficou em dúvida entre duas séries?"}
|
||||
</h2>
|
||||
<p className="mt-3 text-base text-muted">
|
||||
O comparador coloca até quatro modelos lado a lado: núcleos, rádio, memória, periféricos e segurança.
|
||||
{isEn
|
||||
? "The comparator tool puts up to four models side by side: cores, radio, memory, peripherals, and security."
|
||||
: "O comparador coloca até quatro modelos lado a lado: núcleos, rádio, memória, periféricos e segurança."}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/comparacao" 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" />
|
||||
Comparar séries
|
||||
{isEn ? "Compare series" : "Comparar séries"}
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user