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
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:
1 parent
ae61416468
commit
5a9fe676fc
61 files changed
+2517
-920
No files matched your search
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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'é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
@@ -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
@@ -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";
|
||||
|
||||
|
||||
@@ -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'{serieKey} ne sont pas libres pour un usage général. {totalPinos} d'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
@@ -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)"
|
||||
}`}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user