boffire commited on
Commit
1c44377
·
verified ·
1 Parent(s): 63c4f33

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -71
app.py CHANGED
@@ -7,6 +7,7 @@ import warnings
7
  warnings.filterwarnings("ignore", category=FutureWarning)
8
 
9
  import os
 
10
  import requests
11
  import torch
12
  from flask import Flask, request, render_template_string, jsonify
@@ -31,6 +32,10 @@ model = None
31
  tokenizer = None
32
  device = None
33
 
 
 
 
 
34
 
35
  def load_model():
36
  global model, tokenizer, device
@@ -73,6 +78,14 @@ def translate_marian(text):
73
 
74
 
75
  def translate_libre_variant(text, variant_code):
 
 
 
 
 
 
 
 
76
  try:
77
  r = requests.post(
78
  LIBRETRANSLATE_URL,
@@ -83,23 +96,20 @@ def translate_libre_variant(text, variant_code):
83
  r.raise_for_status()
84
  result = r.json().get("translatedText", "[Error: No translation]")
85
  return {"success": True, "text": result}
 
 
 
 
86
  except Exception as e:
87
  return {"success": False, "text": f"[Error: {str(e)[:50]}]"}
88
 
89
 
90
  def translate_libre_all_variants(text):
91
  results = {}
92
- with ThreadPoolExecutor(max_workers=4) as executor:
93
- future_to_name = {
94
- executor.submit(translate_libre_variant, text, code): name
95
- for name, code in KABYLE_VARIANTS.items()
96
- }
97
- for future in as_completed(future_to_name, timeout=15):
98
- name = future_to_name[future]
99
- try:
100
- results[name] = future.result()
101
- except Exception as e:
102
- results[name] = {"success": False, "text": f"[Error: {e}]"}
103
  return results
104
 
105
 
@@ -111,7 +121,7 @@ API_DOCS_TEMPLATE = """<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8
111
  app = Flask(__name__)
112
  CORS(app, resources={r"/*": {"origins": "*"}})
113
 
114
- # Global error handler - NEVER return 500 to clients
115
  @app.errorhandler(Exception)
116
  def handle_exception(e):
117
  print(f"[GLOBAL ERROR] {type(e).__name__}: {e}", flush=True)
@@ -143,7 +153,6 @@ def index():
143
 
144
 
145
  def _extract_text(data):
146
- """Extract text from q field. Weblate sends q as either string or list."""
147
  q = data.get("q", "")
148
  if isinstance(q, list):
149
  return q[0] if q else ""
@@ -153,12 +162,12 @@ def _extract_text(data):
153
  @app.route("/translate", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
154
  def libretranslate_translate():
155
  """
156
- LibreTranslate-compatible translation endpoint.
157
- NEVER returns 500 - always returns 200 with either translation or empty string.
 
158
  """
159
  print(f"[WEblate] === /translate called ({request.method}) ===", flush=True)
160
 
161
- # GET requests - just return empty translation
162
  if request.method == "GET":
163
  return jsonify({"translatedText": ""}), 200
164
 
@@ -169,12 +178,10 @@ def libretranslate_translate():
169
  print(f"[WEblate] Could not read raw body: {e}", flush=True)
170
  raw_data = ""
171
 
172
- # Parse data
173
  data = {}
174
  try:
175
  json_data = request.get_json(silent=True, force=True)
176
  if json_data:
177
- print(f"[WEblate] Parsed as JSON: {json_data}", flush=True)
178
  data = json_data
179
  except Exception as e:
180
  print(f"[WEblate] JSON parse failed: {e}", flush=True)
@@ -183,7 +190,6 @@ def libretranslate_translate():
183
  try:
184
  form_data = request.form
185
  if form_data:
186
- print(f"[WEblate] Parsed as form: {dict(form_data)}", flush=True)
187
  data = dict(form_data)
188
  except Exception as e:
189
  print(f"[WEblate] Form parse failed: {e}", flush=True)
@@ -191,15 +197,10 @@ def libretranslate_translate():
191
  if not data and raw_data:
192
  try:
193
  import json
194
- manual_json = json.loads(raw_data)
195
- print(f"[WEblate] Manually parsed JSON: {manual_json}", flush=True)
196
- data = manual_json
197
  except Exception as e:
198
  print(f"[WEblate] Manual JSON parse failed: {e}", flush=True)
199
 
200
- print(f"[WEblate] Final data: {data}", flush=True)
201
-
202
- # Extract fields
203
  text = _extract_text(data)
204
  source = str(data.get("source", "en")).strip() if data else "en"
205
  target = str(data.get("target", "kab")).strip() if data else "kab"
@@ -207,52 +208,24 @@ def libretranslate_translate():
207
  print(f"[WEblate] text={text[:50]}, source={source}, target={target}", flush=True)
208
 
209
  if not text:
210
- print(f"[WEblate] Empty text, returning empty translation", flush=True)
211
  return jsonify({"translatedText": ""}), 200
212
 
213
- # Handle auto source
214
  if source == "auto":
215
  kab_chars = set("ɛƐɣƔṭḍčǧḥṣẓ")
216
  source = "kab" if any(c in text.lower() for c in kab_chars) else "en"
217
- print(f"[WEblate] Auto-detected source: {source}", flush=True)
218
 
219
- # Only support English -> Kabyle
220
  if source != "en":
221
- print(f"[WEblate] Unsupported source: {source}, returning empty", flush=True)
222
  return jsonify({"translatedText": ""}), 200
223
 
224
- # Map target to known variant codes
225
- valid_targets = set(KABYLE_VARIANTS.values())
226
- if target not in valid_targets:
227
- print(f"[WEblate] Unknown target '{target}', defaulting to 'kab'", flush=True)
228
- target = "kab"
229
-
230
- # Translate
231
  try:
232
- print(f"[WEblate] Calling MarianMT...", flush=True)
233
  marian_results = translate_marian(text)
234
- best_translation = ""
235
  if marian_results and not marian_results[0].startswith("[Error"):
236
- best_translation = marian_results[0]
237
- print(f"[WEblate] MarianMT result: {best_translation[:50]}", flush=True)
238
-
239
- if not best_translation:
240
- print(f"[WEblate] Falling back to LibreTranslate...", flush=True)
241
- libre_result = translate_libre_variant(text, target)
242
- if libre_result["success"]:
243
- best_translation = libre_result["text"]
244
- print(f"[WEblate] LibreTranslate result: {best_translation[:50]}", flush=True)
245
-
246
- response = {"translatedText": best_translation or ""}
247
- print(f"[WEblate] SUCCESS: {response}", flush=True)
248
- return jsonify(response), 200
249
-
250
  except Exception as e:
251
- print(f"[WEblate] Translation failed: {e}", flush=True)
252
- import traceback
253
- traceback.print_exc()
254
- # NEVER return 500
255
- return jsonify({"translatedText": ""}), 200
256
 
257
 
258
  @app.route("/languages", methods=["GET"])
@@ -338,19 +311,12 @@ def api_translate():
338
  return jsonify({"error": f"No matching variants for: {variant_filter}"}), 400
339
 
340
  libre_results = {}
341
- with ThreadPoolExecutor(max_workers=4) as executor:
342
- future_to_name = {
343
- executor.submit(translate_libre_variant, text, code): (name, code)
344
- for name, code in variants_to_use.items()
345
- }
346
- for future in as_completed(future_to_name, timeout=15):
347
- name, code = future_to_name[future]
348
- try:
349
- data = future.result()
350
- except Exception as e:
351
- data = {"success": False, "text": f"[Error: {e}]"}
352
- data["code"] = code
353
- libre_results[name] = data
354
 
355
  result["libre"] = libre_results
356
 
 
7
  warnings.filterwarnings("ignore", category=FutureWarning)
8
 
9
  import os
10
+ import time
11
  import requests
12
  import torch
13
  from flask import Flask, request, render_template_string, jsonify
 
32
  tokenizer = None
33
  device = None
34
 
35
+ # Rate limiting protection
36
+ _last_libre_request = 0
37
+ _MIN_LIBRE_INTERVAL = 0.5 # seconds between LibreTranslate requests
38
+
39
 
40
  def load_model():
41
  global model, tokenizer, device
 
78
 
79
 
80
  def translate_libre_variant(text, variant_code):
81
+ global _last_libre_request
82
+
83
+ # Rate limit protection
84
+ elapsed = time.time() - _last_libre_request
85
+ if elapsed < _MIN_LIBRE_INTERVAL:
86
+ time.sleep(_MIN_LIBRE_INTERVAL - elapsed)
87
+ _last_libre_request = time.time()
88
+
89
  try:
90
  r = requests.post(
91
  LIBRETRANSLATE_URL,
 
96
  r.raise_for_status()
97
  result = r.json().get("translatedText", "[Error: No translation]")
98
  return {"success": True, "text": result}
99
+ except requests.exceptions.HTTPError as e:
100
+ if e.response.status_code == 429:
101
+ return {"success": False, "text": "[Error: Rate limited by LibreTranslate]"}
102
+ return {"success": False, "text": f"[Error: {str(e)[:50]}]"}
103
  except Exception as e:
104
  return {"success": False, "text": f"[Error: {str(e)[:50]}]"}
105
 
106
 
107
  def translate_libre_all_variants(text):
108
  results = {}
109
+ # Sequential instead of parallel to avoid rate limiting
110
+ for name, code in KABYLE_VARIANTS.items():
111
+ results[name] = translate_libre_variant(text, code)
112
+ time.sleep(0.3) # Small delay between requests
 
 
 
 
 
 
 
113
  return results
114
 
115
 
 
121
  app = Flask(__name__)
122
  CORS(app, resources={r"/*": {"origins": "*"}})
123
 
124
+ # Global error handler - NEVER return 500
125
  @app.errorhandler(Exception)
126
  def handle_exception(e):
127
  print(f"[GLOBAL ERROR] {type(e).__name__}: {e}", flush=True)
 
153
 
154
 
155
  def _extract_text(data):
 
156
  q = data.get("q", "")
157
  if isinstance(q, list):
158
  return q[0] if q else ""
 
162
  @app.route("/translate", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])
163
  def libretranslate_translate():
164
  """
165
+ Weblate-compatible translation endpoint.
166
+ Uses ONLY MarianMT - no LibreTranslate fallback to avoid 429 errors.
167
+ NEVER returns 500.
168
  """
169
  print(f"[WEblate] === /translate called ({request.method}) ===", flush=True)
170
 
 
171
  if request.method == "GET":
172
  return jsonify({"translatedText": ""}), 200
173
 
 
178
  print(f"[WEblate] Could not read raw body: {e}", flush=True)
179
  raw_data = ""
180
 
 
181
  data = {}
182
  try:
183
  json_data = request.get_json(silent=True, force=True)
184
  if json_data:
 
185
  data = json_data
186
  except Exception as e:
187
  print(f"[WEblate] JSON parse failed: {e}", flush=True)
 
190
  try:
191
  form_data = request.form
192
  if form_data:
 
193
  data = dict(form_data)
194
  except Exception as e:
195
  print(f"[WEblate] Form parse failed: {e}", flush=True)
 
197
  if not data and raw_data:
198
  try:
199
  import json
200
+ data = json.loads(raw_data)
 
 
201
  except Exception as e:
202
  print(f"[WEblate] Manual JSON parse failed: {e}", flush=True)
203
 
 
 
 
204
  text = _extract_text(data)
205
  source = str(data.get("source", "en")).strip() if data else "en"
206
  target = str(data.get("target", "kab")).strip() if data else "kab"
 
208
  print(f"[WEblate] text={text[:50]}, source={source}, target={target}", flush=True)
209
 
210
  if not text:
 
211
  return jsonify({"translatedText": ""}), 200
212
 
 
213
  if source == "auto":
214
  kab_chars = set("ɛƐɣƔṭḍčǧḥṣẓ")
215
  source = "kab" if any(c in text.lower() for c in kab_chars) else "en"
 
216
 
 
217
  if source != "en":
 
218
  return jsonify({"translatedText": ""}), 200
219
 
220
+ # Only use MarianMT - no LibreTranslate fallback to avoid 429s
 
 
 
 
 
 
221
  try:
 
222
  marian_results = translate_marian(text)
 
223
  if marian_results and not marian_results[0].startswith("[Error"):
224
+ return jsonify({"translatedText": marian_results[0]}), 200
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  except Exception as e:
226
+ print(f"[WEblate] MarianMT failed: {e}", flush=True)
227
+
228
+ return jsonify({"translatedText": ""}), 200
 
 
229
 
230
 
231
  @app.route("/languages", methods=["GET"])
 
311
  return jsonify({"error": f"No matching variants for: {variant_filter}"}), 400
312
 
313
  libre_results = {}
314
+ for name, code in variants_to_use.items():
315
+ libre_results[name] = translate_libre_variant(text, code)
316
+ time.sleep(0.3)
317
+
318
+ for name, data in libre_results.items():
319
+ data["code"] = code
 
 
 
 
 
 
 
320
 
321
  result["libre"] = libre_results
322