feat: 支持多语言
This commit is contained in:
		@@ -46,7 +46,7 @@ pub struct Args {
 | 
			
		||||
    #[arg(long, default_value = "./data/", help = "Path to store database files")]
 | 
			
		||||
    path: String,
 | 
			
		||||
 | 
			
		||||
    #[arg(long, default_value = "./asset/", help = "Path to store database files")]
 | 
			
		||||
    #[arg(long, default_value = "./assets/", help = "Path to store database files")]
 | 
			
		||||
    asset_path: String,
 | 
			
		||||
 | 
			
		||||
    #[arg(long, default_value_t = false, help = "Enable assets server")]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4554
									
								
								webui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4554
									
								
								webui/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -10,9 +10,11 @@
 | 
			
		||||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "i18next": "^24.2.1",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-bootstrap": "^2.10.2",
 | 
			
		||||
    "react-dom": "^18.2.0"
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "react-i18next": "^15.4.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/react": "^18.2.66",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,19 @@
 | 
			
		||||
import {useEffect, useState} from 'react'
 | 
			
		||||
import './Help.css'
 | 
			
		||||
import Request from '../Request.jsx'
 | 
			
		||||
import i18n from 'i18next';
 | 
			
		||||
import i18next from "i18next";
 | 
			
		||||
import {useTranslation} from "react-i18next";
 | 
			
		||||
 | 
			
		||||
let init = false
 | 
			
		||||
let defaultDownloadUrl = "您的服务器管理员没有可供下载的预打补丁的应用程序包。请参阅下面的问题。"
 | 
			
		||||
 | 
			
		||||
function Help() {
 | 
			
		||||
    const [downloadUrl, setDownloadUrl] = useState(<div>{defaultDownloadUrl}</div>);
 | 
			
		||||
    const [downloadUrl, setDownloadUrl] = useState();
 | 
			
		||||
    const [defaultDownloadUrl, setDefaultDownloadUrl] = useState();
 | 
			
		||||
    const [downloadUrliOSGL, setDownloadUrliOSGL] = useState("https://ethanthesleepy.one/public/lovelive/sif2/sif2-gl.ipa");
 | 
			
		||||
    const [downloadUrliOSJP, setDownloadUrliOSJP] = useState("https://ethanthesleepy.one/public/lovelive/sif2/sif2-jp.ipa");
 | 
			
		||||
    const [language, setLanguage] = useState()
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
    const [assetUrl, setAssetUrl] = useState("https://sif2.sif.moe");
 | 
			
		||||
 | 
			
		||||
@@ -23,16 +28,16 @@ function Help() {
 | 
			
		||||
            if (!resp.data.links) return;
 | 
			
		||||
            if (resp.data.links.global && resp.data.links.japan) {
 | 
			
		||||
                setDownloadUrl(
 | 
			
		||||
                        <div>您的服务器管理员有下载链接!下载 <a href={resp.data.links.japan}>日服</a> 或者 <a
 | 
			
		||||
                                href={resp.data.links.global}>国际服</a></div>
 | 
			
		||||
                    <div>{t('has_download_url')}<a href={resp.data.links.japan}>{t('japan')}</a> {t('or')} <a
 | 
			
		||||
                                href={resp.data.links.global}>{t('global')}</a></div>
 | 
			
		||||
                );
 | 
			
		||||
            } else if (resp.data.links.global) {
 | 
			
		||||
                setDownloadUrl(
 | 
			
		||||
                        <div>您的服务器管理员有下载链接!下载 <a href={resp.data.links.global}>国际服</a></div>
 | 
			
		||||
                        <div>{t('has_download_url')}<a href={resp.data.links.global}>{t('global')}</a></div>
 | 
			
		||||
                );
 | 
			
		||||
            } else if (resp.data.links.japan) {
 | 
			
		||||
                setDownloadUrl(
 | 
			
		||||
                        <div>您的服务器管理员有下载链接!下载 <a href={resp.data.links.japan}>日服</a></div>
 | 
			
		||||
                        <div>{t('has_download_url')}<a href={resp.data.links.japan}>{t('japan')}</a></div>
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            if (resp.data.links.assets) {
 | 
			
		||||
@@ -50,62 +55,139 @@ function Help() {
 | 
			
		||||
        })();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setLanguage(i18next.language)
 | 
			
		||||
        setDefaultDownloadUrl(t('default_download_url'))
 | 
			
		||||
        setDownloadUrl(t('default_download_url'))
 | 
			
		||||
        const handleLanguageChanged = (lng) => {
 | 
			
		||||
            setLanguage(lng)
 | 
			
		||||
            setDownloadUrl(t('default_download_url'))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        i18n.on('languageChanged', handleLanguageChanged);
 | 
			
		||||
 | 
			
		||||
        return () => {
 | 
			
		||||
            i18n.off('languageChanged', handleLanguageChanged);
 | 
			
		||||
        };
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const renderNoAndroidDownloadUrl = () => {
 | 
			
		||||
        return <>
 | 
			
		||||
            <h2>我的服务器管理员没有下载链接,我该如何修补apk(安卓)?</h2>
 | 
			
		||||
            <p>你可以用 <a href="https://sif2patch.zhushenwudi.top">@arasfon's sif2 apk 修补工具</a>。
 | 
			
		||||
                在“服务器URL”中填写"{window.location.origin}",在“资源URL”中填写 "{assetUrl}"(默认已经帮你填入了以上信息)。
 | 
			
		||||
                选择你的游戏版本, 更改头部格式为“Lowercase”, 最后点击开始修补。完成修补后使用 <a
 | 
			
		||||
        if (language === "zh") {
 | 
			
		||||
            return <>
 | 
			
		||||
                <h2>我的服务器管理员没有下载链接,我该如何修补apk(安卓)?</h2>
 | 
			
		||||
                <p>你可以用 <a href="https://sif2patch.zhushenwudi.top">@arasfon's sif2 apk 修补工具</a>。
 | 
			
		||||
                    在“服务器URL”中填写"{window.location.origin}",在“资源URL”中填写 "{assetUrl}"(默认已经帮你填入了以上信息)。
 | 
			
		||||
                    选择你的游戏版本, 更改头部格式为“Lowercase”, 最后点击开始修补。完成修补后使用 <a
 | 
			
		||||
                        href="https://github.com/patrickfav/uber-apk-signer">uber-apk-signer</a> 来签署apk.
 | 
			
		||||
                最后便可以安装到你的手机上了!</p>
 | 
			
		||||
        </>
 | 
			
		||||
                    最后便可以安装到你的手机上了!</p>
 | 
			
		||||
            </>
 | 
			
		||||
        } else {
 | 
			
		||||
            return <>
 | 
			
		||||
                <h2>My server admin has no download URLs, how do I patch the apk? (Android)</h2>
 | 
			
		||||
                <p>You can use <a href="https://sif2patch.zhushenwudi.top">@arasfon's sif2 apk 修补工具</a>.
 | 
			
		||||
                    Plug and the asset url, which is "{assetUrl}", into the "Assets URL" textbox,
 | 
			
		||||
                    and then the server url (Which is likely "{window.location.origin}", though this may not be the case).
 | 
			
		||||
                    Select your game version, set "header format" to "Lowercase", and press patch. Once that done, use
 | 
			
		||||
                    <a href="https://github.com/patrickfav/uber-apk-signer">uber-apk-signer</a>
 | 
			
		||||
                    to sign the apk. Then, get it onto your phone and install!</p>
 | 
			
		||||
            </>
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const renderZh = () => {
 | 
			
		||||
        return <div id="home">
 | 
			
		||||
            <h1>帮助/关于</h1>
 | 
			
		||||
            <h2>“ew”是什么?这个服务器是用来做什么的?</h2>
 | 
			
		||||
            <p>“ew”是一个用Rust编写的私人服务器,原作者:Ethan O'Brien,原项目:<a
 | 
			
		||||
                href="https://git.ethanthesleepy.one/ethanaobrien/ew">ew仓库地址</a>。
 | 
			
		||||
                本项目为二次开发版本,用于短命的游戏《Love Live! School idol festival 2 MIRACLE LIVE!》,
 | 
			
		||||
                这是一款以Love Live!为主题的移动节奏游戏。</p>
 | 
			
		||||
 | 
			
		||||
            <h2>我只是想在这个服务器上玩,我该如何安装这个应用程序?(安卓)</h2>
 | 
			
		||||
            <p>{downloadUrl}</p>
 | 
			
		||||
 | 
			
		||||
            {downloadUrl === defaultDownloadUrl ? renderNoAndroidDownloadUrl() : null}
 | 
			
		||||
 | 
			
		||||
            <h2>我该如何安装这款应用?(iOS)</h2>
 | 
			
		||||
            <p>在iOS上运行要比Android简单得多,这要归功于Discord上的Triangle。你首先要下载一个用于 <a
 | 
			
		||||
                href={downloadUrliOSGL}>国际服</a> 或者 <a href={downloadUrliOSJP}>日服</a> 的ipa文件,
 | 
			
		||||
                然后使用 <a href="https://sideloadly.io/">Sideloadly</a> (或任何能够实现侧载的应用程序)来安装这个APP。打开手机系统设置,
 | 
			
		||||
                导航到SIF2应用的设置页面, 填入“Server URL”:"{window.location.origin}" 与 “Assets URL”:"{assetUrl}"。
 | 
			
		||||
                如果您在打开应用程序时遇到任何错误,请确保设置中的所有网址都没有以斜杠结尾。</p>
 | 
			
		||||
 | 
			
		||||
            <h2>求求!我尝试打开SIF2,但它显示为“不可用”(iOS)。</h2>
 | 
			
		||||
            <p>不要删除当前的SIF2,只需重新侧载该应用程序。这是苹果的安全功能。</p>
 | 
			
		||||
 | 
			
		||||
            <h2>程序的执行效率如何?</h2>
 | 
			
		||||
            <p>运行得相当不错。服务器本身占用的存储空间甚至不到20兆字节,并且是用Rust编写的。我个人认为它写得相当好。</p>
 | 
			
		||||
 | 
			
		||||
            <h2>我的电脑/笔记本电脑能运行服务器吗?</h2>
 | 
			
		||||
            <p>非常有可能。如果平台是支持 <a
 | 
			
		||||
                href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">Rust语言</a> 的,
 | 
			
		||||
                那么答案是肯定的!在我有时间设置自动操作之前,建议手动编译项目。 <a
 | 
			
		||||
                    href="https://git.zhushenwudi.top/zhushenwudi/ew">ew仓库地址</a></p>
 | 
			
		||||
 | 
			
		||||
            <h2>服务器现在是宕机了吗?我无法连接。</h2>
 | 
			
		||||
            <p>假设你刚刚在你使用的服务器上加载了这个页面,那么答案是否定的,否则请联系你的服务器管理员。</p>
 | 
			
		||||
 | 
			
		||||
            <h2>打歌活动能正常游玩吗?</h2>
 | 
			
		||||
            <p>大多数活动会让游戏崩溃,不过这确实是不应该的。明星活动部分已实现。你可以提升自己的排名,并在排名表中与其他玩家竞争,但目前尚未实现奖励功能。</p>
 | 
			
		||||
 | 
			
		||||
            <h2>我该如何获取活动UR呢?</h2>
 | 
			
		||||
            <p>可以通过游戏内兑换码的方式领取活动UR:UR</p>
 | 
			
		||||
 | 
			
		||||
            <h2>当我操作窗口关闭时,为什么游戏会崩溃?</h2>
 | 
			
		||||
            <p>这很可能意味着服务器上有某些地方出故障了。如果你是自行托管(服务器),请通过Matrix联系我。否则,联系你的服务器管理员,并让他们报告这个问题。</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const renderEn = () => {
 | 
			
		||||
        return <div id="home">
 | 
			
		||||
            <h1>Help/About</h1>
 | 
			
		||||
            <h2>What is "ew"? What is this server for?</h2>
 | 
			
		||||
            <p>"ew" is a private server, written in Rust, for the short lived game "Love Live! School idol festival 2 MIRACLE LIVE!", a Love Live! themed mobile rhythm game.</p>
 | 
			
		||||
 | 
			
		||||
            <h2>I'm just trying to play on this server, how do I install the app? (Android)</h2>
 | 
			
		||||
            <p>{downloadUrl}</p>
 | 
			
		||||
 | 
			
		||||
            {downloadUrl === defaultDownloadUrl ? renderNoAndroidDownloadUrl() : null}
 | 
			
		||||
 | 
			
		||||
            <h2>How do I install the app? (iOS)</h2>
 | 
			
		||||
            <p>Running on iOS is much simpler than Android, thanks to triangle on the discord. You first download an ipa file for
 | 
			
		||||
                <a href={downloadUrliOSGL}>global</a> or <a href={downloadUrliOSJP}>Japan</a>,
 | 
			
		||||
                and use <a href="https://sideloadly.io/">Sideloadly</a> (or your preferred application installer) to install the app.
 | 
			
		||||
                Then open settings, navigate to the app you just installed, and input the server url (Which is likely "{window.location.origin}",
 | 
			
		||||
                though this may not be the case), and the asset url, which is "{assetUrl}".
 | 
			
		||||
                If you have any errors opening the app, make sure none of the urls in settings end with a slash (/).</p>
 | 
			
		||||
 | 
			
		||||
            <h2>Help! I'm trying to open the app and it shows as "unavailable" (iOS)</h2>
 | 
			
		||||
            <p>Do not delete it, Just re-sideload the app. This is an Apple security feature.</p>
 | 
			
		||||
 | 
			
		||||
            <h2>How well does this work?</h2>
 | 
			
		||||
            <p>Works well enough. The server itself takes up not even 20mb of storage, and it's written in rust. I personally think it's pretty well written.</p>
 | 
			
		||||
 | 
			
		||||
            <h2>Could my computer/laptop run a server?</h2>
 | 
			
		||||
            <p>Very very likely. If the platform is <a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">supported by rust</a>,
 | 
			
		||||
                then the answer is yes! It is recommended to manually compile the project until I get the time to setup actions.
 | 
			
		||||
                <a href="https://git.ethanthesleepy.one/ethanaobrien/ew">ew repo</a></p>
 | 
			
		||||
 | 
			
		||||
            <h2>Is the server down right now? I can't connect</h2>
 | 
			
		||||
            <p>Assuming you have just loaded this page on the server you use, then the answer is no, otherwise please contact your server admin.</p>
 | 
			
		||||
 | 
			
		||||
            <h2>Do events work?</h2>
 | 
			
		||||
            <p>Most events do not, though most should not crash the game. Star events are partially implemented.
 | 
			
		||||
                You can get your rank up, and compete with other players in a ranking table, but no rewards are currently implemented.</p>
 | 
			
		||||
 | 
			
		||||
            <h2>But then, how do I get event URs?</h2>
 | 
			
		||||
            <p>There are serial codes for several things, one of which includes all the event URs. UR</p>
 | 
			
		||||
 | 
			
		||||
            <h2>Why does the game crash when I do x?</h2>
 | 
			
		||||
            <p>This likely means something on the server is broken. If you're self hosting, please contact me via matrix. Otherwise, contact your server admin and ask them to report the issue.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
            <div id="home">
 | 
			
		||||
                <h1>帮助/关于</h1>
 | 
			
		||||
                <h2>“ew”是什么?这个服务器是用来做什么的?</h2>
 | 
			
		||||
                <p>“ew”是一个用Rust编写的私人服务器,原作者:Ethan O'Brien,原项目:<a
 | 
			
		||||
                        href="https://git.ethanthesleepy.one/ethanaobrien/ew">ew仓库地址</a>。
 | 
			
		||||
                    本项目为二次开发版本,用于短命的游戏《Love Live! School idol festival 2 MIRACLE LIVE!》,
 | 
			
		||||
                    这是一款以Love Live!为主题的移动节奏游戏。</p>
 | 
			
		||||
 | 
			
		||||
                <h2>我只是想在这个服务器上玩,我该如何安装这个应用程序?(安卓)</h2>
 | 
			
		||||
                <p>{downloadUrl}</p>
 | 
			
		||||
 | 
			
		||||
                {downloadUrl === defaultDownloadUrl ? renderNoAndroidDownloadUrl() : null}
 | 
			
		||||
 | 
			
		||||
                <h2>我该如何安装这款应用?(iOS)</h2>
 | 
			
		||||
                <p>在iOS上运行要比Android简单得多,这要归功于Discord上的Triangle。你首先要下载一个用于 <a
 | 
			
		||||
                        href={downloadUrliOSGL}>国际服</a> 或者 <a href={downloadUrliOSJP}>日服</a> 的ipa文件,
 | 
			
		||||
                    然后使用 <a href="https://sideloadly.io/">Sideloadly</a> (或任何能够实现侧载的应用程序)来安装这个APP。打开手机系统设置,
 | 
			
		||||
                    导航到SIF2应用的设置页面, 填入“Server URL”:"{window.location.origin}" 与 “Assets URL”:"{assetUrl}"。
 | 
			
		||||
                    如果您在打开应用程序时遇到任何错误,请确保设置中的所有网址都没有以斜杠结尾。</p>
 | 
			
		||||
 | 
			
		||||
                <h2>求求!我尝试打开SIF2,但它显示为“不可用”(iOS)。</h2>
 | 
			
		||||
                <p>不要删除当前的SIF2,只需重新侧载该应用程序。这是苹果的安全功能。</p>
 | 
			
		||||
 | 
			
		||||
                <h2>程序的执行效率如何?</h2>
 | 
			
		||||
                <p>运行得相当不错。服务器本身占用的存储空间甚至不到20兆字节,并且是用Rust编写的。我个人认为它写得相当好。</p>
 | 
			
		||||
 | 
			
		||||
                <h2>我的电脑/笔记本电脑能运行服务器吗?</h2>
 | 
			
		||||
                <p>非常有可能。如果平台是支持 <a
 | 
			
		||||
                        href="https://doc.rust-lang.org/nightly/rustc/platform-support.html">Rust语言</a> 的,
 | 
			
		||||
                    那么答案是肯定的!在我有时间设置自动操作之前,建议手动编译项目。 <a
 | 
			
		||||
                            href="https://git.zhushenwudi.top/zhushenwudi/ew">ew仓库地址</a></p>
 | 
			
		||||
 | 
			
		||||
                <h2>服务器现在是宕机了吗?我无法连接。</h2>
 | 
			
		||||
                <p>假设你刚刚在你使用的服务器上加载了这个页面,那么答案是否定的,否则请联系你的服务器管理员。</p>
 | 
			
		||||
 | 
			
		||||
                <h2>打歌活动能正常游玩吗?</h2>
 | 
			
		||||
                <p>大多数活动会让游戏崩溃,不过这确实是不应该的。明星活动部分已实现。你可以提升自己的排名,并在排名表中与其他玩家竞争,但目前尚未实现奖励功能。</p>
 | 
			
		||||
 | 
			
		||||
                <h2>我该如何获取活动UR呢?</h2>
 | 
			
		||||
                <p>可以通过游戏内兑换码的方式领取活动UR:meow、kode、ganuy</p>
 | 
			
		||||
 | 
			
		||||
                <h2>当我操作窗口关闭时,为什么游戏会崩溃?</h2>
 | 
			
		||||
                <p>这很可能意味着服务器上有某些地方出故障了。如果你是自行托管(服务器),请通过Matrix联系我。否则,联系你的服务器管理员,并让他们报告这个问题。</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        language === 'zh' ? renderZh() : renderEn()
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
import { useState, useEffect } from 'react'
 | 
			
		||||
import './Home.css'
 | 
			
		||||
import Request from '../Request.jsx'
 | 
			
		||||
import {useTranslation} from "react-i18next";
 | 
			
		||||
 | 
			
		||||
let bonusItems = [];
 | 
			
		||||
 | 
			
		||||
function getMigrationID(uid) {
 | 
			
		||||
@@ -21,6 +23,7 @@ function getMigrationID(uid) {
 | 
			
		||||
function Bonus() {
 | 
			
		||||
    const [inputValue, setInputValue] = useState('');
 | 
			
		||||
    const error = useState("");
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    
 | 
			
		||||
    let itemz = [];
 | 
			
		||||
    bonusItems.forEach((e) => {
 | 
			
		||||
@@ -63,7 +66,7 @@ function Bonus() {
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div>
 | 
			
		||||
          <h2>当前登录奖励列表</h2>
 | 
			
		||||
          <h2>{t('current_bonus')}</h2>
 | 
			
		||||
          <div id="error"> { error[0] ? <p>Error: { error[0] } </p> : <p></p> } </div>
 | 
			
		||||
          <ul>
 | 
			
		||||
            {submittedItems.map((item, index) => (
 | 
			
		||||
@@ -78,11 +81,11 @@ function Bonus() {
 | 
			
		||||
              type="text"
 | 
			
		||||
              value={inputValue}
 | 
			
		||||
              onChange={(event) => setInputValue(event.target.value)}
 | 
			
		||||
              placeholder="输入登录奖励ID"
 | 
			
		||||
              placeholder={t('enter_bonus_id')}
 | 
			
		||||
            />
 | 
			
		||||
            <button type="submit">提交</button>
 | 
			
		||||
            <button type="submit">{t('submit')}</button>
 | 
			
		||||
          </form>
 | 
			
		||||
          <p>你可以找到可用登录奖励ID的列表。 <a href="https://git.ethanthesleepy.one/ethanaobrien/ew/src/branch/main/src/router/databases/json/login_bonus.json">这里</a>. 你应该输入指定 <code>id</code></p>
 | 
			
		||||
          <p>{t('find_bonus_in_url')} <a href="https://git.ethanthesleepy.one/ethanaobrien/ew/src/branch/main/src/router/databases/json/login_bonus.json">{t('here')}</a>. {t('should_input')} <code>id</code></p>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@@ -92,6 +95,7 @@ function Home() {
 | 
			
		||||
    const [inputValue, setInputValue] = useState('');
 | 
			
		||||
    const [serverTime, setServerTime] = useState('');
 | 
			
		||||
    const error = useState("");
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    
 | 
			
		||||
    const logout = () => {
 | 
			
		||||
        window.location.href = "/webui/logout";
 | 
			
		||||
@@ -170,10 +174,10 @@ function Home() {
 | 
			
		||||
            
 | 
			
		||||
            userdata(
 | 
			
		||||
                <div>
 | 
			
		||||
                    <p>SIF2 用户id: { user.user.id } </p>
 | 
			
		||||
                    <p>引继码: { getMigrationID(user.user.id) } </p>
 | 
			
		||||
                    <p>等级: { user.user.rank } ({ user.user.exp } exp)</p>
 | 
			
		||||
                    <p>上次登录时间: { (new Date(user.user.last_login_time * 1000)).toString() } </p>
 | 
			
		||||
                    <p>SIF2 ID: { user.user.id } </p>
 | 
			
		||||
                    <p>{t('migration_id')}: { getMigrationID(user.user.id) } </p>
 | 
			
		||||
                    <p>{t('level')}: { user.user.rank } ({ user.user.exp } exp)</p>
 | 
			
		||||
                    <p>{t('last_login_time')}: { (new Date(user.user.last_login_time * 1000)).toString() } </p>
 | 
			
		||||
                    <Bonus />
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
@@ -182,25 +186,25 @@ function Home() {
 | 
			
		||||
  
 | 
			
		||||
    return (
 | 
			
		||||
        <div id="home">
 | 
			
		||||
            <button id="logout" onClick={expor}>导出用户数据</button><br/><br/><br/>
 | 
			
		||||
            <button id="logout" onClick={logout}>登出</button>
 | 
			
		||||
            <h1>用户主页</h1>
 | 
			
		||||
            <button id="logout" onClick={expor}>{t('export_account')}</button><br/><br/><br/>
 | 
			
		||||
            <button id="logout" onClick={logout}>{t('logout')}</button>
 | 
			
		||||
            <h1>{t('home')}</h1>
 | 
			
		||||
            
 | 
			
		||||
            { user ? <div> 
 | 
			
		||||
                { user } 
 | 
			
		||||
                <h2>服务器时间</h2>
 | 
			
		||||
                <div id="error"> { error[0] ? <p>Error: { error[0] } </p> : <p></p> } </div>
 | 
			
		||||
                <p>当前游戏时间为 { serverTime }. 将其设置为0将会设置为当前时间,而-1则会重置到开服时间。时间的推进仍将基于您设置此时间戳的时间。</p>
 | 
			
		||||
                <h2>{t('server_time')}</h2>
 | 
			
		||||
                <div id="error"> { error[0] ? <p>{t('error')}: { error[0] } </p> : <p></p> } </div>
 | 
			
		||||
                <p>{t('current_game_time')} { serverTime }. {t("set_time_hint")}</p>
 | 
			
		||||
                <form onSubmit={handleSubmit}>
 | 
			
		||||
                    <input
 | 
			
		||||
                        type="text"
 | 
			
		||||
                        value={inputValue}
 | 
			
		||||
                        onChange={(event) => setInputValue(event.target.value)}
 | 
			
		||||
                        placeholder="输入服务器时间"
 | 
			
		||||
                        placeholder={t('input_server_time')}
 | 
			
		||||
                    />
 | 
			
		||||
                    <button type="submit">提交</button>
 | 
			
		||||
                    <button type="submit">{t('submit')}</button>
 | 
			
		||||
                </form></div>
 | 
			
		||||
            : <p>Loading...</p> }
 | 
			
		||||
            : <p>{t('loading')}</p> }
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
import { useState } from 'react'
 | 
			
		||||
import './Import.css'
 | 
			
		||||
import Request from '../Request.jsx'
 | 
			
		||||
import {useTranslation} from "react-i18next";
 | 
			
		||||
 | 
			
		||||
function Login() {
 | 
			
		||||
    const error = useState(new URL(window.location).searchParams.get("message") || "");
 | 
			
		||||
    const status = useState("");
 | 
			
		||||
    const uid = useState((window.localStorage && window.localStorage.getItem("ew_uid")) || "");
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    let file=[], file1=[], file2=[], file3=[], password;
 | 
			
		||||
    let has_imported = false;
 | 
			
		||||
 | 
			
		||||
@@ -23,26 +25,26 @@ function Login() {
 | 
			
		||||
                jp: true
 | 
			
		||||
            };
 | 
			
		||||
            if (!data.userdata || !data.userdata.user || !data.userdata.user.id) {
 | 
			
		||||
                error[1]("Incorrect user data file format");
 | 
			
		||||
                error[1](t('user_data_incorrect'));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!data.home || !data.home.home || !data.home.home.information_list) {
 | 
			
		||||
                error[1]("Incorrect home data file format");
 | 
			
		||||
                error[1](t('home_data_incorrect'));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!Array.isArray(data.missions) && data.missions) {
 | 
			
		||||
                error[1]("Incorrect mission data file format");
 | 
			
		||||
                error[1](t('mission_data_incorrect'));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            if (!Array.isArray(data.sif_cards) && data.sif_cards) {
 | 
			
		||||
                error[1]("Incorrect sif card data file format");
 | 
			
		||||
                error[1](t('card_data_incorrect'));
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            let resp = await Request(
 | 
			
		||||
                "/api/webui/import",
 | 
			
		||||
                data
 | 
			
		||||
            );
 | 
			
		||||
            if (resp.result == "OK") {
 | 
			
		||||
            if (resp.result === "OK") {
 | 
			
		||||
                status[1](<div><p>Account imported!</p><p>User id: {resp.uid}</p><p>Migration token: {resp.migration_token}</p></div>);
 | 
			
		||||
            } else {
 | 
			
		||||
                error[1](resp.message);
 | 
			
		||||
@@ -54,27 +56,27 @@ function Login() {
 | 
			
		||||
  
 | 
			
		||||
    return (
 | 
			
		||||
        <div id="login-form">
 | 
			
		||||
            <h1>导入数据</h1>
 | 
			
		||||
            <h1>{t('transfer')}</h1>
 | 
			
		||||
            <form>
 | 
			
		||||
                { <p>{ status[0] } </p> }
 | 
			
		||||
                <label htmlFor="id">userdata.json(必要):</label>
 | 
			
		||||
                <label htmlFor="id">userdata.json({t('required')}):</label>
 | 
			
		||||
                <input type="file" id="id" name="id" onChange={(event) => {file = event.target.files}} accept="application/json"/>
 | 
			
		||||
                
 | 
			
		||||
                <label htmlFor="file1">userhome.json(必要):</label>
 | 
			
		||||
                <label htmlFor="file1">userhome.json({t('required')}):</label>
 | 
			
		||||
                <input type="file" id="file1" name="file1" onChange={(event) => {file1 = event.target.files}} accept="application/json"/>
 | 
			
		||||
                
 | 
			
		||||
                <label htmlFor="file2">missions.json(可选):</label>
 | 
			
		||||
                <label htmlFor="file2">missions.json({t('optional')}):</label>
 | 
			
		||||
                <input type="file" id="file2" name="file2" onChange={(event) => {file2 = event.target.files}} accept="application/json"/>
 | 
			
		||||
                
 | 
			
		||||
                <label htmlFor="file3">sifcards.json(可选):</label>
 | 
			
		||||
                <label htmlFor="file3">sifcards.json({t('optional')}):</label>
 | 
			
		||||
                <input type="file" id="file3" name="file3" onChange={(event) => {file3 = event.target.files}} accept="application/json"/>
 | 
			
		||||
                
 | 
			
		||||
                <label htmlFor="password">数据迁移密钥 (游戏将无法识别特殊字符,只能使用字母和数字,否则您将被锁定):</label>
 | 
			
		||||
                <label htmlFor="password">{t('transfer_passcode')}{t('transfer_passcode_hint')}</label>
 | 
			
		||||
                <input type="password" id="password" name="password" onChange={(event) => {password = event.target.value}} />
 | 
			
		||||
                
 | 
			
		||||
                <input type="submit" value="提交" onClick={handleSubmit}/>
 | 
			
		||||
                <input type="submit" value={t('submit')} onClick={handleSubmit}/>
 | 
			
		||||
                <div id="sub_div">
 | 
			
		||||
                    { error[0] ? <p>错误: { error[0] }。请重新加载页面并重试。</p> : <p></p> }
 | 
			
		||||
                    { error[0] ? <p>{t('error')}: { error[0] }. {t('refresh_and_retry')}</p> : <p></p> }
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								webui/src/locales/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								webui/src/locales/en.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
{
 | 
			
		||||
  "login": "Login",
 | 
			
		||||
  "need_full_input": "Please fill in the information completely",
 | 
			
		||||
  "uid_not_correct": "SIF2 ID should be a number, get it in game personal profile",
 | 
			
		||||
  "transfer_passcode": "Transfer passcode:",
 | 
			
		||||
  "submit": "Submit",
 | 
			
		||||
  "import_user": "Import User",
 | 
			
		||||
  "need_help": "Need help?",
 | 
			
		||||
  "ew_version": "EW version",
 | 
			
		||||
  "view_source": "View source",
 | 
			
		||||
  "view_license": "View license",
 | 
			
		||||
  "current_bonus": "Current login bonus list",
 | 
			
		||||
  "enter_bonus_id": "Enter login bonus ID",
 | 
			
		||||
  "find_bonus_in_url": "You can find a list of available login bonus IDs",
 | 
			
		||||
  "here": "here",
 | 
			
		||||
  "should_input": "You should input the",
 | 
			
		||||
  "migration_id": "Migration id",
 | 
			
		||||
  "level": "Level",
 | 
			
		||||
  "last_login_time": "Last Login",
 | 
			
		||||
  "export_account": "Export account",
 | 
			
		||||
  "logout": "Logout",
 | 
			
		||||
  "home": "Home",
 | 
			
		||||
  "server_time": "Server time",
 | 
			
		||||
  "error": "Error",
 | 
			
		||||
  "current_game_time": "Currently set to",
 | 
			
		||||
  "set_time_hint": "Setting to 0 will set it to now, and -1 will reset to open server time. Time will still progress, based off of when you set this timestamp.",
 | 
			
		||||
  "input_server_time": "Enter Server Time",
 | 
			
		||||
  "loading": "Loading...",
 | 
			
		||||
  "transfer": "Transfer",
 | 
			
		||||
  "required": "required",
 | 
			
		||||
  "optional": "optional",
 | 
			
		||||
  "transfer_passcode_hint": "(game will not recognize special characters, only use letters and numbers or you will be locked out)",
 | 
			
		||||
  "refresh_and_retry": "Please reload the page and try again.",
 | 
			
		||||
  "user_data_incorrect": "Incorrect user data file format",
 | 
			
		||||
  "home_data_incorrect": "Incorrect home data file format",
 | 
			
		||||
  "mission_data_incorrect": "Incorrect mission data file format",
 | 
			
		||||
  "card_data_incorrect": "Incorrect sif card data file format",
 | 
			
		||||
  "default_download_url": "Your server admin has no pre-patched apks to download. See the question below.",
 | 
			
		||||
  "has_download_url": "Your server admin has a link to download! Download ",
 | 
			
		||||
  "japan": "Japan",
 | 
			
		||||
  "global": "Global",
 | 
			
		||||
  "or": "Or"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								webui/src/locales/zh.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								webui/src/locales/zh.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
{
 | 
			
		||||
  "login": "登录",
 | 
			
		||||
  "need_full_input": "请完整填入信息",
 | 
			
		||||
  "uid_not_correct": "SIF2 ID必须是数字,在游戏中个人详情页中获取",
 | 
			
		||||
  "transfer_passcode": "数据迁移密钥:",
 | 
			
		||||
  "submit": "提交",
 | 
			
		||||
  "import_user": "导入用户数据",
 | 
			
		||||
  "need_help": "需要帮助?",
 | 
			
		||||
  "ew_version": "EW 版本号",
 | 
			
		||||
  "view_source": "查看源代码",
 | 
			
		||||
  "view_license": "查看许可证",
 | 
			
		||||
  "current_bonus": "当前登录奖励列表",
 | 
			
		||||
  "enter_bonus_id": "输入登录奖励ID",
 | 
			
		||||
  "find_bonus_in_url": "你可以找到可用登录奖励ID的列表",
 | 
			
		||||
  "here": "这里",
 | 
			
		||||
  "should_input": "你应该输入指定",
 | 
			
		||||
  "migration_id": "引继码",
 | 
			
		||||
  "level": "等级",
 | 
			
		||||
  "last_login_time": "上次登录",
 | 
			
		||||
  "export_account": "导出用户数据",
 | 
			
		||||
  "logout": "登出",
 | 
			
		||||
  "home": "用户主页",
 | 
			
		||||
  "server_time": "服务器时间",
 | 
			
		||||
  "error": "错误",
 | 
			
		||||
  "current_game_time": "当前游戏时间为",
 | 
			
		||||
  "set_time_hint": "将其设置为0将会设置为当前时间,而-1则会重置到开服时间。时间的推进仍将基于您设置此时间戳的时间。",
 | 
			
		||||
  "input_server_time": "输入服务器时间",
 | 
			
		||||
  "loading": "请稍候...",
 | 
			
		||||
  "transfer": "导入数据",
 | 
			
		||||
  "required": "必要",
 | 
			
		||||
  "optional": "可选",
 | 
			
		||||
  "transfer_passcode_hint": "(游戏将无法识别特殊字符,只能使用字母和数字,否则您将被锁定)",
 | 
			
		||||
  "refresh_and_retry": "请重新加载页面并重试。",
 | 
			
		||||
  "user_data_incorrect": "用户数据 文件格式不正确",
 | 
			
		||||
  "home_data_incorrect": "主页数据 文件格式不正确",
 | 
			
		||||
  "mission_data_incorrect": "任务数据 文件格式不正确",
 | 
			
		||||
  "card_data_incorrect": "卡牌数据 文件格式不正确",
 | 
			
		||||
  "default_download_url": "您的服务器管理员没有可供下载的预打补丁的应用程序包。请参阅下面的问题。",
 | 
			
		||||
  "has_download_url": "您的服务器管理员有下载链接!下载 ",
 | 
			
		||||
  "japan": "日服",
 | 
			
		||||
  "global": "国际服",
 | 
			
		||||
  "or": "或者"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +1,13 @@
 | 
			
		||||
import { useState } from 'react'
 | 
			
		||||
import {useState} from 'react'
 | 
			
		||||
import './Login.css'
 | 
			
		||||
import Request from '../Request.jsx'
 | 
			
		||||
import {useTranslation} from "react-i18next";
 | 
			
		||||
 | 
			
		||||
function Login() {
 | 
			
		||||
    const error = useState(new URL(window.location).searchParams.get("message") || "");
 | 
			
		||||
    const uid = useState((window.localStorage && window.localStorage.getItem("ew_uid")) || "");
 | 
			
		||||
    const password = useState("");
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
 | 
			
		||||
    function showError(message) {
 | 
			
		||||
        error[1](message);
 | 
			
		||||
@@ -14,11 +16,11 @@ function Login() {
 | 
			
		||||
    const handleSubmit = async (event) => {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        if (!uid[0] || !password[0]) {
 | 
			
		||||
            showError("Missing userid/password");
 | 
			
		||||
            showError(t('need_full_input'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (isNaN(uid[0])) {
 | 
			
		||||
            showError("UserID should be a number. (The \"Friend ID\" in your profile)");
 | 
			
		||||
            showError(t('uid_not_correct'));
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (window.localStorage) window.localStorage.setItem("ew_uid", uid[0]);
 | 
			
		||||
@@ -29,7 +31,7 @@ function Login() {
 | 
			
		||||
                password: password[0]
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
        if (resp.result == "OK") {
 | 
			
		||||
        if (resp.result === "OK") {
 | 
			
		||||
            window.location.href = "/home/";
 | 
			
		||||
        } else {
 | 
			
		||||
            showError(resp.message);
 | 
			
		||||
@@ -48,19 +50,26 @@ function Login() {
 | 
			
		||||
  
 | 
			
		||||
    return (
 | 
			
		||||
        <div id="login-form">
 | 
			
		||||
            <h1>登录</h1>
 | 
			
		||||
            <h1>{t('login')}</h1>
 | 
			
		||||
            <form>
 | 
			
		||||
                <label htmlFor="id">SIF2 ID:</label>
 | 
			
		||||
                <input type="text" id="id" name="id" onChange={(event) => {uid[1](event.target.value)}} value={uid[0]} />
 | 
			
		||||
                <label htmlFor="password">数据迁移密钥:</label>
 | 
			
		||||
                <input type="password" id="password" name="password" onChange={(event) => {password[1](event.target.value)}} />
 | 
			
		||||
                <input type="submit" value="提交" onClick={handleSubmit}/>
 | 
			
		||||
                <input type="text" id="id" name="id" onChange={(event) => {
 | 
			
		||||
                    uid[1](event.target.value)
 | 
			
		||||
                }} value={uid[0]}/>
 | 
			
		||||
                <label htmlFor="password">{t('transfer_passcode')}</label>
 | 
			
		||||
                <input type="password" id="password" name="password" onChange={(event) => {
 | 
			
		||||
                    password[1](event.target.value)
 | 
			
		||||
                }}/>
 | 
			
		||||
                <input type="submit" value={t('submit')} onClick={handleSubmit}/>
 | 
			
		||||
                <div id="sub_div">
 | 
			
		||||
                    <button onClick={import_user}>导入用户数据</button><br/><br/>
 | 
			
		||||
                    <button onClick={help}>需要帮助?</button><br/><br/>
 | 
			
		||||
                    { error[0] ? <p>Error: { error[0] } </p> : <p></p> }
 | 
			
		||||
                    <button onClick={import_user}>{t('import_user')}</button>
 | 
			
		||||
                    <br/><br/>
 | 
			
		||||
                    <button onClick={help}>{t('need_help')}</button>
 | 
			
		||||
                    <br/><br/>
 | 
			
		||||
                    {error[0] ? <p>Error: {error[0]} </p> : <p></p>}
 | 
			
		||||
                </div>
 | 
			
		||||
                <p>EW 版本号 1.0.1 - <a href="https://git.zhushenwudi.top/zhushenwudi/ew">查看源代码</a> - <a href="https://git.zhushenwudi.top/zhushenwudi/ew/src/branch/main/LICENSE">查看许可证</a></p>
 | 
			
		||||
                <p>{t('ew_version')} 1.0.1 - <a href="https://git.zhushenwudi.top/zhushenwudi/ew">{t('view_source')}</a> - <a
 | 
			
		||||
                    href="https://git.zhushenwudi.top/zhushenwudi/ew/src/branch/main/LICENSE">{t('view_license')}</a></p>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,11 @@ import Login from './login/Login.jsx'
 | 
			
		||||
import Home from './home/Home.jsx'
 | 
			
		||||
import Import from './import/Import.jsx'
 | 
			
		||||
import Help from './help/Help.jsx'
 | 
			
		||||
import LanguageSwitcher from "./utils/LanguageSwitcher.jsx";
 | 
			
		||||
import {I18nextProvider} from 'react-i18next';
 | 
			
		||||
import i18next from 'i18next';
 | 
			
		||||
import enTranslation from './locales/en.json';
 | 
			
		||||
import zhTranslation from './locales/zh.json';
 | 
			
		||||
 | 
			
		||||
let Elem;
 | 
			
		||||
switch (window.location.pathname) {
 | 
			
		||||
@@ -17,16 +22,31 @@ switch (window.location.pathname) {
 | 
			
		||||
        Elem = Import;
 | 
			
		||||
        break;
 | 
			
		||||
    case "/help/":
 | 
			
		||||
      Elem = Help;
 | 
			
		||||
      break;
 | 
			
		||||
        Elem = Help;
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        window.location.pathname = "/";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (Elem) {
 | 
			
		||||
  ReactDOM.createRoot(document.getElementById('root')).render(
 | 
			
		||||
    <React.StrictMode>
 | 
			
		||||
      <Elem />
 | 
			
		||||
    </React.StrictMode>,
 | 
			
		||||
  )
 | 
			
		||||
    const savedLanguage = localStorage.getItem('language') || 'zh';
 | 
			
		||||
    i18next.init({
 | 
			
		||||
        interpolation: {escapeValue: false},
 | 
			
		||||
        lng: savedLanguage,
 | 
			
		||||
        resources: {
 | 
			
		||||
            en: {translation: enTranslation},
 | 
			
		||||
            zh: {translation: zhTranslation},
 | 
			
		||||
        },
 | 
			
		||||
    }).then(() => {
 | 
			
		||||
        ReactDOM.createRoot(document.getElementById('root')).render(
 | 
			
		||||
            <React.StrictMode>
 | 
			
		||||
                <I18nextProvider i18n={i18next}>
 | 
			
		||||
                    <div style={{position: 'relative'}}>
 | 
			
		||||
                        <LanguageSwitcher/>
 | 
			
		||||
                        <Elem/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </I18nextProvider>
 | 
			
		||||
            </React.StrictMode>
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								webui/src/utils/LanguageSwitcher.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								webui/src/utils/LanguageSwitcher.jsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import {useEffect, useState} from 'react';
 | 
			
		||||
import {useTranslation} from 'react-i18next';
 | 
			
		||||
 | 
			
		||||
function LanguageSwitcher() {
 | 
			
		||||
    const {i18n} = useTranslation();
 | 
			
		||||
    const [margin, setMargin] = useState(0);
 | 
			
		||||
    const [language, setLanguage] = useState(() => {
 | 
			
		||||
        return localStorage.getItem('language') || 'zh'; // 从 localStorage 初始化
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        i18n.changeLanguage(language);
 | 
			
		||||
        localStorage.setItem('language', language); // 保存到 localStorage
 | 
			
		||||
    }, [language, i18n]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        changeMargin()
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const handleResize = () => {
 | 
			
		||||
            changeMargin()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        window.addEventListener('resize', handleResize);
 | 
			
		||||
 | 
			
		||||
        return () => {
 | 
			
		||||
            window.removeEventListener('resize', handleResize);
 | 
			
		||||
        };
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const changeMargin = () => {
 | 
			
		||||
        switch (window.location.pathname) {
 | 
			
		||||
            case "/home/":
 | 
			
		||||
            case "/help/":
 | 
			
		||||
                setMargin((window.innerWidth - 228) * 0.05);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                setMargin((window.innerWidth - 418) / 2);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const changeLanguage = () => {
 | 
			
		||||
        const newLanguage = language === 'zh' ? 'en' : 'zh';
 | 
			
		||||
        setLanguage(newLanguage);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <button style={{position: "absolute", top: 0, right: margin, zIndex: 100}} onClick={changeLanguage}>
 | 
			
		||||
            {language === "zh" ? "English" : "中文"}
 | 
			
		||||
        </button>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default LanguageSwitcher;
 | 
			
		||||
		Reference in New Issue
	
	Block a user