Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Emmanuel-Mtz-777/My-Personal-Portfolio/llms.txt

Use this file to discover all available pages before exploring further.

The portfolio at leviek.dev is fully bilingual, serving the same content in both Spanish and English. Rather than relying on a third-party i18n library, the project uses Astro 6’s built-in internationalization routing system. Spanish is the default locale, served at the root path /, and English is available at /en/. All UI text is stored in two JSON files — one per language — and components select the correct file at render time based on the current locale.

Astro i18n Configuration

The i18n routing is declared in astro.config.mjs:
// astro.config.mjs
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import react from "@astrojs/react";

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
  i18n: {
    defaultLocale: "es",
    locales: ["es", "en"],
  },
  integrations: [react()],
});
With this configuration, Astro automatically:
  • Serves the Spanish locale at / (no path prefix, since "es" is the defaultLocale)
  • Serves the English locale at /en/ (prefixed with the locale code)
  • Populates Astro.currentLocale with "es" or "en" on every page

Page Routes

The two locale entry points are separate Astro page files:
FileURLLanguage
src/pages/index.astro/Spanish (default)
src/pages/en/index.astro/en/English
Both pages render the same set of section components (Hero, Experience, Projects, Stack, Education, AboutMe) — the language difference comes entirely from the content each component reads at build time.

Content File Structure

All UI strings are stored in JSON files under src/langs/:
src/langs/
├── es.json   ← Spanish strings (default locale)
└── en.json   ← English strings
Each file shares the same top-level key structure:
KeyDescription
heroGreeting, role title, description, location, image alt text
navNavigation link labels (Experience, Projects, Skills, etc.)
experienceSection title and work history items
projectsSection title and project entries (name, description, tech)
stackSection title and category labels for tech skills
educationDegree entry (name, institution, date range)
certificationsSection title and certification items (name, issuer, date)
aboutAbout Me section paragraphs with highlight keyword arrays

Locale Detection in Components

Components detect the current locale using Astro.currentLocale and import the correct translation object. Here is the pattern used in Header.astro and throughout all section components:
---
import es from "../../langs/es.json";
import en from "../../langs/en.json";

const lang = Astro.currentLocale || "es";
const t = lang === "es" ? es : en;
---
After this setup, t.nav.projects returns "Proyectos" on the Spanish page and "Projects" on the English page.

Hero Section — Side-by-Side Example

The hero key in both language files shows how the same content is expressed in each locale: English (src/langs/en.json):
{
  "hero": {
    "greeting": "Hi! I'm Emmanuel Martínez",
    "role": "Full Stack Web Developer",
    "description": "Passionate about web development, backend architecture, and containerized environments. I enjoy building complete applications from the user interface to the server, developing APIs, integrating services, and creating solutions focused on performance, organization, and scalability using a wide range of technologies.",
    "location": "Aguascalientes, Mexico 🇲🇽"
  }
}
Spanish (src/langs/es.json):
{
  "hero": {
    "greeting": "¡Hola! Soy Emmanuel Martínez",
    "role": "Desarrollador Web Fullstack",
    "description": "Apasionado por el desarrollo web, la arquitectura backend y los contenedores. Me gusta crear aplicaciones completas desde la interfaz hasta el servidor, desarrollando APIs, integrando servicios y construyendo soluciones enfocadas en rendimiento, organización y escalabilidad utilizando diversas tecnologías.",
    "location": "Aguascalientes, Ags., México 🇲🇽"
  }
}

Language Switcher

The Header.astro component renders a pill-style language switcher that links directly between the two routes:
<a href="/">ES</a>
<a href="/en/">EN</a>
The active locale’s button is highlighted with bg-white text-black and the inactive one with text-white/60, determined by comparing lang to "es" or "en".

Adding a New Locale

To add a third locale (for example, French at /fr/):
  1. Add "fr" to the locales array in astro.config.mjs:
    i18n: {
      defaultLocale: "es",
      locales: ["es", "en", "fr"],
    }
    
  2. Create src/langs/fr.json with all the same top-level keys as en.json and es.json.
  3. Create src/pages/fr/index.astro mirroring the structure of src/pages/en/index.astro.
  4. Update the locale detection logic in each component to handle lang === "fr".
  5. Add the /fr/ link to the language switcher in Header.astro.

Updating Existing Content Strings

To update a piece of UI text — for example, a project description or a nav label — open both src/langs/es.json and src/langs/en.json and edit the corresponding key in each file. The change will be reflected at the next pnpm build or pnpm dev reload.
All UI text updates must be made in both language files (es.json and en.json) to keep the Spanish and English versions in sync. A missing or stale key in one file will cause the affected component to display an undefined value.

Build docs developers (and LLMs) love