feat(i18n): Spanish localization (phase 4)
CI / Lint e auditorias (push) Successful in 1m31s
CI / Trivy (dependências e segredos) (push) Successful in 10s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m23s

Complete implementation of Spanish (es-419) language support:
- Canonical catalogs and data: messages/es.json, data/es/**, lib/data/es/**, public/exemplos/es/**
- Canonical slugs: /es/series, /es/catalogo, /es/comparacion, /es/componentes, /es/diagnostico, /es/frameworks, /es/glosario, /es/selector, /es/acerca
- Dedicated search index at /api/busca/es with Spanish accent normalization
- Strict exclusion of book references in Spanish routes and catalogs
- Language selector and root negotiation with es active (ENABLED_LOCALES = ['pt', 'en', 'es'])
- Extended verification suite: check-i18n, smoke-imagem and verifica-vazamentos

Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
Lucas RayanandAntigravity committed 2026-11-01 19:50:20 -03:00
1 parent 0940814522
commit 660b28c09d
72 files changed
+17848 -947

No files matched your search

+47 -9
View File
@@ -3,16 +3,32 @@ import { paginaMeta, jsonLd } from "@/lib/seo";
export async function generateMetadata({ params }) {
const { locale } = await params;
const isEn = locale === "en";
const isEs = locale === "es";
return paginaMeta({
titulo: isEn
titulo: isEs
? "Diagnóstico de errores comunes en ESP32: Guía profesional"
: isEn
? "Common ESP32 Errors & Diagnostics: Professional Guide"
: "Diagnóstico de Erros Comuns no ESP32: Guia Profissional",
descricao: isEn
descricao: isEs
? "Guía técnica definitiva para identificar y solucionar Brownout, Guru Meditation, Task Watchdog, Stack Overflow, fugas de memoria y fallas de arranque en ESP32 (Arduino y ESP-IDF)."
: isEn
? "Definitive technical guide to identify and fix Brownout, Guru Meditation, Task Watchdog, Stack Overflow, memory leaks, and boot issues on ESP32 (Arduino & ESP-IDF)."
: "Guia técnico definitivo para identificar e resolver Brownout, Guru Meditation, Task Watchdog, Stack Overflow, vazamento de memória e falhas de boot no ESP32 (Arduino e ESP-IDF).",
caminho: "/diagnostico",
locale,
keywords: isEn
keywords: isEs
? [
"errores comunes ESP32",
"brownout detector was triggered",
"guru meditation error",
"task watchdog got triggered",
"stack canary watchpoint triggered",
"cómo depurar ESP32",
"ESP32 reiniciando en bucle",
"failed to connect to esp32",
]
: isEn
? [
"common ESP32 errors",
"brownout detector was triggered",
@@ -74,10 +90,30 @@ const faqsEn = [
},
];
const faqsEs = [
{
pergunta: "¿Qué causa el error 'Brownout detector was triggered' en el ESP32?",
resposta: "Ocurre cuando la tensión en el pin 3V3 cae por debajo de 2.8V ~ 2.43V debido a picos rápidos de corriente de la radio Wi-Fi/BLE (que superan los 500mA), cables USB delgados o un regulador LDO inadecuado. La solución de ingeniería implica agregar condensadores Low-ESR de 10µF a 100µF cerca de VCC y usar reguladores LDO robustos de más de 600mA.",
},
{
pergunta: "¿Cómo solucionar el 'Guru Meditation Error (LoadProhibited)'?",
resposta: "LoadProhibited indica un intento de acceder a un puntero nulo (NULL) o inválido en la memoria. La solución consiste en utilizar Espressif Exception Decoder o addr2line para localizar la línea exacta en el código e implementar comprobaciones defensivas como if (puntero != NULL).",
},
{
pergunta: "¿Por qué se activa el Task Watchdog Timer (TWDT) en FreeRTOS?",
resposta: "El TWDT se activa cuando una tarea de mayor prioridad monopoliza la CPU en un bucle bloqueante (por ejemplo: while(1) sin vTaskDelay), impidiendo que la tarea IDLE alimente el watchdog del sistema operativo.",
},
{
pergunta: "¿Por qué fallan las conexiones HTTPS y TLS con el código -0x2700 en ESP32?",
resposta: "Porque al encenderse, el reloj interno del ESP32 comienza en el año 1970. El validador de certificados TLS considera inválido el certificado del servidor por estar en el 'futuro'. Sincronizar el reloj mediante SNTP (configTime) antes de la conexión resuelve el problema.",
},
];
export default async function DiagnosticoLayout({ children, params }) {
const { locale } = await params;
const isEn = locale === "en";
const faqs = isEn ? faqsEn : faqsPt;
const isEs = locale === "es";
const faqs = isEs ? faqsEs : isEn ? faqsEn : faqsPt;
return (
<>
@@ -86,8 +122,8 @@ export default async function DiagnosticoLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.trilha([
{ nome: isEn ? "Home" : "Início", caminho: isEn ? "/en" : "/" },
{ nome: isEn ? "Diagnostics & Common Errors" : "Diagnóstico & Erros Comuns", caminho: isEn ? "/en/diagnostics" : "/diagnostico" },
{ nome: isEs ? "Inicio" : isEn ? "Home" : "Início", caminho: isEs ? "/es" : isEn ? "/en" : "/" },
{ nome: isEs ? "Diagnóstico y errores comunes" : isEn ? "Diagnostics & Common Errors" : "Diagnóstico & Erros Comuns", caminho: isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : "/diagnostico" },
])
),
}}
@@ -105,11 +141,13 @@ export default async function DiagnosticoLayout({ children, params }) {
dangerouslySetInnerHTML={{
__html: JSON.stringify(
jsonLd.aplicacaoWeb({
nome: isEn ? "ESP32 Error Diagnostics & Debugging Guide" : "Guia de Diagnóstico e Erros no ESP32",
descricao: isEn
nome: isEs ? "Guía de diagnóstico y errores en ESP32" : isEn ? "ESP32 Error Diagnostics & Debugging Guide" : "Guia de Diagnóstico e Erros no ESP32",
descricao: isEs
? "Herramienta y guía técnica de análisis de fallas, pánicos de CPU y decodificación de registros en ESP32."
: isEn
? "Technical guide and log decoder for troubleshooting CPU panics, crashes, and exceptions on ESP32."
: "Ferramenta e guia técnico de análise de falhas, pânicos de CPU e decodificação de logs no ESP32.",
caminho: isEn ? "/en/diagnostics" : "/diagnostico",
caminho: isEs ? "/es/diagnostico" : isEn ? "/en/diagnostics" : "/diagnostico",
locale,
})
),
+90 -43
View File
@@ -7,6 +7,7 @@ import Footer from "@/components/Footer";
import PageHero from "@/components/PageHero";
import * as diagnosticoPt from "@/lib/diagnosticoData";
import * as diagnosticoEn from "@/lib/data/en/diagnostico";
import * as diagnosticoEs from "@/lib/data/es/diagnostico";
import {
Activity,
AlertTriangle,
@@ -49,6 +50,13 @@ const EXEMPLOS_LOG_EN = [
["TLS Handshake", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
];
const EXEMPLOS_LOG_ES = [
["Brownout", "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"],
["LoadProhibited", "Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"],
["Task watchdog", "Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"],
["Handshake TLS", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
];
const GRAVIDADE = {
"Crítica": "text-danger",
"Critical": "text-danger",
@@ -56,6 +64,7 @@ const GRAVIDADE = {
High: "text-gold",
"Média": "text-accent",
Medium: "text-accent",
Media: "text-accent",
};
const CHECKLISTS_PT = [
@@ -104,12 +113,36 @@ const CHECKLISTS_EN = [
},
];
const CHECKLISTS_ES = [
{
icon: Zap,
titulo: "Hardware y alimentación",
itens: [
"Condensador de 10 µF a 100 µF de bajo ESR en VCC, más uno cerámico de 100 nF junto al chip.",
"Regulador LDO dimensionado para picos de 600 mA a 1 A, con margen térmico.",
"Ningún periférico forzando niveles lógicos en los pines de strapping durante el arranque.",
"Condensador de 10 µF o 1 µF en el pin EN (RST) para auto-reset estable durante la programación.",
],
},
{
icon: Layers,
titulo: "Firmware y FreeRTOS",
itens: [
"Cada tarea de FreeRTOS cede la CPU con vTaskDelay, colas o semáforos.",
"Cada ISR marcada con IRAM_ATTR y sin llamadas bloqueantes.",
"Sin uso continuo de String de Arduino en bucles para evitar la fragmentación del heap.",
"Hora sincronizada mediante SNTP antes de abrir conexiones TLS/HTTPS.",
],
},
];
export default function DiagnosticoPage() {
const pathname = usePathname();
const isEn = pathname?.startsWith("/en");
const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEn ? diagnosticoEn : diagnosticoPt;
const exemplosLog = isEn ? EXEMPLOS_LOG_EN : EXEMPLOS_LOG_PT;
const checklists = isEn ? CHECKLISTS_EN : CHECKLISTS_PT;
const isEs = pathname?.startsWith("/es");
const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEs ? diagnosticoEs : isEn ? diagnosticoEn : diagnosticoPt;
const exemplosLog = isEs ? EXEMPLOS_LOG_ES : isEn ? EXEMPLOS_LOG_EN : EXEMPLOS_LOG_PT;
const checklists = isEs ? CHECKLISTS_ES : isEn ? CHECKLISTS_EN : CHECKLISTS_PT;
const [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
@@ -139,7 +172,16 @@ export default function DiagnosticoPage() {
}
setResultadoLog(
isEn
isEs
? {
titulo: "Patrón de error no categorizado automáticamente",
tipo: "Desconocido",
explicacao:
"No identificamos una firma clásica de Guru Meditation o Brownout en este fragmento. Verifique que la velocidad de baudios (ej: 115200) sea correcta y si el encabezado 'rst:' o 'panic' está presente.",
acaoRecomendada:
"Busque palabras clave como 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' o 'panic' en la consola serie.",
}
: isEn
? {
titulo: "Error pattern not automatically classified",
tipo: "Unknown",
@@ -213,12 +255,15 @@ export default function DiagnosticoPage() {
<main id="conteudo">
<PageHero
isEn={isEn}
trail={[{ label: isEn ? "Diagnostics" : "Diagnóstico" }]}
isEs={isEs}
trail={[{ label: isEs ? "Diagnóstico" : isEn ? "Diagnostics" : "Diagnóstico" }]}
icon={AlertTriangle}
label={isEn ? "Firmware Debugging" : "Depuração de firmware"}
title={isEn ? "ESP32 Diagnostics and Crash Decoder" : "Diagnóstico de falhas do ESP32"}
label={isEs ? "Depuración de firmware" : isEn ? "Firmware Debugging" : "Depuração de firmware"}
title={isEs ? "Diagnóstico de fallas del ESP32" : isEn ? "ESP32 Diagnostics and Crash Decoder" : "Diagnóstico de falhas do ESP32"}
lede={
isEn
isEs
? "Comprenda la causa raíz de las fallas por Brownout, Guru Meditation, Watchdog y Stack Overflow, y aplique la corrección de ingeniería en lugar de soluciones improvisadas."
: isEn
? "Understand the root cause of Brownout, Guru Meditation, Watchdog, and Stack Overflow crashes, and apply engineering solutions instead of workarounds."
: "Entenda a causa de Brownout, Guru Meditation, Watchdog e Stack Overflow, e aplique a correção de engenharia, não o remendo."
}
@@ -229,9 +274,9 @@ export default function DiagnosticoPage() {
rel="noopener noreferrer"
className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong"
>
{isEn ? "Based on article by Ciência Embarcada" : "Baseado no artigo do Ciência Embarcada"}
{isEs ? "Basado en el artículo de Ciência Embarcada" : isEn ? "Based on article by Ciência Embarcada" : "Baseado no artigo do Ciência Embarcada"}
<ExternalLink className="h-4 w-4" aria-hidden="true" />
<span className="sr-only"> {isEn ? "(opens in a new tab)" : "(abre em nova aba)"}</span>
<span className="sr-only"> {isEs ? "(abre en una nueva pestaña)" : isEn ? "(opens in a new tab)" : "(abre em nova aba)"}</span>
</a>
</PageHero>
@@ -240,14 +285,14 @@ export default function DiagnosticoPage() {
<div className="flex flex-col justify-between gap-4 md:flex-row md:items-end">
<div className="max-w-xl">
<h2 id="decodificador-titulo" className="section-title">
{isEn ? "Paste the log to see the cause" : "Cole o log e veja a causa"}
{isEs ? "Pegue el registro y descubra la causa" : isEn ? "Paste the log to see the cause" : "Cole o log e veja a causa"}
</h2>
<p className="mt-2 text-sm text-muted">
{isEn ? "Copy the serial monitor output snippet. Diagnostics appear instantly." : "Copie o trecho do monitor serial. O diagnóstico aparece na hora."}
{isEs ? "Copie el fragmento del monitor serie. El diagnóstico aparece al instante." : isEn ? "Copy the serial monitor output snippet. Diagnostics appear instantly." : "Copie o trecho do monitor serial. O diagnóstico aparece na hora."}
</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm text-muted">{isEn ? "Test with" : "Testar com"}</span>
<span className="text-sm text-muted">{isEs ? "Probar con" : isEn ? "Test with" : "Testar com"}</span>
{exemplosLog.map(([rotulo, texto]) => (
<button key={rotulo} type="button" onClick={() => analisarLog(texto)} className="chip-btn">
{rotulo}
@@ -258,21 +303,21 @@ export default function DiagnosticoPage() {
<div className="relative mt-5">
<label htmlFor="log-serial" className="sr-only">
{isEn ? "Serial monitor log" : "Log do monitor serial"}
{isEs ? "Registro del monitor serie" : isEn ? "Serial monitor log" : "Log do monitor serial"}
</label>
<textarea
id="log-serial"
rows={4}
value={logInput}
onChange={(e) => analisarLog(e.target.value)}
placeholder={isEn ? "Paste serial monitor log here" : "Cole o log do monitor serial aqui"}
placeholder={isEs ? "Pegue el registro del monitor serie aquí" : isEn ? "Paste serial monitor log here" : "Cole o log do monitor serial aqui"}
spellCheck={false}
className="code-panel field !min-h-32 w-full resize-y !rounded-xl !border-white/10 !bg-[var(--code-bg)] p-4 font-mono text-sm !text-[#e9eef4] placeholder:!text-[#a6afbd]"
/>
{logInput && (
<button type="button" onClick={() => analisarLog("")} className="absolute right-3 top-3 inline-flex min-h-11 items-center gap-1 rounded-full border border-white/15 px-3 text-xs font-medium text-[#e9eef4] hover:bg-white/10">
<X className="h-3.5 w-3.5" aria-hidden="true" />
{isEn ? "Clear" : "Limpar"}
{isEs ? "Limpiar" : isEn ? "Clear" : "Limpar"}
</button>
)}
</div>
@@ -286,8 +331,8 @@ export default function DiagnosticoPage() {
<h3 className="font-semibold text-ink">{resultadoLog.titulo}</h3>
<span className="chip">{resultadoLog.tipo}</span>
</div>
<p><strong className="font-semibold text-ink">{isEn ? "Likely cause:" : "Causa provável:"}</strong> {resultadoLog.explicacao}</p>
<p><strong className="font-semibold text-ink">{isEn ? "What to do:" : "O que fazer:"}</strong> {resultadoLog.acaoRecomendada}</p>
<p><strong className="font-semibold text-ink">{isEs ? "Causa probable:" : isEn ? "Likely cause:" : "Causa provável:"}</strong> {resultadoLog.explicacao}</p>
<p><strong className="font-semibold text-ink">{isEs ? "Qué hacer:" : isEn ? "What to do:" : "O que fazer:"}</strong> {resultadoLog.acaoRecomendada}</p>
</div>
</div>
)}
@@ -297,13 +342,13 @@ export default function DiagnosticoPage() {
<section className="shell section pt-12 sm:pt-14" aria-labelledby="catalogo-titulo">
<h2 id="catalogo-titulo" className="section-title">
{isEn ? "Fault catalog" : "Catálogo de falhas"}
{isEs ? "Catálogo de fallas" : isEn ? "Fault catalog" : "Catálogo de falhas"}
</h2>
<div className="panel mt-6 p-5 sm:p-6">
<div className="relative">
<label htmlFor="busca-erro" className="sr-only">
{isEn ? "Search by error, symptom, or serial message" : "Buscar por erro, sintoma ou mensagem serial"}
{isEs ? "Buscar por error, síntoma o mensaje serie" : isEn ? "Search by error, symptom, or serial message" : "Buscar por erro, sintoma ou mensagem serial"}
</label>
<Search className="pointer-events-none absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-muted" aria-hidden="true" />
<input
@@ -312,14 +357,14 @@ export default function DiagnosticoPage() {
type="search"
value={termoBusca}
onChange={(e) => setTermoBusca(e.target.value)}
placeholder={isEn ? "Search error, symptom, or message" : "Buscar erro, sintoma ou mensagem"}
placeholder={isEs ? "Buscar error, síntoma o mensaje" : isEn ? "Search error, symptom, or message" : "Buscar erro, sintoma ou mensagem"}
autoComplete="off"
className="field pl-12 pr-4 text-base"
/>
</div>
<div className="mt-5">
<h3 className="text-sm font-semibold text-ink">{isEn ? "Symptom" : "Sintoma"}</h3>
<h3 className="text-sm font-semibold text-ink">{isEs ? "Síntoma" : isEn ? "Symptom" : "Sintoma"}</h3>
<div className="mt-3 flex flex-wrap gap-2">
{sintomasRapidos.map((sintoma) => (
<button key={sintoma.id} type="button" onClick={() => setSintomaAtivo(sintomaAtivo === sintoma.id ? null : sintoma.id)} aria-pressed={sintomaAtivo === sintoma.id} className="chip-btn">
@@ -330,11 +375,11 @@ export default function DiagnosticoPage() {
</div>
<div className="mt-5 border-t border-line pt-5">
<h3 className="text-sm font-semibold text-ink">{isEn ? "Area" : "Área"}</h3>
<h3 className="text-sm font-semibold text-ink">{isEs ? "Área" : isEn ? "Area" : "Área"}</h3>
<div className="mt-3 flex flex-wrap gap-2">
<button type="button" onClick={() => setCategoriaAtiva("todos")} aria-pressed={categoriaAtiva === "todos"} className="chip-btn">
<LayoutGrid className="h-4 w-4" aria-hidden="true" />
{isEn ? "All" : "Todas"}
{isEs ? "Todas" : isEn ? "All" : "Todas"}
<span className="tabular-nums opacity-80">{errosCatalogo.length}</span>
</button>
{categoriasDiagnostico.map((cat) => {
@@ -354,13 +399,13 @@ export default function DiagnosticoPage() {
<div className="mt-6 flex flex-wrap items-center justify-between gap-3">
<p role="status" aria-live="polite" className="text-sm font-semibold text-ink">
{errosFiltrados.length} {errosFiltrados.length === 1 ? (isEn ? "fault" : "falha") : (isEn ? "faults" : "falhas")}
{totalFiltros > 0 && <span className="font-normal text-muted">{isEn ? " with current filters" : " com os filtros atuais"}</span>}
{errosFiltrados.length} {errosFiltrados.length === 1 ? (isEs ? "falla" : isEn ? "fault" : "falha") : (isEs ? "fallas" : isEn ? "faults" : "falhas")}
{totalFiltros > 0 && <span className="font-normal text-muted">{isEs ? " con los filtros actuales" : isEn ? " with current filters" : " com os filtros atuais"}</span>}
</p>
{temFiltroAtivo && (
<button type="button" onClick={limparTodosFiltros} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
<RotateCcw className="h-4 w-4" aria-hidden="true" />
{isEn ? "Reset filters" : "Redefinir filtros"}
{isEs ? "Restablecer filtros" : isEn ? "Reset filters" : "Redefinir filtros"}
</button>
)}
</div>
@@ -370,13 +415,13 @@ export default function DiagnosticoPage() {
<div className="card px-6 py-14 text-center">
<HelpCircle className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<h3 className="mt-4 font-display text-xl font-semibold text-ink">
{isEn ? "No faults matching these filters" : "Nenhuma falha com esses filtros"}
{isEs ? "Ninguna falla coincide con estos filtros" : isEn ? "No faults matching these filters" : "Nenhuma falha com esses filtros"}
</h3>
<p className="mx-auto mt-2 max-w-md text-sm text-muted">
{isEn ? "Search for a more general term or remove selected symptom and area." : "Busque por um termo mais geral ou remova o sintoma e a área selecionados."}
{isEs ? "Busque por un término más general o elimine el síntoma y el área seleccionados." : isEn ? "Search for a more general term or remove selected symptom and area." : "Busque por um termo mais geral ou remova o sintoma e a área selecionados."}
</p>
<button type="button" onClick={limparTodosFiltros} className="btn btn-primary mt-6">
{isEn ? "Clear filters" : "Limpar filtros"}
{isEs ? "Limpiar filtros" : isEn ? "Clear filters" : "Limpar filtros"}
</button>
</div>
) : (
@@ -384,7 +429,7 @@ export default function DiagnosticoPage() {
const estaAberto = cardAberto === erro.id;
const lang = linguagemCodigo[erro.id] || "arduino";
const codigoAtual =
lang === "arduino" ? erro.codigoArduino : lang === "espidf" ? erro.codigoEspIdf : erro.codigoMicroPython || (isEn ? "# Example not applicable" : "# Exemplo não aplicável");
lang === "arduino" ? erro.codigoArduino : lang === "espidf" ? erro.codigoEspIdf : erro.codigoMicroPython || (isEs ? "# Ejemplo no aplicable" : isEn ? "# Example not applicable" : "# Exemplo não aplicável");
const catInfo = categoriasDiagnostico.find((c) => c.id === erro.categoria);
const IconeCat = ICONES[catInfo?.icone] ?? AlertTriangle;
@@ -405,7 +450,9 @@ export default function DiagnosticoPage() {
{catInfo?.nome}
</span>
<span className={`chip ${GRAVIDADE[erro.gravidade] ?? "text-accent"}`}>
{isEn ? (
{isEs ? (
<>Severidad {erro.gravidade.toLowerCase()}</>
) : isEn ? (
<>Severity: {erro.gravidade.toLowerCase()}</>
) : (
<>Gravidade {erro.gravidade.toLowerCase()}</>
@@ -427,11 +474,11 @@ export default function DiagnosticoPage() {
<div className="flex items-center justify-between gap-3">
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
<Terminal className="h-4 w-4 text-accent" aria-hidden="true" />
{isEn ? "How it appears in serial monitor" : "Como aparece no monitor serial"}
{isEs ? "Cómo aparece en el monitor serie" : isEn ? "How it appears in serial monitor" : "Como aparece no monitor serial"}
</h4>
<button type="button" onClick={() => copiarParaClipboard(erro.logSerial, `log-${erro.id}`)} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{copiadoId === `log-${erro.id}` ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
{copiadoId === `log-${erro.id}` ? (isEn ? "Copied" : "Copiado") : (isEn ? "Copy log" : "Copiar log")}
{copiadoId === `log-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : "Copiado") : (isEs ? "Copiar registro" : isEn ? "Copy log" : "Copiar log")}
</button>
</div>
<div className="code-panel mt-1">
@@ -442,7 +489,7 @@ export default function DiagnosticoPage() {
<div>
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
<Cpu className="h-4 w-4 text-accent" aria-hidden="true" />
{isEn ? "Root cause" : "Causa raiz"}
{isEs ? "Causa raíz" : isEn ? "Root cause" : "Causa raiz"}
</h4>
<p className="callout mt-2">{erro.causaRaiz}</p>
</div>
@@ -451,17 +498,17 @@ export default function DiagnosticoPage() {
<div className="callout callout-danger">
<p className="flex items-center gap-2 font-semibold text-ink">
<XCircle className="h-4 w-4 shrink-0 text-danger" aria-hidden="true" />
{isEn ? "The common workaround" : "O remendo comum"}
{isEs ? "La solución improvisada común" : isEn ? "The common workaround" : "O remendo comum"}
</p>
<p className="mt-2 font-medium text-ink">{erro.gambiarra.titulo}</p>
<p className="mt-1">{erro.gambiarra.descricao}</p>
<p className="mt-2"><strong className="font-semibold text-danger">{isEn ? "Consequence:" : "Consequência:"}</strong> {erro.gambiarra.consequencia}</p>
<p className="mt-2"><strong className="font-semibold text-danger">{isEs ? "Consecuencia:" : isEn ? "Consequence:" : "Consequência:"}</strong> {erro.gambiarra.consequencia}</p>
</div>
<div className="callout callout-ok">
<p className="flex items-center gap-2 font-semibold text-ink">
<CheckCircle2 className="h-4 w-4 shrink-0 text-ok" aria-hidden="true" />
{isEn ? "The engineering solution" : "A solução de engenharia"}
{isEs ? "La solución de ingeniería" : isEn ? "The engineering solution" : "A solução de engenharia"}
</p>
<p className="mt-2 font-medium text-ink">{erro.solucaoEngenharia.titulo}</p>
<ul className="mt-2 space-y-1.5">
@@ -477,7 +524,7 @@ export default function DiagnosticoPage() {
<div>
<div className="flex flex-wrap items-center justify-between gap-3">
<div role="group" aria-label={isEn ? "Example language" : "Linguagem do exemplo"} className="flex flex-wrap gap-2">
<div role="group" aria-label={isEs ? "Lenguaje del ejemplo" : isEn ? "Example language" : "Linguagem do exemplo"} className="flex flex-wrap gap-2">
{[
["arduino", "Arduino C++"],
["espidf", "ESP-IDF (C)"],
@@ -490,7 +537,7 @@ export default function DiagnosticoPage() {
</div>
<button type="button" onClick={() => copiarParaClipboard(codigoAtual, `code-${erro.id}`)} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{copiadoId === `code-${erro.id}` ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
{copiadoId === `code-${erro.id}` ? (isEn ? "Copied" : "Copiado") : (isEn ? "Copy code" : "Copiar código")}
{copiadoId === `code-${erro.id}` ? (isEs ? "Copiado" : isEn ? "Copied" : "Copiado") : (isEs ? "Copiar código" : isEn ? "Copy code" : "Copiar código")}
</button>
</div>
<div className="code-panel mt-3">
@@ -500,7 +547,7 @@ export default function DiagnosticoPage() {
<div className="callout callout-warn flex items-start gap-3">
<Wrench className="mt-0.5 h-4 w-4 shrink-0 text-gold" aria-hidden="true" />
<p><strong className="font-semibold text-ink">{isEn ? "Engineering tip:" : "Dica de engenharia:"}</strong> {erro.dicaPro}</p>
<p><strong className="font-semibold text-ink">{isEs ? "Consejo de ingeniería:" : isEn ? "Engineering tip:" : "Dica de engenharia:"}</strong> {erro.dicaPro}</p>
</div>
</div>
)}
@@ -513,10 +560,10 @@ export default function DiagnosticoPage() {
<section className="shell pb-20 md:pb-28" aria-labelledby="checklist-titulo">
<h2 id="checklist-titulo" className="section-title">
{isEn ? "Pre-deployment checklist" : "Checklist antes de ir para campo"}
{isEs ? "Lista de verificación previa a campo" : isEn ? "Pre-deployment checklist" : "Checklist antes de ir para campo"}
</h2>
<p className="mt-3 max-w-2xl text-base text-muted">
{isEn ? "Essential items to validate on hardware and firmware before shipping a product." : "Itens essenciais para validar no hardware e no firmware antes de entregar o produto."}
{isEs ? "Elementos esenciales para validar en hardware y firmware antes de entregar el producto." : isEn ? "Essential items to validate on hardware and firmware before shipping a product." : "Itens essenciais para validar no hardware e no firmware antes de entregar o produto."}
</p>
<div className="mt-8 grid gap-4 md:grid-cols-2">
{checklists.map(({ icon: Icon, titulo, itens }) => (