Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ishaq74/concordia/llms.txt

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

Concordia is built as a multilingual platform from the ground up, supporting four languages: French, English, Arabic, and Spanish.

Overview

The internationalization system provides:
  • Four supported languages: French (fr), English (en), Arabic (ar), Spanish (es)
  • URL-based routing: Language prefix in all URLs (/fr/, /en/, etc.)
  • Default language: French (fr)
  • Prefix all locales: Even the default locale uses a prefix
  • Translation files: JSON-based translation system
  • User preferences: Users can select their preferred language

Configuration

The i18n configuration is defined in astro.config.mjs.

Astro i18n Setup

// From astro.config.mjs:29-35
i18n: {
  locales: ['fr', 'en', 'ar', 'es'],
  defaultLocale: 'fr',
  routing: {
    prefixDefaultLocale: true
  }
}
Configuration options:
OptionValueDescription
locales['fr', 'en', 'ar', 'es']Supported language codes (ISO 639-1)
defaultLocale'fr'Default language for the platform
prefixDefaultLocaletrueInclude /fr/ prefix even for French

Root Redirect

// From astro.config.mjs:26-28
redirects: {
  '/': '/fr/'
}
Visitors to the root URL (/) are automatically redirected to /fr/ (French homepage).

Supported Languages

French - Default language
  • Primary language for the Annecy region
  • Most complete translations
  • Used as reference for other translations
  • File: src/i18n/fr.json

Translation Files

Translations are stored as JSON files in src/i18n/:
src/i18n/
  ├── fr.json    # French (default)
  ├── en.json    # English
  ├── ar.json    # Arabic
  └── es.json    # Spanish

Translation File Structure

Each translation file follows the same structure with nested objects:
// From fr.json and en.json
{
  "welcome": "Bienvenue sur Concordia",
  "nav": {
    "home": "Accueil",
    "about": "À propos",
    "services": "Services",
    "blog": "Blog",
    "contact": "Contact",
    "docs": "Docs",
    "places": "Annuaire",
    "forum": "Forum",
    "events": "Événements",
    "education": "Éducation",
    "volunteer": "Bénévolat",
    "transparency": "Transparence",
    "classifieds": "Annonces",
    "trails": "Sentiers",
    "dashboard": "Tableau de bord",
    "admin": "Administration",
    "discover": "Découvrir",
    "community": "Communauté",
    "engage": "S'engager",
    "funding": "Financement",
    "groups": "Groupes",
    "mySpace": "Mon espace"
  },
  "home": {
    "heroTitle": "Concordia Annecy",
    "heroSubtitle": "La plateforme citoyenne du bassin annécien",
    "heroCta": "Explorer",
    "heroCtaSecondary": "Rejoindre la communauté"
  },
  "footer": {
    "company": "Concordia Annecy",
    "description": "Plateforme citoyenne et collaborative...",
    "contact": "Contact",
    "email": "contact@concordia-annecy.fr"
  }
}

Translation Keys

Common translation namespaces:
NamespacePurposeExample Keys
navNavigation menuhome, places, forum, events
homeHomepage contentheroTitle, heroSubtitle, heroCta
footerFooter contentcompany, contact, legal, privacy
docsSidebarDocumentation navigationgettingStarted, introduction
servicesService descriptionsconsulting, development, design

URL Structure

All routes include language prefixes:
https://concordia-annecy.fr/fr/          # French homepage
https://concordia-annecy.fr/en/          # English homepage
https://concordia-annecy.fr/ar/          # Arabic homepage
https://concordia-annecy.fr/es/          # Spanish homepage

https://concordia-annecy.fr/fr/places    # Places in French
https://concordia-annecy.fr/en/places    # Places in English

https://concordia-annecy.fr/fr/blog/article-slug    # Article in French
https://concordia-annecy.fr/en/blog/article-slug    # Same article in English
Benefits of prefixed routing:
  • Clear language indication in URL
  • SEO-friendly (each language is a separate URL)
  • Easy sharing of language-specific links
  • No ambiguity about content language

User Language Preferences

Users can set their preferred language in their profile.

Profile Language Setting

// From concordia-specs.md profile specification
interface Profile {
  preferred_language: "fr" | "en" | "ar" | "es";  // Default: "fr"
}
User flow:
1

New user signs up

Default preferred_language is set to "fr" (French)
2

User updates profile

User can change language preference in account settings
3

System respects preference

Platform displays content in user’s preferred language when available
4

Fallback to default

If translation missing, falls back to French

Content Translation Strategy

UI Translations

Interface elements (buttons, labels, messages) are translated via JSON files:
  • Navigation menus: nav.* keys
  • Form labels: forms.* keys
  • Error messages: errors.* keys
  • Validation messages: validation.* keys

User-Generated Content

Concordia uses separate translation tables for user-generated content:

Place Translations

-- From concordia-specs.md:484-501
CREATE TABLE place_translation (
  id uuid PRIMARY KEY,
  place_id uuid REFERENCES place(id),
  language text NOT NULL,  -- 'fr', 'en', 'ar', 'es'
  name text NOT NULL,
  description text,
  created_at timestamp DEFAULT now(),
  updated_at timestamp DEFAULT now(),
  UNIQUE(place_id, language)
);
Features:
  • Each place can have translations in multiple languages
  • Unique constraint ensures one translation per language
  • Place owners can provide translations for their listings

Article Content (Future)

-- From concordia-specs.md:762-776
CREATE TABLE article_content (
  id uuid PRIMARY KEY,
  article_id uuid REFERENCES article(id),
  content_json jsonb NOT NULL,
  language text NOT NULL,  -- 'fr', 'en', 'ar', 'es'
  updated_at timestamp DEFAULT now(),
  UNIQUE(article_id, language)
);
Planned: Articles will support multiple language versions using the same structure.

RTL (Right-to-Left) Support

Arabic requires special handling for right-to-left layout.

CSS Direction

/* For Arabic locale */
html[lang="ar"] {
  direction: rtl;
}

/* Adjust specific components */
html[lang="ar"] .nav-menu {
  flex-direction: row-reverse;
}

html[lang="ar"] .card {
  text-align: right;
}

Logical Properties

Use CSS logical properties for better RTL support:
/* Instead of margin-left */
margin-inline-start: 1rem;

/* Instead of margin-right */
margin-inline-end: 1rem;

/* Instead of padding-left and padding-right */
padding-inline: 1rem;

/* Instead of border-left */
border-inline-start: 1px solid;
Benefits:
  • Automatically flips for RTL languages
  • No need for duplicate RTL-specific styles
  • Better maintainability

Language Detection

The system detects user language through:
  1. URL path: Primary method (e.g., /fr/, /en/)
  2. User profile: Logged-in users’ preferred_language
  3. Browser headers: Accept-Language header for first visit
  4. Cookie: Remembers anonymous user’s language choice

Detection Priority

1. Explicit URL path (/en/places)
   ↓ (highest priority)
2. User profile setting (if logged in)

3. Language cookie (if set)

4. Browser Accept-Language header

5. Default language (fr)
   (fallback)

Email Translations

System emails are bilingual (French/English) in the same message:
// From auth.ts:69-72
subject: "Password reset - Réinitialisation de votre mot de passe",
text: `Cliquez sur le lien suivant pour réinitialiser votre mot de passe : ${url}`,
html: `<p>Cliquez sur le lien suivant pour réinitialiser votre mot de passe :</p><p><a href="${url}">${url}</a></p>`
Email types:
  • Password reset (bilingual subject)
  • Email verification (bilingual subject)
  • Organization invitations (bilingual)
  • Moderation notifications

Translation Best Practices

1

Keep keys consistent

Use the same key structure across all language files
2

Use nested objects

Group related translations (e.g., nav.home, nav.about)
3

Avoid hardcoded text

Always use translation keys, never hardcode text in components
4

Provide context

Add comments in JSON files to explain context for translators
5

Test RTL layout

Always test UI in Arabic to ensure proper RTL rendering
6

Maintain parity

When adding new features, update all language files simultaneously

Adding a New Language

To add support for a new language:
1

Update Astro config

Add language code to locales array in astro.config.mjs
i18n: {
  locales: ['fr', 'en', 'ar', 'es', 'de'],  // Added German
  defaultLocale: 'fr',
}
2

Create translation file

Copy src/i18n/fr.json to src/i18n/de.json
3

Translate content

Translate all keys in the new file to the target language
4

Update database enum

Add language to profile schema if storing in database:
ALTER TYPE language ADD VALUE 'de';
5

Add RTL support (if needed)

For RTL languages (Hebrew, Farsi, Urdu), add CSS direction rules
6

Test thoroughly

  • Check all pages in new language
  • Verify URL routing works
  • Test language switcher
  • Validate email templates

Translation File Example

Comparing French and English for the same content:
{
  "welcome": "Bienvenue sur Concordia",
  "nav": {
    "home": "Accueil",
    "places": "Annuaire",
    "forum": "Forum",
    "events": "Événements"
  },
  "home": {
    "heroTitle": "Concordia Annecy",
    "heroSubtitle": "La plateforme citoyenne du bassin annécien",
    "heroCta": "Explorer"
  },
  "footer": {
    "company": "Concordia Annecy",
    "description": "Plateforme citoyenne et collaborative",
    "legal": "Mentions légales",
    "privacy": "Politique de confidentialité"
  }
}

Locale-Specific Formatting

Dates

const date = new Date();

// French
date.toLocaleDateString('fr-FR')  // "03/03/2026"

// English
date.toLocaleDateString('en-US')  // "3/3/2026"

// Arabic
date.toLocaleDateString('ar-SA')  // "٣/٣/٢٠٢٦"

Numbers and Currency

const price = 1234.56;

// French
price.toLocaleString('fr-FR', { style: 'currency', currency: 'EUR' })
// "1 234,56 €"

// English
price.toLocaleString('en-US', { style: 'currency', currency: 'EUR' })
// "€1,234.56"

See Also

Build docs developers (and LLMs) love