Daniele
commited on
Commit
Β·
d14edbf
1
Parent(s):
cb42234
wip
Browse files- API_EXAMPLES.md +235 -0
- Dockerfile +27 -0
- README.md +196 -13
- __pycache__/app.cpython-311.pyc +0 -0
- __pycache__/chat_service.cpython-311.pyc +0 -0
- __pycache__/config.cpython-311.pyc +0 -0
- app.py +185 -0
- chat_service.py +198 -0
- config.py +10 -0
- index.html +0 -29
- index.js +0 -76
- requirements.txt +6 -0
- start_server.py +22 -0
- style.css +0 -76
- test_api.py +196 -0
API_EXAMPLES.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Esempi di Richieste API - Local Translation Server
|
| 2 |
+
|
| 3 |
+
## Health Check
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
curl -X GET "http://localhost:8000/"
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
## Ottenere le lingue supportate
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
curl -X GET "http://localhost:8000/languages"
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
## Traduzione singola (GET con parametri)
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
# Traduzione semplice
|
| 19 |
+
curl -X POST "http://localhost:8000/translate-single?text=Ciao%20mondo&target_language=en"
|
| 20 |
+
|
| 21 |
+
# Traduzione con HTML
|
| 22 |
+
curl -X POST "http://localhost:8000/translate-single?text=%3Cp%3ECiao%20%3Cstrong%3Emondo%3C/strong%3E%3C/p%3E&target_language=fr"
|
| 23 |
+
|
| 24 |
+
# Traduzione con placeholder
|
| 25 |
+
curl -X POST "http://localhost:8000/translate-single?text=Ciao%20%7Bname%7D%2C%20come%20stai%3F&target_language=de"
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Traduzione di oggetti locale complessi
|
| 29 |
+
|
| 30 |
+
### Esempio base
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
curl -X POST "http://localhost:8000/translate?target_language=en" \
|
| 34 |
+
-H "Content-Type: application/json" \
|
| 35 |
+
-d '{
|
| 36 |
+
"locales": {
|
| 37 |
+
"welcome": "Benvenuto!",
|
| 38 |
+
"save_button": "Salva",
|
| 39 |
+
"cancel_button": "Annulla"
|
| 40 |
+
}
|
| 41 |
+
}'
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### Esempio con HTML e placeholder
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
curl -X POST "http://localhost:8000/translate?target_language=fr" \
|
| 48 |
+
-H "Content-Type: application/json" \
|
| 49 |
+
-d '{
|
| 50 |
+
"locales": {
|
| 51 |
+
"welcome_message": "Benvenuto nella nostra applicazione, {username}!",
|
| 52 |
+
"notification_html": "<div class=\"alert alert-info\"><strong>Info:</strong> Hai {count} nuovi messaggi</div>",
|
| 53 |
+
"button_save": "Salva documento",
|
| 54 |
+
"error_required": "Questo campo Γ¨ obbligatorio",
|
| 55 |
+
"success_message": "<p>Operazione completata con <em>successo</em>!</p>"
|
| 56 |
+
}
|
| 57 |
+
}'
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### Esempio per applicazione web complessa
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
curl -X POST "http://localhost:8000/translate?target_language=de" \
|
| 64 |
+
-H "Content-Type: application/json" \
|
| 65 |
+
-d '{
|
| 66 |
+
"locales": {
|
| 67 |
+
"nav_home": "Home",
|
| 68 |
+
"nav_products": "Prodotti",
|
| 69 |
+
"nav_contact": "Contatti",
|
| 70 |
+
"hero_title": "Benvenuto nel futuro",
|
| 71 |
+
"hero_subtitle": "La tecnologia che cambia il mondo",
|
| 72 |
+
"cta_button": "Scopri di piΓΉ",
|
| 73 |
+
"form_name_label": "Nome completo",
|
| 74 |
+
"form_email_label": "Indirizzo email",
|
| 75 |
+
"form_message_label": "Il tuo messaggio",
|
| 76 |
+
"form_submit": "Invia messaggio",
|
| 77 |
+
"validation_required": "Questo campo Γ¨ obbligatorio",
|
| 78 |
+
"validation_email": "Inserisci un indirizzo email valido",
|
| 79 |
+
"success_notification": "<div class=\"notification is-success\"><strong>Grazie!</strong> Il tuo messaggio Γ¨ stato inviato</div>",
|
| 80 |
+
"error_notification": "<div class=\"notification is-danger\"><strong>Errore:</strong> Si Γ¨ verificato un problema</div>",
|
| 81 |
+
"user_greeting": "Ciao {firstName}, bentornato!",
|
| 82 |
+
"items_count": "Hai {itemCount} elementi nel carrello",
|
| 83 |
+
"last_login": "Ultimo accesso: {lastLoginDate}",
|
| 84 |
+
"footer_copyright": "Β© 2024 La Nostra Azienda. Tutti i diritti riservati."
|
| 85 |
+
}
|
| 86 |
+
}'
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
### Traduzione verso lingue diverse
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
# Inglese
|
| 93 |
+
curl -X POST "http://localhost:8000/translate?target_language=en" \
|
| 94 |
+
-H "Content-Type: application/json" \
|
| 95 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 96 |
+
|
| 97 |
+
# Francese
|
| 98 |
+
curl -X POST "http://localhost:8000/translate?target_language=fr" \
|
| 99 |
+
-H "Content-Type: application/json" \
|
| 100 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 101 |
+
|
| 102 |
+
# Tedesco
|
| 103 |
+
curl -X POST "http://localhost:8000/translate?target_language=de" \
|
| 104 |
+
-H "Content-Type: application/json" \
|
| 105 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 106 |
+
|
| 107 |
+
# Spagnolo
|
| 108 |
+
curl -X POST "http://localhost:8000/translate?target_language=es" \
|
| 109 |
+
-H "Content-Type: application/json" \
|
| 110 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 111 |
+
|
| 112 |
+
# Russo
|
| 113 |
+
curl -X POST "http://localhost:8000/translate?target_language=ru" \
|
| 114 |
+
-H "Content-Type: application/json" \
|
| 115 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 116 |
+
|
| 117 |
+
# Giapponese
|
| 118 |
+
curl -X POST "http://localhost:8000/translate?target_language=ja" \
|
| 119 |
+
-H "Content-Type: application/json" \
|
| 120 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 121 |
+
|
| 122 |
+
# Cinese
|
| 123 |
+
curl -X POST "http://localhost:8000/translate?target_language=zh" \
|
| 124 |
+
-H "Content-Type: application/json" \
|
| 125 |
+
-d '{"locales": {"greeting": "Buongiorno!", "goodbye": "Arrivederci!"}}'
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
## Test degli errori
|
| 129 |
+
|
| 130 |
+
### Lingua non supportata
|
| 131 |
+
|
| 132 |
+
```bash
|
| 133 |
+
curl -X POST "http://localhost:8000/translate?target_language=xyz" \
|
| 134 |
+
-H "Content-Type: application/json" \
|
| 135 |
+
-d '{"locales": {"test": "Ciao"}}'
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### Body malformato
|
| 139 |
+
|
| 140 |
+
```bash
|
| 141 |
+
curl -X POST "http://localhost:8000/translate?target_language=en" \
|
| 142 |
+
-H "Content-Type: application/json" \
|
| 143 |
+
-d '{"wrong_field": "test"}'
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
### Parametro mancante
|
| 147 |
+
|
| 148 |
+
```bash
|
| 149 |
+
curl -X POST "http://localhost:8000/translate" \
|
| 150 |
+
-H "Content-Type: application/json" \
|
| 151 |
+
-d '{"locales": {"test": "Ciao"}}'
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
## Esempi usando JavaScript (fetch)
|
| 155 |
+
|
| 156 |
+
```javascript
|
| 157 |
+
// Traduzione di oggetti locale
|
| 158 |
+
const translateLocales = async () => {
|
| 159 |
+
const response = await fetch(
|
| 160 |
+
"http://localhost:8000/translate?target_language=en",
|
| 161 |
+
{
|
| 162 |
+
method: "POST",
|
| 163 |
+
headers: {
|
| 164 |
+
"Content-Type": "application/json",
|
| 165 |
+
},
|
| 166 |
+
body: JSON.stringify({
|
| 167 |
+
locales: {
|
| 168 |
+
welcome: "Benvenuto!",
|
| 169 |
+
save_button: "Salva",
|
| 170 |
+
cancel_button: "Annulla",
|
| 171 |
+
},
|
| 172 |
+
}),
|
| 173 |
+
}
|
| 174 |
+
);
|
| 175 |
+
|
| 176 |
+
const data = await response.json();
|
| 177 |
+
console.log(data);
|
| 178 |
+
};
|
| 179 |
+
|
| 180 |
+
// Ottenere lingue supportate
|
| 181 |
+
const getSupportedLanguages = async () => {
|
| 182 |
+
const response = await fetch("http://localhost:8000/languages");
|
| 183 |
+
const data = await response.json();
|
| 184 |
+
console.log("Lingue supportate:", data.supported_languages);
|
| 185 |
+
};
|
| 186 |
+
|
| 187 |
+
// Traduzione singola
|
| 188 |
+
const translateSingle = async () => {
|
| 189 |
+
const response = await fetch(
|
| 190 |
+
"http://localhost:8000/translate-single?text=Ciao mondo&target_language=en",
|
| 191 |
+
{
|
| 192 |
+
method: "POST",
|
| 193 |
+
}
|
| 194 |
+
);
|
| 195 |
+
const data = await response.json();
|
| 196 |
+
console.log(data);
|
| 197 |
+
};
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
## Esempi usando Python (requests)
|
| 201 |
+
|
| 202 |
+
```python
|
| 203 |
+
import requests
|
| 204 |
+
|
| 205 |
+
# Traduzione di oggetti locale
|
| 206 |
+
def translate_locales():
|
| 207 |
+
url = "http://localhost:8000/translate"
|
| 208 |
+
params = {"target_language": "en"}
|
| 209 |
+
data = {
|
| 210 |
+
"locales": {
|
| 211 |
+
"welcome": "Benvenuto!",
|
| 212 |
+
"save_button": "Salva",
|
| 213 |
+
"cancel_button": "Annulla"
|
| 214 |
+
}
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
response = requests.post(url, json=data, params=params)
|
| 218 |
+
print(response.json())
|
| 219 |
+
|
| 220 |
+
# Ottenere lingue supportate
|
| 221 |
+
def get_supported_languages():
|
| 222 |
+
response = requests.get("http://localhost:8000/languages")
|
| 223 |
+
print(response.json())
|
| 224 |
+
|
| 225 |
+
# Traduzione singola
|
| 226 |
+
def translate_single():
|
| 227 |
+
url = "http://localhost:8000/translate-single"
|
| 228 |
+
params = {
|
| 229 |
+
"text": "Ciao mondo",
|
| 230 |
+
"target_language": "en"
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
response = requests.post(url, params=params)
|
| 234 |
+
print(response.json())
|
| 235 |
+
```
|
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Local Translation Server Docker Image
|
| 2 |
+
FROM python:3.9-slim
|
| 3 |
+
|
| 4 |
+
# Installa le dipendenze di sistema necessarie
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
git \
|
| 7 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
+
|
| 9 |
+
# Crea un utente non-root
|
| 10 |
+
RUN useradd -m -u 1000 user
|
| 11 |
+
USER user
|
| 12 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 13 |
+
|
| 14 |
+
WORKDIR /app
|
| 15 |
+
|
| 16 |
+
# Copia e installa i requirements
|
| 17 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 18 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 19 |
+
|
| 20 |
+
# Copia il codice dell'applicazione
|
| 21 |
+
COPY --chown=user . /app
|
| 22 |
+
|
| 23 |
+
# Espone la porta 8000
|
| 24 |
+
EXPOSE 8000
|
| 25 |
+
|
| 26 |
+
# Avvia il server
|
| 27 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
README.md
CHANGED
|
@@ -1,13 +1,196 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Local Translation Server
|
| 2 |
+
|
| 3 |
+
Un servizio di traduzione locale che traduce oggetti JSON con locales dall'italiano ad altre lingue utilizzando il modello T5 di Hugging Face.
|
| 4 |
+
|
| 5 |
+
## Caratteristiche
|
| 6 |
+
|
| 7 |
+
- β¨ Traduzione automatica dall'italiano a piΓΉ di 30 lingue
|
| 8 |
+
- π§ Preserva i tag HTML e i placeholder con parentesi graffe
|
| 9 |
+
- π API REST facile da usare
|
| 10 |
+
- π― Ottimizzato per oggetti locale di applicazioni web
|
| 11 |
+
- π Completamente locale, nessun servizio esterno richiesto
|
| 12 |
+
|
| 13 |
+
## Installazione
|
| 14 |
+
|
| 15 |
+
1. Clona il repository:
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
git clone <repository-url>
|
| 19 |
+
cd locale_translate_server
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
2. Installa le dipendenze:
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
pip install -r requirements.txt
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
3. Avvia il server:
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
python app.py
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
Il server sarΓ disponibile su `http://localhost:8000`
|
| 35 |
+
|
| 36 |
+
## Uso dell'API
|
| 37 |
+
|
| 38 |
+
### Endpoint Principali
|
| 39 |
+
|
| 40 |
+
#### 1. Traduci Oggetti Locale - `POST /translate`
|
| 41 |
+
|
| 42 |
+
Traduce un oggetto JSON contenente locales dall'italiano alla lingua specificata.
|
| 43 |
+
|
| 44 |
+
**Parametri:**
|
| 45 |
+
|
| 46 |
+
- `target_language` (query parameter): Codice della lingua target (es: "en", "fr", "de")
|
| 47 |
+
|
| 48 |
+
**Body della richiesta:**
|
| 49 |
+
|
| 50 |
+
```json
|
| 51 |
+
{
|
| 52 |
+
"locales": {
|
| 53 |
+
"welcome_message": "Benvenuto nella nostra applicazione!",
|
| 54 |
+
"button_save": "Salva",
|
| 55 |
+
"error_required": "Questo campo Γ¨ obbligatorio",
|
| 56 |
+
"html_content": "<p>Clicca <a href='#'>qui</a> per continuare</p>",
|
| 57 |
+
"template_message": "Ciao {name}, hai {count} nuovi messaggi"
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
**Esempio di richiesta:**
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
curl -X POST "http://localhost:8000/translate?target_language=en" \
|
| 66 |
+
-H "Content-Type: application/json" \
|
| 67 |
+
-d '{
|
| 68 |
+
"locales": {
|
| 69 |
+
"welcome": "Benvenuto!",
|
| 70 |
+
"save_btn": "Salva documento"
|
| 71 |
+
}
|
| 72 |
+
}'
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
**Risposta:**
|
| 76 |
+
|
| 77 |
+
```json
|
| 78 |
+
{
|
| 79 |
+
"translated_locales": {
|
| 80 |
+
"welcome": "Welcome!",
|
| 81 |
+
"save_btn": "Save document"
|
| 82 |
+
},
|
| 83 |
+
"source_language": "it",
|
| 84 |
+
"target_language": "en"
|
| 85 |
+
}
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
#### 2. Traduci Singolo Testo - `POST /translate-single`
|
| 89 |
+
|
| 90 |
+
Traduce un singolo testo per test rapidi.
|
| 91 |
+
|
| 92 |
+
**Parametri:**
|
| 93 |
+
|
| 94 |
+
- `text` (query parameter): Testo in italiano da tradurre
|
| 95 |
+
- `target_language` (query parameter): Codice della lingua target
|
| 96 |
+
|
| 97 |
+
**Esempio:**
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
curl -X POST "http://localhost:8000/translate-single?text=Ciao mondo&target_language=en"
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
#### 3. Lingue Supportate - `GET /languages`
|
| 104 |
+
|
| 105 |
+
Restituisce la lista delle lingue supportate.
|
| 106 |
+
|
| 107 |
+
**Risposta:**
|
| 108 |
+
|
| 109 |
+
```json
|
| 110 |
+
{
|
| 111 |
+
"supported_languages": ["en", "fr", "de", "es", "pt", "ru", "ja", "ko", "zh", ...]
|
| 112 |
+
}
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### Lingue Supportate
|
| 116 |
+
|
| 117 |
+
Il servizio supporta traduzione verso le seguenti lingue:
|
| 118 |
+
|
| 119 |
+
- **en** - English
|
| 120 |
+
- **fr** - French
|
| 121 |
+
- **de** - German
|
| 122 |
+
- **es** - Spanish
|
| 123 |
+
- **pt** - Portuguese
|
| 124 |
+
- **ru** - Russian
|
| 125 |
+
- **ja** - Japanese
|
| 126 |
+
- **ko** - Korean
|
| 127 |
+
- **zh** - Chinese
|
| 128 |
+
- **ar** - Arabic
|
| 129 |
+
- **hi** - Hindi
|
| 130 |
+
- **nl** - Dutch
|
| 131 |
+
- **sv** - Swedish
|
| 132 |
+
- **da** - Danish
|
| 133 |
+
- **no** - Norwegian
|
| 134 |
+
- **fi** - Finnish
|
| 135 |
+
- **pl** - Polish
|
| 136 |
+
- **cs** - Czech
|
| 137 |
+
- **hu** - Hungarian
|
| 138 |
+
- **ro** - Romanian
|
| 139 |
+
- **bg** - Bulgarian
|
| 140 |
+
- **hr** - Croatian
|
| 141 |
+
- **sk** - Slovak
|
| 142 |
+
- **sl** - Slovenian
|
| 143 |
+
- **et** - Estonian
|
| 144 |
+
- **lv** - Latvian
|
| 145 |
+
- **lt** - Lithuanian
|
| 146 |
+
- **mt** - Maltese
|
| 147 |
+
- **el** - Greek
|
| 148 |
+
- **tr** - Turkish
|
| 149 |
+
|
| 150 |
+
## Caratteristiche Speciali
|
| 151 |
+
|
| 152 |
+
### Preservazione di HTML e Placeholder
|
| 153 |
+
|
| 154 |
+
Il servizio preserva automaticamente:
|
| 155 |
+
|
| 156 |
+
1. **Tag HTML**: `<p>`, `<a>`, `<strong>`, etc.
|
| 157 |
+
2. **Placeholder con parentesi graffe**: `{name}`, `{count}`, `{date}`, etc.
|
| 158 |
+
|
| 159 |
+
**Esempio:**
|
| 160 |
+
|
| 161 |
+
```
|
| 162 |
+
Input: "Ciao {name}, hai <strong>{count}</strong> messaggi"
|
| 163 |
+
Output: "Hello {name}, you have <strong>{count}</strong> messages"
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
## Configurazione
|
| 167 |
+
|
| 168 |
+
Puoi configurare il servizio tramite variabili d'ambiente:
|
| 169 |
+
|
| 170 |
+
- `MODEL_NAME`: Nome del modello T5 da utilizzare (default: "google/t5-v1_1-base")
|
| 171 |
+
- `ENVIRONMENT`: Ambiente di esecuzione (default: "development")
|
| 172 |
+
- `DEBUG`: Abilita debug logging (default: "true")
|
| 173 |
+
|
| 174 |
+
## Documentazione API
|
| 175 |
+
|
| 176 |
+
Una volta avviato il server, puoi accedere alla documentazione interativa Swagger UI su:
|
| 177 |
+
|
| 178 |
+
`http://localhost:8000/docs`
|
| 179 |
+
|
| 180 |
+
## Performance
|
| 181 |
+
|
| 182 |
+
Il modello T5-base offre un buon bilanciamento tra qualitΓ di traduzione e velocitΓ . Per applicazioni che richiedono traduzione di alta qualitΓ , puoi configurare un modello piΓΉ grande tramite la variabile d'ambiente `MODEL_NAME`:
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
export MODEL_NAME="google/t5-v1_1-large"
|
| 186 |
+
python app.py
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
## Docker
|
| 190 |
+
|
| 191 |
+
Il progetto include un Dockerfile per facilitΓ di deployment:
|
| 192 |
+
|
| 193 |
+
```bash
|
| 194 |
+
docker build -t locale-translate-server .
|
| 195 |
+
docker run -p 8000:8000 locale-translate-server
|
| 196 |
+
```
|
__pycache__/app.cpython-311.pyc
ADDED
|
Binary file (7.86 kB). View file
|
|
|
__pycache__/chat_service.cpython-311.pyc
ADDED
|
Binary file (8.82 kB). View file
|
|
|
__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (902 Bytes). View file
|
|
|
app.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, Query
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from pydantic import BaseModel
|
| 4 |
+
from typing import Dict, Any, Optional, List
|
| 5 |
+
from chat_service import translate_locale, get_supported_languages
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
# Configura il logging
|
| 9 |
+
logging.basicConfig(level=logging.INFO)
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
app = FastAPI(
|
| 13 |
+
title="Local Translation Server",
|
| 14 |
+
description="API per tradurre oggetti locale dall'italiano ad altre lingue",
|
| 15 |
+
version="1.0.0"
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
# Abilita CORS per permettere richieste da altri domini
|
| 19 |
+
app.add_middleware(
|
| 20 |
+
CORSMiddleware,
|
| 21 |
+
allow_origins=["*"],
|
| 22 |
+
allow_credentials=True,
|
| 23 |
+
allow_methods=["*"],
|
| 24 |
+
allow_headers=["*"],
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class LocaleRequest(BaseModel):
|
| 29 |
+
"""Modello per la richiesta di traduzione di un oggetto locale."""
|
| 30 |
+
locales: Dict[str, str]
|
| 31 |
+
|
| 32 |
+
model_config = {
|
| 33 |
+
"json_schema_extra": {
|
| 34 |
+
"example": {
|
| 35 |
+
"locales": {
|
| 36 |
+
"welcome_message": "Benvenuto nella nostra applicazione!",
|
| 37 |
+
"button_save": "Salva",
|
| 38 |
+
"error_required": "Questo campo Γ¨ obbligatorio",
|
| 39 |
+
"html_content": "<p>Clicca <a href='#'>qui</a> per continuare</p>",
|
| 40 |
+
"template_message": "Ciao {name}, hai {count} nuovi messaggi"
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class LocaleResponse(BaseModel):
|
| 48 |
+
"""Modello per la risposta di traduzione."""
|
| 49 |
+
translated_locales: Dict[str, str]
|
| 50 |
+
source_language: str
|
| 51 |
+
target_language: str
|
| 52 |
+
|
| 53 |
+
model_config = {
|
| 54 |
+
"json_schema_extra": {
|
| 55 |
+
"example": {
|
| 56 |
+
"translated_locales": {
|
| 57 |
+
"welcome_message": "Welcome to our application!",
|
| 58 |
+
"button_save": "Save",
|
| 59 |
+
"error_required": "This field is required",
|
| 60 |
+
"html_content": "<p>Click <a href='#'>here</a> to continue</p>",
|
| 61 |
+
"template_message": "Hello {name}, you have {count} new messages"
|
| 62 |
+
},
|
| 63 |
+
"source_language": "it",
|
| 64 |
+
"target_language": "en"
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class LanguagesResponse(BaseModel):
|
| 71 |
+
"""Modello per la risposta delle lingue supportate."""
|
| 72 |
+
supported_languages: List[str]
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@app.get("/", summary="Health Check")
|
| 76 |
+
def health_check():
|
| 77 |
+
"""Endpoint per verificare che il server sia in esecuzione."""
|
| 78 |
+
return {
|
| 79 |
+
"service": "Local Translation Server",
|
| 80 |
+
"status": "running",
|
| 81 |
+
"version": "1.0.0"
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
@app.get("/languages", response_model=LanguagesResponse, summary="Lingue Supportate")
|
| 86 |
+
def get_languages():
|
| 87 |
+
"""Restituisce la lista delle lingue supportate per la traduzione."""
|
| 88 |
+
return LanguagesResponse(supported_languages=get_supported_languages())
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
@app.post("/translate", response_model=LocaleResponse, summary="Traduci Locales")
|
| 92 |
+
async def translate_locales(
|
| 93 |
+
request: LocaleRequest,
|
| 94 |
+
target_language: str = Query(
|
| 95 |
+
...,
|
| 96 |
+
description="Codice della lingua target (es: 'en', 'fr', 'de')",
|
| 97 |
+
example="en"
|
| 98 |
+
)
|
| 99 |
+
):
|
| 100 |
+
"""
|
| 101 |
+
Traduce un oggetto contenente locales dall'italiano alla lingua specificata.
|
| 102 |
+
|
| 103 |
+
- **locales**: Dizionario chiave-valore dove ogni chiave Γ¨ un identificatore
|
| 104 |
+
e ogni valore Γ¨ il testo in italiano da tradurre
|
| 105 |
+
- **target_language**: Codice della lingua target (parametro query)
|
| 106 |
+
|
| 107 |
+
Il servizio preserva:
|
| 108 |
+
- Tag HTML nel testo
|
| 109 |
+
- Placeholders con parentesi graffe (es: {name}, {count})
|
| 110 |
+
- Struttura delle chiavi originali
|
| 111 |
+
"""
|
| 112 |
+
try:
|
| 113 |
+
logger.info(f"Richiesta traduzione per {len(request.locales)} locales verso {target_language}")
|
| 114 |
+
|
| 115 |
+
# Verifica che la lingua target sia supportata
|
| 116 |
+
supported_languages = get_supported_languages()
|
| 117 |
+
if target_language not in supported_languages:
|
| 118 |
+
raise HTTPException(
|
| 119 |
+
status_code=400,
|
| 120 |
+
detail=f"Lingua '{target_language}' non supportata. Lingue disponibili: {supported_languages}"
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Esegui la traduzione
|
| 124 |
+
translated_data = translate_locale(request.locales, target_language)
|
| 125 |
+
|
| 126 |
+
logger.info(f"Traduzione completata per {len(translated_data)} locales")
|
| 127 |
+
|
| 128 |
+
return LocaleResponse(
|
| 129 |
+
translated_locales=translated_data,
|
| 130 |
+
source_language="it",
|
| 131 |
+
target_language=target_language
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
except ValueError as e:
|
| 135 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 136 |
+
except Exception as e:
|
| 137 |
+
logger.error(f"Errore durante la traduzione: {str(e)}")
|
| 138 |
+
raise HTTPException(status_code=500, detail=f"Errore interno del server: {str(e)}")
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
@app.post("/translate-single", summary="Traduci Singolo Testo")
|
| 142 |
+
async def translate_single_text(
|
| 143 |
+
text: str = Query(..., description="Testo in italiano da tradurre"),
|
| 144 |
+
target_language: str = Query(
|
| 145 |
+
...,
|
| 146 |
+
description="Codice della lingua target (es: 'en', 'fr', 'de')",
|
| 147 |
+
example="en"
|
| 148 |
+
)
|
| 149 |
+
):
|
| 150 |
+
"""
|
| 151 |
+
Traduce un singolo testo dall'italiano alla lingua specificata.
|
| 152 |
+
Utile per test rapidi o traduzioni singole.
|
| 153 |
+
"""
|
| 154 |
+
try:
|
| 155 |
+
from chat_service import get_translation_service
|
| 156 |
+
|
| 157 |
+
# Verifica che la lingua target sia supportata
|
| 158 |
+
supported_languages = get_supported_languages()
|
| 159 |
+
if target_language not in supported_languages:
|
| 160 |
+
raise HTTPException(
|
| 161 |
+
status_code=400,
|
| 162 |
+
detail=f"Lingua '{target_language}' non supportata. Lingue disponibili: {supported_languages}"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
# Ottieni il servizio di traduzione e traduci
|
| 166 |
+
translation_service = get_translation_service()
|
| 167 |
+
translated_text = translation_service.translate_text(text, target_language)
|
| 168 |
+
|
| 169 |
+
return {
|
| 170 |
+
"original_text": text,
|
| 171 |
+
"translated_text": translated_text,
|
| 172 |
+
"source_language": "it",
|
| 173 |
+
"target_language": target_language
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
except ValueError as e:
|
| 177 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 178 |
+
except Exception as e:
|
| 179 |
+
logger.error(f"Errore durante la traduzione: {str(e)}")
|
| 180 |
+
raise HTTPException(status_code=500, detail=f"Errore interno del server: {str(e)}")
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
if __name__ == "__main__":
|
| 184 |
+
import uvicorn
|
| 185 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
chat_service.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import pipeline
|
| 2 |
+
import torch
|
| 3 |
+
from config import Config
|
| 4 |
+
from typing import Dict, Any
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
+
# Mapping delle lingue supportate con i loro nomi per i modelli Helsinki-NLP
|
| 8 |
+
LANGUAGE_CODES = {
|
| 9 |
+
"en": "English",
|
| 10 |
+
"fr": "French",
|
| 11 |
+
"de": "German",
|
| 12 |
+
"es": "Spanish",
|
| 13 |
+
"pt": "Portuguese",
|
| 14 |
+
"ru": "Russian",
|
| 15 |
+
"ja": "Japanese",
|
| 16 |
+
"ko": "Korean",
|
| 17 |
+
"zh": "Chinese",
|
| 18 |
+
"ar": "Arabic",
|
| 19 |
+
"hi": "Hindi",
|
| 20 |
+
"nl": "Dutch",
|
| 21 |
+
"sv": "Swedish",
|
| 22 |
+
"da": "Danish",
|
| 23 |
+
"no": "Norwegian",
|
| 24 |
+
"fi": "Finnish",
|
| 25 |
+
"pl": "Polish",
|
| 26 |
+
"cs": "Czech",
|
| 27 |
+
"hu": "Hungarian",
|
| 28 |
+
"ro": "Romanian",
|
| 29 |
+
"bg": "Bulgarian",
|
| 30 |
+
"hr": "Croatian",
|
| 31 |
+
"sk": "Slovak",
|
| 32 |
+
"sl": "Slovenian",
|
| 33 |
+
"et": "Estonian",
|
| 34 |
+
"lv": "Latvian",
|
| 35 |
+
"lt": "Lithuanian",
|
| 36 |
+
"mt": "Maltese",
|
| 37 |
+
"el": "Greek",
|
| 38 |
+
"tr": "Turkish"
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
# Mapping per i modelli di traduzione Helsinki-NLP (da italiano verso altre lingue)
|
| 42 |
+
HELSINKI_MODELS = {
|
| 43 |
+
"en": "Helsinki-NLP/opus-mt-it-en",
|
| 44 |
+
"fr": "Helsinki-NLP/opus-mt-it-fr",
|
| 45 |
+
"de": "Helsinki-NLP/opus-mt-it-de",
|
| 46 |
+
"es": "Helsinki-NLP/opus-mt-it-es",
|
| 47 |
+
"pt": "Helsinki-NLP/opus-mt-it-pt",
|
| 48 |
+
"ru": "Helsinki-NLP/opus-mt-it-ru",
|
| 49 |
+
"nl": "Helsinki-NLP/opus-mt-it-nl",
|
| 50 |
+
"sv": "Helsinki-NLP/opus-mt-it-sv",
|
| 51 |
+
"da": "Helsinki-NLP/opus-mt-it-da",
|
| 52 |
+
"no": "Helsinki-NLP/opus-mt-it-no",
|
| 53 |
+
"fi": "Helsinki-NLP/opus-mt-it-fi",
|
| 54 |
+
"pl": "Helsinki-NLP/opus-mt-it-pl",
|
| 55 |
+
"cs": "Helsinki-NLP/opus-mt-it-cs",
|
| 56 |
+
"hu": "Helsinki-NLP/opus-mt-it-hu",
|
| 57 |
+
"ro": "Helsinki-NLP/opus-mt-it-ro",
|
| 58 |
+
"bg": "Helsinki-NLP/opus-mt-it-bg",
|
| 59 |
+
"hr": "Helsinki-NLP/opus-mt-it-hr",
|
| 60 |
+
"sk": "Helsinki-NLP/opus-mt-it-sk",
|
| 61 |
+
"sl": "Helsinki-NLP/opus-mt-it-sl",
|
| 62 |
+
"et": "Helsinki-NLP/opus-mt-it-et",
|
| 63 |
+
"lv": "Helsinki-NLP/opus-mt-it-lv",
|
| 64 |
+
"lt": "Helsinki-NLP/opus-mt-it-lt",
|
| 65 |
+
"el": "Helsinki-NLP/opus-mt-it-el",
|
| 66 |
+
"tr": "Helsinki-NLP/opus-mt-it-tr"
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class TranslationService:
|
| 71 |
+
def __init__(self, device: str = "cpu"):
|
| 72 |
+
self.device = device
|
| 73 |
+
self.translators = {} # Cache per i translator
|
| 74 |
+
|
| 75 |
+
def _get_translator(self, target_language: str):
|
| 76 |
+
"""Ottiene o crea un translator per la lingua target."""
|
| 77 |
+
if target_language not in self.translators:
|
| 78 |
+
if target_language in HELSINKI_MODELS:
|
| 79 |
+
model_name = HELSINKI_MODELS[target_language]
|
| 80 |
+
try:
|
| 81 |
+
self.translators[target_language] = pipeline(
|
| 82 |
+
"translation",
|
| 83 |
+
model=model_name,
|
| 84 |
+
device=0 if self.device == "cuda" else -1,
|
| 85 |
+
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
|
| 86 |
+
)
|
| 87 |
+
except Exception as e:
|
| 88 |
+
# Fallback per lingue senza modelli specifici
|
| 89 |
+
print(f"Modello non disponibile per {target_language}, usando fallback: {e}")
|
| 90 |
+
return None
|
| 91 |
+
else:
|
| 92 |
+
return None
|
| 93 |
+
return self.translators[target_language]
|
| 94 |
+
|
| 95 |
+
def _extract_placeholders(self, text: str) -> tuple[str, Dict[str, str]]:
|
| 96 |
+
"""Estrae i placeholders HTML e delle parentesi graffe dal testo."""
|
| 97 |
+
placeholders = {}
|
| 98 |
+
|
| 99 |
+
# Pattern per HTML tags
|
| 100 |
+
html_pattern = r'<[^>]+>'
|
| 101 |
+
html_matches = re.findall(html_pattern, text)
|
| 102 |
+
|
| 103 |
+
# Pattern per parentesi graffe
|
| 104 |
+
brace_pattern = r'\{[^}]+\}'
|
| 105 |
+
brace_matches = re.findall(brace_pattern, text)
|
| 106 |
+
|
| 107 |
+
# Sostituisce HTML tags con placeholders
|
| 108 |
+
processed_text = text
|
| 109 |
+
for i, match in enumerate(html_matches):
|
| 110 |
+
placeholder = f"HTMLTAG{i}"
|
| 111 |
+
placeholders[placeholder] = match
|
| 112 |
+
processed_text = processed_text.replace(match, placeholder, 1)
|
| 113 |
+
|
| 114 |
+
# Sostituisce parentesi graffe con placeholders
|
| 115 |
+
for i, match in enumerate(brace_matches):
|
| 116 |
+
placeholder = f"PLACEHOLDER{i}"
|
| 117 |
+
placeholders[placeholder] = match
|
| 118 |
+
processed_text = processed_text.replace(match, placeholder, 1)
|
| 119 |
+
|
| 120 |
+
return processed_text, placeholders
|
| 121 |
+
|
| 122 |
+
def _restore_placeholders(self, text: str, placeholders: Dict[str, str]) -> str:
|
| 123 |
+
"""Ripristina i placeholders nel testo tradotto."""
|
| 124 |
+
for placeholder, original in placeholders.items():
|
| 125 |
+
# Rimuovi spazi extra attorno ai placeholder
|
| 126 |
+
text = text.replace(f" {placeholder} ", original)
|
| 127 |
+
text = text.replace(f" {placeholder}", original)
|
| 128 |
+
text = text.replace(f"{placeholder} ", original)
|
| 129 |
+
text = text.replace(placeholder, original)
|
| 130 |
+
return text
|
| 131 |
+
|
| 132 |
+
def translate_text(self, text: str, target_language: str) -> str:
|
| 133 |
+
"""Traduce il testo dall'italiano alla lingua target."""
|
| 134 |
+
if target_language not in LANGUAGE_CODES:
|
| 135 |
+
raise ValueError(f"Lingua non supportata: {target_language}")
|
| 136 |
+
|
| 137 |
+
# Estrai placeholders
|
| 138 |
+
clean_text, placeholders = self._extract_placeholders(text)
|
| 139 |
+
|
| 140 |
+
# Ottieni il translator
|
| 141 |
+
translator = self._get_translator(target_language)
|
| 142 |
+
if translator is None:
|
| 143 |
+
# Traduzione semplice di fallback (mantiene il testo originale)
|
| 144 |
+
return f"[TRANSLATION NOT AVAILABLE: {text}]"
|
| 145 |
+
|
| 146 |
+
try:
|
| 147 |
+
# Esegui la traduzione
|
| 148 |
+
result = translator(clean_text, max_length=512)
|
| 149 |
+
|
| 150 |
+
if isinstance(result, list) and len(result) > 0:
|
| 151 |
+
translated_text = result[0]['translation_text']
|
| 152 |
+
else:
|
| 153 |
+
translated_text = str(result)
|
| 154 |
+
|
| 155 |
+
# Ripristina i placeholders
|
| 156 |
+
translated_text = self._restore_placeholders(translated_text, placeholders)
|
| 157 |
+
|
| 158 |
+
return translated_text.strip()
|
| 159 |
+
|
| 160 |
+
except Exception as e:
|
| 161 |
+
print(f"Errore durante la traduzione: {e}")
|
| 162 |
+
return f"[TRANSLATION ERROR: {text}]"
|
| 163 |
+
|
| 164 |
+
def translate_locale_object(self, locale_data: Dict[str, Any], target_language: str) -> Dict[str, Any]:
|
| 165 |
+
"""Traduce un oggetto locale completo."""
|
| 166 |
+
if target_language not in LANGUAGE_CODES:
|
| 167 |
+
raise ValueError(f"Lingua non supportata: {target_language}")
|
| 168 |
+
|
| 169 |
+
translated_data = {}
|
| 170 |
+
|
| 171 |
+
for key, content in locale_data.items():
|
| 172 |
+
if isinstance(content, str):
|
| 173 |
+
translated_data[key] = self.translate_text(content, target_language)
|
| 174 |
+
else:
|
| 175 |
+
# Mantieni il valore originale se non Γ¨ una stringa
|
| 176 |
+
translated_data[key] = content
|
| 177 |
+
|
| 178 |
+
return translated_data
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# Istanza globale del servizio di traduzione
|
| 182 |
+
translation_service = None
|
| 183 |
+
|
| 184 |
+
def get_translation_service():
|
| 185 |
+
global translation_service
|
| 186 |
+
if translation_service is None:
|
| 187 |
+
config = Config()
|
| 188 |
+
translation_service = TranslationService(device=config.DEVICE)
|
| 189 |
+
return translation_service
|
| 190 |
+
|
| 191 |
+
def translate_locale(locale_data: Dict[str, Any], target_language: str) -> Dict[str, Any]:
|
| 192 |
+
"""Funzione helper per tradurre un oggetto locale."""
|
| 193 |
+
service = get_translation_service()
|
| 194 |
+
return service.translate_locale_object(locale_data, target_language)
|
| 195 |
+
|
| 196 |
+
def get_supported_languages():
|
| 197 |
+
"""Restituisce la lista delle lingue supportate."""
|
| 198 |
+
return list(HELSINKI_MODELS.keys())
|
config.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
class Config:
|
| 5 |
+
PROJECT_NAME = "Local Translation Server"
|
| 6 |
+
VERSION = "1.0.0"
|
| 7 |
+
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
|
| 8 |
+
DEBUG = os.getenv("DEBUG", "true") == "true"
|
| 9 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "google/mt5-small") # Usando mT5 per traduzione multilingue
|
| 10 |
+
DEVICE = "mps" if torch.mps.is_available() else "cpu"
|
index.html
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en">
|
| 3 |
-
|
| 4 |
-
<head>
|
| 5 |
-
<meta charset="UTF-8" />
|
| 6 |
-
<link rel="stylesheet" href="style.css" />
|
| 7 |
-
|
| 8 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 9 |
-
<title>Transformers.js - Object Detection</title>
|
| 10 |
-
</head>
|
| 11 |
-
|
| 12 |
-
<body>
|
| 13 |
-
<h1>Object Detection w/ π€ Transformers.js</h1>
|
| 14 |
-
<label id="container" for="upload">
|
| 15 |
-
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 16 |
-
<path fill="#000"
|
| 17 |
-
d="M3.5 24.3a3 3 0 0 1-1.9-.8c-.5-.5-.8-1.2-.8-1.9V2.9c0-.7.3-1.3.8-1.9.6-.5 1.2-.7 2-.7h18.6c.7 0 1.3.2 1.9.7.5.6.7 1.2.7 2v18.6c0 .7-.2 1.4-.7 1.9a3 3 0 0 1-2 .8H3.6Zm0-2.7h18.7V2.9H3.5v18.7Zm2.7-2.7h13.3c.3 0 .5 0 .6-.3v-.7l-3.7-5a.6.6 0 0 0-.6-.2c-.2 0-.4 0-.5.3l-3.5 4.6-2.4-3.3a.6.6 0 0 0-.6-.3c-.2 0-.4.1-.5.3l-2.7 3.6c-.1.2-.2.4 0 .7.1.2.3.3.6.3Z">
|
| 18 |
-
</path>
|
| 19 |
-
</svg>
|
| 20 |
-
Click to upload image
|
| 21 |
-
<label id="example">(or try example)</label>
|
| 22 |
-
</label>
|
| 23 |
-
<label id="status">Loading model...</label>
|
| 24 |
-
<input id="upload" type="file" accept="image/*" />
|
| 25 |
-
|
| 26 |
-
<script src="index.js" type="module"></script>
|
| 27 |
-
</body>
|
| 28 |
-
|
| 29 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
index.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.4.1';
|
| 2 |
-
|
| 3 |
-
// Reference the elements that we will need
|
| 4 |
-
const status = document.getElementById('status');
|
| 5 |
-
const fileUpload = document.getElementById('upload');
|
| 6 |
-
const imageContainer = document.getElementById('container');
|
| 7 |
-
const example = document.getElementById('example');
|
| 8 |
-
|
| 9 |
-
const EXAMPLE_URL = 'https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/city-streets.jpg';
|
| 10 |
-
|
| 11 |
-
// Create a new object detection pipeline
|
| 12 |
-
status.textContent = 'Loading model...';
|
| 13 |
-
const detector = await pipeline('object-detection', 'Xenova/detr-resnet-50');
|
| 14 |
-
status.textContent = 'Ready';
|
| 15 |
-
|
| 16 |
-
example.addEventListener('click', (e) => {
|
| 17 |
-
e.preventDefault();
|
| 18 |
-
detect(EXAMPLE_URL);
|
| 19 |
-
});
|
| 20 |
-
|
| 21 |
-
fileUpload.addEventListener('change', function (e) {
|
| 22 |
-
const file = e.target.files[0];
|
| 23 |
-
if (!file) {
|
| 24 |
-
return;
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
const reader = new FileReader();
|
| 28 |
-
|
| 29 |
-
// Set up a callback when the file is loaded
|
| 30 |
-
reader.onload = e2 => detect(e2.target.result);
|
| 31 |
-
|
| 32 |
-
reader.readAsDataURL(file);
|
| 33 |
-
});
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
// Detect objects in the image
|
| 37 |
-
async function detect(img) {
|
| 38 |
-
imageContainer.innerHTML = '';
|
| 39 |
-
imageContainer.style.backgroundImage = `url(${img})`;
|
| 40 |
-
|
| 41 |
-
status.textContent = 'Analysing...';
|
| 42 |
-
const output = await detector(img, {
|
| 43 |
-
threshold: 0.5,
|
| 44 |
-
percentage: true,
|
| 45 |
-
});
|
| 46 |
-
status.textContent = '';
|
| 47 |
-
output.forEach(renderBox);
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
// Render a bounding box and label on the image
|
| 51 |
-
function renderBox({ box, label }) {
|
| 52 |
-
const { xmax, xmin, ymax, ymin } = box;
|
| 53 |
-
|
| 54 |
-
// Generate a random color for the box
|
| 55 |
-
const color = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, 0);
|
| 56 |
-
|
| 57 |
-
// Draw the box
|
| 58 |
-
const boxElement = document.createElement('div');
|
| 59 |
-
boxElement.className = 'bounding-box';
|
| 60 |
-
Object.assign(boxElement.style, {
|
| 61 |
-
borderColor: color,
|
| 62 |
-
left: 100 * xmin + '%',
|
| 63 |
-
top: 100 * ymin + '%',
|
| 64 |
-
width: 100 * (xmax - xmin) + '%',
|
| 65 |
-
height: 100 * (ymax - ymin) + '%',
|
| 66 |
-
})
|
| 67 |
-
|
| 68 |
-
// Draw label
|
| 69 |
-
const labelElement = document.createElement('span');
|
| 70 |
-
labelElement.textContent = label;
|
| 71 |
-
labelElement.className = 'bounding-box-label';
|
| 72 |
-
labelElement.style.backgroundColor = color;
|
| 73 |
-
|
| 74 |
-
boxElement.appendChild(labelElement);
|
| 75 |
-
imageContainer.appendChild(boxElement);
|
| 76 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn[standard]
|
| 3 |
+
transformers
|
| 4 |
+
torch
|
| 5 |
+
sentencepiece
|
| 6 |
+
protobuf
|
start_server.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script per avviare il Local Translation Server
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import uvicorn
|
| 7 |
+
from app import app
|
| 8 |
+
|
| 9 |
+
if __name__ == "__main__":
|
| 10 |
+
print("π Avvio Local Translation Server...")
|
| 11 |
+
print("π‘ Server disponibile su: http://localhost:8000")
|
| 12 |
+
print("π Documentazione API: http://localhost:8000/docs")
|
| 13 |
+
print("π Interfaccia Redoc: http://localhost:8000/redoc")
|
| 14 |
+
print("\n" + "="*50)
|
| 15 |
+
|
| 16 |
+
uvicorn.run(
|
| 17 |
+
app,
|
| 18 |
+
host="0.0.0.0",
|
| 19 |
+
port=8000,
|
| 20 |
+
log_level="info",
|
| 21 |
+
reload=False # Disabilita il reload automatico in produzione
|
| 22 |
+
)
|
style.css
DELETED
|
@@ -1,76 +0,0 @@
|
|
| 1 |
-
* {
|
| 2 |
-
box-sizing: border-box;
|
| 3 |
-
padding: 0;
|
| 4 |
-
margin: 0;
|
| 5 |
-
font-family: sans-serif;
|
| 6 |
-
}
|
| 7 |
-
|
| 8 |
-
html,
|
| 9 |
-
body {
|
| 10 |
-
height: 100%;
|
| 11 |
-
}
|
| 12 |
-
|
| 13 |
-
body {
|
| 14 |
-
padding: 32px;
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
body,
|
| 18 |
-
#container {
|
| 19 |
-
display: flex;
|
| 20 |
-
flex-direction: column;
|
| 21 |
-
justify-content: center;
|
| 22 |
-
align-items: center;
|
| 23 |
-
}
|
| 24 |
-
|
| 25 |
-
#container {
|
| 26 |
-
position: relative;
|
| 27 |
-
gap: 0.4rem;
|
| 28 |
-
|
| 29 |
-
width: 640px;
|
| 30 |
-
height: 640px;
|
| 31 |
-
max-width: 100%;
|
| 32 |
-
max-height: 100%;
|
| 33 |
-
|
| 34 |
-
border: 2px dashed #D1D5DB;
|
| 35 |
-
border-radius: 0.75rem;
|
| 36 |
-
overflow: hidden;
|
| 37 |
-
cursor: pointer;
|
| 38 |
-
margin: 1rem;
|
| 39 |
-
|
| 40 |
-
background-size: 100% 100%;
|
| 41 |
-
background-position: center;
|
| 42 |
-
background-repeat: no-repeat;
|
| 43 |
-
font-size: 18px;
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
#upload {
|
| 47 |
-
display: none;
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
svg {
|
| 51 |
-
pointer-events: none;
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
#example {
|
| 55 |
-
font-size: 14px;
|
| 56 |
-
text-decoration: underline;
|
| 57 |
-
cursor: pointer;
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
#example:hover {
|
| 61 |
-
color: #2563EB;
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
.bounding-box {
|
| 65 |
-
position: absolute;
|
| 66 |
-
box-sizing: border-box;
|
| 67 |
-
border: solid 2px;
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
.bounding-box-label {
|
| 71 |
-
color: white;
|
| 72 |
-
position: absolute;
|
| 73 |
-
font-size: 12px;
|
| 74 |
-
margin: -16px 0 0 -2px;
|
| 75 |
-
padding: 1px;
|
| 76 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
test_api.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script di test per l'API di traduzione locale.
|
| 4 |
+
Questo script testa tutti gli endpoint principali del servizio.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import requests
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
# Configurazione
|
| 12 |
+
BASE_URL = "http://localhost:8000"
|
| 13 |
+
|
| 14 |
+
def test_health_check():
|
| 15 |
+
"""Testa l'endpoint di health check."""
|
| 16 |
+
print("π₯ Test Health Check...")
|
| 17 |
+
try:
|
| 18 |
+
response = requests.get(f"{BASE_URL}/")
|
| 19 |
+
print(f"Status: {response.status_code}")
|
| 20 |
+
print(f"Response: {response.json()}")
|
| 21 |
+
print("β
Health check OK\n")
|
| 22 |
+
return True
|
| 23 |
+
except Exception as e:
|
| 24 |
+
print(f"β Health check failed: {e}\n")
|
| 25 |
+
return False
|
| 26 |
+
|
| 27 |
+
def test_get_languages():
|
| 28 |
+
"""Testa l'endpoint per ottenere le lingue supportate."""
|
| 29 |
+
print("π Test Lingue Supportate...")
|
| 30 |
+
try:
|
| 31 |
+
response = requests.get(f"{BASE_URL}/languages")
|
| 32 |
+
data = response.json()
|
| 33 |
+
languages = data["supported_languages"]
|
| 34 |
+
print(f"Status: {response.status_code}")
|
| 35 |
+
print(f"Lingue supportate ({len(languages)}): {languages[:10]}...")
|
| 36 |
+
print("β
Lingue supportate OK\n")
|
| 37 |
+
return True
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"β Test lingue failed: {e}\n")
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
def test_translate_single():
|
| 43 |
+
"""Testa la traduzione di un singolo testo."""
|
| 44 |
+
print("π Test Traduzione Singola...")
|
| 45 |
+
try:
|
| 46 |
+
params = {
|
| 47 |
+
"text": "Ciao mondo! Come stai oggi?",
|
| 48 |
+
"target_language": "en"
|
| 49 |
+
}
|
| 50 |
+
response = requests.post(f"{BASE_URL}/translate-single", params=params)
|
| 51 |
+
data = response.json()
|
| 52 |
+
print(f"Status: {response.status_code}")
|
| 53 |
+
print(f"Testo originale: {data['original_text']}")
|
| 54 |
+
print(f"Testo tradotto: {data['translated_text']}")
|
| 55 |
+
print("β
Traduzione singola OK\n")
|
| 56 |
+
return True
|
| 57 |
+
except Exception as e:
|
| 58 |
+
print(f"β Test traduzione singola failed: {e}\n")
|
| 59 |
+
return False
|
| 60 |
+
|
| 61 |
+
def test_translate_locales():
|
| 62 |
+
"""Testa la traduzione di oggetti locale complessi."""
|
| 63 |
+
print("π Test Traduzione Locales...")
|
| 64 |
+
try:
|
| 65 |
+
locale_data = {
|
| 66 |
+
"locales": {
|
| 67 |
+
"welcome_message": "Benvenuto nella nostra applicazione!",
|
| 68 |
+
"button_save": "Salva documento",
|
| 69 |
+
"error_required": "Questo campo Γ¨ obbligatorio",
|
| 70 |
+
"html_content": "<p>Clicca <a href='#'>qui</a> per continuare</p>",
|
| 71 |
+
"template_message": "Ciao {name}, hai {count} nuovi messaggi",
|
| 72 |
+
"complex_html": "<div class='alert'><strong>Attenzione:</strong> Operazione completata con successo!</div>"
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
params = {"target_language": "en"}
|
| 77 |
+
response = requests.post(
|
| 78 |
+
f"{BASE_URL}/translate",
|
| 79 |
+
json=locale_data,
|
| 80 |
+
params=params
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
data = response.json()
|
| 84 |
+
print(f"Status: {response.status_code}")
|
| 85 |
+
print("Traduzioni:")
|
| 86 |
+
for key, value in data["translated_locales"].items():
|
| 87 |
+
original = locale_data["locales"][key]
|
| 88 |
+
print(f" {key}:")
|
| 89 |
+
print(f" IT: {original}")
|
| 90 |
+
print(f" EN: {value}")
|
| 91 |
+
|
| 92 |
+
print("β
Traduzione locales OK\n")
|
| 93 |
+
return True
|
| 94 |
+
except Exception as e:
|
| 95 |
+
print(f"β Test traduzione locales failed: {e}\n")
|
| 96 |
+
return False
|
| 97 |
+
|
| 98 |
+
def test_multiple_languages():
|
| 99 |
+
"""Testa la traduzione verso lingue diverse."""
|
| 100 |
+
print("π£οΈ Test Lingue Multiple...")
|
| 101 |
+
try:
|
| 102 |
+
locale_data = {
|
| 103 |
+
"locales": {
|
| 104 |
+
"greeting": "Buongiorno!",
|
| 105 |
+
"goodbye": "Arrivederci!"
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
languages_to_test = ["en", "fr", "de", "es"]
|
| 110 |
+
|
| 111 |
+
for lang in languages_to_test:
|
| 112 |
+
params = {"target_language": lang}
|
| 113 |
+
response = requests.post(
|
| 114 |
+
f"{BASE_URL}/translate",
|
| 115 |
+
json=locale_data,
|
| 116 |
+
params=params
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
data = response.json()
|
| 120 |
+
print(f"Traduzione in {lang}:")
|
| 121 |
+
for key, value in data["translated_locales"].items():
|
| 122 |
+
print(f" {key}: {value}")
|
| 123 |
+
|
| 124 |
+
print("β
Test lingue multiple OK\n")
|
| 125 |
+
return True
|
| 126 |
+
except Exception as e:
|
| 127 |
+
print(f"β Test lingue multiple failed: {e}\n")
|
| 128 |
+
return False
|
| 129 |
+
|
| 130 |
+
def test_error_handling():
|
| 131 |
+
"""Testa la gestione degli errori."""
|
| 132 |
+
print("β οΈ Test Gestione Errori...")
|
| 133 |
+
try:
|
| 134 |
+
# Test lingua non supportata
|
| 135 |
+
locale_data = {"locales": {"test": "Ciao"}}
|
| 136 |
+
params = {"target_language": "xx"} # Lingua inesistente
|
| 137 |
+
|
| 138 |
+
response = requests.post(
|
| 139 |
+
f"{BASE_URL}/translate",
|
| 140 |
+
json=locale_data,
|
| 141 |
+
params=params
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
print(f"Status per lingua inesistente: {response.status_code}")
|
| 145 |
+
if response.status_code == 400:
|
| 146 |
+
print("β
Gestione errore lingua inesistente OK")
|
| 147 |
+
|
| 148 |
+
# Test body malformato
|
| 149 |
+
response = requests.post(
|
| 150 |
+
f"{BASE_URL}/translate",
|
| 151 |
+
json={"wrong_field": "test"},
|
| 152 |
+
params={"target_language": "en"}
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
print(f"Status per body malformato: {response.status_code}")
|
| 156 |
+
print("β
Test gestione errori OK\n")
|
| 157 |
+
return True
|
| 158 |
+
except Exception as e:
|
| 159 |
+
print(f"β Test gestione errori failed: {e}\n")
|
| 160 |
+
return False
|
| 161 |
+
|
| 162 |
+
def main():
|
| 163 |
+
"""Esegue tutti i test."""
|
| 164 |
+
print("π Avvio test del Local Translation Server\n")
|
| 165 |
+
print("=" * 50)
|
| 166 |
+
|
| 167 |
+
tests = [
|
| 168 |
+
test_health_check,
|
| 169 |
+
test_get_languages,
|
| 170 |
+
test_translate_single,
|
| 171 |
+
test_translate_locales,
|
| 172 |
+
test_multiple_languages,
|
| 173 |
+
test_error_handling
|
| 174 |
+
]
|
| 175 |
+
|
| 176 |
+
passed = 0
|
| 177 |
+
total = len(tests)
|
| 178 |
+
|
| 179 |
+
for test in tests:
|
| 180 |
+
try:
|
| 181 |
+
if test():
|
| 182 |
+
passed += 1
|
| 183 |
+
time.sleep(1) # Pausa tra i test
|
| 184 |
+
except Exception as e:
|
| 185 |
+
print(f"β Test failed with exception: {e}\n")
|
| 186 |
+
|
| 187 |
+
print("=" * 50)
|
| 188 |
+
print(f"π Risultati: {passed}/{total} test passati")
|
| 189 |
+
|
| 190 |
+
if passed == total:
|
| 191 |
+
print("π Tutti i test sono passati!")
|
| 192 |
+
else:
|
| 193 |
+
print("β οΈ Alcuni test sono falliti. Controlla i log sopra.")
|
| 194 |
+
|
| 195 |
+
if __name__ == "__main__":
|
| 196 |
+
main()
|