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
@@ -5,11 +5,16 @@ 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 frameworksDataPt from "@/public/frameworks.json";
|
||||
import frameworksDataEn from "@/data/en/frameworks.json";
|
||||
import { getLocalizedHref } from "@/i18n/links";
|
||||
import { ArrowRight, ArrowUpRight, BookOpen, Box, Check, Code, Layers } from "lucide-react";
|
||||
|
||||
export default async function FrameworkDetail({ params }) {
|
||||
const { key } = await params;
|
||||
const { locale, key } = await params;
|
||||
const isEn = locale === "en";
|
||||
const currentLocale = isEn ? "en" : "pt";
|
||||
const frameworksData = isEn ? frameworksDataEn : frameworksDataPt;
|
||||
const framework = frameworksData[key];
|
||||
|
||||
if (!framework) {
|
||||
@@ -31,9 +36,9 @@ export default async function FrameworkDetail({ params }) {
|
||||
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)}` },
|
||||
{ nome: isEn ? "Home" : "Início", caminho: isEn ? "/en" : "/" },
|
||||
{ nome: isEn ? "Frameworks" : "Frameworks", caminho: getLocalizedHref("/frameworks", currentLocale) },
|
||||
{ nome: framework.nome, caminho: getLocalizedHref(`/frameworks/${encodeURIComponent(key)}`, currentLocale) },
|
||||
])),
|
||||
}}
|
||||
/>
|
||||
@@ -42,7 +47,7 @@ export default async function FrameworkDetail({ params }) {
|
||||
|
||||
<main id="conteudo">
|
||||
<PageHero
|
||||
trail={[{ label: "Frameworks e SDKs", href: "/frameworks" }, { label: framework.nome }]}
|
||||
trail={[{ label: isEn ? "Frameworks & SDKs" : "Frameworks e SDKs", href: getLocalizedHref("/frameworks", currentLocale) }, { label: framework.nome }]}
|
||||
icon={Layers}
|
||||
label={framework.tipo}
|
||||
title={framework.nome}
|
||||
@@ -51,40 +56,42 @@ export default async function FrameworkDetail({ params }) {
|
||||
{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
|
||||
{isEn ? "Official documentation" : "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
|
||||
{isEn ? "GitHub repository" : "Repositório no GitHub"}
|
||||
<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 gap-3 md:grid-cols-3">
|
||||
<div className="tile">
|
||||
<dt className="text-xs font-medium text-muted">Linguagem</dt>
|
||||
<dt className="text-xs font-medium text-muted">{isEn ? "Language" : "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>
|
||||
<dt className="text-xs font-medium text-muted">{isEn ? "Category" : "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>
|
||||
<dt className="text-xs font-medium text-muted">{isEn ? "Main purpose" : "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">
|
||||
<section className="shell mt-12 grid gap-5 lg:grid-cols-2" aria-label={isEn ? "Features and applications" : "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>
|
||||
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
|
||||
{isEn ? "Key features" : "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">
|
||||
@@ -96,7 +103,9 @@ export default async function FrameworkDetail({ params }) {
|
||||
</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>
|
||||
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
|
||||
{isEn ? "When to use" : "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">
|
||||
@@ -113,17 +122,29 @@ export default async function FrameworkDetail({ params }) {
|
||||
<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
|
||||
{isEn ? "ESP-IDF 4.4 or higher" : "ESP-IDF 4.4 ou superior"}
|
||||
</span>
|
||||
<h2 className="section-title mt-4">Módulos com o ESP Component Registry</h2>
|
||||
<h2 className="section-title mt-4">
|
||||
{isEn ? "Modules with the ESP Component Registry" : "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.
|
||||
{isEn ? (
|
||||
<>
|
||||
In ESP-IDF v4.4+ and v5.x projects, libraries no longer need to be added as Git submodules. Use{" "}
|
||||
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf_component.yml</code> and the command{" "}
|
||||
<code className="rounded bg-surface-2 px-1.5 py-0.5 font-mono text-sm">idf.py add-dependency</code> to download display drivers (LVGL), industrial stacks (Modbus), RainMaker, and more.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
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
|
||||
<Link href={getLocalizedHref("/componentes", currentLocale)} className="btn btn-primary shrink-0">
|
||||
{isEn ? "Explore components" : "Explorar componentes"}
|
||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||
</Link>
|
||||
</div>
|
||||
@@ -137,9 +158,9 @@ export default async function FrameworkDetail({ params }) {
|
||||
)}
|
||||
|
||||
<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">
|
||||
<Link href={getLocalizedHref("/frameworks", currentLocale)} className="inline-flex min-h-11 items-center gap-2 text-sm font-medium text-accent hover:text-accent-strong">
|
||||
<ArrowRight className="h-4 w-4 rotate-180" aria-hidden="true" />
|
||||
Ver todos os frameworks
|
||||
{isEn ? "View all frameworks" : "Ver todos os frameworks"}
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
@@ -150,31 +171,43 @@ export default async function FrameworkDetail({ params }) {
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { key } = await params;
|
||||
const { locale, key } = await params;
|
||||
const isEn = locale === "en";
|
||||
const currentLocale = isEn ? "en" : "pt";
|
||||
const frameworksData = isEn ? frameworksDataEn : frameworksDataPt;
|
||||
const framework = frameworksData[key];
|
||||
|
||||
if (!framework) {
|
||||
return { title: "Framework não encontrado", robots: { index: false, follow: false } };
|
||||
return { title: isEn ? "Framework not found" : "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.`,
|
||||
titulo: isEn ? `${framework.nome} for ESP32 · Technical Guide` : `${framework.nome} para ESP32 · Guia Técnico`,
|
||||
descricao: isEn
|
||||
? `${framework.nome_completo}: ${framework.descricao} Guide with practical code examples and use cases.`
|
||||
: `${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",
|
||||
],
|
||||
locale: currentLocale,
|
||||
keywords: isEn
|
||||
? [
|
||||
framework.nome, framework.nome_completo,
|
||||
`${framework.nome} ESP32`, `program ESP32 with ${framework.nome}`,
|
||||
"ESP32", "ESP32 framework",
|
||||
]
|
||||
: [
|
||||
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) => ({
|
||||
return Object.keys(frameworksDataPt).map((key) => ({
|
||||
key: key,
|
||||
}));
|
||||
}
|
||||
Reference in new issue
Block a user