Skip to content

Commit 4a88f8c

Browse files
authored
Merge pull request #13 from JodazStudio/develop
Develop
2 parents 9c03b22 + 5314100 commit 4a88f8c

8 files changed

Lines changed: 93 additions & 8 deletions

File tree

.astro/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"_variables": {
3-
"lastUpdateCheck": 1772339173160
3+
"lastUpdateCheck": 1775922065555
44
}
55
}

.env.example

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
# .env
2-
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
1+
PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

src/components/BaseHead.astro

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,23 @@ const esURL = lang === 'es' ? canonicalURL.href : (alternateUrl ?? new URL('/es/
5555
<!-- Fonts -->
5656
<link rel="preconnect" href="https://fonts.googleapis.com" />
5757
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
58-
<link
59-
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
60-
rel="stylesheet"
61-
/>
58+
59+
{
60+
import.meta.env.PUBLIC_GA_MEASUREMENT_ID && (
61+
<>
62+
<script
63+
is:inline
64+
async
65+
src={`https://www.googletagmanager.com/gtag/js?id=${import.meta.env.PUBLIC_GA_MEASUREMENT_ID}`}
66+
/>
67+
<script is:inline define:vars={{ id: import.meta.env.PUBLIC_GA_MEASUREMENT_ID }}>
68+
window.dataLayer = window.dataLayer || [];
69+
window.gtag = function () {
70+
window.dataLayer.push(arguments);
71+
};
72+
gtag('js', new Date());
73+
gtag('config', id);
74+
</script>
75+
</>
76+
)
77+
}

src/components/HeaderInteractive.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ const HeaderInteractive = ({ navItems, logoSrc, lang, altLangHref }: Props) => {
4646

4747
const altLang = lang === 'es' ? 'EN' : 'ES';
4848

49+
const handleLangSwitch = () => {
50+
const targetLang = lang === 'es' ? 'en' : 'es';
51+
localStorage.setItem('preferred_lang', targetLang);
52+
sessionStorage.removeItem('lang_redirected');
53+
};
54+
4955
return (
5056
<header
5157
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
@@ -95,6 +101,7 @@ const HeaderInteractive = ({ navItems, logoSrc, lang, altLangHref }: Props) => {
95101
{/* Language switcher */}
96102
<a
97103
href={altLangHref}
104+
onClick={handleLangSwitch}
98105
className={`text-sm px-3 py-1 rounded-full border transition-all duration-300 font-bold ${
99106
isScrolled || isMenuOpen
100107
? 'text-foreground border-foreground/20 hover:border-primary hover:text-primary'
@@ -146,6 +153,7 @@ const HeaderInteractive = ({ navItems, logoSrc, lang, altLangHref }: Props) => {
146153
<div className="pt-6 border-t border-foreground/10">
147154
<a
148155
href={altLangHref}
156+
onClick={handleLangSwitch}
149157
className="inline-flex items-center text-lg font-bold text-foreground hover:text-primary transition-colors"
150158
aria-label={`Switch to ${altLang}`}
151159
>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
// src/components/LanguageDetector.astro
3+
// Client-side browser language detection with SEO-safe redirection.
4+
// Only performs automatic detection on the home page to avoid breaking
5+
// deep-linked / indexed pages. Manual preference (via header toggle)
6+
// is respected on ALL pages.
7+
import type { Lang } from '@/lib/i18n';
8+
9+
interface Props {
10+
lang: Lang;
11+
alternateUrl?: string;
12+
}
13+
14+
const { lang, alternateUrl } = Astro.props;
15+
---
16+
17+
<script
18+
define:vars={{ pageLang: lang, alternateUrl: alternateUrl ?? null }}
19+
>
20+
(function detectLanguage() {
21+
const PREFERRED_LANG_KEY = 'preferred_lang';
22+
const REDIRECTED_KEY = 'lang_redirected';
23+
24+
// Never redirect if we already redirected this session (prevents loops)
25+
if (sessionStorage.getItem(REDIRECTED_KEY)) return;
26+
27+
const storedPref = localStorage.getItem(PREFERRED_LANG_KEY);
28+
const isHomePage =
29+
window.location.pathname === '/' ||
30+
window.location.pathname === '/es/' ||
31+
window.location.pathname === '/es';
32+
33+
/** @type {'en' | 'es' | null} */
34+
let targetLang = null;
35+
36+
if (storedPref === 'en' || storedPref === 'es') {
37+
// User has explicitly chosen a language before — respect it everywhere
38+
targetLang = storedPref;
39+
} else if (isHomePage) {
40+
// First visit, no preference saved — detect from browser, but ONLY on
41+
// the home page to preserve SEO for deep-linked pages.
42+
const browserLang = navigator.language || navigator.userLanguage || 'en';
43+
targetLang = browserLang.startsWith('es') ? 'es' : 'en';
44+
45+
// Persist the detected preference so subsequent pages are consistent
46+
localStorage.setItem(PREFERRED_LANG_KEY, targetLang);
47+
}
48+
49+
// If we have a target and it differs from the current page, redirect
50+
if (targetLang && targetLang !== pageLang && alternateUrl) {
51+
sessionStorage.setItem(REDIRECTED_KEY, '1');
52+
window.location.replace(alternateUrl);
53+
}
54+
})();
55+
</script>

src/layouts/BaseLayout.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import BaseHead from '@/components/BaseHead.astro';
44
import Header from '@/components/Header.astro';
55
import Footer from '@/components/Footer.astro';
6+
import LanguageDetector from '@/components/LanguageDetector.astro';
67
import type { Lang } from '@/lib/i18n';
78
import '@/index.css';
89
@@ -20,6 +21,7 @@ const { title, description, lang = 'en', alternateUrl } = Astro.props;
2021
<html lang={lang}>
2122
<head>
2223
<BaseHead title={title} description={description} lang={lang} alternateUrl={alternateUrl} />
24+
<LanguageDetector lang={lang} alternateUrl={alternateUrl} />
2325
</head>
2426
<body class="min-h-screen bg-background flex flex-col">
2527
<Header lang={lang} />

src/lib/analytics.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// src/lib/analytics.ts
22
// Utility for Google Analytics event tracking
33

4-
const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID;
4+
const GA_MEASUREMENT_ID = import.meta.env.PUBLIC_GA_MEASUREMENT_ID;
55

66
export function trackEvent({
77
action,
@@ -20,6 +20,8 @@ export function trackEvent({
2020
event_label: label,
2121
value: value,
2222
});
23+
} else if (import.meta.env.DEV) {
24+
console.log('[Analytics]', { action, category, label, value });
2325
}
2426
}
2527

src/lib/i18n.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const translations = { en, es } as const;
55

66
export type Lang = 'en' | 'es';
77

8+
/** localStorage key for the user's explicitly chosen language */
9+
export const PREFERRED_LANG_KEY = 'preferred_lang' as const;
10+
811
export function getLang(url: URL): Lang {
912
const [, lang] = url.pathname.split('/');
1013
if (lang === 'es') return 'es';

0 commit comments

Comments
 (0)