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,13 +19,33 @@ import UserStatus from "./component/UserStatus";
import ReviewPage from "./component/ReviewPage"; import ReviewPage from "./component/ReviewPage";
import UserProfile from "./component/UserProfile"; import UserProfile from "./component/UserProfile";
import FeedbackPage from "./component/FeedbackPage"; import FeedbackPage from "./component/FeedbackPage";
import { useState } from "react"; import { useEffect, useState } from "react";
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
function App() { function App() {
const [playingFile, setPlayingFile] = useState({}); const [playingFile, setPlayingFile] = useState({});
const [user, setUser] = 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 ( return (
<div className="base"> <div className="base">
<langCodeContext.Provider value={{ langCode, setLangCode }}>
<Router> <Router>
<header className="header"> <header className="header">
<h3 className="title"> <h3 className="title">
@@ -35,16 +55,16 @@ function App() {
</h3> </h3>
<nav className="nav"> <nav className="nav">
<NavLink to="/" className="nav-link"> <NavLink to="/" className="nav-link">
Feeling luckly {Tr("Feeling luckly")}
</NavLink> </NavLink>
<NavLink to="/files" className="nav-link"> <NavLink to="/files" className="nav-link">
Files {Tr("Files")}
</NavLink> </NavLink>
<NavLink to="/folders" className="nav-link"> <NavLink to="/folders" className="nav-link">
Folders {Tr("Folders")}
</NavLink> </NavLink>
<NavLink to="/manage" className="nav-link"> <NavLink to="/manage" className="nav-link">
Manage {Tr("Manage")}
</NavLink> </NavLink>
</nav> </nav>
</header> </header>
@@ -69,7 +89,13 @@ function App() {
/> />
<Route <Route
path="/manage" path="/manage"
element={<Manage user={user} setUser={setUser} />} element={
<Manage
user={user}
setUser={setUser}
setLangCode={setLangCode}
/>
}
/> />
<Route <Route
path="/manage/feedbacks" path="/manage/feedbacks"
@@ -84,7 +110,10 @@ function App() {
element={<Register user={user} setUser={setUser} />} element={<Register user={user} setUser={setUser} />}
/> />
<Route path="/manage/tags" element={<Tags user={user} />} /> <Route path="/manage/tags" element={<Tags user={user} />} />
<Route path="/manage/tags/:id" element={<EditTag user={user} />} /> <Route
path="/manage/tags/:id"
element={<EditTag user={user} />}
/>
<Route <Route
path="/manage/reviews/:id" path="/manage/reviews/:id"
element={<EditReview user={user} />} element={<EditReview user={user} />}
@@ -118,6 +147,7 @@ function App() {
setPlayingFile={setPlayingFile} setPlayingFile={setPlayingFile}
/> />
</Router> </Router>
</langCodeContext.Provider>
</div> </div>
); );
} }

View File

@@ -3,6 +3,7 @@ import { useNavigate } from "react-router";
import { CalcReadableFilesizeDetail } from "./Common"; import { CalcReadableFilesizeDetail } from "./Common";
import FfmpegConfig from "./FfmpegConfig"; import FfmpegConfig from "./FfmpegConfig";
import FileDialog from "./FileDialog"; import FileDialog from "./FileDialog";
import { Tr } from "../translate";
function AudioPlayer(props) { function AudioPlayer(props) {
// props.playingFile // props.playingFile
@@ -67,7 +68,7 @@ function AudioPlayer(props) {
return ( return (
<footer className="vertical"> <footer className="vertical">
<h5>Player status</h5> <h5>{Tr("Player status")}</h5>
{props.playingFile.id && ( {props.playingFile.id && (
<span> <span>
<FileDialog <FileDialog
@@ -105,7 +106,7 @@ function AudioPlayer(props) {
props.setPlayingFile({}); props.setPlayingFile({});
}} }}
> >
Stop {Tr("Stop")}
</button> </button>
)} )}
</span> </span>
@@ -138,7 +139,7 @@ function AudioPlayer(props) {
); );
}} }}
> >
Stop Timer {Tr("Stop Timer")}
</button> </button>
</span> </span>
@@ -149,7 +150,7 @@ function AudioPlayer(props) {
onChange={(event) => setLoop(event.target.checked)} onChange={(event) => setLoop(event.target.checked)}
type="checkbox" type="checkbox"
/> />
<label>Loop</label> <label>{Tr("Loop")}</label>
</span> </span>
<span> <span>
@@ -158,7 +159,7 @@ function AudioPlayer(props) {
onChange={(event) => setRaw(event.target.checked)} onChange={(event) => setRaw(event.target.checked)}
type="checkbox" type="checkbox"
/> />
<label>Raw</label> <label>{Tr("Raw")}</label>
</span> </span>
{!raw && ( {!raw && (
@@ -168,7 +169,7 @@ function AudioPlayer(props) {
onChange={(event) => setPrepare(event.target.checked)} onChange={(event) => setPrepare(event.target.checked)}
type="checkbox" type="checkbox"
/> />
<label>Prepare</label> <label>{Tr("Prepare")}</label>
</span> </span>
)} )}
</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() { function Database() {
const [walkPath, setWalkPath] = useState(""); 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 [tags, setTags] = useState([]);
const [selectedTags, setSelectedTags] = useState([]); const [selectedTags, setSelectedTags] = useState([]);
const [updating, setUpdating] = useState(false); const [updating, setUpdating] = useState(false);
const { langCode } = useContext(langCodeContext);
function getTags() { function getTags() {
fetch("/api/v1/get_tags") fetch("/api/v1/get_tags")
@@ -60,21 +64,21 @@ function Database() {
} }
return ( return (
<div> <div>
<h3>Update Database</h3> <h3>{Tr("Update Database")}</h3>
<input <input
type="text" type="text"
value={walkPath} value={walkPath}
placeholder="walk path" placeholder={tr("walk path", langCode)}
onChange={(e) => setWalkPath(e.target.value)} onChange={(e) => setWalkPath(e.target.value)}
/> />
<input <input
type="text" type="text"
value={patternString} value={patternString}
placeholder="pattern wav flac mp3" placeholder={tr("pattern wav flac mp3", langCode)}
onChange={(e) => setPatternString(e.target.value)} onChange={(e) => setPatternString(e.target.value)}
/> />
<div> <div>
<h4>Tags</h4> <h4>{Tr("Tags")}</h4>
{tags.map((tag) => ( {tags.map((tag) => (
<div key={tag.id}> <div key={tag.id}>
<input <input
@@ -101,7 +105,7 @@ function Database() {
}} }}
disabled={updating} disabled={updating}
> >
{updating ? "Updating..." : "Update Database"} {updating ? Tr("Updating...") : Tr("Update Database")}
</button> </button>
</div> </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 { useParams, useNavigate } from "react-router";
import { tr, Tr, langCodeContext } from "../translate";
function SingleReview() { function SingleReview() {
let params = useParams(); let params = useParams();
let navigate = useNavigate(); let navigate = useNavigate();
const { langCode } = useContext(langCodeContext)
const [review, setReview] = useState({ const [review, setReview] = useState({
id: "", id: "",
@@ -50,7 +52,7 @@ function SingleReview() {
if (data.error) { if (data.error) {
alert(data.error); alert(data.error);
} else { } else {
alert("Review updated!"); alert(tr("Review updated", langCode));
navigate(-1); navigate(-1);
} }
}); });
@@ -71,7 +73,7 @@ function SingleReview() {
if (data.error) { if (data.error) {
alert(data.error); alert(data.error);
} else { } else {
alert("Review deleted!"); alert(tr("Review deleted", langCode));
navigate(-1); navigate(-1);
} }
}); });
@@ -83,14 +85,14 @@ function SingleReview() {
return ( return (
<div className="page"> <div className="page">
<h3>Edit Review</h3> <h3>{Tr("Edit Review")}</h3>
<textarea <textarea
value={review.content} value={review.content}
onChange={(e) => setReview({ ...review, content: e.target.value })} onChange={(e) => setReview({ ...review, content: e.target.value })}
></textarea> ></textarea>
<div> <div>
<button onClick={() => deleteReview()}>Delete</button> <button onClick={() => deleteReview()}>{Tr("Delete")}</button>
<button onClick={() => save()}>Save</button> <button onClick={() => save()}>{Tr("Save")}</button>
</div> </div>
</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 { useParams, useNavigate } from "react-router";
import { tr, Tr, langCodeContext } from "../translate";
function EditTag() { function EditTag() {
let params = useParams(); let params = useParams();
let navigate = useNavigate(); let navigate = useNavigate();
const { langCode } = useContext(langCodeContext);
const [tag, setTag] = useState({ const [tag, setTag] = useState({
id: "", id: "",
@@ -54,7 +56,7 @@ function EditTag() {
if (data.error) { if (data.error) {
alert(data.error); alert(data.error);
} else { } else {
alert("Tag updated successfully"); alert(tr("Tag updated successfully", langCode));
refreshTagInfo(); refreshTagInfo();
} }
}); });
@@ -79,7 +81,7 @@ function EditTag() {
if (data.error) { if (data.error) {
alert(data.error); alert(data.error);
} else { } else {
alert("Tag deleted successfully"); alert(tr("Tag deleted successfully", langCode));
navigate(-1); navigate(-1);
} }
}); });
@@ -87,9 +89,9 @@ function EditTag() {
return ( return (
<div className="page"> <div className="page">
<h3>Edit Tag</h3> <h3>{Tr("Edit Tag")}</h3>
<div> <div>
<label htmlFor="id">ID</label> <label htmlFor="id">{Tr("ID")}</label>
<input <input
type="text" type="text"
disabled disabled
@@ -98,7 +100,7 @@ function EditTag() {
value={tag.id} value={tag.id}
onChange={(e) => setTag({ ...tag, id: e.target.value })} onChange={(e) => setTag({ ...tag, id: e.target.value })}
/> />
<label htmlFor="name">Created By</label> <label htmlFor="name">{Tr("Created by")}</label>
<input <input
type="text" type="text"
disabled disabled
@@ -115,7 +117,7 @@ function EditTag() {
}) })
} }
/> />
<label htmlFor="name">Name</label> <label htmlFor="name">{Tr("Name")}</label>
<input <input
type="text" type="text"
name="name" name="name"
@@ -123,15 +125,15 @@ function EditTag() {
value={tag.name} value={tag.name}
onChange={(e) => setTag({ ...tag, name: e.target.value })} onChange={(e) => setTag({ ...tag, name: e.target.value })}
/> />
<label htmlFor="description">Description</label> <label htmlFor="description">{Tr("Description")}</label>
<textarea <textarea
name="description" name="description"
id="description" id="description"
value={tag.description} value={tag.description}
onChange={(e) => setTag({ ...tag, description: e.target.value })} onChange={(e) => setTag({ ...tag, description: e.target.value })}
/> />
<button onClick={deleteTag}>Delete</button> <button onClick={deleteTag}>{Tr("Delete")}</button>
<button onClick={() => updateTagInfo()}>Save</button> <button onClick={() => updateTagInfo()}>{Tr("Save")}</button>
</div> </div>
</div> </div>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import { useNavigate } from "react-router"; import { useNavigate } from "react-router";
import { Tr } from "../translate";
function FoldersTable(props) { function FoldersTable(props) {
let navigate = useNavigate(); let navigate = useNavigate();
@@ -9,8 +10,8 @@ function FoldersTable(props) {
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Folder name</th> <th>{Tr("Folder name")}</th>
<th>Action</th> <th>{Tr("Action")}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -23,7 +24,7 @@ function FoldersTable(props) {
{folder.foldername} {folder.foldername}
</td> </td>
<td onClick={() => navigate(`/folders/${folder.id}`)}> <td onClick={() => navigate(`/folders/${folder.id}`)}>
<button>View</button> <button>{Tr("View")}</button>
</td> </td>
</tr> </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 { useNavigate } from "react-router-dom";
import { useQuery } from "./Common"; import { useQuery } from "./Common";
import FilesTable from "./FilesTable"; import FilesTable from "./FilesTable";
import { Tr, tr, langCodeContext } from "../translate";
function GetRandomFiles(props) { function GetRandomFiles(props) {
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
@@ -10,6 +11,7 @@ function GetRandomFiles(props) {
const navigator = useNavigate(); const navigator = useNavigate();
const query = useQuery(); const query = useQuery();
const selectedTag = query.get("t") || ""; const selectedTag = query.get("t") || "";
const { langCode } = useContext(langCodeContext);
function getRandomFiles() { function getRandomFiles() {
setIsLoading(true); setIsLoading(true);
@@ -84,7 +86,7 @@ function GetRandomFiles(props) {
<div className="page"> <div className="page">
<div className="search_toolbar"> <div className="search_toolbar">
<button className="refresh" onClick={() => refresh(setFiles)}> <button className="refresh" onClick={() => refresh(setFiles)}>
{isLoading ? "Loading..." : "Refresh"} {isLoading ? Tr("Loading...") : Tr("Refresh")}
</button> </button>
<select <select
className="tag_select" className="tag_select"
@@ -93,7 +95,7 @@ function GetRandomFiles(props) {
}} }}
value={selectedTag} value={selectedTag}
> >
<option value="">All</option> <option value="">{tr("All", langCode)}</option>
{tags.map((tag) => ( {tags.map((tag) => (
<option key={tag.id} value={tag.id}> <option key={tag.id} value={tag.id}>
{tag.name} {tag.name}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import ReviewEntry from "./ReviewEntry"; import ReviewEntry from "./ReviewEntry";
import { Tr } from "../translate";
function ReviewPage(props) { function ReviewPage(props) {
let params = useParams(); let params = useParams();
@@ -55,7 +56,7 @@ function ReviewPage(props) {
return ( return (
<div className="page"> <div className="page">
<h3>Review Page</h3> <h3>{Tr("Review Page")}</h3>
<div> <div>
{reviews.map((review) => ( {reviews.map((review) => (
<ReviewEntry key={review.id} review={review} user={props.user} /> <ReviewEntry key={review.id} review={review} user={props.user} />
@@ -66,7 +67,7 @@ function ReviewPage(props) {
value={newReview} value={newReview}
onChange={(e) => setNewReview(e.target.value)} onChange={(e) => setNewReview(e.target.value)}
/> />
<button onClick={() => submitReview()}>Submit</button> <button onClick={() => submitReview()}>{Tr("Submit")}</button>
</div> </div>
</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 { useNavigate } from "react-router-dom";
import { useQuery } from "./Common"; import { useQuery } from "./Common";
import FilesTable from "./FilesTable"; import FilesTable from "./FilesTable";
import { Tr, tr, langCodeContext } from "../translate";
function SearchFiles(props) { function SearchFiles(props) {
const navigator = useNavigate(); const navigator = useNavigate();
@@ -12,6 +13,7 @@ function SearchFiles(props) {
const offset = parseInt(query.get("o")) || 0; const offset = parseInt(query.get("o")) || 0;
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const limit = 10; const limit = 10;
const { langCode } = useContext(langCodeContext);
function searchFiles() { function searchFiles() {
// check empty filename // check empty filename
@@ -57,7 +59,7 @@ function SearchFiles(props) {
return ( return (
<div className="page"> <div className="page">
<h3>Search Files</h3> <h3>{Tr("Search Files")}</h3>
<div className="search_toolbar"> <div className="search_toolbar">
<input <input
onChange={(event) => setFilenameInput(event.target.value)} onChange={(event) => setFilenameInput(event.target.value)}
@@ -67,7 +69,7 @@ function SearchFiles(props) {
} }
}} }}
type="text" type="text"
placeholder="Enter filename" placeholder={tr("Enter filename", langCode)}
value={filenameInput} value={filenameInput}
/> />
<button <button
@@ -75,13 +77,13 @@ function SearchFiles(props) {
navigator(`/files?q=${filenameInput}&o=0`); navigator(`/files?q=${filenameInput}&o=0`);
}} }}
> >
{isLoading ? "Loading..." : "Search"} {isLoading ? Tr("Loading...") : Tr("Search")}
</button> </button>
<button onClick={lastPage}>Last page</button> <button onClick={lastPage}>{Tr("Last page")}</button>
<button disabled> <button disabled>
{offset} - {offset + files.length} {offset} - {offset + files.length}
</button> </button>
<button onClick={nextPage}>Next page</button> <button onClick={nextPage}>{Tr("Next page")}</button>
</div> </div>
<FilesTable setPlayingFile={props.setPlayingFile} files={files} /> <FilesTable setPlayingFile={props.setPlayingFile} files={files} />
</div> </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 { useNavigate } from "react-router-dom";
import { useQuery } from "./Common"; import { useQuery } from "./Common";
import FoldersTable from "./FoldersTable"; import FoldersTable from "./FoldersTable";
import { Tr, tr, langCodeContext } from "../translate";
function SearchFolders() { function SearchFolders() {
const navigator = useNavigate(); const navigator = useNavigate();
@@ -12,6 +13,7 @@ function SearchFolders() {
const offset = parseInt(query.get("o")) || 0; const offset = parseInt(query.get("o")) || 0;
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const limit = 10; const limit = 10;
const { langCode } = useContext(langCodeContext);
function searchFolder() { function searchFolder() {
if (foldername === "") { if (foldername === "") {
@@ -55,7 +57,7 @@ function SearchFolders() {
return ( return (
<div className="page"> <div className="page">
<h3>Search Folders</h3> <h3>{Tr("Search Folders")}</h3>
<div className="search_toolbar"> <div className="search_toolbar">
<input <input
onChange={(event) => setFoldernameInput(event.target.value)} onChange={(event) => setFoldernameInput(event.target.value)}
@@ -65,7 +67,7 @@ function SearchFolders() {
} }
}} }}
type="text" type="text"
placeholder="Enter folder name" placeholder={tr("Enter folder name", langCode)}
value={foldernameInput} value={foldernameInput}
/> />
<button <button
@@ -73,13 +75,13 @@ function SearchFolders() {
navigator(`/folders?q=${foldernameInput}&o=0`); navigator(`/folders?q=${foldernameInput}&o=0`);
}} }}
> >
{isLoading ? "Loading..." : "Search"} {isLoading ? Tr("Loading...") : Tr("Search")}
</button> </button>
<button onClick={lastPage}>Last page</button> <button onClick={lastPage}>{Tr("Last page")}</button>
<button disabled> <button disabled>
{offset} - {offset + limit} {offset} - {offset + limit}
</button> </button>
<button onClick={nextPage}>Next page</button> <button onClick={nextPage}>{Tr("Next page")}</button>
</div> </div>
<FoldersTable folders={folders} /> <FoldersTable folders={folders} />
</div> </div>

View File

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

View File

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

View File

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