feat(i18n): French and German localization (phase 5)
- Add full message catalogs and content for French (fr) and German (de) - Implement ASCII canonical slugs for French and German routes - Add language-specific search endpoints (/api/busca/fr, /api/busca/de) - Configure strict 404 responses for /fr/livro and /de/livro - Localize SEO, OpenGraph (fr_FR, de_DE), Twitter and JSON-LD schemas - Add localized code example comments across 14 examples for fr and de - Enable 'fr' and 'de' in ENABLED_LOCALES with root negotiation and redirect - Extend test suite in check-i18n, smoke-imagem and verifica-vazamentos Co-Authored-By: Antigravity <noreply@google.com>
This commit is contained in:
1 parent
7b673aeb4b
commit
7fce46b9e1
104 files changed
+33369
-686
No files matched your search
@@ -41,10 +41,50 @@ const HEADER_MAP_ES = {
|
||||
"LEDs": "LEDs",
|
||||
};
|
||||
|
||||
const HEADER_MAP_FR = {
|
||||
"Nome": "Nom",
|
||||
"Descrição": "Description",
|
||||
"Série": "Série",
|
||||
"Loja Oficial": "Boutique officielle",
|
||||
"Fabricante": "Fabricant",
|
||||
"Processador": "Processeur",
|
||||
"Memória Flash": "Mémoire flash",
|
||||
"PSRAM": "PSRAM",
|
||||
"Conector USB": "Connecteur USB",
|
||||
"Interface USB": "Interface USB",
|
||||
"Antena": "Antenne",
|
||||
"Bateria": "Batterie",
|
||||
"Display": "Écran",
|
||||
"Sensores": "Capteurs",
|
||||
"Botões": "Boutons",
|
||||
"LEDs": "LEDs",
|
||||
};
|
||||
|
||||
const HEADER_MAP_DE = {
|
||||
"Nome": "Name",
|
||||
"Descrição": "Beschreibung",
|
||||
"Série": "Serie",
|
||||
"Loja Oficial": "Offizieller Shop",
|
||||
"Fabricante": "Hersteller",
|
||||
"Processador": "Prozessor",
|
||||
"Memória Flash": "Flash-Speicher",
|
||||
"PSRAM": "PSRAM",
|
||||
"Conector USB": "USB-Anschluss",
|
||||
"Interface USB": "USB-Schnittstelle",
|
||||
"Antena": "Antenne",
|
||||
"Bateria": "Batterie",
|
||||
"Display": "Display",
|
||||
"Sensores": "Sensoren",
|
||||
"Botões": "Taster",
|
||||
"LEDs": "LEDs",
|
||||
};
|
||||
|
||||
export default function BoardCard({ board }) {
|
||||
const pathname = usePathname() || "";
|
||||
const isEn = pathname === "/en" || pathname.startsWith("/en/");
|
||||
const isEs = pathname === "/es" || pathname.startsWith("/es/");
|
||||
const isFr = pathname === "/fr" || pathname.startsWith("/fr/");
|
||||
const isDe = pathname === "/de" || pathname.startsWith("/de/");
|
||||
|
||||
const mainFields = ["Nome", "Link", "Imagem", "Loja Oficial"];
|
||||
|
||||
@@ -67,21 +107,31 @@ 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" : "Loja oficial"}
|
||||
{isEn ? "Official store" : isEs ? "Tienda oficial" : isFr ? "Boutique officielle" : 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" : "Placa genérica")}
|
||||
{board.Nome || (isEn ? "Generic board" : isEs ? "Placa genérica" : isFr ? "Carte générique" : 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" : "Recursos embarcados"}</p>
|
||||
<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"}
|
||||
</p>
|
||||
<ul className="mt-2 flex flex-wrap gap-1.5">
|
||||
{recursos.map((campo) => {
|
||||
const opcional = valor(campo) === "opcional";
|
||||
const label = isEn ? (HEADER_MAP_EN[campo] || campo) : isEs ? (HEADER_MAP_ES[campo] || campo) : campo;
|
||||
const label = isEn
|
||||
? (HEADER_MAP_EN[campo] || campo)
|
||||
: isEs
|
||||
? (HEADER_MAP_ES[campo] || campo)
|
||||
: isFr
|
||||
? (HEADER_MAP_FR[campo] || campo)
|
||||
: isDe
|
||||
? (HEADER_MAP_DE[campo] || campo)
|
||||
: campo;
|
||||
return (
|
||||
<li key={campo} className={`chip ${opcional ? "text-gold" : "text-ink-soft"}`}>
|
||||
{opcional ? (
|
||||
@@ -90,7 +140,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)" : " (opcional)"}</span>}
|
||||
{opcional && <span className="sr-only">{isEn ? " (optional)" : isEs ? " (opcional)" : isFr ? " (optionnel)" : isDe ? " (optional)" : " (opcional)"}</span>}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@@ -100,9 +150,11 @@ 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" : "Ver no AliExpress"}
|
||||
{isEn ? "View on AliExpress" : isEs ? "Ver en AliExpress" : isFr ? "Voir sur 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)" : " (abre em nova aba)"}</span>
|
||||
<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)"}
|
||||
</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user