Spaces:
Sleeping
Sleeping
| """ | |
| Free Translator β Hugging Face Spaces | |
| Google Translate (unofficial, no API key) + MyMemory fallback | |
| Added: 70+ languages, light theme, better readability | |
| """ | |
| import json | |
| import urllib.request | |
| import urllib.parse | |
| import urllib.error | |
| import gradio as gr | |
| # βββ Language list (70+ languages) βββββββββββββββββββββββββββββββββββββββββββ | |
| LANGUAGES = [ | |
| ("Albanian", "sq"), ("Amharic", "am"), ("Arabic", "ar"), ("Armenian", "hy"), | |
| ("Azerbaijani", "az"), ("Basque", "eu"), ("Belarusian", "be"), ("Bengali", "bn"), | |
| ("Bosnian", "bs"), ("Bulgarian", "bg"), ("Burmese", "my"), ("Catalan", "ca"), | |
| ("Cebuano", "ceb"), ("Chinese (Simplified)", "zh-CN"), ("Chinese (Traditional)", "zh-TW"), | |
| ("Croatian", "hr"), ("Czech", "cs"), ("Danish", "da"), ("Dutch", "nl"), | |
| ("English", "en"), ("Esperanto", "eo"), ("Estonian", "et"), ("Finnish", "fi"), | |
| ("French", "fr"), ("Galician", "gl"), ("Georgian", "ka"), ("German", "de"), | |
| ("Greek", "el"), ("Gujarati", "gu"), ("Haitian Creole", "ht"), ("Hausa", "ha"), | |
| ("Hebrew", "he"), ("Hindi", "hi"), ("Hmong", "hmn"), ("Hungarian", "hu"), | |
| ("Icelandic", "is"), ("Igbo", "ig"), ("Indonesian", "id"), ("Irish", "ga"), | |
| ("Italian", "it"), ("Japanese", "ja"), ("Javanese", "jw"), ("Kannada", "kn"), | |
| ("Kazakh", "kk"), ("Khmer", "km"), ("Korean", "ko"), ("Kurdish", "ku"), | |
| ("Kyrgyz", "ky"), ("Lao", "lo"), ("Latin", "la"), ("Latvian", "lv"), | |
| ("Lithuanian", "lt"), ("Luxembourgish", "lb"), ("Macedonian", "mk"), ("Malagasy", "mg"), | |
| ("Malay", "ms"), ("Malayalam", "ml"), ("Maltese", "mt"), ("Maori", "mi"), | |
| ("Marathi", "mr"), ("Mongolian", "mn"), ("Nepali", "ne"), ("Norwegian", "no"), | |
| ("Pashto", "ps"), ("Persian (Farsi)", "fa"), ("Polish", "pl"), ("Portuguese", "pt"), | |
| ("Punjabi", "pa"), ("Romanian", "ro"), ("Russian", "ru"), ("Samoan", "sm"), | |
| ("Scottish Gaelic", "gd"), ("Serbian", "sr"), ("Sesotho", "st"), ("Shona", "sn"), | |
| ("Sindhi", "sd"), ("Sinhala", "si"), ("Slovak", "sk"), ("Slovenian", "sl"), | |
| ("Somali", "so"), ("Spanish", "es"), ("Sundanese", "su"), ("Swahili", "sw"), | |
| ("Swedish", "sv"), ("Tagalog (Filipino)", "tl"), ("Tajik", "tg"), ("Tamil", "ta"), | |
| ("Telugu", "te"), ("Thai", "th"), ("Turkish", "tr"), ("Ukrainian", "uk"), | |
| ("Urdu", "ur"), ("Uzbek", "uz"), ("Vietnamese", "vi"), ("Welsh", "cy"), | |
| ("Xhosa", "xh"), ("Yiddish", "yi"), ("Yoruba", "yo"), ("Zulu", "zu"), | |
| ] | |
| # Sort alphabetically by language name | |
| LANGUAGES = sorted(LANGUAGES, key=lambda x: x[0]) | |
| LANG_NAMES = [l[0] for l in LANGUAGES] | |
| LANG_CODE = {l[0]: l[1] for l in LANGUAGES} | |
| CHUNK_SIZE = 4500 | |
| UA = ( | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " | |
| "AppleWebKit/537.36 (KHTML, like Gecko) " | |
| "Chrome/120.0.0.0 Safari/537.36" | |
| ) | |
| # βββ Translation engines (unchanged) βββββββββββββββββββββββββββββββββββββββββ | |
| def google_translate(text: str, src: str, tgt: str) -> str: | |
| url = "https://translate.googleapis.com/translate_a/single" | |
| params = urllib.parse.urlencode({ | |
| "client": "gtx", "sl": src, "tl": tgt, "dt": "t", "q": text, | |
| }) | |
| req = urllib.request.Request(f"{url}?{params}", headers={"User-Agent": UA}) | |
| with urllib.request.urlopen(req, timeout=15) as r: | |
| data = json.loads(r.read().decode("utf-8")) | |
| return "".join(seg[0] for seg in data[0] if seg[0]) | |
| def mymemory_translate(text: str, src: str, tgt: str) -> str: | |
| url = "https://api.mymemory.translated.net/get" | |
| params = urllib.parse.urlencode({"q": text, "langpair": f"{src}|{tgt}"}) | |
| req = urllib.request.Request(f"{url}?{params}", headers={"User-Agent": "Mozilla/5.0"}) | |
| with urllib.request.urlopen(req, timeout=15) as r: | |
| data = json.loads(r.read().decode("utf-8")) | |
| if data.get("responseStatus") == 200: | |
| return data["responseData"]["translatedText"] | |
| raise RuntimeError(data.get("responseDetails", "MyMemory error")) | |
| def split_chunks(text: str, size: int) -> list[str]: | |
| if len(text) <= size: | |
| return [text] | |
| chunks = [] | |
| while text: | |
| if len(text) <= size: | |
| chunks.append(text) | |
| break | |
| cut = size | |
| for sep in ("\n\n", "\n", ". ", "γ", " "): | |
| idx = text.rfind(sep, 0, cut) | |
| if idx != -1: | |
| cut = idx + len(sep) | |
| break | |
| chunks.append(text[:cut]) | |
| text = text[cut:] | |
| return chunks | |
| def do_translate(text: str, src_name: str, tgt_name: str) -> str: | |
| text = (text or "").strip() | |
| if not text: | |
| return "" | |
| src = LANG_CODE.get(src_name, "en") | |
| tgt = LANG_CODE.get(tgt_name, "vi") | |
| if src == tgt: | |
| return text | |
| chunks = split_chunks(text, CHUNK_SIZE) | |
| parts = [] | |
| for chunk in chunks: | |
| try: | |
| parts.append(google_translate(chunk, src, tgt)) | |
| except Exception: | |
| try: | |
| parts.append(mymemory_translate(chunk, src, tgt)) | |
| except Exception as e: | |
| parts.append(f"[Lα»i: {e}]") | |
| return "".join(parts) | |
| def swap_and_translate(src_name, tgt_name, src_text, tgt_text): | |
| new_src_text = tgt_text | |
| result = do_translate(new_src_text, tgt_name, src_name) | |
| return tgt_name, src_name, new_src_text, result | |
| def char_count(text: str) -> str: | |
| n = len(text or "") | |
| chunks = max(1, (n + CHUNK_SIZE - 1) // CHUNK_SIZE) | |
| if n == 0: | |
| return "0 kΓ½ tα»±" | |
| if n > CHUNK_SIZE: | |
| return f"{n:,} kΓ½ tα»± Β· {chunks} phαΊ§n" | |
| return f"{n:,} kΓ½ tα»±" | |
| # βββ Light Mode CSS (easy to read, not dark) βββββββββββββββββββββββββββββββββ | |
| CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500&display=swap'); | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body, .gradio-container { | |
| background: #f8fafc !important; | |
| font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, sans-serif !important; | |
| color: #0f172a !important; | |
| } | |
| /* header */ | |
| #header { | |
| text-align: center; | |
| padding: 48px 24px 32px; | |
| position: relative; | |
| } | |
| #header h1 { | |
| font-size: clamp(2.2rem, 6vw, 3.5rem) !important; | |
| font-weight: 700 !important; | |
| letter-spacing: -1px !important; | |
| background: linear-gradient(135deg, #1e293b 0%, #3b82f6 50%, #0f172a 100%); | |
| -webkit-background-clip: text !important; | |
| -webkit-text-fill-color: transparent !important; | |
| background-clip: text !important; | |
| margin: 0 0 8px !important; | |
| } | |
| #header p { | |
| color: #475569 !important; | |
| font-family: 'JetBrains Mono', monospace !important; | |
| font-size: 13px !important; | |
| letter-spacing: 0.3px !important; | |
| } | |
| .badge-row { | |
| display: flex; | |
| justify-content: center; | |
| gap: 12px; | |
| margin-top: 20px; | |
| flex-wrap: wrap; | |
| } | |
| .badge { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 11px; | |
| font-weight: 500; | |
| padding: 5px 14px; | |
| border-radius: 40px; | |
| background: #ffffff; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.03), 0 1px 3px rgba(0,0,0,0.05); | |
| border: 1px solid #e2e8f0; | |
| } | |
| .badge-green { color: #15803d; border-left: 3px solid #22c55e; } | |
| .badge-blue { color: #1e40af; border-left: 3px solid #3b82f6; } | |
| .badge-purple { color: #6b21a5; border-left: 3px solid #a855f7; } | |
| /* main card */ | |
| #main-panel { | |
| background: #ffffff; | |
| border: 1px solid #e2e8f0; | |
| border-radius: 28px; | |
| padding: 28px; | |
| margin: 0 auto 32px; | |
| max-width: 1200px; | |
| box-shadow: 0 8px 20px rgba(0,0,0,0.02), 0 2px 6px rgba(0,0,0,0.05); | |
| transition: box-shadow 0.2s; | |
| } | |
| #main-panel:hover { | |
| box-shadow: 0 20px 35px -12px rgba(0,0,0,0.08); | |
| } | |
| /* labels */ | |
| label, .label-wrap span { | |
| font-family: 'JetBrains Mono', monospace !important; | |
| font-size: 11px !important; | |
| font-weight: 500 !important; | |
| letter-spacing: 0.5px !important; | |
| color: #334155 !important; | |
| text-transform: uppercase !important; | |
| margin-bottom: 8px !important; | |
| } | |
| /* textareas */ | |
| textarea, .scroll-hide { | |
| background: #ffffff !important; | |
| color: #0f172a !important; | |
| border: 1.5px solid #e2e8f0 !important; | |
| border-radius: 18px !important; | |
| font-family: 'Inter', monospace !important; | |
| font-size: 15px !important; | |
| line-height: 1.6 !important; | |
| padding: 16px 18px !important; | |
| resize: vertical !important; | |
| transition: all 0.2s ease !important; | |
| box-shadow: 0 1px 2px rgba(0,0,0,0.02) !important; | |
| } | |
| textarea:focus { | |
| border-color: #3b82f6 !important; | |
| outline: none !important; | |
| box-shadow: 0 0 0 3px rgba(59,130,246,0.15) !important; | |
| } | |
| textarea[readonly] { | |
| background: #f9f9fc !important; | |
| color: #1e293b !important; | |
| border-color: #e2e8f0 !important; | |
| } | |
| /* dropdowns */ | |
| .wrap-inner, select, .multiselect { | |
| background: #ffffff !important; | |
| color: #0f172a !important; | |
| border: 1.5px solid #e2e8f0 !important; | |
| border-radius: 16px !important; | |
| font-family: 'Inter', monospace !important; | |
| font-size: 13px !important; | |
| font-weight: 500 !important; | |
| padding: 10px 14px !important; | |
| } | |
| .wrap-inner:hover, select:hover { | |
| border-color: #94a3b8 !important; | |
| } | |
| /* buttons */ | |
| button, .btn { | |
| font-family: 'Inter', sans-serif !important; | |
| font-weight: 600 !important; | |
| border-radius: 40px !important; | |
| transition: all 0.2s ease !important; | |
| cursor: pointer !important; | |
| } | |
| #translate-btn button, button.primary { | |
| background: #0f172a !important; | |
| color: white !important; | |
| border: none !important; | |
| padding: 12px 32px !important; | |
| font-size: 14px !important; | |
| box-shadow: 0 2px 5px rgba(0,0,0,0.05), 0 1px 2px rgba(0,0,0,0.03) !important; | |
| } | |
| #translate-btn button:hover, button.primary:hover { | |
| background: #1e293b !important; | |
| transform: translateY(-1px); | |
| box-shadow: 0 10px 20px -8px rgba(15,23,42,0.2); | |
| } | |
| #swap-btn button, button.secondary { | |
| background: #ffffff !important; | |
| color: #1e293b !important; | |
| border: 1.5px solid #e2e8f0 !important; | |
| padding: 10px 18px !important; | |
| font-size: 20px !important; | |
| font-weight: 500 !important; | |
| } | |
| #swap-btn button:hover { | |
| background: #f1f5f9 !important; | |
| border-color: #cbd5e1 !important; | |
| transform: rotate(180deg) scale(1.02); | |
| } | |
| #clear-btn button { | |
| background: #ffffff !important; | |
| color: #475569 !important; | |
| border: 1.5px solid #e2e8f0 !important; | |
| padding: 8px 20px !important; | |
| font-size: 13px !important; | |
| } | |
| #clear-btn button:hover { | |
| color: #dc2626 !important; | |
| border-color: #fecaca !important; | |
| background: #fef2f2 !important; | |
| } | |
| /* char counter */ | |
| #char-count { | |
| font-family: 'JetBrains Mono', monospace !important; | |
| font-size: 11px !important; | |
| color: #475569 !important; | |
| text-align: right !important; | |
| margin-top: 8px; | |
| } | |
| /* divider */ | |
| .divider { | |
| height: 1px; | |
| background: #eef2ff; | |
| margin: 20px 0; | |
| } | |
| /* footer */ | |
| #footer { | |
| text-align: center; | |
| padding: 0 0 40px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 11px; | |
| color: #64748b; | |
| letter-spacing: 0.3px; | |
| } | |
| /* scrollbar */ | |
| ::-webkit-scrollbar { width: 6px; height: 6px; } | |
| ::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 10px; } | |
| ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; } | |
| ::-webkit-scrollbar-thumb:hover { background: #94a3b8; } | |
| /* responsive */ | |
| @media (max-width: 720px) { | |
| #main-panel { padding: 18px; margin: 0 12px 24px; } | |
| #header { padding: 32px 16px 20px; } | |
| .badge { font-size: 9px; padding: 3px 10px; } | |
| } | |
| """ | |
| # βββ Gradio UI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build_app(): | |
| with gr.Blocks(css=CSS, title="Free Translator", theme=gr.themes.Soft()) as demo: | |
| gr.HTML(""" | |
| <div id="header"> | |
| <h1>π Free Translator</h1> | |
| <p>Google Translate (unofficial) Β· 70+ languages Β· No API key</p> | |
| <div class="badge-row"> | |
| <span class="badge badge-green">β Free & unlimited</span> | |
| <span class="badge badge-blue">β‘ Fast & reliable</span> | |
| <span class="badge badge-purple">π MyMemory fallback</span> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Group(elem_id="main-panel"): | |
| with gr.Row(elem_id="lang-row"): | |
| src_combo = gr.Dropdown( | |
| choices=LANG_NAMES, | |
| value="English", | |
| label="FROM", | |
| scale=2, | |
| interactive=True, | |
| ) | |
| swap_btn = gr.Button("β", elem_id="swap-btn", scale=0, min_width=70) | |
| tgt_combo = gr.Dropdown( | |
| choices=LANG_NAMES, | |
| value="Vietnamese", | |
| label="TO", | |
| scale=2, | |
| interactive=True, | |
| ) | |
| gr.HTML('<div class="divider"></div>') | |
| with gr.Row(): | |
| with gr.Column(): | |
| src_text = gr.Textbox( | |
| label="INPUT", | |
| placeholder="Paste or type text to translate...", | |
| lines=14, | |
| max_lines=30, | |
| show_copy_button=True, | |
| interactive=True, | |
| ) | |
| char_lbl = gr.Markdown("0 kΓ½ tα»±", elem_id="char-count") | |
| with gr.Column(): | |
| tgt_text = gr.Textbox( | |
| label="TRANSLATION", | |
| placeholder="Translation will appear here...", | |
| lines=14, | |
| max_lines=30, | |
| interactive=True, | |
| show_copy_button=True, | |
| ) | |
| gr.HTML('<div class="divider"></div>') | |
| with gr.Row(): | |
| clear_btn = gr.Button("β Clear", elem_id="clear-btn", scale=0, min_width=120) | |
| translate_btn = gr.Button("Translate β", elem_id="translate-btn", variant="primary", scale=0, min_width=180) | |
| gr.HTML(""" | |
| <div id="footer"> | |
| Powered by Google Translate Β· MyMemory backup Β· Made with Gradio | |
| </div> | |
| """) | |
| # Events | |
| src_text.change(fn=char_count, inputs=src_text, outputs=char_lbl) | |
| translate_btn.click(fn=do_translate, inputs=[src_text, src_combo, tgt_combo], outputs=tgt_text) | |
| src_text.submit(fn=do_translate, inputs=[src_text, src_combo, tgt_combo], outputs=tgt_text) | |
| swap_btn.click(fn=swap_and_translate, inputs=[src_combo, tgt_combo, src_text, tgt_text], | |
| outputs=[src_combo, tgt_combo, src_text, tgt_text]) | |
| clear_btn.click(fn=lambda: ("", "", "0 kΓ½ tα»±"), outputs=[src_text, tgt_text, char_lbl]) | |
| return demo | |
| if __name__ == "__main__": | |
| app = build_app() | |
| app.launch(server_name="0.0.0.0", server_port=7860, share=True) |