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
@@ -6,6 +6,14 @@ Tudo o que muda de relevante no ESPDocs está aqui. O formato segue o
|
|||||||
|
|
||||||
## [Não lançado]
|
## [Não lançado]
|
||||||
|
|
||||||
|
### Adicionado
|
||||||
|
|
||||||
|
- Infraestrutura de internacionalização com `next-intl` (Fase 1):
|
||||||
|
- Roteamento com prefixo de idioma sob `app/[locale]/`, com 6 locales declarados (`pt`, `en`, `es`, `fr`, `de`, `zh-CN`).
|
||||||
|
- `middleware.js` com negociação de idioma na raiz por cookie `NEXT_LOCALE` e cabeçalho `Accept-Language` (com pesos `q`), redirecionamento 308 de URLs antigas sem prefixo para `/pt/`, e retorno 404 para `/livro` fora do português.
|
||||||
|
- Seletor de idiomas acessível no cabeçalho com atalho para os 6 idiomas, persistência via cookie `NEXT_LOCALE` e mapeamento de rota correspondente.
|
||||||
|
- Regras de tipografia de sistema CJK para `zh-CN` e hifenização/quebra de palavras para `de` e `fr`.
|
||||||
|
|
||||||
## [1.1.0] - 2026-11-01
|
## [1.1.0] - 2026-11-01
|
||||||
|
|
||||||
### Adicionado
|
### Adicionado
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,162 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import { JetBrains_Mono, Manrope, Space_Grotesk } from "next/font/google";
|
||||||
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
|
import { getMessages } from "next-intl/server";
|
||||||
|
import { routing } from "@/i18n/routing";
|
||||||
|
import "../globals.css";
|
||||||
|
import Providers from "@/components/Providers";
|
||||||
|
import { SITE, jsonLd } from "@/lib/seo";
|
||||||
|
|
||||||
|
const spaceGrotesk = Space_Grotesk({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-space-grotesk",
|
||||||
|
display: "swap",
|
||||||
|
weight: ["500", "600", "700"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const manrope = Manrope({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-manrope",
|
||||||
|
display: "swap",
|
||||||
|
weight: ["400", "500", "600", "700"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const jetbrainsMono = JetBrains_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-jetbrains",
|
||||||
|
display: "swap",
|
||||||
|
weight: ["400", "500", "600"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const LANG_MAP = {
|
||||||
|
pt: "pt-BR",
|
||||||
|
en: "en",
|
||||||
|
es: "es-419",
|
||||||
|
fr: "fr",
|
||||||
|
de: "de",
|
||||||
|
"zh-CN": "zh-CN",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function generateStaticParams() {
|
||||||
|
return routing.locales.map((locale) => ({ locale }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateMetadata({ params }) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const ogLocaleMap = {
|
||||||
|
pt: "pt_BR",
|
||||||
|
en: "en_US",
|
||||||
|
es: "es_419",
|
||||||
|
fr: "fr_FR",
|
||||||
|
de: "de_DE",
|
||||||
|
"zh-CN": "zh_CN",
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadataBase: new URL(SITE.url),
|
||||||
|
title: {
|
||||||
|
default: "ESPDocs · Documentação do ESP32 em português",
|
||||||
|
template: "%s · ESPDocs",
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro. Datasheets, periféricos, segurança em hardware e diagramas de conexão.",
|
||||||
|
applicationName: "ESPDocs",
|
||||||
|
authors: [{ name: "Lucas Rayan Guerra", url: "https://lucasrguerra.dev.br" }],
|
||||||
|
creator: "Lucas Rayan Guerra",
|
||||||
|
publisher: "Ciência Embarcada",
|
||||||
|
category: "technology",
|
||||||
|
keywords: [
|
||||||
|
"ESP32", "ESP32 português", "documentação ESP32", "pinout ESP32",
|
||||||
|
"ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4", "ESP32-H2",
|
||||||
|
"comparar ESP32", "qual ESP32 escolher", "datasheet ESP32",
|
||||||
|
"microcontrolador", "Espressif", "ESP-IDF", "IoT", "sistemas embarcados",
|
||||||
|
],
|
||||||
|
alternates: { canonical: `/${locale}` },
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
locale: ogLocaleMap[locale] || "pt_BR",
|
||||||
|
url: `${SITE.url}/${locale}`,
|
||||||
|
siteName: SITE.nome,
|
||||||
|
title: "ESPDocs · Documentação do ESP32 em português",
|
||||||
|
description:
|
||||||
|
"Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro.",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "ESPDocs · Documentação do ESP32 em português",
|
||||||
|
description:
|
||||||
|
"Especificações, pinagem, comparação e seleção das 12 séries de ESP32, em português.",
|
||||||
|
},
|
||||||
|
robots: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
googleBot: {
|
||||||
|
index: true,
|
||||||
|
follow: true,
|
||||||
|
"max-image-preview": "large",
|
||||||
|
"max-snippet": -1,
|
||||||
|
"max-video-preview": -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icons: {
|
||||||
|
icon: [
|
||||||
|
{ url: "/favicon.ico", sizes: "48x48", type: "image/x-icon" },
|
||||||
|
{ url: "/favicon-48x48.png", sizes: "48x48", type: "image/png" },
|
||||||
|
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
||||||
|
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
||||||
|
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
|
||||||
|
{ url: "/icon-512.png", sizes: "512x512", type: "image/png" },
|
||||||
|
{ url: "/icon.svg", type: "image/svg+xml" },
|
||||||
|
],
|
||||||
|
apple: [
|
||||||
|
{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" },
|
||||||
|
],
|
||||||
|
shortcut: "/favicon.ico",
|
||||||
|
},
|
||||||
|
manifest: "/site.webmanifest",
|
||||||
|
formatDetection: { telephone: false, address: false, email: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const viewport = {
|
||||||
|
themeColor: [
|
||||||
|
{ media: "(prefers-color-scheme: light)", color: "#f5f6f8" },
|
||||||
|
{ media: "(prefers-color-scheme: dark)", color: "#07090d" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function RootLayout({ children, params }) {
|
||||||
|
const { locale } = await params;
|
||||||
|
if (!routing.locales.includes(locale)) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const lang = LANG_MAP[locale] || locale;
|
||||||
|
const messages = await getMessages();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={lang} className={`${spaceGrotesk.variable} ${manrope.variable} ${jetbrainsMono.variable}`} suppressHydrationWarning>
|
||||||
|
<head>
|
||||||
|
{/* Dados estruturados do site e da organização. É o que permite ao
|
||||||
|
Google montar o box de busca interna e entender a autoria. */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.siteESociedade()) }}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body className="font-sans antialiased bg-canvas text-ink overflow-x-clip" suppressHydrationWarning>
|
||||||
|
<a
|
||||||
|
href="#conteudo"
|
||||||
|
className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[200] focus:rounded-full focus:bg-ink focus:px-5 focus:py-3 focus:text-sm focus:font-semibold focus:text-canvas"
|
||||||
|
>
|
||||||
|
Pular para o conteúdo
|
||||||
|
</a>
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
<Providers>
|
||||||
|
{children}
|
||||||
|
</Providers>
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { notFound } from "next/navigation";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
import BookCover from "@/components/BookCover";
|
import BookCover from "@/components/BookCover";
|
||||||
@@ -16,19 +17,29 @@ import {
|
|||||||
Wifi,
|
Wifi,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
export const metadata = paginaMeta({
|
export function generateStaticParams() {
|
||||||
titulo: "Microcontroladores ESP32, o livro",
|
return [{ locale: "pt" }];
|
||||||
descricao:
|
}
|
||||||
"Conheça o livro Microcontroladores ESP32: conectividade, eficiência e desempenho. 12 partes e 78 capítulos em português sobre arquitetura, energia, rádio, periféricos e firmware.",
|
|
||||||
caminho: "/livro",
|
export async function generateMetadata({ params }) {
|
||||||
keywords: [
|
const { locale } = await params;
|
||||||
"livro ESP32",
|
if (locale !== "pt") {
|
||||||
"Microcontroladores ESP32",
|
return {};
|
||||||
"livro de ESP32 em português",
|
}
|
||||||
"Lucas Rayan Guerra",
|
return paginaMeta({
|
||||||
"Ciência Embarcada",
|
titulo: "Microcontroladores ESP32, o livro",
|
||||||
],
|
descricao:
|
||||||
});
|
"Conheça o livro Microcontroladores ESP32: conectividade, eficiência e desempenho. 12 partes e 78 capítulos em português sobre arquitetura, energia, rádio, periféricos e firmware.",
|
||||||
|
caminho: "/pt/livro",
|
||||||
|
keywords: [
|
||||||
|
"livro ESP32",
|
||||||
|
"Microcontroladores ESP32",
|
||||||
|
"livro de ESP32 em português",
|
||||||
|
"Lucas Rayan Guerra",
|
||||||
|
"Ciência Embarcada",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const CHAPTERS = [
|
const CHAPTERS = [
|
||||||
{
|
{
|
||||||
@@ -128,7 +139,11 @@ const FACTS = [
|
|||||||
["ISBN", SITE.livroIsbn],
|
["ISBN", SITE.livroIsbn],
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function LivroPage() {
|
export default async function LivroPage({ params }) {
|
||||||
|
const { locale } = await params;
|
||||||
|
if (locale !== "pt") {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="site-canvas min-h-screen">
|
<div className="site-canvas min-h-screen">
|
||||||
<script
|
<script
|
||||||
File renamed without changes.
@@ -5,6 +5,7 @@ import Link from "next/link";
|
|||||||
import seriesData from "@/public/series.json";
|
import seriesData from "@/public/series.json";
|
||||||
import frameworksData from "@/public/frameworks.json";
|
import frameworksData from "@/public/frameworks.json";
|
||||||
import { SITE } from "@/lib/seo";
|
import { SITE } from "@/lib/seo";
|
||||||
|
import { getLocalizedHref } from "@/components/LanguageSelector";
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@@ -87,7 +88,11 @@ const ERRORS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function Home() {
|
export default async function Home({ params }) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const isPt = locale === "pt";
|
||||||
|
const getLinkHref = (href) => getLocalizedHref(`/${locale}${href}`, locale);
|
||||||
|
|
||||||
const seriesCount = Object.keys(seriesData).length;
|
const seriesCount = Object.keys(seriesData).length;
|
||||||
const frameworkCount = Object.keys(frameworksData).length;
|
const frameworkCount = Object.keys(frameworksData).length;
|
||||||
const featured = FEATURED.map((key) => [key, seriesData[key]]).filter(([, serie]) => Boolean(serie));
|
const featured = FEATURED.map((key) => [key, seriesData[key]]).filter(([, serie]) => Boolean(serie));
|
||||||
@@ -113,11 +118,11 @@ export default function Home() {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
|
||||||
<Link href="/seletor" className="btn btn-primary w-full sm:w-auto">
|
<Link href={getLinkHref("/seletor")} className="btn btn-primary w-full sm:w-auto">
|
||||||
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
||||||
Escolher meu chip
|
Escolher meu chip
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="/series" className="btn btn-ghost w-full sm:w-auto">
|
<Link href={getLinkHref("/series")} className="btn btn-ghost w-full sm:w-auto">
|
||||||
Explorar as {seriesCount} séries
|
Explorar as {seriesCount} séries
|
||||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -127,10 +132,14 @@ export default function Home() {
|
|||||||
<span><strong className="font-semibold text-ink">{seriesCount}</strong> séries</span>
|
<span><strong className="font-semibold text-ink">{seriesCount}</strong> séries</span>
|
||||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||||
<span><strong className="font-semibold text-ink">{frameworkCount}</strong> frameworks</span>
|
<span><strong className="font-semibold text-ink">{frameworkCount}</strong> frameworks</span>
|
||||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
{isPt && (
|
||||||
<span><strong className="font-semibold text-ink">78</strong> capítulos no livro</span>
|
<>
|
||||||
<span aria-hidden="true" className="text-line-strong">·</span>
|
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||||
<span>100% em português</span>
|
<span><strong className="font-semibold text-ink">78</strong> capítulos no livro</span>
|
||||||
|
<span aria-hidden="true" className="text-line-strong">·</span>
|
||||||
|
<span>100% em português</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -176,72 +185,74 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="shell section pt-8 sm:pt-12" aria-labelledby="livro-titulo">
|
{isPt && (
|
||||||
<div className="relative isolate overflow-hidden rounded-[2rem] bg-[#0b0d10] ring-1 ring-white/10 dark:bg-[#05070a] dark:ring-white/15 sm:rounded-[2.5rem]">
|
<section className="shell section pt-8 sm:pt-12" aria-labelledby="livro-titulo">
|
||||||
<div
|
<div className="relative isolate overflow-hidden rounded-[2rem] bg-[#0b0d10] ring-1 ring-white/10 dark:bg-[#05070a] dark:ring-white/15 sm:rounded-[2.5rem]">
|
||||||
className="pointer-events-none absolute -left-24 top-1/2 -z-10 h-80 w-80 -translate-y-1/2 rounded-full bg-[#2dd4bf]/15 blur-3xl"
|
<div
|
||||||
aria-hidden="true"
|
className="pointer-events-none absolute -left-24 top-1/2 -z-10 h-80 w-80 -translate-y-1/2 rounded-full bg-[#2dd4bf]/15 blur-3xl"
|
||||||
/>
|
aria-hidden="true"
|
||||||
<div className="grid lg:grid-cols-[0.82fr_1.18fr]">
|
/>
|
||||||
<div className="order-1 flex flex-col justify-center p-7 sm:p-10 lg:order-2 lg:p-14">
|
<div className="grid lg:grid-cols-[0.82fr_1.18fr]">
|
||||||
<span className="inline-flex w-fit items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3 py-1 text-xs font-semibold text-[#75cdc1]">
|
<div className="order-1 flex flex-col justify-center p-7 sm:p-10 lg:order-2 lg:p-14">
|
||||||
<BookOpen className="h-3.5 w-3.5" aria-hidden="true" />
|
<span className="inline-flex w-fit items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3 py-1 text-xs font-semibold text-[#75cdc1]">
|
||||||
Livro novo
|
<BookOpen className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
</span>
|
Livro novo
|
||||||
<h2 id="livro-titulo" className="mt-5 font-display text-[clamp(2rem,4.2vw,3.2rem)] font-semibold leading-[1.05] tracking-[-0.04em] text-[#f4f6f8]">
|
</span>
|
||||||
{SITE.livroTitulo}
|
<h2 id="livro-titulo" className="mt-5 font-display text-[clamp(2rem,4.2vw,3.2rem)] font-semibold leading-[1.05] tracking-[-0.04em] text-[#f4f6f8]">
|
||||||
</h2>
|
{SITE.livroTitulo}
|
||||||
<p className="mt-2 text-base text-[#a6afbd]">{SITE.livroSubtitulo}</p>
|
</h2>
|
||||||
<p className="mt-5 max-w-xl text-sm leading-relaxed text-[#c4cbd5]">
|
<p className="mt-2 text-base text-[#a6afbd]">{SITE.livroSubtitulo}</p>
|
||||||
Um percurso completo pelo hardware e pelo firmware do ESP32: da arquitetura do chip
|
<p className="mt-5 max-w-xl text-sm leading-relaxed text-[#c4cbd5]">
|
||||||
à implantação de produtos conectados. São 12 partes e 78 capítulos sobre Arduino,
|
Um percurso completo pelo hardware e pelo firmware do ESP32: da arquitetura do chip
|
||||||
ESP-IDF, FreeRTOS, Wi-Fi, BLE, Thread e Matter, periféricos, depuração, baixo consumo
|
à implantação de produtos conectados. São 12 partes e 78 capítulos sobre Arduino,
|
||||||
e segurança.
|
ESP-IDF, FreeRTOS, Wi-Fi, BLE, Thread e Matter, periféricos, depuração, baixo consumo
|
||||||
</p>
|
e segurança.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p className="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-[#a6afbd]">
|
<p className="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1.5 text-xs text-[#a6afbd]">
|
||||||
<span>12 partes</span>
|
<span>12 partes</span>
|
||||||
<span aria-hidden="true" className="text-white/20">·</span>
|
<span aria-hidden="true" className="text-white/20">·</span>
|
||||||
<span>78 capítulos</span>
|
<span>78 capítulos</span>
|
||||||
<span aria-hidden="true" className="text-white/20">·</span>
|
<span aria-hidden="true" className="text-white/20">·</span>
|
||||||
<span>ISBN {SITE.livroIsbn}</span>
|
<span>ISBN {SITE.livroIsbn}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-7 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
|
<div className="mt-7 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
|
||||||
<a
|
<a
|
||||||
href={SITE.livroAmazon}
|
href={SITE.livroAmazon}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full bg-[#2dd4bf] px-5 text-sm font-semibold text-[#06201d] transition-colors duration-300 hover:bg-[#5eead4]"
|
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full bg-[#2dd4bf] px-5 text-sm font-semibold text-[#06201d] transition-colors duration-300 hover:bg-[#5eead4]"
|
||||||
>
|
>
|
||||||
Comprar digital
|
Comprar digital
|
||||||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href={SITE.livroUiclap}
|
href={SITE.livroUiclap}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-white/20 px-5 text-sm font-semibold text-[#f4f6f8] transition-colors duration-300 hover:border-white/40 hover:bg-white/5"
|
className="inline-flex min-h-11 items-center justify-center gap-2 rounded-full border border-white/20 px-5 text-sm font-semibold text-[#f4f6f8] transition-colors duration-300 hover:border-white/40 hover:bg-white/5"
|
||||||
>
|
>
|
||||||
Comprar impresso
|
Comprar impresso
|
||||||
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<Link href="/livro" className="inline-flex min-h-11 items-center justify-center gap-1.5 rounded-full px-3 text-sm font-medium text-[#a6afbd] transition-colors duration-300 hover:text-[#f4f6f8]">
|
<Link href="/pt/livro" className="inline-flex min-h-11 items-center justify-center gap-1.5 rounded-full px-3 text-sm font-medium text-[#a6afbd] transition-colors duration-300 hover:text-[#f4f6f8]">
|
||||||
Ficha completa
|
Ficha completa
|
||||||
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
<ArrowRight className="h-4 w-4" aria-hidden="true" />
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="order-2 flex items-center justify-center border-t border-white/10 p-8 sm:p-12 lg:order-1 lg:border-r lg:border-t-0 lg:p-14">
|
<div className="order-2 flex items-center justify-center border-t border-white/10 p-8 sm:p-12 lg:order-1 lg:border-r lg:border-t-0 lg:p-14">
|
||||||
<div className="relative w-[min(15.5rem,68%)] lg:w-[min(17rem,100%)]">
|
<div className="relative w-[min(15.5rem,68%)] lg:w-[min(17rem,100%)]">
|
||||||
<div className="absolute -inset-10 -z-10 rounded-full bg-[#2dd4bf]/10 blur-3xl" aria-hidden="true" />
|
<div className="absolute -inset-10 -z-10 rounded-full bg-[#2dd4bf]/10 blur-3xl" aria-hidden="true" />
|
||||||
<BookCover />
|
<BookCover />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</section>
|
)}
|
||||||
|
|
||||||
<section className="shell section" aria-labelledby="ferramentas-titulo">
|
<section className="shell section" aria-labelledby="ferramentas-titulo">
|
||||||
<div className="mx-auto max-w-2xl text-center">
|
<div className="mx-auto max-w-2xl text-center">
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -759,4 +759,28 @@
|
|||||||
.no-scrollbar::-webkit-scrollbar {
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------
|
||||||
|
Tipografia e layout internacionalizados (seção 9b da spec)
|
||||||
|
------------------------------------------------------------------------ */
|
||||||
|
html[lang="zh-CN"] {
|
||||||
|
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "Noto Sans SC", sans-serif;
|
||||||
|
letter-spacing: 0;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[lang="zh-CN"] code,
|
||||||
|
html[lang="zh-CN"] pre,
|
||||||
|
html[lang="zh-CN"] kbd {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[lang="de"],
|
||||||
|
html[lang="fr"] {
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, li, dd {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
-134
@@ -1,134 +0,0 @@
|
|||||||
import { JetBrains_Mono, Manrope, Space_Grotesk } from "next/font/google";
|
|
||||||
import "./globals.css";
|
|
||||||
import Providers from "@/components/Providers";
|
|
||||||
import { SITE, jsonLd } from "@/lib/seo";
|
|
||||||
|
|
||||||
const spaceGrotesk = Space_Grotesk({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-space-grotesk",
|
|
||||||
display: "swap",
|
|
||||||
weight: ["500", "600", "700"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const manrope = Manrope({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-manrope",
|
|
||||||
display: "swap",
|
|
||||||
weight: ["400", "500", "600", "700"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const jetbrainsMono = JetBrains_Mono({
|
|
||||||
subsets: ["latin"],
|
|
||||||
variable: "--font-jetbrains",
|
|
||||||
display: "swap",
|
|
||||||
weight: ["400", "500", "600"],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const metadata = {
|
|
||||||
// metadataBase é o que transforma caminhos relativos em URLs absolutas nas
|
|
||||||
// tags canonical e og:image. Sem ele, o Google recebe og:image quebrado.
|
|
||||||
metadataBase: new URL(SITE.url),
|
|
||||||
|
|
||||||
title: {
|
|
||||||
default: "ESPDocs · Documentação do ESP32 em português",
|
|
||||||
// Cada página fornece só o próprio nome; o sufixo entra automaticamente.
|
|
||||||
template: "%s · ESPDocs",
|
|
||||||
},
|
|
||||||
description:
|
|
||||||
"Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro. Datasheets, periféricos, segurança em hardware e diagramas de conexão.",
|
|
||||||
|
|
||||||
applicationName: "ESPDocs",
|
|
||||||
authors: [{ name: "Lucas Rayan Guerra", url: "https://lucasrguerra.dev.br" }],
|
|
||||||
creator: "Lucas Rayan Guerra",
|
|
||||||
publisher: "Ciência Embarcada",
|
|
||||||
category: "technology",
|
|
||||||
|
|
||||||
keywords: [
|
|
||||||
"ESP32", "ESP32 português", "documentação ESP32", "pinout ESP32",
|
|
||||||
"ESP32-S3", "ESP32-C3", "ESP32-C6", "ESP32-P4", "ESP32-H2",
|
|
||||||
"comparar ESP32", "qual ESP32 escolher", "datasheet ESP32",
|
|
||||||
"microcontrolador", "Espressif", "ESP-IDF", "IoT", "sistemas embarcados",
|
|
||||||
],
|
|
||||||
|
|
||||||
alternates: { canonical: "/" },
|
|
||||||
|
|
||||||
openGraph: {
|
|
||||||
type: "website",
|
|
||||||
locale: "pt_BR",
|
|
||||||
url: SITE.url,
|
|
||||||
siteName: "ESPDocs",
|
|
||||||
title: "ESPDocs · Documentação do ESP32 em português",
|
|
||||||
description:
|
|
||||||
"Especificações, pinagem, comparação e seleção das 12 séries de microcontroladores ESP32, em português brasileiro.",
|
|
||||||
},
|
|
||||||
|
|
||||||
twitter: {
|
|
||||||
card: "summary_large_image",
|
|
||||||
title: "ESPDocs · Documentação do ESP32 em português",
|
|
||||||
description:
|
|
||||||
"Especificações, pinagem, comparação e seleção das 12 séries de ESP32, em português.",
|
|
||||||
},
|
|
||||||
|
|
||||||
robots: {
|
|
||||||
index: true,
|
|
||||||
follow: true,
|
|
||||||
googleBot: {
|
|
||||||
index: true,
|
|
||||||
follow: true,
|
|
||||||
"max-image-preview": "large",
|
|
||||||
"max-snippet": -1,
|
|
||||||
"max-video-preview": -1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
icons: {
|
|
||||||
icon: [
|
|
||||||
{ url: "/favicon.ico", sizes: "48x48", type: "image/x-icon" },
|
|
||||||
{ url: "/favicon-48x48.png", sizes: "48x48", type: "image/png" },
|
|
||||||
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
|
|
||||||
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
|
|
||||||
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
|
|
||||||
{ url: "/icon-512.png", sizes: "512x512", type: "image/png" },
|
|
||||||
{ url: "/icon.svg", type: "image/svg+xml" },
|
|
||||||
],
|
|
||||||
apple: [
|
|
||||||
{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" },
|
|
||||||
],
|
|
||||||
shortcut: "/favicon.ico",
|
|
||||||
},
|
|
||||||
manifest: "/site.webmanifest",
|
|
||||||
formatDetection: { telephone: false, address: false, email: false },
|
|
||||||
};
|
|
||||||
|
|
||||||
export const viewport = {
|
|
||||||
themeColor: [
|
|
||||||
{ media: "(prefers-color-scheme: light)", color: "#f5f6f8" },
|
|
||||||
{ media: "(prefers-color-scheme: dark)", color: "#07090d" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function RootLayout({ children }) {
|
|
||||||
return (
|
|
||||||
<html lang="pt-BR" className={`${spaceGrotesk.variable} ${manrope.variable} ${jetbrainsMono.variable}`} suppressHydrationWarning>
|
|
||||||
<head>
|
|
||||||
{/* Dados estruturados do site e da organização. É o que permite ao
|
|
||||||
Google montar o box de busca interna e entender a autoria. */}
|
|
||||||
<script
|
|
||||||
type="application/ld+json"
|
|
||||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd.siteESociedade()) }}
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body className="font-sans antialiased bg-canvas text-ink overflow-x-clip" suppressHydrationWarning>
|
|
||||||
<a
|
|
||||||
href="#conteudo"
|
|
||||||
className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[200] focus:rounded-full focus:bg-ink focus:px-5 focus:py-3 focus:text-sm focus:font-semibold focus:text-canvas"
|
|
||||||
>
|
|
||||||
Pular para o conteúdo
|
|
||||||
</a>
|
|
||||||
<Providers>
|
|
||||||
{children}
|
|
||||||
</Providers>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+80
-65
@@ -1,17 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import Logo from "@/components/Logo";
|
import Logo from "@/components/Logo";
|
||||||
import GiteaMark from "@/components/GiteaMark";
|
import GiteaMark from "@/components/GiteaMark";
|
||||||
import { ArrowUpRight } from "lucide-react";
|
import { ArrowUpRight } from "lucide-react";
|
||||||
import { SITE } from "@/lib/seo";
|
import { SITE } from "@/lib/seo";
|
||||||
|
import { LOCALES, getLocalizedHref } from "@/components/LanguageSelector";
|
||||||
|
|
||||||
const COLUMNS = [
|
const BASE_COLUMNS = [
|
||||||
{
|
{
|
||||||
title: "Explorar",
|
title: "Explorar",
|
||||||
links: [
|
links: [
|
||||||
{ href: "/series", label: "Séries ESP32" },
|
{ href: "/series", label: "Séries ESP32" },
|
||||||
{ href: "/comparacao", label: "Comparador" },
|
{ href: "/comparacao", label: "Comparador" },
|
||||||
{ href: "/catalogo", label: "Catálogo de placas" },
|
{ href: "/catalogo", label: "Catálogo de placas" },
|
||||||
{ href: "/livro", label: "O livro" },
|
{ href: "/livro", label: "O livro", livroOnly: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -35,14 +39,26 @@ const COLUMNS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
const pathname = usePathname() || "";
|
||||||
const year = new Date().getFullYear();
|
const year = new Date().getFullYear();
|
||||||
|
|
||||||
|
const currentLocale =
|
||||||
|
LOCALES.find((l) => pathname === `/${l.code}` || pathname.startsWith(`/${l.code}/`))?.code || "pt";
|
||||||
|
const isPt = currentLocale === "pt";
|
||||||
|
|
||||||
|
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
|
||||||
|
|
||||||
|
const columns = BASE_COLUMNS.map((col) => ({
|
||||||
|
...col,
|
||||||
|
links: col.links.filter((l) => !l.livroOnly || isPt),
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="mt-20 border-t border-line md:mt-28" aria-label="Rodapé do ESPDocs">
|
<footer className="mt-20 border-t border-line md:mt-28" aria-label="Rodapé do ESPDocs">
|
||||||
<div className="shell py-14 md:py-20">
|
<div className="shell py-14 md:py-20">
|
||||||
<div className="grid gap-12 lg:grid-cols-[1.1fr_2fr]">
|
<div className="grid gap-12 lg:grid-cols-[1.1fr_2fr]">
|
||||||
<div>
|
<div>
|
||||||
<Link href="/" className="inline-flex min-h-11 items-center gap-2.5" aria-label="ESPDocs, página inicial">
|
<Link href={getLinkHref("/")} className="inline-flex min-h-11 items-center gap-2.5" aria-label="ESPDocs, página inicial">
|
||||||
<Logo className="h-7 w-7" />
|
<Logo className="h-7 w-7" />
|
||||||
<span className="font-display text-2xl font-semibold tracking-[-0.045em] text-ink">ESPDocs</span>
|
<span className="font-display text-2xl font-semibold tracking-[-0.045em] text-ink">ESPDocs</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -51,41 +67,45 @@ export default function Footer() {
|
|||||||
Dados públicos, curadoria editorial e links para as fontes oficiais.
|
Dados públicos, curadoria editorial e links para as fontes oficiais.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<Link
|
{isPt && (
|
||||||
href="/livro"
|
<>
|
||||||
className="group mt-7 grid grid-cols-[3rem_1fr_auto] items-center gap-3.5 rounded-2xl border border-line bg-surface p-3.5 transition-colors duration-300 hover:border-line-strong"
|
<Link
|
||||||
>
|
href="/pt/livro"
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
className="group mt-7 grid grid-cols-[3rem_1fr_auto] items-center gap-3.5 rounded-2xl border border-line bg-surface p-3.5 transition-colors duration-300 hover:border-line-strong"
|
||||||
<img
|
>
|
||||||
src={SITE.livroCapa}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
alt=""
|
<img
|
||||||
aria-hidden="true"
|
src={SITE.livroCapa}
|
||||||
width={48}
|
alt=""
|
||||||
height={72}
|
aria-hidden="true"
|
||||||
className="h-[72px] w-12 rounded-md object-cover shadow-md"
|
width={48}
|
||||||
/>
|
height={72}
|
||||||
<span className="min-w-0">
|
className="h-[72px] w-12 rounded-md object-cover shadow-md"
|
||||||
<span className="text-xs font-semibold text-accent">Livro novo</span>
|
/>
|
||||||
<span className="mt-0.5 block font-display text-base font-semibold leading-tight text-ink">Microcontroladores ESP32</span>
|
<span className="min-w-0">
|
||||||
<span className="mt-1 block text-xs text-muted">Conectividade, eficiência e desempenho</span>
|
<span className="text-xs font-semibold text-accent">Livro novo</span>
|
||||||
</span>
|
<span className="mt-0.5 block font-display text-base font-semibold leading-tight text-ink">Microcontroladores ESP32</span>
|
||||||
<ArrowUpRight className="h-4 w-4 text-faint transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-accent" aria-hidden="true" />
|
<span className="mt-1 block text-xs text-muted">Conectividade, eficiência e desempenho</span>
|
||||||
</Link>
|
</span>
|
||||||
|
<ArrowUpRight className="h-4 w-4 text-faint transition-transform duration-300 group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-accent" aria-hidden="true" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm">
|
<div className="mt-4 flex flex-wrap gap-x-5 gap-y-2 text-sm">
|
||||||
<a href={SITE.livroAmazon} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
<a href={SITE.livroAmazon} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
||||||
Amazon
|
Amazon
|
||||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<a href={SITE.livroUiclap} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
<a href={SITE.livroUiclap} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 text-muted transition-colors hover:text-accent">
|
||||||
UICLAP
|
UICLAP
|
||||||
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
<ArrowUpRight className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-8 sm:grid-cols-4">
|
<div className="grid grid-cols-2 gap-8 sm:grid-cols-4">
|
||||||
{COLUMNS.map((column) => (
|
{columns.map((column) => (
|
||||||
<nav key={column.title} aria-label={column.title}>
|
<nav key={column.title} aria-label={column.title}>
|
||||||
<h2 className="text-sm font-semibold text-ink">{column.title}</h2>
|
<h2 className="text-sm font-semibold text-ink">{column.title}</h2>
|
||||||
<ul className="mt-2 space-y-0">
|
<ul className="mt-2 space-y-0">
|
||||||
@@ -102,7 +122,7 @@ export default function Footer() {
|
|||||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
<Link href={link.href} className="inline-flex min-h-11 min-w-11 items-center text-sm text-muted transition-colors hover:text-accent">
|
<Link href={getLinkHref(link.href)} className="inline-flex min-h-11 min-w-11 items-center text-sm text-muted transition-colors hover:text-accent">
|
||||||
{link.label}
|
{link.label}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
@@ -119,45 +139,40 @@ export default function Footer() {
|
|||||||
<br />
|
<br />
|
||||||
<span className="text-xs text-faint">Ciência Embarcada</span>
|
<span className="text-xs text-faint">Ciência Embarcada</span>
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-4 flex items-center gap-2">
|
<div className="mt-2 flex flex-wrap gap-x-4 gap-y-1 text-sm">
|
||||||
<a
|
<a href={SITE.autorUrl} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center text-muted transition-colors hover:text-accent">
|
||||||
href={SITE.repositorio}
|
Site pessoal
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
aria-label="Gitea do projeto (abre em nova aba)"
|
|
||||||
className="grid h-11 w-11 place-items-center rounded-full border border-line text-ink transition-colors hover:border-line-strong hover:bg-surface"
|
|
||||||
>
|
|
||||||
<GiteaMark className="h-4 w-4" />
|
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a href={SITE.autorLinkedin} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center text-muted transition-colors hover:text-accent">
|
||||||
href={SITE.autorLinkedin}
|
LinkedIn
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
aria-label="LinkedIn do autor (abre em nova aba)"
|
|
||||||
className="grid h-11 w-11 place-items-center rounded-full border border-line text-ink transition-colors hover:border-line-strong hover:bg-surface"
|
|
||||||
>
|
|
||||||
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
||||||
<path d="M4.98 3.5a2.5 2.5 0 1 1 0 5.001 2.5 2.5 0 0 1 0-5zM3 8.98h4v12H3v-12zm6.5 0h3.8v1.6h.1c.5-.9 1.8-1.9 3.6-1.9 3.8 0 4.5 2.5 4.5 5.8v6.5h-4v-5.8c0-1.4-.1-3.2-2-3.2-2 0-2.3 1.6-2.3 3.1v5.9h-4v-12z" />
|
|
||||||
</svg>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-14 flex flex-col gap-4 border-t border-line pt-6 text-xs leading-relaxed text-faint md:flex-row md:items-center md:justify-between">
|
<div className="mt-12 flex flex-col items-center justify-between gap-4 border-t border-line pt-8 text-xs text-muted sm:flex-row">
|
||||||
<p>© {year} ESPDocs. Projeto independente, sem afiliação com a Espressif Systems.</p>
|
<p>© {year} ESPDocs · Conteúdo sob licença MIT. Feito para a comunidade de sistemas embarcados.</p>
|
||||||
<p className="flex flex-wrap items-center gap-x-5 gap-y-2">
|
<div className="flex items-center gap-6">
|
||||||
<a href={SITE.blog} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 transition-colors hover:text-accent">
|
<a
|
||||||
|
href={SITE.repositorio}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex min-h-11 items-center gap-1.5 transition-colors hover:text-accent"
|
||||||
|
>
|
||||||
|
<GiteaMark className="h-4 w-4" aria-hidden="true" />
|
||||||
|
<span>Gitea</span>
|
||||||
|
</a>
|
||||||
|
<span>·</span>
|
||||||
|
<a
|
||||||
|
href={SITE.blog}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex min-h-11 items-center transition-colors hover:text-accent"
|
||||||
|
>
|
||||||
Ciência Embarcada
|
Ciência Embarcada
|
||||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
|
||||||
</a>
|
</a>
|
||||||
<a href={SITE.autorUrl} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-11 items-center gap-1 transition-colors hover:text-accent">
|
</div>
|
||||||
lucasrguerra.dev.br
|
|
||||||
<ArrowUpRight className="h-3 w-3" aria-hidden="true" />
|
|
||||||
</a>
|
|
||||||
<span>Fontes oficiais: Espressif Systems.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
+58
-39
@@ -5,6 +5,7 @@ import { createPortal } from "react-dom";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Logo from "@/components/Logo";
|
import Logo from "@/components/Logo";
|
||||||
import SearchDialog from "@/components/SearchDialog";
|
import SearchDialog from "@/components/SearchDialog";
|
||||||
|
import LanguageSelector, { LOCALES, getLocalizedHref } from "@/components/LanguageSelector";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import {
|
import {
|
||||||
@@ -69,12 +70,16 @@ const MOBILE_LINKS = [
|
|||||||
{ href: "/sobre", label: "Sobre" },
|
{ href: "/sobre", label: "Sobre" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function isActivePath(pathname, href) {
|
function isActivePath(pathname, href, currentLocale) {
|
||||||
return href === "/" ? pathname === "/" : pathname === href || String(pathname).startsWith(`${href}/`);
|
const localized = getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
|
||||||
|
if (href === "/" || localized === `/${currentLocale}/` || localized === `/${currentLocale}`) {
|
||||||
|
return pathname === `/${currentLocale}` || pathname === `/${currentLocale}/`;
|
||||||
|
}
|
||||||
|
return pathname === localized || String(pathname).startsWith(`${localized}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname() || "";
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const [openMenu, setOpenMenu] = useState(null);
|
const [openMenu, setOpenMenu] = useState(null);
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
@@ -82,6 +87,12 @@ export default function Header() {
|
|||||||
const closeRef = useRef(null);
|
const closeRef = useRef(null);
|
||||||
const menuButtonRef = useRef(null);
|
const menuButtonRef = useRef(null);
|
||||||
|
|
||||||
|
const currentLocale =
|
||||||
|
LOCALES.find((l) => pathname === `/${l.code}` || pathname.startsWith(`/${l.code}/`))?.code || "pt";
|
||||||
|
const isPt = currentLocale === "pt";
|
||||||
|
|
||||||
|
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
|
||||||
|
|
||||||
useEffect(() => setMounted(true), []);
|
useEffect(() => setMounted(true), []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -118,18 +129,20 @@ export default function Header() {
|
|||||||
};
|
};
|
||||||
}, [drawerOpen]);
|
}, [drawerOpen]);
|
||||||
|
|
||||||
|
const visibleMobileLinks = isPt ? MOBILE_LINKS : MOBILE_LINKS.filter((l) => l.href !== "/livro");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 pt-3 sm:pt-4">
|
<header className="sticky top-0 z-50 pt-3 sm:pt-4">
|
||||||
<div className="shell">
|
<div className="shell">
|
||||||
<div className="nav-pill flex h-16 items-center justify-between gap-3 rounded-full px-3 sm:px-4">
|
<div className="nav-pill flex h-16 items-center justify-between gap-3 rounded-full px-3 sm:px-4">
|
||||||
<Link href="/" className="flex min-h-11 shrink-0 items-center gap-2.5 rounded-full pl-1 pr-2" aria-label="ESPDocs, página inicial">
|
<Link href={getLinkHref("/")} className="flex min-h-11 shrink-0 items-center gap-2.5 rounded-full pl-1 pr-2" aria-label="ESPDocs, página inicial">
|
||||||
<Logo className="h-[30px] w-[30px]" />
|
<Logo className="h-[30px] w-[30px]" />
|
||||||
<span className="font-display text-[1.3rem] font-semibold leading-none tracking-[-0.045em] text-ink">ESPDocs</span>
|
<span className="font-display text-[1.3rem] font-semibold leading-none tracking-[-0.045em] text-ink">ESPDocs</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<nav ref={navRef} className="hidden items-center gap-0.5 lg:flex" aria-label="Navegação principal">
|
<nav ref={navRef} className="hidden items-center gap-0.5 lg:flex" aria-label="Navegação principal">
|
||||||
{MENUS.map((menu) => {
|
{MENUS.map((menu) => {
|
||||||
const active = menu.items.some((item) => isActivePath(pathname, item.href));
|
const active = menu.items.some((item) => isActivePath(pathname, item.href, currentLocale));
|
||||||
const expanded = openMenu === menu.id;
|
const expanded = openMenu === menu.id;
|
||||||
return (
|
return (
|
||||||
<div key={menu.id} className="relative">
|
<div key={menu.id} className="relative">
|
||||||
@@ -150,12 +163,13 @@ export default function Header() {
|
|||||||
>
|
>
|
||||||
{menu.items.map((item) => {
|
{menu.items.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
|
const localizedHref = getLinkHref(item.href);
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={localizedHref}
|
||||||
className="group flex items-start gap-3 rounded-2xl px-3 py-3 transition-colors hover:bg-surface-2"
|
className="group flex items-start gap-3 rounded-2xl px-3 py-3 transition-colors hover:bg-surface-2"
|
||||||
aria-current={isActivePath(pathname, item.href) ? "page" : undefined}
|
aria-current={isActivePath(pathname, item.href, currentLocale) ? "page" : undefined}
|
||||||
>
|
>
|
||||||
<span className="mt-0.5 grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-accent-soft text-accent transition-colors group-hover:bg-surface">
|
<span className="mt-0.5 grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-accent-soft text-accent transition-colors group-hover:bg-surface">
|
||||||
<Icon className="h-4 w-4" aria-hidden="true" />
|
<Icon className="h-4 w-4" aria-hidden="true" />
|
||||||
@@ -173,20 +187,23 @@ export default function Header() {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<Link href="/livro" className={`nav-link ${isActivePath(pathname, "/livro") ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/livro") ? "page" : undefined}>
|
{isPt && (
|
||||||
Livro
|
<Link href={getLinkHref("/livro")} className={`nav-link ${isActivePath(pathname, "/livro", currentLocale) ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/livro", currentLocale) ? "page" : undefined}>
|
||||||
</Link>
|
Livro
|
||||||
<Link href="/sobre" className={`nav-link ${isActivePath(pathname, "/sobre") ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/sobre") ? "page" : undefined}>
|
</Link>
|
||||||
|
)}
|
||||||
|
<Link href={getLinkHref("/sobre")} className={`nav-link ${isActivePath(pathname, "/sobre", currentLocale) ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/sobre", currentLocale) ? "page" : undefined}>
|
||||||
Sobre
|
Sobre
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Link href="/seletor" className="btn btn-primary btn-sm hidden lg:inline-flex">
|
<Link href={getLinkHref("/seletor")} className="btn btn-primary btn-sm hidden lg:inline-flex">
|
||||||
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
||||||
Selecionar chip
|
Selecionar chip
|
||||||
</Link>
|
</Link>
|
||||||
<SearchDialog />
|
<SearchDialog />
|
||||||
|
<LanguageSelector />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<button
|
<button
|
||||||
ref={menuButtonRef}
|
ref={menuButtonRef}
|
||||||
@@ -238,38 +255,40 @@ export default function Header() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-4 pb-2">
|
{isPt && (
|
||||||
<Link
|
<div className="px-4 pb-2">
|
||||||
href="/livro"
|
<Link
|
||||||
onClick={() => setDrawerOpen(false)}
|
href="/pt/livro"
|
||||||
className="group grid grid-cols-[3.25rem_1fr_auto] items-center gap-3 rounded-2xl bg-[#0b0d10] p-3 text-[#f4f6f8] ring-1 ring-white/10"
|
onClick={() => setDrawerOpen(false)}
|
||||||
>
|
className="group grid grid-cols-[3.25rem_1fr_auto] items-center gap-3 rounded-2xl bg-[#0b0d10] p-3 text-[#f4f6f8] ring-1 ring-white/10"
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
>
|
||||||
<img
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
src="/livro/capa.jpg"
|
<img
|
||||||
alt=""
|
src="/livro/capa.jpg"
|
||||||
aria-hidden="true"
|
alt=""
|
||||||
width={52}
|
aria-hidden="true"
|
||||||
height={78}
|
width={52}
|
||||||
className="h-[78px] w-[52px] rounded-md object-cover shadow-lg"
|
height={78}
|
||||||
/>
|
className="h-[78px] w-[52px] rounded-md object-cover shadow-lg"
|
||||||
<span className="min-w-0">
|
/>
|
||||||
<span className="text-[0.6875rem] font-semibold text-[#75cdc1]">Livro novo</span>
|
<span className="min-w-0">
|
||||||
<span className="mt-1 block font-display text-lg font-semibold leading-tight">Microcontroladores ESP32</span>
|
<span className="text-[0.6875rem] font-semibold text-[#75cdc1]">Livro novo</span>
|
||||||
<span className="mt-1 block text-xs text-[#a6afbd]">Conectividade, eficiência e desempenho</span>
|
<span className="mt-1 block font-display text-lg font-semibold leading-tight">Microcontroladores ESP32</span>
|
||||||
</span>
|
<span className="mt-1 block text-xs text-[#a6afbd]">Conectividade, eficiência e desempenho</span>
|
||||||
<ArrowRight className="h-4 w-4 shrink-0 text-[#75cdc1] transition-transform duration-300 group-hover:translate-x-1" aria-hidden="true" />
|
</span>
|
||||||
</Link>
|
<ArrowRight className="h-4 w-4 shrink-0 text-[#75cdc1] transition-transform duration-300 group-hover:translate-x-1" aria-hidden="true" />
|
||||||
</div>
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<nav className="flex-1 overflow-y-auto px-3 py-2" aria-label="Navegação móvel">
|
<nav className="flex-1 overflow-y-auto px-3 py-2" aria-label="Navegação móvel">
|
||||||
{MOBILE_LINKS.map((link) => (
|
{visibleMobileLinks.map((link) => (
|
||||||
<Link
|
<Link
|
||||||
key={link.href}
|
key={link.href}
|
||||||
href={link.href}
|
href={getLinkHref(link.href)}
|
||||||
onClick={() => setDrawerOpen(false)}
|
onClick={() => setDrawerOpen(false)}
|
||||||
className="group flex min-h-11 items-center justify-between gap-3 rounded-xl px-3 text-sm font-medium text-ink-soft transition-colors hover:bg-surface-2 hover:text-ink"
|
className="group flex min-h-11 items-center justify-between gap-3 rounded-xl px-3 text-sm font-medium text-ink-soft transition-colors hover:bg-surface-2 hover:text-ink"
|
||||||
aria-current={isActivePath(pathname, link.href) ? "page" : undefined}
|
aria-current={isActivePath(pathname, link.href, currentLocale) ? "page" : undefined}
|
||||||
>
|
>
|
||||||
{link.label}
|
{link.label}
|
||||||
<ArrowRight className="h-4 w-4 text-faint transition-transform duration-300 group-hover:translate-x-0.5 group-hover:text-accent" aria-hidden="true" />
|
<ArrowRight className="h-4 w-4 text-faint transition-transform duration-300 group-hover:translate-x-0.5 group-hover:text-accent" aria-hidden="true" />
|
||||||
@@ -278,7 +297,7 @@ export default function Header() {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="p-4 pt-2">
|
<div className="p-4 pt-2">
|
||||||
<Link href="/seletor" onClick={() => setDrawerOpen(false)} className="btn btn-primary w-full">
|
<Link href={getLinkHref("/seletor")} onClick={() => setDrawerOpen(false)} className="btn btn-primary w-full">
|
||||||
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
|
||||||
Selecionar meu chip
|
Selecionar meu chip
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { ChevronDown, Globe } from "lucide-react";
|
||||||
|
|
||||||
|
export const LOCALES = [
|
||||||
|
{ code: "pt", label: "Português", short: "PT", lang: "pt-BR" },
|
||||||
|
{ code: "en", label: "English", short: "EN", lang: "en" },
|
||||||
|
{ code: "es", label: "Español", short: "ES", lang: "es-419" },
|
||||||
|
{ code: "fr", label: "Français", short: "FR", lang: "fr" },
|
||||||
|
{ code: "de", label: "Deutsch", short: "DE", lang: "de" },
|
||||||
|
{ code: "zh-CN", label: "简体中文", short: "中", lang: "zh-CN" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const CANONICAL_ROUTES = [
|
||||||
|
{
|
||||||
|
id: "series_item",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:series|serien)\/([^/]+)/,
|
||||||
|
getSlug: (loc, key) => (loc === "de" ? `/serien/${key}` : `/series/${key}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "frameworks_item",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/frameworks\/([^/]+)/,
|
||||||
|
getSlug: (loc, key) => `/frameworks/${key}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "series",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:series|serien)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => (loc === "de" ? "/serien" : "/series"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "catalogo",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:catalogo|catalog|catalogue|katalog)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "de") return "/katalog";
|
||||||
|
if (loc === "fr") return "/catalogue";
|
||||||
|
if (loc === "en" || loc === "zh-CN") return "/catalog";
|
||||||
|
return "/catalogo";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "comparacao",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:comparacao|compare|comparacion|comparaison|vergleich)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "en" || loc === "zh-CN") return "/compare";
|
||||||
|
if (loc === "es") return "/comparacion";
|
||||||
|
if (loc === "fr") return "/comparaison";
|
||||||
|
if (loc === "de") return "/vergleich";
|
||||||
|
return "/comparacao";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "componentes",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:componentes|components|composants|komponenten)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "en" || loc === "zh-CN") return "/components";
|
||||||
|
if (loc === "fr") return "/composants";
|
||||||
|
if (loc === "de") return "/komponenten";
|
||||||
|
return "/componentes";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "diagnostico",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:diagnostico|diagnostics|diagnostic|diagnose)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "en" || loc === "zh-CN") return "/diagnostics";
|
||||||
|
if (loc === "fr") return "/diagnostic";
|
||||||
|
if (loc === "de") return "/diagnose";
|
||||||
|
return "/diagnostico";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "frameworks",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/frameworks(?:\/|$)/,
|
||||||
|
getSlug: () => "/frameworks",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "glossario",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:glossario|glossary|glosario|glossaire|glossar)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "en" || loc === "zh-CN") return "/glossary";
|
||||||
|
if (loc === "es") return "/glosario";
|
||||||
|
if (loc === "fr") return "/glossaire";
|
||||||
|
if (loc === "de") return "/glossar";
|
||||||
|
return "/glossario";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "seletor",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:seletor|selector|selecteur|auswahl)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "en" || loc === "es" || loc === "zh-CN") return "/selector";
|
||||||
|
if (loc === "fr") return "/selecteur";
|
||||||
|
if (loc === "de") return "/auswahl";
|
||||||
|
return "/seletor";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sobre",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/(?:sobre|about|acerca|a-propos|ueber-uns)(?:\/|$)/,
|
||||||
|
getSlug: (loc) => {
|
||||||
|
if (loc === "en" || loc === "zh-CN") return "/about";
|
||||||
|
if (loc === "es") return "/acerca";
|
||||||
|
if (loc === "fr") return "/a-propos";
|
||||||
|
if (loc === "de") return "/ueber-uns";
|
||||||
|
return "/sobre";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "livro",
|
||||||
|
match: /^\/(?:pt|en|es|fr|de|zh-CN)\/livro(?:\/|$)/,
|
||||||
|
getSlug: (loc) => (loc === "pt" ? "/livro" : ""),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getLocalizedHref(pathname, targetLocale) {
|
||||||
|
if (!pathname || pathname === "/" || pathname === `/${targetLocale}` || pathname === `/${targetLocale}/`) {
|
||||||
|
return `/${targetLocale}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const route of CANONICAL_ROUTES) {
|
||||||
|
const m = pathname.match(route.match);
|
||||||
|
if (m) {
|
||||||
|
const slug = route.getSlug(targetLocale, m[1]);
|
||||||
|
return slug ? `/${targetLocale}${slug}` : `/${targetLocale}/`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `/${targetLocale}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LanguageSelector() {
|
||||||
|
const pathname = usePathname() || "";
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [hash, setHash] = useState("");
|
||||||
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
|
// Detecta locale da URL atual
|
||||||
|
const currentLocaleCode =
|
||||||
|
LOCALES.find((l) => pathname === `/${l.code}` || pathname.startsWith(`/${l.code}/`))?.code || "pt";
|
||||||
|
|
||||||
|
const currentLocale = LOCALES.find((l) => l.code === currentLocaleCode) || LOCALES[0];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
setHash(window.location.hash || "");
|
||||||
|
}
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onPointerDown(e) {
|
||||||
|
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onKeyDown(e) {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener("mousedown", onPointerDown);
|
||||||
|
document.addEventListener("keydown", onKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", onPointerDown);
|
||||||
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSelect = (code) => {
|
||||||
|
if (typeof document !== "undefined") {
|
||||||
|
document.cookie = `NEXT_LOCALE=${code}; path=/; max-age=31536000; SameSite=Lax`;
|
||||||
|
}
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="relative">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setOpen(!open)}
|
||||||
|
className="flex h-11 min-w-[44px] items-center gap-1.5 rounded-full border border-line bg-surface px-3 text-sm font-semibold text-ink transition-colors hover:bg-surface-2 focus-visible:outline-2 focus-visible:outline-accent"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-label={`Idioma atual: ${currentLocale.label}. Clique para trocar de idioma.`}
|
||||||
|
title={`Idioma: ${currentLocale.label}`}
|
||||||
|
>
|
||||||
|
<Globe className="h-4 w-4 text-muted" aria-hidden="true" />
|
||||||
|
<span className="font-semibold">{currentLocale.short}</span>
|
||||||
|
<ChevronDown
|
||||||
|
className={`h-3 w-3 text-muted transition-transform duration-200 ${open ? "rotate-180" : ""}`}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{open && (
|
||||||
|
<div
|
||||||
|
role="listbox"
|
||||||
|
aria-label="Idiomas disponíveis"
|
||||||
|
className="absolute right-0 top-full mt-2 w-48 rounded-2xl border border-line bg-surface p-1.5 shadow-[0_24px_60px_-20px_rgba(15,23,42,0.35)] dark:shadow-[0_24px_60px_-20px_rgba(0,0,0,0.8)] z-50"
|
||||||
|
>
|
||||||
|
{LOCALES.map((loc) => {
|
||||||
|
const isActive = loc.code === currentLocaleCode;
|
||||||
|
const href = `${getLocalizedHref(pathname, loc.code)}${hash}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={loc.code}
|
||||||
|
href={href}
|
||||||
|
lang={loc.lang}
|
||||||
|
hrefLang={loc.lang}
|
||||||
|
role="option"
|
||||||
|
aria-selected={isActive}
|
||||||
|
aria-current={isActive ? "true" : undefined}
|
||||||
|
onClick={() => handleSelect(loc.code)}
|
||||||
|
className={`flex min-h-[44px] items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors ${
|
||||||
|
isActive
|
||||||
|
? "bg-accent-soft text-accent font-semibold"
|
||||||
|
: "text-ink-soft hover:bg-surface-2 hover:text-ink"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>{loc.label}</span>
|
||||||
|
<span className={`text-xs ${isActive ? "text-accent" : "text-muted"}`}>
|
||||||
|
{loc.short}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { createNavigation } from "next-intl/navigation";
|
||||||
|
import { routing } from "./routing";
|
||||||
|
|
||||||
|
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { getRequestConfig } from "next-intl/server";
|
||||||
|
import { hasLocale } from "next-intl";
|
||||||
|
import { routing } from "./routing";
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
let locale = await requestLocale;
|
||||||
|
if (!locale || !hasLocale(routing.locales, locale)) {
|
||||||
|
locale = routing.defaultLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
let messages = {};
|
||||||
|
try {
|
||||||
|
messages = (await import(`../messages/${locale}.json`)).default;
|
||||||
|
} catch {
|
||||||
|
try {
|
||||||
|
messages = (await import("../messages/pt.json")).default;
|
||||||
|
} catch {
|
||||||
|
messages = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
});
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
import { defineRouting } from "next-intl/routing";
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
locales: ["pt", "en", "es", "fr", "de", "zh-CN"],
|
||||||
|
defaultLocale: "en",
|
||||||
|
localePrefix: "always",
|
||||||
|
pathnames: {
|
||||||
|
"/": "/",
|
||||||
|
"/series": {
|
||||||
|
pt: "/series",
|
||||||
|
en: "/series",
|
||||||
|
es: "/series",
|
||||||
|
fr: "/series",
|
||||||
|
de: "/serien",
|
||||||
|
"zh-CN": "/series",
|
||||||
|
},
|
||||||
|
"/series/[key]": {
|
||||||
|
pt: "/series/[key]",
|
||||||
|
en: "/series/[key]",
|
||||||
|
es: "/series/[key]",
|
||||||
|
fr: "/series/[key]",
|
||||||
|
de: "/serien/[key]",
|
||||||
|
"zh-CN": "/series/[key]",
|
||||||
|
},
|
||||||
|
"/catalogo": {
|
||||||
|
pt: "/catalogo",
|
||||||
|
en: "/catalog",
|
||||||
|
es: "/catalogo",
|
||||||
|
fr: "/catalogue",
|
||||||
|
de: "/katalog",
|
||||||
|
"zh-CN": "/catalog",
|
||||||
|
},
|
||||||
|
"/comparacao": {
|
||||||
|
pt: "/comparacao",
|
||||||
|
en: "/compare",
|
||||||
|
es: "/comparacion",
|
||||||
|
fr: "/comparaison",
|
||||||
|
de: "/vergleich",
|
||||||
|
"zh-CN": "/compare",
|
||||||
|
},
|
||||||
|
"/componentes": {
|
||||||
|
pt: "/componentes",
|
||||||
|
en: "/components",
|
||||||
|
es: "/componentes",
|
||||||
|
fr: "/composants",
|
||||||
|
de: "/komponenten",
|
||||||
|
"zh-CN": "/components",
|
||||||
|
},
|
||||||
|
"/diagnostico": {
|
||||||
|
pt: "/diagnostico",
|
||||||
|
en: "/diagnostics",
|
||||||
|
es: "/diagnostico",
|
||||||
|
fr: "/diagnostic",
|
||||||
|
de: "/diagnose",
|
||||||
|
"zh-CN": "/diagnostics",
|
||||||
|
},
|
||||||
|
"/frameworks": {
|
||||||
|
pt: "/frameworks",
|
||||||
|
en: "/frameworks",
|
||||||
|
es: "/frameworks",
|
||||||
|
fr: "/frameworks",
|
||||||
|
de: "/frameworks",
|
||||||
|
"zh-CN": "/frameworks",
|
||||||
|
},
|
||||||
|
"/frameworks/[key]": {
|
||||||
|
pt: "/frameworks/[key]",
|
||||||
|
en: "/frameworks/[key]",
|
||||||
|
es: "/frameworks/[key]",
|
||||||
|
fr: "/frameworks/[key]",
|
||||||
|
de: "/frameworks/[key]",
|
||||||
|
"zh-CN": "/frameworks/[key]",
|
||||||
|
},
|
||||||
|
"/glossario": {
|
||||||
|
pt: "/glossario",
|
||||||
|
en: "/glossary",
|
||||||
|
es: "/glosario",
|
||||||
|
fr: "/glossaire",
|
||||||
|
de: "/glossar",
|
||||||
|
"zh-CN": "/glossary",
|
||||||
|
},
|
||||||
|
"/seletor": {
|
||||||
|
pt: "/seletor",
|
||||||
|
en: "/selector",
|
||||||
|
es: "/selector",
|
||||||
|
fr: "/selecteur",
|
||||||
|
de: "/auswahl",
|
||||||
|
"zh-CN": "/selector",
|
||||||
|
},
|
||||||
|
"/sobre": {
|
||||||
|
pt: "/sobre",
|
||||||
|
en: "/about",
|
||||||
|
es: "/acerca",
|
||||||
|
fr: "/a-propos",
|
||||||
|
de: "/ueber-uns",
|
||||||
|
"zh-CN": "/about",
|
||||||
|
},
|
||||||
|
"/livro": {
|
||||||
|
pt: "/livro",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
import createMiddleware from "next-intl/middleware";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { routing } from "./i18n/routing";
|
||||||
|
|
||||||
|
const intlMiddleware = createMiddleware(routing);
|
||||||
|
|
||||||
|
const SUPPORTED_LOCALES = routing.locales;
|
||||||
|
|
||||||
|
function parseAcceptLanguage(header) {
|
||||||
|
if (!header) return [];
|
||||||
|
return header
|
||||||
|
.split(",")
|
||||||
|
.map((part) => {
|
||||||
|
const [langRange, qPart] = part.trim().split(";");
|
||||||
|
let q = 1.0;
|
||||||
|
if (qPart) {
|
||||||
|
const match = qPart.match(/q=([\d.]+)/);
|
||||||
|
if (match) {
|
||||||
|
const parsed = parseFloat(match[1]);
|
||||||
|
if (!isNaN(parsed)) q = parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { lang: langRange.trim().toLowerCase(), q };
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.q - a.q);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchLocale(lang) {
|
||||||
|
if (lang.startsWith("pt")) return "pt";
|
||||||
|
if (lang.startsWith("es")) return "es";
|
||||||
|
if (lang.startsWith("fr")) return "fr";
|
||||||
|
if (lang.startsWith("de")) return "de";
|
||||||
|
if (lang.startsWith("zh")) return "zh-CN";
|
||||||
|
if (lang.startsWith("en")) return "en";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveRootLocale(request) {
|
||||||
|
const cookieValue = request.cookies.get("NEXT_LOCALE")?.value;
|
||||||
|
if (cookieValue && SUPPORTED_LOCALES.includes(cookieValue)) {
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
const acceptLang = request.headers.get("accept-language");
|
||||||
|
const parsed = parseAcceptLanguage(acceptLang);
|
||||||
|
for (const { lang } of parsed) {
|
||||||
|
const matched = matchLocale(lang);
|
||||||
|
if (matched) return matched;
|
||||||
|
}
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function middleware(request) {
|
||||||
|
const { pathname, search } = request.nextUrl;
|
||||||
|
|
||||||
|
// Exclusões de caminhos e arquivos estáticos
|
||||||
|
if (
|
||||||
|
pathname.startsWith("/api") ||
|
||||||
|
pathname.startsWith("/_next") ||
|
||||||
|
pathname === "/robots.txt" ||
|
||||||
|
pathname === "/sitemap.xml" ||
|
||||||
|
pathname === "/google21a790a2c23c0ec5.html" ||
|
||||||
|
/\.[a-zA-Z0-9]+$/.test(pathname)
|
||||||
|
) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Raiz: negociação de idioma (307)
|
||||||
|
if (pathname === "/") {
|
||||||
|
const targetLocale = resolveRootLocale(request);
|
||||||
|
const url = new URL(`/${targetLocale}/`, request.url);
|
||||||
|
url.search = search;
|
||||||
|
const response = NextResponse.redirect(url, 307);
|
||||||
|
response.headers.set("Vary", "Accept-Language, Cookie");
|
||||||
|
response.headers.set("Cache-Control", "private, no-store");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Livro fora do português: 404
|
||||||
|
if (/^\/(en|es|fr|de|zh-CN)\/livro(\/.*)?$/.test(pathname)) {
|
||||||
|
return new NextResponse(null, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. URLs antigas sem prefixo reconhecido: redirecionamento 308 para /pt/<caminho>
|
||||||
|
const hasLocalePrefix = SUPPORTED_LOCALES.some(
|
||||||
|
(loc) => pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasLocalePrefix) {
|
||||||
|
const url = new URL(`/pt${pathname}`, request.url);
|
||||||
|
url.search = search;
|
||||||
|
return NextResponse.redirect(url, 308);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Delegação para o roteamento do next-intl
|
||||||
|
return intlMiddleware(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: [
|
||||||
|
"/((?!api|_next|_vercel|.*\\..*|robots\\.txt|sitemap\\.xml|google21a790a2c23c0ec5\\.html).*)",
|
||||||
|
],
|
||||||
|
};
|
||||||
+6
-1
@@ -1,3 +1,7 @@
|
|||||||
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin("./i18n/request.js");
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
// A imagem Docker liga isto (NEXT_OUTPUT=standalone) para empacotar só o que
|
// A imagem Docker liga isto (NEXT_OUTPUT=standalone) para empacotar só o que
|
||||||
@@ -13,4 +17,5 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default withNextIntl(nextConfig);
|
||||||
|
|
||||||
Generated
+779
-3
@@ -11,6 +11,7 @@
|
|||||||
"googleapis": "^165.0.0",
|
"googleapis": "^165.0.0",
|
||||||
"lucide-react": "^1.16.0",
|
"lucide-react": "^1.16.0",
|
||||||
"next": "^15.5.28",
|
"next": "^15.5.28",
|
||||||
|
"next-intl": "^4.14.9",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0"
|
"react-dom": "19.1.0"
|
||||||
@@ -37,6 +38,31 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eloqnt/config": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eloqnt/config/-/config-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-YRDigSaiJEzKDfzTth5kG+L/PKfB3mo8h7yNzpXExPfvQmcD05WoZT57PdhJE0jI+Vva6eb0OCU8hnHHgdME3g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@eloqnt/format-json": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eloqnt/format-json/-/format-json-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-4Pkb+HyzgWNJXoU48aJhUyNZSjZvMGooPCNG1ZFmBm55xTiNNPQCNXH66Ob6sXS6RvbWZrRpCf+Uo/Jz9K9g+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eloqnt/config": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@eloqnt/format-po": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eloqnt/format-po/-/format-po-0.1.1.tgz",
|
||||||
|
"integrity": "sha512-aLtmIoqXGEaeTT0RcsLN8sZ3aIrsT/c/dVs28zrLhDjO3+vA3KNP+r98N0xq8w/k36+/GIUt4uZNbqcA71GVWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eloqnt/config": "^0.1.1",
|
||||||
|
"po-parser": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.10.0",
|
"version": "1.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||||
@@ -214,6 +240,36 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "3.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.7.tgz",
|
||||||
|
"integrity": "sha512-zXfhLpvA6T7+efdt9JLbBwZ00tT7NsBMDVnDu8rpHeNNv8KfRZAMo2gkG0k9lK/Nzc//3kJ9pImsfuJxk3KhUA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "3.5.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.22.tgz",
|
||||||
|
"integrity": "sha512-YuwN+dDKgssfuYwtOyGQD0CSn7K46g9cv1Fp0VgKpjZDDFvv8b8crzP990IcJhRPWHhKzCGsbUSJVIguyyFA5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-skeleton-parser": "2.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "2.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.12.tgz",
|
||||||
|
"integrity": "sha512-ca7tZMLpfHX3sUD7hP6gE/qr6Iq89EDd3ZLeRhbsB7r0UD55DGJq+tmFLzrGrnx5StahUevQmQQVtbDRHsfDHA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.8.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.14.tgz",
|
||||||
|
"integrity": "sha512-8O7V38QmbB11+ryJ3KM/znrzXtiqgpXzqNoqDsndHZZ5/zDKTTL+cf5F0mzXkQEwhx8IlWnqYvEovf2S5EwLTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -1108,6 +1164,310 @@
|
|||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@parcel/watcher": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.0.3",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"node-addon-api": "^7.0.0",
|
||||||
|
"picomatch": "^4.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@parcel/watcher-android-arm64": "2.6.0",
|
||||||
|
"@parcel/watcher-darwin-arm64": "2.6.0",
|
||||||
|
"@parcel/watcher-darwin-x64": "2.6.0",
|
||||||
|
"@parcel/watcher-freebsd-x64": "2.6.0",
|
||||||
|
"@parcel/watcher-linux-arm-glibc": "2.6.0",
|
||||||
|
"@parcel/watcher-linux-arm-musl": "2.6.0",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc": "2.6.0",
|
||||||
|
"@parcel/watcher-linux-arm64-musl": "2.6.0",
|
||||||
|
"@parcel/watcher-linux-x64-glibc": "2.6.0",
|
||||||
|
"@parcel/watcher-linux-x64-musl": "2.6.0",
|
||||||
|
"@parcel/watcher-win32-arm64": "2.6.0",
|
||||||
|
"@parcel/watcher-win32-x64": "2.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-android-arm64": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-darwin-x64": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-arm64": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher-win32-x64": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/parcel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@parcel/watcher/node_modules/picomatch": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
@@ -1161,6 +1521,228 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@schummar/icu-type-parser": {
|
||||||
|
"version": "1.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||||
|
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-arm64": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-VRreJpbRygpSD1hj3HBEOIA1xjXYnhB+IfcKsxZRhCIBGRP4UkqxZH4/ipFh/heIRp7EKQ56E8ygwEftLNCWXw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-darwin-x64": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-KqCJ04pMYDBUYpAOFGUFR8TEjqo4h0Vtwe/za9xNRDKsxSDnjgM7HVhfO0uNNpMMlM2ao2E6d0iLpApIRvNGnA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-tLkw3uWxQj69qBRX5Xm8vbq+5iX5e/ktoPXhrDeFFa2jNMc+AWcxa0bqwatvVGK40v5mcmz6Ulu5Nk9jF8ciMA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-lFHVlHB0B5C7FwyprWJQp7b2iF/ohj2RqnMiv8uCnFJc+rYP0Py2I7ftZ+rOX4Zu7Ra9eMk7GnmuxAywCZYUJQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-arm64-musl": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-gXDgrMMDsWT7r5gZsml10snBS9yaoVYwU9vGffa+DAts4ctEMrMHv+G8AH+ZxCV9gr5MfdgtGGQKjXjLay4mXQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-ppc64-gnu": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-hZF7J2YzXiADZ4eQxTFOWJcSSy3fbUR8/DcCXqiBHh0bVyLl2Y+O5pVWxk/r/XV5tA9n7DnnLlvkPPsHoajE9w==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-s390x-gnu": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-rmtGwkAlbl+fQMGrGUMklUmByueOmt1ju9sKsE5YWVA0KfFYM0W87WVx4lFYQXt+E4P3zkLjeqo/AUa5mFbF1A==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-gnu": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-EUJsyrFw3aNE64iQzk3bDqjADgl4l0vpxfwVMjMuZIooNg35yKqvkw3bYNEhQyrBDY0+5kKoiirGLpth3Wf3lA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-linux-x64-musl": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-ZXUnLjxTHxvNhVmQmYlb8XNKArfi/MiGoW5DmfuME8ttCLH9OJTqJVFy7g1yYxUCgw3tU6dF1uU59N8P/Yr5Pw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-jnnQpAHiWZgEobVfOPvJJzm1Jh4ZEIKh2IqcDqHv6zJcjvaEkHdkt+qdRsJ9AqvK7g18WBRHTnvKm5IxdbkagA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-TaVTnNm9c1I+VG3hJUt11uspwbTrTQ9+fUPzODlvx4tj/ChYqUm4cKTIHw3O97hILxpmhBR0hbvLEs9seOV59A==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/core-win32-x64-msvc": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-aPv58Clf6SdAQtouzDYKbEtcCETY2hOXlrHgSFnFIJQOlMWINHSGazaM1Iv63/9wOUDPq6+0ZFytMqnPtER9OQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0 AND MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@swc/counter": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@@ -1170,6 +1752,15 @@
|
|||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@swc/types": {
|
||||||
|
"version": "0.1.28",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz",
|
||||||
|
"integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
|
||||||
@@ -2699,6 +3290,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/content-type": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -2847,7 +3451,6 @@
|
|||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"devOptional": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -4233,6 +4836,21 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/icu-minify": {
|
||||||
|
"version": "4.14.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.14.9.tgz",
|
||||||
|
"integrity": "sha512-PmO+fNnC3K3mfPKTnm3RrPERrhQZnjm1AlpyZQ0ShbXi8Y/kYZ3YOde8TEjCLz9k3P2DmzkBn8lykGrCAi1Ajw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-messageformat-parser": "^3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -4285,6 +4903,31 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "11.2.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.15.tgz",
|
||||||
|
"integrity": "sha512-W4VNytsXyWgW5lvVchmsb9xgA5rSKqoflu3/IQ9UCtB0z4R+Bx9MoEkwFT/+30RCl/KzUoH0QGGe9lppF5SuvQ==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "3.1.7",
|
||||||
|
"@formatjs/icu-messageformat-parser": "3.5.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/intl-messageformat/node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "3.5.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.18.tgz",
|
||||||
|
"integrity": "sha512-wX1efcL8d7K5QapOTRsP/OLTA/kpwe4LwqWyNxDNg5iRs8i9jJbCyxB5P8jc4KOW5oiLzz2DJr+BRY5xMV9+bA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/icu-skeleton-parser": "2.1.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/intl-messageformat/node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "2.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.11.tgz",
|
||||||
|
"integrity": "sha512-j8cUmOJzVgkHuS0QiQ6ga76UIoLOFSAMWhs7aZJztH3aAdCOAE6vpC8KVvFB4cU10ON0y2/5oOVmPJ43s2lTwA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
@@ -4450,7 +5093,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -4505,7 +5147,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -5386,6 +6027,22 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"content-type": "^2.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.5.28",
|
"version": "15.5.28",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.5.28.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.5.28.tgz",
|
||||||
@@ -5438,6 +6095,86 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl": {
|
||||||
|
"version": "4.14.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.14.9.tgz",
|
||||||
|
"integrity": "sha512-uauRtUC+TrYNA1OL/QcwL9irndLrI9jK3Wdaa9W0B5AeKh9cqVdNeT0IWgLnhBqBRrVwppVdI0M+C1+JdGPQ3w==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@eloqnt/config": "^0.1.1",
|
||||||
|
"@eloqnt/format-json": "^0.1.0",
|
||||||
|
"@eloqnt/format-po": "^0.1.1",
|
||||||
|
"@formatjs/intl-localematcher": "^0.8.1",
|
||||||
|
"@parcel/watcher": "^2.4.1",
|
||||||
|
"@swc/core": "~1.16.0",
|
||||||
|
"icu-minify": "^4.14.9",
|
||||||
|
"negotiator": "^1.0.0",
|
||||||
|
"next-intl-swc-plugin-extractor": "4.14.9",
|
||||||
|
"use-intl": "^4.14.9"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-intl-swc-plugin-extractor": {
|
||||||
|
"version": "4.14.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.14.9.tgz",
|
||||||
|
"integrity": "sha512-8jAkmDwQJo6RDavBHm9QRl1YTn2WXf+rI4SVhMaZxZcxopnwZic6fKTXOoNIo3kzC+a6vJySkOkXmHTVFLAYtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/next-intl/node_modules/@swc/core": {
|
||||||
|
"version": "1.16.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.21.tgz",
|
||||||
|
"integrity": "sha512-sE2asan0US6gcp11oBDFJb5+s+ltaAPBcagVQGub1+qo+8TsQET2TmXfJCw/Oy0w1eEPp1rV1wsR/Umx2KXGcw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@swc/counter": "^0.1.3",
|
||||||
|
"@swc/types": "^0.1.28"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/swc"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@swc/core-darwin-arm64": "1.16.21",
|
||||||
|
"@swc/core-darwin-x64": "1.16.21",
|
||||||
|
"@swc/core-linux-arm-gnueabihf": "1.16.21",
|
||||||
|
"@swc/core-linux-arm64-gnu": "1.16.21",
|
||||||
|
"@swc/core-linux-arm64-musl": "1.16.21",
|
||||||
|
"@swc/core-linux-ppc64-gnu": "1.16.21",
|
||||||
|
"@swc/core-linux-s390x-gnu": "1.16.21",
|
||||||
|
"@swc/core-linux-x64-gnu": "1.16.21",
|
||||||
|
"@swc/core-linux-x64-musl": "1.16.21",
|
||||||
|
"@swc/core-win32-arm64-msvc": "1.16.21",
|
||||||
|
"@swc/core-win32-ia32-msvc": "1.16.21",
|
||||||
|
"@swc/core-win32-x64-msvc": "1.16.21"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@swc/helpers": ">=0.5.17"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/helpers": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next-themes": {
|
"node_modules/next-themes": {
|
||||||
"version": "0.4.6",
|
"version": "0.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
|
||||||
@@ -5448,6 +6185,12 @@
|
|||||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-addon-api": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-domexception": {
|
"node_modules/node-domexception": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||||
@@ -5786,6 +6529,12 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/po-parser": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-NdTrKgh0oO7+y+RFX8KKhOB438x/j94UGXEFzl/7pHH0JjWSn42iJ9tgmPksIO6n1JKDHmNDcejPJfKspljB9g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/possible-typed-array-names": {
|
"node_modules/possible-typed-array-names": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||||
@@ -7009,6 +7758,33 @@
|
|||||||
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
|
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
|
||||||
"license": "BSD"
|
"license": "BSD"
|
||||||
},
|
},
|
||||||
|
"node_modules/use-intl": {
|
||||||
|
"version": "4.14.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.14.9.tgz",
|
||||||
|
"integrity": "sha512-37ktvdCCz+6JY2VFPl/GV69Ds4ahiUT2c2xEZZRZYxdgGLxFsUbdxzjQVRm3YMIOUU0VPs6zvlifb4Yjk4IxIg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "^3.1.0",
|
||||||
|
"@schummar/icu-type-parser": "1.21.5",
|
||||||
|
"icu-minify": "^4.14.9",
|
||||||
|
"intl-messageformat": "^11.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/web-streams-polyfill": {
|
"node_modules/web-streams-polyfill": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"googleapis": "^165.0.0",
|
"googleapis": "^165.0.0",
|
||||||
"lucide-react": "^1.16.0",
|
"lucide-react": "^1.16.0",
|
||||||
"next": "^15.5.28",
|
"next": "^15.5.28",
|
||||||
|
"next-intl": "^4.14.9",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0"
|
"react-dom": "19.1.0"
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ function obterRotasExistentes() {
|
|||||||
if (arq.endsWith("page.jsx") || arq.endsWith("page.js")) {
|
if (arq.endsWith("page.jsx") || arq.endsWith("page.js")) {
|
||||||
let rota = arq
|
let rota = arq
|
||||||
.replace(/^app/, "")
|
.replace(/^app/, "")
|
||||||
|
.replace(/\/\[locale\]/, "")
|
||||||
.replace(/\/page\.(jsx|js)$/, "");
|
.replace(/\/page\.(jsx|js)$/, "");
|
||||||
if (rota === "") rota = "/";
|
if (rota === "") rota = "/";
|
||||||
rotas.add(rota);
|
rotas.add(rota);
|
||||||
@@ -132,7 +133,12 @@ function auditarPerformanceConfigs() {
|
|||||||
|
|
||||||
// Checar globals.css e layout.jsx para transições no body
|
// Checar globals.css e layout.jsx para transições no body
|
||||||
const globalsCss = existsSync("app/globals.css") ? readFileSync("app/globals.css", "utf8") : "";
|
const globalsCss = existsSync("app/globals.css") ? readFileSync("app/globals.css", "utf8") : "";
|
||||||
const layoutJsx = existsSync("app/layout.jsx") ? readFileSync("app/layout.jsx", "utf8") : "";
|
const layoutPath = existsSync("app/[locale]/layout.jsx")
|
||||||
|
? "app/[locale]/layout.jsx"
|
||||||
|
: existsSync("app/layout.jsx")
|
||||||
|
? "app/layout.jsx"
|
||||||
|
: null;
|
||||||
|
const layoutJsx = layoutPath ? readFileSync(layoutPath, "utf8") : "";
|
||||||
|
|
||||||
if (globalsCss.includes("body {\n\t\t@apply bg-slate-50 text-slate-900 transition-colors")) {
|
if (globalsCss.includes("body {\n\t\t@apply bg-slate-50 text-slate-900 transition-colors")) {
|
||||||
falha("globals.css tem transition-colors global no body (provoca animações não compostas no load).");
|
falha("globals.css tem transition-colors global no body (provoca animações não compostas no load).");
|
||||||
@@ -299,7 +305,8 @@ function auditarRotasInternas() {
|
|||||||
if (caminho.startsWith("/api") || caminho.startsWith("/favicon") || caminho.startsWith("/icon") || caminho.startsWith("/logo") || caminho.startsWith("/site.webmanifest")) {
|
if (caminho.startsWith("/api") || caminho.startsWith("/favicon") || caminho.startsWith("/icon") || caminho.startsWith("/logo") || caminho.startsWith("/site.webmanifest")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const existe = rotasValidas.has(caminho) || rotasDinamicas.some((rx) => rx.test(caminho));
|
const caminhoSemLocale = caminho.replace(/^\/(?:pt|en|es|fr|de|zh-CN)(?=\/|$)/, "") || "/";
|
||||||
|
const existe = rotasValidas.has(caminho) || rotasValidas.has(caminhoSemLocale) || rotasDinamicas.some((rx) => rx.test(caminho) || rx.test(caminhoSemLocale));
|
||||||
if (!existe) {
|
if (!existe) {
|
||||||
falha(`${arq} -> Link interno aponta para rota inexistente: '${caminho}'`);
|
falha(`${arq} -> Link interno aponta para rota inexistente: '${caminho}'`);
|
||||||
rotasQuebradas++;
|
rotasQuebradas++;
|
||||||
|
|||||||
Reference in new issue
Block a user