Skip to main content

Overview

Adosa Real Estate supports Spanish (default) and English using a custom i18n implementation with Astro’s dynamic routing. The system is located in src/i18n/ and uses the [...lang] routing pattern.
Spanish content is served at root URLs (/propiedades), while English uses the /en prefix (/en/propiedades).

Supported Languages

Defined in src/i18n/ui.ts:
export const languages = {
  es: 'Español',
  en: 'English',
};

export const defaultLang = 'es';

Spanish (es)

Default language - root URLs//propiedades/contacto

English (en)

Secondary language - /en prefix/en/en/propiedades/en/contacto

Translation Strings

All UI translations are defined in the ui object in src/i18n/ui.ts:
export const ui = {
  es: {
    'nav.home': 'Sobre Adosa',
    'nav.properties': 'Propiedades',
    'nav.mission': 'Misión',
    'nav.contact': 'Contacto',
    'property.bedrooms': 'Dorm.',
    'property.bathrooms': 'Baños',
    'property.price': 'Precio',
    // ... more translations
  },
  en: {
    'nav.home': 'About Adosa',
    'nav.properties': 'Properties',
    'nav.mission': 'Mission',
    'nav.contact': 'Contact',
    'property.bedrooms': 'Beds',
    'property.bathrooms': 'Baths',
    'property.price': 'Price',
    // ... more translations
  },
} as const;
  • Navigation: nav.home, nav.properties, nav.mission, nav.contact
  • Footer: footer.rights, footer.privacy, footer.cookies
  • Property Details: property.bedrooms, property.bathrooms, property.built, property.plot, property.price, property.details, property.description, property.location, property.type, property.reference, property.energy, property.community, property.ibi
  • Property Grid: grid.location, grid.typology, grid.beds, grid.baths, grid.all, grid.any, grid.apartment, grid.house, grid.land, grid.featured
  • Contact Form: contact.title, contact.name, contact.phone, contact.email, contact.message, contact.send

Routing Pattern

Dynamic Language Routes

All pages use the [...lang] catch-all parameter:
src/pages/
└── [...lang]/
    ├── index.astro           → / or /en
    ├── propiedades.astro     → /propiedades or /en/propiedades
    ├── contacto.astro        → /contacto or /en/contacto
    └── propiedades/
        └── [...slug].astro   → /propiedades/villa-123 or /en/propiedades/villa-123

Static Path Generation

Each page must export getStaticPaths() to generate both language versions:
// src/i18n/utils.ts
export function getStaticPaths() {
  return [
    { params: { lang: undefined } },  // Spanish (default)
    { params: { lang: 'en' } },       // English
  ];
}
Usage in pages:
---
// src/pages/[...lang]/index.astro
import { getStaticPaths as i18nGetStaticPaths } from '../../i18n/utils';

export async function getStaticPaths() {
  return i18nGetStaticPaths();
}

const { lang: langParam } = Astro.params;
const lang = (langParam || 'es') as 'es' | 'en';
---

i18n Utility Functions

getLangFromUrl()

Extracts the language from the current URL:
import { getLangFromUrl } from '../../i18n/utils';

const lang = getLangFromUrl(Astro.url);
// Returns 'es' or 'en'
// src/i18n/utils.ts
export function getLangFromUrl(url: URL) {
  const [, lang] = url.pathname.split('/');
  if (lang in ui) return lang as keyof typeof ui;
  return defaultLang;
}

useTranslations()

Creates a translation function for the current language:
import { useTranslations } from '../../i18n/utils';

const t = useTranslations(lang);
// src/i18n/utils.ts
export function useTranslations(lang: keyof typeof ui) {
  return function t(key: keyof typeof ui[typeof defaultLang]) {
    return ui[lang][key] || ui[defaultLang][key];
  }
}
The translation function falls back to Spanish if a key is missing in English.

useTranslatedPath()

Generates language-aware URLs for navigation:
import { useTranslatedPath } from '../../i18n/utils';

const translatePath = useTranslatedPath(lang);
// src/i18n/utils.ts
export function useTranslatedPath(lang: keyof typeof ui) {
  return function translatePath(path: string, l: string = lang) {
    return l === defaultLang ? path : `/${l}${path}`;
  }
}

Complete Example

Here’s a full page implementation with i18n:
---
// src/pages/[...lang]/propiedades.astro
import BaseLayout from '../../layouts/BaseLayout.astro';
import { 
  getStaticPaths as i18nGetStaticPaths,
  getLangFromUrl,
  useTranslations,
  useTranslatedPath 
} from '../../i18n/utils';
import { PropertyService } from '../../services/api/properties';

export async function getStaticPaths() {
  return i18nGetStaticPaths();
}

const { lang: langParam } = Astro.params;
const lang = (langParam || 'es') as 'es' | 'en';
const t = useTranslations(lang);
const translatePath = useTranslatedPath(lang);

// Fetch properties in the correct language
const apiLang = lang === 'en' ? 'en-GB' : 'es-ES';
const properties = await PropertyService.getAll(apiLang);
---

<BaseLayout 
  title={t('nav.properties')} 
  lang={lang}
>
  <h1>{t('nav.properties')}</h1>
  
  <div class="property-grid">
    {properties.map(property => (
      <a href={translatePath(`/propiedades/${property.id}`)}>
        <img src={property.image} alt={property.title} />
        <h3>{property.title}</h3>
        <p>{property.bedrooms} {t('property.bedrooms')} | {property.bathrooms} {t('property.bathrooms')}</p>
        <p>{property.price}</p>
      </a>
    ))}
  </div>
</BaseLayout>

API Language Support

The eGO Real Estate API is called with language-specific parameters:
// src/services/api/properties.ts
static async getAll(lang: "es-ES" | "en-GB" = "es-ES"): Promise<Property[]> {
  const endpoint = '/v1/Properties';
  const data = await ApiCore.request<{ Properties: any[] }>(endpoint, {}, lang);
  // ...
}
The language is passed to API headers:
// src/services/api/api.ts
url.searchParams.set("Language", lang);
headers.set("Language", lang);
Property descriptions, locations, and other content are automatically fetched in the requested language.

Inline Translations

For page-specific content not in ui.ts, use inline conditionals:
---
const isEn = lang === 'en';

const HERO_TITLE = isEn
  ? "Your real estate agency in San Pedro Alcántara"
  : "Tu inmobiliaria en San Pedro Alcántara";
---

<h1>{HERO_TITLE}</h1>
For shared UI elements, always add translations to ui.ts instead of using inline conditionals.

Best Practices

1

Always use getStaticPaths()

Every page in [...lang]/ must export static paths:
export async function getStaticPaths() {
  return i18nGetStaticPaths();
}
2

Extract language early

Get the language at the top of your component:
const { lang: langParam } = Astro.params;
const lang = (langParam || 'es') as 'es' | 'en';
3

Use translation functions

Create t() and translatePath() functions:
const t = useTranslations(lang);
const translatePath = useTranslatedPath(lang);
4

Pass language to API calls

Convert to API format when fetching data:
const apiLang = lang === 'en' ? 'en-GB' : 'es-ES';
const data = await PropertyService.getAll(apiLang);

Adding a New Language

To add French, for example:
1

Update language list

// src/i18n/ui.ts
export const languages = {
  es: 'Español',
  en: 'English',
  fr: 'Français',
};
2

Add translations

export const ui = {
  es: { /* ... */ },
  en: { /* ... */ },
  fr: {
    'nav.home': 'À propos d\'Adosa',
    'nav.properties': 'Propriétés',
    // ...
  },
} as const;
3

Update getStaticPaths()

export function getStaticPaths() {
  return [
    { params: { lang: undefined } },
    { params: { lang: 'en' } },
    { params: { lang: 'fr' } },
  ];
}
4

Update API language mapping

const apiLang = 
  lang === 'en' ? 'en-GB' :
  lang === 'fr' ? 'fr-FR' :
  'es-ES';

Architecture

Learn about the routing system

API Integration

Understand API language parameters

Navigation Component

See i18n in action with the navigation

Pages Guide

Creating new multi-language pages

Build docs developers (and LLMs) love