mirror of
https://github.com/cjo4m06/mcp-shrimp-task-manager.git
synced 2025-07-27 00:12:26 +08:00
新增多語系支援功能,包含語言切換按鈕及相應的翻譯資料結構,提升網站的可用性與國際化體驗。更新 HTML、CSS 和 JavaScript 檔案以整合新功能,並調整導航欄及內容以支持多語言顯示,增強用戶互動性。
This commit is contained in:
parent
5d7c36dcb2
commit
8af3e34c62
@ -647,18 +647,6 @@ header nav a[href*="github"]:hover svg {
|
|||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workflow-icon-mobile::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 48px;
|
|
||||||
left: 24px;
|
|
||||||
width: 2px;
|
|
||||||
height: 0;
|
|
||||||
background-color: #e2e8f0;
|
|
||||||
animation: verticalLineGrow 1s forwards;
|
|
||||||
animation-delay: 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes verticalLineGrow {
|
@keyframes verticalLineGrow {
|
||||||
from {
|
from {
|
||||||
height: 0;
|
height: 0;
|
||||||
@ -1024,3 +1012,51 @@ footer ul li:hover {
|
|||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 語言切換按鈕 */
|
||||||
|
.language-switcher {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(203, 213, 225, 0.5);
|
||||||
|
color: var(--gray-dark);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.1);
|
||||||
|
border-color: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn.active {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 在移動設備上調整語言切換按鈕 */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.language-switcher {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-btn {
|
||||||
|
padding: 0.4rem 0.75rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
1084
docs/web/js/i18n.js
Normal file
1084
docs/web/js/i18n.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -892,11 +892,11 @@ function initWorkflowModal() {
|
|||||||
detailLinks.forEach((link) => {
|
detailLinks.forEach((link) => {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const stepIndex = parseInt(this.getAttribute("data-step")) - 1;
|
const stepIndex = parseInt(this.getAttribute("data-step"));
|
||||||
|
const lang = localStorage.getItem("preferred-language") || "en";
|
||||||
if (stepIndex >= 0 && stepIndex < workflowDetails["zh-TW"].length) {
|
if (stepIndex >= 0 && workflowDetails[lang][stepIndex]) {
|
||||||
modalTitle.textContent = workflowDetails["zh-TW"][stepIndex].title;
|
modalTitle.textContent = workflowDetails[lang][stepIndex].title;
|
||||||
modalContent.innerHTML = workflowDetails["zh-TW"][stepIndex].content;
|
modalContent.innerHTML = workflowDetails[lang][stepIndex].content;
|
||||||
modal.classList.remove("hidden");
|
modal.classList.remove("hidden");
|
||||||
modal.classList.add("active");
|
modal.classList.add("active");
|
||||||
}
|
}
|
||||||
@ -1273,37 +1273,188 @@ function isInViewport(element) {
|
|||||||
* 初始化多語系功能
|
* 初始化多語系功能
|
||||||
*/
|
*/
|
||||||
function initMultiLanguage() {
|
function initMultiLanguage() {
|
||||||
// 檢查 i18n.js 是否已載入,如果已載入則調用 initLanguage 函數
|
// 檢查 i18n.js 是否已載入
|
||||||
if (typeof initLanguage === "function") {
|
if (typeof i18n !== "undefined") {
|
||||||
initLanguage();
|
// 優先使用增強版初始化函數
|
||||||
|
if (typeof enhancedInitializeLanguage === "function") {
|
||||||
|
enhancedInitializeLanguage();
|
||||||
|
} else if (typeof initializeLanguage === "function") {
|
||||||
|
// 兼容性處理,如果增強版函數不存在則使用原始方法
|
||||||
|
initializeLanguage();
|
||||||
|
} else {
|
||||||
|
console.warn("多語系初始化函數不可用,將使用基本初始化");
|
||||||
|
// 基本初始化 - 在i18n.js無法正確載入時提供基本功能
|
||||||
|
try {
|
||||||
|
const currentLang =
|
||||||
|
localStorage.getItem("preferred-language") ||
|
||||||
|
(navigator.language && navigator.language.startsWith("zh")
|
||||||
|
? "zh-TW"
|
||||||
|
: "en");
|
||||||
|
document.documentElement.setAttribute("lang", currentLang);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("基本語言初始化失敗:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 為語言切換添加自定義事件
|
||||||
|
try {
|
||||||
|
document.querySelectorAll(".lang-btn").forEach(function (btn) {
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
const lang = this.getAttribute("data-lang");
|
||||||
|
|
||||||
|
// 優先使用增強版語言切換函數
|
||||||
|
if (typeof enhancedSetLanguage === "function") {
|
||||||
|
enhancedSetLanguage(lang);
|
||||||
|
} else if (typeof setLanguageWithAnimation === "function") {
|
||||||
|
// 次優先使用帶動畫效果的語言切換
|
||||||
|
setLanguageWithAnimation(lang);
|
||||||
|
} else if (typeof setLanguage === "function") {
|
||||||
|
// 兼容性處理,使用基本語言切換函數
|
||||||
|
setLanguage(lang);
|
||||||
|
} else {
|
||||||
|
console.warn("語言切換函數不可用");
|
||||||
|
// 最基本處理 - 更新 HTML lang 屬性並保存偏好
|
||||||
|
try {
|
||||||
|
localStorage.setItem("preferred-language", lang);
|
||||||
|
document.documentElement.setAttribute("lang", lang);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("基本語言切換失敗:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("為語言按鈕添加事件監聽器時出錯:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化時執行批量翻譯,優化性能
|
||||||
|
if (typeof batchApplyTranslations === "function") {
|
||||||
|
batchApplyTranslations();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("i18n.js 尚未載入,無法啟用完整多語系功能");
|
||||||
|
// 嘗試提供基本的多語系支持
|
||||||
|
try {
|
||||||
|
const basicLanguageSupport = function () {
|
||||||
|
const langBtns = document.querySelectorAll(".lang-btn");
|
||||||
|
if (langBtns.length === 0) return;
|
||||||
|
|
||||||
|
langBtns.forEach((btn) => {
|
||||||
|
btn.addEventListener("click", function () {
|
||||||
|
const lang = this.getAttribute("data-lang");
|
||||||
|
try {
|
||||||
|
localStorage.setItem("preferred-language", lang);
|
||||||
|
document.documentElement.setAttribute("lang", lang);
|
||||||
|
|
||||||
|
// 更新按鈕狀態
|
||||||
|
langBtns.forEach((b) => {
|
||||||
|
if (b.getAttribute("data-lang") === lang) {
|
||||||
|
b.classList.add("active");
|
||||||
|
} else {
|
||||||
|
b.classList.remove("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("基本語言切換失敗:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化按鈕狀態
|
||||||
|
try {
|
||||||
|
const savedLang =
|
||||||
|
localStorage.getItem("preferred-language") ||
|
||||||
|
(navigator.language && navigator.language.startsWith("zh")
|
||||||
|
? "zh-TW"
|
||||||
|
: "en");
|
||||||
|
|
||||||
|
langBtns.forEach((btn) => {
|
||||||
|
if (btn.getAttribute("data-lang") === savedLang) {
|
||||||
|
btn.classList.add("active");
|
||||||
|
} else {
|
||||||
|
btn.classList.remove("active");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.documentElement.setAttribute("lang", savedLang);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("初始化語言按鈕狀態失敗:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
basicLanguageSupport();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("基本多語系支持初始化失敗:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 監聽語言切換事件
|
// 監聽語言切換事件
|
||||||
document.addEventListener("languageChanged", function (event) {
|
try {
|
||||||
const lang = event.detail.language;
|
document.addEventListener("languageChanged", function (event) {
|
||||||
console.log("Language changed to:", lang);
|
const lang = event.detail.language;
|
||||||
|
console.log("Language changed to:", lang);
|
||||||
|
|
||||||
// 語言切換後的特殊處理邏輯
|
// 使用 translateText 函數更新特殊元素
|
||||||
// 更新複製按鈕文字
|
const updateSpecialElements = function () {
|
||||||
const copyBtns = document.querySelectorAll(".copy-cmd-btn");
|
// 安全地取得翻譯函數
|
||||||
const copyText = lang === "en" ? "Copy" : "複製";
|
const getTranslation = (key, defaultText) => {
|
||||||
|
if (typeof safeTranslate === "function") {
|
||||||
|
return safeTranslate(key, defaultText);
|
||||||
|
} else if (typeof translateText === "function") {
|
||||||
|
return translateText(key, defaultText);
|
||||||
|
} else {
|
||||||
|
return lang === "en" ? defaultText.en : defaultText.zh;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
copyBtns.forEach((btn) => {
|
try {
|
||||||
// 只更新沒有顯示"已複製"的按鈕
|
// 更新複製按鈕文字
|
||||||
if (btn.textContent !== "Copied!" && btn.textContent !== "已複製!") {
|
const copyBtns = document.querySelectorAll(".copy-cmd-btn");
|
||||||
btn.textContent = copyText;
|
const copyText = getTranslation("common.copy", {
|
||||||
|
en: "Copy",
|
||||||
|
zh: "複製",
|
||||||
|
});
|
||||||
|
|
||||||
|
copyBtns.forEach((btn) => {
|
||||||
|
// 只更新沒有顯示"已複製"的按鈕
|
||||||
|
if (
|
||||||
|
btn.textContent !== "Copied!" &&
|
||||||
|
btn.textContent !== "已複製!"
|
||||||
|
) {
|
||||||
|
btn.textContent = copyText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("更新複製按鈕文字失敗:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 更新模態窗口中的關閉按鈕文字
|
||||||
|
const closeModalBtn = document.getElementById("close-modal-btn");
|
||||||
|
if (closeModalBtn) {
|
||||||
|
closeModalBtn.textContent = getTranslation("common.close", {
|
||||||
|
en: "Close",
|
||||||
|
zh: "關閉",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("更新關閉按鈕文字失敗:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用 setTimeout 避免阻塞 UI
|
||||||
|
setTimeout(updateSpecialElements, 0);
|
||||||
|
|
||||||
|
// 根據當前語言更新工作流程模態內容
|
||||||
|
try {
|
||||||
|
updateWorkflowModalContent(lang);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("更新工作流程模態內容失敗:", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
// 更新模態窗口中的關閉按鈕文字
|
console.error("添加語言變更事件監聽器失敗:", e);
|
||||||
const closeModalBtn = document.getElementById("close-modal-btn");
|
}
|
||||||
if (closeModalBtn) {
|
|
||||||
closeModalBtn.textContent = lang === "en" ? "Close" : "關閉";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根據當前語言更新工作流程模態內容
|
|
||||||
updateWorkflowModalContent(lang);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1326,8 +1477,19 @@ function updateWorkflowModalContent(lang) {
|
|||||||
|
|
||||||
if (workflowDetails[langKey] && workflowDetails[langKey][currentStep]) {
|
if (workflowDetails[langKey] && workflowDetails[langKey][currentStep]) {
|
||||||
const stepData = workflowDetails[langKey][currentStep];
|
const stepData = workflowDetails[langKey][currentStep];
|
||||||
modalTitle.textContent = stepData.title;
|
|
||||||
modalContent.innerHTML = stepData.content;
|
// 使用 requestAnimationFrame 優化渲染性能
|
||||||
|
requestAnimationFrame(function () {
|
||||||
|
modalTitle.textContent = stepData.title;
|
||||||
|
modalContent.innerHTML = stepData.content;
|
||||||
|
|
||||||
|
// 為動態生成的內容添加 data-i18n 屬性
|
||||||
|
const dynamicElements = modalContent.querySelectorAll("h4, p, li");
|
||||||
|
dynamicElements.forEach(function (el, index) {
|
||||||
|
const key = `workflow.step${currentStep}.content.${index}`;
|
||||||
|
el.setAttribute("data-i18n-dynamic", key);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user