Files
ESPDocs/middleware.js
T
Lucas RayanandClaude Opus 5.5 1860e34cbb
Release / Plano (reaproveitar a imagem?) (push) Successful in 7s
CI / Lint e auditorias (push) Successful in 1m48s
CI / Trivy (dependências e segredos) (push) Successful in 12s
CI / Imagem Docker (linux/arm64) (push) Failing after 2m27s
Release / ci (push) Failing after 4m31s
Release / Publica a imagem (linux/arm64) (push) Skipped
Release / Cria a release no Gitea (push) Skipped
feat(formacoes): módulo 2 (DHT22 + LCD 16x2 no Wokwi), PDFs e página de entrada
- Formação 2 da LASER: slides visuais, apostila, projeto do Wokwi e scanner
  I2C, validados no simulador (leitura normal, temperatura negativa e sensor
  desconectado). Circuito com pull-ups de 10 kΩ e 4,7 kΩ para 3V3 e aviso
  sobre os pull-ups de 5 V da plaquinha PCF8574.
- PDFs prontos dos slides e das apostilas dos módulos 1 e 2;
  slides-pdf agora também exporta a apostila.
- Página de entrada em /formacoes e /formacoes/N, gerada por
  scripts/formacoes-viewer.mjs a partir do formacao.json de cada pasta.
- A apostila passou de index.html para apostila.html.
- Corrige o link do Fritzing da apostila 1 (/apostilas/1 não existe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 13:11:28 -03:00

137 lines
4.4 KiB
JavaScript

import createMiddleware from "next-intl/middleware";
import { NextResponse } from "next/server";
import { routing } from "./i18n/routing";
import { ALL_LOCALE_CODES, ENABLED_LOCALES, getLocalizedHref } from "./i18n/links";
const intlMiddleware = createMiddleware(routing);
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 && ENABLED_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 && ENABLED_LOCALES.includes(matched)) {
return matched;
}
}
return ENABLED_LOCALES.includes("en") ? "en" : (ENABLED_LOCALES[0] || "pt");
}
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();
}
// Material das formações: /formacoes e /formacoes/N mostram a página de
// entrada gerada em public/formacoes. Sem isto a rota cairia na regra de
// URL antiga e iria para /pt/formacoes, que não existe.
if (/^\/formacoes(\/\d+)?\/?$/.test(pathname)) {
const url = request.nextUrl.clone();
url.pathname = `${pathname.replace(/\/$/, "")}/index.html`;
return NextResponse.rewrite(url);
}
// 1. Raiz: negociação de idioma (307) considerando apenas idiomas ATIVOS
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. Idiomas INATIVOS: prefixos (/es, /fr, /de, /zh-CN etc.)
// Redirecionam com 307 para o mesmo caminho no idioma fallback (en se ativo, senão pt) quando o slug existir lá.
const inactiveLocales = ALL_LOCALE_CODES.filter((l) => !ENABLED_LOCALES.includes(l));
if (inactiveLocales.length > 0) {
const isInactive = inactiveLocales.some(
(loc) => pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)
);
if (isInactive) {
const fallbackLocale = ENABLED_LOCALES.includes("en") ? "en" : "pt";
const targetPath = getLocalizedHref(pathname, fallbackLocale);
const url = new URL(targetPath, request.url);
if (search && !targetPath.includes("?")) {
url.search = search;
}
return NextResponse.redirect(url, 307);
}
}
// 3. Livro fora do português para idiomas ATIVOS que não são pt: 404
const activeNonPt = ENABLED_LOCALES.filter((l) => l !== "pt");
if (activeNonPt.length > 0) {
const isBookInOtherActiveLocale = activeNonPt.some(
(loc) => pathname === `/${loc}/livro` || pathname.startsWith(`/${loc}/livro/`)
);
if (isBookInOtherActiveLocale) {
return new NextResponse(null, { status: 404 });
}
}
// 4. URLs antigas sem prefixo reconhecido: redirecionamento 308 para /pt/<caminho>
const hasActivePrefix = ENABLED_LOCALES.some(
(loc) => pathname === `/${loc}` || pathname.startsWith(`/${loc}/`)
);
if (!hasActivePrefix) {
const url = new URL(`/pt${pathname}`, request.url);
url.search = search;
return NextResponse.redirect(url, 308);
}
// 5. Delegação para o roteamento do next-intl para idiomas ativos
return intlMiddleware(request);
}
export const config = {
matcher: [
"/((?!api|_next|_vercel|.*\\..*|robots\\.txt|sitemap\\.xml|google21a790a2c23c0ec5\\.html).*)",
],
};