The portfolio implements a robust internationalization system supporting three languages: English, Spanish (Castellano), and Basque (Euskera). The implementation uses React Context API for state management and includes dynamic SEO metadata updates.
Language Context Implementation
The internationalization system is built around a custom LanguageContext that provides language state and translation functions throughout the application.
Core Context Structure
src/context/LanguageContext.tsx
type Language = 'en' | 'es' | 'eu' ;
interface LanguageContextType {
language : Language ;
setLanguage : ( lang : Language ) => void ;
t : ( key : string , params ?: Record < string , string | number >) => string ;
}
const LanguageContext = createContext < LanguageContextType | undefined >( undefined );
The context exposes three key properties:
language: Current active language
setLanguage: Function to switch languages
t: Translation function with parameter interpolation support
Translation Object Structure
All translations are stored in a centralized object with dot-notation keys for easy organization:
src/context/LanguageContext.tsx
const translations = {
en: {
'header.home' : 'Back to home' ,
'header.logoAlt' : 'Yeray Garrido Logo - Back to top' ,
'hero.role' : 'Software Engineer & Full Stack Developer' ,
'hero.portfolio' : '© {year} PORTFOLIO' ,
'hero.downloadCv' : 'Download CV' ,
'intro.title1' : 'SOFTWARE' ,
'intro.title2' : 'ENGINEERING' ,
// ... more translations
},
es: {
'header.home' : 'Volver al inicio' ,
'header.logoAlt' : 'Logotipo de Yeray Garrido - Volver al inicio' ,
'hero.role' : 'Software Engineer & Full Stack Developer' ,
'hero.portfolio' : '© {year} PORTAFOLIO' ,
'hero.downloadCv' : 'Descargar CV' ,
'intro.title1' : 'INGENIERÍA' ,
'intro.title2' : 'DE SOFTWARE' ,
// ... more translations
},
eu: {
'header.home' : 'Hasierara itzuli' ,
'header.logoAlt' : 'Yeray Garridoren logotipoa - Itzuli hasierara' ,
'hero.role' : 'Software Engineer & Full Stack Developer' ,
'hero.portfolio' : '© {year} PORTFOLIOA' ,
'hero.downloadCv' : 'CV-a Deskargatu' ,
'intro.title1' : 'SOFTWARE' ,
'intro.title2' : 'INGENIARITZA' ,
// ... more translations
}
};
Naming Convention
Translation keys follow a hierarchical pattern:
component.element: Basic element translations
component.element.variant: Specific variants or states
Parameters use curly brace syntax: {year}, {name}
The useLanguage Hook
Components access the language context through a custom hook:
src/context/LanguageContext.tsx
export const useLanguage = () => {
const context = useContext ( LanguageContext );
if ( ! context ) {
throw new Error ( 'useLanguage must be used within a LanguageProvider' );
}
return context ;
};
This hook provides type-safe access to the language state and ensures components are wrapped in the provider.
Translation Function with Parameter Interpolation
The t function handles translation lookup and parameter substitution:
src/context/LanguageContext.tsx
const t = ( key : string , params ?: Record < string , string | number >) => {
let str = translations [ language ][ key as keyof typeof translations [ 'en' ]] || key ;
if ( params ) {
Object . keys ( params ). forEach ( k => {
str = str . replace ( `{ ${ k } }` , String ( params [ k ]));
});
}
return str ;
};
Features:
Fallback to key if translation missing
Dynamic parameter interpolation
Type-safe key lookup
Support for string and number parameters
Usage Example
import { useLanguage } from '../context/LanguageContext' ;
function Hero () {
const { t } = useLanguage ();
const currentYear = new Date (). getFullYear ();
return (
< div >
< h1 > { t ( 'hero.role' ) } </ h1 >
< p > { t ( 'hero.portfolio' , { year: currentYear }) } </ p >
< button > { t ( 'hero.downloadCv' ) } </ button >
</ div >
);
}
The language provider automatically updates SEO metadata when the language changes:
src/context/LanguageContext.tsx
useEffect (() => {
// Update HTML lang attribute for accessibility and SEO
document . documentElement . lang = language ;
// SEO data per language
const seoData = {
es: {
title: 'Yeray Garrido | Ingeniero de Software & Desarrollador Full Stack' ,
description: 'Ingeniero de Software especializado en PHP, Java y desarrollo WordPress a medida...' ,
},
en: {
title: 'Yeray Garrido | Software Engineer & Full Stack Developer' ,
description: 'Software Engineer specializing in PHP, Java, and custom WordPress development...' ,
},
eu: {
title: 'Yeray Garrido | Software Ingeniaria & Full Stack Garatzailea' ,
description: 'PHP, Java eta neurrirako WordPress garapenean espezializatutako Software Ingeniaria...' ,
},
};
const { title , description } = seoData [ language ];
// Update document title
document . title = title ;
// Helper function to update meta tags
const setMeta = ( selector : string , content : string ) => {
document . querySelector ( selector )?. setAttribute ( 'content' , content );
};
// Standard meta tags
setMeta ( 'meta[name="title"]' , title );
setMeta ( 'meta[name="description"]' , description );
// Open Graph tags
setMeta ( 'meta[property="og:title"]' , title );
setMeta ( 'meta[property="og:description"]' , description );
setMeta ( 'meta[property="og:locale"]' ,
language === 'es' ? 'es_ES' : language === 'en' ? 'en_US' : 'eu_EU'
);
// Twitter Card tags
setMeta ( 'meta[name="twitter:title"]' , title );
setMeta ( 'meta[name="twitter:description"]' , description );
}, [ language ]);
SEO Elements Updated
Updates the browser tab title and search engine display title
Sets document.documentElement.lang for screen readers and search engines
Standard meta description for search engine snippets
Language Switching Implementation
The Header component provides language switching functionality:
src/components/Header.tsx
import { useLanguage } from '../context/LanguageContext' ;
function Header () {
const { language , setLanguage , t } = useLanguage ();
const cycleLanguage = () => {
const langs : Language [] = [ 'es' , 'en' , 'eu' ];
const idx = langs . indexOf ( language );
setLanguage ( langs [( idx + 1 ) % langs . length ]);
};
return (
< button onClick = { cycleLanguage } aria-label = { t ( 'header.switchLanguage' ) } >
{ language . toUpperCase () }
</ button >
);
}
Provider Setup
The language provider wraps the entire application:
import { LanguageProvider } from './context/LanguageContext' ;
export default function App () {
return (
< LanguageProvider >
< main >
{ /* App components */ }
</ main >
</ LanguageProvider >
);
}
Best Practices
Organized Keys Use dot notation for hierarchical organization: component.element.variant
Parameter Interpolation Use curly braces for dynamic values: {year}, {name}, {count}
Fallback Handling Translation function returns the key itself if translation is missing
SEO First All SEO metadata updates automatically on language change
Single translation object : All translations loaded once at startup
Memoized context value : Prevents unnecessary re-renders
Efficient updates : Only metadata changes trigger DOM updates
No external dependencies : Pure React implementation with no i18n libraries
The default language is set to Spanish ('es') in the provider’s initial state.