diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 20b1579..fcec355 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -973,6 +973,7 @@ dependencies = [ "salvo", "serde", "serde_json", + "sys-locale", "tauri", "tauri-build", "thiserror", @@ -4096,6 +4097,15 @@ dependencies = [ "futures-core", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "system-configuration" version = "0.5.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 247bfa3..4ef095b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -42,6 +42,7 @@ tracing-subscriber = { version = "0", features = [ tracing = { version = "0", features = ["log", "release_max_level_info"] } tracing-appender = '0' rust-embed = "8" +sys-locale = "0" [features] diff --git a/src-tauri/src/i18n/en_us.rs b/src-tauri/src/i18n/en_us.rs new file mode 100644 index 0000000..454377d --- /dev/null +++ b/src-tauri/src/i18n/en_us.rs @@ -0,0 +1,33 @@ +use super::Translations; + +pub(super) const EN_US: &Translations = &Translations { + window_title: "Fluxy", + dark_mode_tooltip: "Switch to Light Mode", + light_mode_tooltip: "Switch to Dark Mode", + about_button_tooltip: "About and Help", + about_dialog_github_tooltip: "Visit Official Website", + about_dialog_feedback_tooltip: "Provide Feedback or Get Help", + home_label_text: "Select Transfer Method", + home_button_text: "Back to Home", + home_receive_button_text: "Receive", + home_send_button_text: "Send", + qrcode_page_title: "Scan to Connect", + qrcode_page_url_label: "Or visit in another computer's browser", + qrcode_page_url_tooltip: "Copy Link to Clipboard", + qrcode_page_url_copied_message: "Link Copied", + qrcode_page_toast_message: "Please scan this QR code with your phone", + ok_button_text: "Confirm", + clear_button_text: "Clear File List", + send_page_title: "Send Files", + send_page_empty_drop_description: "Drag files here", + send_page_drop_description: "You can continue dragging more files", + list_item_file_size_label: "Size", + list_item_file_type_label: "Type", + send_page_list_item_tooltip: "Click to Preview File", + receive_page_empty_description: "Please upload files from your phone or another computer", + receive_page_list_item_tooltip: "Click to Open File with Default Program", + receive_page_dropdown_open_button_label: "Open", + receive_page_dropdown_pick_button_label: "Change", + receive_page_directory_path_label: "Save Directory", + receive_page_directory_path_tooltip: "Click to Open Directory in File Explorer", +}; diff --git a/src-tauri/src/i18n/mod.rs b/src-tauri/src/i18n/mod.rs new file mode 100644 index 0000000..6eed7e3 --- /dev/null +++ b/src-tauri/src/i18n/mod.rs @@ -0,0 +1,61 @@ +use std::{collections::HashMap, sync::LazyLock}; + +use serde::Serialize; + +mod en_us; +mod zh_cn; + +pub static LOCALES: LazyLock> = LazyLock::new(|| { + [(&Locale::ZhCN, zh_cn::ZH_CN), (&Locale::EnUS, en_us::EN_US)] + .iter() + .copied() + .collect() +}); + +#[derive(PartialEq, Eq, Hash)] +pub enum Locale { + ZhCN, + EnUS, +} + +impl From for Locale { + fn from(value: String) -> Self { + match value.as_ref() { + "zh-CNa" => Self::ZhCN, + _ => Self::EnUS, + } + } +} + +#[derive(PartialEq, Eq, Hash, Serialize)] +pub struct Translations { + pub window_title: &'static str, + pub dark_mode_tooltip: &'static str, + pub light_mode_tooltip: &'static str, + pub about_button_tooltip: &'static str, + pub about_dialog_github_tooltip: &'static str, + pub about_dialog_feedback_tooltip: &'static str, + pub home_label_text: &'static str, + pub home_button_text: &'static str, + pub home_send_button_text: &'static str, + pub home_receive_button_text: &'static str, + pub qrcode_page_title: &'static str, + pub qrcode_page_url_label: &'static str, + pub qrcode_page_url_tooltip: &'static str, + pub qrcode_page_url_copied_message: &'static str, + pub qrcode_page_toast_message: &'static str, + pub ok_button_text: &'static str, + pub clear_button_text: &'static str, + pub send_page_title: &'static str, + pub send_page_empty_drop_description: &'static str, + pub send_page_drop_description: &'static str, + pub list_item_file_size_label: &'static str, + pub list_item_file_type_label: &'static str, + pub send_page_list_item_tooltip: &'static str, + pub receive_page_empty_description: &'static str, + pub receive_page_list_item_tooltip: &'static str, + pub receive_page_dropdown_open_button_label: &'static str, + pub receive_page_dropdown_pick_button_label: &'static str, + pub receive_page_directory_path_label: &'static str, + pub receive_page_directory_path_tooltip: &'static str, +} diff --git a/src-tauri/src/i18n/zh_cn.rs b/src-tauri/src/i18n/zh_cn.rs new file mode 100644 index 0000000..373614b --- /dev/null +++ b/src-tauri/src/i18n/zh_cn.rs @@ -0,0 +1,33 @@ +use super::Translations; + +pub(super) const ZH_CN: &Translations = &Translations { + window_title: "小路速传", + dark_mode_tooltip: "切换为亮色", + light_mode_tooltip: "切换为暗色", + about_button_tooltip: "关于和帮助", + about_dialog_github_tooltip: "访问官网", + about_dialog_feedback_tooltip: "反馈问题或寻求帮助", + home_label_text: "选择传输方式", + home_button_text: "回到主页", + home_receive_button_text: "接收", + home_send_button_text: "发送", + qrcode_page_title: "扫码连接", + qrcode_page_url_label: "或在另一台电脑中通过浏览器中访问", + qrcode_page_url_tooltip: "复制链接到剪贴板", + qrcode_page_url_copied_message: "已复制链接", + qrcode_page_toast_message: "请使用手机扫描此二维码", + ok_button_text: "确认", + clear_button_text: "清空文件列表", + send_page_title: "发送文件", + send_page_empty_drop_description: "将文件拖到此处", + send_page_drop_description: "可继续拖入文件", + list_item_file_size_label: "大小", + list_item_file_type_label: "类型", + send_page_list_item_tooltip: "单击预览文件", + receive_page_empty_description: "请在手机端或者另一台电脑中上传文件", + receive_page_list_item_tooltip: "单击使用默认程序打开此文件", + receive_page_dropdown_open_button_label: "打开", + receive_page_dropdown_pick_button_label: "修改", + receive_page_directory_path_label: "保存目录", + receive_page_directory_path_tooltip: "单击在文件管理器中打开此目录", +}; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index d39e053..7506ef4 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,6 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] mod error; +mod i18n; mod lazy; #[cfg(target_os = "linux")] mod linux; @@ -22,12 +23,14 @@ use std::{ use qrcode_generator::QrCodeEcc; use serde::Serialize; +use sys_locale::get_locale; use tauri::{AppHandle, Manager, UpdaterEvent}; use time::macros::{format_description, offset}; use tokio::fs::File; use tracing::Level; use tracing_subscriber::fmt::time::OffsetTime; +use crate::i18n::{Locale, Translations, LOCALES}; use crate::lazy::LOCAL_IP; #[cfg(target_os = "macos")] use crate::menu::{handle_menu_event, new_menu}; @@ -190,6 +193,12 @@ async fn get_files_metadata(paths: Vec) -> FluxyResult> { Ok(files) } +#[tauri::command] +fn get_locale_translations() -> &'static Translations { + let locale: Locale = get_locale().unwrap_or_else(|| String::from("en-US")).into(); + LOCALES[&locale] +} + #[tauri::command] fn is_linux() -> bool { cfg!(target_os = "linux") @@ -244,6 +253,11 @@ async fn main() -> FluxyResult<()> { #[cfg(not(debug_assertions))] builder.json().init(); + let locale = get_locale().unwrap_or_else(|| String::from("en-US")); + debug!("current locale: {}", locale); + let locale: Locale = locale.into(); + let translations = LOCALES[&locale]; + #[cfg(target_os = "linux")] { let scale_factor = crate::linux::get_scale_factor()?; @@ -262,6 +276,7 @@ async fn main() -> FluxyResult<()> { let mut builder = tauri::Builder::default() .setup(|app| { if let Some(w) = app.get_window("main") { + w.set_title(translations.window_title).unwrap(); if MAIN_WINDOW.set(w).is_err() { error!(message = "设置主窗口失败"); app.handle().exit(1); @@ -277,7 +292,8 @@ async fn main() -> FluxyResult<()> { get_files_metadata, get_send_files_url_qr_code, is_linux, - show_main_window + show_main_window, + get_locale_translations ]); // windows 和 linux 的菜单在窗口内, 无法自动切换暗色, 所以不使用菜单 diff --git a/src-tauri/src/server/error.rs b/src-tauri/src/server/error.rs index 4b695ec..5221e4d 100644 --- a/src-tauri/src/server/error.rs +++ b/src-tauri/src/server/error.rs @@ -20,10 +20,7 @@ impl ServerError { ) -> Self { let msg: Option<&str> = error.into(); let advice: Option<&str> = advice.into(); - let advice = match advice { - None => None, - Some(s) => Some(s.to_owned()), - }; + let advice = advice.map(|s| s.to_owned()); match msg { None => Self::Internal, diff --git a/src/App.tsx b/src/App.tsx index 7ea3c25..8ce5461 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { Match, Switch, createSignal, onMount } from "solid-js"; +import { Match, Switch, createResource, createSignal, onMount } from "solid-js"; import { TbArrowsTransferUp, TbArrowsTransferDown } from "solid-icons/tb"; import { BiRegularSun, BiSolidMoon } from "solid-icons/bi"; import { @@ -15,7 +15,7 @@ import "~/App.scss"; import useDark from "alley-components/lib/hooks/useDark"; import About from "./about"; import { AppContext } from "./context"; -import { showMainWindow } from "./api"; +import { getLocaleTranslations, showMainWindow } from "./api"; enum Mode { Send = 1, @@ -27,6 +27,7 @@ const App = () => { const [mode, setMode] = createSignal(null); const [showAbout, setShowAbout] = createSignal(false); + const [translations] = createResource(getLocaleTranslations); const goHome = () => setMode(null); @@ -36,10 +37,18 @@ const App = () => { setShowAbout(true) }, }} > - + { -
选择传输方式
+
{translations()?.home_label_text}
{suspense( { icon={} onClick={() => setMode(Mode.Receive)} > - 接收 + {translations()?.home_receive_button_text} , )} @@ -74,7 +83,7 @@ const App = () => { icon={} onClick={() => setMode(Mode.Send)} > - 发送 + {translations()?.home_send_button_text} , )} diff --git a/src/about.tsx b/src/about.tsx index 05c721d..17b5946 100644 --- a/src/about.tsx +++ b/src/about.tsx @@ -8,14 +8,16 @@ import { LazyTypographyTitle, } from "./lazy"; import useDark from "alley-components/lib/hooks/useDark"; -import { createSignal, onMount } from "solid-js"; +import { createSignal, onMount, useContext } from "solid-js"; import { app } from "@tauri-apps/api"; import { AiFillGithub } from "solid-icons/ai"; import { RiCommunicationFeedbackLine } from "solid-icons/ri"; import { open } from "@tauri-apps/api/shell"; import "./about.scss"; +import { AppContext } from "./context"; const About = () => { + const { translations } = useContext(AppContext)!; const [name, setName] = createSignal(""); const [version, setVersion] = createSignal(""); @@ -39,7 +41,10 @@ const About = () => { - + } type="plain" @@ -48,7 +53,10 @@ const About = () => { /> - + } type="plain" diff --git a/src/api.ts b/src/api.ts index b250c74..75c078e 100644 --- a/src/api.ts +++ b/src/api.ts @@ -26,3 +26,6 @@ export const getSendFilesUrlQrCode = async (files: SendFile[]) => export const isLinux = async () => await invoke("is_linux"); export const showMainWindow = async () => invoke("show_main_window"); + +export const getLocaleTranslations = async () => + invoke("get_locale_translations"); diff --git a/src/components/aboutButton/index.tsx b/src/components/aboutButton/index.tsx index ecb3309..b56d99f 100644 --- a/src/components/aboutButton/index.tsx +++ b/src/components/aboutButton/index.tsx @@ -6,12 +6,13 @@ import { LazyFloatButton } from "~/lazy"; const AboutButton = () => { const { about: { onShow }, + translations, } = useContext(AppContext)!; return ( } - tooltip="关于和帮助" + tooltip={translations()!.about_button_tooltip} onClick={onShow} /> ); diff --git a/src/components/qrcode/index.tsx b/src/components/qrcode/index.tsx index 14ee007..900744c 100644 --- a/src/components/qrcode/index.tsx +++ b/src/components/qrcode/index.tsx @@ -10,7 +10,8 @@ import { } from "~/lazy"; import "./index.scss"; import { AiFillCopy } from "solid-icons/ai"; -import { createSignal } from "solid-js"; +import { createSignal, useContext } from "solid-js"; +import { AppContext } from "~/context"; interface QRCodeProps { qrcode: QrCode; @@ -19,6 +20,7 @@ interface QRCodeProps { const baseClassName = "qr-code"; const QRCode = ({ qrcode }: QRCodeProps) => { + const { translations } = useContext(AppContext)!; const [showToast, setShowToast] = createSignal(false); return ( @@ -28,10 +30,17 @@ const QRCode = ({ qrcode }: QRCodeProps) => { justify="center" direction="vertical" > -

扫码连接

+ { }} + /> + +

{translations()?.qrcode_page_title}

-
或在另一台电脑中通过浏览器中访问
+
{translations()?.qrcode_page_url_label}
{ {qrcode.url} - + } shape="circle" @@ -61,7 +70,7 @@ const QRCode = ({ qrcode }: QRCodeProps) => { autoHideDuration={1000} alert={{ type: "success", - message: "已复制链接", + message: translations()?.qrcode_page_url_copied_message, }} /> diff --git a/src/pages/receive/fileListItem.tsx b/src/pages/receive/fileListItem.tsx index 997b4e8..692004c 100644 --- a/src/pages/receive/fileListItem.tsx +++ b/src/pages/receive/fileListItem.tsx @@ -1,4 +1,4 @@ -import { Show } from "solid-js"; +import { Show, useContext } from "solid-js"; import { open } from "@tauri-apps/api/shell"; import { AiFillCheckCircle } from "solid-icons/ai"; import fileType from "./fileType"; @@ -10,6 +10,7 @@ import { LazyTooltip, LazyTypographyText, } from "~/lazy"; +import { AppContext } from "~/context"; interface FileListItemProps { index?: number; @@ -21,6 +22,8 @@ interface FileListItemProps { } const FileListItem = (props: FileListItemProps) => { + const { translations } = useContext(AppContext)!; + const extension = getExtension(props.name); return ( @@ -38,7 +41,10 @@ const FileListItem = (props: FileListItemProps) => { {props.name} } > - + open(props.path)}>{props.name} @@ -46,8 +52,12 @@ const FileListItem = (props: FileListItemProps) => { } description={ - 大小: {props.size} - 类型:{fileType(extension)} + + {translations()?.list_item_file_size_label}: {props.size} + + + {translations()?.list_item_file_type_label}: {fileType(extension)} + } extra={ diff --git a/src/pages/receive/header.tsx b/src/pages/receive/header.tsx index c2e9768..13de81d 100644 --- a/src/pages/receive/header.tsx +++ b/src/pages/receive/header.tsx @@ -1,14 +1,17 @@ import { open as pick } from "@tauri-apps/api/dialog"; import { open } from "@tauri-apps/api/shell"; -import { createEffect, createSignal, onMount } from "solid-js"; +import { createEffect, createSignal, onMount, useContext } from "solid-js"; import { changeDownloadsDir, getDownloadsDir, isLinux } from "~/api"; import type { MenuItemProps } from "alley-components/lib/components/dropdown"; import Loading from "alley-components/lib/components/spinner"; import { LazyCol, LazyDropdown, LazyLink, LazyRow, LazyTooltip } from "~/lazy"; +import { AppContext } from "~/context"; const baseClassName = "receive-header"; const Header = () => { + const { translations } = useContext(AppContext)!; + const [downloadDir, setDownloadDir] = createSignal( undefined, ); @@ -44,11 +47,11 @@ const Header = () => { const dropdownItems: MenuItemProps[] = [ { - label: "打开", + label: translations()!.receive_page_dropdown_open_button_label, onClick: () => open(downloadDir()!), }, { - label: "修改", + label: translations()!.receive_page_dropdown_pick_button_label, onClick: () => pickDirectory(), }, ]; @@ -69,7 +72,9 @@ const Header = () => { top={dropdownTop()} left={18} > - 保存目录 + + {translations()?.receive_page_directory_path_label} + @@ -79,7 +84,10 @@ const Header = () => { align="center" justify="center" > - + { setOpenDropDown(false); diff --git a/src/pages/receive/index.tsx b/src/pages/receive/index.tsx index fe6ec1b..abce48e 100644 --- a/src/pages/receive/index.tsx +++ b/src/pages/receive/index.tsx @@ -22,7 +22,6 @@ import { LazyQrcode, LazyList, LazyFloatButtonGroup, - LazyToast, LazyAboutButton, } from "~/lazy"; import { createStore } from "solid-js/store"; @@ -30,7 +29,7 @@ import { AiFillDelete, AiOutlineHome } from "solid-icons/ai"; import { AppContext } from "~/context"; const Receive = () => { - const { goHome } = useContext(AppContext)!; + const { goHome, translations } = useContext(AppContext)!; const [qrcode, setQrcode] = createSignal(null); @@ -101,7 +100,7 @@ const Receive = () => { } onClick={() => setFileList([])} danger @@ -109,7 +108,7 @@ const Receive = () => { } onClick={() => { setTaskList([]); @@ -123,16 +122,8 @@ const Receive = () => { return ( - {/* biome-ignore lint/style/noNonNullAssertion: */} - { }} - /> - {floatButtons()} @@ -148,7 +139,9 @@ const Receive = () => { align="center" justify="center" > - + diff --git a/src/pages/send/index.tsx b/src/pages/send/index.tsx index 41496eb..60ccf67 100644 --- a/src/pages/send/index.tsx +++ b/src/pages/send/index.tsx @@ -37,7 +37,7 @@ import { open } from "@tauri-apps/api/shell"; import { AppContext } from "~/context"; const Send = () => { - const { goHome } = useContext(AppContext)!; + const { goHome, translations } = useContext(AppContext)!; const [files, setFiles] = createSignal([]); @@ -98,12 +98,12 @@ const Send = () => { icon={} onClick={() => setFiles([])} danger - tooltip={"清空文件列表"} + tooltip={translations()?.clear_button_text} /> } onClick={goHome} /> @@ -120,7 +120,7 @@ const Send = () => { justify="center" direction="vertical" > -
发送文件
+
{translations()?.send_page_title}
{ + open(file.path)}> {file.name} @@ -146,9 +149,15 @@ const Send = () => { } description={ <> - 大小: {file.size} + + {translations()?.list_item_file_size_label}:{" "} + {file.size} +      - 类型: {file.extension} + + {translations()?.list_item_file_type_label}:{" "} + {file.extension} + } extra={[ @@ -167,11 +176,13 @@ const Send = () => { /> - 可继续拖入文件 + {translations()?.send_page_drop_description} ) : ( - + )} @@ -181,7 +192,7 @@ const Send = () => { block disabled={isEmpty()} > - 确认 + {translations()?.ok_button_text}
diff --git a/static/src/App.tsx b/static/src/App.tsx index 1aa34a5..1bab796 100644 --- a/static/src/App.tsx +++ b/static/src/App.tsx @@ -5,6 +5,8 @@ import Send from "~/pages/send"; import Receive from "~/pages/receive"; import SwitchDark from "~/components/switch"; import "~/App.scss"; +import { getLocale } from "./i18n"; +import LocaleContext from "./context"; type Mode = "receive" | "send"; @@ -14,6 +16,8 @@ const App = () => { const params = new URLSearchParams(url.search); const mode = params.get("mode") as Mode | null; + const locale = getLocale(); + const [isDark, setIsDark] = createSignal(false); onMount(() => { @@ -43,7 +47,7 @@ const App = () => { }); return ( -
+ {
@@ -76,7 +80,7 @@ const App = () => { -
+ ); }; diff --git a/static/src/components/upload/fileItem.tsx b/static/src/components/upload/fileItem.tsx index fe29b5e..29390f3 100644 --- a/static/src/components/upload/fileItem.tsx +++ b/static/src/components/upload/fileItem.tsx @@ -6,6 +6,9 @@ import Space from "~/components/space"; import DotLoading from "~/components/loading/dot"; import List from "~/components/list"; import Progress from "../progress"; +import { Show, useContext } from "solid-js"; +import LocaleContext from "~/context"; +import ZH_CN from "~/i18n/zh_cn"; const getExtension = (name: string): string => { const dotIndex = name.lastIndexOf("."); @@ -24,6 +27,7 @@ interface FileItemProps { } const FileItem = (props: FileItemProps) => { + const locale = useContext(LocaleContext)!; const extension = getExtension(props.file.name); return ( @@ -36,8 +40,15 @@ const FileItem = (props: FileItemProps) => { } description={ - 大小: {formatFileSize(props.file.size)} - 类型:{fileType(extension)} + + {locale.file_item_file_size_label}: + {formatFileSize(props.file.size)} + + + + {locale.file_item_file_type_label}:{fileType(extension)} + + } extra={ diff --git a/static/src/components/upload/index.tsx b/static/src/components/upload/index.tsx index 09272a1..102fe2c 100644 --- a/static/src/components/upload/index.tsx +++ b/static/src/components/upload/index.tsx @@ -1,4 +1,10 @@ -import { type JSX, createEffect, createSignal, createUniqueId } from "solid-js"; +import { + type JSX, + createEffect, + createSignal, + createUniqueId, + useContext, +} from "solid-js"; import { createStore } from "solid-js/store"; import { getFileItemIndex } from "./util"; import request from "./request"; @@ -9,6 +15,7 @@ import FileItem from "./fileItem"; import "./index.scss"; import Button from "../button"; import { AiOutlinePlus } from "solid-icons/ai"; +import LocaleContext from "~/context"; interface UploadProps { action: string; @@ -18,6 +25,8 @@ interface UploadProps { } const Upload = ({ action, headers, withCredentials, method }: UploadProps) => { + const locale = useContext(LocaleContext)!; + let fileInput: HTMLInputElement | undefined; const [fileItems, setFileItems] = createStore([]); @@ -129,13 +138,13 @@ const Upload = ({ action, headers, withCredentials, method }: UploadProps) => {
) : ( ( (); + +export default LocaleContext; diff --git a/static/src/i18n/en_us.ts b/static/src/i18n/en_us.ts new file mode 100644 index 0000000..e81c064 --- /dev/null +++ b/static/src/i18n/en_us.ts @@ -0,0 +1,24 @@ +import type { Locale } from "."; + +const EN_US: Locale = { + invalid_request: "Invalid request", + mode_is_required: "Query parameter 'mode' is required", + + file_item_file_size_label: "Size", + file_item_file_type_label: "Type", + + send_page_title: "Send File", + send_page_empty_title: "No File Selected", + send_page_empty_description: + "Click the plus button in the top left corner to select files", + send_page_uploading_tooltip: + "Click the red button on the right to interrupt unfinished tasks", + + receive_page_title: "Receive File", + receive_page_toast: + "Do not refresh this page, or the file list will be cleared", + receive_page_file_list_header: + "Click the filename or the button on the right to download", +}; + +export default EN_US; diff --git a/static/src/i18n/index.ts b/static/src/i18n/index.ts new file mode 100644 index 0000000..1773198 --- /dev/null +++ b/static/src/i18n/index.ts @@ -0,0 +1,29 @@ +import EN_US from "./en_us"; +import ZH_CN from "./zh_cn"; + +export interface Locale { + invalid_request: string; + mode_is_required: string; + + file_item_file_size_label: string; + file_item_file_type_label: string; + + send_page_title: string; + send_page_empty_title: string; + send_page_empty_description: string; + send_page_uploading_tooltip: string; + + receive_page_title: string; + receive_page_toast: string; + receive_page_file_list_header: string; +} + +export const getLocale = (): Locale => { + const language = navigator.language; + + if (language === "zh-CN") { + return ZH_CN; + } + + return EN_US; +}; diff --git a/static/src/i18n/zh_cn.ts b/static/src/i18n/zh_cn.ts new file mode 100644 index 0000000..1ad0a9a --- /dev/null +++ b/static/src/i18n/zh_cn.ts @@ -0,0 +1,20 @@ +import type { Locale } from "."; + +const ZH_CN: Locale = { + invalid_request: "无效的请求", + mode_is_required: "缺少查询参数:mode", + + file_item_file_size_label: "大小", + file_item_file_type_label: "类型", + + send_page_title: "发送文件", + send_page_empty_title: "未选择文件", + send_page_empty_description: "点击左上角的加号按钮选择文件", + send_page_uploading_tooltip: "未完成的任务可点击右侧红色按钮中断", + + receive_page_title: "接收文件", + receive_page_toast: "不要刷新此页面,否则文件列表将会被清空", + receive_page_file_list_header: "点击文件名或右侧按钮即可下载", +}; + +export default ZH_CN; diff --git a/static/src/pages/receive/index.tsx b/static/src/pages/receive/index.tsx index 359b25b..98c0892 100644 --- a/static/src/pages/receive/index.tsx +++ b/static/src/pages/receive/index.tsx @@ -1,4 +1,4 @@ -import { Match, Switch, createResource } from "solid-js"; +import { Match, Show, Switch, createResource, useContext } from "solid-js"; import { AiOutlineCloudDownload } from "solid-icons/ai"; import Result from "~/components/result"; import SpinLoading from "~/components/loading/spin"; @@ -8,6 +8,8 @@ import List from "~/components/list"; import fileType from "./fileType"; import Link from "~/components/link"; import "./index.scss"; +import LocaleContext from "~/context"; +import ZH_CN from "~/i18n/zh_cn"; type ResponseData = SendFile[] | BadRequest; @@ -24,11 +26,13 @@ const fetchData = async (): Promise => { }; const Receive = () => { + const locale = useContext(LocaleContext)!; + const [data] = createResource(fetchData); return (
-
接收文件
+
{locale.receive_page_title}
@@ -51,14 +55,11 @@ const Receive = () => {
- + { const url = "/download/" + encodeURIComponent(item.path); @@ -78,8 +79,12 @@ const Receive = () => { } description={ - 大小:{item.size} - 类型:{fileType(item.extension)} + + {locale.file_item_file_size_label}:{item.size} + + + 类型:{fileType(item.extension)} + } extra={[ diff --git a/static/src/pages/send/index.tsx b/static/src/pages/send/index.tsx index bb175c4..643d4ce 100644 --- a/static/src/pages/send/index.tsx +++ b/static/src/pages/send/index.tsx @@ -1,10 +1,14 @@ import Upload from "~/components/upload"; import "./index.scss"; +import { useContext } from "solid-js"; +import LocaleContext from "~/context"; const Send = () => { + const locale = useContext(LocaleContext)!; + return (
-
发送文件
+
{locale.send_page_title}
); diff --git a/types/context.d.ts b/types/context.d.ts index 8534a1e..ed2f9ed 100644 --- a/types/context.d.ts +++ b/types/context.d.ts @@ -8,4 +8,5 @@ interface AppContext { show: Accessor; onShow: () => void; }; + translations: Resource; } diff --git a/types/index.d.ts b/types/index.d.ts index 3bca5b4..72a7e26 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -21,3 +21,35 @@ interface SendFile { } type CSSProperties = JSX.CSSProperties; + +interface Translations { + window_title: string; + dark_mode_tooltip: string; + light_mode_tooltip: string; + about_button_tooltip: string; + about_dialog_github_tooltip: string; + about_dialog_feedback_tooltip: string; + home_label_text: string; + home_button_text: string; + home_send_button_text: string; + home_receive_button_text: string; + qrcode_page_title: string; + qrcode_page_url_label: string; + qrcode_page_url_tooltip: string; + qrcode_page_url_copied_message: string; + qrcode_page_toast_message: string; + ok_button_text: string; + clear_button_text: string; + send_page_title: string; + send_page_empty_drop_description: string; + send_page_drop_description: string; + list_item_file_size_label: string; + list_item_file_type_label: string; + send_page_list_item_tooltip: string; + receive_page_empty_description: string; + receive_page_list_item_tooltip: string; + receive_page_dropdown_open_button_label: string; + receive_page_dropdown_pick_button_label: string; + receive_page_directory_path_label: string; + receive_page_directory_path_tooltip: string; +}