dd2ebf863a
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>
241 lines
7.0 KiB
Markdown
241 lines
7.0 KiB
Markdown
# 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:**
|
|
```python
|
|
from flask_babel import gettext as _
|
|
|
|
flash(_("Profilo aggiornato con successo"))
|
|
```
|
|
|
|
**In Jinja2 templates:**
|
|
```jinja2
|
|
<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:**
|
|
```html
|
|
<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:**
|
|
```html
|
|
<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:
|
|
|
|
```bash
|
|
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**:
|
|
```html
|
|
<a href="{{ url_for('set_language', lang='en') }}">English</a>
|
|
<a href="{{ url_for('set_language', lang='it') }}">Italiano</a>
|
|
```
|
|
|
|
**With Alpine.js**:
|
|
```html
|
|
<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:
|
|
```json
|
|
{
|
|
"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:
|
|
```python
|
|
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
|
|
|
|
```bash
|
|
# 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**:
|
|
```bash
|
|
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
|
|
|
|
- Flask-Babel: https://python-babel.github.io/flask-babel/
|
|
- Babel: http://babel.pocoo.org/
|
|
- Alpine.js i18n: https://github.com/alpine-collective/alpine-i18n
|
|
- PO file format: https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html
|