add multi language support

This commit is contained in:
2022-07-22 11:52:06 +08:00
parent ff85724982
commit ba1e96db26
25 changed files with 494 additions and 252 deletions

View File

@@ -19,105 +19,135 @@ import UserStatus from "./component/UserStatus";
import ReviewPage from "./component/ReviewPage";
import UserProfile from "./component/UserProfile";
import FeedbackPage from "./component/FeedbackPage";
import { useState } from "react";
import { useEffect, useState } from "react";
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
function App() {
const [playingFile, setPlayingFile] = useState({});
const [user, setUser] = useState({});
const [langCode, setLangCode] = useState("en_US");
// select language
useEffect(() => {
const browserCode = window.navigator.language;
for (const key in LANG_OPTIONS) {
for (const i in LANG_OPTIONS[key].matches) {
const code = LANG_OPTIONS[key].matches[i];
if (code === browserCode) {
setLangCode(key);
return;
}
}
}
// fallback to english
setLangCode('en-US');
}, []);
return (
<div className="base">
<Router>
<header className="header">
<h3 className="title">
<img src="favicon.png" alt="logo" className="logo" />
<span className="title-text">MSW Open Music Project</span>
<UserStatus user={user} setUser={setUser} />
</h3>
<nav className="nav">
<NavLink to="/" className="nav-link">
Feeling luckly
</NavLink>
<NavLink to="/files" className="nav-link">
Files
</NavLink>
<NavLink to="/folders" className="nav-link">
Folders
</NavLink>
<NavLink to="/manage" className="nav-link">
Manage
</NavLink>
</nav>
</header>
<main>
<Routes>
<Route
index
path="/"
element={<GetRandomFiles setPlayingFile={setPlayingFile} />}
/>
<Route
path="/files"
element={<SearchFiles setPlayingFile={setPlayingFile} />}
/>
<Route
path="/folders"
element={<SearchFolders setPlayingFile={setPlayingFile} />}
/>
<Route
path="/folders/:id"
element={<FilesInFolder setPlayingFile={setPlayingFile} />}
/>
<Route
path="/manage"
element={<Manage user={user} setUser={setUser} />}
/>
<Route
path="/manage/feedbacks"
element={<FeedbackPage user={user} />}
/>
<Route
path="/manage/login"
element={<Login user={user} setUser={setUser} />}
/>
<Route
path="/manage/register"
element={<Register user={user} setUser={setUser} />}
/>
<Route path="/manage/tags" element={<Tags user={user} />} />
<Route path="/manage/tags/:id" element={<EditTag user={user} />} />
<Route
path="/manage/reviews/:id"
element={<EditReview user={user} />}
/>
<Route
path="/manage/users"
element={<ManageUser user={user} setUser={setUser} />}
/>
<Route
path="/manage/users/:id"
element={<UserProfile user={user} setUser={setUser} />}
/>
<Route
path="/files/:id"
element={<FileInfo setPlayingFile={setPlayingFile} />}
/>
<Route
path="/files/:id/share"
element={<Share setPlayingFile={setPlayingFile} />}
/>
<Route
path="/files/:id/review"
element={
<ReviewPage user={user} setPlayingFile={setPlayingFile} />
}
/>
</Routes>
</main>
<AudioPlayer
playingFile={playingFile}
setPlayingFile={setPlayingFile}
/>
</Router>
<langCodeContext.Provider value={{ langCode, setLangCode }}>
<Router>
<header className="header">
<h3 className="title">
<img src="favicon.png" alt="logo" className="logo" />
<span className="title-text">MSW Open Music Project</span>
<UserStatus user={user} setUser={setUser} />
</h3>
<nav className="nav">
<NavLink to="/" className="nav-link">
{Tr("Feeling luckly")}
</NavLink>
<NavLink to="/files" className="nav-link">
{Tr("Files")}
</NavLink>
<NavLink to="/folders" className="nav-link">
{Tr("Folders")}
</NavLink>
<NavLink to="/manage" className="nav-link">
{Tr("Manage")}
</NavLink>
</nav>
</header>
<main>
<Routes>
<Route
index
path="/"
element={<GetRandomFiles setPlayingFile={setPlayingFile} />}
/>
<Route
path="/files"
element={<SearchFiles setPlayingFile={setPlayingFile} />}
/>
<Route
path="/folders"
element={<SearchFolders setPlayingFile={setPlayingFile} />}
/>
<Route
path="/folders/:id"
element={<FilesInFolder setPlayingFile={setPlayingFile} />}
/>
<Route
path="/manage"
element={
<Manage
user={user}
setUser={setUser}
setLangCode={setLangCode}
/>
}
/>
<Route
path="/manage/feedbacks"
element={<FeedbackPage user={user} />}
/>
<Route
path="/manage/login"
element={<Login user={user} setUser={setUser} />}
/>
<Route
path="/manage/register"
element={<Register user={user} setUser={setUser} />}
/>
<Route path="/manage/tags" element={<Tags user={user} />} />
<Route
path="/manage/tags/:id"
element={<EditTag user={user} />}
/>
<Route
path="/manage/reviews/:id"
element={<EditReview user={user} />}
/>
<Route
path="/manage/users"
element={<ManageUser user={user} setUser={setUser} />}
/>
<Route
path="/manage/users/:id"
element={<UserProfile user={user} setUser={setUser} />}
/>
<Route
path="/files/:id"
element={<FileInfo setPlayingFile={setPlayingFile} />}
/>
<Route
path="/files/:id/share"
element={<Share setPlayingFile={setPlayingFile} />}
/>
<Route
path="/files/:id/review"
element={
<ReviewPage user={user} setPlayingFile={setPlayingFile} />
}
/>
</Routes>
</main>
<AudioPlayer
playingFile={playingFile}
setPlayingFile={setPlayingFile}
/>
</Router>
</langCodeContext.Provider>
</div>
);
}

View File

@@ -3,6 +3,7 @@ import { useNavigate } from "react-router";
import { CalcReadableFilesizeDetail } from "./Common";
import FfmpegConfig from "./FfmpegConfig";
import FileDialog from "./FileDialog";
import { Tr } from "../translate";
function AudioPlayer(props) {
// props.playingFile
@@ -67,7 +68,7 @@ function AudioPlayer(props) {
return (
<footer className="vertical">
<h5>Player status</h5>
<h5>{Tr("Player status")}</h5>
{props.playingFile.id && (
<span>
<FileDialog
@@ -105,7 +106,7 @@ function AudioPlayer(props) {
props.setPlayingFile({});
}}
>
Stop
{Tr("Stop")}
</button>
)}
</span>
@@ -138,7 +139,7 @@ function AudioPlayer(props) {
);
}}
>
Stop Timer
{Tr("Stop Timer")}
</button>
</span>
@@ -149,7 +150,7 @@ function AudioPlayer(props) {
onChange={(event) => setLoop(event.target.checked)}
type="checkbox"
/>
<label>Loop</label>
<label>{Tr("Loop")}</label>
</span>
<span>
@@ -158,7 +159,7 @@ function AudioPlayer(props) {
onChange={(event) => setRaw(event.target.checked)}
type="checkbox"
/>
<label>Raw</label>
<label>{Tr("Raw")}</label>
</span>
{!raw && (
@@ -168,7 +169,7 @@ function AudioPlayer(props) {
onChange={(event) => setPrepare(event.target.checked)}
type="checkbox"
/>
<label>Prepare</label>
<label>{Tr("Prepare")}</label>
</span>
)}
</span>

View File

@@ -1,11 +1,15 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useContext } from "react";
import { Tr, tr, langCodeContext } from "../translate";
function Database() {
const [walkPath, setWalkPath] = useState("");
const [patternString, setPatternString] = useState("wav flac mp3 ogg m4a mka");
const [patternString, setPatternString] = useState(
"wav flac mp3 ogg m4a mka"
);
const [tags, setTags] = useState([]);
const [selectedTags, setSelectedTags] = useState([]);
const [updating, setUpdating] = useState(false);
const { langCode } = useContext(langCodeContext);
function getTags() {
fetch("/api/v1/get_tags")
@@ -60,21 +64,21 @@ function Database() {
}
return (
<div>
<h3>Update Database</h3>
<h3>{Tr("Update Database")}</h3>
<input
type="text"
value={walkPath}
placeholder="walk path"
placeholder={tr("walk path", langCode)}
onChange={(e) => setWalkPath(e.target.value)}
/>
<input
type="text"
value={patternString}
placeholder="pattern wav flac mp3"
placeholder={tr("pattern wav flac mp3", langCode)}
onChange={(e) => setPatternString(e.target.value)}
/>
<div>
<h4>Tags</h4>
<h4>{Tr("Tags")}</h4>
{tags.map((tag) => (
<div key={tag.id}>
<input
@@ -101,7 +105,7 @@ function Database() {
}}
disabled={updating}
>
{updating ? "Updating..." : "Update Database"}
{updating ? Tr("Updating...") : Tr("Update Database")}
</button>
</div>
);

View File

@@ -1,9 +1,11 @@
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router";
import { tr, Tr, langCodeContext } from "../translate";
function SingleReview() {
let params = useParams();
let navigate = useNavigate();
const { langCode } = useContext(langCodeContext)
const [review, setReview] = useState({
id: "",
@@ -50,7 +52,7 @@ function SingleReview() {
if (data.error) {
alert(data.error);
} else {
alert("Review updated!");
alert(tr("Review updated", langCode));
navigate(-1);
}
});
@@ -71,7 +73,7 @@ function SingleReview() {
if (data.error) {
alert(data.error);
} else {
alert("Review deleted!");
alert(tr("Review deleted", langCode));
navigate(-1);
}
});
@@ -83,14 +85,14 @@ function SingleReview() {
return (
<div className="page">
<h3>Edit Review</h3>
<h3>{Tr("Edit Review")}</h3>
<textarea
value={review.content}
onChange={(e) => setReview({ ...review, content: e.target.value })}
></textarea>
<div>
<button onClick={() => deleteReview()}>Delete</button>
<button onClick={() => save()}>Save</button>
<button onClick={() => deleteReview()}>{Tr("Delete")}</button>
<button onClick={() => save()}>{Tr("Save")}</button>
</div>
</div>
);

View File

@@ -1,9 +1,11 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useContext } from "react";
import { useParams, useNavigate } from "react-router";
import { tr, Tr, langCodeContext } from "../translate";
function EditTag() {
let params = useParams();
let navigate = useNavigate();
const { langCode } = useContext(langCodeContext);
const [tag, setTag] = useState({
id: "",
@@ -54,7 +56,7 @@ function EditTag() {
if (data.error) {
alert(data.error);
} else {
alert("Tag updated successfully");
alert(tr("Tag updated successfully", langCode));
refreshTagInfo();
}
});
@@ -79,7 +81,7 @@ function EditTag() {
if (data.error) {
alert(data.error);
} else {
alert("Tag deleted successfully");
alert(tr("Tag deleted successfully", langCode));
navigate(-1);
}
});
@@ -87,9 +89,9 @@ function EditTag() {
return (
<div className="page">
<h3>Edit Tag</h3>
<h3>{Tr("Edit Tag")}</h3>
<div>
<label htmlFor="id">ID</label>
<label htmlFor="id">{Tr("ID")}</label>
<input
type="text"
disabled
@@ -98,7 +100,7 @@ function EditTag() {
value={tag.id}
onChange={(e) => setTag({ ...tag, id: e.target.value })}
/>
<label htmlFor="name">Created By</label>
<label htmlFor="name">{Tr("Created by")}</label>
<input
type="text"
disabled
@@ -115,7 +117,7 @@ function EditTag() {
})
}
/>
<label htmlFor="name">Name</label>
<label htmlFor="name">{Tr("Name")}</label>
<input
type="text"
name="name"
@@ -123,15 +125,15 @@ function EditTag() {
value={tag.name}
onChange={(e) => setTag({ ...tag, name: e.target.value })}
/>
<label htmlFor="description">Description</label>
<label htmlFor="description">{Tr("Description")}</label>
<textarea
name="description"
id="description"
value={tag.description}
onChange={(e) => setTag({ ...tag, description: e.target.value })}
/>
<button onClick={deleteTag}>Delete</button>
<button onClick={() => updateTagInfo()}>Save</button>
<button onClick={deleteTag}>{Tr("Delete")}</button>
<button onClick={() => updateTagInfo()}>{Tr("Save")}</button>
</div>
</div>
);

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { convertIntToDateTime } from "./Common";
import { Tr } from "../translate";
function FeedbackPage() {
const [content, setContext] = useState("");
@@ -45,17 +46,17 @@ function FeedbackPage() {
return (
<div className="page">
<h3>Feedback</h3>
<h3>{Tr("Feedbacks")}</h3>
<textarea value={content} onChange={(e) => setContext(e.target.value)} />
<button onClick={() => submitFeedback()}>Submit</button>
<button onClick={() => submitFeedback()}>{Tr("Submit")}</button>
<div>
<table>
<thead>
<tr>
<th>User</th>
<th>Feedback</th>
<th>Date</th>
<th>Action</th>
<th>{Tr("User")}</th>
<th>{Tr("Feedback")}</th>
<th>{Tr("Date")}</th>
<th>{Tr("Action")}</th>
</tr>
</thead>
<tbody>
@@ -90,7 +91,7 @@ function FeedbackPage() {
});
}}
>
Delete
{Tr("Delete")}
</button>
</td>
</tr>

View File

@@ -1,4 +1,5 @@
import { useNavigate } from "react-router";
import { Tr } from "../translate";
function FileDialog(props) {
// props.showStatus
@@ -23,9 +24,9 @@ function FileDialog(props) {
{props.file.filename}
</p>
<p>
Play: play using browser player.
{Tr("Play: play using browser player.")}
<br />
Info for more actions.
{Tr("Info for more actions.")}
</p>
<button
onClick={() => {
@@ -33,7 +34,7 @@ function FileDialog(props) {
props.setShowStatus(false);
}}
>
Info
{Tr("Info")}
</button>
<button
onClick={() => {
@@ -41,9 +42,9 @@ function FileDialog(props) {
props.setShowStatus(false);
}}
>
Play
{Tr("Play")}
</button>
<button onClick={() => props.setShowStatus(false)}>Close</button>
<button onClick={() => props.setShowStatus(false)}>{Tr("Close")}</button>
</dialog>
);
}

View File

@@ -1,5 +1,6 @@
import { useNavigate, useParams } from "react-router";
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { Tr, tr, langCodeContext } from "../translate";
function FileInfo(props) {
let navigate = useNavigate();
@@ -14,6 +15,7 @@ function FileInfo(props) {
const [tags, setTags] = useState([]);
const [tagsOnFile, setTagsOnFile] = useState([]);
const [selectedTagID, setSelectedTagID] = useState("");
const { langCode } = useContext(langCodeContext);
function refresh() {
fetch(`/api/v1/get_file_info`, {
@@ -90,7 +92,9 @@ function FileInfo(props) {
function deleteFile() {
// show Warning
if (window.confirm("Are you sure you want to delete this file?")) {
if (
window.confirm(tr("Are you sure you want to delete this file?", langCode))
) {
fetch(`/api/v1/delete_file`, {
method: "POST",
headers: {
@@ -127,7 +131,7 @@ function FileInfo(props) {
if (data.error) {
alert(data.error);
} else {
alert("Filename updated");
alert(tr("Filename updated", langCode));
refresh();
}
});
@@ -163,42 +167,42 @@ function FileInfo(props) {
return (
<div className="page">
<h3>File Details</h3>
<h3>{Tr("File Details")}</h3>
<div>
<a href={downloadURL} download>
<button>Download</button>
<button>{Tr("Download")}</button>
</a>
<button
onClick={() => {
props.setPlayingFile(file);
}}
>
Play
{Tr("Play")}
</button>
<button
onClick={() => {
navigate(`/files/${params.id}/review`);
}}
>
Review
{Tr("Review")}
</button>
<button
onClick={() => {
navigate(`/files/${params.id}/share`);
}}
>
Share
{Tr("Share")}
</button>
<button
onClick={() => {
deleteFile();
}}
>
Delete
{Tr("Delete")}
</button>
</div>
<div>
<label htmlFor="foldername">Folder Name:</label>
<label htmlFor="foldername">{Tr("Folder Name")}</label>
<input
type="text"
id="foldername"
@@ -208,7 +212,7 @@ function FileInfo(props) {
}}
readOnly
/>
<label htmlFor="filename">File Name:</label>
<label htmlFor="filename">{Tr("Filename")}</label>
<input
type="text"
id="filename"
@@ -220,15 +224,15 @@ function FileInfo(props) {
});
}}
/>
<label htmlFor="filesize">File Size:</label>
<label htmlFor="filesize">{Tr("File size")}</label>
<input type="text" id="filesize" value={file.filesize} readOnly />
</div>
<div className="horizontal">
<button onClick={updateFilename}>Save</button>
<button onClick={resetFilename}>Reset</button>
<button onClick={updateFilename}>{Tr("Save")}</button>
<button onClick={resetFilename}>{Tr("Reset")}</button>
</div>
<div>
<label>Tags:</label>
<label>{Tr("Tags")}</label>
<ul>
{tagsOnFile.map((tag) => {
return (
@@ -245,7 +249,7 @@ function FileInfo(props) {
removeTagOnFile(tag.id);
}}
>
Remove
{Tr("Remove")}
</button>
</li>
);
@@ -257,7 +261,7 @@ function FileInfo(props) {
setSelectedTagID(e.target.value);
}}
>
<option value="">Select a tag</option>
<option value="">{tr("Select a tag", langCode)}</option>
{tags.map((tag) => {
return (
<option key={tag.id} value={tag.id}>
@@ -270,7 +274,7 @@ function FileInfo(props) {
onClick={() => {
// check empty
if (selectedTagID === "") {
alert("Please select a tag");
alert(tr("Please select a tag", langCode));
return;
}
fetch(`/api/v1/put_tag_on_file`, {
@@ -293,7 +297,7 @@ function FileInfo(props) {
});
}}
>
Add Tag
{Tr("Add tag")}
</button>
</div>
</div>

View File

@@ -3,6 +3,7 @@ import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "./Common";
import FilesTable from "./FilesTable";
import { Tr } from "../translate";
function FilesInFolder(props) {
let params = useParams();
@@ -107,13 +108,15 @@ function FilesInFolder(props) {
return (
<div className="page">
<h3>Files in Folder</h3>
<h3>{Tr("Files in Folder")}</h3>
<div className="search_toolbar">
<button onClick={lastPage}>Last page</button>
<button onClick={lastPage}>{Tr("Last page")}</button>
<button disabled>
{isLoading ? "Loading..." : `${offset} - ${offset + files.length}`}
{isLoading
? Tr("Loading...")
: `${offset} - ${offset + files.length}`}
</button>
<button onClick={nextPage}>Next page</button>
<button onClick={nextPage}>{Tr("Next page")}</button>
</div>
<FilesTable setPlayingFile={props.setPlayingFile} files={files} />
<div>
@@ -123,8 +126,8 @@ function FilesInFolder(props) {
onChange={(e) => setNewFoldername(e.target.value)}
/>
<div>
<button onClick={() => updateFoldername()}>Save</button>
<button onClick={() => resetFoldername()}>Reset</button>
<button onClick={() => updateFoldername()}>{Tr("Save")}</button>
<button onClick={() => resetFoldername()}>{Tr("Reset")}</button>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import FileEntry from "./FileEntry";
import { Tr } from "../translate";
function FilesTable(props) {
if (props.files.length === 0) {
@@ -8,9 +9,9 @@ function FilesTable(props) {
<table>
<thead>
<tr>
<th>Filename</th>
<th>Folder Name</th>
<th>Size</th>
<th>{Tr("Filename")}</th>
<th>{Tr("Folder Name")}</th>
<th>{Tr("Size")}</th>
</tr>
</thead>
<tbody>

View File

@@ -1,4 +1,5 @@
import { useNavigate } from "react-router";
import { Tr } from "../translate";
function FoldersTable(props) {
let navigate = useNavigate();
@@ -9,8 +10,8 @@ function FoldersTable(props) {
<table>
<thead>
<tr>
<th>Folder name</th>
<th>Action</th>
<th>{Tr("Folder name")}</th>
<th>{Tr("Action")}</th>
</tr>
</thead>
<tbody>
@@ -23,7 +24,7 @@ function FoldersTable(props) {
{folder.foldername}
</td>
<td onClick={() => navigate(`/folders/${folder.id}`)}>
<button>View</button>
<button>{Tr("View")}</button>
</td>
</tr>
))}

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "./Common";
import FilesTable from "./FilesTable";
import { Tr, tr, langCodeContext } from "../translate";
function GetRandomFiles(props) {
const [files, setFiles] = useState([]);
@@ -10,6 +11,7 @@ function GetRandomFiles(props) {
const navigator = useNavigate();
const query = useQuery();
const selectedTag = query.get("t") || "";
const { langCode } = useContext(langCodeContext);
function getRandomFiles() {
setIsLoading(true);
@@ -84,7 +86,7 @@ function GetRandomFiles(props) {
<div className="page">
<div className="search_toolbar">
<button className="refresh" onClick={() => refresh(setFiles)}>
{isLoading ? "Loading..." : "Refresh"}
{isLoading ? Tr("Loading...") : Tr("Refresh")}
</button>
<select
className="tag_select"
@@ -93,7 +95,7 @@ function GetRandomFiles(props) {
}}
value={selectedTag}
>
<option value="">All</option>
<option value="">{tr("All", langCode)}</option>
{tags.map((tag) => (
<option key={tag.id} value={tag.id}>
{tag.name}

View File

@@ -1,14 +1,16 @@
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useContext, useState } from "react";
import { Tr, tr, langCodeContext } from "../translate";
function Login(props) {
let navigate = useNavigate();
let [username, setUsername] = useState("");
let [password, setPassword] = useState("");
const { langCode } = useContext(langCodeContext);
function login() {
if (!username || !password) {
alert("Please enter username and password");
alert(tr("Please enter username and password", langCode));
return;
}
fetch("/api/v1/login", {
@@ -34,15 +36,15 @@ function Login(props) {
return (
<div className="page">
<h2>Login</h2>
<label htmlFor="username">Username</label>
<h2>{Tr("Login")}</h2>
<label htmlFor="username">{Tr("Username")}</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor="password">Password</label>
<label htmlFor="password">{Tr("Password")}</label>
<input
type="password"
id="password"
@@ -56,13 +58,13 @@ function Login(props) {
}}
/>
<span>
<button onClick={login}>Login</button>
<button onClick={login}>{Tr("Login")}</button>
<button
onClick={() => {
navigate("/manage/register");
}}
>
Register
{Tr("Register")}
</button>
</span>
</div>

View File

@@ -1,13 +1,32 @@
import { useNavigate } from "react-router";
import Database from "./Database";
import { Tr, langCodeContext, LANG_OPTIONS } from "../translate";
import { useContext } from "react";
function Manage(props) {
let navigate = useNavigate();
const { langCode, setLangCode } = useContext(langCodeContext);
const codes = Object.keys(LANG_OPTIONS);
return (
<div className="page">
<h2>Manage</h2>
<p>Hi, {props.user.username}</p>
<h2>{Tr("Manage")}</h2>
<p>
{Tr("Hi")}, {props.user.username}
</p>
<select
onChange={(event) => {
setLangCode(codes[event.target.selectedIndex]);
}}
>
{codes.map((code) => {
const langOption = LANG_OPTIONS[code];
return <option key={code}>{langOption.name}</option>;
})}
</select>
{props.user.role === 0 && (
<div>
<button
@@ -15,14 +34,14 @@ function Manage(props) {
navigate("/manage/login");
}}
>
Login
{Tr("Login")}
</button>
<button
onClick={() => {
navigate("/manage/register");
}}
>
Register
{Tr("Register")}
</button>
</div>
)}
@@ -33,7 +52,7 @@ function Manage(props) {
navigate(`/manage/users/${props.user.id}`);
}}
>
Profile
{Tr("Profile")}
</button>
<button
onClick={() => {
@@ -48,15 +67,17 @@ function Manage(props) {
});
}}
>
Logout
{Tr("Logout")}
</button>
</div>
)}
<hr />
<div className="horizontal">
<button onClick={() => navigate("/manage/tags")}>Tags</button>
<button onClick={() => navigate("/manage/users")}>Users</button>
<button onClick={() => navigate("/manage/feedbacks")}>Feedbacks</button>
<button onClick={() => navigate("/manage/tags")}>{Tr("Tags")}</button>
<button onClick={() => navigate("/manage/users")}>{Tr("Users")}</button>
<button onClick={() => navigate("/manage/feedbacks")}>
{Tr("Feedbacks")}
</button>
</div>
<Database />
</div>

View File

@@ -1,12 +1,13 @@
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Tr } from "../translate";
function ManageUser() {
const [users, setUsers] = useState([]);
const roleDict = {
0: "Anonymous",
1: "Admin",
2: "Normal User",
2: "User",
};
function getUsers() {
@@ -27,13 +28,13 @@ function ManageUser() {
return (
<div className="page">
<h3>Manage User</h3>
<h3>{Tr("Manage User")}</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th>Active</th>
<th>{Tr("Name")}</th>
<th>{Tr("Role")}</th>
<th>{Tr("Active")}</th>
</tr>
</thead>
<tbody>
@@ -42,7 +43,7 @@ function ManageUser() {
<td>
<Link to={`/manage/users/${user.id}`}>@{user.username}</Link>
</td>
<td>{roleDict[user.role]}</td>
<td>{Tr(roleDict[user.role])}</td>
<td>
<input
type="checkbox"
@@ -57,13 +58,15 @@ function ManageUser() {
id: user.id,
active: e.target.checked,
}),
}).then((res) => res.json()).then((data) => {
if (data.error) {
alert(data.error);
} else {
getUsers();
}
});
})
.then((res) => res.json())
.then((data) => {
if (data.error) {
alert(data.error);
} else {
getUsers();
}
});
}}
/>
</td>

View File

@@ -1,5 +1,6 @@
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { useContext, useState } from "react";
import { tr, Tr, langCodeContext } from "../translate";
function Register() {
let navigate = useNavigate();
@@ -7,12 +8,13 @@ function Register() {
const [password, setPassword] = useState("");
const [password2, setPassword2] = useState("");
const [role, setRole] = useState("");
const { langCode } = useContext(langCodeContext);
function register() {
if (!username || !password || !password2 || !role) {
alert("Please fill out all fields");
alert(tr("Please fill out all fields", langCode));
} else if (password !== password2) {
alert("Passwords do not match");
alert(tr("Password do not match", langCode));
} else {
fetch("/api/v1/register", {
method: "POST",
@@ -38,22 +40,22 @@ function Register() {
return (
<div className="page">
<h2>Register</h2>
<label htmlFor="username">Username</label>
<h2>{Tr("Register")}</h2>
<label htmlFor="username">{Tr("Username")}</label>
<input
type="text"
id="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<label htmlFor="password">Password</label>
<label htmlFor="password">{Tr("Password")}</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<label htmlFor="password2">Confirm Password</label>
<label htmlFor="password2">{Tr("Confirm Password")}</label>
<input
type="password"
id="password2"
@@ -66,13 +68,13 @@ function Register() {
}
}}
/>
<label htmlFor="role">Role</label>
<label htmlFor="role">{Tr("Role")}</label>
<select value={role} onChange={(e) => setRole(e.target.value)}>
<option value="">Select a role</option>
<option value="2">User</option>
<option value="1">Admin</option>
<option value="">{tr("Select a role", langCode)}</option>
<option value="2">{tr("User", langCode)}</option>
<option value="1">{tr("Admin", langCode)}</option>
</select>
<button onClick={register}>Register</button>
<button onClick={register}>{Tr("Register")}</button>
</div>
);
}

View File

@@ -1,25 +1,28 @@
import { Link } from "react-router-dom";
import { convertIntToDateTime } from "./Common";
import { Tr, tr, langCodeContext } from "../translate";
import { useContext } from "react";
function ReviewEntry(props) {
const { langCode } = useContext(langCodeContext);
return (
<div>
<h4>
<Link to={`/manage/users/${props.review.user.id}`}>
@{props.review.user.username}
</Link>{" "}
review{" "}
{Tr("review")}{" "}
<Link to={`/files/${props.review.file.id}`}>
{props.review.file.filename}
</Link>{" "}
on {convertIntToDateTime(props.review.created_at)}{" "}
{Tr("on")} {convertIntToDateTime(props.review.created_at)}{" "}
{props.review.updated_at !== 0 &&
"(modified on " +
convertIntToDateTime(props.review.updated_at) +
")"}{" "}
`(${tr("modified on", langCode)} ${convertIntToDateTime(
props.review.updated_at
)} ) `}
{(props.user.role === 1 || props.review.user.id === props.user.id) &&
props.user.role !== 0 && (
<Link to={`/manage/reviews/${props.review.id}`}>Edit</Link>
<Link to={`/manage/reviews/${props.review.id}`}>{Tr("Edit")}</Link>
)}
</h4>
<p>{props.review.content}</p>

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router";
import ReviewEntry from "./ReviewEntry";
import { Tr } from "../translate";
function ReviewPage(props) {
let params = useParams();
@@ -55,7 +56,7 @@ function ReviewPage(props) {
return (
<div className="page">
<h3>Review Page</h3>
<h3>{Tr("Review Page")}</h3>
<div>
{reviews.map((review) => (
<ReviewEntry key={review.id} review={review} user={props.user} />
@@ -66,7 +67,7 @@ function ReviewPage(props) {
value={newReview}
onChange={(e) => setNewReview(e.target.value)}
/>
<button onClick={() => submitReview()}>Submit</button>
<button onClick={() => submitReview()}>{Tr("Submit")}</button>
</div>
</div>
);

View File

@@ -1,7 +1,8 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useContext } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "./Common";
import FilesTable from "./FilesTable";
import { Tr, tr, langCodeContext } from "../translate";
function SearchFiles(props) {
const navigator = useNavigate();
@@ -12,6 +13,7 @@ function SearchFiles(props) {
const offset = parseInt(query.get("o")) || 0;
const [isLoading, setIsLoading] = useState(false);
const limit = 10;
const { langCode } = useContext(langCodeContext);
function searchFiles() {
// check empty filename
@@ -57,7 +59,7 @@ function SearchFiles(props) {
return (
<div className="page">
<h3>Search Files</h3>
<h3>{Tr("Search Files")}</h3>
<div className="search_toolbar">
<input
onChange={(event) => setFilenameInput(event.target.value)}
@@ -67,7 +69,7 @@ function SearchFiles(props) {
}
}}
type="text"
placeholder="Enter filename"
placeholder={tr("Enter filename", langCode)}
value={filenameInput}
/>
<button
@@ -75,13 +77,13 @@ function SearchFiles(props) {
navigator(`/files?q=${filenameInput}&o=0`);
}}
>
{isLoading ? "Loading..." : "Search"}
{isLoading ? Tr("Loading...") : Tr("Search")}
</button>
<button onClick={lastPage}>Last page</button>
<button onClick={lastPage}>{Tr("Last page")}</button>
<button disabled>
{offset} - {offset + files.length}
</button>
<button onClick={nextPage}>Next page</button>
<button onClick={nextPage}>{Tr("Next page")}</button>
</div>
<FilesTable setPlayingFile={props.setPlayingFile} files={files} />
</div>

View File

@@ -1,7 +1,8 @@
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useQuery } from "./Common";
import FoldersTable from "./FoldersTable";
import { Tr, tr, langCodeContext } from "../translate";
function SearchFolders() {
const navigator = useNavigate();
@@ -12,6 +13,7 @@ function SearchFolders() {
const offset = parseInt(query.get("o")) || 0;
const [isLoading, setIsLoading] = useState(false);
const limit = 10;
const { langCode } = useContext(langCodeContext);
function searchFolder() {
if (foldername === "") {
@@ -55,7 +57,7 @@ function SearchFolders() {
return (
<div className="page">
<h3>Search Folders</h3>
<h3>{Tr("Search Folders")}</h3>
<div className="search_toolbar">
<input
onChange={(event) => setFoldernameInput(event.target.value)}
@@ -65,7 +67,7 @@ function SearchFolders() {
}
}}
type="text"
placeholder="Enter folder name"
placeholder={tr("Enter folder name", langCode)}
value={foldernameInput}
/>
<button
@@ -73,13 +75,13 @@ function SearchFolders() {
navigator(`/folders?q=${foldernameInput}&o=0`);
}}
>
{isLoading ? "Loading..." : "Search"}
{isLoading ? Tr("Loading...") : Tr("Search")}
</button>
<button onClick={lastPage}>Last page</button>
<button onClick={lastPage}>{Tr("Last page")}</button>
<button disabled>
{offset} - {offset + limit}
</button>
<button onClick={nextPage}>Next page</button>
<button onClick={nextPage}>{Tr("Next page")}</button>
</div>
<FoldersTable folders={folders} />
</div>

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router";
import FilesTable from "./FilesTable";
import { Tr } from "../translate";
function Share(props) {
let params = useParams();
@@ -23,13 +24,14 @@ function Share(props) {
}, [params]);
return (
<div className="page">
<h3>Share with others!</h3>
<h3>{Tr("Share with others!")}</h3>
<p>
👇 Click the filename below to enjoy music!
<br />
{Tr("Share link")}:{" "}
<a href={window.location.href}>{window.location.href}</a>
</p>
<p>
Share link: <a href={window.location.href}>{window.location.href}</a>
👇 {Tr("Click the filename below to enjoy music!")}
<br />
</p>
<FilesTable setPlayingFile={props.setPlayingFile} files={file} />
</div>

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { Tr } from "../translate";
function Tags() {
const [tags, setTags] = useState([]);
@@ -25,14 +26,14 @@ function Tags() {
return (
<div className="page">
<h3>Tags</h3>
<h3>{Tr("Tags")}</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Created By</th>
<th>Actions</th>
<th>{Tr("Name")}</th>
<th>{Tr("Description")}</th>
<th>{Tr("Created by")}</th>
<th>{Tr("Action")}</th>
</tr>
</thead>
<tbody>
@@ -46,25 +47,25 @@ function Tags() {
</Link>
</td>
<td>
<Link to={`/manage/tags/${tag.id}`}>Edit</Link>
<Link to={`/manage/tags/${tag.id}`}>{Tr("Edit")}</Link>
</td>
</tr>
))}
</tbody>
</table>
{!showAddTag && (
<button onClick={() => setShowAddTag(true)}>Add Tag</button>
<button onClick={() => setShowAddTag(true)}>{Tr("Add tag")}</button>
)}
{showAddTag && (
<div>
<label htmlFor="newTagName">New Tag Name</label>
<label htmlFor="newTagName">{Tr("New Tag Name")}</label>
<input
type="text"
id="newTagName"
value={newTagName}
onChange={(e) => setNewTagName(e.target.value)}
/>
<label htmlFor="newTagDescription">New Tag Description</label>
<label htmlFor="newTagDescription">{Tr("New Tag Description")}</label>
<textarea
id="newTagDescription"
value={newTagDescription}
@@ -94,7 +95,7 @@ function Tags() {
});
}}
>
Create Tag
{Tr("Create tag")}
</button>
</div>
)}

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from "react";
import { useState, useEffect, useContext } from "react";
import { useParams } from "react-router";
import ReviewEntry from "./ReviewEntry";
import { Tr, tr, langCodeContext } from "../translate";
function UserProfile(props) {
let params = useParams();
@@ -15,6 +16,7 @@ function UserProfile(props) {
active: false,
avatar_id: 0,
});
const { langCode } = useContext(langCodeContext);
function getReviews() {
fetch("/api/v1/get_reviews_by_user", {
@@ -63,7 +65,7 @@ function UserProfile(props) {
return (
<div className="page">
<h3>User Profile</h3>
<h3>{Tr("User Profile")}</h3>
<div className="horizontal">
<input
type="text"
@@ -103,26 +105,26 @@ function UserProfile(props) {
}}
disabled={props.user.id !== user.id && props.user.role !== 1}
>
Save Username
{Tr("Save username")}
</button>
</div>
<div>
<input
type="password"
value={oldPassword}
placeholder="Old Password"
placeholder={tr("Old password", langCode)}
onChange={(e) => setOldPassword(e.target.value)}
/>
<input
type="password"
value={newPassword}
placeholder="New Password"
placeholder={tr("New password", langCode)}
onChange={(e) => setNewPassword(e.target.value)}
/>
<input
type="password"
value={newPasswordConfirm}
placeholder="Confirm New Password"
placeholder={tr("Confirm new password", langCode)}
onChange={(e) => setNewPasswordConfirm(e.target.value)}
/>
<button
@@ -144,7 +146,7 @@ function UserProfile(props) {
if (data.error) {
alert(data.error);
} else {
alert("Password updated successfully!");
alert(tr("Password updated successfully!", langCode));
}
});
}}
@@ -154,10 +156,10 @@ function UserProfile(props) {
newPassword.length === 0
}
>
Change Password
{Tr("Change password")}
</button>
</div>
<h4>Reviews</h4>
<h4>{Tr("Reviews")}</h4>
{reviews.map((review) => (
<ReviewEntry key={review.id} review={review} user={props.user} />
))}

View File

@@ -0,0 +1,44 @@
import { createContext, renderToString } from "react";
import MAP_zh_CN from "./zh_CN";
const LANG_OPTIONS = {
"en-US": {
name: "English",
langMap: {},
matches: ["en-US", "en"],
},
"zh-CN": {
name: "中文(简体)",
langMap: MAP_zh_CN,
matches: ["zh-CN", "zh"],
},
};
const langCodeContext = createContext("en-US");
function tr(text, langCode) {
const option = LANG_OPTIONS[langCode];
if (option === undefined) {
return text;
}
const langMap = LANG_OPTIONS[langCode].langMap;
const translatedText = langMap[text.toLowerCase()];
if (translatedText === undefined) {
return text;
}
return translatedText;
}
function Tr(text) {
return (
<langCodeContext.Consumer>
{({ langCode }) => {
return tr(text, langCode);
}}
</langCodeContext.Consumer>
);
}
export { tr, Tr, LANG_OPTIONS, langCodeContext };

105
web/src/translate/zh_CN.js Normal file
View File

@@ -0,0 +1,105 @@
const LANG_zh_CN = {
"feeling luckly": "随机",
files: "文件",
folders: "文件夹",
manage: "管理",
"manage user": "用户管理",
active: "激活",
"search files": "搜索文件",
"search folders": "搜索文件夹",
"enter filename": "输入文件名",
"enter folder name": "输入文件名",
search: "搜索",
"last page": "上一页",
all: "全部",
"loading...": "加载中...",
"next page": "下一页",
"search polders": "搜索文件夹",
"share with others!": "分享给好友!",
"click the filename below to enjoy music!": "点击下面的文件名开始享受音乐!",
"share link": "分享链接",
hi: "您好",
profile: "个人信息",
"user profile": "用户信息",
"save username": "更改用户名",
save: "保存",
reset: "重置",
"old password": "旧密码",
"new password": "新密码",
"confirm new password": "确认新密码",
"change password": "更改密码",
reviews: "评论",
review: "评论",
on: "在",
edit: "编辑",
"modified on": "修改于",
share: "分享",
delete: "删除",
remove: "移除",
"file details": "文件详情",
download: "下载",
logout: "登出",
tags: "标签",
"add tag": "添加标签",
"select a tag": "选择一个标签",
"review page": "评论页面",
submit: "提交",
users: "用户",
feedbacks: "反馈",
feedback: "反馈",
date: "时间",
action: "操作",
"new tag name": "新标签名",
"new tag description": "新标签描述",
"update database": "更新索引",
"updating...": "更新中...",
refresh: "刷新",
filename: "文件名",
"folder name": "文件夹名",
size: "大小",
"player status": "播放状态",
play: "播放",
stop: "停止",
"stop timer": "定时停止",
loop: "循环",
raw: "无损",
prepare: "预转码",
"file size": "文件大小",
login: "登陆",
register: "注册",
"play: play using browser player.": "播放: 使用浏览器播放",
"info for more actions.": "详细: 查看更多相关信息",
info: "详细",
close: "关闭",
"please enter username and password": "请输入用户名和密码",
username: "用户名",
password: "密码",
"please fill out all fields": "请完整填写所有信息",
"password do not match": "两次密码不一致",
"password updated successfully!": "密码已成功更新!",
role: "身份",
user: "用户",
admin: "管理员",
anonymous: "匿名",
"select a role": "选择身份",
"walk path": "遍历目录",
"pattern wav flac mp3": "拓展名 wav flac mp3",
"review updated": "已修改评论",
"review deleted": "已删除评论",
"edit review": "编辑评论",
view: "查看",
"tag updated successfully": "标签修改成功",
"tag deleted successfully": "标签删除成功",
"edit tag": "编辑标签",
id: "编号",
"created by": "创建者",
"create tag": "创建新标签",
name: "名称",
description: "描述",
"are you sure you want to delete this file?": "你确定要删除这个文件吗?",
"filename updated": "已修改文件名",
"please select a tag": "请选择一个标签",
"files in folder": "文件夹内",
};
export default LANG_zh_CN;