Skip to content

Commit 23d85fb

Browse files
committed
refactor: ruteo por idioma sin redirección para SEO y aviso de idioma
- Servir el español (idioma por defecto) en la raíz / como contenido real e indexable y los idiomas adicionales bajo /<lang> (por ahora /en). Se elimina la página de enrutamiento con redirección automática, que impedía a Google rastrear el contenido real del sitio. - Añadir components/global/LangNotice.astro (+ lang-notice.css): aviso discreto que invita a cambiar al idioma del navegador —en ese idioma— solo si existe esa versión; cambia únicamente al pulsar (sin redirección automática) y recuerda el descarte. - Layout: el idioma llega por prop; URLs canónicas por idioma, alternativas hreflang (es-CO, en, x-default) y og:locale dinámico. theme-color a #0a1c2b (el fondo real del sitio). - Centralizar las rutas localizadas en el helper localizedPath (i18n) y usarlo en Navbar y Footer; añadir la sección langNotice a los locales. - Eliminar Router.astro, router.css, la ruta pages/es/ y la sección router del i18n, ya innecesarias. - Actualizar CHANGELOG.md y README.md; elevar la versión a 0.11.0.
1 parent 4b2e069 commit 23d85fb

17 files changed

Lines changed: 374 additions & 607 deletions

File tree

CHANGELOG.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,22 @@ Todos los cambios notables en este proyecto serán documentados en este archivo
216216

217217
### Corregido
218218

219-
- **Responsive del hero**: se saca el hero de `u-grid` y se acota con su propio contenedor, de modo que la ventana de código ya no desborda en móvil (su scroll horizontal queda contenido dentro de la ventana). El titular en gradiente deja de recortar los descendentes (la «g» de «digital»).
219+
- **Responsive del hero**: se saca el hero de `u-grid` y se acota con su propio contenedor, de modo que la ventana de código ya no desborda en móvil (su scroll horizontal queda contenido dentro de la ventana). El titular en gradiente deja de recortar los descendentes (la «g» de «digital»).
220+
221+
## [0.11.0] - 2026-07-11
222+
223+
### Cambiado
224+
225+
- **Ruteo por idioma sin redirección (SEO)**: el español (idioma por defecto) pasa a vivir en la raíz `/` como contenido real e indexable, y los idiomas adicionales bajo `/<lang>` (por ahora `/en`). Se elimina la página de enrutamiento con redirección automática, que impedía a Google rastrear el contenido real del sitio.
226+
- **`src/layouts/Layout.astro`**: el idioma llega por prop (no por `Astro.params`), lo que da URLs canónicas correctas por idioma; se añaden alternativas `hreflang` (`es-CO`, `en`, `x-default`) y `og:locale` dinámico para SEO multiidioma. `theme-color` actualizado a `#0a1c2b` (el fondo real del sitio).
227+
- **`Navbar` y `Footer`**: los enlaces y el intercambio de idioma se reconstruyen con el nuevo helper de rutas.
228+
- **`src/data/i18n.ts`**: nuevo helper `localizedPath(lang, subpath)` como fuente única de rutas localizadas.
229+
230+
### Añadido
231+
232+
- **`src/components/global/LangNotice.astro`** y **`src/styles/lang-notice.css`**: aviso discreto que, solo si el idioma del navegador difiere del de la página y existe esa versión, invita a cambiar de idioma —en el idioma de destino—. No redirige de forma automática: solo cambia si el usuario pulsa, y recuerda el descarte.
233+
- **`src/data/locales/{es,en}.ts`**: sección `langNotice`.
234+
235+
### Eliminado
236+
237+
- **`src/components/pages/Router.astro`** y **`src/styles/router.css`**: la página de enrutamiento por redirección, ya innecesaria. También la ruta `src/pages/es/` (el español ahora es la raíz) y la sección `router` del esquema i18n.

README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ ctgcode.com/
4848
│ ├── components/ # Componentes reutilizables del sitio.
4949
│ │ ├── global/ # Componentes globales y de alcance transversal.
5050
│ │ │ ├── Footer.astro # Cierre del sitio «el fondo del mar»: invitación, mapa, colofón y marca hundida.
51+
│ │ │ ├── LangNotice.astro # Aviso de idioma disponible (cambio manual, sin redirección automática).
5152
│ │ │ └── Navbar.astro # Cabecera responsiva: marca, navegación, switch de idioma, CTA y menú móvil.
5253
│ │ ├── pages/ # Componentes de composición de páginas completas.
5354
│ │ │ ├── Home.astro # Composición de la home (única fuente de la verdad), parametrizada por idioma.
54-
│ │ │ ├── NotFound.astro # Página 404 «el náufrago».
55-
│ │ │ └── Router.astro # Página de entrada «la carta de navegación» (detección de idioma).
55+
│ │ │ └── NotFound.astro # Página 404 «el náufrago».
5656
│ │ └── ui/ # Componentes de interfaz reutilizables.
5757
│ │ └── CTA.astro # Botón de acción de marca («el faro del atardecer»).
5858
│ ├── content.config.ts # Definición de colecciones de contenido y su validación con Astro Content Collections.
@@ -66,11 +66,9 @@ ctgcode.com/
6666
│ │ └── Layout.astro # Layout principal con SEO, metadata OG y carga de estilos globales.
6767
│ ├── pages/ # Páginas y rutas del sitio.
6868
│ │ ├── 404.astro # Página 404 (delega en el componente NotFound).
69-
│ │ ├── index.astro # Entrada que detecta el idioma y redirige (delega en Router).
70-
│ │ ├── en/ # Carpeta para las páginas en inglés.
71-
│ │ │ └── index.astro # Ruta en inglés (delega en el componente Home).
72-
│ │ └── es/ # Carpeta para las páginas en español.
73-
│ │ └── index.astro # Ruta en español (delega en el componente Home).
69+
│ │ ├── index.astro # Home en español (idioma por defecto en la raíz; delega en Home).
70+
│ │ └── en/ # Idiomas adicionales bajo /<lang>.
71+
│ │ └── index.astro # Home en inglés (delega en el componente Home).
7472
│ └── styles/ # Sistema de diseño y estilos de componentes.
7573
│ ├── global.css # Punto de entrada (barrel) que orquesta el sistema de diseño.
7674
│ ├── tokens.css # Tokens del sistema: paleta, color, spacing, tipografía, movimiento…
@@ -83,9 +81,9 @@ ctgcode.com/
8381
│ ├── cta.css # Estilos del CTA de marca (components/ui/CTA.astro).
8482
│ ├── footer.css # Estilos del footer (components/global/Footer.astro).
8583
│ ├── home.css # Estilos de la home (components/pages/Home.astro).
84+
│ ├── lang-notice.css # Estilos del aviso de idioma (components/global/LangNotice.astro).
8685
│ ├── navbar.css # Estilos de la navbar (components/global/Navbar.astro).
87-
│ ├── not-found.css # Estilos de la 404 (components/pages/NotFound.astro).
88-
│ └── router.css # Estilos de la entrada (components/pages/Router.astro).
86+
│ └── not-found.css # Estilos de la 404 (components/pages/NotFound.astro).
8987
├── .gitignore # Reglas de Git para ignorar artefactos generados y archivos locales no deseados.
9088
├── astro.config.mjs # Configuración principal de Astro y sus integraciones.
9189
├── bun.lock # Lockfile de Bun para reproducibilidad de dependencias.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ctg-code",
3-
"version": "0.10.0",
3+
"version": "0.11.0",
44
"dependencies": {
55
"@astrojs/partytown": "^2.1.7",
66
"@fontsource-variable/inter": "^5.2.8",

src/components/global/Footer.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
* Estilos en styles/footer.css.
1111
*/
12-
import { locales, languages, type LocaleSchema } from "../../data/i18n";
12+
import { locales, languages, localizedPath, type LocaleSchema } from "../../data/i18n";
1313
import CTA from "../ui/CTA.astro";
1414
import "../../styles/footer.css";
1515
@@ -21,7 +21,7 @@ const { lang } = Astro.props;
2121
const t: LocaleSchema = locales[lang];
2222
2323
const otherLang = lang === "es" ? "en" : "es";
24-
const home = `/${lang}/`;
24+
const home = localizedPath(lang);
2525
const year = new Date().getFullYear();
2626
2727
// La sonda: continúa la numeración de la navbar (01/02/03) con el idioma (04).
@@ -84,7 +84,7 @@ const navItems = [
8484
<li>
8585
<a
8686
class="ft-link"
87-
href={`/${otherLang}/`}
87+
href={localizedPath(otherLang)}
8888
hreflang={otherLang}
8989
lang={otherLang}
9090
>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
/**
3+
* LangNotice — aviso de idioma disponible.
4+
*
5+
* Si el idioma del navegador difiere del de la página Y existe esa versión,
6+
* muestra un aviso discreto (en el idioma de destino) invitando a cambiar.
7+
* NO redirige solo: únicamente al pulsar el enlace. Se recuerda el descarte.
8+
*
9+
* Renderiza una tarjeta oculta por cada otro idioma disponible; el script
10+
* revela la que coincide con `navigator.language`. Estilos en lang-notice.css.
11+
*/
12+
import { locales, localizedPath, type LocaleSchema } from "../../data/i18n";
13+
import "../../styles/lang-notice.css";
14+
15+
interface Props {
16+
lang: keyof typeof locales;
17+
}
18+
19+
const { lang } = Astro.props;
20+
21+
const others = (Object.keys(locales) as (keyof typeof locales)[]).filter(
22+
(l) => l !== lang,
23+
);
24+
---
25+
26+
<div class="lang-notice" data-lang-notice>
27+
{
28+
others.map((l) => {
29+
const n: LocaleSchema["langNotice"] = locales[l].langNotice;
30+
return (
31+
<aside
32+
class="ln-card"
33+
data-ln-for={l}
34+
role="status"
35+
aria-label={n.message}
36+
lang={l}
37+
hidden
38+
>
39+
<span class="ln-icon" aria-hidden="true">
40+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
41+
<circle cx="12" cy="12" r="9" />
42+
<path d="M3 12h18" />
43+
<path d="M12 3c2.5 2.4 3.8 5.6 3.8 9s-1.3 6.6-3.8 9c-2.5-2.4-3.8-5.6-3.8-9s1.3-6.6 3.8-9z" />
44+
</svg>
45+
</span>
46+
47+
<p class="ln-msg">{n.message}</p>
48+
49+
<a
50+
class="ln-action"
51+
href={localizedPath(l)}
52+
hreflang={l}
53+
lang={l}
54+
data-ln-go
55+
>
56+
{n.action}
57+
</a>
58+
59+
<button
60+
class="ln-close"
61+
type="button"
62+
data-ln-dismiss
63+
aria-label={n.dismiss}
64+
>
65+
<span aria-hidden="true">&times;</span>
66+
</button>
67+
</aside>
68+
);
69+
})
70+
}
71+
</div>
72+
73+
<script>
74+
const KEY = "ctg-lang-notice-dismissed";
75+
const root = document.querySelector<HTMLElement>("[data-lang-notice]");
76+
77+
if (root && localStorage.getItem(KEY) !== "1") {
78+
const browserLang = (navigator.language || "")
79+
.split("-")[0]
80+
.toLowerCase();
81+
const card = root.querySelector<HTMLElement>(
82+
`[data-ln-for="${browserLang}"]`,
83+
);
84+
85+
if (card) {
86+
const dismiss = () => localStorage.setItem(KEY, "1");
87+
88+
const close = () => {
89+
dismiss();
90+
card.removeAttribute("data-open");
91+
card.addEventListener(
92+
"transitionend",
93+
() => {
94+
card.hidden = true;
95+
},
96+
{ once: true },
97+
);
98+
};
99+
100+
// Revelar (doble rAF para que la transición de entrada corra).
101+
card.hidden = false;
102+
requestAnimationFrame(() =>
103+
requestAnimationFrame(() => card.setAttribute("data-open", "")),
104+
);
105+
106+
card
107+
.querySelector<HTMLButtonElement>("[data-ln-dismiss]")
108+
?.addEventListener("click", close);
109+
110+
// Al cambiar de idioma no hace falta volver a avisar.
111+
card
112+
.querySelector<HTMLAnchorElement>("[data-ln-go]")
113+
?.addEventListener("click", dismiss);
114+
115+
document.addEventListener("keydown", (e) => {
116+
if (e.key === "Escape" && !card.hidden) close();
117+
});
118+
}
119+
}
120+
</script>

src/components/global/Navbar.astro

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Responsive: en <60rem la nav se colapsa en un overlay a pantalla completa
1111
* (con bloqueo de scroll del documento). En >=60rem se muestra la nav en línea.
1212
*/
13-
import { locales, type LocaleSchema } from "../../data/i18n";
13+
import { locales, localizedPath, type LocaleSchema } from "../../data/i18n";
1414
import CTA from "../ui/CTA.astro";
1515
import logo from "../../assets/logo.svg?raw";
1616
import "../../styles/navbar.css";
@@ -23,11 +23,14 @@ const { lang } = Astro.props;
2323
const t: LocaleSchema = locales[lang];
2424
2525
const otherLang = lang === "es" ? "en" : "es";
26-
const altPath = Astro.url.pathname.replace(
27-
/^\/(es|en)(?=\/|$)/,
28-
`/${otherLang}`,
29-
);
30-
const home = `/${lang}/`;
26+
const home = localizedPath(lang);
27+
28+
// Switch de idioma: misma página, otra lengua. Se quita el prefijo del idioma
29+
// actual (es vive en la raíz) y se reconstruye con el otro.
30+
const currentSubpath = Astro.url.pathname
31+
.replace(/^\/(en)(?=\/|$)/, "")
32+
.replace(/^\/+/, "");
33+
const altPath = localizedPath(otherLang, currentSubpath);
3134
3235
// Enlaces de sección numerados como metadatos de estudio (voz mono).
3336
const navItems = [

src/components/pages/Home.astro

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22
/**
33
* Home — composición de la página de inicio (única fuente de la verdad).
44
*
5-
* Los archivos de ruta (pages/es/index.astro, pages/en/index.astro) solo
6-
* eligen el idioma; toda la estructura, textos (vía locales) y estilos viven
7-
* aquí una sola vez.
5+
* Los archivos de ruta (pages/index.astro = es, pages/en/index.astro = en)
6+
* solo eligen el idioma; toda la estructura, textos (vía locales) y estilos
7+
* viven aquí una sola vez.
88
*
99
* HERO — "compilar el atardecer": un snippet se escribe en vivo en la ventana
1010
* de código y, al terminar, el sol tras ella se enciende (data-compiled).
1111
*/
1212
import Layout from "../../layouts/Layout.astro";
1313
import Navbar from "../global/Navbar.astro";
1414
import Footer from "../global/Footer.astro";
15+
import LangNotice from "../global/LangNotice.astro";
1516
import CTA from "../ui/CTA.astro";
1617
import { locales } from "../../data/i18n";
1718
import "../../styles/home.css";
@@ -52,7 +53,7 @@ function tokenize(line: string): Token[] {
5253
const codeLines = t.hero.code.map((line) => tokenize(line));
5354
---
5455

55-
<Layout title={title} description={t.hero.description} pageName="home">
56+
<Layout title={title} description={t.hero.description} pageName="home" lang={lang}>
5657
<Navbar lang={lang} />
5758

5859
<main>
@@ -185,6 +186,8 @@ const codeLines = t.hero.code.map((line) => tokenize(line));
185186
</main>
186187

187188
<Footer lang={lang} />
189+
190+
<LangNotice lang={lang} />
188191
</Layout>
189192

190193
<script>

src/components/pages/NotFound.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const t = locales.es.notFound;
1919
const en = locales.en.notFound;
2020
---
2121

22-
<Layout title="404 — CTG Code" description={t.lead} pageName="404">
22+
<Layout title="404 — CTG Code" description={t.lead} pageName="404" lang="es">
2323
<main class="nf-scene u-grid" data-tone="night">
2424
<a class="brand-mono nf-brand" href="/">
2525
<span class="brace">&#123;</span> ctgcode <span class="brace">&#125;</span

0 commit comments

Comments
 (0)