feat(i18n): Simplified Chinese (zh-CN), search answers per locale, fr/de/es data fixes
CI / Lint e auditorias (push) Successful in 1m10s
CI / Trivy (dependências e segredos) (push) Successful in 12s
CI / Imagem Docker (linux/arm64) (push) Successful in 2m39s
Release / Plano (reaproveitar a imagem?) (push) Successful in 6s
Release / ci (push) Skipped
Release / Publica a imagem (linux/arm64) (push) Successful in 18s
Release / Cria a release no Gitea (push) Successful in 5s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-11-01 23:28:39 -03:00
1 parent ae61416468
commit 5a9fe676fc
61 files changed
+2517 -920

No files matched your search

+26 -7
View File
@@ -78,6 +78,24 @@ const HEADER_MAP_DE = {
"Botões": "Taster",
"LEDs": "LEDs",
};
const HEADER_MAP_ZH = {
"Nome": "名称",
"Descrição": "描述",
"Série": "系列",
"Loja Oficial": "官方商店",
"Fabricante": "制造商",
"Processador": "处理器",
"Memória Flash": "闪存",
"PSRAM": "PSRAM",
"Conector USB": "USB 接口",
"Interface USB": "USB 接口类型",
"Antena": "天线",
"Bateria": "电池",
"Display": "显示屏",
"Sensores": "传感器",
"Botões": "按键",
"LEDs": "LED",
};
export default function BoardCard({ board }) {
const pathname = usePathname() || "";
@@ -85,6 +103,7 @@ export default function BoardCard({ board }) {
const isEs = pathname === "/es" || pathname.startsWith("/es/");
const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
const isDe = pathname === "/de" || pathname.startsWith("/de/");
const isZh = pathname === "/zh-CN" || pathname.startsWith("/zh-CN/");
const mainFields = ["Nome", "Link", "Imagem", "Loja Oficial"];
@@ -107,18 +126,18 @@ export default function BoardCard({ board }) {
{lojaOficial && (
<span className="chip chip-accent mb-3 w-fit">
<Award className="h-3.5 w-3.5" aria-hidden="true" />
{isEn ? "Official store" : isEs ? "Tienda oficial" : isFr ? "Boutique officielle" : isDe ? "Offizieller Shop" : "Loja oficial"}
{isEn ? "Official store" : isEs ? "Tienda oficial" : isFr ? "Boutique officielle" : isZh ? "官方商店" : isDe ? "Offizieller Shop" : "Loja oficial"}
</span>
)}
<h3 className="line-clamp-2 font-display text-lg font-semibold leading-snug tracking-[-0.02em] text-ink">
{board.Nome || (isEn ? "Generic board" : isEs ? "Placa genérica" : isFr ? "Carte générique" : isDe ? "Generisches Board" : "Placa genérica")}
{board.Nome || (isEn ? "Generic board" : isEs ? "Placa genérica" : isFr ? "Carte générique" : isZh ? "通用开发板" : isDe ? "Generisches Board" : "Placa genérica")}
</h3>
{recursos.length > 0 && (
<div className="mt-4">
<p className="text-xs font-medium text-muted">
{isEn ? "On-board features" : isEs ? "Recursos integrados" : isFr ? "Fonctionnalités intégrées" : isDe ? "Integrierte Funktionen" : "Recursos embarcados"}
{isEn ? "On-board features" : isEs ? "Recursos integrados" : isFr ? "Fonctionnalités intégrées" : isZh ? "板载资源" : isDe ? "Integrierte Funktionen" : "Recursos embarcados"}
</p>
<ul className="mt-2 flex flex-wrap gap-1.5">
{recursos.map((campo) => {
@@ -129,7 +148,7 @@ export default function BoardCard({ board }) {
? (HEADER_MAP_ES[campo] || campo)
: isFr
? (HEADER_MAP_FR[campo] || campo)
: isDe
: isZh ? (HEADER_MAP_ZH[campo] || campo) : isDe
? (HEADER_MAP_DE[campo] || campo)
: campo;
return (
@@ -140,7 +159,7 @@ export default function BoardCard({ board }) {
<Check className="h-3 w-3 text-ok" aria-hidden="true" />
)}
{label}
{opcional && <span className="sr-only">{isEn ? " (optional)" : isEs ? " (opcional)" : isFr ? " (optionnel)" : isDe ? " (optional)" : " (opcional)"}</span>}
{opcional && <span className="sr-only">{isEn ? " (optional)" : isEs ? " (opcional)" : isFr ? " (optionnel)" : isZh ? "(可选)" : isDe ? " (optional)" : " (opcional)"}</span>}
</li>
);
})}
@@ -150,10 +169,10 @@ export default function BoardCard({ board }) {
{board.Link && (
<a href={board.Link} target="_blank" rel="noopener noreferrer" className="btn btn-primary mt-6 w-full">
{isEn ? "View on AliExpress" : isEs ? "Ver en AliExpress" : isFr ? "Voir sur AliExpress" : isDe ? "Auf AliExpress ansehen" : "Ver no AliExpress"}
{isEn ? "View on AliExpress" : isEs ? "Ver en AliExpress" : isFr ? "Voir sur AliExpress" : isZh ? "在 AliExpress 查看" : isDe ? "Auf AliExpress ansehen" : "Ver no AliExpress"}
<ArrowUpRight className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">
{isEn ? " (opens in new tab)" : isEs ? " (abre en una pestaña nueva)" : isFr ? " (ouvre un nouvel onglet)" : isDe ? " (öffnet in neuem Tab)" : " (abre em nova aba)"}
{isEn ? " (opens in new tab)" : isEs ? " (abre en una pestaña nueva)" : isFr ? " (ouvre un nouvel onglet)" : isZh ? "(在新标签页打开)" : isDe ? " (öffnet in neuem Tab)" : " (abre em nova aba)"}
</span>
</a>
)}
+16 -15
View File
@@ -10,7 +10,8 @@ export default function CodeExample({ framework }) {
const isEs = pathname === "/es" || pathname.startsWith("/es/");
const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
const isDe = pathname === "/de" || pathname.startsWith("/de/");
const loc = isEn ? "en" : isEs ? "es" : isFr ? "fr" : isDe ? "de" : "pt";
const isZh = pathname === "/zh-CN" || pathname.startsWith("/zh-CN/");
const loc = isEn ? "en" : isEs ? "es" : isFr ? "fr" : isZh ? "zh-CN" : isDe ? "de" : "pt";
const examplesUrl = framework?.exemplos;
@@ -35,7 +36,7 @@ export default function CodeExample({ framework }) {
? `// Ejemplos completos: ${examplesUrl || "ver repositorio"}\n`
: isFr
? `// Exemples complets : ${examplesUrl || "voir le dépôt"}\n`
: isDe
: isZh ? `// 完整示例:${examplesUrl || "见代码仓库"}\n` : isDe
? `// Vollständige Beispiele: ${examplesUrl || "Repository ansehen"}\n`
: `// Exemplos completos: ${examplesUrl || "ver repositório"}\n`
);
@@ -78,7 +79,7 @@ export default function CodeExample({ framework }) {
? "// El archivo de ejemplo está vacío. Consulte el repositorio oficial.\n"
: isFr
? "// Le fichier d'exemple est vide. Consultez le dépôt officiel.\n"
: isDe
: isZh ? "// 示例文件为空,请查阅官方代码仓库。\n" : isDe
? "// Die Beispieldatei ist leer. Bitte konsultieren Sie das offizielle Repository.\n"
: "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n"
);
@@ -89,7 +90,7 @@ export default function CodeExample({ framework }) {
? "archivo vacío"
: isFr
? "fichier vide"
: isDe
: isZh ? "文件为空" : isDe
? "leere Datei"
: "arquivo vazio"
);
@@ -104,7 +105,7 @@ export default function CodeExample({ framework }) {
? "// No fue posible cargar el ejemplo.\n// Consulte el repositorio oficial para ver los ejemplos completos.\n"
: isFr
? "// Impossible de charger l'exemple.\n// Consultez le dépôt officiel pour des exemples complets.\n"
: isDe
: isZh ? "// 无法加载示例。\n// 完整示例请查阅官方代码仓库。\n" : isDe
? "// Beispiel konnte nicht geladen werden.\n// Vollständige Beispiele finden Sie im offiziellen Repository.\n"
: "// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n"
);
@@ -116,7 +117,7 @@ export default function CodeExample({ framework }) {
? "fallo al cargar"
: isFr
? "échec du chargement"
: isDe
: isZh ? "加载失败" : isDe
? "Ladefehler"
: "falha ao carregar")
);
@@ -151,7 +152,7 @@ export default function CodeExample({ framework }) {
? "Ejemplo de código"
: isFr
? "Exemple de code"
: isDe
: isZh ? "代码示例" : isDe
? "Codebeispiel"
: "Exemplo de código"}
</h2>
@@ -162,7 +163,7 @@ export default function CodeExample({ framework }) {
? `Fragmento de inicio en ${framework?.linguagem}. Ajuste los pines y la configuración para su placa.`
: isFr
? `Extrait de départ en ${framework?.linguagem}. Adaptez les broches et la configuration à votre carte.`
: isDe
: isZh ? `${framework?.linguagem} 的起始代码片段,请根据你的开发板调整引脚和配置。` : isDe
? `Startbeispiel in ${framework?.linguagem}. Passen Sie Pins und Konfiguration an Ihr Board an.`
: `Trecho de partida em ${framework?.linguagem}. Ajuste pinos e configuração à sua placa.`}
</p>
@@ -173,13 +174,13 @@ export default function CodeExample({ framework }) {
{copied ? <Check className="h-4 w-4 text-ok" aria-hidden="true" /> : <Copy className="h-4 w-4" aria-hidden="true" />}
<span role="status">
{copied
? (isEn ? "Copied" : isEs ? "Copiado" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado")
: (isEn ? "Copy" : isEs ? "Copiar" : isFr ? "Copier" : isDe ? "Kopieren" : "Copiar")}
? (isEn ? "Copied" : isEs ? "Copiado" : isFr ? "Copié" : isZh ? "已复制" : isDe ? "Kopiert" : "Copiado")
: (isEn ? "Copy" : isEs ? "Copiar" : isFr ? "Copier" : isZh ? "复制" : isDe ? "Kopieren" : "Copiar")}
</span>
</button>
{examplesUrl && (
<a href={examplesUrl} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-sm">
{isEn ? "More examples" : isEs ? "Más ejemplos" : isFr ? "Plus d'exemples" : isDe ? "Weitere Beispiele" : "Mais exemplos"}
{isEn ? "More examples" : isEs ? "Más ejemplos" : isFr ? "Plus d'exemples" : isZh ? "更多示例" : isDe ? "Weitere Beispiele" : "Mais exemplos"}
<ExternalLink className="h-4 w-4" aria-hidden="true" />
<span className="sr-only">
{isEn
@@ -188,7 +189,7 @@ export default function CodeExample({ framework }) {
? " (abre en una pestaña nueva)"
: isFr
? " (ouvre un nouvel onglet)"
: isDe
: isZh ? "(在新标签页打开)" : isDe
? " (öffnet in neuem Tab)"
: " (abre em nova aba)"}
</span>
@@ -208,7 +209,7 @@ export default function CodeExample({ framework }) {
? "Cargando ejemplo…"
: isFr
? "Chargement de l'exemple…"
: isDe
: isZh ? "正在加载示例…" : isDe
? "Beispiel wird geladen…"
: "Carregando exemplo…")
: framework?.linguagem}
@@ -223,7 +224,7 @@ export default function CodeExample({ framework }) {
? `Código de ejemplo en ${framework?.linguagem}`
: isFr
? `Exemple de code en ${framework?.linguagem}`
: isDe
: isZh ? `${framework?.linguagem} 示例代码` : isDe
? `Codebeispiel in ${framework?.linguagem}`
: `Código de exemplo em ${framework?.linguagem}`
}
@@ -247,7 +248,7 @@ export default function CodeExample({ framework }) {
? `No se pudo cargar el ejemplo local (${error}). Utilice “Más ejemplos” para ver el código oficial.`
: isFr
? `Impossible de charger l'exemple local (${error}). Utilisez « Plus d'exemples » pour consulter le code officiel.`
: isDe
: isZh ? `无法加载本地示例(${error})。请使用“更多示例”查看官方代码。` : isDe
? `Lokales Beispiel konnte nicht geladen werden (${error}). Nutzen Sie „Weitere Beispiele“, um den offiziellen Code anzusehen.`
: `Não foi possível carregar o exemplo local (${error}). Use “Mais exemplos” para ver o código oficial.`}
</p>
+70 -40
View File
@@ -124,6 +124,28 @@ const FILTER_CATEGORIES_DE = [
{ id: "I2C", name: "I2C", color: "#f59e0b" },
{ id: "Antenne", name: "Antenne", color: "#06b6d4" },
];
const FILTER_CATEGORIES_ZH = [
{ id: "All", name: "全部", color: "#8b5cf6" },
{ id: "GND", name: "接地(GND)", color: "#64748b" },
{ id: "Power", name: "电源", color: "#ef4444" },
{ id: "GPIO", name: "GPIO", color: "#22c55e" },
{ id: "Low Power", name: "低功耗 GPIO", color: "#f59e0b" },
{ id: "ADC", name: "ADC", color: "#ec4899" },
{ id: "DAC", name: "DAC", color: "#a855f7" },
{ id: "Touch", name: "触摸", color: "#06b6d4" },
{ id: "UART", name: "UART", color: "#3b82f6" },
{ id: "SPI", name: "SPI", color: "#f43f5e" },
{ id: "USB", name: "USB", color: "#a855f7" },
{ id: "Clock", name: "时钟", color: "#eab308" },
{ id: "JTAG", name: "JTAG", color: "#6366f1" },
{ id: "Strapping", name: "启动配置", color: "#dc2626" },
{ id: "DSI", name: "显示(DSI)", color: "#f97316" },
{ id: "CSI", name: "摄像头(CSI)", color: "#14b8a6" },
{ id: "Ethernet", name: "以太网", color: "#0284c7" },
{ id: "SD", name: "SD 卡", color: "#10b981" },
{ id: "I2C", name: "I2C", color: "#f59e0b" },
{ id: "Antena", name: "天线", color: "#06b6d4" },
];
const type_colors = {
power: "#ef4444", // high-voltage red
@@ -154,6 +176,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
const isEs = pathname?.startsWith("/es");
const isFr = pathname?.startsWith("/fr");
const isDe = pathname?.startsWith("/de");
const isZh = pathname?.startsWith("/zh-CN");
const router = useRouter();
const pinosFiltro = useMemo(() => new Set((params.get("pinos") ?? "").split(",").filter(Boolean)), [params]);
const motivoFiltro = params.get("motivo") ?? "";
@@ -171,7 +194,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? FILTER_CATEGORIES_ES
: isFr
? FILTER_CATEGORIES_FR
: isDe
: isZh ? FILTER_CATEGORIES_ZH : isDe
? FILTER_CATEGORIES_DE
: FILTER_CATEGORIES;
@@ -196,7 +219,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
const nomeCategoria = (cat) => {
if (cat === "Strapping") {
return isEn ? "Boot mode" : isEs ? "Arranque (Boot)" : isFr ? "Démarrage (Boot)" : isDe ? "Bootmodus" : "Inicialização";
return isEn ? "Boot mode" : isEs ? "Arranque (Boot)" : isFr ? "Démarrage (Boot)" : isZh ? "启动配置" : isDe ? "Bootmodus" : "Inicialização";
}
if (isEn) {
const found = FILTER_CATEGORIES_EN.find((f) => f.id === cat);
@@ -214,6 +237,10 @@ function ConnectionsDiagramInner({ connections, serie }) {
const found = FILTER_CATEGORIES_DE.find((f) => f.id === cat);
if (found) return found.name;
}
if (isZh) {
const found = FILTER_CATEGORIES_ZH.find((f) => f.id === cat);
if (found) return found.name;
}
return cat;
};
@@ -270,7 +297,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="px-4 py-14 text-center">
<Sliders className="mx-auto h-10 w-10 text-muted" aria-hidden="true" />
<p className="mt-4 font-display text-xl font-semibold text-ink">
{isEn ? "Pinout diagram unavailable" : isEs ? "Diagrama de conexiones no disponible" : isFr ? "Brochage non disponible" : isDe ? "Pinbelegungsdiagramm nicht verfügbar" : "Diagrama de conexões indisponível"}
{isEn ? "Pinout diagram unavailable" : isEs ? "Diagrama de conexiones no disponible" : isFr ? "Brochage non disponible" : isZh ? "连接图暂不可用" : isDe ? "Pinbelegungsdiagramm nicht verfügbar" : "Diagrama de conexões indisponível"}
</p>
<p className="mt-1 text-sm text-muted">
{isEn
@@ -279,7 +306,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? "Este modelo todavía no dispone de un mapa detallado de conexiones y buses."
: isFr
? "Ce modèle ne dispose pas encore d'un plan détaillé des broches et des bus."
: isDe
: isZh ? "该型号尚无详细的连接与总线图。" : isDe
? "Dieses Modell verfügt noch über keinen detaillierten Pinbelegungs- und Busplan."
: "Este modelo ainda não tem um mapa detalhado de conexões e barramentos."}
</p>
@@ -293,7 +320,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="space-y-6">
<div>
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
{isEn ? "Interactive pinout diagram" : isEs ? "Diagrama de conexiones interactivo" : isFr ? "Brochage interactif" : isDe ? "Interaktives Pinbelegungsdiagramm" : "Diagrama de conexões interativo"}
{isEn ? "Interactive pinout diagram" : isEs ? "Diagrama de conexiones interactivo" : isFr ? "Brochage interactif" : isZh ? "交互式连接图" : isDe ? "Interaktives Pinbelegungsdiagramm" : "Diagrama de conexões interativo"}
</h2>
<p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted">
{isEn ? (
@@ -302,7 +329,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
`Todas las conexiones y la multiplexación del ${serie.nome_completo}. Busque por nombre o número, filtre por función y abra un pin para ver los detalles.`
) : isFr ? (
`Toutes les connexions et le multiplexage de l'${serie.nome_completo}. Recherchez par nom ou numéro, filtrez par fonction et ouvrez une broche pour voir les détails.`
) : isDe ? (
) : isZh ? ( `${serie.nome_completo} 的全部连接与复用。可按名称或编号搜索、按功能筛选,并展开引脚查看详情。` ) : isDe ? (
`Alle Anschlüsse und das Multiplexing des ${serie.nome_completo}. Suchen Sie nach Name oder Nummer, filtern Sie nach Funktion und öffnen Sie einen Pin für Details.`
) : (
`Todas as conexões e a multiplexação do ${serie.nome_completo}. Busque por nome ou número, filtre por função e abra um pino para ver os detalhes.`
@@ -314,15 +341,15 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div role="status" className="callout flex flex-wrap items-center justify-between gap-3">
<p className="text-sm text-ink">
<strong className="font-semibold">
{isEn ? "Filtered by search:" : isEs ? "Filtrado por la búsqueda:" : isFr ? "Filtré par la recherche :" : isDe ? "Nach Suche gefiltert:" : "Filtrado pela busca:"}
{isEn ? "Filtered by search:" : isEs ? "Filtrado por la búsqueda:" : isFr ? "Filtré par la recherche :" : isZh ? "按搜索筛选:" : isDe ? "Nach Suche gefiltert:" : "Filtrado pela busca:"}
</strong>{" "}
{motivoFiltro || (isEn ? "selected pins" : isEs ? "pines seleccionados" : isFr ? "broches sélectionnées" : isDe ? "ausgewählte Pins" : "pinos selecionados")} ({pinosFiltro.size}{" "}
{motivoFiltro || (isEn ? "selected pins" : isEs ? "pines seleccionados" : isFr ? "broches sélectionnées" : isZh ? "个引脚已选中" : isDe ? "ausgewählte Pins" : "pinos selecionados")} ({pinosFiltro.size}{" "}
{pinosFiltro.size === 1
? (isEn ? "pin" : isEs ? "pin" : isFr ? "broche" : isDe ? "Pin" : "pino")
: (isEn ? "pins" : isEs ? "pines" : isFr ? "broches" : isDe ? "Pins" : "pinos")})
? (isEn ? "pin" : isEs ? "pin" : isFr ? "broche" : isZh ? "个引脚" : isDe ? "Pin" : "pino")
: (isEn ? "pins" : isEs ? "pines" : isFr ? "broches" : isZh ? "个引脚" : isDe ? "Pins" : "pinos")})
</p>
<button type="button" onClick={mostrarTodos} className="btn btn-ghost btn-sm">
{isEn ? "Show all pins" : isEs ? "Mostrar todos los pines" : isFr ? "Afficher toutes les broches" : isDe ? "Alle Pins anzeigen" : "Mostrar todos os pinos"}
{isEn ? "Show all pins" : isEs ? "Mostrar todos los pines" : isFr ? "Afficher toutes les broches" : isZh ? "显示全部引脚" : isDe ? "Alle Pins anzeigen" : "Mostrar todos os pinos"}
</button>
</div>
)}
@@ -330,7 +357,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="flex flex-col gap-3 sm:flex-row">
<div className="relative flex-1">
<label htmlFor="busca-pino" className="sr-only">
{isEn ? "Search connection by name or number" : isEs ? "Buscar conexión por nombre o número" : isFr ? "Rechercher une connexion par nom ou numéro" : isDe ? "Anschluss nach Name oder Nummer suchen" : "Buscar conexão por nome ou número"}
{isEn ? "Search connection by name or number" : isEs ? "Buscar conexión por nombre o número" : isFr ? "Rechercher une connexion par nom ou numéro" : isZh ? "按名称或编号搜索连接" : isDe ? "Anschluss nach Name oder Nummer suchen" : "Buscar conexão por nome ou número"}
</label>
<Search className="pointer-events-none absolute left-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted" aria-hidden="true" />
<input
@@ -343,7 +370,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? "Buscar pin (ej.: GPIO1, TXD, XTAL)"
: isFr
? "Rechercher une broche (ex. : GPIO1, TXD, XTAL)"
: isDe
: isZh ? "搜索引脚(例如:GPIO1、TXD、XTAL)" : isDe
? "Pin suchen (z. B.: GPIO1, TXD, XTAL)"
: "Buscar pino (ex.: GPIO1, TXD, XTAL)"
}
@@ -353,21 +380,21 @@ function ConnectionsDiagramInner({ connections, serie }) {
className="field pl-11 pr-4 text-sm"
/>
</div>
<div role="group" aria-label={isEn ? "Display mode" : isEs ? "Modo de visualización" : isFr ? "Mode d'affichage" : isDe ? "Anzeigemodus" : "Modo de exibição"} className="flex gap-2">
<div role="group" aria-label={isEn ? "Display mode" : isEs ? "Modo de visualización" : isFr ? "Mode d'affichage" : isZh ? "显示模式" : isDe ? "Anzeigemodus" : "Modo de exibição"} className="flex gap-2">
<button type="button" onClick={() => setViewMode("grid")} aria-pressed={viewMode === "grid"} className="chip-btn">
<Grid className="h-4 w-4" aria-hidden="true" />
{isEn ? "Grid" : isEs ? "Cuadrícula" : isFr ? "Grille" : isDe ? "Raster" : "Grade"}
{isEn ? "Grid" : isEs ? "Cuadrícula" : isFr ? "Grille" : isZh ? "网格" : isDe ? "Raster" : "Grade"}
</button>
<button type="button" onClick={() => setViewMode("list")} aria-pressed={viewMode === "list"} className="chip-btn">
<List className="h-4 w-4" aria-hidden="true" />
{isEn ? "List" : isEs ? "Lista" : isFr ? "Liste" : isDe ? "Liste" : "Lista"}
{isEn ? "List" : isEs ? "Lista" : isFr ? "Liste" : isZh ? "列表" : isDe ? "Liste" : "Lista"}
</button>
</div>
</div>
<div>
<div className="flex items-center justify-between gap-3">
<h3 className="text-sm font-semibold text-ink">{isEn ? "Pin function" : isEs ? "Función del pin" : isFr ? "Fonction de la broche" : isDe ? "Pin-Funktion" : "Função do pino"}</h3>
<h3 className="text-sm font-semibold text-ink">{isEn ? "Pin function" : isEs ? "Función del pin" : isFr ? "Fonction de la broche" : isZh ? "引脚功能" : isDe ? "Pin-Funktion" : "Função do pino"}</h3>
<p role="status" aria-live="polite" className="text-sm text-muted">
{isEn ? (
`${visibleConnections.length} of ${connections.length} pins`
@@ -375,14 +402,14 @@ function ConnectionsDiagramInner({ connections, serie }) {
`${visibleConnections.length} de ${connections.length} pines`
) : isFr ? (
`${visibleConnections.length} sur ${connections.length} broches`
) : isDe ? (
) : isZh ? ( `共 ${connections.length} 个引脚,显示 ${visibleConnections.length} 个` ) : isDe ? (
`${visibleConnections.length} von ${connections.length} Pins`
) : (
`${visibleConnections.length} de ${connections.length} pinos`
)}
</p>
</div>
<div role="group" aria-label={isEn ? "Filter by function" : isEs ? "Filtrar por función" : isFr ? "Filtrer par fonction" : isDe ? "Nach Funktion filtern" : "Filtrar por função"} className="mt-3 flex flex-wrap gap-2">
<div role="group" aria-label={isEn ? "Filter by function" : isEs ? "Filtrar por función" : isFr ? "Filtrer par fonction" : isZh ? "按功能筛选" : isDe ? "Nach Funktion filtern" : "Filtrar por função"} className="mt-3 flex flex-wrap gap-2">
{filterCategories.map((filter) => {
const isActive = activeFilter === filter.id;
const count = connections.filter((c) => (filter.id === "All" ? true : c.categorias.includes(filter.id))).length;
@@ -401,7 +428,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
{visibleConnections.length === 0 ? (
<div className="rounded-2xl border border-dashed border-line-strong px-6 py-12 text-center">
<Search className="mx-auto h-8 w-8 text-muted" aria-hidden="true" />
<p className="mt-3 font-semibold text-ink">{isEn ? "No connections found" : isEs ? "No se encontraron conexiones" : isFr ? "Aucune connexion trouvée" : isDe ? "Keine Anschlüsse gefunden" : "Nenhuma conexão encontrada"}</p>
<p className="mt-3 font-semibold text-ink">{isEn ? "No connections found" : isEs ? "No se encontraron conexiones" : isFr ? "Aucune connexion trouvée" : isZh ? "未找到连接" : isDe ? "Keine Anschlüsse gefunden" : "Nenhuma conexão encontrada"}</p>
<p className="mt-1 text-sm text-muted">
{isEn
? "Adjust your search or return to all functions."
@@ -409,12 +436,12 @@ function ConnectionsDiagramInner({ connections, serie }) {
? "Ajuste la búsqueda o vuelva a todas las funciones."
: isFr
? "Ajustez votre recherche ou revenez à toutes les fonctions."
: isDe
: isZh ? "请调整搜索,或返回全部功能。" : isDe
? "Passen Sie Ihre Suche an oder kehren Sie zu allen Funktionen zurück."
: "Ajuste a busca ou volte para todas as funções."}
</p>
<button type="button" onClick={() => { setActiveFilter("All"); setSearchQuery(""); if (pinosFiltro.size) mostrarTodos(); }} className="btn btn-primary btn-sm mt-5">
{isEn ? "Clear search and filters" : isEs ? "Limpiar búsqueda y filtros" : isFr ? "Effacer la recherche et les filtres" : isDe ? "Suche und Filter zurücksetzen" : "Limpar busca e filtros"}
{isEn ? "Clear search and filters" : isEs ? "Limpiar búsqueda y filtros" : isFr ? "Effacer la recherche et les filtres" : isZh ? "清除搜索和筛选" : isDe ? "Suche und Filter zurücksetzen" : "Limpar busca e filtros"}
</button>
</div>
) : viewMode === "grid" ? (
@@ -434,7 +461,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Pin ${connection.nome}, número ${connection.numero}${connection.avisos ? ", con restricciones de uso" : ""}`
: isFr
? `Broche ${connection.nome}, numéro ${connection.numero}${connection.avisos ? ", avec restrictions d'utilisation" : ""}`
: isDe
: isZh ? `引脚 ${connection.nome},编号 ${connection.numero}${connection.avisos ? ",有使用限制" : ""}` : isDe
? `Pin ${connection.nome}, Nummer ${connection.numero}${connection.avisos ? ", mit Nutzungseinschränkungen" : ""}`
: `Pino ${connection.nome}, número ${connection.numero}${connection.avisos ? ", com restrições de uso" : ""}`
}
@@ -472,16 +499,16 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Conexiones del ${serie.nome_completo}`
: isFr
? `Connexions de l'${serie.nome_completo}`
: isDe
: isZh ? `${serie.nome_completo} 的连接` : isDe
? `Anschlüsse des ${serie.nome_completo}`
: `Conexões do ${serie.nome_completo}`}
</caption>
<thead>
<tr className="border-b border-line bg-surface-2 text-muted">
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Pin" : isEs ? "Pin" : isFr ? "Broche" : isDe ? "Pin" : "Pino"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Name" : isEs ? "Nombre" : isFr ? "Nom" : isDe ? "Name" : "Nome"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Functions" : isEs ? "Funciones" : isFr ? "Fonctions" : isDe ? "Funktionen" : "Funções"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Usage" : isEs ? "Uso" : isFr ? "Utilisation" : isDe ? "Nutzung" : "Uso"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Pin" : isEs ? "Pin" : isFr ? "Broche" : isZh ? "引脚" : isDe ? "Pin" : "Pino"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Name" : isEs ? "Nombre" : isFr ? "Nom" : isZh ? "名称" : isDe ? "Name" : "Nome"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Functions" : isEs ? "Funciones" : isFr ? "Fonctions" : isZh ? "功能" : isDe ? "Funktionen" : "Funções"}</th>
<th scope="col" className="px-4 py-3 font-medium">{isEn ? "Usage" : isEs ? "Uso" : isFr ? "Utilisation" : isZh ? "用途" : isDe ? "Nutzung" : "Uso"}</th>
</tr>
</thead>
<tbody className="divide-y divide-line">
@@ -501,7 +528,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Abrir detalles del pin ${connection.nome}, número ${connection.numero}`
: isFr
? `Ouvrir les détails de la broche ${connection.nome}, numéro ${connection.numero}`
: isDe
: isZh ? `展开引脚 ${connection.nome} 的详情,编号 ${connection.numero}` : isDe
? `Pindetails für ${connection.nome}, Nummer ${connection.numero} öffnen`
: `Abrir detalhes do pino ${connection.nome}, número ${connection.numero}`
}
@@ -519,12 +546,12 @@ function ConnectionsDiagramInner({ connections, serie }) {
{connection.avisos ? (
<span className="inline-flex items-center gap-1.5 font-medium text-danger">
<AlertTriangle className="h-4 w-4" aria-hidden="true" />
{isEn ? "Restricted" : isEs ? "Restringido" : isFr ? "Restreint" : isDe ? "Eingeschränkt" : "Restrito"}
{isEn ? "Restricted" : isEs ? "Restringido" : isFr ? "Restreint" : isZh ? "受限" : isDe ? "Eingeschränkt" : "Restrito"}
</span>
) : (
<span className="inline-flex items-center gap-1.5 text-ok">
<CheckCircle className="h-4 w-4" aria-hidden="true" />
{isEn ? "Free" : isEs ? "Libre" : isFr ? "Libre" : isDe ? "Frei" : "Livre"}
{isEn ? "Free" : isEs ? "Libre" : isFr ? "Libre" : isZh ? "可用" : isDe ? "Frei" : "Livre"}
</span>
)}
</td>
@@ -540,7 +567,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="callout callout-warn flex items-start gap-3">
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0 text-gold" aria-hidden="true" />
<div>
<p className="font-semibold text-ink">{isEn ? "Pinout notes" : isEs ? "Notas sobre el patillaje" : isFr ? "Remarques sur le brochage" : isDe ? "Hinweise zur Pinbelegung" : "Notas sobre a pinagem"}</p>
<p className="font-semibold text-ink">{isEn ? "Pinout notes" : isEs ? "Notas sobre el patillaje" : isFr ? "Remarques sur le brochage" : isZh ? "引脚说明" : isDe ? "Hinweise zur Pinbelegung" : "Notas sobre a pinagem"}</p>
<ul className="mt-1 list-disc space-y-1 pl-5">
{isEn ? (
<>
@@ -557,7 +584,10 @@ function ConnectionsDiagramInner({ connections, serie }) {
<li>Les broches signalées par une icône d'avertissement ont un rôle spécial au démarrage ou des restrictions analogiques. Ouvrez la broche pour voir les détails.</li>
<li>Isolez les pistes tactiles capacitives des bruits RF couplés.</li>
</>
) : isDe ? (
) : isZh ? ( <>
<li>带警告图标的引脚在启动时有特殊作用,或存在模拟功能限制。展开该引脚查看详情。</li>
<li>请将电容触摸电路与耦合了射频噪声的引脚隔离开。</li>
</> ) : isDe ? (
<>
<li>Pins mit einem Warnsymbol spielen eine besondere Rolle beim Booten oder unterliegen analogen Einschränkungen. Öffnen Sie den Pin für Details.</li>
<li>Isolieren Sie kapazitive Touch-Leitungen von eingekoppeltem HF-Rauschen.</li>
@@ -602,7 +632,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
? `Pin físico ${selectedConnection.numero}`
: isFr
? `Broche physique ${selectedConnection.numero}`
: isDe
: isZh ? `物理引脚 ${selectedConnection.numero}` : isDe
? `Physischer Pin ${selectedConnection.numero}`
: `Pino físico ${selectedConnection.numero}`}
</p>
@@ -611,7 +641,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<button
type="button"
onClick={fechar}
aria-label={isEn ? "Close pin details" : isEs ? "Cerrar detalles del pin" : isFr ? "Fermer les détails de la broche" : isDe ? "Pindetails schließen" : "Fechar detalhes do pino"}
aria-label={isEn ? "Close pin details" : isEs ? "Cerrar detalles del pin" : isFr ? "Fermer les détails de la broche" : isZh ? "关闭引脚详情" : isDe ? "Pindetails schließen" : "Fechar detalhes do pino"}
className="grid h-11 w-11 shrink-0 place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink"
>
<X className="h-5 w-5" aria-hidden="true" />
@@ -621,7 +651,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="flex-1 space-y-6 overflow-y-auto p-5 sm:p-7">
{selectedConnection.categorias.length > 0 && (
<section>
<h4 className="text-sm font-semibold text-ink">{isEn ? "Categories" : isEs ? "Categorías" : isFr ? "Catégories" : isDe ? "Kategorien" : "Categorias"}</h4>
<h4 className="text-sm font-semibold text-ink">{isEn ? "Categories" : isEs ? "Categorías" : isFr ? "Catégories" : isZh ? "类别" : isDe ? "Kategorien" : "Categorias"}</h4>
<ul className="mt-3 flex flex-wrap gap-2">
{selectedConnection.categorias.map((cat) => (
<li key={cat} className="chip">
@@ -637,7 +667,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<section>
<h4 className="flex items-center gap-2 text-sm font-semibold text-ink">
<Zap className="h-4 w-4 text-danger" aria-hidden="true" />
{isEn ? "Power domain" : isEs ? "Dominio de alimentación" : isFr ? "Domaine d'alimentation" : isDe ? "Spannungsbereich" : "Domínio de alimentação"}
{isEn ? "Power domain" : isEs ? "Dominio de alimentación" : isFr ? "Domaine d'alimentation" : isZh ? "供电域" : isDe ? "Spannungsbereich" : "Domínio de alimentação"}
</h4>
<ul className="tile mt-3 space-y-2">
{selectedConnection.alimentacao.map((source) => (
@@ -648,7 +678,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
)}
<section>
<h4 className="text-sm font-semibold text-ink">{isEn ? "Available functions" : isEs ? "Funciones disponibles" : isFr ? "Fonctions disponibles" : isDe ? "Verfügbare Funktionen" : "Funções disponíveis"}</h4>
<h4 className="text-sm font-semibold text-ink">{isEn ? "Available functions" : isEs ? "Funciones disponibles" : isFr ? "Fonctions disponibles" : isZh ? "可用功能" : isDe ? "Verfügbare Funktionen" : "Funções disponíveis"}</h4>
<ul className="tile mt-3 grid gap-2.5 sm:grid-cols-2">
{selectedConnection.funcoes.map((func) => (
<li key={func} className="flex items-center gap-2.5 text-sm text-ink-soft">
@@ -663,7 +693,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<section className="callout callout-danger">
<h4 className="flex items-center gap-2 font-semibold text-ink">
<AlertTriangle className="h-4 w-4 text-danger" aria-hidden="true" />
{isEn ? "Engineering notes" : isEs ? "Notas de ingeniería" : isFr ? "Notes d'ingénierie" : isDe ? "Entwicklungshinweise" : "Avisos de engenharia"}
{isEn ? "Engineering notes" : isEs ? "Notas de ingeniería" : isFr ? "Notes d'ingénierie" : isZh ? "工程警告" : isDe ? "Entwicklungshinweise" : "Avisos de engenharia"}
</h4>
<ul className="mt-2 list-disc space-y-1.5 pl-5">
{selectedConnection.avisos.map((aviso) => (
@@ -676,7 +706,7 @@ function ConnectionsDiagramInner({ connections, serie }) {
<div className="border-t border-line p-4 sm:px-7">
<button type="button" onClick={fechar} className="btn btn-primary w-full">
{isEn ? "Close" : isEs ? "Cerrar" : isFr ? "Fermer" : isDe ? "Schließen" : "Fechar"}
{isEn ? "Close" : isEs ? "Cerrar" : isFr ? "Fermer" : isZh ? "关闭" : isDe ? "Schließen" : "Fechar"}
</button>
</div>
</div>
+38 -9
View File
@@ -153,6 +153,34 @@ const COLUMNS_DE = [
],
},
];
const COLUMNS_ZH = [
{
title: "探索",
links: [
{ href: "/series", label: "ESP32 系列" },
{ href: "/comparacao", label: "对比工具" },
{ href: "/catalogo", label: "开发板目录" },
],
},
{
title: "工具",
links: [
{ href: "/seletor", label: "智能选型" },
{ href: "/diagnostico", label: "故障诊断" },
{ href: "/componentes", label: "Component Registry" },
{ href: "/frameworks", label: "框架与 SDK" },
],
},
{
title: "项目",
links: [
{ href: "/sobre", label: "关于 ESPDocs" },
{ href: "/glossario", label: "术语表" },
{ href: SITE.repositorio, label: "代码仓库", external: true },
{ href: `${SITE.repositorio}/issues`, label: "反馈问题", external: true },
],
},
];
export default function Footer() {
const pathname = usePathname() || "";
@@ -165,6 +193,7 @@ export default function Footer() {
const isEs = currentLocale === "es";
const isFr = currentLocale === "fr";
const isDe = currentLocale === "de";
const isZh = currentLocale === "zh-CN";
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -177,7 +206,7 @@ export default function Footer() {
? COLUMNS_ES
: isFr
? COLUMNS_FR
: isDe
: isZh ? COLUMNS_ZH : isDe
? COLUMNS_DE
: COLUMNS_EN;
@@ -191,7 +220,7 @@ export default function Footer() {
? "Pie de página de ESPDocs"
: isFr
? "Pied de page d'ESPDocs"
: isDe
: isZh ? "ESPDocs 页脚" : isDe
? "ESPDocs-Fußzeile"
: "ESPDocs footer"
}
@@ -209,7 +238,7 @@ export default function Footer() {
? "ESPDocs, página de inicio"
: isFr
? "ESPDocs, page d'accueil"
: isDe
: isZh ? "ESPDocs,首页" : isDe
? "ESPDocs, Startseite"
: "ESPDocs, homepage"
}
@@ -233,7 +262,7 @@ export default function Footer() {
Documentation indépendante de l&apos;écosystème ESP32.
Données publiques, sélection éditoriale et liens vers les sources officielles.
</>
) : isDe ? (
) : isZh ? ( <>ESP32 生态的独立文档。公开数据、编辑整理,并链接到官方资料来源。</> ) : isDe ? (
<>
Unabhängige Dokumentation des ESP32-Ökosystems.
Öffentliche Daten, redaktionelle Betreuung und Links zu offiziellen Quellen.
@@ -312,7 +341,7 @@ export default function Footer() {
))}
<div>
<h2 className="text-sm font-semibold text-ink">{isPt ? "Autor" : isEs ? "Autor" : isFr ? "Auteur" : isDe ? "Autor" : "Author"}</h2>
<h2 className="text-sm font-semibold text-ink">{isPt ? "Autor" : isEs ? "Autor" : isFr ? "Auteur" : isZh ? "作者" : isDe ? "Autor" : "Author"}</h2>
<p className="mt-4 text-sm leading-relaxed text-muted">
Lucas Rayan Guerra
<br />
@@ -330,7 +359,7 @@ export default function Footer() {
? "Gitea del proyecto (abre en una pestaña nueva)"
: isFr
? "Gitea du projet (ouvre un nouvel onglet)"
: isDe
: isZh ? "项目 Gitea(在新标签页打开)" : isDe
? "Projekt-Gitea (öffnet in neuem Tab)"
: "Project Gitea (opens in new tab)"
}
@@ -349,7 +378,7 @@ export default function Footer() {
? "LinkedIn del autor (abre en una pestaña nueva)"
: isFr
? "LinkedIn de l'auteur (ouvre un nouvel onglet)"
: isDe
: isZh ? "作者 LinkedIn(在新标签页打开)" : isDe
? "LinkedIn des Autors (öffnet in neuem Tab)"
: "Author LinkedIn (opens in new tab)"
}
@@ -373,7 +402,7 @@ export default function Footer() {
? "ESPDocs. Proyecto independiente, sin afiliación con Espressif Systems."
: isFr
? "ESPDocs. Projet indépendant, sans affiliation avec Espressif Systems."
: isDe
: isZh ? "ESPDocs。独立项目,与乐鑫(Espressif Systems)无任何关联。" : isDe
? "ESPDocs. Unabhängiges Projekt, nicht verbunden mit Espressif Systems."
: "ESPDocs. Independent project, not affiliated with Espressif Systems."}
</p>
@@ -393,7 +422,7 @@ export default function Footer() {
? "Fuentes oficiales: Espressif Systems."
: isFr
? "Sources officielles : Espressif Systems."
: isDe
: isZh ? "官方资料来源:乐鑫(Espressif Systems)。" : isDe
? "Offizielle Quellen: Espressif Systems."
: "Official sources: Espressif Systems."}
</span>
+60 -19
View File
@@ -177,6 +177,35 @@ const MENUS_DE = [
],
},
];
const MENUS_ZH = [
{
id: "hardware",
label: "硬件",
items: [
{ href: "/series", label: "ESP32 系列", desc: "12 款芯片的引脚与规格", icon: Cpu },
{ href: "/comparacao", label: "对比工具", desc: "最多四个系列并排对比", icon: Scale },
{ href: "/catalogo", label: "开发板目录", desc: "DevKit、引脚与购买渠道", icon: Box },
],
},
{
id: "tools",
label: "工具",
items: [
{ href: "/seletor", label: "智能选型", desc: "为你的项目找到合适的芯片", icon: SlidersHorizontal },
{ href: "/diagnostico", label: "故障诊断", desc: "欠压、启动循环、看门狗等问题", icon: AlertTriangle },
{ href: "/componentes", label: "Component Registry", desc: "驱动与 idf_component.yml 清单", icon: Search },
],
},
{
id: "library",
label: "资料库",
items: [
{ href: "/frameworks", label: "框架与 SDK", desc: "ESP-IDF、Arduino、Rust 与 Matter", icon: BookOpen },
{ href: "/glossario", label: "技术术语表", desc: "数据手册中出现的术语", icon: BookA },
{ href: "/sobre", label: "关于本项目", desc: "作者、方法与开放性", icon: Sparkles },
],
},
];
const MOBILE_LINKS_PT = [
{ href: "/series", label: "Séries ESP32" },
@@ -238,6 +267,17 @@ const MOBILE_LINKS_DE = [
{ href: "/catalogo", label: "Board-Katalog" },
{ href: "/sobre", label: "Über uns" },
];
const MOBILE_LINKS_ZH = [
{ href: "/series", label: "ESP32 系列" },
{ href: "/comparacao", label: "对比工具" },
{ href: "/seletor", label: "智能选型" },
{ href: "/diagnostico", label: "故障诊断" },
{ href: "/frameworks", label: "框架与 SDK" },
{ href: "/componentes", label: "Component Registry" },
{ href: "/glossario", label: "术语表" },
{ href: "/catalogo", label: "开发板目录" },
{ href: "/sobre", label: "关于" },
];
function isActivePath(pathname, href, currentLocale) {
const localized = getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -263,6 +303,7 @@ export default function Header() {
const isEs = currentLocale === "es";
const isFr = currentLocale === "fr";
const isDe = currentLocale === "de";
const isZh = currentLocale === "zh-CN";
const getLinkHref = (href) => getLocalizedHref(`/${currentLocale}${href}`, currentLocale);
@@ -308,10 +349,10 @@ export default function Header() {
? MOBILE_LINKS_ES
: isFr
? MOBILE_LINKS_FR
: isDe
: isZh ? MOBILE_LINKS_ZH : isDe
? MOBILE_LINKS_DE
: MOBILE_LINKS_EN;
const MENUS = isPt ? MENUS_PT : isEs ? MENUS_ES : isFr ? MENUS_FR : isDe ? MENUS_DE : MENUS_EN;
const MENUS = isPt ? MENUS_PT : isEs ? MENUS_ES : isFr ? MENUS_FR : isZh ? MENUS_ZH : isDe ? MENUS_DE : MENUS_EN;
return (
<header className="sticky top-0 z-50 pt-3 sm:pt-4">
@@ -327,7 +368,7 @@ export default function Header() {
? "ESPDocs, página de inicio"
: isFr
? "ESPDocs, page d'accueil"
: isDe
: isZh ? "ESPDocs,首页" : isDe
? "ESPDocs, Startseite"
: "ESPDocs, homepage"
}
@@ -346,7 +387,7 @@ export default function Header() {
? "Navegación principal"
: isFr
? "Navigation principale"
: isDe
: isZh ? "主导航" : isDe
? "Hauptnavigation"
: "Main navigation"
}
@@ -403,18 +444,18 @@ export default function Header() {
</Link>
)}
<Link href={getLinkHref("/sobre")} className={`nav-link ${isActivePath(pathname, "/sobre", currentLocale) ? "is-active" : ""}`} aria-current={isActivePath(pathname, "/sobre", currentLocale) ? "page" : undefined}>
{isPt ? "Sobre" : isEs ? "Acerca de" : isFr ? "À propos" : isDe ? "Über uns" : "About"}
{isPt ? "Sobre" : isEs ? "Acerca de" : isFr ? "À propos" : isZh ? "关于" : isDe ? "Über uns" : "About"}
</Link>
</nav>
<div className="flex items-center gap-1 sm:gap-1.5">
<Link href={getLinkHref("/seletor")} className="btn btn-primary btn-sm hidden lg:inline-flex">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{isPt ? "Selecionar chip" : isEs ? "Seleccionar chip" : isFr ? "Choisir une puce" : isDe ? "Chip auswählen" : "Select chip"}
{isPt ? "Selecionar chip" : isEs ? "Seleccionar chip" : isFr ? "Choisir une puce" : isZh ? "选择芯片" : isDe ? "Chip auswählen" : "Select chip"}
</Link>
<SearchDialog />
<LanguageSelector />
<ThemeToggle isPt={isPt} isEs={isEs} isFr={isFr} isDe={isDe} />
<ThemeToggle isPt={isPt} isEs={isEs} isFr={isFr} isDe={isDe} isZh={isZh} />
<button
ref={menuButtonRef}
type="button"
@@ -426,7 +467,7 @@ export default function Header() {
? "Abrir menú de navegación"
: isFr
? "Ouvrir le menu de navigation"
: isDe
: isZh ? "打开导航菜单" : isDe
? "Navigationsmenü öffnen"
: "Open navigation menu"
}
@@ -459,7 +500,7 @@ export default function Header() {
? "Menú de navegación"
: isFr
? "Menu de navigation"
: isDe
: isZh ? "导航菜单" : isDe
? "Navigationsmenü"
: "Navigation menu"
}
@@ -472,10 +513,10 @@ export default function Header() {
<div className="flex items-start justify-between px-5 py-5">
<div>
<p className="text-xs font-semibold text-muted">
{isPt ? "Navegação" : isEs ? "Navegación" : isFr ? "Navigation" : isDe ? "Navigation" : "Navigation"}
{isPt ? "Navegação" : isEs ? "Navegación" : isFr ? "Navigation" : isZh ? "导航" : isDe ? "Navigation" : "Navigation"}
</p>
<p className="mt-1 font-display text-2xl font-semibold tracking-[-0.04em] text-ink">
{isPt ? "Para onde ir" : isEs ? "A dónde ir" : isFr ? "Où aller" : isDe ? "Wohin möchten Sie" : "Where to go"}
{isPt ? "Para onde ir" : isEs ? "A dónde ir" : isFr ? "Où aller" : isZh ? "前往" : isDe ? "Wohin möchten Sie" : "Where to go"}
</p>
</div>
<button
@@ -483,7 +524,7 @@ export default function Header() {
type="button"
onClick={() => setDrawerOpen(false)}
className="grid h-10 w-10 place-items-center rounded-full border border-line text-ink transition-colors hover:bg-surface-2"
aria-label={isPt ? "Fechar menu" : isEs ? "Cerrar menú" : isFr ? "Fermer le menu" : isDe ? "Menü schließen" : "Close menu"}
aria-label={isPt ? "Fechar menu" : isEs ? "Cerrar menú" : isFr ? "Fermer le menu" : isZh ? "关闭菜单" : isDe ? "Menü schließen" : "Close menu"}
>
<X className="h-5 w-5" aria-hidden="true" />
</button>
@@ -517,7 +558,7 @@ export default function Header() {
<nav
className="flex-1 overflow-y-auto px-3 py-2"
aria-label={isPt ? "Navegação móvel" : isEs ? "Navegación móvil" : isFr ? "Navigation mobile" : isDe ? "Mobile Navigation" : "Mobile navigation"}
aria-label={isPt ? "Navegação móvel" : isEs ? "Navegación móvil" : isFr ? "Navigation mobile" : isZh ? "移动端导航" : isDe ? "Mobile Navigation" : "Mobile navigation"}
>
{visibleMobileLinks.map((link) => (
<Link
@@ -536,7 +577,7 @@ export default function Header() {
<div className="p-4 pt-2">
<Link href={getLinkHref("/seletor")} onClick={() => setDrawerOpen(false)} className="btn btn-primary w-full">
<SlidersHorizontal className="h-4 w-4" aria-hidden="true" />
{isPt ? "Selecionar meu chip" : isEs ? "Seleccionar mi chip" : isFr ? "Choisir ma puce" : isDe ? "Meinen Chip auswählen" : "Select my chip"}
{isPt ? "Selecionar meu chip" : isEs ? "Seleccionar mi chip" : isFr ? "Choisir ma puce" : isZh ? "选择我的芯片" : isDe ? "Meinen Chip auswählen" : "Select my chip"}
</Link>
</div>
</div>
@@ -547,7 +588,7 @@ export default function Header() {
);
}
function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false }) {
function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false, isZh = false }) {
const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
@@ -572,7 +613,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Activar tema claro"
: isFr
? "Activer le thème clair"
: isDe
: isZh ? "切换到浅色主题" : isDe
? "Helles Design aktivieren"
: "Switch to light theme"
: isPt
@@ -581,7 +622,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Activar tema oscuro"
: isFr
? "Activer le thème sombre"
: isDe
: isZh ? "切换到深色主题" : isDe
? "Dunkles Design aktivieren"
: "Switch to dark theme"
}
@@ -593,7 +634,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Tema claro"
: isFr
? "Thème clair"
: isDe
: isZh ? "浅色主题" : isDe
? "Helles Design"
: "Light theme"
: isPt
@@ -602,7 +643,7 @@ function ThemeToggle({ isPt = true, isEs = false, isFr = false, isDe = false })
? "Tema oscuro"
: isFr
? "Thème sombre"
: isDe
: isZh ? "深色主题" : isDe
? "Dunkles Design"
: "Dark theme"
}
+10 -3
View File
@@ -17,6 +17,7 @@ export default function PageHero({
isEs: isEsProp,
isFr: isFrProp,
isDe: isDeProp,
isZh: isZhProp,
locale: localeProp,
}) {
const center = align === "center";
@@ -44,16 +45,22 @@ export default function PageHero({
: localeProp
? localeProp === "de"
: trail.some((t) => t.isDe || (t.href && t.href.startsWith("/de")));
const isZh =
isZhProp !== undefined
? isZhProp
: localeProp
? localeProp === "zh-CN"
: trail.some((t) => t.isZh || (t.href && t.href.startsWith("/zh-CN")));
const homeHref = isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isDe ? "/de" : "/";
const homeLabel = isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isDe ? "Startseite" : "Início";
const homeHref = isEn ? "/en" : isEs ? "/es" : isFr ? "/fr" : isZh ? "/zh-CN" : isDe ? "/de" : "/";
const homeLabel = isEn ? "Home" : isEs ? "Inicio" : isFr ? "Accueil" : isZh ? "首页" : isDe ? "Startseite" : "Início";
const breadcrumbAria = isEn
? "Breadcrumb navigation"
: isEs
? "Navegación por migas de pan"
: isFr
? "Fil d'Ariane"
: isDe
: isZh ? "面包屑导航" : isDe
? "Brotkrumennavigation"
: "Trilha de navegação";
+73 -10
View File
@@ -322,6 +322,66 @@ const GRUPOS_DE = [
combina: (avisos) => /unbeschaltet|floating/i.test(avisos),
},
];
const GRUPOS_ZH = [
{
id: "entrada",
titulo: "仅输入",
icone: ArrowDownToLine,
cor: "text-accent",
explicacao:
"仅能作输入的引脚。它们不能配置为输出,也没有内部上拉或下拉电阻。如有需要,请在外部电路中添加。",
combina: (avisos) => /仅为输入|仅输入|input[- ]only/i.test(avisos),
},
{
id: "flash",
titulo: "连接到闪存/PSRAM",
icone: HardDrive,
cor: "text-copper",
explicacao:
"已连接到存放并执行固件的存储器。把它们当作通用 GPIO 使用,通常会使芯片崩溃或破坏程序执行。在带内置闪存的模组中,这些引脚不会引出到封装引脚上。",
combina: (avisos) =>
/(flash|psram)/i.test(avisos) &&
/(专用于|存储使用|不得用于|不建议|dedicated to)/i.test(avisos),
},
{
id: "strapping",
titulo: "启动配置引脚(Strapping,启动模式)",
icone: Power,
cor: "text-gold",
explicacao:
"芯片在复位期间会对这些引脚的逻辑电平采样,以决定启动模式。启动完成后可以使用它们,但外部电路在复位期间不能把它们强制到错误的电平,否则开发板将无法启动,或意外进入下载模式。",
combina: (avisos, cats) =>
cats.includes("Strapping") ||
/strapping|启动模式|启动配置|boot mode/i.test(avisos),
},
{
id: "debug",
titulo: "烧录与调试",
icone: Bug,
cor: "text-accent",
explicacao:
"这些是用于烧录的 UART 和 JTAG 线。它们仍可用作 GPIO,但一旦复用,就必须先断开所接外设,才能对开发板烧录和调试。",
combina: (avisos, cats) => /调试|烧录|debugging/i.test(avisos) || cats.includes("JTAG"),
},
{
id: "usb",
titulo: "原生 USB",
icone: Usb,
cor: "text-accent",
explicacao:
"默认连接到内部的 USB Serial/JTAG 控制器。要把它们用作 GPIO,就必须禁用该外设,同时也会失去直接通过 USB 烧录的能力。",
combina: (avisos) => /usb serial\/jtag|usb_d[+-]/i.test(avisos),
},
{
id: "eletrico",
titulo: "不能悬空",
icone: Zap,
cor: "text-gold",
explicacao:
"需要确定的硬件逻辑电平:外部上拉或 RC 延时电路。悬空会导致随机复位或启动不稳定。",
combina: (avisos) => /浮空|悬空|floating/i.test(avisos),
},
];
/** Nome do pino + o GPIO correspondente, quando o nome é uma função (ex.: SENSOR_VP). */
function rotuloDoPino(pino) {
@@ -329,14 +389,14 @@ function rotuloDoPino(pino) {
return gpio && gpio !== pino.nome ? `${pino.nome} · ${gpio}` : pino.nome;
}
function derivarGrupos(conexoes, isEn, isEs, isFr, isDe) {
function derivarGrupos(conexoes, isEn, isEs, isFr, isDe, isZh = false) {
const listaGrupos = isEn
? GRUPOS_EN
: isEs
? GRUPOS_ES
: isFr
? GRUPOS_FR
: isDe
: isZh ? GRUPOS_ZH : isDe
? GRUPOS_DE
: GRUPOS_PT;
return listaGrupos.map((grupo) => {
@@ -354,15 +414,16 @@ function derivarGrupos(conexoes, isEn, isEs, isFr, isDe) {
}).filter((g) => g.pinos.length > 0);
}
export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs = false, isFr = false, isDe = false, locale }) {
export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs = false, isFr = false, isDe = false, isZh = false, locale }) {
if (!conexoes?.length) return null;
const effectiveIsEn = isEn || locale === "en";
const effectiveIsEs = isEs || locale === "es";
const effectiveIsFr = isFr || locale === "fr";
const effectiveIsDe = isDe || locale === "de";
const effectiveIsZh = isZh || locale === "zh-CN";
const grupos = derivarGrupos(conexoes, effectiveIsEn, effectiveIsEs, effectiveIsFr, effectiveIsDe);
const grupos = derivarGrupos(conexoes, effectiveIsEn, effectiveIsEs, effectiveIsFr, effectiveIsDe, effectiveIsZh);
if (!grupos.length) return null;
const totalPinos = new Set(grupos.flatMap((g) => g.pinos)).size;
@@ -379,7 +440,7 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
? "Pines con restricción de uso"
: effectiveIsFr
? "Broches avec restrictions d'utilisation"
: effectiveIsDe
: effectiveIsZh ? "有使用限制的引脚" : effectiveIsDe
? "Pins mit Nutzungseinschränkungen"
: "Pinos com restrição de uso"}
</h2>
@@ -397,7 +458,9 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
<>
Toutes les broches de l&apos;{serieKey} ne sont pas libres pour un usage général. {totalPinos} d&apos;entre elles ont des conditions : occupées par la mémoire, lues pendant le démarrage ou fonctionnent uniquement en entrée. Pensez à vérifier avant de finaliser votre schéma.
</>
) : effectiveIsDe ? (
) : effectiveIsZh ? ( <>
{serieKey} 上并非每个引脚都可以随意用作通用 IO。其中有 {totalPinos} 个带有使用条件:被存储器占用、在启动时被读取,或只能作输入。在定稿原理图之前,建议先核对一遍。
</> ) : effectiveIsDe ? (
<>
Nicht jeder Pin des {serieKey} steht für allgemeine Zwecke frei zur Verfügung. {totalPinos} davon unterliegen Bedingungen: durch Speicher belegt, beim Booten abgefragt oder reine Eingangspins. Prüfen Sie dies vor Abschluss Ihres Schaltplans.
</>
@@ -425,7 +488,7 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
? "pin"
: effectiveIsFr
? "broche"
: effectiveIsDe
: effectiveIsZh ? "个引脚" : effectiveIsDe
? "Pin"
: "pino"
: effectiveIsEn
@@ -434,7 +497,7 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
? "pines"
: effectiveIsFr
? "broches"
: effectiveIsDe
: effectiveIsZh ? "个引脚" : effectiveIsDe
? "Pins"
: "pinos"}
</span>
@@ -461,14 +524,14 @@ export default function PinRestrictions({ conexoes, serieKey, isEn = false, isEs
"Estos grupos proceden de los mismos datos que el diagrama de conexiones. Allí podrá consultar pino a pino todas las funciones, el dominio de alimentación y los avisos completos."
) : effectiveIsFr ? (
"Ces groupes sont issus des mêmes données que le schéma de brochage. Vous pouvez y consulter broche par broche l'ensemble des fonctions, le domaine d'alimentation et les remarques complètes."
) : effectiveIsDe ? (
) : effectiveIsZh ? ( "这些分组来自与连接图相同的数据。在那里可以逐个引脚查看全部功能、供电域和完整的警告。" ) : effectiveIsDe ? (
"Diese Gruppen stammen aus denselben Daten wie das Pinbelegungsdiagramm. Dort finden Sie für jeden Pin alle Funktionen, den Spannungsbereich und die vollständigen Entwicklungshinweise."
) : (
"Estes grupos vêm dos mesmos dados do diagrama de conexões. Lá você vê pino a pino todas as funções, o domínio de alimentação e os avisos completos."
)}
</p>
<Link href="#connections" className="btn btn-ghost shrink-0">
{effectiveIsEn ? "Open pinout diagram" : effectiveIsEs ? "Abrir diagrama de pines" : effectiveIsFr ? "Ouvrir le brochage" : effectiveIsDe ? "Pinbelegung öffnen" : "Abrir diagrama de pinos"}
{effectiveIsEn ? "Open pinout diagram" : effectiveIsEs ? "Abrir diagrama de pines" : effectiveIsFr ? "Ouvrir le brochage" : effectiveIsZh ? "打开引脚图" : effectiveIsDe ? "Pinbelegung öffnen" : "Abrir diagrama de pinos"}
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</Link>
</div>
+40 -26
View File
@@ -75,6 +75,16 @@ const TIPOS_DE = {
erro: { label: "Fehler", Icon: AlertTriangle, cor: "bg-gold-soft text-gold" },
componente: { label: "Komponente", Icon: Box, cor: "bg-surface-2 text-ink-soft" },
};
const TIPOS_ZH = {
resposta: { label: "答案", Icon: Sparkles, cor: "bg-accent text-accent-contrast" },
pino: { label: "引脚", Icon: Plug, cor: "bg-accent text-accent-contrast" },
pagina: { label: "页面", Icon: FileText, cor: "bg-surface-2 text-ink-soft" },
serie: { label: "系列", Icon: Cpu, cor: "bg-accent-soft text-accent" },
framework: { label: "框架", Icon: Terminal, cor: "bg-teal-soft text-teal" },
glossario: { label: "术语", Icon: BookOpen, cor: "bg-copper-soft text-copper" },
erro: { label: "错误", Icon: AlertTriangle, cor: "bg-gold-soft text-gold" },
componente: { label: "组件", Icon: Box, cor: "bg-surface-2 text-ink-soft" },
};
const INTELIGENTES = new Set(["resposta", "pino"]);
@@ -82,6 +92,7 @@ const EXEMPLOS_EN = ["flash s3", "usb pins c6", "gpio0 esp32", "brownout", "LVGL
const EXEMPLOS_ES = ["flash s3", "pines usb c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"];
const EXEMPLOS_FR = ["flash s3", "broches usb c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"];
const EXEMPLOS_DE = ["flash s3", "usb pins c6", "gpio0 esp32", "brownout", "LVGL", "FreeRTOS"];
const EXEMPLOS_ZH = ["s3 闪存", "c6 usb 引脚", "esp32 gpio0", "brownout", "LVGL", "FreeRTOS"];
function Destaque({ texto, termos }) {
if (!termos.length) return texto;
@@ -142,14 +153,17 @@ export default function SearchDialog() {
? "fr"
: pathname === "/de" || pathname.startsWith("/de/")
? "de"
: pathname === "/zh-CN" || pathname.startsWith("/zh-CN/")
? "zh-CN"
: "pt";
const isEn = locale === "en";
const isEs = locale === "es";
const isFr = locale === "fr";
const isDe = locale === "de";
const isZh = locale === "zh-CN";
const TIPOS = isEn ? TIPOS_EN : isEs ? TIPOS_ES : isFr ? TIPOS_FR : isDe ? TIPOS_DE : TIPOS_PT;
const EXEMPLOS = isEn ? EXEMPLOS_EN : isEs ? EXEMPLOS_ES : isFr ? EXEMPLOS_FR : isDe ? EXEMPLOS_DE : EXEMPLOS_DE_BUSCA;
const TIPOS = isEn ? TIPOS_EN : isEs ? TIPOS_ES : isFr ? TIPOS_FR : isZh ? TIPOS_ZH : isDe ? TIPOS_DE : TIPOS_PT;
const EXEMPLOS = isEn ? EXEMPLOS_EN : isEs ? EXEMPLOS_ES : isFr ? EXEMPLOS_FR : isZh ? EXEMPLOS_ZH : isDe ? EXEMPLOS_DE : EXEMPLOS_DE_BUSCA;
const [aberto, setAberto] = useState(false);
const [montado, setMontado] = useState(false);
@@ -196,7 +210,7 @@ export default function SearchDialog() {
? "/api/busca/es?v=2"
: isFr
? "/api/busca/fr?v=2"
: isDe
: isZh ? "/api/busca/zh-CN?v=2" : isDe
? "/api/busca/de?v=2"
: "/api/busca?v=2";
fetch(url, { cache: "no-cache" })
@@ -222,7 +236,7 @@ export default function SearchDialog() {
return () => {
cancelado = true;
};
}, [isEn, isEs, isFr, isDe]);
}, [isEn, isEs, isFr, isDe, isZh]);
// Carrega o índice (textos + especificações + pinos) na primeira abertura.
useEffect(() => {
@@ -293,17 +307,17 @@ export default function SearchDialog() {
id="botao-busca"
onClick={() => setAberto(true)}
className="flex h-11 min-w-11 shrink-0 cursor-pointer items-center justify-center gap-2 rounded-full border border-line px-3 text-ink transition-colors hover:bg-surface-2 xl:px-4"
aria-label={isEn ? "Search ESPDocs (Ctrl+K)" : isEs ? "Buscar en ESPDocs (Ctrl+K)" : isFr ? "Rechercher sur ESPDocs (Ctrl+K)" : isDe ? "ESPDocs durchsuchen (Strg+K)" : "Buscar no ESPDocs (Ctrl+K)"}
aria-label={isEn ? "Search ESPDocs (Ctrl+K)" : isEs ? "Buscar en ESPDocs (Ctrl+K)" : isFr ? "Rechercher sur ESPDocs (Ctrl+K)" : isZh ? "在 ESPDocs 中搜索(Ctrl+K)" : isDe ? "ESPDocs durchsuchen (Strg+K)" : "Buscar no ESPDocs (Ctrl+K)"}
>
<Search className="h-4 w-4" aria-hidden="true" />
<span className="hidden text-sm font-medium xl:inline">{isEn ? "Search" : isEs ? "Buscar" : isFr ? "Rechercher" : isDe ? "Suchen" : "Buscar"}</span>
<kbd className="hidden rounded-md border border-line bg-surface-2 px-1.5 py-0.5 font-mono text-xs font-semibold text-muted xl:inline">{isDe ? "Strg K" : "Ctrl K"}</kbd>
<span className="hidden text-sm font-medium xl:inline">{isEn ? "Search" : isEs ? "Buscar" : isFr ? "Rechercher" : isZh ? "搜索" : isDe ? "Suchen" : "Buscar"}</span>
<kbd className="hidden rounded-md border border-line bg-surface-2 px-1.5 py-0.5 font-mono text-xs font-semibold text-muted xl:inline">{isZh ? "Ctrl K" : isDe ? "Strg K" : "Ctrl K"}</kbd>
</button>
{montado &&
aberto &&
createPortal(
<div className="fixed inset-0 z-[120] flex items-start justify-center px-4 pt-[8vh]" role="dialog" aria-modal="true" aria-label={isEn ? "Search ESPDocs" : isEs ? "Buscar en ESPDocs" : isFr ? "Rechercher sur ESPDocs" : isDe ? "ESPDocs durchsuchen" : "Buscar no ESPDocs"}>
<div className="fixed inset-0 z-[120] flex items-start justify-center px-4 pt-[8vh]" role="dialog" aria-modal="true" aria-label={isEn ? "Search ESPDocs" : isEs ? "Buscar en ESPDocs" : isFr ? "Rechercher sur ESPDocs" : isZh ? "在 ESPDocs 中搜索" : isDe ? "ESPDocs durchsuchen" : "Buscar no ESPDocs"}>
<div className="dialog-scrim absolute inset-0 bg-black/45 backdrop-blur-sm" onClick={fechar} />
<div className="dialog-panel relative flex max-h-[80vh] w-full max-w-2xl flex-col overflow-hidden rounded-3xl border border-line bg-canvas shadow-2xl">
<div className="flex items-center gap-3 border-b border-line px-5">
@@ -322,20 +336,20 @@ export default function SearchDialog() {
? "Pregunte a ESPDocs: “flash s3”, “pines usb c6”, “gpio0 esp32”…"
: isFr
? "Demandez à ESPDocs : « flash s3 », « broches usb c6 », « gpio0 esp32 »…"
: isDe
: isZh ? "向 ESPDocs 提问:“s3 闪存”、“c6 usb 引脚”、“esp32 gpio0”…" : isDe
? "ESPDocs fragen: „flash s3“, „usb pins c6“, „gpio0 esp32“… "
: "Pergunte ao ESPDocs: “flash s3”, “pinos usb c6”, “gpio0 esp32”…"
}
className="flex-1 bg-transparent py-5 text-base text-ink outline-none placeholder:text-muted"
autoComplete="off"
spellCheck={false}
aria-label={isEn ? "Search query" : isEs ? "Término de búsqueda" : isFr ? "Requête de recherche" : isDe ? "Suchbegriff" : "Termo de busca"}
aria-label={isEn ? "Search query" : isEs ? "Término de búsqueda" : isFr ? "Requête de recherche" : isZh ? "搜索词" : isDe ? "Suchbegriff" : "Termo de busca"}
/>
<button
type="button"
onClick={fechar}
className="grid h-9 w-9 cursor-pointer place-items-center rounded-full text-muted transition-colors hover:bg-surface-2 hover:text-ink"
aria-label={isEn ? "Close search" : isEs ? "Cerrar búsqueda" : isFr ? "Fermer la recherche" : isDe ? "Suche schließen" : "Fechar busca"}
aria-label={isEn ? "Close search" : isEs ? "Cerrar búsqueda" : isFr ? "Fermer la recherche" : isZh ? "关闭搜索" : isDe ? "Suche schließen" : "Fechar busca"}
>
<X className="h-4 w-4" />
</button>
@@ -343,7 +357,7 @@ export default function SearchDialog() {
{entendeu && (
<div className="flex flex-wrap items-center gap-2 border-b border-line bg-surface px-5 py-2.5">
<span className="text-xs font-semibold text-muted">{isEn ? "Understood:" : isEs ? "Entendido:" : isFr ? "Compris :" : isDe ? "Erkannt:" : "Entendi:"}</span>
<span className="text-xs font-semibold text-muted">{isEn ? "Understood:" : isEs ? "Entendido:" : isFr ? "Compris :" : isZh ? "理解为:" : isDe ? "Erkannt:" : "Entendi:"}</span>
{interpretacao.chips.map((c) => (
<Pilula key={c.chave} forte>{c.nome}</Pilula>
))}
@@ -357,8 +371,8 @@ export default function SearchDialog() {
<div className="flex flex-wrap items-center gap-2 border-b border-line px-5 py-2.5">
<span className="text-xs font-semibold text-muted">
{interpretacao.chips.length
? (isEn ? "Ask about:" : isEs ? "Preguntar sobre:" : isFr ? "Poser une question sur :" : isDe ? "Frage zu:" : "Perguntar sobre:")
: (isEn ? "Which chip?" : isEs ? "¿Para qué chip?" : isFr ? "Pour quelle puce ?" : isDe ? "Für welchen Chip?" : "Para qual chip?")}
? (isEn ? "Ask about:" : isEs ? "Preguntar sobre:" : isFr ? "Poser une question sur :" : isZh ? "查询:" : isDe ? "Frage zu:" : "Perguntar sobre:")
: (isEn ? "Which chip?" : isEs ? "¿Para qué chip?" : isFr ? "Pour quelle puce ?" : isZh ? "针对哪款芯片?" : isDe ? "Für welchen Chip?" : "Para qual chip?")}
</span>
{sugestoes.map((s) => (
<Pilula key={s.rotulo} onClick={() => anexar(s.anexar)}>
@@ -378,7 +392,7 @@ export default function SearchDialog() {
? "No se pudo cargar el índice de búsqueda."
: isFr
? "Impossible de charger l'index de recherche."
: isDe
: isZh ? "无法加载搜索索引。" : isDe
? "Suchindex konnte nicht geladen werden."
: "Não foi possível carregar o índice de busca."}
</p>
@@ -387,20 +401,20 @@ export default function SearchDialog() {
onClick={carregarIndice}
className="btn btn-primary btn-sm mt-3"
>
{isEn ? "Try again" : isEs ? "Reintentar" : isFr ? "Réessayer" : isDe ? "Erneut versuchen" : "Tentar novamente"}
{isEn ? "Try again" : isEs ? "Reintentar" : isFr ? "Réessayer" : isZh ? "重试" : isDe ? "Erneut versuchen" : "Tentar novamente"}
</button>
</div>
)}
{!erro && !indice && (
<p className="p-6 text-center text-sm text-muted">
{isEn ? "Loading…" : isEs ? "Cargando…" : isFr ? "Chargement…" : isDe ? "Wird geladen…" : "Carregando…"}
{isEn ? "Loading…" : isEs ? "Cargando…" : isFr ? "Chargement…" : isZh ? "加载中…" : isDe ? "Wird geladen…" : "Carregando…"}
</p>
)}
{indice && vazia && (
<div className="px-3 pb-3 pt-1">
<p className="text-xs font-semibold text-muted">
{isEn ? "Try asking" : isEs ? "Pruebe a preguntar" : isFr ? "Essayez de demander" : isDe ? "Versuchen Sie" : "Experimente perguntar"}
{isEn ? "Try asking" : isEs ? "Pruebe a preguntar" : isFr ? "Essayez de demander" : isZh ? "试着问问" : isDe ? "Versuchen Sie" : "Experimente perguntar"}
</p>
<div className="mt-2 flex flex-wrap gap-2">
{EXEMPLOS.map((ex) => (
@@ -410,7 +424,7 @@ export default function SearchDialog() {
))}
</div>
<p className="mt-4 text-xs font-semibold text-muted">
{isEn ? "Go to" : isEs ? "Ir a" : isFr ? "Aller à" : isDe ? "Gehe zu" : "Ir para"}
{isEn ? "Go to" : isEs ? "Ir a" : isFr ? "Aller à" : isZh ? "前往" : isDe ? "Gehe zu" : "Ir para"}
</p>
</div>
)}
@@ -424,7 +438,7 @@ export default function SearchDialog() {
? `Sin resultados para “${consulta}”`
: isFr
? `Aucun résultat pour « ${consulta} »`
: isDe
: isZh ? `未找到“${consulta}”的结果` : isDe
? `Keine Ergebnisse für „${consulta}“`
: `Nenhum resultado para “${consulta}”`}
</p>
@@ -435,7 +449,7 @@ export default function SearchDialog() {
? "Pruebe “brownout”, “flash s3”, “pines usb c6” o “LVGL”."
: isFr
? "Essayez « brownout », « flash s3 », « broches usb c6 » ou « LVGL »."
: isDe
: isZh ? "试试“brownout”、“s3 闪存”、“c6 usb 引脚”或“LVGL”。" : isDe
? "Versuchen Sie „brownout“, „flash s3“, „usb pins c6“ oder „LVGL“."
: "Tente “brownout”, “flash s3”, “pinos usb c6” ou “LVGL”."}
</p>
@@ -501,9 +515,9 @@ export default function SearchDialog() {
</div>
<div className="hidden items-center gap-4 border-t border-line bg-surface px-4 py-2.5 text-xs text-muted md:flex">
<span className="flex items-center gap-1"><ArrowUp className="h-3 w-3" /><ArrowDown className="h-3 w-3" /> {isEn ? "navigate" : isEs ? "navegar" : isFr ? "naviguer" : isDe ? "navigieren" : "navegar"}</span>
<span className="flex items-center gap-1"><CornerDownLeft className="h-3 w-3" /> {isEn ? "open" : isEs ? "abrir" : isFr ? "ouvrir" : isDe ? "öffnen" : "abrir"}</span>
<span><kbd className="font-mono font-semibold">Esc</kbd> {isEn ? "close" : isEs ? "cerrar" : isFr ? "fermer" : isDe ? "schließen" : "fechar"}</span>
<span className="flex items-center gap-1"><ArrowUp className="h-3 w-3" /><ArrowDown className="h-3 w-3" /> {isEn ? "navigate" : isEs ? "navegar" : isFr ? "naviguer" : isZh ? "导航" : isDe ? "navigieren" : "navegar"}</span>
<span className="flex items-center gap-1"><CornerDownLeft className="h-3 w-3" /> {isEn ? "open" : isEs ? "abrir" : isFr ? "ouvrir" : isZh ? "打开" : isDe ? "öffnen" : "abrir"}</span>
<span><kbd className="font-mono font-semibold">Esc</kbd> {isEn ? "close" : isEs ? "cerrar" : isFr ? "fermer" : isZh ? "关闭" : isDe ? "schließen" : "fechar"}</span>
<span className="ml-auto">
{vazia
? (isEn
@@ -512,7 +526,7 @@ export default function SearchDialog() {
? "Consejo: presione / en cualquier página"
: isFr
? "Astuce : appuyez sur / sur n'importe quelle page"
: isDe
: isZh ? "提示:在任意页面按 / 键" : isDe
? "Tipp: Drücken Sie / auf einer beliebigen Seite"
: "Dica: pressione / em qualquer página")
: `${resultados.length} ${
@@ -522,7 +536,7 @@ export default function SearchDialog() {
? "resultado(s)"
: isFr
? "résultat(s)"
: isDe
: isZh ? "条结果" : isDe
? "Ergebnis(se)"
: "resultado(s)"
}`}
+5 -2
View File
@@ -2,11 +2,12 @@
import { useEffect, useRef, useState } from "react";
export default function SeriesTabMenu({ tabs, isEn = false, isEs = false, isFr = false, isDe = false, locale }) {
export default function SeriesTabMenu({ tabs, isEn = false, isEs = false, isFr = false, isDe = false, isZh = false, locale }) {
const effectiveIsEn = isEn || locale === "en";
const effectiveIsEs = isEs || locale === "es";
const effectiveIsFr = isFr || locale === "fr";
const effectiveIsDe = isDe || locale === "de";
const effectiveIsZh = isZh || locale === "zh-CN";
const availableTabs = tabs.filter((tab) => tab.available);
const [activeTab, setActiveTab] = useState(availableTabs[0]?.id);
const refs = useRef({});
@@ -66,7 +67,9 @@ export default function SeriesTabMenu({ tabs, isEn = false, isEs = false, isFr =
<div
role="tablist"
aria-label={
effectiveIsEn
effectiveIsZh
? "开发板与图示"
: effectiveIsEn
? "Board and diagram"
: effectiveIsEs
? "Placa y diagrama"