Skip to content

Commit 33f3bb0

Browse files
authored
Create Machine-Translation-Tool.py
1 parent edc6a4b commit 33f3bb0

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import threading
2+
import tkinter as tk
3+
from tkinter import messagebox, filedialog
4+
from dataclasses import dataclass
5+
from typing import List
6+
import csv
7+
import os
8+
9+
import ttkbootstrap as tb
10+
from ttkbootstrap.constants import *
11+
from googletrans import Translator, LANGUAGES
12+
from tkinterdnd2 import TkinterDnD, DND_FILES
13+
14+
# ---------------- GLOBAL STATE ---------------- #
15+
all_translations: List["TranslationResult"] = []
16+
translator = Translator()
17+
18+
# ---------------- DATA STRUCTURE ---------------- #
19+
@dataclass
20+
class TranslationResult:
21+
original_text: str
22+
translated_text: str
23+
src_lang: str
24+
dest_lang: str
25+
26+
# ---------------- HELPERS ---------------- #
27+
def cap(text: str) -> str:
28+
return text[:1].upper() + text[1:] if text else text
29+
30+
def copy_to_clipboard(text: str):
31+
app.clipboard_clear()
32+
app.clipboard_append(text)
33+
messagebox.showinfo("Copied", "Translation copied to clipboard")
34+
35+
# ---------------- DRAG & DROP ---------------- #
36+
def drop_text_file(event):
37+
files = app.tk.splitlist(event.data)
38+
for file in files:
39+
if file.lower().endswith(".txt"):
40+
try:
41+
with open(file, "r", encoding="utf-8") as f:
42+
content = f.read()
43+
text_entry.delete("1.0", END)
44+
text_entry.insert("1.0", content)
45+
except Exception as e:
46+
messagebox.showerror("File Error", str(e))
47+
48+
# ---------------- EXPORT ---------------- #
49+
def export_translations(fmt: str):
50+
if not all_translations:
51+
messagebox.showwarning("No Data", "No translations to export")
52+
return
53+
54+
filetypes = [("Text File", "*.txt")] if fmt == "txt" else [("CSV File", "*.csv")]
55+
path = filedialog.asksaveasfilename(defaultextension=f".{fmt}", filetypes=filetypes)
56+
57+
if not path:
58+
return
59+
60+
try:
61+
if fmt == "txt":
62+
with open(path, "w", encoding="utf-8") as f:
63+
for r in all_translations:
64+
f.write(f"Source ({cap(LANGUAGES[r.src_lang])})\n")
65+
f.write(f"Original: {r.original_text}\n")
66+
f.write(f"Target ({cap(LANGUAGES[r.dest_lang])})\n")
67+
f.write(f"Translated: {r.translated_text}\n")
68+
f.write("-" * 60 + "\n")
69+
else:
70+
with open(path, "w", newline="", encoding="utf-8") as f:
71+
writer = csv.writer(f)
72+
writer.writerow(["Source Language", "Target Language", "Original", "Translated"])
73+
for r in all_translations:
74+
writer.writerow([
75+
cap(LANGUAGES[r.src_lang]),
76+
cap(LANGUAGES[r.dest_lang]),
77+
r.original_text,
78+
r.translated_text
79+
])
80+
81+
messagebox.showinfo("Exported", "Translations exported successfully")
82+
except Exception as e:
83+
messagebox.showerror("Export Error", str(e))
84+
85+
# ---------------- TRANSLATION ---------------- #
86+
def fetch_translations(text, langs):
87+
results = []
88+
try:
89+
detected = translator.detect(text).lang
90+
for lang in langs:
91+
t = translator.translate(text, src=detected, dest=lang)
92+
results.append(
93+
TranslationResult(text, t.text, detected, lang)
94+
)
95+
except Exception as e:
96+
messagebox.showerror("Translation Error", str(e))
97+
return results
98+
99+
# ---------------- DISPLAY ---------------- #
100+
def display_results():
101+
for w in results_frame.winfo_children():
102+
w.destroy()
103+
104+
for r in all_translations:
105+
card = tb.Frame(results_frame, padding=15)
106+
card.pack(fill=X, pady=8)
107+
108+
tb.Label(
109+
card,
110+
text=f"Original ({cap(LANGUAGES[r.src_lang])})",
111+
font=("Segoe UI", 11)
112+
).pack(anchor=W)
113+
114+
tb.Label(
115+
card,
116+
text=r.original_text,
117+
wraplength=900
118+
).pack(anchor=W)
119+
120+
row = tb.Frame(card)
121+
row.pack(fill=X, pady=5)
122+
123+
tb.Label(
124+
row,
125+
text=f"{cap(LANGUAGES[r.dest_lang])}: {r.translated_text}",
126+
font=("Segoe UI", 14, "bold"),
127+
wraplength=800
128+
).pack(side=LEFT)
129+
130+
tb.Button(
131+
row,
132+
text="📋 Copy",
133+
command=lambda t=r.translated_text: copy_to_clipboard(t)
134+
).pack(side=LEFT, padx=10)
135+
136+
# ---------------- SEARCH FILTER ---------------- #
137+
def filter_languages(*_):
138+
query = search_var.get().lower()
139+
lang_listbox.delete(0, END)
140+
for lang in all_language_names:
141+
if query in lang.lower():
142+
lang_listbox.insert(END, lang)
143+
144+
# ---------------- RUN TRANSLATION ---------------- #
145+
def perform_translation():
146+
text = text_entry.get("1.0", END).strip()
147+
selections = lang_listbox.curselection()
148+
149+
if not text or not selections:
150+
messagebox.showwarning("Input Required", "Enter text and select languages")
151+
return
152+
153+
langs = []
154+
for i in selections:
155+
name = lang_listbox.get(i)
156+
for code, lang in LANGUAGES.items():
157+
if cap(lang) == name:
158+
langs.append(code)
159+
160+
threading.Thread(
161+
target=run_translation,
162+
args=(text, langs),
163+
daemon=True
164+
).start()
165+
166+
def run_translation(text, langs):
167+
global all_translations
168+
all_translations = fetch_translations(text, langs)
169+
app.after(0, display_results)
170+
171+
# ---------------- UI ---------------- #
172+
app = TkinterDnD.Tk()
173+
app.title("🌐 Machine Translation Tool")
174+
app.geometry("1000x780")
175+
176+
style = tb.Style("flatly")
177+
178+
top = tb.Frame(app, padding=15)
179+
top.pack(fill=X)
180+
181+
tb.Label(
182+
top,
183+
text="🌐 Machine Translation Tool",
184+
font=("Segoe UI", 18, "bold")
185+
).pack(anchor=W)
186+
187+
text_entry = tk.Text(top, height=5, font=("Segoe UI", 12))
188+
text_entry.pack(fill=X, pady=8)
189+
190+
# Drag & Drop
191+
text_entry.drop_target_register(DND_FILES)
192+
text_entry.dnd_bind("<<Drop>>", drop_text_file)
193+
194+
tb.Label(top, text="Search Languages:", font=("Segoe UI", 11)).pack(anchor=W)
195+
196+
search_var = tk.StringVar()
197+
search_var.trace_add("write", filter_languages)
198+
199+
search_entry = tb.Entry(top, textvariable=search_var)
200+
search_entry.pack(fill=X, pady=5)
201+
202+
lang_listbox = tk.Listbox(top, selectmode=MULTIPLE, height=8)
203+
lang_listbox.pack(fill=X)
204+
205+
all_language_names = sorted(cap(v) for v in LANGUAGES.values())
206+
for l in all_language_names:
207+
lang_listbox.insert(END, l)
208+
209+
btn_row = tb.Frame(top)
210+
btn_row.pack(fill=X, pady=5)
211+
212+
tb.Button(btn_row, text="Translate", bootstyle="success", command=perform_translation).pack(side=LEFT)
213+
tb.Button(btn_row, text="Export TXT", command=lambda: export_translations("txt")).pack(side=LEFT, padx=5)
214+
215+
# ---------------- RESULTS (SCROLL ONLY) ---------------- #
216+
results_container = tb.Frame(app)
217+
results_container.pack(fill=BOTH, expand=True)
218+
219+
canvas = tk.Canvas(results_container)
220+
scroll = tb.Scrollbar(results_container, command=canvas.yview)
221+
results_frame = tb.Frame(canvas)
222+
223+
canvas.create_window((0, 0), window=results_frame, anchor="nw")
224+
canvas.configure(yscrollcommand=scroll.set)
225+
226+
results_frame.bind(
227+
"<Configure>",
228+
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
229+
)
230+
231+
canvas.pack(side=LEFT, fill=BOTH, expand=True)
232+
scroll.pack(side=RIGHT, fill=Y)
233+
234+
app.mainloop()

0 commit comments

Comments
 (0)