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
+110
-20
@@ -8,7 +8,9 @@ export default function CodeExample({ framework }) {
|
||||
const pathname = usePathname() || "";
|
||||
const isEn = pathname === "/en" || pathname.startsWith("/en/");
|
||||
const isEs = pathname === "/es" || pathname.startsWith("/es/");
|
||||
const loc = isEn ? "en" : isEs ? "es" : "pt";
|
||||
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 examplesUrl = framework?.exemplos;
|
||||
|
||||
@@ -30,8 +32,12 @@ export default function CodeExample({ framework }) {
|
||||
isEn
|
||||
? `// Full examples: ${examplesUrl || "see repository"}\n`
|
||||
: isEs
|
||||
? `// Ejemplos completos: ${examplesUrl || "ver repositorio"}\n`
|
||||
: `// Exemplos completos: ${examplesUrl || "ver repositório"}\n`
|
||||
? `// Ejemplos completos: ${examplesUrl || "ver repositorio"}\n`
|
||||
: isFr
|
||||
? `// Exemples complets : ${examplesUrl || "voir le dépôt"}\n`
|
||||
: isDe
|
||||
? `// Vollständige Beispiele: ${examplesUrl || "Repository ansehen"}\n`
|
||||
: `// Exemplos completos: ${examplesUrl || "ver repositório"}\n`
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -69,10 +75,24 @@ export default function CodeExample({ framework }) {
|
||||
isEn
|
||||
? "// The example file is empty. Refer to the official repository.\n"
|
||||
: isEs
|
||||
? "// El archivo de ejemplo está vacío. Consulte el repositorio oficial.\n"
|
||||
: "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n"
|
||||
? "// 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
|
||||
? "// Die Beispieldatei ist leer. Bitte konsultieren Sie das offizielle Repository.\n"
|
||||
: "// O arquivo de exemplo está vazio. Consulte o repositório oficial.\n"
|
||||
);
|
||||
setError(
|
||||
isEn
|
||||
? "empty file"
|
||||
: isEs
|
||||
? "archivo vacío"
|
||||
: isFr
|
||||
? "fichier vide"
|
||||
: isDe
|
||||
? "leere Datei"
|
||||
: "arquivo vazio"
|
||||
);
|
||||
setError(isEn ? "empty file" : isEs ? "archivo vacío" : "arquivo vazio");
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -81,10 +101,25 @@ export default function CodeExample({ framework }) {
|
||||
isEn
|
||||
? "// Could not load example.\n// Refer to official repository for complete examples.\n"
|
||||
: isEs
|
||||
? "// No fue posible cargar el ejemplo.\n// Consulte el repositorio oficial para ver los ejemplos completos.\n"
|
||||
: "// Não foi possível carregar o exemplo.\n// Consulte o repositório oficial para exemplos completos.\n"
|
||||
? "// 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
|
||||
? "// 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"
|
||||
);
|
||||
setError(
|
||||
err.message ||
|
||||
(isEn
|
||||
? "load failure"
|
||||
: isEs
|
||||
? "fallo al cargar"
|
||||
: isFr
|
||||
? "échec du chargement"
|
||||
: isDe
|
||||
? "Ladefehler"
|
||||
: "falha ao carregar")
|
||||
);
|
||||
setError(err.message || (isEn ? "load failure" : isEs ? "fallo al cargar" : "falha ao carregar"));
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
@@ -93,7 +128,7 @@ export default function CodeExample({ framework }) {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [framework?.exemplo, framework?.exemplos, isEn, isEs, loc]);
|
||||
}, [framework?.exemplo, framework?.exemplos, isEn, isEs, isFr, isDe, loc]);
|
||||
|
||||
const lines = (sample || "").replace(/\n$/, "").split("\n");
|
||||
|
||||
@@ -110,27 +145,53 @@ export default function CodeExample({ framework }) {
|
||||
<div className="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="font-display text-xl font-semibold tracking-[-0.03em] text-ink">
|
||||
{isEn ? "Code example" : isEs ? "Ejemplo de código" : "Exemplo de código"}
|
||||
{isEn
|
||||
? "Code example"
|
||||
: isEs
|
||||
? "Ejemplo de código"
|
||||
: isFr
|
||||
? "Exemple de code"
|
||||
: isDe
|
||||
? "Codebeispiel"
|
||||
: "Exemplo de código"}
|
||||
</h2>
|
||||
<p className="mt-1 text-sm text-muted">
|
||||
{isEn
|
||||
? `Starter snippet in ${framework?.linguagem}. Adjust pins and configuration for your board.`
|
||||
: isEs
|
||||
? `Fragmento de inicio en ${framework?.linguagem}. Ajuste los pines y la configuración para su placa.`
|
||||
: `Trecho de partida em ${framework?.linguagem}. Ajuste pinos e configuração à sua placa.`}
|
||||
? `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
|
||||
? `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>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<button type="button" onClick={handleCopy} className="btn btn-ghost btn-sm">
|
||||
{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" : "Copiado") : (isEn ? "Copy" : isEs ? "Copiar" : "Copiar")}</span>
|
||||
<span role="status">
|
||||
{copied
|
||||
? (isEn ? "Copied" : isEs ? "Copiado" : isFr ? "Copié" : isDe ? "Kopiert" : "Copiado")
|
||||
: (isEn ? "Copy" : isEs ? "Copiar" : isFr ? "Copier" : 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" : "Mais exemplos"}
|
||||
{isEn ? "More examples" : isEs ? "Más ejemplos" : isFr ? "Plus d'exemples" : isDe ? "Weitere Beispiele" : "Mais exemplos"}
|
||||
<ExternalLink 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>
|
||||
@@ -139,9 +200,34 @@ export default function CodeExample({ framework }) {
|
||||
<div className="code-panel mt-5" aria-busy={loading}>
|
||||
<div className="flex items-center justify-between border-b border-white/10 px-4 py-2.5 text-xs text-[#a6afbd]">
|
||||
<span className="font-mono">{framework?.nome}</span>
|
||||
<span>{loading ? (isEn ? "Loading example…" : isEs ? "Cargando ejemplo…" : "Carregando exemplo…") : framework?.linguagem}</span>
|
||||
<span>
|
||||
{loading
|
||||
? (isEn
|
||||
? "Loading example…"
|
||||
: isEs
|
||||
? "Cargando ejemplo…"
|
||||
: isFr
|
||||
? "Chargement de l'exemple…"
|
||||
: isDe
|
||||
? "Beispiel wird geladen…"
|
||||
: "Carregando exemplo…")
|
||||
: framework?.linguagem}
|
||||
</span>
|
||||
</div>
|
||||
<pre tabIndex={0} aria-label={isEn ? `Code example in ${framework?.linguagem}` : isEs ? `Código de ejemplo en ${framework?.linguagem}` : `Código de exemplo em ${framework?.linguagem}`}>
|
||||
<pre
|
||||
tabIndex={0}
|
||||
aria-label={
|
||||
isEn
|
||||
? `Code example in ${framework?.linguagem}`
|
||||
: isEs
|
||||
? `Código de ejemplo en ${framework?.linguagem}`
|
||||
: isFr
|
||||
? `Exemple de code en ${framework?.linguagem}`
|
||||
: isDe
|
||||
? `Codebeispiel in ${framework?.linguagem}`
|
||||
: `Código de exemplo em ${framework?.linguagem}`
|
||||
}
|
||||
>
|
||||
<code>
|
||||
{lines.map((line, idx) => (
|
||||
<span key={idx} className="flex">
|
||||
@@ -158,8 +244,12 @@ export default function CodeExample({ framework }) {
|
||||
{isEn
|
||||
? `Could not load local example (${error}). Use “More examples” to view official code.`
|
||||
: isEs
|
||||
? `No se pudo cargar el ejemplo local (${error}). Utilice “Más ejemplos” para ver el código oficial.`
|
||||
: `Não foi possível carregar o exemplo local (${error}). Use “Mais exemplos” para ver o código oficial.`}
|
||||
? `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
|
||||
? `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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user