/** * Fonte única das informações de SEO do ESPDocs. * * Centralizar aqui evita o problema clássico de metadata espalhado: a URL do * site aparecendo com valores diferentes em cada arquivo, ou uma página nova * nascendo sem título próprio. */ import { ENABLED_LOCALES, getLocalizedHref } from "@/i18n/links"; export const SITE = { url: "https://espdocs.cienciaembarcada.com.br", nome: "ESPDocs", autor: "Lucas Rayan Guerra", autorUrl: "https://lucasrguerra.dev.br", autorLinkedin: "https://linkedin.com/in/lucasrguerra", autorEmail: "contato@lucasrguerra.dev.br", repositorio: "https://git.lucasrguerra.dev.br/lucasrguerra/ESPDocs", blog: "https://cienciaembarcada.com.br", livroTitulo: "Microcontroladores ESP32", livroSubtitulo: "Conectividade, Eficiência e Desempenho", livroIsbn: "978-65-02-43432-1", livroCapa: "/livro/capa.jpg", livroAmazon: "https://a.co/d/0fdCvzlg", livroUiclap: "https://loja.uiclap.com/titulo/ua213708", }; /** * Monta o objeto `metadata` de uma página, já com canonical e Open Graph. * Usar isto em vez de escrever o objeto à mão garante que nenhuma página fique * sem canonical ou sem og:title. */ export function paginaMeta({ titulo, descricao, caminho, keywords = [], tipo = "website", locale = "pt" }) { const currentLocale = locale || "pt"; const basePath = caminho.startsWith("/") ? caminho : `/${caminho}`; const canonicalPath = getLocalizedHref(`/pt${basePath}`, currentLocale); const url = `${SITE.url}${canonicalPath}`; const languages = {}; for (const loc of ENABLED_LOCALES) { if (basePath === "/livro" && loc !== "pt") continue; const locHref = getLocalizedHref(`/pt${basePath}`, loc); const langKey = loc === "pt" ? "pt-BR" : loc === "es" ? "es-419" : loc; languages[langKey] = `${SITE.url}${locHref}`; } if (ENABLED_LOCALES.includes("en") && basePath !== "/livro") { languages["x-default"] = `${SITE.url}${getLocalizedHref(`/pt${basePath}`, "en")}`; } const ogLocaleMap = { pt: "pt_BR", en: "en_US", es: "es_419", fr: "fr_FR", de: "de_DE", "zh-CN": "zh_CN", }; const alternateLocales = ENABLED_LOCALES .filter((l) => l !== currentLocale && (basePath !== "/livro" || l === "pt")) .map((l) => ogLocaleMap[l]) .filter(Boolean); return { title: titulo, description: descricao, keywords, alternates: { canonical: canonicalPath, languages: Object.keys(languages).length > 1 ? languages : undefined, }, openGraph: { type: tipo, locale: ogLocaleMap[currentLocale] || "pt_BR", alternateLocale: alternateLocales.length > 0 ? alternateLocales : undefined, url, siteName: SITE.nome, title: `${titulo} · ${SITE.nome}`, description: descricao, }, twitter: { card: "summary_large_image", title: `${titulo} · ${SITE.nome}`, description: descricao, }, }; } /* ------------------------------------------------------------------------ Dados estruturados (JSON-LD). São eles que rendem os resultados ricos: trilha de navegação, box de busca e reconhecimento de autoria. ------------------------------------------------------------------------ */ export const jsonLd = { siteESociedade(locale = "pt") { const isEn = locale === "en"; const isEs = locale === "es"; const isFr = locale === "fr"; const isDe = locale === "de"; const orgAnchor = isEn ? "organization" : isEs ? "organizacion" : (isFr || isDe) ? "organisation" : "organizacao"; const authorAnchor = isEn ? "author" : isFr ? "auteur" : "autor"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; return { "@context": "https://schema.org", "@graph": [ { "@type": "WebSite", "@id": `${SITE.url}/#site`, url: `${SITE.url}/${locale}`, name: SITE.nome, inLanguage: inLang, description: isEn ? "ESP32 ecosystem documentation: specifications, pinouts, comparison, and series selection." : isEs ? "Documentación del ecosistema ESP32: especificaciones, patillaje, comparación y selección de series." : isFr ? "Documentation de l'écosystème ESP32 : spécifications, brochages, comparaison et sélection de séries." : isDe ? "Dokumentation des ESP32-Ökosystems: Spezifikationen, Pinbelegung, Vergleich und Serienauswahl." : "Documentação do ecossistema ESP32 em português brasileiro: especificações, pinagem, comparação e seleção de séries.", publisher: { "@id": `${SITE.url}/#${orgAnchor}` }, }, { "@type": "Organization", "@id": `${SITE.url}/#${orgAnchor}`, name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, image: `${SITE.url}/icon-512.png`, sameAs: [SITE.autorLinkedin, SITE.repositorio, SITE.blog], }, { "@type": "Person", "@id": `${SITE.url}/#${authorAnchor}`, name: SITE.autor, url: SITE.autorUrl, email: SITE.autorEmail, sameAs: [SITE.autorLinkedin, SITE.repositorio, SITE.blog], }, ], }; }, /** Trilha de navegação: aparece acima do título no resultado de busca. */ trilha(itens) { return { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: itens.map((it, i) => ({ "@type": "ListItem", position: i + 1, name: it.nome, item: `${SITE.url}${it.caminho}`, })), }; }, /** Documentação e ficha técnica da série como TechArticle para o Google. */ serie(chave, serie, locale = "pt") { const isEn = locale === "en"; const isEs = locale === "es"; const isFr = locale === "fr"; const isDe = locale === "de"; const prop = (nome, valor) => valor ? { "@type": "PropertyValue", name: nome, value: String(valor) } : null; const propriedades = [ prop(isEn ? "Manufacturer" : isEs ? "Fabricante" : isFr ? "Fabricant" : isDe ? "Hersteller" : "Fabricante", "Espressif Systems"), prop(isEn ? "Architecture" : isEs ? "Arquitectura" : isFr ? "Architecture" : isDe ? "Architektur" : "Arquitetura"), prop(isEn ? "Cores" : isEs ? "Núcleos" : isFr ? "Cœurs" : isDe ? "Kerne" : "Núcleos", serie.nucleos), prop(isEn ? "Max frequency" : isEs ? "Frecuencia máxima" : isFr ? "Fréquence maximale" : isDe ? "Maximale Frequenz" : "Frequência máxima", serie.frequencia), prop("SRAM", serie.memoria_sram), prop("RTC SRAM", serie.memoria_sram_rtc), prop(isEn ? "Supported Flash" : isEs ? "Flash soportada" : isFr ? "Flash prise en charge" : isDe ? "Unterstützter Flash" : "Flash suportada", serie.flash_suportada), prop(isEn ? "Supported PSRAM" : isEs ? "PSRAM soportada" : isFr ? "PSRAM prise en charge" : isDe ? "Unterstütztes PSRAM" : "PSRAM suportada", serie.psram_suportada), prop("GPIO", serie.gpio), prop("Wi-Fi", serie.wifi), prop("Bluetooth", serie.bluetooth), prop("Zigbee / Thread / 802.15.4", serie.zigbee_thread), prop(isEn ? "Package" : isEs ? "Encapsulado" : isFr ? "Boîtier" : isDe ? "Gehäuse" : "Encapsulamento", serie.encapsulamento), prop(isEn ? "Minimum ESP-IDF" : isEs ? "ESP-IDF mínimo" : isFr ? "ESP-IDF minimum" : isDe ? "Mindestens ESP-IDF" : "ESP-IDF mínimo", serie.esp_idf_minimo), ].filter(Boolean); const imagem = serie.placa ? `${SITE.url}${serie.placa}` : `${SITE.url}/opengraph-image.png`; const basePath = isDe ? `/de/serien/${chave}` : isFr ? `/fr/series/${chave}` : isEn ? `/en/series/${chave}` : isEs ? `/es/series/${chave}` : `/series/${chave}`; const anchor = isEn ? "article" : isEs ? "articulo" : isFr ? "article" : isDe ? "artikel" : "artigo"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; return { "@context": "https://schema.org", "@type": "TechArticle", "@id": `${SITE.url}${basePath}#${anchor}`, headline: isEn ? `${serie.nome_completo || chave}: specifications and pinout` : isEs ? `${serie.nome_completo || chave}: especificaciones y patillaje` : isFr ? `${serie.nome_completo || chave} : spécifications et brochage` : isDe ? `${serie.nome_completo || chave}: Spezifikationen und Pinbelegung` : `${serie.nome_completo || chave}: especificações e pinagem`, name: isEn ? `${serie.nome_completo || chave} - Technical Documentation and Specifications` : isEs ? `${serie.nome_completo || chave} - Documentación técnica y especificaciones` : isFr ? `${serie.nome_completo || chave} - Documentation technique et spécifications` : isDe ? `${serie.nome_completo || chave} - Technische Dokumentation und Spezifikationen` : `${serie.nome_completo || chave} - Documentação Técnica e Especificações`, description: serie.descricao, image: [imagem], inLanguage: inLang, datePublished: "2026-08-25T00:00:00.000Z", dateModified: new Date().toISOString(), mainEntityOfPage: `${SITE.url}${basePath}`, url: `${SITE.url}${basePath}`, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, }, about: { "@type": "Thing", name: serie.nome_completo || chave, description: serie.descricao, additionalProperty: propriedades, }, }; }, /** Documentação técnica de framework. */ framework(chave, framework, locale = "pt") { const isEn = locale === "en"; const isEs = locale === "es"; const isFr = locale === "fr"; const isDe = locale === "de"; const basePath = isDe ? `/de/frameworks/${encodeURIComponent(chave)}` : isFr ? `/fr/frameworks/${encodeURIComponent(chave)}` : isEn ? `/en/frameworks/${encodeURIComponent(chave)}` : isEs ? `/es/frameworks/${encodeURIComponent(chave)}` : `/frameworks/${encodeURIComponent(chave)}`; const anchor = isEn ? "article" : isEs ? "articulo" : isFr ? "article" : isDe ? "artikel" : "artigo"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; return { "@context": "https://schema.org", "@type": "TechArticle", "@id": `${SITE.url}${basePath}#${anchor}`, headline: isEn ? `${framework.nome} for ESP32: Guide and Documentation` : isEs ? `${framework.nome} para ESP32: Guía y documentación` : isFr ? `${framework.nome} pour ESP32 : Guide et documentation` : isDe ? `${framework.nome} für ESP32: Leitfaden und Dokumentation` : `${framework.nome} para ESP32: Guia e Documentação`, name: isEn ? `${framework.nome_completo || framework.nome} for ESP32` : isFr ? `${framework.nome_completo || framework.nome} pour ESP32` : isDe ? `${framework.nome_completo || framework.nome} für ESP32` : `${framework.nome_completo || framework.nome} para ESP32`, description: framework.descricao, image: [`${SITE.url}/opengraph-image.png`], inLanguage: inLang, datePublished: "2026-08-25T00:00:00.000Z", dateModified: new Date().toISOString(), mainEntityOfPage: `${SITE.url}${basePath}`, url: `${SITE.url}${basePath}`, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, }, about: { "@type": "SoftwareApplication", name: framework.nome_completo || framework.nome, applicationCategory: "DeveloperApplication", operatingSystem: "Embedded", description: framework.descricao, url: framework.documentacao || `${SITE.url}${basePath}`, }, }; }, /** Glossário: marca cada verbete como termo definido. */ glossario(categorias, locale = "pt") { const isEn = locale === "en"; const isEs = locale === "es"; const isFr = locale === "fr"; const isDe = locale === "de"; const basePath = isDe ? "/de/glossar" : isFr ? "/fr/glossaire" : isEn ? "/en/glossary" : isEs ? "/es/glosario" : "/glossario"; const anchor = isDe ? "glossar" : isFr ? "glossaire" : isEn ? "glossary" : isEs ? "glosario" : "glossario"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; return { "@context": "https://schema.org", "@type": "DefinedTermSet", "@id": `${SITE.url}${basePath}#${anchor}`, name: isEn ? "ESP32 Technical Glossary" : isEs ? "Glosario técnico de ESP32" : isFr ? "Glossaire technique de l'ESP32" : isDe ? "Technisches Glossar für ESP32" : "Glossário Técnico do ESP32", inLanguage: inLang, hasDefinedTerm: categorias.flatMap((cat) => cat.termos.map((t) => ({ "@type": "DefinedTerm", name: t.termo, description: t.definicao, inDefinedTermSet: `${SITE.url}${basePath}#${anchor}`, })) ), }; }, /** Perguntas frequentes: pode render o bloco expansível no Google. */ perguntas(pares) { return { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: pares.map(({ pergunta, resposta }) => ({ "@type": "Question", name: pergunta, acceptedAnswer: { "@type": "Answer", text: resposta }, })), }; }, /** Aplicação web / ferramenta interativa (Seletor, Comparador, Catálogo). */ aplicacaoWeb({ nome, descricao, caminho, locale = "pt" }) { const isEn = locale === "en"; const isEs = locale === "es"; const isFr = locale === "fr"; const isDe = locale === "de"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; return { "@context": "https://schema.org", "@type": "WebApplication", "@id": `${SITE.url}${caminho}#app`, name: nome, description: descricao, url: `${SITE.url}${caminho}`, applicationCategory: "DeveloperApplication", operatingSystem: "All", browserRequirements: "Requires JavaScript", inLanguage: inLang, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, logo: { "@type": "ImageObject", url: `${SITE.url}/icon-512.png`, width: 512, height: 512, }, }, offers: { "@type": "Offer", price: "0", priceCurrency: "BRL", }, }; }, /** Livro do autor: dados de obra para mecanismos de busca. */ livro() { return { "@context": "https://schema.org", "@type": "Book", "@id": `${SITE.url}/livro#livro`, name: SITE.livroTitulo, alternateName: `${SITE.livroTitulo}: ${SITE.livroSubtitulo}`, description: "Livro em português sobre o hardware e o firmware do ESP32: arquitetura, energia, rádio, periféricos e projeto de produto, em 12 partes e 78 capítulos.", inLanguage: "pt-BR", url: `${SITE.url}/livro`, isbn: SITE.livroIsbn, image: `${SITE.url}${SITE.livroCapa}`, author: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, }, publisher: { "@type": "Organization", name: "Ciência Embarcada", url: SITE.blog, }, about: [ { "@type": "Thing", name: "ESP32" }, { "@type": "Thing", name: "Sistemas embarcados" }, { "@type": "Thing", name: "Internet das Coisas" }, ], workExample: [ { "@type": "Book", "@id": `${SITE.url}/livro#digital`, bookFormat: "https://schema.org/EBook", url: SITE.livroAmazon, potentialAction: { "@type": "BuyAction", target: SITE.livroAmazon, }, }, { "@type": "Book", "@id": `${SITE.url}/livro#fisico`, bookFormat: "https://schema.org/Paperback", url: SITE.livroUiclap, potentialAction: { "@type": "BuyAction", target: SITE.livroUiclap, }, }, ], }; }, /** Página Sobre com metadados E-E-A-T de autoria e reputação. */ paginaSobre(locale = "pt") { const isEn = locale === "en"; const isEs = locale === "es"; const isFr = locale === "fr"; const isDe = locale === "de"; const basePath = isDe ? "/de/ueber-uns" : isFr ? "/fr/a-propos" : isEn ? "/en/about" : isEs ? "/es/acerca" : "/sobre"; const anchor = isDe ? "ueber-uns" : isFr ? "a-propos" : isEn ? "about" : isEs ? "acerca" : "sobre"; const inLang = isEn ? "en" : isEs ? "es-419" : isFr ? "fr" : isDe ? "de" : "pt-BR"; return { "@context": "https://schema.org", "@type": "AboutPage", "@id": `${SITE.url}${basePath}#${anchor}`, name: isEn ? "About ESPDocs" : isEs ? "Acerca de ESPDocs" : isFr ? "À propos d'ESPDocs" : isDe ? "Über ESPDocs" : "Sobre o ESPDocs", description: isEn ? "ESPDocs is an independent platform organizing technical documentation of the ESP32 ecosystem." : isEs ? "ESPDocs es una plataforma independiente que reúne la documentación técnica del ecosistema ESP32." : isFr ? "ESPDocs est une plateforme indépendante qui rassemble la documentation technique de l'écosystème ESP32." : isDe ? "ESPDocs ist eine unabhängige Plattform, die die technische Dokumentation des ESP32-Ökosystems zusammenführt." : "O ESPDocs é uma plataforma independente que reúne a documentação do ecossistema ESP32 em português brasileiro.", url: `${SITE.url}${basePath}`, inLanguage: inLang, mainEntity: { "@type": "Person", name: SITE.autor, url: SITE.autorUrl, email: SITE.autorEmail, sameAs: [ SITE.autorLinkedin, SITE.repositorio, SITE.blog, ], }, }; }, };