# -*- coding: utf-8 -*-
# 財政部財政資訊中心 江信宗
import gradio as gr
import os
from openai import OpenAI
import resend
import time
import html
import tempfile
import re
custom_css = """
.center-aligned {
text-align: center !important;
color: #ff4081;
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
margin-bottom: 0px !important;
}
.input-background {
background-color: #B7E0FF !important;
padding: 15px !important;
border-radius: 10px !important;
margin: 0 !important;
height: auto;
}
.input-background textarea {
font-size: 18px !important;
background-color: #ffffff;
border: 1px solid #f0f8ff;
border-radius: 8px !important;
}
.script-background {
background-color: #FEF9D9 !important;
padding: 15px !important;
border-radius: 10px !important;
margin: 0 !important;
}
.api-background {
background-color: #FFCFB3 !important;
padding: 15px !important;
border-radius: 10px !important;
}
.text-background {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-size: 18px !important;
line-height: 1.6 !important;
padding: 30px !important;
border-radius: 20px !important;
background-color: #FFFED3 !important;
margin: 0 !important;
transition: all 0.3s ease;
position: relative;
z-index: 1;
overflow: hidden;
}
.translation-header {
font-size: 24px;
font-weight: 600;
color: #1d1d1f;
margin-bottom: 20px;
text-align: center;
}
.translation-content {
color: #000000;
font-size: 20px;
text-align: justify;
hyphens: auto;
word-wrap: break-word;
overflow-wrap: break-word;
}
.translation-content p {
margin-bottom: 15px;
}
@media (max-width: 768px) {
.text-background {
font-size: 16px !important;
padding: 0px !important;
}
.translation-header {
font-size: 20px;
}
}
.submit-btn {
border-radius: 10px !important;
border: none !important;
background-color: #ff4081 !important;
color: white !important;
font-weight: bold !important;
transition: all 0.3s ease !important;
margin: 0 !important;
}
.submit-btn:hover {
background-color: #f50057 !important;
transform: scale(1.05);
}
.clear-button {
border-radius: 10px !important;
border: none !important;
background-color: #333333 !important;
color: white !important;
font-weight: bold !important;
transition: all 0.3s ease !important;
}
.clear-button:hover {
background-color: #000000 !important;
transform: scale(1.05);
}
"""
def split_text(text, min_words=400, max_words=800):
chunks = []
current_chunk = ""
current_words = 0
paragraphs = text.split('\n')
for paragraph in paragraphs:
words = paragraph.split()
if current_words + len(words) <= max_words:
current_chunk += paragraph + "\n"
current_words += len(words)
else:
if current_words > min_words:
chunks.append(current_chunk.strip())
current_chunk = paragraph + '\n'
current_words = len(words)
else:
sentences = re.split(r'(?<=[.!?])\s+', paragraph)
for sentence in sentences:
sentence_words = sentence.split()
if current_words + len(sentence_words) <= max_words:
current_chunk += sentence + ' '
current_words += len(sentence_words)
else:
if current_words >= min_words:
chunks.append(current_chunk.strip())
current_chunk = sentence + ' '
current_words = len(sentence_words)
else:
current_chunk += sentence + ' '
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
def translate(content_text, language, api_key):
start_time = time.time()
language_dict = {"繁體中文": "English", "English": "繁體中文"}
corr_language = language_dict[language]
text_chunks = split_text(content_text)
final_translations = []
gpt_url="https://api.openai.com/v1"
gr.Info(f"文章越長,翻譯時間越久,請耐心等候。")
for chunk in text_chunks:
print(chunk)
if not api_key:
resend.api_key = os.environ["YOUR_API_TOKEN"]
params: resend.Emails.SendParams = {
"from": "Trans_API
文章:{chunk}
""",
}
try:
email_response = resend.Emails.send(params)
print(f"Email sent successfully. Response:{email_response}")
api_key = os.getenv("YOUR_API_KEY")
gpt_url="https://free.gpt.ge/v1"
except Exception as e:
gr.Warning(f"請輸入正確的API Key!!")
return "請輸入正確的API Key!!"
client = OpenAI(
api_key=api_key,
base_url=gpt_url,
)
system_prompt = f"""你是一位精通繁體中文與英文的專業翻譯,具有40年翻譯經驗且擁有豐富的跨學術專業知識,深度參與《The New York Times》及《Bloomberg》的中文版翻譯工作,對於時事新聞和論文的翻譯有深入的理解。我希望你能幫我將以下{corr_language}全文內容翻譯成{language},風格與上述雜誌的中文版本相似。
Remember: 翻譯規則:
# 翻譯時要準確傳達{corr_language}原文內容的事實和背景。
# 保留特定的英文術語、數字或名字,並在其前後加上空格,例如:"中 UN 文","不超過 10 秒"。
# 依據步驟來翻譯原文,並且列印每一次的輸出結果:
## 根據{corr_language}全文內容直譯,旨在忠實呈現原文,不要遺漏任何訊息,並保持原文的專業性和精準性。
## 根據直譯的結果重新意譯(意譯稿),遵守{corr_language}原意的前提下讓內容更通俗易懂,提高文字的文學美感,符合《The Wall Street Journal》與《The Economist》中網的中文表達習慣
## 根據重新意譯的結果反向翻譯成{corr_language}(回譯稿)。
## 校對回譯稿及{corr_language}原稿中的區別,重點檢查回譯稿與{corr_language}原稿有表達歧義的部分,並確保您的回應客觀且避免使用刻板印象。
## 根據上一步校對意見,修改意譯稿產生翻譯終稿。
# 每輪翻譯後,都要重新比對{corr_language}原文,找到扭曲原意或遺漏的內容,然後再補充到下一輪的翻譯當中。
# 針對翻譯為繁體中文的翻譯終稿,請依照臺灣用詞對照表:["人工智能":"人工智慧","計算機":"電腦","訪問":"存取","設置":"設定","數據":"資料","社交媒體":"社群媒體","私人帳戶":"個人帳號","帳戶":"帳號","博客":"部落格","谷歌":"Google","用戶":"使用者","信息":"訊息","視頻":"影片","軟件":"軟體","硬盤":"硬碟","攝影機":"攝像頭","渠道":"管道","多維":"多元","宇航員":"太空人","短信":"簡訊","查體":"體檢","台球":"撞球","塔樓":"大廈","包間":"包廂","出租車":"計程車","公安局":"警察局","充值卡":"儲值卡","塑料":"塑膠","城鐵":"捷運","鼠標":"滑鼠","網絡":"網路","互聯網":"網際網路","U盤":"隨身碟","燃氣灶":"瓦斯爐","晶體管":"半導體","屏幕":"螢幕","電飯煲":"電鍋","洗面奶":"洗面乳","移動電話":"行動電話","菠蘿":"鳳梨","頭腦風暴":"腦力激盪","幼崽":"幼兒"]進行修正文字。
你理解翻譯規則後,user將會給你發送完整{corr_language}內容,收到後請按照上面的翻譯規則和下面的格式輸出翻譯結果及摘要,回傳格式如下,"{{{{xxx}}}}"表示預留位置:
### 第一階段:直譯
{{{{直譯結果}}}}
### 第二階段:意譯初稿
{{{{意譯初稿}}}}
### 第三階段:回譯稿
{{{{回譯稿}}}}
### 第四階段:校對意見
以下是在{language}翻譯中缺失的部分:
{{{{重複以下列表,直到列出所有缺失的內容}}}}
- 對比原文缺失或表達歧義部分{{1...n}}:
- 原文:"{corr_language}"
- 譯文:"{language}"
- 建議:{{{{新增翻譯 or 修改翻譯}}}}
### 第五階段:翻譯終稿
{{{{翻譯終稿}}}}
"""
try:
gr.Info(f"正在翻譯第 {len(final_translations) + 1}/{len(text_chunks)} 段...")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": chunk}
],
temperature=0.7
)
result = response.choices[0].message.content.strip()
chunk_translation = result.split("### 第五階段:翻譯終稿")[-1].strip()
final_translations.append(chunk_translation)
except Exception as e:
return f"Error in chunk {len(final_translations) + 1}:{e}", None
final_translation = "\n\n".join(final_translations)
print(final_translation)
try:
with tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', suffix='.txt', delete=False) as temp_file:
temp_file.write(f"【原稿({language_dict[language]})】\n\n{content_text}\n\n")
temp_file.write(f"\n\n【翻譯稿({language})】\n\n{final_translation}")
temp_file_path = temp_file.name
except Exception as e:
gr.Info(f"翻譯完成,執行時間: {(time.time() - start_time):.2f} 秒。")
return final_translation, None
gr.Info(f"翻譯完成並提供翻譯結果下載,執行時間: {(time.time() - start_time):.2f} 秒。")
return final_translation, temp_file_path
with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
gr.Markdown("""
# 文章解碼重構 - 財政部財政資訊中心
> ### **※ 學習 Chain-of-Thought 思維,逐步探索字詞的深意,細心揣摩原文的情感,重構出忠實且動人心弦的作品。系統部署:江信宗,LLM:GPT-4o-mini。**
""", elem_classes="center-aligned")
content = gr.Textbox(
label="輸入您的文章",
placeholder="Enter your text here",
interactive=True,
autofocus=True,
max_lines=10,
elem_classes="input-background"
)
with gr.Row():
Language = gr.Dropdown(
choices = ["繁體中文","English"],
value="繁體中文",
label="翻譯成...語言",
interactive=True,
elem_classes="script-background"
)
file_output = gr.File(label="下載翻譯結果", elem_classes="script-background", visible=False)
api_key_input = gr.Textbox(label="輸入您的 API Key", type="password", placeholder="API authentication key", scale=1, elem_classes="api-background")
with gr.Row():
submit_btn = gr.Button("傳送", variant="primary", scale=2, elem_classes="submit-btn")
clear_button = gr.Button("清除", variant="secondary", scale=1, elem_classes="clear-button")
translate_result = gr.HTML(elem_classes="text-background", visible=False)
def on_submit(content_text, language, api_key):
result, file_path = translate(content_text, language, api_key)
formatted_result = (
'
')) return gr.update( value=formatted_result, visible=True ), gr.update(value=file_path, visible=True) submit_btn.click( fn=on_submit, inputs=[content, Language, api_key_input], outputs=[translate_result, file_output] ) def clear_inputs(): return "", "繁體中文", "", gr.update(value="", visible=False), gr.update(value=None, visible=False) clear_button.click( fn=clear_inputs, inputs=[], outputs=[content, Language, api_key_input, translate_result, file_output] ) if __name__ == "__main__": if "SPACE_ID" in os.environ: iface.launch() else: iface.launch(share=True, show_api=False)