Skip to main content
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>
  );
}

Dynamic SEO Metadata Updates

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
Social media sharing metadata (Facebook, LinkedIn)
  • og:title
  • og:description
  • og:locale
Twitter-specific sharing metadata
  • twitter:title
  • twitter:description

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:
src/App.tsx
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

Performance Considerations

  • 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.

Build docs developers (and LLMs) love