Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import requests
|
3 |
+
import html
|
4 |
+
import os
|
5 |
+
|
6 |
+
# LibreTranslate instance
|
7 |
+
API_URL = "https://axxam-libretranslate-kabyle.hf.space/translate"
|
8 |
+
API_KEY = os.environ["API_KEY"]
|
9 |
+
|
10 |
+
# Language codes
|
11 |
+
langs = {
|
12 |
+
"English": "en",
|
13 |
+
"French": "fr",
|
14 |
+
"Italian": "it",
|
15 |
+
"Occitan (26000)": "oc_comp2",
|
16 |
+
"Taqbaylit (LTCommunity)": "kab",
|
17 |
+
"Taqbaylit (Tasenselkimt)": "kab_comp",
|
18 |
+
"Taqbaylit (51000)": "kab_comp2",
|
19 |
+
"Taqbaylit (Google)": "google",
|
20 |
+
"Taqbaylit (All Variants)": "kab_all"
|
21 |
+
}
|
22 |
+
|
23 |
+
kabyle_variants = {
|
24 |
+
"Taqbaylit (LTCommunity)": "kab",
|
25 |
+
"Taqbaylit (Tasenselkimt)": "kab_comp",
|
26 |
+
"Taqbaylit (51000)": "kab_comp2",
|
27 |
+
"Taqbaylit (Google)": "google"
|
28 |
+
}
|
29 |
+
|
30 |
+
def highlight_differences(translations_dict):
|
31 |
+
token_lists = {k: v.split() for k, v in translations_dict.items()}
|
32 |
+
max_len = max(len(tokens) for tokens in token_lists.values())
|
33 |
+
aligned = {k: [] for k in token_lists.keys()}
|
34 |
+
|
35 |
+
for i in range(max_len):
|
36 |
+
words_at_i = [token_lists[k][i] if i < len(token_lists[k]) else "" for k in token_lists]
|
37 |
+
if len(set(words_at_i)) == 1:
|
38 |
+
for k in token_lists:
|
39 |
+
aligned[k].append(words_at_i[0])
|
40 |
+
else:
|
41 |
+
for k in token_lists:
|
42 |
+
word = token_lists[k][i] if i < len(token_lists[k]) else ""
|
43 |
+
# Use gold color for highlights that works in both themes
|
44 |
+
highlighted = f"<span style='color:#FFD700; font-weight:bold'>{word}</span>"
|
45 |
+
aligned[k].append(highlighted)
|
46 |
+
|
47 |
+
return {k: " ".join(tokens) for k, tokens in aligned.items()}
|
48 |
+
|
49 |
+
|
50 |
+
def translate(text, source_lang, target_lang):
|
51 |
+
source_code = langs.get(source_lang, "en")
|
52 |
+
target_code = langs.get(target_lang)
|
53 |
+
|
54 |
+
if target_code == "kab_all":
|
55 |
+
# Escape and format the original text with dark-theme friendly styling
|
56 |
+
escaped_text = html.escape(text).replace('\n', '<br>')
|
57 |
+
header = f"""
|
58 |
+
<div style='
|
59 |
+
margin-bottom: 25px;
|
60 |
+
padding: 15px;
|
61 |
+
border-radius: 8px;
|
62 |
+
background-color: rgba(120, 120, 120, 0.15);
|
63 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
64 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
65 |
+
'>
|
66 |
+
<div style='font-size: 0.9em; opacity: 0.8; margin-bottom: 5px;'>ORIGINAL TEXT</div>
|
67 |
+
<div style='font-size: 1.1em;'>{escaped_text}</div>
|
68 |
+
<div style='margin-top: 8px; font-size: 0.85em; opacity: 0.7;'>
|
69 |
+
From: {source_lang} → To: {target_lang}
|
70 |
+
</div>
|
71 |
+
</div>
|
72 |
+
"""
|
73 |
+
|
74 |
+
translations_dict = {}
|
75 |
+
alternatives_dict = {}
|
76 |
+
|
77 |
+
for name, code in kabyle_variants.items():
|
78 |
+
if code == "google":
|
79 |
+
# Fallback source for Google
|
80 |
+
google_source = source_code if source_code in ["en", "fr", "it", "es"] else "fr"
|
81 |
+
params = {
|
82 |
+
"engine": "google",
|
83 |
+
"from": google_source,
|
84 |
+
"to": "ber-Latn",
|
85 |
+
"text": text
|
86 |
+
}
|
87 |
+
try:
|
88 |
+
response = requests.get("https://mozhi.pussthecat.org/api/translate", params=params)
|
89 |
+
result = response.json()
|
90 |
+
translations_dict[name] = result.get("translated-text", "<i>No result</i>")
|
91 |
+
alternatives_dict[name] = ""
|
92 |
+
except Exception as e:
|
93 |
+
translations_dict[name] = f"<i>Google Error: {str(e)}</i>"
|
94 |
+
alternatives_dict[name] = ""
|
95 |
+
else:
|
96 |
+
data = {
|
97 |
+
"q": text,
|
98 |
+
"source": source_code,
|
99 |
+
"target": code,
|
100 |
+
"format": "text",
|
101 |
+
"alternatives": 3,
|
102 |
+
"api_key": API_KEY
|
103 |
+
}
|
104 |
+
try:
|
105 |
+
response = requests.post(API_URL, data=data, headers={"Content-Type": "application/x-www-form-urlencoded"})
|
106 |
+
result = response.json()
|
107 |
+
|
108 |
+
if "translatedText" in result:
|
109 |
+
translations_dict[name] = result["translatedText"]
|
110 |
+
alternatives_dict[name] = ", ".join(result.get("alternatives", []))
|
111 |
+
elif "error" in result:
|
112 |
+
translations_dict[name] = f"<i>API Error: {result['error']}</i>"
|
113 |
+
alternatives_dict[name] = ""
|
114 |
+
else:
|
115 |
+
translations_dict[name] = "<i>Unexpected response</i>"
|
116 |
+
alternatives_dict[name] = ""
|
117 |
+
except Exception as e:
|
118 |
+
translations_dict[name] = f"<i>Exception: {str(e)}</i>"
|
119 |
+
alternatives_dict[name] = ""
|
120 |
+
|
121 |
+
highlighted = highlight_differences(translations_dict)
|
122 |
+
|
123 |
+
outputs = []
|
124 |
+
for name in kabyle_variants:
|
125 |
+
output = f"<div style='margin-bottom: 15px;'><b>{name}</b>:<br>Translation: {highlighted[name]}"
|
126 |
+
if alternatives_dict.get(name):
|
127 |
+
output += f"<br>Alternatives: <span style='opacity:0.8;'>{alternatives_dict[name]}</span>"
|
128 |
+
output += "</div>"
|
129 |
+
outputs.append(output)
|
130 |
+
|
131 |
+
return header + "".join(outputs)
|
132 |
+
|
133 |
+
elif target_code == "google":
|
134 |
+
google_source = source_code if source_code in ["en", "fr", "it", "es"] else "fr"
|
135 |
+
params = {
|
136 |
+
"engine": "google",
|
137 |
+
"from": google_source,
|
138 |
+
"to": "ber-Latn",
|
139 |
+
"text": text
|
140 |
+
}
|
141 |
+
try:
|
142 |
+
response = requests.get("https://mozhi.pussthecat.org/api/translate", params=params)
|
143 |
+
result = response.json()
|
144 |
+
return f"<b>Translation:</b> {result.get('translated-text', '<i>No result</i>')}"
|
145 |
+
except Exception as e:
|
146 |
+
return f"<i>Google Error: {str(e)}</i>"
|
147 |
+
|
148 |
+
else:
|
149 |
+
data = {
|
150 |
+
"q": text,
|
151 |
+
"source": source_code,
|
152 |
+
"target": target_code,
|
153 |
+
"format": "text",
|
154 |
+
"alternatives": 3,
|
155 |
+
"api_key": API_KEY
|
156 |
+
}
|
157 |
+
|
158 |
+
try:
|
159 |
+
response = requests.post(API_URL, data=data, headers={"Content-Type": "application/x-www-form-urlencoded"})
|
160 |
+
result = response.json()
|
161 |
+
|
162 |
+
if "translatedText" in result:
|
163 |
+
output = f"<b>Translation:</b> {result['translatedText']}"
|
164 |
+
if result.get("alternatives"):
|
165 |
+
alt = ", ".join(result["alternatives"])
|
166 |
+
output += f"<br><b>Alternatives:</b> {alt}"
|
167 |
+
return output
|
168 |
+
elif "error" in result:
|
169 |
+
return f"<i>API Error: {result['error']}</i>"
|
170 |
+
else:
|
171 |
+
return f"<i>Unexpected response: {result}</i>"
|
172 |
+
except Exception as e:
|
173 |
+
return f"<i>Exception: {str(e)}</i>"
|
174 |
+
|
175 |
+
|
176 |
+
# Gradio UI with dark theme friendly styling
|
177 |
+
with gr.Blocks(theme=gr.themes.Default()) as demo:
|
178 |
+
gr.Markdown("# Kabyle Translator")
|
179 |
+
gr.Markdown("Compare Kabyle translations from LibreTranslate (variants) and Google Translate via Mozhi. Differences are highlighted.")
|
180 |
+
|
181 |
+
with gr.Row():
|
182 |
+
with gr.Column():
|
183 |
+
input_text = gr.Textbox(label="Enter text to translate", lines=3)
|
184 |
+
source_lang = gr.Dropdown(choices=list(langs.keys()), label="From language", value="English")
|
185 |
+
target_lang = gr.Dropdown(choices=list(langs.keys()), label="To language", value="Taqbaylit (All Variants)")
|
186 |
+
submit_btn = gr.Button("Translate")
|
187 |
+
|
188 |
+
with gr.Column():
|
189 |
+
output = gr.HTML(label="Translation Results")
|
190 |
+
|
191 |
+
submit_btn.click(
|
192 |
+
fn=translate,
|
193 |
+
inputs=[input_text, source_lang, target_lang],
|
194 |
+
outputs=output
|
195 |
+
)
|
196 |
+
|
197 |
+
demo.launch()
|