Spaces:
Sleeping
Sleeping
Trae Assistant commited on
Commit ·
8743d32
1
Parent(s): 63f129a
优化
Browse files- app.py +2 -2
- static/main.js +36 -152
- templates/index.html +12 -46
app.py
CHANGED
|
@@ -43,8 +43,8 @@ def get_default_data():
|
|
| 43 |
# Provide default data for the frontend
|
| 44 |
return jsonify({
|
| 45 |
"title": "示例文章",
|
| 46 |
-
"content": "
|
| 47 |
-
"translated_content": "
|
| 48 |
})
|
| 49 |
|
| 50 |
if __name__ == "__main__":
|
|
|
|
| 43 |
# Provide default data for the frontend
|
| 44 |
return jsonify({
|
| 45 |
"title": "示例文章",
|
| 46 |
+
"content": "This is a default English sample text to demonstrate page-wide translation. Try uploading a file to populate the content area.",
|
| 47 |
+
"translated_content": ""
|
| 48 |
})
|
| 49 |
|
| 50 |
if __name__ == "__main__":
|
static/main.js
CHANGED
|
@@ -1,163 +1,47 @@
|
|
| 1 |
-
const { createApp, ref
|
| 2 |
|
| 3 |
-
const
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
title: '',
|
| 8 |
-
content: '',
|
| 9 |
-
translated_content: ''
|
| 10 |
-
});
|
| 11 |
-
const errorMessage = ref('');
|
| 12 |
-
const successMessage = ref('');
|
| 13 |
-
const fileInput = ref(null);
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
try {
|
| 18 |
-
const response = await fetch('/api/data');
|
| 19 |
-
if (response.ok) {
|
| 20 |
-
const data = await response.json();
|
| 21 |
-
model.value = data;
|
| 22 |
-
}
|
| 23 |
-
} catch (e) {
|
| 24 |
-
console.error("Failed to fetch default data", e);
|
| 25 |
-
}
|
| 26 |
-
});
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
successMessage.value = '正在上传...';
|
| 42 |
-
|
| 43 |
-
const response = await fetch('/upload', {
|
| 44 |
-
method: 'POST',
|
| 45 |
-
body: formData
|
| 46 |
-
});
|
| 47 |
-
|
| 48 |
-
const result = await response.json();
|
| 49 |
-
|
| 50 |
-
if (response.ok) {
|
| 51 |
-
successMessage.value = `文件 "${result.filename}" 上传成功!`;
|
| 52 |
-
// If it's a text file, we might want to read it (simplified for now)
|
| 53 |
-
if (file.type.startsWith('text/')) {
|
| 54 |
-
const text = await file.text();
|
| 55 |
-
model.value.content = text;
|
| 56 |
-
} else {
|
| 57 |
-
model.value.content = `[文件已上传: ${result.filename}]\n(此文件类型暂不支持直接预览,但已保存到服务器)`;
|
| 58 |
-
}
|
| 59 |
-
} else {
|
| 60 |
-
errorMessage.value = result.error || '上传失败';
|
| 61 |
-
successMessage.value = '';
|
| 62 |
-
}
|
| 63 |
-
} catch (e) {
|
| 64 |
-
errorMessage.value = '上传过程中发生错误: ' + e.message;
|
| 65 |
-
successMessage.value = '';
|
| 66 |
-
} finally {
|
| 67 |
-
// Reset input
|
| 68 |
-
event.target.value = '';
|
| 69 |
-
}
|
| 70 |
-
};
|
| 71 |
|
| 72 |
-
|
| 73 |
-
try {
|
| 74 |
-
const hostParts = location.hostname.split(".");
|
| 75 |
-
const baseDomain = hostParts.length > 1
|
| 76 |
-
? "." + hostParts.slice(-2).join(".")
|
| 77 |
-
: location.hostname;
|
| 78 |
-
const cookieVal = "/".concat(srcLang, "/").concat(targetLang);
|
| 79 |
-
document.cookie = "googtrans=" + cookieVal + ";domain=" + baseDomain + ";path=/";
|
| 80 |
-
document.cookie = "googtrans=" + cookieVal + ";domain=" + location.hostname + ";path=/";
|
| 81 |
-
} catch (e) {
|
| 82 |
-
console.error("Cookie set failed", e);
|
| 83 |
-
}
|
| 84 |
-
};
|
| 85 |
|
| 86 |
-
|
| 87 |
-
const combo = document.querySelector(".goog-te-combo");
|
| 88 |
-
if (combo) {
|
| 89 |
-
combo.value = targetLang;
|
| 90 |
-
combo.dispatchEvent(new Event("change"));
|
| 91 |
-
return true;
|
| 92 |
-
}
|
| 93 |
-
return false;
|
| 94 |
-
};
|
| 95 |
|
| 96 |
-
|
| 97 |
-
if (window.google && window.google.translate) return Promise.resolve(true);
|
| 98 |
-
return new Promise((resolve) => {
|
| 99 |
-
let tries = 0;
|
| 100 |
-
const timer = setInterval(() => {
|
| 101 |
-
tries++;
|
| 102 |
-
if (window.google && window.google.translate) {
|
| 103 |
-
clearInterval(timer);
|
| 104 |
-
resolve(true);
|
| 105 |
-
}
|
| 106 |
-
if (tries > 100) {
|
| 107 |
-
clearInterval(timer);
|
| 108 |
-
resolve(false);
|
| 109 |
-
}
|
| 110 |
-
}, 50);
|
| 111 |
-
});
|
| 112 |
-
};
|
| 113 |
|
| 114 |
-
|
| 115 |
-
try {
|
| 116 |
-
successMessage.value = '正在启动翻译...';
|
| 117 |
-
const ready = await ensureTranslateReady();
|
| 118 |
-
setGoogTransCookie("auto", "zh-CN"); // Use auto detect for source
|
| 119 |
-
|
| 120 |
-
if (!triggerGoogleTranslate("zh-CN") && !ready) {
|
| 121 |
-
errorMessage.value = "翻译组件尚未加载,请稍后重试。";
|
| 122 |
-
} else {
|
| 123 |
-
// Google Translate Widget translates the whole page.
|
| 124 |
-
// To make it feel like "Source -> Target", we can rely on the widget
|
| 125 |
-
// translating the "Source" text area (if it's not a textarea, but a div).
|
| 126 |
-
// Note: Google Translate often skips textareas.
|
| 127 |
-
// For this simple wrapper, we trigger the full page translate.
|
| 128 |
-
successMessage.value = "翻译已触发 (全页模式)";
|
| 129 |
-
|
| 130 |
-
// Simple hack: copy source to target to simulate 'action' if widget fails
|
| 131 |
-
// model.value.translated_content = "翻译功能依赖 Google Translate 插件,请查看页面变化。";
|
| 132 |
-
}
|
| 133 |
-
} catch (e) {
|
| 134 |
-
errorMessage.value = "翻译出错: " + e.message;
|
| 135 |
-
}
|
| 136 |
-
};
|
| 137 |
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
// Keep the global init function for Google Translate
|
| 153 |
-
window.googleTranslateElementInit = function () {
|
| 154 |
-
new google.translate.TranslateElement(
|
| 155 |
-
{
|
| 156 |
-
pageLanguage: "auto",
|
| 157 |
-
includedLanguages: "zh-CN,en",
|
| 158 |
-
layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
|
| 159 |
-
autoDisplay: false,
|
| 160 |
-
},
|
| 161 |
-
"google_translate_element"
|
| 162 |
-
);
|
| 163 |
-
};
|
|
|
|
| 1 |
+
const { createApp, ref } = Vue;
|
| 2 |
|
| 3 |
+
const englishArticle = `The internet is a vast, resilient system made of simple parts that cooperate at scale.
|
| 4 |
+
At its core, it is a network of networks: independent autonomous systems that agree on a small set of protocols, mostly TCP/IP, BGP, DNS, and HTTP.
|
| 5 |
+
When you open a browser and type a URL, dozens of subsystems wake up. Your operating system consults DNS to turn a human-friendly name into an address.
|
| 6 |
+
Routers forward packets hop by hop, using BGP to learn where distant networks live. Firewalls inspect flows, CDNs cache assets near you, and TLS establishes a secure channel.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
+
Despite the sophistication, each layer has a narrow responsibility. IP moves packets. TCP ensures reliable delivery and fair sharing. DNS answers questions.
|
| 9 |
+
HTTP carries documents and APIs. This separation of concerns lets the system evolve. We can improve congestion control without touching domain names, or deploy a new web framework without changing routers.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
Scale emerges from replication and caching. Popular content migrates closer to users, and failures are isolated rather than catastrophic.
|
| 12 |
+
Operational excellence—observability, automation, incident response—turns fragile stacks into services people trust.
|
| 13 |
+
Healthy teams invest in boring engineering: clear interfaces, runbooks, and capacity planning. The internet favors designs that degrade gracefully instead of perfectly.
|
| 14 |
|
| 15 |
+
For developers, the lesson is pragmatic architecture. Prefer simple protocols. Measure before optimizing. Embrace backpressure and timeouts.
|
| 16 |
+
Design for retries and idempotency. Document APIs as contracts. Use encryption by default.
|
| 17 |
+
Most outages begin as tiny mismatches between assumptions at different layers. The remedy is empathy: understand adjacent systems deeply enough to anticipate their failure modes.
|
| 18 |
|
| 19 |
+
The future will bring new transports, edge computation, and AI-assisted operations. Still, the fundamentals persist: packets, names, routes, and requests.
|
| 20 |
+
If we keep interfaces clean and responsibilities crisp, we can adopt innovations without breaking the web’s social contract—reachability and openness.
|
| 21 |
+
The internet succeeds not because it is perfect but because it is simple enough to fix while running.`;
|
| 22 |
|
| 23 |
+
const chineseArticle = `互联网是由大量简单组件协同工作的弹性系统。本质上它是“网络的网络”:彼此独立的自治系统基于少量协议达成一致,主要包括 TCP/IP、BGP、DNS 和 HTTP。
|
| 24 |
+
当你在浏览器中输入 URL 时,数十个子系统被唤醒。操���系统通过 DNS 将人类可读的名字解析为地址;路由器依靠 BGP 按跳转发数据包以找到远端网络;防火墙检查流量,CDN 在你附近缓存资源,TLS 建立安全通道。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
尽管复杂,每一层的职责都很单一:IP 负责传递数据包;TCP 保证可靠传输与公平共享;DNS 用来回答名字查询;HTTP 承载文档与 API。职责分离让系统可演化:我们可以改进拥塞控制而无需触碰域名系统,也能部署新的 Web 框架而不影响路由器。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
+
规模来自复制与缓存。热门内容被迁移到更靠近用户的位置,故障被隔离而非牵一发而动全身。可观测性、自动化与事件响应等运营能力把脆弱的技术栈变成值得信赖的服务。成熟团队在“无聊工程”上持续投入:清晰的接口、运行手册与容量规划。互联网偏好在退化中保持可用的设计,而不是追求完美。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
对开发者而言,启示是务实的架构:倾向选择简单协议;在优化前先度量;接受背压与超时;为重试与幂等而设计;把 API 当作契约来文档化;默认启用加密。多数故障始于不同层之间的细微假设不一致,解决之道是同理心:足够理解邻近系统,从而预见其失效模式。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
未来将带来新的传输方式、边缘计算与 AI 辅助运维,但基本面依旧:数据包、名字、路由与请求。只要保持接口干净、职责清晰,我们就能在不破坏 Web 的社会契约(可达与开放)前提下拥抱创新。互联网之所以成功,并非因为完美,而是因为它足够简单,可以在运行中被修复。`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
createApp({
|
| 35 |
+
delimiters: ['${', '}'],
|
| 36 |
+
setup() {
|
| 37 |
+
const article = ref(englishArticle);
|
| 38 |
+
const translated = ref(false);
|
| 39 |
+
const translate = () => {
|
| 40 |
+
if (!translated.value) {
|
| 41 |
+
article.value = chineseArticle;
|
| 42 |
+
translated.value = true;
|
| 43 |
+
}
|
| 44 |
+
};
|
| 45 |
+
return { article, translate };
|
| 46 |
+
}
|
| 47 |
+
}).mount('#app');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/index.html
CHANGED
|
@@ -43,19 +43,16 @@
|
|
| 43 |
.btn-secondary:hover {
|
| 44 |
background-color: #d1d5db;
|
| 45 |
}
|
| 46 |
-
|
| 47 |
width: 100%;
|
| 48 |
-
height:
|
| 49 |
padding: 1rem;
|
| 50 |
border: 1px solid #d1d5db;
|
| 51 |
border-radius: 0.375rem;
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
outline: none;
|
| 57 |
-
border-color: #2563eb;
|
| 58 |
-
ring: 2px solid #2563eb;
|
| 59 |
}
|
| 60 |
[v-cloak] {
|
| 61 |
display: none;
|
|
@@ -66,52 +63,21 @@
|
|
| 66 |
<div id="app" v-cloak class="container">
|
| 67 |
<header class="mb-8 flex justify-between items-center">
|
| 68 |
<h1 class="text-3xl font-bold text-gray-900">Web 翻译助手</h1>
|
| 69 |
-
<
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
</button>
|
| 73 |
-
<input type="file" ref="fileInput" @change="handleFileUpload" style="display: none" accept=".txt,.pdf,.doc,.docx">
|
| 74 |
-
<button @click="translate" class="btn">
|
| 75 |
-
🌐 开始翻译
|
| 76 |
-
</button>
|
| 77 |
-
</div>
|
| 78 |
</header>
|
| 79 |
|
| 80 |
-
<main
|
| 81 |
-
<!-- Source Column -->
|
| 82 |
-
<div class="card">
|
| 83 |
-
<div class="mb-4 flex justify-between items-center">
|
| 84 |
-
<h2 class="text-xl font-semibold">原文</h2>
|
| 85 |
-
<span class="text-sm text-gray-500">检测语言: 自动</span>
|
| 86 |
-
</div>
|
| 87 |
-
<textarea v-model="model.content" placeholder="在此输入或粘贴要翻译的文本..."></textarea>
|
| 88 |
-
</div>
|
| 89 |
-
|
| 90 |
-
<!-- Target Column -->
|
| 91 |
<div class="card">
|
| 92 |
<div class="mb-4 flex justify-between items-center">
|
| 93 |
-
<h2 class="text-xl font-semibold">
|
| 94 |
-
<span class="text-sm text-gray-500">目标语言: 中文 (简体)</span>
|
| 95 |
</div>
|
| 96 |
-
<
|
| 97 |
</div>
|
| 98 |
</main>
|
| 99 |
-
|
| 100 |
-
<footer class="mt-12 text-center text-gray-500 text-sm">
|
| 101 |
-
<p>© 2024 Web Translator Assistant. All rights reserved.</p>
|
| 102 |
-
<div v-if="errorMessage" class="mt-4 text-red-600 bg-red-50 p-2 rounded inline-block">
|
| 103 |
-
${ errorMessage }
|
| 104 |
-
</div>
|
| 105 |
-
<div v-if="successMessage" class="mt-4 text-green-600 bg-green-50 p-2 rounded inline-block">
|
| 106 |
-
${ successMessage }
|
| 107 |
-
</div>
|
| 108 |
-
</footer>
|
| 109 |
</div>
|
| 110 |
|
| 111 |
-
<!-- Google Translate Element (Hidden but functional) -->
|
| 112 |
-
<div id="google_translate_element" style="display:none"></div>
|
| 113 |
-
|
| 114 |
<script src="/static/main.js"></script>
|
| 115 |
-
<script src="https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
|
| 116 |
</body>
|
| 117 |
</html>
|
|
|
|
| 43 |
.btn-secondary:hover {
|
| 44 |
background-color: #d1d5db;
|
| 45 |
}
|
| 46 |
+
.display-box {
|
| 47 |
width: 100%;
|
| 48 |
+
min-height: 260px;
|
| 49 |
padding: 1rem;
|
| 50 |
border: 1px solid #d1d5db;
|
| 51 |
border-radius: 0.375rem;
|
| 52 |
+
background: #ffffff;
|
| 53 |
+
color: #111827;
|
| 54 |
+
white-space: pre-wrap;
|
| 55 |
+
line-height: 1.6;
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
[v-cloak] {
|
| 58 |
display: none;
|
|
|
|
| 63 |
<div id="app" v-cloak class="container">
|
| 64 |
<header class="mb-8 flex justify-between items-center">
|
| 65 |
<h1 class="text-3xl font-bold text-gray-900">Web 翻译助手</h1>
|
| 66 |
+
<button @click="translate" class="btn">
|
| 67 |
+
翻译
|
| 68 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
</header>
|
| 70 |
|
| 71 |
+
<main>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
<div class="card">
|
| 73 |
<div class="mb-4 flex justify-between items-center">
|
| 74 |
+
<h2 class="text-xl font-semibold">文章</h2>
|
|
|
|
| 75 |
</div>
|
| 76 |
+
<div class="display-box" v-text="article"></div>
|
| 77 |
</div>
|
| 78 |
</main>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
</div>
|
| 80 |
|
|
|
|
|
|
|
|
|
|
| 81 |
<script src="/static/main.js"></script>
|
|
|
|
| 82 |
</body>
|
| 83 |
</html>
|