Files
TieMeasureFlow/client/I18N_SETUP.md
T
Adriano dd2ebf863a feat: FASE 7 - Polish & Testing (security, i18n, test suite, docs)
Security hardening: CORS lockdown, rate limiting middleware con sliding
window e eviction IP stale, security headers (CSP, HSTS, X-Frame-Options),
session cookie hardening, filename sanitization upload.

i18n completion: internazionalizzati barcode.js e csv-export.js con bridge
window.BARCODE_I18N/CSV_I18N, aggiornati .po IT/EN con 27 nuove stringhe.

Tablet UX: touch target 44px per dispositivi coarse pointer.

Test suite: 101 test totali (76 server + 25 client), copertura completa
di tutti i router API, autenticazione, ruoli, CRUD, SPC, file upload,
security integration. Infrastruttura SQLite async in-memory con fixtures.

Fix critici: MissingGreenlet in recipe_service (selectinload eager),
route ordering tasks.py, auth_service bcrypt diretto, Measurement.id
Integer per SQLite.

Documentazione: API.md (riferimento completo 40+ endpoint),
DEPLOYMENT.md (guida produzione con Docker/Nginx/SSL),
USER_GUIDE.md (manuale utente per ruolo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:10:24 +01:00

7.0 KiB

TieMeasureFlow i18n Setup Guide

Overview

TieMeasureFlow supports complete internationalization (i18n) with:

  • Server-side: Flask-Babel for Python/Jinja2 templates
  • Client-side: alpinejs-i18n for JavaScript/Alpine.js
  • Supported languages: Italian (IT - default) and English (EN)

Directory Structure

client/
├── translations/               # Flask-Babel translations
│   ├── babel.cfg              # Extraction config
│   ├── it/LC_MESSAGES/
│   │   ├── messages.po        # Italian strings
│   │   └── messages.mo        # Compiled binary (generated)
│   └── en/LC_MESSAGES/
│       ├── messages.po        # English translations
│       └── messages.mo        # Compiled binary (generated)
├── static/js/locales/         # Alpine.js i18n
│   ├── it.json                # Italian client-side strings
│   └── en.json                # English client-side strings
└── compile_translations.py    # Utility to compile .po → .mo

How It Works

Language Selection

  1. User session: Language stored in session["language"] (persistent)
  2. Browser fallback: Uses Accept-Language header if no session
  3. Default: Italian (IT)

Server-Side (Flask-Babel)

In Python code:

from flask_babel import gettext as _

flash(_("Profilo aggiornato con successo"))

In Jinja2 templates:

<h1>{{ _('Accedi al sistema') }}</h1>

{# With variables #}
<p>{{ _('Benvenuto, %(name)s!', name=user.display_name) }}</p>

Client-Side (Alpine.js i18n)

Load i18n in base template:

<script src="/static/js/lib/alpinejs-i18n.min.js"></script>
<script>
  // Initialize i18n with current language
  Alpine.plugin(AlpineI18n);
  Alpine.store('i18n', {
    locale: '{{ current_language }}',
    fallbackLocale: 'it',
    messages: {}
  });

  // Load locale files
  fetch('/static/js/locales/{{ current_language }}.json')
    .then(r => r.json())
    .then(data => Alpine.store('i18n').messages['{{ current_language }}'] = data);
</script>

Use in templates:

<button @click="submit" x-text="$t('numpad.submit')"></button>

<p x-text="$t('auth.welcome', {name: userName})"></p>

Workflow

1. Add New Strings

Python/Templates:

  1. Wrap strings with _() or {{ _() }}
  2. Extract strings: pybabel extract -F translations/babel.cfg -o translations/messages.pot .
  3. Update catalogs: pybabel update -i translations/messages.pot -d translations
  4. Edit translations/*/LC_MESSAGES/messages.po files
  5. Compile: python compile_translations.py

JavaScript/Alpine:

  1. Add keys to static/js/locales/it.json (Italian strings)
  2. Add translations to static/js/locales/en.json
  3. No compilation needed (loaded at runtime)

2. Compile Translations

After editing .po files:

cd client
python compile_translations.py

This generates .mo binary files that Flask-Babel uses at runtime.

3. Language Switching

Endpoint: GET /set-language/<lang>

Example:

<a href="{{ url_for('set_language', lang='en') }}">English</a>
<a href="{{ url_for('set_language', lang='it') }}">Italiano</a>

With Alpine.js:

<button @click="$store.i18n.locale = 'en'; window.location.href='/set-language/en'">
  English
</button>

Translation Keys

Server-Side (messages.po)

56 strings covering:

  • Login: "Accedi al sistema", "Credenziali non valide", etc.
  • Navbar: "Misure", "Ricette", "Statistiche", etc.
  • Profile: "Profilo Utente", "Salva Modifiche", etc.
  • Flash messages: "Benvenuto, %(name)s!", "Logout effettuato", etc.
  • Common UI: "Caricamento...", "Salva", "Annulla", "Conferma", etc.
  • Measurements: "Conforme", "Non Conforme", etc.

Client-Side (locales/*.json)

Structured keys:

{
  "numpad": { "title", "clear", "submit", "decimal", "close" },
  "measurement": { "pass", "warning", "fail", "value", "next" },
  "common": { "loading", "save", "cancel", "confirm", "delete", "edit", ... },
  "theme": { "light", "dark", "toggle" },
  "language": { "switch", "it", "en" },
  "auth": { "login", "username", "password", "signIn", ... },
  "nav": { "measurements", "recipes", "statistics", ... },
  "profile": { "title", "displayName", "language", "theme", ... }
}

Configuration

config.py must define:

BABEL_DEFAULT_LOCALE = 'it'
BABEL_TRANSLATION_DIRECTORIES = 'translations'
LANGUAGES = {
    'it': 'Italiano',
    'en': 'English'
}

app.py provides:

  • get_locale(): Language selector function
  • /set-language/<lang>: Language switch endpoint
  • inject_globals(): Exposes current_language and languages to templates

Best Practices

  1. Always use Italian as msgid: Italian strings are the canonical keys
  2. Keep client/server keys synced: Use similar naming across .po and .json
  3. Test both languages: Switch languages and verify all pages
  4. Recompile after editing: Run compile_translations.py after changing .po files
  5. Use variables for dynamic content: _('Welcome, %(name)s!', name=user) or $t('auth.welcome', {name})
  6. Avoid hardcoded strings: Always use translation functions

Troubleshooting

Translations not appearing:

  • Verify .mo files exist: find translations -name "*.mo"
  • Check session language: session["language"] or browser console
  • Recompile: python compile_translations.py
  • Restart Flask server

Client-side i18n not working:

  • Check browser console for fetch errors
  • Verify JSON files exist: static/js/locales/{it,en}.json
  • Check Alpine.js i18n plugin is loaded
  • Verify Alpine.store('i18n') is initialized

Missing translations show as keys:

  • Add missing keys to .po or .json files
  • Recompile .po files if server-side
  • Reload page if client-side

Commands Reference

# Extract translatable strings from code
pybabel extract -F translations/babel.cfg -o translations/messages.pot .

# Initialize new language (first time)
pybabel init -i translations/messages.pot -d translations -l en

# Update existing catalogs with new strings
pybabel update -i translations/messages.pot -d translations

# Compile .po to .mo (required after editing)
python compile_translations.py

# Check translation statistics
msgfmt --statistics translations/it/LC_MESSAGES/messages.po
msgfmt --statistics translations/en/LC_MESSAGES/messages.po

Future Expansion

To add a new language (e.g., German):

  1. Server-side:

    pybabel init -i translations/messages.pot -d translations -l de
    # Edit translations/de/LC_MESSAGES/messages.po
    python compile_translations.py
    
  2. Client-side:

    • Create static/js/locales/de.json
    • Add to config: LANGUAGES = {..., 'de': 'Deutsch'}
  3. Update loader: Modify base template to support new locale

Resources