-
Notifications
You must be signed in to change notification settings - Fork 0
YU_10 #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
YU_10 #11
Conversation
…nglish locales - Updated Astro configuration to enable server output for i18n functionality. - Added i18n configuration with locales and default locale settings. - Refactored ContactForm component to utilize translations based on the current language. - Enhanced Head component to dynamically set titles and descriptions based on the selected language. - Created LanguagePicker component for language selection and routing. - Updated Navigation component to reflect translated menu items. - Removed old pages and replaced them with new localized versions for both English and Spanish. - Added utility functions for language detection and translation handling.
WalkthroughEnabled site-wide i18n (es/en) with locale-prefixed routing, switched Astro output to server, localized layout/meta, header, navigation, and contact form. Added LanguagePicker and Navigation components. Created localized ES/EN pages, redirected root to /es, and removed the old Spanish contact page. Introduced shared i18n utilities for Astro and React. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Browser
participant Router as Astro Router
participant i18n as i18n/utils
participant UI as Layout/Components
User->>Browser: Request /en/... or /es/...
Browser->>Router: GET /{lang}/{route}
Router->>i18n: getLangFromUrl(URL)
i18n-->>Router: lang
Router->>UI: Render with lang
UI->>i18n: useTranslations(lang)
i18n-->>UI: t(key)
UI-->>Browser: Localized HTML (titles, nav, content)
Note over UI,i18n: Links built with getRelativeLocaleUrl / useTranslatedPath
sequenceDiagram
autonumber
actor User
participant Browser
participant Router as Astro Router
User->>Browser: Request /
Browser->>Router: GET /
Router-->>Browser: 302 Redirect to /es/
Browser->>Router: GET /es/
Router-->>Browser: 200 Localized page
sequenceDiagram
autonumber
actor User
participant Picker as LanguagePicker
participant i18n as i18n/utils
participant Router as Browser Location
User->>Picker: Change <select> to target lang
Picker->>i18n: translatePath(currentRoute, targetLang)
i18n-->>Picker: /{targetLang}/{translatedRoute}
Picker->>Router: window.location = translated URL
Router-->>User: Navigated to localized page
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
yellowumbrella-web | fc786e4 | Commit Preview URL Branch Preview URL |
Sep 24 2025, 04:12 AM |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements internationalization (i18n) support for the Yellow Umbrella website, enabling Spanish and English locales with proper routing and content translation.
- Restructured the site to use language-prefixed routes (e.g.,
/es/,/en/) - Created comprehensive translation system with utilities for both Astro and React components
- Added language switching functionality with automatic route translation
Reviewed Changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| astro.config.mjs | Configured i18n settings and changed output to server mode |
| src/pages/index.astro | Converted to redirect page for default language routing |
| src/pages/es/* | Created Spanish localized pages with translation integration |
| src/pages/en/* | Created English localized pages with translation integration |
| src/i18n/ui.ts | Defined translation keys and route mappings for both languages |
| src/i18n/utils.ts | Implemented language detection and translation utilities |
| src/i18n/react-utils.ts | Created React-specific translation utilities |
| src/components/* | Updated components to use translations and language-aware routing |
| src/layouts/Layout.astro | Integrated language picker and dynamic meta tags |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| --- | ||
| import Layout from "../layouts/Layout.astro"; | ||
| import Head from "../components/Head.astro"; | ||
| //Esta pagina es necesaria para que i18n funcione. |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment contains a grammatical error. It should be 'Esta página es necesaria para que i18n funcione.' (missing accent on 'página').
| //Esta pagina es necesaria para que i18n funcione. | |
| //Esta página es necesaria para que i18n funcione. |
| import Layout from "../layouts/Layout.astro"; | ||
| import Head from "../components/Head.astro"; | ||
| //Esta pagina es necesaria para que i18n funcione. | ||
| // Redirección automática al idioma predeterminado (español) |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment contains a spelling error. It should be 'Redirección automática al idioma predeterminado (español)' (missing accent on 'Redirección').
| const currentLang = lang || getLangFromUrlReact(window.location.pathname); | ||
| const t = useTranslationsReact(currentLang as 'es' | 'en'); | ||
|
|
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing window.location.pathname during server-side rendering will cause an error since window is undefined on the server. This should be handled with proper client-side detection or the lang prop should be required.
| const currentLang = lang || getLangFromUrlReact(window.location.pathname); | |
| const t = useTranslationsReact(currentLang as 'es' | 'en'); | |
| const [currentLang, setCurrentLang] = React.useState<string>(lang || 'es'); | |
| React.useEffect(() => { | |
| if (!lang && typeof window !== 'undefined') { | |
| setCurrentLang(getLangFromUrlReact(window.location.pathname)); | |
| } | |
| }, [lang]); | |
| const t = useTranslationsReact(currentLang as 'es' | 'en'); |
| export default defineConfig({ | ||
| integrations: [react(), tailwind()], | ||
| output: "hybrid", | ||
| output: "server", //El output server es necesario para que i18n funcione, para mas info mirar la documentacion del mismo |
Copilot
AI
Sep 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment contains spelling errors. It should be 'más' (with accent) and 'documentación' (with accent).
| output: "server", //El output server es necesario para que i18n funcione, para mas info mirar la documentacion del mismo | |
| output: "server", //El output server es necesario para que i18n funcione, para más info mirar la documentación del mismo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (17)
src/i18n/ui.ts (3)
24-35: Spanish strings contain untranslated English; fix the ES copy.Translate the ES values for better UX.
Apply:
- 'site.description': 'The Yellow Umbrella Website ☂️', + 'site.description': 'Sitio web de Yellow Umbrella ☂️', @@ - 'home.subtitle': 'It\'s raining outside, take this', + 'home.subtitle': 'Está lloviendo fuera, toma esto',
6-7: Remove unusedshowDefaultLangor wire it in.It’s defined but unused and could confuse future readers, especially since config uses
prefixDefaultLocale: true.-export const showDefaultLang = false; +// If needed, consume this flag where routes are generated; otherwise, remove: +// export const showDefaultLang = false;
9-20: Add const assertion toroutesfor stronger typing.Helps catch key typos across utilities and consumers.
-export const routes = { +export const routes = { en: { 'sobre-nosotros': 'about-us', 'contacto': 'contact', 'redes-sociales': 'social-networks', }, es: { 'about-us': 'sobre-nosotros', 'contact': 'contacto', 'social-networks': 'redes-sociales', }, -}; +} as const;src/i18n/react-utils.ts (1)
3-7: Avoid false fallback when a translation is an empty string. Use nullish coalescing.
||treats empty strings as falsy. Prefer??.export function useTranslationsReact(lang: keyof typeof ui) { return function t(key: keyof typeof ui[typeof defaultLang]) { - return ui[lang][key] || ui[defaultLang][key]; + return ui[lang][key] ?? ui[defaultLang][key]; } }src/pages/en/index.astro (1)
12-14: Remove empty wrapper div.Slight cleanup; no behavior change.
- <div class=""> - <Head/> - </div> + <Head />src/pages/index.astro (1)
2-4: Derive redirect target from default locale to avoid drift.Keeps the redirect aligned with config if default locale changes.
-// Redirección automática al idioma predeterminado (español) -return Astro.redirect('/es/'); +// Redirección automática al idioma predeterminado +import { defaultLang } from '../i18n/ui'; +return Astro.redirect(`/${defaultLang}/`);src/pages/es/sobre-nosotros.astro (1)
17-29: Fix minor Spanish grammar and spelling.“Para ti” (no tilde) and “paraguas” (sin diéresis).
- <span class="text-justify text-white font-monaspace"> + <span class="text-justify text-white font-mono"> Yellow Umbrella significa muchas cosas. Para tí, si eres el dueño de un negocio emergente, significa resguardo y cobertura de todas tus necesidades de cloud, ciberseguridad, y desarrollo.<br><br> @@ Nuestros valores son claros: Estamos comprometidos con el código abierto y la accesibilidad a nuestros servicios y proyectos. Nuestro futuro juntos no implica dependencia. - Buscamos cubrirte y entrenar a tu equipo para que llegado el momento, puedas cerrar el paragüas + Buscamos cubrirte y entrenar a tu equipo para que llegado el momento, puedas cerrar el paraguas y continuar tu camino sin nosotros.Confirm
font-monoexists in your Tailwind config, or keep the original if you have a customfont-monaspaceutility.src/pages/en/contact.astro (1)
21-21: Defer React hydration for better UX.Use
client:idle(orclient:visible) instead ofclient:loadto reduce main-thread contention on page load.- <ContactForm client:load lang={lang} /> + <ContactForm client:idle lang={lang} />If the form must be usable immediately above the fold, keep
client:load; otherwise preferidleorvisible.src/pages/es/contacto.astro (1)
21-21: Defer React hydration for better UX.Mirror the EN page suggestion.
- <ContactForm client:load lang={lang} /> + <ContactForm client:idle lang={lang} />src/components/Navigation.astro (2)
28-50: Fix invalid Tailwind utilities and remove redundant inline CSS
- Classes top-20px, left-20px, z-1000 aren’t valid Tailwind. Use arbitrary values.
- Inline <style> duplicates/conflicts the position/z-index.
Apply this diff:
-<nav class="fixed top-20px left-20px z-1000 bg-white/5 backdrop-blur-md rounded-lg p-4"> +<nav class="fixed top-[20px] left-[20px] z-[1000] bg-white/5 backdrop-blur-md rounded-lg p-4"> @@ -<style> - nav { - position: fixed; - top: 20px; - left: 20px; - z-index: 999; - } -</style>
8-25: Avoid hardcoding per-lang paths in menuItemsYou already have route translation utilities; consider generating paths via a single source of truth to avoid drift.
src/pages/es/redes-sociales.astro (1)
33-41: Guard against unknown icons in dataIf data.socials contains an unexpected icon key, SocialCard will receive undefined. Add a safe fallback:
- icon={iconMap[output.icon]} + icon={iconMap[output.icon] ?? twitter}src/pages/en/social-networks.astro (2)
33-41: Add fallback for missing/unknown iconsMirror the ES page safeguard:
- icon={iconMap[output.icon]} + icon={iconMap[output.icon] ?? twitter}
14-20: Nit: keep comments in English within en/ pagesChange “Crear mapeo de iconos” to an English comment for consistency.
src/components/LanguagePicker.astro (1)
13-31: Accessibility and cleanup nits
- Add an accessible label to the select (e.g., aria-label) using your translations.
- getRelativeLocaleUrl import is unused; remove.
Example:
- <select + <select + aria-label="Change language"src/components/Head.astro (1)
16-17: Head renders home content on all pages that include itIf Head is intended as a generic header, consider accepting children/props to customize or hide the hero (subtitle/title) so subpages don’t duplicate headings.
src/i18n/utils.ts (1)
15-39: Route translation utility looks solidHandles root, leading slashes, and route mapping both ways. Consider documenting that only single-segment routes are supported today.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
astro.config.mjs(1 hunks)src/components/ContactForm.tsx(9 hunks)src/components/Head.astro(1 hunks)src/components/LanguagePicker.astro(1 hunks)src/components/Navigation.astro(1 hunks)src/i18n/react-utils.ts(1 hunks)src/i18n/ui.ts(1 hunks)src/i18n/utils.ts(1 hunks)src/layouts/Layout.astro(2 hunks)src/pages/contacto.astro(0 hunks)src/pages/en/about-us.astro(1 hunks)src/pages/en/contact.astro(1 hunks)src/pages/en/index.astro(1 hunks)src/pages/en/social-networks.astro(1 hunks)src/pages/es/contacto.astro(1 hunks)src/pages/es/index.astro(1 hunks)src/pages/es/redes-sociales.astro(1 hunks)src/pages/es/sobre-nosotros.astro(1 hunks)src/pages/index.astro(1 hunks)
💤 Files with no reviewable changes (1)
- src/pages/contacto.astro
🧰 Additional context used
🧬 Code graph analysis (3)
src/i18n/react-utils.ts (1)
src/i18n/ui.ts (2)
ui(22-107)defaultLang(6-6)
src/i18n/utils.ts (1)
src/i18n/ui.ts (3)
ui(22-107)defaultLang(6-6)routes(9-20)
src/components/ContactForm.tsx (1)
src/i18n/react-utils.ts (2)
getLangFromUrlReact(9-13)useTranslationsReact(3-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Workers Builds: yellowumbrella-web
🔇 Additional comments (22)
astro.config.mjs (2)
10-12: Cloudflare SSR alignment looks good.
output: "server"with@astrojs/cloudflarematches the SSR requirement for Workers. No issues spotted.If you previously relied on static generation, verify there are no implicit assumptions (e.g., file-based redirects) that break under SSR-only deployment.
16-22: Astro i18n config is valid — requires Astro >= 3.7.0i18n.locales and i18n.defaultLocale were added in Astro 3.5.0; i18n.routing.prefixDefaultLocale was added in 3.7.0. If the project uses Astro >= 3.7.0, this config is supported.
src/pages/es/index.astro (1)
10-16: LGTM for the ES home page scaffoldUses i18n helpers correctly and wires Layout/Head as intended.
src/pages/en/about-us.astro (1)
10-32: Confirm Head usage on subpagesIncluding renders the home hero (home.subtitle/home.title). On About this will create a second H1 and duplicate hero content. If unintentional, remove here or make Head accept a “variant” prop to hide the hero on non-home pages.
src/i18n/utils.ts (2)
3-7: LGTM: robust lang derivation with sane fallbackgetLangFromUrl handles missing/unknown prefixes and falls back to defaultLang.
41-62: Note on getRouteFromUrl limitationsIt returns only the last segment; multi-segment pages would not translate fully. Fine for current pages; revisit if you add nested routes.
src/pages/en/social-networks.astro (1)
5-21: No action required — socials.json icon keys match iconMapVerified src/api/socials.json contains icons github-circle, linkedin, twitter — all are covered by the iconMap in src/pages/en/social-networks.astro.
src/pages/es/redes-sociales.astro (1)
5-21: socials.json shape and icon keys — no action requiredsrc/api/socials.json has a top-level "socials" array (3 items) and the only icon values are: twitter, github-circle, linkedin.
src/layouts/Layout.astro (5)
10-11: LGTM!The imports are correctly structured and follow proper i18n implementation patterns.
13-14: LGTM!The language detection and translation initialization follows best practices for Astro i18n implementation.
18-18: LGTM!Setting the
langattribute dynamically based on the detected language is essential for proper SEO and accessibility.
32-32: LGTM!The translation-driven meta tags implementation is correct and ensures proper localization of SEO elements including description, Open Graph, and Twitter Card metadata.
Also applies to: 38-42, 47-48
54-54: LGTM!Adding the LanguagePicker component to provide users with language selection functionality is appropriate for the i18n implementation.
src/components/ContactForm.tsx (9)
7-7: LGTM!The import statement correctly brings in the React-specific i18n utilities for client-side translation handling.
17-24: LGTM!The interface definition and language detection logic is well-structured. The fallback to URL-based detection when no
langprop is provided ensures robust language handling.
53-53: LGTM!Error messages are properly localized using the translation system, ensuring consistent user experience across languages.
Also applies to: 76-76, 86-86
100-100: LGTM!Form labels, placeholders, and validation error messages are properly internationalized, ensuring a fully localized user experience.
Also applies to: 105-105, 109-109, 115-115, 120-120, 124-124, 130-130, 135-135, 142-142, 148-148, 155-155
160-165: LGTM!The phone input configuration correctly adapts the default country based on the selected language, providing a better user experience by setting appropriate defaults.
175-175: LGTM!Phone validation messages and form field labels are properly localized.
Also applies to: 182-182, 188-188
199-199: LGTM!The Turnstile language configuration correctly uses the detected language, ensuring the CAPTCHA appears in the appropriate language for better user accessibility.
223-223: LGTM!Button text and success messages are properly internationalized for consistent user experience.
Also applies to: 229-229
23-23: No SSR issue — ContactForm is client-only and receives lang propBoth src/pages/es/contacto.astro and src/pages/en/contact.astro render ContactForm with client:load and pass lang, so window access happens only in the browser; no change required.
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'sobre-nosotros' : 'about-us')}> | ||
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | ||
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight"> | ||
| ¿Quiénes somos? | ||
| {t('home.nav.about')} | ||
| </span> | ||
| </button> | ||
| </a> | ||
| <a href="https://blog.yellowumbrella.dev" target="_blank" rel="noreferrer"> | ||
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | ||
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight"> | ||
| Blog | ||
| {t('home.nav.blog')} | ||
| </span> | ||
| </button> | ||
| </a> | ||
| <a href="/redes-sociales"> | ||
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'redes-sociales' : 'social-networks')}> | ||
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | ||
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight"> | ||
| Redes sociales | ||
| {t('home.nav.social')} | ||
| </span> | ||
| </button> | ||
| </a> | ||
| <a href="/contacto"> | ||
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'contacto' : 'contact')}> | ||
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | ||
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight"> | ||
| Contacto | ||
| {t('home.nav.contact')} | ||
| </span> | ||
| </button> | ||
| </a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix invalid nested interactive elements (button inside anchor)
An anchor containing a button is invalid and harms accessibility. Style the anchor as a button instead.
Apply this diff:
- <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'sobre-nosotros' : 'about-us')}>
- <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
- <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight">
+ <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'sobre-nosotros' : 'about-us')}
+ class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
+ <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight">
{t('home.nav.about')}
</span>
- </button>
</a>
- <a href="https://blog.yellowumbrella.dev" target="_blank" rel="noreferrer">
- <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
- <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight">
+ <a href="https://blog.yellowumbrella.dev" target="_blank" rel="noopener noreferrer"
+ class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
+ <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight">
{t('home.nav.blog')}
</span>
- </button>
</a>
- <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'redes-sociales' : 'social-networks')}>
- <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
- <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight">
+ <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'redes-sociales' : 'social-networks')}
+ class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
+ <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight">
{t('home.nav.social')}
</span>
- </button>
</a>
- <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'contacto' : 'contact')}>
- <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
- <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight">
+ <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'contacto' : 'contact')}
+ class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]">
+ <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight">
{t('home.nav.contact')}
</span>
- </button>
</a>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'sobre-nosotros' : 'about-us')}> | |
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight"> | |
| ¿Quiénes somos? | |
| {t('home.nav.about')} | |
| </span> | |
| </button> | |
| </a> | |
| <a href="https://blog.yellowumbrella.dev" target="_blank" rel="noreferrer"> | |
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight"> | |
| Blog | |
| {t('home.nav.blog')} | |
| </span> | |
| </button> | |
| </a> | |
| <a href="/redes-sociales"> | |
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'redes-sociales' : 'social-networks')}> | |
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight"> | |
| Redes sociales | |
| {t('home.nav.social')} | |
| </span> | |
| </button> | |
| </a> | |
| <a href="/contacto"> | |
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'contacto' : 'contact')}> | |
| <button class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight"> | |
| Contacto | |
| {t('home.nav.contact')} | |
| </span> | |
| </button> | |
| </a> | |
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'sobre-nosotros' : 'about-us')} | |
| class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight"> | |
| {t('home.nav.about')} | |
| </span> | |
| </a> | |
| <a href="https://blog.yellowumbrella.dev" target="_blank" rel="noopener noreferrer" | |
| class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight"> | |
| {t('home.nav.blog')} | |
| </span> | |
| </a> | |
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'redes-sociales' : 'social-networks')} | |
| class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-lg lg:text-base md:text-sm sm:text-xs text-center leading-tight"> | |
| {t('home.nav.social')} | |
| </span> | |
| </a> | |
| <a href={getRelativeLocaleUrl(lang, lang === 'es' ? 'contacto' : 'contact')} | |
| class="relative inline-flex items-center justify-center w-32 h-16 p-[2px] mb-2 mr-4 overflow-hidden font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-[#ffd300] to-[#773376] group-hover:from-[#ffd300] group-hover:to-[#773376] hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-[#773376] dark:focus:ring-[#773376]"> | |
| <span class="relative w-full h-full px-2 py-1 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-[6px] group-hover:bg-opacity-0 flex items-center justify-center text-xl lg:text-lg md:text-base sm:text-sm text-center leading-tight"> | |
| {t('home.nav.contact')} | |
| </span> | |
| </a> |
🤖 Prompt for AI Agents
In src/components/Head.astro lines 20-47: the markup nests <button> inside <a>,
which is invalid and inaccessible; remove the inner <button> elements and move
their classes/attributes to the parent <a> so each anchor is styled like a
button (keep href, target and rel for external links), preserve the inner <span>
content and classes, and ensure the anchor retains the focus/hover styles (add
any focus-related attributes to the <a> as needed) so anchors remain
keyboard-focusable and visually identical to the previous button styling.
| <script> | ||
| function initLanguagePicker() { | ||
| const select = document.getElementById('language-select') as HTMLSelectElement; | ||
|
|
||
| if (select) { | ||
| // Remover event listeners previos para evitar duplicados | ||
| const newSelect = select.cloneNode(true) as HTMLSelectElement; | ||
| select.parentNode?.replaceChild(newSelect, select); | ||
|
|
||
| newSelect.addEventListener('change', function() { | ||
| const selectedOption = this.options[this.selectedIndex]; | ||
| const url = selectedOption.getAttribute('data-url'); | ||
| if (url) { | ||
| window.location.href = url; | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // Inicializar cuando se carga la página | ||
| document.addEventListener('DOMContentLoaded', initLanguagePicker); | ||
|
|
||
| // Reinicializar después de cada transición de página de Astro | ||
| document.addEventListener('astro:page-load', initLanguagePicker); | ||
| </script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove TypeScript from browser script to avoid runtime errors
The script tag runs in the browser; TS assertions (as HTMLSelectElement) will break. Use plain JS and narrow via instanceof.
Apply this diff:
<script>
- function initLanguagePicker() {
- const select = document.getElementById('language-select') as HTMLSelectElement;
-
- if (select) {
- // Remover event listeners previos para evitar duplicados
- const newSelect = select.cloneNode(true) as HTMLSelectElement;
- select.parentNode?.replaceChild(newSelect, select);
-
- newSelect.addEventListener('change', function() {
- const selectedOption = this.options[this.selectedIndex];
- const url = selectedOption.getAttribute('data-url');
- if (url) {
- window.location.href = url;
- }
- });
- }
- }
-
- // Inicializar cuando se carga la página
- document.addEventListener('DOMContentLoaded', initLanguagePicker);
-
- // Reinicializar después de cada transición de página de Astro
- document.addEventListener('astro:page-load', initLanguagePicker);
+ function initLanguagePicker() {
+ const select = document.getElementById('language-select');
+ if (select && select instanceof HTMLSelectElement) {
+ // Remove previous listeners by cloning
+ const newSelect = select.cloneNode(true);
+ select.parentNode?.replaceChild(newSelect, select);
+ newSelect.addEventListener('change', (e) => {
+ const target = e.currentTarget;
+ if (target && target instanceof HTMLSelectElement) {
+ const option = target.options[target.selectedIndex];
+ const url = option.getAttribute('data-url');
+ if (url) window.location.assign(url);
+ }
+ });
+ }
+ }
+ document.addEventListener('DOMContentLoaded', initLanguagePicker);
+ document.addEventListener('astro:page-load', initLanguagePicker);
</script>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <script> | |
| function initLanguagePicker() { | |
| const select = document.getElementById('language-select') as HTMLSelectElement; | |
| if (select) { | |
| // Remover event listeners previos para evitar duplicados | |
| const newSelect = select.cloneNode(true) as HTMLSelectElement; | |
| select.parentNode?.replaceChild(newSelect, select); | |
| newSelect.addEventListener('change', function() { | |
| const selectedOption = this.options[this.selectedIndex]; | |
| const url = selectedOption.getAttribute('data-url'); | |
| if (url) { | |
| window.location.href = url; | |
| } | |
| }); | |
| } | |
| } | |
| // Inicializar cuando se carga la página | |
| document.addEventListener('DOMContentLoaded', initLanguagePicker); | |
| // Reinicializar después de cada transición de página de Astro | |
| document.addEventListener('astro:page-load', initLanguagePicker); | |
| </script> | |
| <script> | |
| function initLanguagePicker() { | |
| const select = document.getElementById('language-select'); | |
| if (select && select instanceof HTMLSelectElement) { | |
| // Remove previous listeners by cloning | |
| const newSelect = select.cloneNode(true); | |
| select.parentNode?.replaceChild(newSelect, select); | |
| newSelect.addEventListener('change', (e) => { | |
| const target = e.currentTarget; | |
| if (target && target instanceof HTMLSelectElement) { | |
| const option = target.options[target.selectedIndex]; | |
| const url = option.getAttribute('data-url'); | |
| if (url) window.location.assign(url); | |
| } | |
| }); | |
| } | |
| } | |
| document.addEventListener('DOMContentLoaded', initLanguagePicker); | |
| document.addEventListener('astro:page-load', initLanguagePicker); | |
| </script> |
🤖 Prompt for AI Agents
In src/components/LanguagePicker.astro around lines 33 to 57, the inline script
uses TypeScript-only syntax (type assertions like "as HTMLSelectElement") which
will cause runtime errors in the browser; remove all TypeScript syntax and use
plain JavaScript: get the element with document.getElementById, check it with
instanceof HTMLSelectElement before using it, avoid TS casts on cloneNode (just
treat the result as an Element and guard with instanceof), and inside the change
handler reference the element via the captured newSelect variable or
event.target (narrowed with instanceof) to read options and data-url; keep the
DOMContentLoaded and astro:page-load listeners but ensure the script contains
only valid JS and proper instanceof checks.
| export function getLangFromUrlReact(url: string) { | ||
| const [, lang] = url.split('/'); | ||
| if (lang in ui) return lang as keyof typeof ui; | ||
| return defaultLang; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make URL language extraction robust for absolute URLs.
url.split('/') breaks for absolute URLs (https://…). Parse pathname first.
-export function getLangFromUrlReact(url: string) {
- const [, lang] = url.split('/');
- if (lang in ui) return lang as keyof typeof ui;
- return defaultLang;
-}
+export function getLangFromUrlReact(url: string) {
+ let pathname = url;
+ try {
+ // If absolute URL, extract pathname; otherwise assume it's already a path
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) {
+ pathname = new URL(url).pathname;
+ }
+ } catch {
+ // noop; fallback to given string
+ }
+ const first = pathname.replace(/^\/+/, '').split('/')[0] ?? '';
+ if (first && first in ui) return first as keyof typeof ui;
+ return defaultLang;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function getLangFromUrlReact(url: string) { | |
| const [, lang] = url.split('/'); | |
| if (lang in ui) return lang as keyof typeof ui; | |
| return defaultLang; | |
| } | |
| export function getLangFromUrlReact(url: string) { | |
| let pathname = url; | |
| try { | |
| // If absolute URL, extract pathname; otherwise assume it's already a path | |
| if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(url)) { | |
| pathname = new URL(url).pathname; | |
| } | |
| } catch { | |
| // noop; fallback to given string | |
| } | |
| const first = pathname.replace(/^\/+/, '').split('/')[0] ?? ''; | |
| if (first && first in ui) return first as keyof typeof ui; | |
| return defaultLang; | |
| } |
🤖 Prompt for AI Agents
In src/i18n/react-utils.ts around lines 9 to 13, the current url.split('/')
approach fails for absolute URLs; instead parse the URL's pathname before
splitting — use the URL constructor when input looks like an absolute URL
(catching errors) or fallback to treating the input as a relative path, get
pathname (or the original string), trim leading/trailing slashes, split on '/'
and take the first segment as lang; then check if that lang exists in ui and
return it as keyof typeof ui or return defaultLang if not.
feat(i18n): Implement internationalization support with Spanish and English locales
Summary by CodeRabbit
New Features
Changes
Chores