feat(i18n): routing and localization infrastructure (phase 1)
Implement Phase 1 of i18n spec: - Configure next-intl with 6 locales (pt, en, es, fr, de, zh-CN) and pathnames - Move routes to app/[locale] structure with localized layouts and metadata - Add middleware for root language negotiation (307), legacy redirects (308), and 404 for non-pt book - Implement language selector dropdown in header with cookie persistence - Add system typography rules for zh-CN and word-break/hyphenation for de/fr Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
1 parent
e92aea0c84
commit
83c0ba3e74
38 files changed
+1699
-324
No files matched your search
@@ -0,0 +1,180 @@
|
||||
import { paginaMeta, jsonLd } from "@/lib/seo";
|
||||
import Link from "next/link";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import { notFound } from "next/navigation";
|
||||
import CodeExample from "@/components/CodeExample";
|
||||
import frameworksData from "@/public/frameworks.json";
|
||||
import { ArrowRight, ArrowUpRight, BookOpen, Box, Check, Code, Layers } from "lucide-react";
|
||||
|
||||
export default async function FrameworkDetail({ params }) {
|
||||
const { key } = await params;
|
||||
const framework = frameworksData[key];
|
||||
|
||||
if (!framework) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const features = framework.caracteristicas || [];
|
||||
const useCases = framework.casos_uso || [];
|
||||
const isEspIdf = key === "ESP-IDF";
|
||||
|
||||
return (
|
||||
<div className="site-canvas min-h-screen">
|
||||
{/* Ficha técnica do framework e trilha de navegação para o Google */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.framework(key, framework)) }}
|
||||
/>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(jsonLd.trilha([
|
||||
{ nome: "Início", caminho: "/" },
|
||||
{ nome: "Frameworks", caminho: "/frameworks" },
|
||||
{ nome: framework.nome, caminho: `/frameworks/${encodeURIComponent(key)}` },
|
||||
])),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Header />
|
||||
|
||||
<main id="conteudo">
|
||||
<PageHero
|
||||
trail={[{ label: "Frameworks e SDKs", href: "/frameworks" }, { label: framework.nome }]}
|
||||
icon={Layers}
|
||||
label={framework.tipo}
|
||||
title={framework.nome}
|
||||
lede={framework.descricao}
|
||||
>
|
||||
{framework.documentacao && (
|
||||
<a href={framework.documentacao} target="_blank" rel="noopener noreferrer" className="btn btn-primary">
|
||||
<BookOpen className="h-4 w-4" aria-hidden="true" />
|
||||
Documentação oficial
|
||||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||||
</a>
|
||||
)}
|
||||
{framework.repositorio && (
|
||||
<a href={framework.repositorio} target="_blank" rel="noopener noreferrer" className="btn btn-ghost">
|
||||
<Code className="h-4 w-4" aria-hidden="true" />
|
||||
Repositório no GitHub
|
||||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||||
</a>
|
||||
)}
|
||||
</PageHero>
|
||||
|
||||
<section className="shell mt-10" aria-label="Resumo">
|
||||
<dl className="grid gap-3 md:grid-cols-3">
|
||||
<div className="tile">
|
||||
<dt className="text-xs font-medium text-muted">Linguagem</dt>
|
||||
<dd className="mt-1 font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{framework.linguagem}</dd>
|
||||
<dd className="mt-0.5 text-xs text-muted">{framework.nome_completo}</dd>
|
||||
</div>
|
||||
<div className="tile">
|
||||
<dt className="text-xs font-medium text-muted">Categoria</dt>
|
||||
<dd className="mt-1 font-display text-2xl font-semibold tracking-[-0.03em] text-ink">{framework.tipo}</dd>
|
||||
</div>
|
||||
<div className="tile">
|
||||
<dt className="text-xs font-medium text-muted">Objetivo principal</dt>
|
||||
<dd className="mt-1 text-sm font-medium leading-relaxed text-ink">{framework.funcao_principal}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section className="shell mt-12 grid gap-5 lg:grid-cols-2" aria-label="Recursos e aplicações">
|
||||
<div className="card p-5 sm:p-7">
|
||||
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">Recursos de destaque</h2>
|
||||
<ul className="mt-5 space-y-3">
|
||||
{features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-3 text-sm leading-relaxed text-ink-soft">
|
||||
<Check className="mt-0.5 h-4 w-4 shrink-0 text-ok" aria-hidden="true" />
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="card p-5 sm:p-7">
|
||||
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">Quando usar</h2>
|
||||
<ul className="mt-5 space-y-2.5">
|
||||
{useCases.map((useCase) => (
|
||||
<li key={useCase} className="tile text-sm font-medium text-ink-soft">
|
||||
{useCase}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{isEspIdf && (
|
||||
<section className="shell mt-8">
|
||||
<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">
|
||||
<span className="chip chip-accent">
|
||||
<Box className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
ESP-IDF 4.4 ou superior
|
||||
</span>
|
||||
<h2 className="section-title mt-4">Módulos com o ESP Component Registry</h2>
|
||||
<p className="mt-3 text-base leading-relaxed text-muted">
|
||||
Nos projetos ESP-IDF v4.4+ e v5.x, as bibliotecas não precisam mais entrar como submódulos Git. Use o{" "}
|
||||
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> e o comando{" "}
|
||||
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf.py add-dependency</code> para baixar drivers de display (LVGL), pilhas industriais (Modbus), RainMaker e mais.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/componentes" className="btn btn-primary shrink-0">
|
||||
Explorar componentes
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{framework.exemplo && (
|
||||
<section className="shell mt-8">
|
||||
<CodeExample framework={framework} />
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="shell section pt-10">
|
||||
<Link href="/frameworks" className="inline-flex min-h-11 items-center gap-2 text-sm font-medium text-accent hover:text-accent-strong">
|
||||
<ArrowRight className="h-4 w-4 rotate-180" aria-hidden="true" />
|
||||
Ver todos os frameworks
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { key } = await params;
|
||||
const framework = frameworksData[key];
|
||||
|
||||
if (!framework) {
|
||||
return { title: "Framework não encontrado", robots: { index: false, follow: false } };
|
||||
}
|
||||
|
||||
return {
|
||||
...paginaMeta({
|
||||
titulo: `${framework.nome} para ESP32 · Guia Técnico`,
|
||||
descricao: `${framework.nome_completo}: ${framework.descricao} Guia com exemplos práticos de código e casos de uso.`,
|
||||
caminho: `/frameworks/${encodeURIComponent(key)}`,
|
||||
tipo: "article",
|
||||
keywords: [
|
||||
framework.nome, framework.nome_completo,
|
||||
`${framework.nome} ESP32`, `programar ESP32 com ${framework.nome}`,
|
||||
"ESP32", "framework ESP32",
|
||||
],
|
||||
}),
|
||||
other: { "article:section": "Frameworks" },
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return Object.keys(frameworksData).map((key) => ({
|
||||
key: key,
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { paginaMeta, jsonLd } from "@/lib/seo";
|
||||
|
||||
export const metadata = paginaMeta({
|
||||
titulo: "Frameworks & SDKs para ESP32: Guia Completo e Comparativo",
|
||||
descricao: "Guia completo de desenvolvimento: ESP-IDF, ESP-RainMaker, ESP-Matter, Rust, ESP-ADF, ESP-SR, ESP-WHO, Arduino e MicroPython com exemplos de código.",
|
||||
caminho: "/frameworks",
|
||||
keywords: [
|
||||
"ESP-IDF",
|
||||
"ESP-RainMaker",
|
||||
"ESP-Matter",
|
||||
"Rust ESP32",
|
||||
"ESP-SR",
|
||||
"ESP-ADF",
|
||||
"ESP-IoT-Solution",
|
||||
"Arduino ESP32",
|
||||
"MicroPython ESP32",
|
||||
"frameworks ESP32",
|
||||
"programar ESP32",
|
||||
],
|
||||
});
|
||||
|
||||
export default function FrameworksLayout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(
|
||||
jsonLd.trilha([
|
||||
{ nome: "Início", caminho: "/" },
|
||||
{ nome: "Frameworks", caminho: "/frameworks" },
|
||||
])
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import PageHero from "@/components/PageHero";
|
||||
import SectionHead from "@/components/SectionHead";
|
||||
import Link from "next/link";
|
||||
import frameworksData from "@/public/frameworks.json";
|
||||
import { ArrowRight, ArrowUpRight, Box, Brain, Cloud, GraduationCap, Layers, Mic, Music, Sparkles } from "lucide-react";
|
||||
|
||||
const GUIAS = [
|
||||
{
|
||||
icon: GraduationCap,
|
||||
titulo: "Estou começando",
|
||||
texto: "Arduino ou MicroPython são os caminhos mais rápidos para testar sensores e APIs. Para produto, vá de ESP-IDF ou Rust.",
|
||||
links: [
|
||||
["Arduino", "/frameworks/Arduino"],
|
||||
["MicroPython", "/frameworks/MicroPython"],
|
||||
["Rust", "/frameworks/Rust-ESP"],
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Cloud,
|
||||
titulo: "Nuvem e casa conectada",
|
||||
texto: "ESP-Matter para interoperabilidade, ESP-RainMaker para nuvem pronta e Zigbee para redes locais de baixo consumo.",
|
||||
links: [
|
||||
["RainMaker", "/frameworks/ESP-RainMaker"],
|
||||
["Matter", "/frameworks/ESP-Matter"],
|
||||
["Zigbee", "/frameworks/ESP-Zigbee-SDK"],
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: Sparkles,
|
||||
titulo: "Áudio, voz e IA",
|
||||
texto: "ESP-ADF para áudio, ESP-SR para reconhecimento de fala offline e ESP-NN para acelerar TinyML.",
|
||||
links: [
|
||||
["Áudio", "/frameworks/ESP-ADF", Music],
|
||||
["Voz", "/frameworks/ESP-SR", Mic],
|
||||
["IA", "/frameworks/ESP-NN", Brain],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function Frameworks() {
|
||||
const frameworks = Object.entries(frameworksData);
|
||||
|
||||
return (
|
||||
<div className="site-canvas min-h-screen">
|
||||
<Header />
|
||||
|
||||
<main id="conteudo">
|
||||
<PageHero
|
||||
trail={[{ label: "Frameworks e SDKs" }]}
|
||||
icon={Layers}
|
||||
label={`${frameworks.length} frameworks e SDKs`}
|
||||
title="Frameworks e SDKs do ESP32"
|
||||
lede="Do C/C++ nativo com FreeRTOS ao Rust, passando por nuvem IoT, reconhecimento de voz, áudio e Matter. Escolha pelo que você quer construir."
|
||||
/>
|
||||
|
||||
<section className="shell pt-10 sm:pt-12" aria-label="Por onde começar">
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
{GUIAS.map(({ icon: Icon, titulo, texto, links }) => (
|
||||
<div key={titulo} className="card flex flex-col p-5 sm:p-6">
|
||||
<span className="grid h-10 w-10 place-items-center rounded-full bg-accent-soft text-accent">
|
||||
<Icon className="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
<h2 className="mt-4 font-display text-xl font-semibold tracking-[-0.03em] text-ink">{titulo}</h2>
|
||||
<p className="mt-2 flex-1 text-sm leading-relaxed text-muted">{texto}</p>
|
||||
<ul className="mt-5 flex flex-wrap gap-2 border-t border-line pt-4">
|
||||
{links.map(([rotulo, href, Extra]) => (
|
||||
<li key={rotulo}>
|
||||
<Link href={href} className="chip-btn">
|
||||
{Extra && <Extra className="h-4 w-4" aria-hidden="true" />}
|
||||
{rotulo}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="shell mt-10">
|
||||
<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">
|
||||
<span className="chip chip-accent">
|
||||
<Box className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
Arquitetura modular
|
||||
</span>
|
||||
<h2 className="section-title mt-4">ESP Component Registry</h2>
|
||||
<p className="mt-3 text-base leading-relaxed text-muted">
|
||||
Instale drivers de display (LVGL), pilhas industriais (Modbus), recursos de nuvem e pacotes de placa
|
||||
(BSPs) com uma linha no manifesto <code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code>.
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/componentes" className="btn btn-primary shrink-0">
|
||||
Explorar componentes
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="shell section" aria-labelledby="todos-titulo">
|
||||
<SectionHead id="todos-titulo" eyebrow="Todos" title="Todos os frameworks" lede="Cada ficha traz recursos, aplicações ideais, exemplo de código e links oficiais." />
|
||||
|
||||
<div className="mt-8 grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{frameworks.map(([key, framework]) => (
|
||||
<Link key={key} href={`/frameworks/${encodeURIComponent(key)}`} className="card card-interactive group flex flex-col p-5 sm:p-6">
|
||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||||
<h3 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">{framework.nome}</h3>
|
||||
<span className="chip max-w-full">{framework.linguagem}</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-muted">{framework.tipo}</p>
|
||||
<p className="mt-3 line-clamp-3 text-sm leading-relaxed text-muted">{framework.descricao}</p>
|
||||
|
||||
<ul className="mt-5 flex flex-1 flex-wrap content-start gap-1.5">
|
||||
{framework.casos_uso.slice(0, 3).map((caso) => (
|
||||
<li key={caso} className="chip max-w-full">
|
||||
<span className="truncate">{caso}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<span className="mt-5 inline-flex items-center gap-1 border-t border-line pt-4 text-sm font-medium text-accent">
|
||||
Ver detalhes e exemplo
|
||||
<ArrowUpRight className="h-4 w-4 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5" aria-hidden="true" />
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user