# Fichier: python_cheats/cheatsheets/babel.txt
# Cheatsheet Babel Python - Guide Complet d'Internationalisation (i18n) et Localisation (l10n)

# ============================================================================
# QU'EST-CE QUE BABEL ?
# ============================================================================

# Babel est une bibliothèque Python pour l'internationalisation (i18n) et la 
# localisation (l10n). Elle permet de :
# 
# 1. TRADUIRE votre application en plusieurs langues
# 2. FORMATER dates, heures, nombres selon les locales
# 3. GÉRER devises et fuseaux horaires
# 4. EXTRAIRE et COMPILER des fichiers de traduction
# 5. SUPPORTER formats pluriels complexes de différentes langues
#
# Termes clés :
# - i18n (internationalization) : Préparer le code pour supporter plusieurs langues
# - l10n (localization) : Adapter l'app à une langue/région spécifique
# - Locale : Combinaison langue + région (ex: fr_FR, en_US, es_MX)
# - gettext : Système standard GNU de traduction
# - PO/POT/MO : Fichiers de traduction (Portable Object, Template, Machine Object)


# ============================================================================
# [OK] INSTALLATION
# ============================================================================

# Installation basique
pip install babel

# Installation avec extras
pip install babel[flask]           # Support Flask-Babel
pip install babel[django]          # Support Django

# Vérifier installation
python -c "import babel; print(babel.__version__)"
pybabel --version

# Dépendances principales
# - pytz : Gestion des fuseaux horaires
# - setuptools : Pour extraction des messages


# ============================================================================
# [OK] CONCEPTS FONDAMENTAUX
# ============================================================================

# === LOCALES ===
# Format : langue_TERRITOIRE
# Exemples :
#   fr_FR : Français (France)
#   fr_CA : Français (Canada)
#   en_US : Anglais (États-Unis)
#   en_GB : Anglais (Royaume-Uni)
#   es_ES : Espagnol (Espagne)
#   es_MX : Espagnol (Mexique)
#   zh_CN : Chinois simplifié (Chine)
#   pt_BR : Portugais (Brésil)

# === FICHIERS DE TRADUCTION ===
# .pot (Portable Object Template) : Template avec toutes les chaînes à traduire
# .po  (Portable Object) : Fichier de traduction pour une langue spécifique
# .mo  (Machine Object) : Version compilée binaire du .po (utilisée en production)

# === STRUCTURE TYPIQUE ===
# myproject/
# ├── myapp/
# │   ├── __init__.py
# │   └── views.py
# ├── translations/          # Ou 'locales/'
# │   ├── fr/
# │   │   └── LC_MESSAGES/
# │   │       ├── messages.po
# │   │       └── messages.mo
# │   ├── es/
# │   │   └── LC_MESSAGES/
# │   │       ├── messages.po
# │   │       └── messages.mo
# │   └── messages.pot       # Template
# ├── babel.cfg              # Configuration extraction
# └── setup.py


# ============================================================================
# [OK] UTILISATION DE BASE - LOCALES
# ============================================================================

from babel import Locale

# Créer un objet Locale
locale_fr = Locale('fr', 'FR')
locale_en = Locale('en', 'US')
locale_es = Locale('es')           # Sans territoire

# Parser depuis string
locale = Locale.parse('fr_FR')
locale = Locale.parse('en-US')     # Format avec tiret aussi supporté
locale = Locale.parse('fr')        # Juste la langue

# Propriétés d'un Locale
print(locale_fr.language)          # 'fr'
print(locale_fr.territory)         # 'FR'
print(locale_fr.script)            # None (ex: 'Latn' pour latin)
print(locale_fr.variant)           # None (variantes dialectales)

# Noms lisibles
print(locale_fr.display_name)      # 'français'
print(locale_fr.english_name)      # 'French'
print(locale_fr.get_display_name('en'))  # 'French' (en anglais)
print(locale_fr.get_display_name('es'))  # 'francés' (en espagnol)

# Informations sur langue et territoire
print(locale_fr.language_name)     # 'français'
print(locale_fr.territory_name)    # 'France'
print(locale_fr.get_language_name('en'))    # 'French'
print(locale_fr.get_territory_name('en'))   # 'France'

# Lister tous les locales disponibles
from babel import localedata

all_locales = localedata.locale_identifiers()
print(f"Nombre de locales : {len(list(all_locales))}")

# Vérifier si un locale existe
if localedata.exists('fr_FR'):
    print("Locale fr_FR existe")

# Locale par défaut du système
import babel
default_locale = babel.default_locale()
print(f"Locale par défaut: {default_locale}")

# Négociation de locale (choix meilleur match)
from babel import negotiate_locale

supported = ['en_US', 'de_DE', 'fr_FR']
requested = ['fr', 'en_GB', 'en']
best = negotiate_locale(requested, supported)
print(f"Meilleur match: {best}")  # 'fr_FR'


# ============================================================================
# [OK] FORMATAGE DES DATES ET HEURES
# ============================================================================

from babel.dates import (
    format_date, format_datetime, format_time,
    format_timedelta, format_interval,
    get_timezone, get_timezone_name,
    get_period_names, get_day_names, get_month_names
)
from datetime import datetime, date, time, timedelta
import pytz

# === DATES ===

today = date(2024, 12, 25)

# Formats prédéfinis
print(format_date(today, locale='fr_FR'))           # '25 déc. 2024'
print(format_date(today, locale='en_US'))           # 'Dec 25, 2024'
print(format_date(today, locale='de_DE'))           # '25.12.2024'

# Formats spécifiques : short, medium, long, full
print(format_date(today, format='short', locale='fr_FR'))    # '25/12/2024'
print(format_date(today, format='medium', locale='fr_FR'))   # '25 déc. 2024'
print(format_date(today, format='long', locale='fr_FR'))     # '25 décembre 2024'
print(format_date(today, format='full', locale='fr_FR'))     # 'mercredi 25 décembre 2024'

# Format personnalisé (pattern Unicode)
# y = année, M = mois, d = jour, E = jour semaine
print(format_date(today, format='EEEE d MMMM y', locale='fr_FR'))
# 'mercredi 25 décembre 2024'

print(format_date(today, format='dd/MM/yyyy', locale='fr_FR'))
# '25/12/2024'

# Patterns communs :
# yyyy-MM-dd        : 2024-12-25
# dd/MM/yyyy        : 25/12/2024
# MMMM d, yyyy      : December 25, 2024
# EEEE, MMMM d      : Wednesday, December 25
# d MMM y           : 25 Dec 2024

# === HEURES ===

now = datetime(2024, 12, 25, 14, 30, 45)

# Formats prédéfinis
print(format_time(now, locale='fr_FR'))             # '14:30:45'
print(format_time(now, locale='en_US'))             # '2:30:45 PM'

print(format_time(now, format='short', locale='fr_FR'))    # '14:30'
print(format_time(now, format='medium', locale='fr_FR'))   # '14:30:45'
print(format_time(now, format='long', locale='fr_FR'))     # '14:30:45 UTC'
print(format_time(now, format='full', locale='fr_FR'))     # '14:30:45 UTC'

# Format personnalisé
# H = heure 24h, h = heure 12h, m = minute, s = seconde
print(format_time(now, format='HH:mm', locale='fr_FR'))    # '14:30'
print(format_time(now, format='h:mm a', locale='en_US'))   # '2:30 PM'

# === DATETIME ===

# Formats combinés date + heure
print(format_datetime(now, locale='fr_FR'))
# '25 déc. 2024, 14:30:45'

print(format_datetime(now, format='short', locale='fr_FR'))
# '25/12/2024 14:30'

print(format_datetime(now, format='full', locale='fr_FR'))
# 'mercredi 25 décembre 2024 à 14:30:45 UTC'

# Format personnalisé
print(format_datetime(now, format='EEEE d MMMM y à HH:mm', locale='fr_FR'))
# 'mercredi 25 décembre 2024 à 14:30'

# === FUSEAUX HORAIRES ===

# Créer datetime avec timezone
tz_paris = pytz.timezone('Europe/Paris')
dt_paris = tz_paris.localize(datetime(2024, 12, 25, 14, 30))

# Formater avec timezone
print(format_datetime(dt_paris, locale='fr_FR', tzinfo=tz_paris))

# Obtenir nom du timezone
tz_name = get_timezone_name(tz_paris, locale='fr_FR')
print(tz_name)  # 'heure normale d'Europe centrale'

# Différents formats de timezone
print(get_timezone_name(tz_paris, width='short', locale='fr_FR'))  # 'UTC+1'
print(get_timezone_name(tz_paris, width='long', locale='fr_FR'))   # 'heure normale...'

# Obtenir objet timezone
tz = get_timezone('Europe/Paris')
tz = get_timezone('America/New_York')

# === TIMEDELTA (DURÉES) ===

delta = timedelta(days=5, hours=3, minutes=30)

# Format simple
print(format_timedelta(delta, locale='fr_FR'))
# '5 jours'

# Avec granularité
print(format_timedelta(delta, granularity='hour', locale='fr_FR'))
# '5 jours et 3 heures'

print(format_timedelta(delta, granularity='minute', locale='fr_FR'))
# '5 jours, 3 heures et 30 minutes'

# Format court
print(format_timedelta(delta, format='short', locale='fr_FR'))
# '5 j'

# Durée négative
delta_neg = timedelta(days=-2)
print(format_timedelta(delta_neg, locale='fr_FR'))
# 'il y a 2 jours'

# Seuil pour affichage
print(format_timedelta(delta, threshold=2, locale='fr_FR'))
# '5 jours et 3 heures' (affiche 2 plus grandes unités)

# === INTERVALLES DE DATES ===

start = datetime(2024, 12, 20, 10, 0)
end = datetime(2024, 12, 25, 15, 30)

# Formater intervalle
print(format_interval(start, end, locale='fr_FR'))
# '20 – 25 déc. 2024'

# Avec format spécifique
print(format_interval(start, end, format='short', locale='fr_FR'))
# '20/12/2024 – 25/12/2024'

# Intervalle dans même jour
start_day = datetime(2024, 12, 25, 10, 0)
end_day = datetime(2024, 12, 25, 15, 30)
print(format_interval(start_day, end_day, locale='fr_FR'))
# '25 déc. 2024, 10:00 – 15:30'

# === NOMS DES JOURS ET MOIS ===

# Noms des jours
days = get_day_names(locale='fr_FR')
print(days)  # {0: 'lundi', 1: 'mardi', ...}

# Formats différents : wide, abbreviated, narrow
days_abbr = get_day_names('abbreviated', locale='fr_FR')
print(days_abbr)  # {0: 'lun.', 1: 'mar.', ...}

days_narrow = get_day_names('narrow', locale='fr_FR')
print(days_narrow)  # {0: 'L', 1: 'M', ...}

# Noms des mois
months = get_month_names(locale='fr_FR')
print(months)  # {1: 'janvier', 2: 'février', ...}

months_abbr = get_month_names('abbreviated', locale='fr_FR')
print(months_abbr)  # {1: 'janv.', 2: 'févr.', ...}

# Contexte : format vs stand-alone
months_standalone = get_month_names('wide', context='stand-alone', locale='fr_FR')

# Périodes du jour (AM/PM)
periods = get_period_names(locale='fr_FR')
print(periods)  # {'am': 'AM', 'pm': 'PM'}

# === DATES RELATIVES ===

from babel.dates import format_skeleton

# Format skeleton (pattern flexible)
print(format_skeleton('yMMMd', date(2024, 12, 25), locale='fr_FR'))
# '25 déc. 2024'


# ============================================================================
# [OK] FORMATAGE DES NOMBRES
# ============================================================================

from babel.numbers import (
    format_number, format_decimal, format_currency,
    format_percent, format_scientific,
    get_currency_name, get_currency_symbol,
    get_decimal_symbol, get_group_symbol
)

# === NOMBRES ENTIERS ===

num = 1234567

# Format par défaut
print(format_number(num, locale='fr_FR'))    # '1 234 567'
print(format_number(num, locale='en_US'))    # '1,234,567'
print(format_number(num, locale='de_DE'))    # '1.234.567'

# === NOMBRES DÉCIMAUX ===

decimal = 1234.5678

# Format décimal
print(format_decimal(decimal, locale='fr_FR'))
# '1 234,568' (arrondi à 3 décimales par défaut)

print(format_decimal(decimal, locale='en_US'))
# '1,234.568'

# Précision spécifique
print(format_decimal(decimal, format='#,##0.00', locale='fr_FR'))
# '1 234,57' (2 décimales)

print(format_decimal(decimal, format='#,##0.0000', locale='fr_FR'))
# '1 234,5678' (4 décimales)

# Format court (K, M, B)
big_num = 1234567890
print(format_decimal(big_num, format='#,##0.##', locale='fr_FR'))
# '1 234 567 890'

# Avec format compact (nécessite pattern spécial)
from babel.numbers import format_compact_decimal
print(format_compact_decimal(big_num, locale='fr_FR'))
# '1,2 Md' (milliards)

print(format_compact_decimal(1234567, locale='en_US'))
# '1.2M'

# === DEVISES ===

amount = 1234.56

# Format avec symbole
print(format_currency(amount, 'EUR', locale='fr_FR'))
# '1 234,56 €'

print(format_currency(amount, 'USD', locale='en_US'))
# '$1,234.56'

print(format_currency(amount, 'GBP', locale='en_GB'))
# '£1,234.56'

print(format_currency(amount, 'JPY', locale='ja_JP'))
# '¥1,235' (pas de décimales pour JPY)

# Format avec code devise
print(format_currency(amount, 'EUR', format='¤¤ #,##0.00', locale='fr_FR'))
# 'EUR 1 234,56'

# Format long
print(format_currency(amount, 'EUR', format_type='name', locale='fr_FR'))
# '1 234,56 euros'

# Obtenir nom de la devise
print(get_currency_name('EUR', locale='fr_FR'))    # 'euro'
print(get_currency_name('USD', locale='fr_FR'))    # 'dollar des États-Unis'
print(get_currency_name('EUR', count=2, locale='fr_FR'))  # 'euros'

# Obtenir symbole de devise
print(get_currency_symbol('EUR', locale='fr_FR'))  # '€'
print(get_currency_symbol('USD', locale='en_US'))  # '$'

# Arrondir selon règles de la devise
from babel.numbers import get_currency_precision
precision = get_currency_precision('JPY')
print(precision)  # 0 (pas de décimales)

# === POURCENTAGES ===

percent = 0.1234

print(format_percent(percent, locale='fr_FR'))
# '12 %'

print(format_percent(percent, locale='en_US'))
# '12%'

# Avec précision
print(format_percent(percent, format='#,##0.00%', locale='fr_FR'))
# '12,34 %'

print(format_percent(percent, format='#,##0.####%', locale='fr_FR'))
# '12,34 %'

# Valeur déjà en pourcentage
percent_value = 12.34
print(format_percent(percent_value / 100, locale='fr_FR'))
# '12 %'

# === NOTATION SCIENTIFIQUE ===

sci_num = 1234567.89

print(format_scientific(sci_num, locale='fr_FR'))
# '1,234568E6'

print(format_scientific(sci_num, locale='en_US'))
# '1.234568E6'

# Avec précision
print(format_scientific(sci_num, format='0.00E0', locale='fr_FR'))
# '1,23E6'

# === SYMBOLES NUMÉRIQUES ===

# Séparateur décimal
print(get_decimal_symbol(locale='fr_FR'))  # ','
print(get_decimal_symbol(locale='en_US'))  # '.'

# Séparateur de groupes
print(get_group_symbol(locale='fr_FR'))    # ' ' (espace)
print(get_group_symbol(locale='en_US'))    # ','

# === PATTERNS PERSONNALISÉS ===

# Patterns pour format_decimal et format_number :
# #     : Chiffre, omis si 0
# 0     : Chiffre, toujours affiché
# ,     : Séparateur de groupes
# .     : Séparateur décimal
# %     : Multiplie par 100 et ajoute %
# ‰     : Multiplie par 1000 et ajoute ‰
# ¤     : Symbole devise
# ¤¤    : Code devise (EUR, USD)
# ;     : Sépare pattern positif et négatif

# Exemples :
print(format_decimal(1234.5, format='000,000.00', locale='fr_FR'))
# '001 234,50'

print(format_decimal(-1234.5, format='#,##0.00;(#,##0.00)', locale='en_US'))
# '(1,234.50)' (négatif entre parenthèses)


# ============================================================================
# [OK] FORMATAGE DES UNITÉS
# ============================================================================

from babel.units import (
    format_unit,
    get_unit_name
)

# === UNITÉS DE MESURE ===

# Longueur
print(format_unit(5, 'length-meter', locale='fr_FR'))
# '5 mètres'

print(format_unit(5, 'length-meter', length='short', locale='fr_FR'))
# '5 m'

print(format_unit(5, 'length-meter', length='narrow', locale='fr_FR'))
# '5m'

# Volume
print(format_unit(2.5, 'volume-liter', locale='fr_FR'))
# '2,5 litres'

# Vitesse
print(format_unit(100, 'speed-kilometer-per-hour', locale='fr_FR'))
# '100 kilomètres par heure'

# Température
print(format_unit(25, 'temperature-celsius', locale='fr_FR'))
# '25 degrés Celsius'

# Masse
print(format_unit(50, 'mass-kilogram', locale='fr_FR'))
# '50 kilogrammes'

# Durée
print(format_unit(3, 'duration-hour', locale='fr_FR'))
# '3 heures'

# === UNITÉS COMPOSÉES ===

# Mètres par seconde
print(format_unit(10, 'speed-meter-per-second', locale='fr_FR'))
# '10 mètres par seconde'

# === OBTENIR NOMS D'UNITÉS ===

# Nom complet
print(get_unit_name('length-meter', locale='fr_FR'))
# 'mètres'

# Nom court
print(get_unit_name('length-meter', length='short', locale='fr_FR'))
# 'm'

# Pluralisation automatique
print(format_unit(1, 'length-meter', locale='fr_FR'))
# '1 mètre' (singulier)

print(format_unit(2, 'length-meter', locale='fr_FR'))
# '2 mètres' (pluriel)

# === UNITÉS DISPONIBLES ===
# length: meter, kilometer, centimeter, millimeter, mile, yard, foot, inch
# volume: liter, milliliter, gallon, fluid-ounce, cup
# mass: kilogram, gram, milligram, pound, ounce
# temperature: celsius, fahrenheit, kelvin
# speed: kilometer-per-hour, meter-per-second, mile-per-hour
# duration: year, month, week, day, hour, minute, second, millisecond
# area: square-meter, square-kilometer, hectare, acre
# digital: bit, byte, kilobyte, megabyte, gigabyte, terabyte


# ============================================================================
# [OK] PLURALISATION
# ============================================================================

from babel.plural import PluralRule

# === RÈGLES DE PLURALISATION ===

# Obtenir règle pour un locale
rule_fr = PluralRule({'one': 'n in 0..1', 'other': ''})
rule_en = PluralRule({'one': 'n is 1', 'other': ''})

# Français : 0 et 1 = singulier, reste = pluriel
# Anglais : 1 = singulier, reste = pluriel
# Russe : 3 formes (one, few, many)
# Arabe : 6 formes (zero, one, two, few, many, other)

# Obtenir forme plurielle pour un nombre
from babel import Locale

locale_fr = Locale.parse('fr_FR')
rule = locale_fr.plural_form

print(rule(0))   # 'one' (0 livre)
print(rule(1))   # 'one' (1 livre)
print(rule(2))   # 'other' (2 livres)
print(rule(10))  # 'other' (10 livres)

# Exemple avec anglais
locale_en = Locale.parse('en_US')
rule_en = locale_en.plural_form

print(rule_en(1))  # 'one' (1 book)
print(rule_en(2))  # 'other' (2 books)

# === UTILISATION DANS TRADUCTIONS ===

# Dans fichiers .po, format pluriel :
# msgid "I have {count} apple"
# msgid_plural "I have {count} apples"
# msgstr[0] "J'ai {count} pomme"
# msgstr[1] "J'ai {count} pommes"


# ============================================================================
# [OK] LISTES
# ============================================================================

from babel.lists import format_list

# === FORMATAGE DE LISTES ===

items = ['Alice', 'Bob', 'Charlie']

# Format standard
print(format_list(items, locale='fr_FR'))
# 'Alice, Bob et Charlie'

print(format_list(items, locale='en_US'))
# 'Alice, Bob, and Charlie'

# Styles différents
print(format_list(items, style='standard', locale='fr_FR'))
# 'Alice, Bob et Charlie'

print(format_list(items, style='or', locale='fr_FR'))
# 'Alice, Bob ou Charlie'

print(format_list(items, style='unit', locale='fr_FR'))
# 'Alice, Bob, Charlie' (pour unités)

# Liste courte
short_list = ['Alice', 'Bob']
print(format_list(short_list, locale='fr_FR'))
# 'Alice et Bob'

# Liste avec un élément
single = ['Alice']
print(format_list(single, locale='fr_FR'))
# 'Alice'

# Liste vide
print(format_list([], locale='fr_FR'))
# ''


# ============================================================================
# [OK] EXTRACTION DES MESSAGES À TRADUIRE
# ============================================================================

# Babel peut extraire automatiquement les chaînes à traduire de votre code

# === MARQUER LES CHAÎNES POUR TRADUCTION ===

# Méthode 1 : Fonction _ (gettext)
from gettext import gettext as _

message = _("Hello, World!")
message = _("Welcome to our application")

# Méthode 2 : Avec format
name = "Alice"
message = _("Hello, {name}!").format(name=name)

# Méthode 3 : Pluriel (ngettext)
from gettext import ngettext

count = 5
message = ngettext(
    "You have {count} message",
    "You have {count} messages",
    count
).format(count=count)

# Méthode 4 : Contexte (pgettext)
# Permet de différencier même mot dans contextes différents
from gettext import pgettext

# "Open" dans menu
menu_open = pgettext("menu", "Open")

# "Open" comme état
status_open = pgettext("status", "Open")

# Méthode 5 : Lazy translation (pour variables globales)
# Avec Flask-Babel ou Django
from flask_babel import lazy_gettext as _l

ERROR_MESSAGE = _l("An error occurred")  # Traduit au moment de l'utilisation

# === FICHIER babel.cfg ===

# Créer babel.cfg à la racine du projet
# Ce fichier indique à Babel où chercher les traductions

# Exemple de babel.cfg :
"""
[python: **.py]
[jinja2: **/templates/**.html]
encoding = utf-8

[javascript: **.js]
extract_messages = jQuery.i18n.prop
"""

# Options pour Python :
# encoding : Encodage des fichiers source
# keywords : Fonctions à extraire (défaut: _, gettext, ngettext, etc.)

# babel.cfg avancé :
"""
# Python files
[python: **.py]
encoding = utf-8

# Jinja2 templates (Flask)
[jinja2: **/templates/**.html]
encoding = utf-8
extensions = jinja2.ext.autoescape, jinja2.ext.with_

# Mako templates
[mako: **/templates/**.mako]

# JavaScript files
[javascript: **.js]
extract_messages = $t, i18n.t

# TypeScript files
[javascript: **.ts]
[javascript: **.tsx]
"""

# === EXTRAIRE LES MESSAGES ===

# Commande de base
# pybabel extract -o messages.pot .

# Avec babel.cfg
# pybabel extract -F babel.cfg -o translations/messages.pot .

# Options utiles :
# -F, --mapping-file : Fichier de configuration (babel.cfg)
# -o, --output-file : Fichier de sortie (.pot)
# --keywords : Fonctions supplémentaires à extraire
# --no-default-keywords : Ne pas utiliser keywords par défaut
# --add-comments : Ajouter commentaires précédant les traductions
# --strip-comments : Retirer les commentaires
# --charset : Charset du fichier de sortie
# --no-location : Ne pas inclure localisations du code source
# --omit-header : Ne pas inclure l'en-tête

# Exemples complets :

# Extraire avec commentaires
# pybabel extract -F babel.cfg -o messages.pot --add-comments=TRANSLATORS .

# Extraire avec keywords personnalisés
# pybabel extract -F babel.cfg -o messages.pot --keywords=translate --keywords=_t .

# Extraire plusieurs dossiers
# pybabel extract -F babel.cfg -o messages.pot myapp/ templates/

# === INITIALISER TRADUCTION POUR UNE LANGUE ===

# Créer nouveau fichier .po pour une langue
# pybabel init -i messages.pot -d translations -l fr

# Créer pour plusieurs langues
# pybabel init -i messages.pot -d translations -l fr
# pybabel init -i messages.pot -d translations -l es
# pybabel init -i messages.pot -d translations -l de

# Options :
# -i, --input-file : Fichier template (.pot)
# -d, --output-dir : Dossier de sortie (ex: translations/, locales/)
# -l, --locale : Code locale (ex: fr, es_MX, pt_BR)
# --domain : Domaine (défaut: messages)

# Structure créée :
# translations/
#   fr/
#     LC_MESSAGES/
#       messages.po

# === METTRE À JOUR TRADUCTIONS EXISTANTES ===

# Après modification du code, extraire nouveaux messages
# pybabel extract -F babel.cfg -o messages.pot .

# Mettre à jour tous les fichiers .po
# pybabel update -i messages.pot -d translations

# Mettre à jour une langue spécifique
# pybabel update -i messages.pot -d translations -l fr

# Options :
# --update-header-comment : Mettre à jour en-tête
# --previous : Garder anciennes traductions en commentaires
# --ignore-obsolete : Ignorer entrées obsolètes
# --no-fuzzy-matching : Désactiver matching approximatif

# Exemple complet :
# pybabel update -i translations/messages.pot -d translations --previous

# === COMPILER LES TRADUCTIONS ===

# Compiler tous les .po en .mo (binaire pour production)
# pybabel compile -d translations

# Compiler une langue spécifique
# pybabel compile -d translations -l fr

# Compiler avec statistiques
# pybabel compile -d translations --statistics

# Options :
# -f, --use-fuzzy : Inclure traductions marquées "fuzzy"
# --statistics : Afficher statistiques de traduction

# === ÉDITER FICHIERS .PO ===

# Fichiers .po sont des fichiers texte éditables

# Exemple de contenu messages.po :
"""
# French translations for MyApp.
msgid ""
msgstr ""
"Project-Id-Version: MyApp 1.0\n"
"Language: fr\n"
"Content-Type: text/plain; charset=UTF-8\n"

#: myapp/views.py:10
msgid "Hello, World!"
msgstr "Bonjour le monde !"

#: myapp/views.py:15
msgid "Welcome to our application"
msgstr "Bienvenue dans notre application"

#: myapp/views.py:20
#, python-format
msgid "Hello, %(name)s!"
msgstr "Bonjour, %(name)s !"

#: myapp/views.py:25
msgid "You have {count} message"
msgid_plural "You have {count} messages"
msgstr[0] "Vous avez {count} message"
msgstr[1] "Vous avez {count} messages"

# Contexte
#: myapp/menu.py:10
msgctxt "menu"
msgid "Open"
msgstr "Ouvrir"

#: myapp/status.py:5
msgctxt "status"
msgid "Open"
msgstr "Ouvert"

# Avec commentaire pour traducteur
#. TRANSLATORS: Greeting message shown on homepage
#: myapp/views.py:30
msgid "Welcome!"
msgstr "Bienvenue !"

# Traduction floue (à vérifier)
#: myapp/views.py:35
#, fuzzy
msgid "Recent changes"
msgstr "Changements récents"

# Obsolète (plus dans le code)
#~ msgid "Old message"
#~ msgstr "Ancien message"
"""

# === OUTILS D'ÉDITION .PO ===

# 1. Poedit (GUI, multiplateforme)
#    https://poedit.net/

# 2. Lokalize (KDE)
# 3. Gtranslator (GNOME)
# 4. VS Code avec extension "gettext"
# 5. Éditeur de texte simple

# Workflow typique :
# 1. pybabel extract -F babel.cfg -o messages.pot .
# 2. pybabel update -i messages.pot -d translations
# 3. Éditer fichiers .po (avec Poedit ou éditeur)
# 4. pybabel compile -d translations
# 5. Tester dans l'application


# ============================================================================
# [OK] INTÉGRATION AVEC FRAMEWORKS
# ============================================================================

# === FLASK ===

# Installer Flask-Babel
# pip install Flask-Babel

from flask import Flask, request
from flask_babel import Babel, gettext, ngettext, lazy_gettext

app = Flask(__name__)
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations'

babel = Babel(app)

# Déterminer locale depuis requête
@babel.localeselector
def get_locale():
    # 1. Depuis paramètre URL
    locale = request.args.get('lang')
    if locale:
        return locale
    
    # 2. Depuis session
    # return session.get('language')
    
    # 3. Depuis cookie
    # return request.cookies.get('language')
    
    # 4. Négociation automatique depuis Accept-Language
    return request.accept_languages.best_match(['en', 'fr', 'es', 'de'])

# Déterminer timezone
@babel.timezoneselector
def get_timezone():
    # Depuis session utilisateur
    user = getattr(g, 'user', None)
    if user:
        return user.timezone
    return 'UTC'

# Utiliser dans vues
@app.route('/')
def index():
    welcome = gettext('Welcome!')
    return welcome

@app.route('/messages/<int:count>')
def messages(count):
    msg = ngettext(
        'You have %(count)d message',
        'You have %(count)d messages',
        count
    )
    return msg % {'count': count}

# Lazy translation pour variables globales
FLASH_MESSAGES = {
    'success': lazy_gettext('Operation successful'),
    'error': lazy_gettext('An error occurred')
}

# Dans templates Jinja2
"""
<!-- templates/index.html -->
<h1>{{ _('Welcome!') }}</h1>
<p>{{ _('Hello, %(name)s!', name=user.name) }}</p>

<!-- Pluriel -->
<p>{{ ngettext('%(num)d item', '%(num)d items', items|length) }}</p>

<!-- Format date -->
<p>{{ format_datetime(date) }}</p>
<p>{{ format_date(date, format='full') }}</p>

<!-- Format nombre -->
<p>{{ format_number(1234.56) }}</p>
<p>{{ format_currency(99.99, 'EUR') }}</p>
"""

# babel.cfg pour Flask
"""
[python: **.py]
[jinja2: **/templates/**.html]
encoding = utf-8
extensions=jinja2.ext.autoescape,jinja2.ext.with_
"""

# Commandes pour Flask
# pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .
# pybabel init -i messages.pot -d translations -l fr
# pybabel compile -d translations

# === DJANGO ===

# Django a support i18n intégré utilisant Babel en arrière-plan

# settings.py
LANGUAGE_CODE = 'en'
LANGUAGES = [
    ('en', 'English'),
    ('fr', 'Français'),
    ('es', 'Español'),
]
LOCALE_PATHS = [
    BASE_DIR / 'locale',
]
USE_I18N = True
USE_L10N = True

# Middleware
MIDDLEWARE = [
    'django.middleware.locale.LocaleMiddleware',
    # ... autres middlewares
]

# Dans le code
from django.utils.translation import gettext, gettext_lazy, ngettext

# Traduction simple
message = gettext("Hello, World!")

# Traduction lazy (pour models, forms)
class Article(models.Model):
    title = models.CharField(
        max_length=200,
        verbose_name=gettext_lazy("Title")
    )

# Pluriel
messages = ngettext(
    "%(count)d message",
    "%(count)d messages",
    count
) % {'count': count}

# Dans templates Django
"""
{% load i18n %}

<h1>{% trans "Welcome!" %}</h1>
<p>{% blocktrans with name=user.name %}Hello, {{ name }}!{% endblocktrans %}</p>

<!-- Pluriel -->
{% blocktrans count counter=items|length %}
There is {{ counter }} item
{% plural %}
There are {{ counter }} items
{% endblocktrans %}
"""

# Commandes Django
# python manage.py makemessages -l fr
# python manage.py makemessages -l es
# python manage.py makemessages --all  # Toutes les langues configurées
# python manage.py compilemessages

# URLs avec préfixe langue
from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path('', views.index, name='index'),
    path('about/', views.about, name='about'),
)
# Génère : /en/about/, /fr/about/, etc.

# === FASTAPI ===

from fastapi import FastAPI, Request
from babel import negotiate_locale
import gettext

app = FastAPI()

# Configuration locales
LOCALES = {
    'en': gettext.translation('messages', 'locales', languages=['en']),
    'fr': gettext.translation('messages', 'locales', languages=['fr']),
}

def get_translator(locale: str):
    return LOCALES.get(locale, LOCALES['en']).gettext

@app.middleware("http")
async def add_locale(request: Request, call_next):
    # Déterminer locale
    accept_lang = request.headers.get('accept-language', 'en')
    locale = negotiate_locale([accept_lang], ['en', 'fr'])
    request.state.locale = locale or 'en'
    
    response = await call_next(request)
    return response

@app.get("/")
async def root(request: Request):
    _ = get_translator(request.state.locale)
    return {"message": _("Welcome to our API")}

# === BOTTLE ===

from bottle import Bottle, request, template
import gettext

app = Bottle()

def get_translation():
    locale = request.get_cookie('locale', 'en')
    try:
        trans = gettext.translation('messages', 'locales', languages=[locale])
        return trans.gettext
    except:
        return lambda x: x

@app.route('/')
def index():
    _ = get_translation()
    return template('index', welcome=_("Welcome!"))

# === PYRAMID ===

from pyramid.config import Configurator
from pyramid.i18n import TranslationStringFactory

_ = TranslationStringFactory('myapp')

def main(global_config, **settings):
    config = Configurator(settings=settings)
    config.add_translation_dirs('myapp:locale')
    config.set_locale_negotiator(locale_negotiator)
    return config.make_wsgi_app()

def locale_negotiator(request):
    locale = request.params.get('locale')
    if locale:
        return locale
    return request.accept_language.best_match(['en', 'fr', 'es'])


# ============================================================================
# [OK] UTILISATION AVANCÉE - GETTEXT NATIF
# ============================================================================

import gettext

# === INSTALLATION TRADUCTIONS ===

# Méthode 1 : Installation simple
translation = gettext.translation(
    'messages',              # Domaine (nom fichier sans .mo)
    localedir='locales',     # Dossier contenant traductions
    languages=['fr']         # Liste de langues par ordre de préférence
)

# Obtenir fonction de traduction
_ = translation.gettext

# Utiliser
print(_("Hello, World!"))  # "Bonjour le monde !"

# Méthode 2 : Installation globale
translation.install()
# Crée _ dans builtins, disponible partout
print(_("Hello!"))

# Méthode 3 : Avec fallback
try:
    translation = gettext.translation('messages', 'locales', languages=['fr'])
except FileNotFoundError:
    translation = gettext.NullTranslations()

_ = translation.gettext

# Méthode 4 : Plusieurs domaines
# Pour séparer traductions par module
main_trans = gettext.translation('messages', 'locales', languages=['fr'])
errors_trans = gettext.translation('errors', 'locales', languages=['fr'])

_ = main_trans.gettext
_e = errors_trans.gettext

print(_("Welcome"))        # Depuis messages.mo
print(_e("Error 404"))     # Depuis errors.mo

# === CLASSES DE TRADUCTION ===

# GNUTranslations : Lit fichiers .mo
trans = gettext.GNUTranslations(open('locales/fr/LC_MESSAGES/messages.mo', 'rb'))

# NullTranslations : Fallback, retourne messages originaux
null_trans = gettext.NullTranslations()

# === MÉTHODES DE TRADUCTION ===

translation = gettext.translation('messages', 'locales', languages=['fr'])

# gettext() ou _() : Traduction simple
msg = translation.gettext("Hello")

# ngettext() : Pluriel
msg = translation.ngettext(
    "%(num)d file",
    "%(num)d files",
    5
) % {'num': 5}

# pgettext() : Avec contexte
msg = translation.pgettext("menu", "Open")

# npgettext() : Contexte + pluriel
msg = translation.npgettext(
    "inbox",
    "%(num)d new message",
    "%(num)d new messages",
    3
) % {'num': 3}

# lgettext() : Retourne bytes au lieu de str
msg_bytes = translation.lgettext("Hello")

# === CHANGEMENT DE LOCALE DYNAMIQUE ===

class TranslationManager:
    def __init__(self, localedir='locales'):
        self.localedir = localedir
        self.current_locale = 'en'
        self.translations = {}
    
    def load_translation(self, locale):
        if locale not in self.translations:
            try:
                self.translations[locale] = gettext.translation(
                    'messages',
                    self.localedir,
                    languages=[locale]
                )
            except FileNotFoundError:
                self.translations[locale] = gettext.NullTranslations()
    
    def set_locale(self, locale):
        self.load_translation(locale)
        self.current_locale = locale
    
    def gettext(self, message):
        return self.translations[self.current_locale].gettext(message)
    
    def ngettext(self, singular, plural, n):
        return self.translations[self.current_locale].ngettext(singular, plural, n)

# Utilisation
tm = TranslationManager()
tm.set_locale('fr')
print(tm.gettext("Hello"))  # "Bonjour"

tm.set_locale('es')
print(tm.gettext("Hello"))  # "Hola"

# === CONTEXTE LOCAL AVEC CONTEXTVARS ===

from contextvars import ContextVar
import gettext

current_locale = ContextVar('locale', default='en')

def get_translation():
    locale = current_locale.get()
    return gettext.translation('messages', 'locales', languages=[locale])

def _(message):
    return get_translation().gettext(message)

# Dans différents contextes (async, threads)
current_locale.set('fr')
print(_("Hello"))  # "Bonjour"

current_locale.set('es')
print(_("Hello"))  # "Hola"


# ============================================================================
# [OK] GESTION DES DONNÉES CLDR
# ============================================================================

# CLDR (Common Locale Data Repository) : Base de données Unicode
# Babel utilise CLDR pour données de localisation

from babel import localedata
from babel.core import get_global

# === DONNÉES DISPONIBLES ===

locale = Locale.parse('fr_FR')

# Territoires
territories = locale.territories
print(territories.get('US'))  # 'États-Unis'
print(territories.get('FR'))  # 'France'

# Langues
languages = locale.languages
print(languages.get('en'))    # 'anglais'
print(languages.get('fr'))    # 'français'

# Scripts d'écriture
scripts = locale.scripts
print(scripts.get('Latn'))    # 'latin'
print(scripts.get('Arab'))    # 'arabe'

# Variantes
variants = locale.variants

# === PATTERNS DE FORMATAGE ===

# Patterns date/heure
date_formats = locale.date_formats
print(date_formats['short'])   # Pattern date courte
print(date_formats['medium'])  # Pattern date moyenne
print(date_formats['long'])    # Pattern date longue
print(date_formats['full'])    # Pattern date complète

time_formats = locale.time_formats
datetime_formats = locale.datetime_formats

# Patterns nombres
number_symbols = locale.number_symbols
print(number_symbols['decimal'])     # Séparateur décimal
print(number_symbols['group'])       # Séparateur milliers
print(number_symbols['minusSign'])   # Signe moins

# === INFORMATIONS CALENDRIER ===

# Premier jour de la semaine
first_week_day = locale.first_week_day
print(first_week_day)  # 0 = lundi pour fr_FR, 6 = dimanche pour en_US

# Jours weekend
weekend_start = locale.weekend_start
weekend_end = locale.weekend_end

# === MÉTADONNÉES ===

# Direction d'écriture
character_order = locale.character_order
print(character_order)  # 'left-to-right' ou 'right-to-left'

# === ACCÈS AUX DONNÉES CLDR BRUTES ===

# Charger données locale
data = localedata.load('fr_FR')

# Explorer structure
print(data.keys())

# Accès granulaire
date_data = data['dates']
number_data = data['numbers']


# ============================================================================
# [OK] SUPPORT MULTIPLE DOMAINES
# ============================================================================

# Séparer traductions par domaine (module, fonctionnalité)

# Structure :
# locales/
#   fr/
#     LC_MESSAGES/
#       messages.mo      # Traductions principales
#       errors.mo        # Messages d'erreur
#       admin.mo         # Interface admin
#       emails.mo        # Templates emails

# Extraction par domaine
# pybabel extract -F babel.cfg -o messages.pot myapp/
# pybabel extract -F babel_errors.cfg -o errors.pot myapp/errors/
# pybabel extract -F babel_admin.cfg -o admin.pot myapp/admin/

# Initialisation
# pybabel init -i messages.pot -d locales -l fr --domain=messages
# pybabel init -i errors.pot -d locales -l fr --domain=errors

# Utilisation
main_t = gettext.translation('messages', 'locales', languages=['fr'])
errors_t = gettext.translation('errors', 'locales', languages=['fr'])

_ = main_t.gettext
_e = errors_t.gettext

print(_("Welcome"))           # Depuis messages.mo
print(_e("File not found"))   # Depuis errors.mo

# Classe avec domaines multiples
class MultiDomainTranslation:
    def __init__(self, locale, localedir='locales'):
        self.locale = locale
        self.localedir = localedir
        self.domains = {}
    
    def load_domain(self, domain):
        if domain not in self.domains:
            try:
                self.domains[domain] = gettext.translation(
                    domain,
                    self.localedir,
                    languages=[self.locale]
                )
            except FileNotFoundError:
                self.domains[domain] = gettext.NullTranslations()
    
    def gettext(self, message, domain='messages'):
        self.load_domain(domain)
        return self.domains[domain].gettext(message)

# Utilisation
trans = MultiDomainTranslation('fr')
print(trans.gettext("Welcome", domain='messages'))
print(trans.gettext("Error 404", domain='errors'))


# ============================================================================
# [OK] FORMATAGE PERSONNALISÉ
# ============================================================================

from babel import dates, numbers
from babel.core import Locale

# === CRÉER FORMATS PERSONNALISÉS ===

locale = Locale.parse('fr_FR')

# Format date personnalisé
custom_date_format = "Le EEEE d MMMM yyyy 'à' HH'h'mm"
formatted = dates.format_datetime(
    datetime.now(),
    format=custom_date_format,
    locale=locale
)
# "Le mercredi 25 décembre 2024 à 14h30"

# Format nombre avec pattern
custom_number_format = "#,##0.00 'unités'"
formatted = numbers.format_decimal(
    1234.56,
    format=custom_number_format,
    locale=locale
)
# "1 234,56 unités"

# === FORMATEURS RÉUTILISABLES ===

class CustomFormatter:
    def __init__(self, locale):
        self.locale = Locale.parse(locale) if isinstance(locale, str) else locale
    
    def format_price(self, amount, currency='EUR'):
        return numbers.format_currency(
            amount,
            currency,
            locale=self.locale
        )
    
    def format_short_date(self, dt):
        return dates.format_date(dt, format='short', locale=self.locale)
    
    def format_long_datetime(self, dt):
        return dates.format_datetime(dt, format='full', locale=self.locale)
    
    def format_file_size(self, size_bytes):
        # Convertir en unité appropriée
        units = ['o', 'Ko', 'Mo', 'Go', 'To']
        unit_index = 0
        size = float(size_bytes)
        
        while size >= 1024 and unit_index < len(units) - 1:
            size /= 1024
            unit_index += 1
        
        return f"{numbers.format_decimal(size, format='#,##0.##', locale=self.locale)} {units[unit_index]}"

# Utilisation
fmt = CustomFormatter('fr_FR')
print(fmt.format_price(99.99))              # "99,99 €"
print(fmt.format_short_date(date.today()))  # "25/12/2024"
print(fmt.format_file_size(1536000))        # "1,46 Mo"


# ============================================================================
# [OK] VALIDATION ET PARSING
# ============================================================================

from babel.dates import parse_date, parse_time, parse_pattern
from babel.numbers import parse_number, parse_decimal

# === PARSER DATES ===

# Parser depuis string formatée
date_str = "25/12/2024"
parsed_date = parse_date(date_str, locale='fr_FR')
print(parsed_date)  # date(2024, 12, 25)

# Différents formats
date_str2 = "25 décembre 2024"
# parse_date supporte formats localisés basiques

# === PARSER NOMBRES ===

# Parser nombre selon locale
num_str = "1 234,56"
parsed = parse_decimal(num_str, locale='fr_FR')
print(parsed)  # Decimal('1234.56')

num_str_us = "1,234.56"
parsed_us = parse_decimal(num_str_us, locale='en_US')
print(parsed_us)  # Decimal('1234.56')

# Parser entier
int_str = "1 234 567"
parsed_int = parse_number(int_str, locale='fr_FR')
print(parsed_int)  # 1234567

# === PATTERNS ===

# Parser pattern date
pattern = dates.parse_pattern("dd/MM/yyyy")
print(pattern)  # DateTimePattern object

# Analyser pattern
print(pattern.format)  # Format string


# ============================================================================
# [OK] SUPPORT FUSEAUX HORAIRES
# ============================================================================

from babel.dates import (
    get_timezone,
    get_timezone_gmt,
    get_timezone_location,
    get_timezone_name,
    LOCALTZ
)
import pytz
from datetime import datetime

# === OBTENIR TIMEZONES ===

# Timezone par nom
tz_paris = get_timezone('Europe/Paris')
tz_tokyo = get_timezone('Asia/Tokyo')
tz_ny = get_timezone('America/New_York')

# Timezone local
local_tz = LOCALTZ

# === NOMS DE TIMEZONES ===

locale = Locale.parse('fr_FR')

# Nom complet
name = get_timezone_name(tz_paris, locale=locale)
print(name)  # "heure normale d'Europe centrale"

# Nom court
short_name = get_timezone_name(tz_paris, width='short', locale=locale)
print(short_name)  # "UTC+1"

# Avec horaire d'été/hiver
winter_dt = datetime(2024, 1, 15, tzinfo=tz_paris)
summer_dt = datetime(2024, 7, 15, tzinfo=tz_paris)

winter_name = get_timezone_name(tz_paris, datetime=winter_dt, locale=locale)
summer_name = get_timezone_name(tz_paris, datetime=summer_dt, locale=locale)

# === FORMAT GMT ===

gmt_format = get_timezone_gmt(tz_paris, locale=locale)
print(gmt_format)  # "UTC+01:00"

gmt_short = get_timezone_gmt(tz_paris, width='short', locale=locale)
print(gmt_short)  # "+0100"

# === LOCALISATION TIMEZONE ===

location = get_timezone_location(tz_paris, locale=locale)
print(location)  # "heure : Paris"

# === CONVERSION ET FORMATAGE ===

# Datetime avec timezone
dt_utc = datetime(2024, 12, 25, 12, 0, tzinfo=pytz.UTC)

# Convertir vers autre timezone
dt_paris = dt_utc.astimezone(tz_paris)

# Formater avec info timezone
formatted = dates.format_datetime(
    dt_paris,
    format='full',
    tzinfo=tz_paris,
    locale='fr_FR'
)
print(formatted)
# "mercredi 25 décembre 2024 à 13:00:00 heure normale d'Europe centrale"

# === LISTER TIMEZONES ===

# Tous les timezones disponibles
all_timezones = pytz.all_timezones
print(f"Nombre de timezones: {len(all_timezones)}")

# Timezones communs
common_timezones = pytz.common_timezones
print(f"Timezones communs: {len(common_timezones)}")

# Par région
for tz_name in pytz.common_timezones:
    if tz_name.startswith('Europe/'):
        print(tz_name)


# ============================================================================
# [OK] MIDDLEWARE ET CONTEXT PROCESSORS
# ============================================================================

# === MIDDLEWARE FLASK ===

from flask import Flask, g, request, session
from flask_babel import Babel
from werkzeug.middleware.dispatcher import DispatcherMiddleware

app = Flask(__name__)
babel = Babel(app)

class LocaleMiddleware:
    def __init__(self, app):
        self.app = app
    
    def __call__(self, environ, start_response):
        # Extraire locale depuis différentes sources
        locale = None
        
        # 1. Depuis URL (/fr/page)
        path = environ.get('PATH_INFO', '')
        if path.startswith('/fr/'):
            locale = 'fr'
        elif path.startswith('/en/'):
            locale = 'en'
        
        # 2. Depuis cookie
        if not locale:
            cookies = environ.get('HTTP_COOKIE', '')
            for cookie in cookies.split(';'):
                if cookie.strip().startswith('locale='):
                    locale = cookie.split('=')[1]
        
        # 3. Depuis Accept-Language header
        if not locale:
            accept_lang = environ.get('HTTP_ACCEPT_LANGUAGE', '')
            # Parser et choisir meilleur match
        
        # Stocker dans environ
        environ['locale'] = locale or 'en'
        
        return self.app(environ, start_response)

# Appliquer middleware
app.wsgi_app = LocaleMiddleware(app.wsgi_app)

# === CONTEXT PROCESSOR FLASK ===

@app.context_processor
def inject_locale():
    """Injecter fonctions i18n dans tous les templates"""
    return {
        'locale': get_locale(),
        'format_date': dates.format_date,
        'format_datetime': dates.format_datetime,
        'format_number': numbers.format_number,
        'format_currency': numbers.format_currency,
        'format_percent': numbers.format_percent,
    }

# Dans templates :
"""
<p>{{ format_date(article.published_date) }}</p>
<p>{{ format_currency(product.price, 'EUR') }}</p>
"""

# === DÉCORATEUR POUR VUES ===

from functools import wraps

def with_locale(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # Déterminer locale
        locale = request.args.get('lang') or session.get('locale') or 'en'
        session['locale'] = locale
        g.locale = locale
        
        # Installer traduction
        translation = gettext.translation(
            'messages',
            'locales',
            languages=[locale],
            fallback=True
        )
        translation.install()
        
        return f(*args, **kwargs)
    return decorated_function

@app.route('/products')
@with_locale
def products():
    title = _("Our Products")
    return render_template('products.html', title=title)

# === SÉLECTEUR LANGUE DANS TEMPLATE ===

"""
<!-- Sélecteur de langue -->
<div class="language-selector">
    <a href="{{ url_for('set_language', lang='en') }}">English</a>
    <a href="{{ url_for('set_language', lang='fr') }}">Français</a>
    <a href="{{ url_for('set_language', lang='es') }}">Español</a>
</div>
"""

@app.route('/set-language/<lang>')
def set_language(lang):
    session['locale'] = lang
    # Rediriger vers page précédente
    return redirect(request.referrer or url_for('index'))


# ============================================================================
# [OK] TESTS ET VALIDATION
# ============================================================================

import unittest
from babel import Locale
from babel.messages import Catalog
from babel.messages.pofile import read_po, write_po

# === TESTER TRADUCTIONS ===

class TestTranslations(unittest.TestCase):
    def setUp(self):
        self.locale_fr = Locale.parse('fr_FR')
        self.locale_en = Locale.parse('en_US')
    
    def test_locale_exists(self):
        """Vérifier que les locales existent"""
        from babel import localedata
        self.assertTrue(localedata.exists('fr_FR'))
        self.assertTrue(localedata.exists('en_US'))
    
    def test_date_formatting(self):
        """Tester formatage dates"""
        from babel.dates import format_date
        from datetime import date
        
        test_date = date(2024, 12, 25)
        
        # Français
        formatted_fr = format_date(test_date, locale='fr_FR')
        self.assertIn('déc', formatted_fr.lower())
        
        # Anglais
        formatted_en = format_date(test_date, locale='en_US')
        self.assertIn('dec', formatted_en.lower())
    
    def test_number_formatting(self):
        """Tester formatage nombres"""
        from babel.numbers import format_decimal
        
        number = 1234.56
        
        # Français (virgule décimale, espace milliers)
        formatted_fr = format_decimal(number, locale='fr_FR')
        self.assertIn(',', formatted_fr)
        self.assertIn(' ', formatted_fr)
        
        # Anglais (point décimal, virgule milliers)
        formatted_en = format_decimal(number, locale='en_US')
        self.assertIn('.', formatted_en)
        self.assertIn(',', formatted_en)
    
    def test_currency_formatting(self):
        """Tester formatage devises"""
        from babel.numbers import format_currency
        
        amount = 99.99
        
        formatted_eur = format_currency(amount, 'EUR', locale='fr_FR')
        self.assertIn('€', formatted_eur)
        
        formatted_usd = format_currency(amount, 'USD', locale='en_US')
        self.assertIn(', formatted_usd)
    
    def test_translation_coverage(self):
        """Vérifier couverture traductions"""
        import os
        from babel.messages.pofile import read_po
        
        po_file = 'locales/fr/LC_MESSAGES/messages.po'
        if os.path.exists(po_file):
            with open(po_file, 'rb') as f:
                catalog = read_po(f)
            
            total = 0
            translated = 0
            
            for message in catalog:
                if message.id and not message.id == '':
                    total += 1
                    if message.string:
                        translated += 1
            
            coverage = (translated / total * 100) if total > 0 else 0
            print(f"Couverture traduction: {coverage:.2f}%")
            
            # Assertion: au moins 80% traduit
            self.assertGreaterEqual(coverage, 80.0)
    
    def test_plural_forms(self):
        """Tester formes plurielles"""
        locale_fr = Locale.parse('fr_FR')
        locale_en = Locale.parse('en_US')
        
        # Français: 0 et 1 = singulier
        self.assertEqual(locale_fr.plural_form(0), 'one')
        self.assertEqual(locale_fr.plural_form(1), 'one')
        self.assertEqual(locale_fr.plural_form(2), 'other')
        
        # Anglais: 1 = singulier
        self.assertEqual(locale_en.plural_form(0), 'other')
        self.assertEqual(locale_en.plural_form(1), 'one')
        self.assertEqual(locale_en.plural_form(2), 'other')

# === VALIDER FICHIERS .PO ===

def validate_po_file(po_path):
    """Valider fichier .po"""
    errors = []
    
    with open(po_path, 'rb') as f:
        catalog = read_po(f)
    
    for message in catalog:
        # Vérifier messages vides
        if message.id and not message.string:
            errors.append(f"Message non traduit: {message.id}")
        
        # Vérifier variables cohérentes
        if message.id and message.string:
            # Extraire variables {var} ou %(var)s
            import re
            vars_source = set(re.findall(r'{(\w+)}|%\((\w+)\)', message.id))
            vars_trans = set(re.findall(r'{(\w+)}|%\((\w+)\)', message.string))
            
            if vars_source != vars_trans:
                errors.append(f"Variables incohérentes dans: {message.id}")
        
        # Vérifier messages fuzzy
        if message.fuzzy:
            errors.append(f"Message fuzzy: {message.id}")
    
    return errors

# Utilisation
errors = validate_po_file('locales/fr/LC_MESSAGES/messages.po')
if errors:
    print(f"Erreurs trouvées: {len(errors)}")
    for error in errors[:10]:  # Afficher 10 premières
        print(f"  - {error}")

# === SCRIPT DE VALIDATION COMPLET ===

#!/usr/bin/env python
"""
Script de validation des traductions
Usage: python validate_translations.py
"""

import os
import sys
from babel.messages.pofile import read_po

def validate_catalog(catalog, locale):
    """Valider un catalogue de traductions"""
    issues = {
        'untranslated': [],
        'fuzzy': [],
        'variable_mismatch': [],
        'empty': []
    }
    
    total = 0
    translated = 0
    
    for message in catalog:
        if not message.id or message.id == '':
            continue
        
        total += 1
        
        # Message non traduit
        if not message.string:
            issues['untranslated'].append(message.id)
            continue
        
        translated += 1
        
        # Message vide
        if message.string.strip() == '':
            issues['empty'].append(message.id)
        
        # Message fuzzy
        if message.fuzzy:
            issues['fuzzy'].append(message.id)
        
        # Variables incohérentes
        import re
        vars_id = set(re.findall(r'\{(\w+)\}|%\((\w+)\)s', message.id))
        vars_str = set(re.findall(r'\{(\w+)\}|%\((\w+)\)s', message.string))
        
        vars_id = {v for t in vars_id for v in t if v}
        vars_str = {v for t in vars_str for v in t if v}
        
        if vars_id != vars_str:
            issues['variable_mismatch'].append({
                'message': message.id,
                'expected': vars_id,
                'found': vars_str
            })
    
    coverage = (translated / total * 100) if total > 0 else 0
    
    return {
        'total': total,
        'translated': translated,
        'coverage': coverage,
        'issues': issues
    }

def main():
    locales_dir = 'locales'
    
    if not os.path.exists(locales_dir):
        print(f"Erreur: Dossier {locales_dir} introuvable")
        sys.exit(1)
    
    all_valid = True
    
    # Parcourir tous les locales
    for locale in os.listdir(locales_dir):
        po_path = os.path.join(locales_dir, locale, 'LC_MESSAGES', 'messages.po')
        
        if not os.path.exists(po_path):
            continue
        
        print(f"\n{'='*60}")
        print(f"Validation: {locale}")
        print('='*60)
        
        with open(po_path, 'rb') as f:
            catalog = read_po(f)
        
        result = validate_catalog(catalog, locale)
        
        print(f"Total messages: {result['total']}")
        print(f"Traduits: {result['translated']}")
        print(f"Couverture: {result['coverage']:.2f}%")
        
        # Afficher problèmes
        issues = result['issues']
        
        if issues['untranslated']:
            print(f"\n[ATTENTION] Messages non traduits: {len(issues['untranslated'])}")
            for msg in issues['untranslated'][:5]:
                print(f"  - {msg}")
            if len(issues['untranslated']) > 5:
                print(f"  ... et {len(issues['untranslated']) - 5} autres")
            all_valid = False
        
        if issues['fuzzy']:
            print(f"\n[ATTENTION] Messages fuzzy: {len(issues['fuzzy'])}")
            for msg in issues['fuzzy'][:5]:
                print(f"  - {msg}")
            all_valid = False
        
        if issues['empty']:
            print(f"\n[ATTENTION] Messages vides: {len(issues['empty'])}")
            for msg in issues['empty'][:5]:
                print(f"  - {msg}")
            all_valid = False
        
        if issues['variable_mismatch']:
            print(f"\n[ATTENTION] Variables incohérentes: {len(issues['variable_mismatch'])}")
            for issue in issues['variable_mismatch'][:3]:
                print(f"  - {issue['message']}")
                print(f"    Attendu: {issue['expected']}")
                print(f"    Trouvé: {issue['found']}")
            all_valid = False
        
        if not any(issues.values()):
            print("\n[OK] Aucun problème détecté")
    
    print(f"\n{'='*60}")
    if all_valid:
        print("[OK] Toutes les traductions sont valides")
        sys.exit(0)
    else:
        print("[X] Des problèmes ont été détectés")
        sys.exit(1)

if __name__ == '__main__':
    main()


# ============================================================================
# [OK] OPTIMISATION ET PERFORMANCE
# ============================================================================

# === MISE EN CACHE ===

from functools import lru_cache
from babel import Locale
from babel.dates import format_date, format_datetime
from babel.numbers import format_decimal, format_currency

class CachedFormatter:
    """Formatter avec mise en cache des locales"""
    
    def __init__(self):
        self._locale_cache = {}
    
    @lru_cache(maxsize=128)
    def get_locale(self, locale_str):
        """Cache des objets Locale"""
        return Locale.parse(locale_str)
    
    @lru_cache(maxsize=512)
    def format_date_cached(self, date_str, locale_str, format='medium'):
        """Cache des dates formatées"""
        from datetime import datetime
        dt = datetime.fromisoformat(date_str)
        return format_date(dt, format=format, locale=locale_str)
    
    @lru_cache(maxsize=512)
    def format_number_cached(self, number, locale_str):
        """Cache des nombres formatés"""
        return format_decimal(number, locale=locale_str)

formatter = CachedFormatter()

# Utilisation
print(formatter.format_date_cached('2024-12-25', 'fr_FR'))
print(formatter.format_number_cached(1234.56, 'fr_FR'))

# === COMPILATION .MO ===

# Toujours compiler les .po en .mo pour production
# Les .mo sont binaires et beaucoup plus rapides à charger

# Compiler tous les locales
# pybabel compile -d locales

# Vérifier taille des fichiers
import os

def compare_po_mo_sizes(locales_dir='locales'):
    """Comparer tailles .po vs .mo"""
    for locale in os.listdir(locales_dir):
        po_path = os.path.join(locales_dir, locale, 'LC_MESSAGES', 'messages.po')
        mo_path = os.path.join(locales_dir, locale, 'LC_MESSAGES', 'messages.mo')
        
        if os.path.exists(po_path) and os.path.exists(mo_path):
            po_size = os.path.getsize(po_path)
            mo_size = os.path.getsize(mo_path)
            reduction = (1 - mo_size / po_size) * 100
            
            print(f"{locale}:")
            print(f"  .po: {po_size:,} bytes")
            print(f"  .mo: {mo_size:,} bytes")
            print(f"  Réduction: {reduction:.1f}%")

# === LAZY LOADING ===

class LazyTranslation:
    """Chargement paresseux des traductions"""
    
    def __init__(self, localedir='locales'):
        self.localedir = localedir
        self._translations = {}
        self._current_locale = None
    
    def set_locale(self, locale):
        """Définir locale actuel"""
        self._current_locale = locale
    
    def _get_translation(self, locale):
        """Charger traduction si nécessaire"""
        if locale not in self._translations:
            try:
                self._translations[locale] = gettext.translation(
                    'messages',
                    self.localedir,
                    languages=[locale]
                )
            except FileNotFoundError:
                self._translations[locale] = gettext.NullTranslations()
        
        return self._translations[locale]
    
    def gettext(self, message):
        """Traduire message"""
        if not self._current_locale:
            return message
        
        trans = self._get_translation(self._current_locale)
        return trans.gettext(message)
    
    def clear_cache(self):
        """Vider cache (utile après mise à jour traductions)"""
        self._translations.clear()

# === PRÉCHARGEMENT POUR PERFORMANCE ===

def preload_translations(locales, localedir='locales'):
    """Précharger toutes les traductions au démarrage"""
    translations = {}
    
    for locale in locales:
        try:
            translations[locale] = gettext.translation(
                'messages',
                localedir,
                languages=[locale]
            )
            print(f"[OK] Chargé: {locale}")
        except FileNotFoundError:
            print(f"[X] Introuvable: {locale}")
            translations[locale] = gettext.NullTranslations()
    
    return translations

# Au démarrage de l'application
SUPPORTED_LOCALES = ['en', 'fr', 'es', 'de']
translations = preload_translations(SUPPORTED_LOCALES)

# === BENCHMARK ===

import time

def benchmark_formatting():
    """Benchmarker différentes opérations"""
    from babel import Locale
    from babel.dates import format_date
    from babel.numbers import format_decimal
    from datetime import date
    
    locale = Locale.parse('fr_FR')
    test_date = date(2024, 12, 25)
    test_number = 1234.56
    iterations = 10000
    
    # Test formatage date
    start = time.time()
    for _ in range(iterations):
        format_date(test_date, locale=locale)
    date_time = time.time() - start
    
    # Test formatage nombre
    start = time.time()
    for _ in range(iterations):
        format_decimal(test_number, locale=locale)
    number_time = time.time() - start
    
    print(f"Formatage date: {date_time:.4f}s ({iterations/date_time:.0f} ops/s)")
    print(f"Formatage nombre: {number_time:.4f}s ({iterations/number_time:.0f} ops/s)")

benchmark_formatting()


# ============================================================================
# [OK] CATALOGUE DE MESSAGES PROGRAMMATIQUE
# ============================================================================

from babel.messages import Catalog
from babel.messages.pofile import write_po, read_po
from babel.messages.mofile import write_mo
from datetime import datetime

# === CRÉER CATALOGUE ===

# Créer nouveau catalogue
catalog = Catalog(
    project='MyProject',
    version='1.0',
    copyright_holder='My Company',
    msgid_bugs_address='bugs@example.com',
    creation_date=datetime.now(),
    revision_date=datetime.now(),
    language='fr',
    locale='fr_FR'
)

# Ajouter messages
catalog.add('Hello, World!', string='Bonjour le monde !')
catalog.add('Welcome', string='Bienvenue')

# Ajouter message avec contexte
catalog.add('Open', string='Ouvrir', context='menu')
catalog.add('Open', string='Ouvert', context='status')

# Ajouter message pluriel
catalog.add(
    ('You have %(count)d message', 'You have %(count)d messages'),
    string=('Vous avez %(count)d message', 'Vous avez %(count)d messages')
)

# Ajouter avec métadonnées
catalog.add(
    'Save',
    string='Enregistrer',
    locations=[('views.py', 42)],
    flags=['python-format'],
    user_comments=['Button label']
)

# === SAUVEGARDER CATALOGUE ===

# Sauvegarder en .po
with open('messages.po', 'wb') as f:
    write_po(f, catalog, width=80)

# Sauvegarder en .mo
with open('messages.mo', 'wb') as f:
    write_mo(f, catalog)

# === LIRE CATALOGUE ===

# Lire depuis .po
with open('messages.po', 'rb') as f:
    catalog = read_po(f)

# Parcourir messages
for message in catalog:
    if message.id:
        print(f"ID: {message.id}")
        print(f"String: {message.string}")
        print(f"Context: {message.context}")
        print(f"Locations: {message.locations}")
        print()

# === MODIFIER CATALOGUE ===

# Charger catalogue existant
with open('locales/fr/LC_MESSAGES/messages.po', 'rb') as f:
    catalog = read_po(f)

# Modifier message
for message in catalog:
    if message.id == 'Hello':
        message.string = 'Salut'
        message.flags.add('fuzzy')

# Ajouter nouveau message
catalog.add('New message', string='Nouveau message')

# Supprimer message obsolète
catalog.delete('Old message')

# Sauvegarder modifications
with open('locales/fr/LC_MESSAGES/messages.po', 'wb') as f:
    write_po(f, catalog)

# === FUSIONNER CATALOGUES ===

def merge_catalogs(base_catalog, update_catalog):
    """Fusionner deux catalogues"""
    for message in update_catalog:
        if message.id and message.id not in base_catalog:
            base_catalog.add(
                message.id,
                string=message.string,
                locations=message.locations,
                flags=message.flags,
                user_comments=message.user_comments,
                context=message.context
            )
    
    return base_catalog

# === STATISTIQUES CATALOGUE ===

def catalog_stats(catalog):
    """Obtenir statistiques d'un catalogue"""
    total = 0
    translated = 0
    fuzzy = 0
    untranslated = 0
    
    for message in catalog:
        if not message.id or message.id == '':
            continue
        
        total += 1
        
        if message.string:
            translated += 1
            if message.fuzzy:
                fuzzy += 1
        else:
            untranslated += 1
    
    return {
        'total': total,
        'translated': translated,
        'fuzzy': fuzzy,
        'untranslated': untranslated,
        'coverage': (translated / total * 100) if total > 0 else 0
    }

# Utilisation
with open('locales/fr/LC_MESSAGES/messages.po', 'rb') as f:
    catalog = read_po(f)

stats = catalog_stats(catalog)
print(f"Total: {stats['total']}")
print(f"Traduits: {stats['translated']}")
print(f"Fuzzy: {stats['fuzzy']}")
print(f"Non traduits: {stats['untranslated']}")
print(f"Couverture: {stats['coverage']:.2f}%")


# ============================================================================
# [OK] OUTILS ET UTILITAIRES
# ============================================================================

# === CONVERSION ENTRE FORMATS ===

# JSON vers PO
import json
from babel.messages import Catalog

def json_to_po(json_file, po_file, locale='fr'):
    """Convertir JSON vers PO"""
    with open(json_file, 'r', encoding='utf-8') as f:
        translations = json.load(f)
    
    catalog = Catalog(locale=locale)
    
    def add_from_dict(data, prefix=''):
        for key, value in data.items():
            full_key = f"{prefix}.{key}" if prefix else key
            
            if isinstance(value, dict):
                add_from_dict(value, full_key)
            else:
                catalog.add(full_key, string=value)
    
    add_from_dict(translations)
    
    with open(po_file, 'wb') as f:
        write_po(f, catalog)

# PO vers JSON
def po_to_json(po_file, json_file):
    """Convertir PO vers JSON"""
    with open(po_file, 'rb') as f:
        catalog = read_po(f)
    
    translations = {}
    
    for message in catalog:
        if message.id and message.string:
            # Gérer clés imbriquées (ex: "user.name")
            parts = message.id.split('.')
            current = translations
            
            for part in parts[:-1]:
                if part not in current:
                    current[part] = {}
                current = current[part]
            
            current[parts[-1]] = message.string
    
    with open(json_file, 'w', encoding='utf-8') as f:
        json.dump(translations, f, ensure_ascii=False, indent=2)

# === DÉTECTION LANGUE ===

from babel import Locale

def detect_locale_from_text(text):
    """Détecter langue depuis texte (basique)"""
    # Mots caractéristiques par langue
    indicators = {
        'fr': ['le', 'la', 'les', 'de', 'et', 'un', 'une', 'dans'],
        'en': ['the', 'a', 'an', 'and', 'or', 'in', 'on', 'at'],
        'es': ['el', 'la', 'los', 'las', 'de', 'y', 'en', 'un'],
        'de': ['der', 'die', 'das', 'und', 'in', 'von', 'zu', 'mit'],
    }
    
    words = text.lower().split()
    scores = {lang: 0 for lang in indicators}
    
    for word in words:
        for lang, keywords in indicators.items():
            if word in keywords:
                scores[lang] += 1
    
    detected = max(scores, key=scores.get)
    return detected if scores[detected] > 0 else None

# Utilisation
text = "Bonjour, comment allez-vous aujourd'hui ?"
detected = detect_locale_from_text(text)
print(f"Langue détectée: {detected}")  # 'fr'

# === GÉNÉRATION RAPPORT ===

def generate_translation_report(locales_dir='locales', output='report.html'):
    """Générer rapport HTML des traductions"""
    import os
    from datetime import datetime
    
    html = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>Rapport de Traduction</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 20px; }
            table { border-collapse: collapse; width: 100%; margin: 20px 0; }
            th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
            th { background-color: #4CAF50; color: white; }
            tr:nth-child(even) { background-color: #f2f2f2; }
            .coverage { font-weight: bold; }
            .good { color: green; }
            .warning { color: orange; }
            .bad { color: red; }
        </style>
    </head>
    <body>
        <h1>Rapport de Traduction</h1>
        <p>Généré le: """ + datetime.now().strftime('%Y-%m-%d %H:%M:%S') + """</p>
        <table>
            <tr>
                <th>Locale</th>
                <th>Total</th>
                <th>Traduits</th>
                <th>Fuzzy</th>
                <th>Non traduits</th>
                <th>Couverture</th>
            </tr>
    """
    
    for locale in sorted(os.listdir(locales_dir)):
        po_path = os.path.join(locales_dir, locale, 'LC_MESSAGES', 'messages.po')
        
        if not os.path.exists(po_path):
            continue
        
        with open(po_path, 'rb') as f:
            catalog = read_po(f)
        
        stats = catalog_stats(catalog)
        coverage = stats['coverage']
        
        # Classe CSS selon couverture
        coverage_class = 'good' if coverage >= 90 else 'warning' if coverage >= 70 else 'bad'
        
        html += f"""
            <tr>
                <td><strong>{locale}</strong></td>
                <td>{stats['total']}</td>
                <td>{stats['translated']}</td>
                <td>{stats['fuzzy']}</td>
                <td>{stats['untranslated']}</td>
                <td class="coverage {coverage_class}">{coverage:.2f}%</td>
            </tr>
        """
    
    html += """
        </table>
    </body>
    </html>
    """
    
    with open(output, 'w', encoding='utf-8') as f:
        f.write(html)
    
    print(f"Rapport généré: {output}")

# Utilisation
generate_translation_report()

# === SYNCHRONISATION TRADUCTIONS ===

def sync_translations_from_template(pot_file, locales_dir='locales'):
    """Synchroniser toutes les traductions depuis template"""
    import os
    import subprocess
    
    if not os.path.exists(pot_file):
        print(f"Erreur: {pot_file} introuvable")
        return
    
    for locale in os.listdir(locales_dir):
        po_path = os.path.join(locales_dir, locale, 'LC_MESSAGES', 'messages.po')
        
        if os.path.exists(po_path):
            print(f"Mise à jour: {locale}")
            subprocess.run([
                'pybabel', 'update',
                '-i', pot_file,
                '-d', locales_dir,
                '-l', locale
            ])
    
    print("Synchronisation terminée")

# Utilisation
sync_translations_from_template('messages.pot')


# ============================================================================
# [OK] EXEMPLES COMPLETS D'APPLICATIONS
# ============================================================================

# === EXEMPLE 1: APPLICATION FLASK COMPLÈTE ===

"""
# app.py
from flask import Flask, render_template, request, session, redirect, url_for
from flask_babel import Babel, gettext, lazy_gettext as _l
from datetime import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['BABEL_DEFAULT_LOCALE'] = 'en'
app.config['BABEL_SUPPORTED_LOCALES'] = ['en', 'fr', 'es', 'de']
app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations'

babel = Babel(app)

@babel.localeselector
def get_locale():
    # 1. Depuis URL
    if 'lang' in request.args:
        session['locale'] = request.args.get('lang')
    
    # 2. Depuis session
    if 'locale' in session:
        return session['locale']
    
    # 3. Depuis navigateur
    return request.accept_languages.best_match(
        app.config['BABEL_SUPPORTED_LOCALES']
    )

@app.route('/')
def index():
    products = [
        {'name': 'Product 1', 'price': 29.99},
        {'name': 'Product 2', 'price': 49.99},
        {'name': 'Product 3', 'price': 99.99},
    ]
    
    return render_template(
        'index.html',
        products=products,
        current_time=datetime.now()
    )

@app.route('/set-language/<lang>')
def set_language(lang):
    if lang in app.config['BABEL_SUPPORTED_LOCALES']:
        session['locale'] = lang
    return redirect(request.referrer or url_for('index'))

if __name__ == '__main__':
    app.run(debug=True)
"""

# templates/index.html
"""
<!DOCTYPE html>
<html>
<head>
    <title>{{ _('My Shop') }}</title>
    <meta charset="utf-8">
</head>
<body>
    <!-- Sélecteur de langue -->
    <div class="language-selector">
        <a href="{{ url_for('set_language', lang='en') }}">English</a>
        <a href="{{ url_for('set_language', lang='fr') }}">Français</a>
        <a href="{{ url_for('set_language', lang='es') }}">Español</a>
        <a href="{{ url_for('set_language', lang='de') }}">Deutsch</a>
    </div>
    
    <h1>{{ _('Welcome to our shop!') }}</h1>
    
    <p>
        {{ _('Current time:') }} 
        {{ format_datetime(current_time, format='full') }}
    </p>
    
    <h2>{{ _('Our Products') }}</h2>
    
    <table>
        <tr>
            <th>{{ _('Product') }}</th>
            <th>{{ _('Price') }}</th>
        </tr>
        {% for product in products %}
        <tr>
            <td>{{ product.name }}</td>
            <td>{{ format_currency(product.price, 'EUR') }}</td>
        </tr>
        {% endfor %}
    </table>
    
    <p>
        {{ ngettext(
            'You have