feat: add theme toggle functionality and update localization files

- Implemented a theme toggle feature allowing users to switch between light and dark modes.
- Added a new ThemeToggle component for the theme switching button.
- Updated theme CSS to support dark mode variables and system preference detection.
- Enhanced localization files to include translations for the "toggleTheme" key across multiple languages.
This commit is contained in:
Your Name 2026-01-21 15:00:34 +08:00
parent 1f850dd926
commit db5f47586e
72 changed files with 338 additions and 77 deletions

View file

@ -172,6 +172,16 @@ docker compose up -d
--- ---
## Dark Mode
ConvertX-CN 支援亮色與暗色主題。
- 可手動切換主題(點擊導覽列的太陽/月亮圖示)
- 偏好設定自動儲存於瀏覽器
- 預設跟隨系統色彩偏好
---
## MinerU ## MinerU
ConvertX 內建文件轉換引擎。 ConvertX 內建文件轉換引擎。

102
public/theme.js Normal file
View file

@ -0,0 +1,102 @@
// ===== Theme Toggle Script =====
// Handles dark/light mode switching with localStorage persistence
// and system preference detection
(function () {
const THEME_KEY = "theme";
// Get stored theme or detect system preference
function getPreferredTheme() {
const storedTheme = localStorage.getItem(THEME_KEY);
if (storedTheme) {
return storedTheme;
}
// Return null to let CSS handle system preference
return null;
}
// Get current effective theme (what's actually being displayed)
function getEffectiveTheme() {
const storedTheme = localStorage.getItem(THEME_KEY);
if (storedTheme) {
return storedTheme;
}
// Check system preference
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
// Apply theme to document
function applyTheme(theme) {
const root = document.documentElement;
if (theme === "dark") {
root.setAttribute("data-theme", "dark");
} else if (theme === "light") {
root.setAttribute("data-theme", "light");
} else {
// Remove attribute to follow system preference
root.removeAttribute("data-theme");
}
updateThemeIcons();
}
// Update theme toggle icons visibility
function updateThemeIcons() {
const effectiveTheme = getEffectiveTheme();
const lightIcon = document.getElementById("theme-icon-light");
const darkIcon = document.getElementById("theme-icon-dark");
if (lightIcon && darkIcon) {
if (effectiveTheme === "dark") {
// In dark mode, show sun icon (to switch to light)
lightIcon.classList.remove("hidden");
darkIcon.classList.add("hidden");
} else {
// In light mode, show moon icon (to switch to dark)
lightIcon.classList.add("hidden");
darkIcon.classList.remove("hidden");
}
}
}
// Toggle theme
function toggleTheme() {
const currentTheme = getEffectiveTheme();
const newTheme = currentTheme === "dark" ? "light" : "dark";
localStorage.setItem(THEME_KEY, newTheme);
applyTheme(newTheme);
}
// Initialize theme on page load
function initTheme() {
const preferredTheme = getPreferredTheme();
applyTheme(preferredTheme);
// Listen for system preference changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
// Only update if no manual preference is set
if (!localStorage.getItem(THEME_KEY)) {
applyTheme(null);
}
});
}
// Apply theme immediately to prevent flash
const preferredTheme = getPreferredTheme();
if (preferredTheme) {
document.documentElement.setAttribute("data-theme", preferredTheme);
}
// Set up event listener when DOM is ready
document.addEventListener("DOMContentLoaded", function () {
initTheme();
const themeToggle = document.getElementById("theme-toggle");
if (themeToggle) {
themeToggle.addEventListener("click", toggleTheme);
}
});
// Expose toggle function globally for programmatic access
window.toggleTheme = toggleTheme;
window.getEffectiveTheme = getEffectiveTheme;
})();

View file

@ -26,6 +26,7 @@ export const BaseHtml = ({
<link rel="manifest" href={`${webroot}/site.webmanifest`} /> <link rel="manifest" href={`${webroot}/site.webmanifest`} />
<script>{`window.__TRANSLATIONS__ = ${JSON.stringify(getTranslations(locale))};`}</script> <script>{`window.__TRANSLATIONS__ = ${JSON.stringify(getTranslations(locale))};`}</script>
<script src={`${webroot}/i18n.js`} defer /> <script src={`${webroot}/i18n.js`} defer />
<script src={`${webroot}/theme.js`} />
</head> </head>
<body class={`flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200`}> <body class={`flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200`}>
{children} {children}

View file

@ -5,6 +5,7 @@ import {
defaultLocale, defaultLocale,
} from "../i18n/index"; } from "../i18n/index";
import { LanguageSelector } from "./languageSelector"; import { LanguageSelector } from "./languageSelector";
import { ThemeToggle } from "./themeToggle";
export const Header = ({ export const Header = ({
loggedIn, loggedIn,
@ -66,6 +67,9 @@ export const Header = ({
</a> </a>
</li> </li>
) : null} ) : null}
<li>
<ThemeToggle locale={locale} t={t} />
</li>
<li> <li>
<LanguageSelector currentLocale={locale} webroot={webroot} t={t} /> <LanguageSelector currentLocale={locale} webroot={webroot} t={t} />
</li> </li>
@ -98,6 +102,9 @@ export const Header = ({
</a> </a>
</li> </li>
) : null} ) : null}
<li>
<ThemeToggle locale={locale} t={t} />
</li>
<li> <li>
<LanguageSelector currentLocale={locale} webroot={webroot} t={t} /> <LanguageSelector currentLocale={locale} webroot={webroot} t={t} />
</li> </li>

View file

@ -0,0 +1,55 @@
import { type SupportedLocale, type Translator, createTranslator, defaultLocale } from "../i18n/index";
export const ThemeToggle = ({
locale = defaultLocale,
t = createTranslator(defaultLocale),
}: {
locale?: SupportedLocale;
t?: Translator;
}) => {
return (
<button
type="button"
id="theme-toggle"
class={`
flex items-center gap-1 text-accent-600 transition-all
hover:text-accent-500
`}
aria-label={t("nav", "toggleTheme")}
title={t("nav", "toggleTheme")}
>
{/* Sun icon (shown in dark mode) */}
<svg
id="theme-icon-light"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="hidden h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
/>
</svg>
{/* Moon icon (shown in light mode) */}
<svg
id="theme-icon-dark"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="hidden h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
/>
</svg>
</button>
);
};

View file

@ -26,7 +26,8 @@
"logout": "Teken uit", "logout": "Teken uit",
"login": "Teken in", "login": "Teken in",
"register": "Registreer", "register": "Registreer",
"language": "Taal" "language": "Taal",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Omskakel", "title": "Omskakel",

View file

@ -26,7 +26,8 @@
"logout": "ውጣ", "logout": "ውጣ",
"login": "ግባ", "login": "ግባ",
"register": "ተመዝገብ", "register": "ተመዝገብ",
"language": "ቋንቋ" "language": "ቋንቋ",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "ቀይር", "title": "ቀይር",

View file

@ -26,7 +26,8 @@
"logout": "تسجيل الخروج", "logout": "تسجيل الخروج",
"login": "تسجيل الدخول", "login": "تسجيل الدخول",
"register": "التسجيل", "register": "التسجيل",
"language": "اللغة" "language": "اللغة",
"toggleTheme": "تبديل المظهر"
}, },
"convert": { "convert": {
"title": "تحويل", "title": "تحويل",

View file

@ -26,7 +26,8 @@
"logout": "Изход", "logout": "Изход",
"login": "Вход", "login": "Вход",
"register": "Регистрация", "register": "Регистрация",
"language": "Език" "language": "Език",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Конвертиране", "title": "Конвертиране",

View file

@ -26,7 +26,8 @@
"logout": "লগআউট", "logout": "লগআউট",
"login": "লগইন", "login": "লগইন",
"register": "নিবন্ধন", "register": "নিবন্ধন",
"language": "ভাষা" "language": "ভাষা",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "রূপান্তর করুন", "title": "রূপান্তর করুন",

View file

@ -26,7 +26,8 @@
"logout": "Tancar sessió", "logout": "Tancar sessió",
"login": "Iniciar sessió", "login": "Iniciar sessió",
"register": "Registrar-se", "register": "Registrar-se",
"language": "Idioma" "language": "Idioma",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Convertir", "title": "Convertir",

View file

@ -26,7 +26,8 @@
"logout": "Odhlásit", "logout": "Odhlásit",
"login": "Přihlásit", "login": "Přihlásit",
"register": "Registrovat", "register": "Registrovat",
"language": "Jazyk" "language": "Jazyk",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Převést", "title": "Převést",

View file

@ -26,7 +26,8 @@
"logout": "Allgofnodi", "logout": "Allgofnodi",
"login": "Mewngofnodi", "login": "Mewngofnodi",
"register": "Cofrestru", "register": "Cofrestru",
"language": "Iaith" "language": "Iaith",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Trosi", "title": "Trosi",

View file

@ -26,7 +26,8 @@
"logout": "Log ud", "logout": "Log ud",
"login": "Log ind", "login": "Log ind",
"register": "Registrer", "register": "Registrer",
"language": "Sprog" "language": "Sprog",
"toggleTheme": "Skift tema"
}, },
"convert": { "convert": {
"title": "Konverter", "title": "Konverter",

View file

@ -26,7 +26,8 @@
"logout": "Abmelden", "logout": "Abmelden",
"login": "Anmelden", "login": "Anmelden",
"register": "Registrieren", "register": "Registrieren",
"language": "Sprache" "language": "Sprache",
"toggleTheme": "Thema wechseln"
}, },
"convert": { "convert": {
"title": "Konvertieren", "title": "Konvertieren",

View file

@ -26,7 +26,8 @@
"logout": "Αποσύνδεση", "logout": "Αποσύνδεση",
"login": "Σύνδεση", "login": "Σύνδεση",
"register": "Εγγραφή", "register": "Εγγραφή",
"language": "Γλώσσα" "language": "Γλώσσα",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Μετατροπή", "title": "Μετατροπή",

View file

@ -26,7 +26,8 @@
"logout": "Logout", "logout": "Logout",
"login": "Login", "login": "Login",
"register": "Register", "register": "Register",
"language": "Language" "language": "Language",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Convert", "title": "Convert",

View file

@ -26,7 +26,8 @@
"logout": "Cerrar sesión", "logout": "Cerrar sesión",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"register": "Registrarse", "register": "Registrarse",
"language": "Idioma" "language": "Idioma",
"toggleTheme": "Cambiar tema"
}, },
"convert": { "convert": {
"title": "Convertir", "title": "Convertir",

View file

@ -26,7 +26,8 @@
"logout": "Logi välja", "logout": "Logi välja",
"login": "Logi sisse", "login": "Logi sisse",
"register": "Registreeru", "register": "Registreeru",
"language": "Keel" "language": "Keel",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Teisenda", "title": "Teisenda",

View file

@ -26,7 +26,8 @@
"logout": "Saioa itxi", "logout": "Saioa itxi",
"login": "Saioa hasi", "login": "Saioa hasi",
"register": "Erregistratu", "register": "Erregistratu",
"language": "Hizkuntza" "language": "Hizkuntza",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Bihurtu", "title": "Bihurtu",

View file

@ -26,7 +26,8 @@
"logout": "خروج", "logout": "خروج",
"login": "ورود", "login": "ورود",
"register": "ثبت نام", "register": "ثبت نام",
"language": "زبان" "language": "زبان",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "تبدیل", "title": "تبدیل",

View file

@ -26,7 +26,8 @@
"logout": "Kirjaudu ulos", "logout": "Kirjaudu ulos",
"login": "Kirjaudu sisään", "login": "Kirjaudu sisään",
"register": "Rekisteröidy", "register": "Rekisteröidy",
"language": "Kieli" "language": "Kieli",
"toggleTheme": "Vaihda teemaa"
}, },
"convert": { "convert": {
"title": "Muunna", "title": "Muunna",

View file

@ -26,7 +26,8 @@
"logout": "Mag-logout", "logout": "Mag-logout",
"login": "Mag-login", "login": "Mag-login",
"register": "Magrehistro", "register": "Magrehistro",
"language": "Wika" "language": "Wika",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "I-convert", "title": "I-convert",

View file

@ -26,7 +26,8 @@
"logout": "Déconnexion", "logout": "Déconnexion",
"login": "Connexion", "login": "Connexion",
"register": "S'inscrire", "register": "S'inscrire",
"language": "Langue" "language": "Langue",
"toggleTheme": "Changer le thème"
}, },
"convert": { "convert": {
"title": "Convertir", "title": "Convertir",

View file

@ -26,7 +26,8 @@
"logout": "Logáil amach", "logout": "Logáil amach",
"login": "Logáil isteach", "login": "Logáil isteach",
"register": "Cláraigh", "register": "Cláraigh",
"language": "Teanga" "language": "Teanga",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Tiontaigh", "title": "Tiontaigh",

View file

@ -26,7 +26,8 @@
"logout": "Pechar sesión", "logout": "Pechar sesión",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"register": "Rexistrarse", "register": "Rexistrarse",
"language": "Idioma" "language": "Idioma",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Converter", "title": "Converter",

View file

@ -26,7 +26,8 @@
"logout": "લૉગઆઉટ", "logout": "લૉગઆઉટ",
"login": "લૉગિન", "login": "લૉગિન",
"register": "નોંધણી કરો", "register": "નોંધણી કરો",
"language": "ભાષા" "language": "ભાષા",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "રૂપાંતર કરો", "title": "રૂપાંતર કરો",

View file

@ -26,7 +26,8 @@
"logout": "התנתק", "logout": "התנתק",
"login": "התחבר", "login": "התחבר",
"register": "הירשם", "register": "הירשם",
"language": "שפה" "language": "שפה",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "המר", "title": "המר",

View file

@ -26,7 +26,8 @@
"logout": "लॉग आउट", "logout": "लॉग आउट",
"login": "लॉग इन", "login": "लॉग इन",
"register": "पंजीकरण", "register": "पंजीकरण",
"language": "भाषा" "language": "भाषा",
"toggleTheme": "थीम बदलें"
}, },
"convert": { "convert": {
"title": "कन्वर्ट करें", "title": "कन्वर्ट करें",

View file

@ -26,7 +26,8 @@
"logout": "Odjava", "logout": "Odjava",
"login": "Prijava", "login": "Prijava",
"register": "Registracija", "register": "Registracija",
"language": "Jezik" "language": "Jezik",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Pretvori", "title": "Pretvori",

View file

@ -26,7 +26,8 @@
"logout": "Kijelentkezés", "logout": "Kijelentkezés",
"login": "Bejelentkezés", "login": "Bejelentkezés",
"register": "Regisztráció", "register": "Regisztráció",
"language": "Nyelv" "language": "Nyelv",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Konvertálás", "title": "Konvertálás",

View file

@ -26,7 +26,8 @@
"logout": "Keluar", "logout": "Keluar",
"login": "Masuk", "login": "Masuk",
"register": "Daftar", "register": "Daftar",
"language": "Bahasa" "language": "Bahasa",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Konversi", "title": "Konversi",

View file

@ -26,7 +26,8 @@
"logout": "Útskrá", "logout": "Útskrá",
"login": "Innskrá", "login": "Innskrá",
"register": "Nýskrá", "register": "Nýskrá",
"language": "Tungumál" "language": "Tungumál",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Umbreyta", "title": "Umbreyta",

View file

@ -26,7 +26,8 @@
"logout": "Esci", "logout": "Esci",
"login": "Accedi", "login": "Accedi",
"register": "Registrati", "register": "Registrati",
"language": "Lingua" "language": "Lingua",
"toggleTheme": "Cambia tema"
}, },
"convert": { "convert": {
"title": "Converti", "title": "Converti",

View file

@ -26,7 +26,8 @@
"logout": "ログアウト", "logout": "ログアウト",
"login": "ログイン", "login": "ログイン",
"register": "登録", "register": "登録",
"language": "言語" "language": "言語",
"toggleTheme": "テーマ切替"
}, },
"convert": { "convert": {
"title": "変換", "title": "変換",

View file

@ -26,7 +26,8 @@
"logout": "ចាកចេញ", "logout": "ចាកចេញ",
"login": "ចូល", "login": "ចូល",
"register": "ចុះឈ្មោះ", "register": "ចុះឈ្មោះ",
"language": "ភាសា" "language": "ភាសា",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "បម្លែង", "title": "បម្លែង",

View file

@ -26,7 +26,8 @@
"logout": "ಲಾಗ್ ಔಟ್", "logout": "ಲಾಗ್ ಔಟ್",
"login": "ಲಾಗಿನ್", "login": "ಲಾಗಿನ್",
"register": "ನೋಂದಣಿ", "register": "ನೋಂದಣಿ",
"language": "ಭಾಷೆ" "language": "ಭಾಷೆ",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "ಪರಿವರ್ತಿಸಿ", "title": "ಪರಿವರ್ತಿಸಿ",

View file

@ -26,7 +26,8 @@
"logout": "로그아웃", "logout": "로그아웃",
"login": "로그인", "login": "로그인",
"register": "회원가입", "register": "회원가입",
"language": "언어" "language": "언어",
"toggleTheme": "테마 전환"
}, },
"convert": { "convert": {
"title": "변환", "title": "변환",

View file

@ -26,7 +26,8 @@
"logout": "ອອກຈາກລະບົບ", "logout": "ອອກຈາກລະບົບ",
"login": "ເຂົ້າສູ່ລະບົບ", "login": "ເຂົ້າສູ່ລະບົບ",
"register": "ລົງທະບຽນ", "register": "ລົງທະບຽນ",
"language": "ພາສາ" "language": "ພາສາ",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "ແປງ", "title": "ແປງ",

View file

@ -26,7 +26,8 @@
"logout": "Atsijungti", "logout": "Atsijungti",
"login": "Prisijungti", "login": "Prisijungti",
"register": "Registruotis", "register": "Registruotis",
"language": "Kalba" "language": "Kalba",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Konvertuoti", "title": "Konvertuoti",

View file

@ -26,7 +26,8 @@
"logout": "Izrakstīties", "logout": "Izrakstīties",
"login": "Ierakstīties", "login": "Ierakstīties",
"register": "Reģistrēties", "register": "Reģistrēties",
"language": "Valoda" "language": "Valoda",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Konvertēt", "title": "Konvertēt",

View file

@ -26,7 +26,8 @@
"logout": "Одјава", "logout": "Одјава",
"login": "Најава", "login": "Најава",
"register": "Регистрација", "register": "Регистрација",
"language": "Јазик" "language": "Јазик",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Конвертирај", "title": "Конвертирај",

View file

@ -26,7 +26,8 @@
"logout": "ലോഗൗട്ട്", "logout": "ലോഗൗട്ട്",
"login": "ലോഗിൻ", "login": "ലോഗിൻ",
"register": "രജിസ്റ്റർ", "register": "രജിസ്റ്റർ",
"language": "ഭാഷ" "language": "ഭാഷ",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "പരിവർത്തനം ചെയ്യുക", "title": "പരിവർത്തനം ചെയ്യുക",

View file

@ -26,7 +26,8 @@
"logout": "लॉगआउट", "logout": "लॉगआउट",
"login": "लॉगिन", "login": "लॉगिन",
"register": "नोंदणी करा", "register": "नोंदणी करा",
"language": "भाषा" "language": "भाषा",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "रूपांतर करा", "title": "रूपांतर करा",

View file

@ -26,7 +26,8 @@
"logout": "Log keluar", "logout": "Log keluar",
"login": "Log masuk", "login": "Log masuk",
"register": "Daftar", "register": "Daftar",
"language": "Bahasa" "language": "Bahasa",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Tukar", "title": "Tukar",

View file

@ -26,7 +26,8 @@
"logout": "Oħroġ", "logout": "Oħroġ",
"login": "Idħol", "login": "Idħol",
"register": "Irreġistra", "register": "Irreġistra",
"language": "Lingwa" "language": "Lingwa",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Ikkonverti", "title": "Ikkonverti",

View file

@ -26,7 +26,8 @@
"logout": "ထွက်", "logout": "ထွက်",
"login": "ဝင်", "login": "ဝင်",
"register": "စာရင်းသွင်း", "register": "စာရင်းသွင်း",
"language": "ဘာသာစကား" "language": "ဘာသာစကား",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "ပြောင်း", "title": "ပြောင်း",

View file

@ -26,7 +26,8 @@
"logout": "लग आउट", "logout": "लग आउट",
"login": "लग इन", "login": "लग इन",
"register": "दर्ता गर्नुहोस्", "register": "दर्ता गर्नुहोस्",
"language": "भाषा" "language": "भाषा",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "रूपान्तरण गर्नुहोस्", "title": "रूपान्तरण गर्नुहोस्",

View file

@ -26,7 +26,8 @@
"logout": "Uitloggen", "logout": "Uitloggen",
"login": "Inloggen", "login": "Inloggen",
"register": "Registreren", "register": "Registreren",
"language": "Taal" "language": "Taal",
"toggleTheme": "Thema wisselen"
}, },
"convert": { "convert": {
"title": "Converteren", "title": "Converteren",

View file

@ -26,7 +26,8 @@
"logout": "Logg ut", "logout": "Logg ut",
"login": "Logg inn", "login": "Logg inn",
"register": "Registrer", "register": "Registrer",
"language": "Språk" "language": "Språk",
"toggleTheme": "Bytt tema"
}, },
"convert": { "convert": {
"title": "Konverter", "title": "Konverter",

View file

@ -26,7 +26,8 @@
"logout": "Wyloguj", "logout": "Wyloguj",
"login": "Zaloguj", "login": "Zaloguj",
"register": "Zarejestruj", "register": "Zarejestruj",
"language": "Język" "language": "Język",
"toggleTheme": "Zmień motyw"
}, },
"convert": { "convert": {
"title": "Konwertuj", "title": "Konwertuj",

View file

@ -26,7 +26,8 @@
"logout": "Sair", "logout": "Sair",
"login": "Entrar", "login": "Entrar",
"register": "Registrar", "register": "Registrar",
"language": "Idioma" "language": "Idioma",
"toggleTheme": "Alternar tema"
}, },
"convert": { "convert": {
"title": "Converter", "title": "Converter",

View file

@ -26,7 +26,8 @@
"logout": "Deconectare", "logout": "Deconectare",
"login": "Autentificare", "login": "Autentificare",
"register": "Înregistrare", "register": "Înregistrare",
"language": "Limbă" "language": "Limbă",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Convertire", "title": "Convertire",

View file

@ -26,7 +26,8 @@
"logout": "Выйти", "logout": "Выйти",
"login": "Войти", "login": "Войти",
"register": "Регистрация", "register": "Регистрация",
"language": "Язык" "language": "Язык",
"toggleTheme": "Сменить тему"
}, },
"convert": { "convert": {
"title": "Конвертировать", "title": "Конвертировать",

View file

@ -26,7 +26,8 @@
"logout": "පිටවන්න", "logout": "පිටවන්න",
"login": "පිවිසෙන්න", "login": "පිවිසෙන්න",
"register": "ලියාපදිංචි වන්න", "register": "ලියාපදිංචි වන්න",
"language": "භාෂාව" "language": "භාෂාව",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "පරිවර්තනය", "title": "පරිවර්තනය",

View file

@ -26,7 +26,8 @@
"logout": "Odhlásiť sa", "logout": "Odhlásiť sa",
"login": "Prihlásiť sa", "login": "Prihlásiť sa",
"register": "Registrovať", "register": "Registrovať",
"language": "Jazyk" "language": "Jazyk",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Konvertovať", "title": "Konvertovať",

View file

@ -26,7 +26,8 @@
"logout": "Odjava", "logout": "Odjava",
"login": "Prijava", "login": "Prijava",
"register": "Registracija", "register": "Registracija",
"language": "Jezik" "language": "Jezik",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Pretvori", "title": "Pretvori",

View file

@ -26,7 +26,8 @@
"logout": "Dilni", "logout": "Dilni",
"login": "Hyni", "login": "Hyni",
"register": "Regjistrohu", "register": "Regjistrohu",
"language": "Gjuha" "language": "Gjuha",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Konverto", "title": "Konverto",

View file

@ -26,7 +26,8 @@
"logout": "Одјава", "logout": "Одјава",
"login": "Пријава", "login": "Пријава",
"register": "Регистрација", "register": "Регистрација",
"language": "Језик" "language": "Језик",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Конвертуј", "title": "Конвертуј",

View file

@ -26,7 +26,8 @@
"logout": "Logga ut", "logout": "Logga ut",
"login": "Logga in", "login": "Logga in",
"register": "Registrera", "register": "Registrera",
"language": "Språk" "language": "Språk",
"toggleTheme": "Byt tema"
}, },
"convert": { "convert": {
"title": "Konvertera", "title": "Konvertera",

View file

@ -26,7 +26,8 @@
"logout": "Toka", "logout": "Toka",
"login": "Ingia", "login": "Ingia",
"register": "Jisajili", "register": "Jisajili",
"language": "Lugha" "language": "Lugha",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Badilisha", "title": "Badilisha",

View file

@ -26,7 +26,8 @@
"logout": "வெளியேறு", "logout": "வெளியேறு",
"login": "உள்நுழை", "login": "உள்நுழை",
"register": "பதிவு செய்", "register": "பதிவு செய்",
"language": "மொழி" "language": "மொழி",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "மாற்று", "title": "மாற்று",

View file

@ -26,7 +26,8 @@
"logout": "లాగ్ అవుట్", "logout": "లాగ్ అవుట్",
"login": "లాగిన్", "login": "లాగిన్",
"register": "నమోదు", "register": "నమోదు",
"language": "భాష" "language": "భాష",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "మార్చు", "title": "మార్చు",

View file

@ -26,7 +26,8 @@
"logout": "ออกจากระบบ", "logout": "ออกจากระบบ",
"login": "เข้าสู่ระบบ", "login": "เข้าสู่ระบบ",
"register": "ลงทะเบียน", "register": "ลงทะเบียน",
"language": "ภาษา" "language": "ภาษา",
"toggleTheme": "เปลี่ยนธีม"
}, },
"convert": { "convert": {
"title": "แปลงไฟล์", "title": "แปลงไฟล์",

View file

@ -26,7 +26,8 @@
"logout": ıkış", "logout": ıkış",
"login": "Giriş", "login": "Giriş",
"register": "Kayıt Ol", "register": "Kayıt Ol",
"language": "Dil" "language": "Dil",
"toggleTheme": "Tema değiştir"
}, },
"convert": { "convert": {
"title": "Dönüştür", "title": "Dönüştür",

View file

@ -26,7 +26,8 @@
"logout": "Вийти", "logout": "Вийти",
"login": "Увійти", "login": "Увійти",
"register": "Реєстрація", "register": "Реєстрація",
"language": "Мова" "language": "Мова",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Конвертувати", "title": "Конвертувати",

View file

@ -26,7 +26,8 @@
"logout": "Đăng xuất", "logout": "Đăng xuất",
"login": "Đăng nhập", "login": "Đăng nhập",
"register": "Đăng ký", "register": "Đăng ký",
"language": "Ngôn ngữ" "language": "Ngôn ngữ",
"toggleTheme": "Đổi giao diện"
}, },
"convert": { "convert": {
"title": "Chuyển đổi", "title": "Chuyển đổi",

View file

@ -26,7 +26,8 @@
"logout": "登出", "logout": "登出",
"login": "登录", "login": "登录",
"register": "注册", "register": "注册",
"language": "语言" "language": "语言",
"toggleTheme": "切换主题"
}, },
"convert": { "convert": {
"title": "转换", "title": "转换",

View file

@ -26,7 +26,8 @@
"logout": "登出", "logout": "登出",
"login": "登入", "login": "登入",
"register": "註冊", "register": "註冊",
"language": "語言" "language": "語言",
"toggleTheme": "切換主題"
}, },
"convert": { "convert": {
"title": "轉換", "title": "轉換",

View file

@ -26,7 +26,8 @@
"logout": "Phuma", "logout": "Phuma",
"login": "Ngena", "login": "Ngena",
"register": "Bhalisa", "register": "Bhalisa",
"language": "Ulimi" "language": "Ulimi",
"toggleTheme": "Toggle theme"
}, },
"convert": { "convert": {
"title": "Guqula", "title": "Guqula",

View file

@ -4,6 +4,7 @@ import { Elysia, t } from "elysia";
import { BaseHtml } from "../components/base"; import { BaseHtml } from "../components/base";
import { Header } from "../components/header"; import { Header } from "../components/header";
import { LanguageSelector } from "../components/languageSelector"; import { LanguageSelector } from "../components/languageSelector";
import { ThemeToggle } from "../components/themeToggle";
import db from "../db/db"; import db from "../db/db";
import { User } from "../db/types"; import { User } from "../db/types";
import { import {
@ -102,7 +103,10 @@ export const user = new Elysia()
`} `}
> >
<strong>ConvertX-CN</strong> <strong>ConvertX-CN</strong>
<div class="flex items-center gap-4">
<ThemeToggle locale={locale} t={t} />
<LanguageSelector currentLocale={locale} webroot={WEBROOT} t={t} /> <LanguageSelector currentLocale={locale} webroot={WEBROOT} t={t} />
</div>
</nav> </nav>
</header> </header>
<main <main

View file

@ -1,7 +1,7 @@
/* ===== Light Mode (Default) ===== */
:root { :root {
/* Light mode */
--contrast: oklch(100% 0 0); --contrast: oklch(100% 0 0);
/* Neutral colors - Gray */ /* Neutral colors - Gray (Light) */
--neutral-950: oklch(98.5% 0.002 247.839); --neutral-950: oklch(98.5% 0.002 247.839);
--neutral-900: oklch(96.7% 0.003 264.542); --neutral-900: oklch(96.7% 0.003 264.542);
--neutral-800: oklch(92.8% 0.006 264.531); --neutral-800: oklch(92.8% 0.006 264.531);
@ -13,19 +13,16 @@
--neutral-200: oklch(26.9% 0 0); --neutral-200: oklch(26.9% 0 0);
--neutral-100: oklch(21% 0.034 264.665); --neutral-100: oklch(21% 0.034 264.665);
--neutral-50: oklch(13% 0.028 261.692); --neutral-50: oklch(13% 0.028 261.692);
/* lime-700 */ /* Accent colors - lime */
--accent-600: oklch(53.2% 0.157 131.589); --accent-600: oklch(53.2% 0.157 131.589);
/* lime-600 */
--accent-500: oklch(64.8% 0.2 131.684); --accent-500: oklch(64.8% 0.2 131.684);
/* lime-500 */
--accent-400: oklch(76.8% 0.233 130.85); --accent-400: oklch(76.8% 0.233 130.85);
} }
@media (prefers-color-scheme: dark) { /* ===== Dark Mode Variables ===== */
/* Dark mode */ :root[data-theme="dark"] {
:root {
--contrast: oklch(0% 0 0); --contrast: oklch(0% 0 0);
/* Neutral colors - Gray */ /* Neutral colors - Gray (Dark) */
--neutral-950: oklch(13% 0.028 261.692); --neutral-950: oklch(13% 0.028 261.692);
--neutral-900: oklch(21% 0.034 264.665); --neutral-900: oklch(21% 0.034 264.665);
--neutral-800: oklch(27.8% 0.033 256.848); --neutral-800: oklch(27.8% 0.033 256.848);
@ -37,11 +34,31 @@
--neutral-200: oklch(92.8% 0.006 264.531); --neutral-200: oklch(92.8% 0.006 264.531);
--neutral-100: oklch(96.7% 0.003 264.542); --neutral-100: oklch(96.7% 0.003 264.542);
--neutral-50: oklch(98.5% 0.002 247.839); --neutral-50: oklch(98.5% 0.002 247.839);
/* lime-600 */ /* Accent colors - lime (brighter for dark mode) */
--accent-600: oklch(64.8% 0.2 131.684);
--accent-500: oklch(76.8% 0.233 130.85);
--accent-400: oklch(84.1% 0.238 128.85);
}
/* ===== System Preference: Dark Mode (when no manual preference set) ===== */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--contrast: oklch(0% 0 0);
/* Neutral colors - Gray (Dark) */
--neutral-950: oklch(13% 0.028 261.692);
--neutral-900: oklch(21% 0.034 264.665);
--neutral-800: oklch(27.8% 0.033 256.848);
--neutral-700: oklch(37.3% 0.034 259.733);
--neutral-600: oklch(44.6% 0.03 256.802);
--neutral-500: oklch(55.1% 0.027 264.364);
--neutral-400: oklch(70.7% 0.022 261.325);
--neutral-300: oklch(87.2% 0.01 258.338);
--neutral-200: oklch(92.8% 0.006 264.531);
--neutral-100: oklch(96.7% 0.003 264.542);
--neutral-50: oklch(98.5% 0.002 247.839);
/* Accent colors - lime (brighter for dark mode) */
--accent-600: oklch(64.8% 0.2 131.684); --accent-600: oklch(64.8% 0.2 131.684);
/* lime-500 */
--accent-500: oklch(76.8% 0.233 130.85); --accent-500: oklch(76.8% 0.233 130.85);
/* lime-400 */
--accent-400: oklch(84.1% 0.238 128.85); --accent-400: oklch(84.1% 0.238 128.85);
} }
} }