mirror of https://github.com/casbin/casnode.git
feat: add translation config ui
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
This commit is contained in:
parent
7a50aa5ee2
commit
bee1726dc0
|
@ -25,5 +25,4 @@ casdoorEndpoint = http://localhost:8000
|
|||
clientId = 014ae4bd048734ca2dea
|
||||
clientSecret = xxx
|
||||
jwtSecret = CasdoorSecret
|
||||
casdoorOrganization = "casbin-forum"
|
||||
googleTranslationKey = xxx
|
||||
casdoorOrganization = "casbin-forum"
|
|
@ -17,11 +17,6 @@ package controllers
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
beego "github.com/beego/beego/v2/adapter"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -651,82 +646,17 @@ func (c *ApiController) TranslateTopic() {
|
|||
|
||||
topicId := util.ParseInt(topicIdStr)
|
||||
|
||||
var result TopicTranslateData
|
||||
translateData := &object.TranslateData{}
|
||||
|
||||
topic := object.GetTopic(topicId)
|
||||
if topic == nil || topic.Deleted {
|
||||
result.ErrMsg = "Invalid TopicId"
|
||||
c.Data["json"] = result
|
||||
translateData.ErrMsg = "Invalid TopicId"
|
||||
c.Data["json"] = translateData
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
contentStr := topic.Content
|
||||
|
||||
replaceStr := "<code>RplaceWithCasnodeTranslator<code/>"
|
||||
contentReg := regexp.MustCompile(`(?s)\x60{1,3}[^\x60](.*?)\x60{1,3}`)
|
||||
translateReg := regexp.MustCompile(replaceStr)
|
||||
|
||||
codeBlocks := contentReg.FindAllString(contentStr, -1)
|
||||
var cbList []string
|
||||
|
||||
if codeBlocks != nil {
|
||||
for _, cbItem := range codeBlocks {
|
||||
cbList = append(cbList, cbItem)
|
||||
}
|
||||
}
|
||||
|
||||
contentStr = contentReg.ReplaceAllString(contentStr, replaceStr)
|
||||
|
||||
params := url.Values{
|
||||
"target": {targetLang},
|
||||
"format": {"text"},
|
||||
"key": {beego.AppConfig.String("googleTranslationKey")},
|
||||
"q": {contentStr},
|
||||
}
|
||||
resp, _ := http.PostForm("https://translation.googleapis.com/language/translate/v2", params)
|
||||
defer resp.Body.Close()
|
||||
|
||||
respByte, _ := ioutil.ReadAll(resp.Body)
|
||||
var translateResp GoogleTranslationResult
|
||||
translateResp.Error.Code = 0
|
||||
|
||||
err := json.Unmarshal(respByte, &translateResp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
translateStr := translateResp.Data.Translations[0].TranslatedText
|
||||
detectSrcLang := translateResp.Data.Translations[0].DetectedSourceLanguage
|
||||
|
||||
replacedCb := translateReg.FindAllString(translateStr, -1)
|
||||
var replacedCbList []string
|
||||
if replacedCb != nil {
|
||||
for _, replacedCbItem := range replacedCb {
|
||||
replacedCbList = append(replacedCbList, replacedCbItem)
|
||||
}
|
||||
}
|
||||
|
||||
if len(replacedCbList) != len(codeBlocks) {
|
||||
result.ErrMsg = "Translate Failed"
|
||||
c.Data["json"] = result
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
replaceIndex := 0
|
||||
translateStr = translateReg.ReplaceAllStringFunc(translateStr, func(src string) string {
|
||||
replaceIndex = replaceIndex + 1
|
||||
return cbList[replaceIndex-1]
|
||||
})
|
||||
|
||||
if translateResp.Error.Code != 0 {
|
||||
result.ErrMsg = translateResp.Error.Message
|
||||
} else {
|
||||
result.SrcLang = detectSrcLang
|
||||
result.Target = translateStr
|
||||
}
|
||||
|
||||
c.Data["json"] = result
|
||||
c.Data["json"] = object.StrTranslate(topic.Content, targetLang)
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/casbin/casnode/object"
|
||||
)
|
||||
|
||||
func (c *ApiController) UpdateTranslator() {
|
||||
var resp Response
|
||||
if !object.CheckModIdentity(c.GetSessionUsername()) {
|
||||
resp = Response{Status: "fail", Msg: "Unauthorized."}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
var translator object.Translator
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &translator)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
object.UpdateTranslator(translator)
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Msg: "success"}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) AddTranslator() {
|
||||
var resp Response
|
||||
if !object.CheckModIdentity(c.GetSessionUsername()) {
|
||||
resp = Response{Status: "fail", Msg: "Unauthorized."}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
var translator object.Translator
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &translator)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
res := object.GetTranslator(translator.Id)
|
||||
if len(*res) > 0 {
|
||||
resp = Response{Status: "fail", Msg: "Translator ID existed"}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if object.AddTranslator(translator) {
|
||||
resp = Response{Status: "ok", Msg: "Success"}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
} else {
|
||||
resp = Response{Status: "fail", Msg: "Add translator failed"}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiController) GetTranslator() {
|
||||
id := c.Input().Get("id")
|
||||
res := object.GetTranslator(id)
|
||||
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetEnableTranslator() {
|
||||
res := object.GetEnableTranslator()
|
||||
|
||||
c.Data["json"] = res
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) VisibleTranslator() {
|
||||
resp := Response{Status: "ok", Data: false}
|
||||
|
||||
res := object.GetEnableTranslator()
|
||||
if res != nil {
|
||||
if res.Visible {
|
||||
resp = Response{Status: "ok", Data: true}
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) DelTranslator() {
|
||||
id := c.Input().Get("id")
|
||||
resp := Response{Status: "fail", Msg: "Delete translator failed"}
|
||||
if object.DelTranslator(id) {
|
||||
resp = Response{Status: "ok", Msg: "Success"}
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -84,27 +84,3 @@ type adminNodeInfo struct {
|
|||
TopicNum int `json:"topicNum"`
|
||||
FavoritesNum int `json:"favoritesNum"`
|
||||
}
|
||||
|
||||
type TopicTranslateData struct {
|
||||
SrcLang string `json:"srcLang"`
|
||||
Target string `json:"target"`
|
||||
ErrMsg string `json:"err_msg"`
|
||||
}
|
||||
|
||||
type GoogleTranslationResult struct {
|
||||
Data struct {
|
||||
Translations []struct {
|
||||
TranslatedText string `json:"translatedText"`
|
||||
DetectedSourceLanguage string `json:"detectedSourceLanguage"`
|
||||
} `json:"translations"`
|
||||
} `json:"data"`
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Errors []struct {
|
||||
Message string `json:"message"`
|
||||
Domain string `json:"domain"`
|
||||
Reason string `json:"reason"`
|
||||
} `json:"errors"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
|
|
@ -120,6 +120,11 @@ func (a *Adapter) createTable() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Translator))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Node))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Translator struct {
|
||||
Id string `xorm:"varchar(50) notnull pk" json:"id"`
|
||||
Name string `xorm:"varchar(50)" json:"name"`
|
||||
Translator string `xorm:"varchar(50)" json:"translator"`
|
||||
Key string `xorm:"varchar(200)" json:"key"`
|
||||
Enable bool `xorm:"bool" json:"enable"`
|
||||
Visible bool `xorm:"bool" json:"visible"`
|
||||
}
|
||||
|
||||
type TranslateData struct {
|
||||
SrcLang string `json:"srcLang"`
|
||||
Target string `json:"target"`
|
||||
ErrMsg string `json:"err_msg"`
|
||||
}
|
||||
|
||||
type GoogleTranslationResult struct {
|
||||
Data struct {
|
||||
Translations []struct {
|
||||
TranslatedText string `json:"translatedText"`
|
||||
DetectedSourceLanguage string `json:"detectedSourceLanguage"`
|
||||
} `json:"translations"`
|
||||
} `json:"data"`
|
||||
Error struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Errors []struct {
|
||||
Message string `json:"message"`
|
||||
Domain string `json:"domain"`
|
||||
Reason string `json:"reason"`
|
||||
} `json:"errors"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func StrTranslate(srcStr, targetLang string) *TranslateData {
|
||||
replaceStr := "<code>RplaceWithCasnodeTranslator<code/>"
|
||||
contentReg := regexp.MustCompile(`(?s)\x60{1,3}[^\x60](.*?)\x60{1,3}`)
|
||||
translateReg := regexp.MustCompile(replaceStr)
|
||||
translateData := &TranslateData{}
|
||||
|
||||
translator := GetEnableTranslator()
|
||||
if translator == nil || !translator.Visible {
|
||||
translateData.ErrMsg = "Translate Failed"
|
||||
return translateData
|
||||
}
|
||||
|
||||
codeBlocks := contentReg.FindAllString(srcStr, -1)
|
||||
var cbList []string
|
||||
|
||||
if codeBlocks != nil {
|
||||
for _, cbItem := range codeBlocks {
|
||||
cbList = append(cbList, cbItem)
|
||||
}
|
||||
}
|
||||
|
||||
srcStr = contentReg.ReplaceAllString(srcStr, replaceStr)
|
||||
|
||||
params := url.Values{
|
||||
"target": {targetLang},
|
||||
"format": {"text"},
|
||||
"key": {translator.Key},
|
||||
"q": {srcStr},
|
||||
}
|
||||
resp, _ := http.PostForm("https://translation.googleapis.com/language/translate/v2", params)
|
||||
defer resp.Body.Close()
|
||||
|
||||
respByte, _ := ioutil.ReadAll(resp.Body)
|
||||
var translateResp GoogleTranslationResult
|
||||
translateResp.Error.Code = 0
|
||||
|
||||
err := json.Unmarshal(respByte, &translateResp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
translateStr := translateResp.Data.Translations[0].TranslatedText
|
||||
detectSrcLang := translateResp.Data.Translations[0].DetectedSourceLanguage
|
||||
|
||||
replacedCb := translateReg.FindAllString(translateStr, -1)
|
||||
var replacedCbList []string
|
||||
if replacedCb != nil {
|
||||
for _, replacedCbItem := range replacedCb {
|
||||
replacedCbList = append(replacedCbList, replacedCbItem)
|
||||
}
|
||||
}
|
||||
|
||||
if len(replacedCbList) != len(codeBlocks) {
|
||||
translateData.ErrMsg = "Translate Failed"
|
||||
return translateData
|
||||
}
|
||||
|
||||
replaceIndex := 0
|
||||
translateStr = translateReg.ReplaceAllStringFunc(translateStr, func(src string) string {
|
||||
replaceIndex = replaceIndex + 1
|
||||
return cbList[replaceIndex-1]
|
||||
})
|
||||
|
||||
if translateResp.Error.Code != 0 {
|
||||
translateData.ErrMsg = translateResp.Error.Message
|
||||
} else {
|
||||
translateData.SrcLang = detectSrcLang
|
||||
translateData.Target = translateStr
|
||||
}
|
||||
|
||||
return translateData
|
||||
}
|
||||
|
||||
func AddTranslator(translator Translator) bool {
|
||||
affected, err := adapter.Engine.Insert(translator)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetTranslator(id string) *[]Translator {
|
||||
translators := []Translator{}
|
||||
var err error
|
||||
if id != "" {
|
||||
err = adapter.Engine.Where("id = ?", id).Find(&translators)
|
||||
} else {
|
||||
err = adapter.Engine.Find(&translators)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &translators
|
||||
}
|
||||
|
||||
func GetEnableTranslator() *Translator {
|
||||
var translator Translator
|
||||
resultNum, err := adapter.Engine.Where("enable = ?", true).Get(&translator)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if resultNum {
|
||||
return &translator
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateTranslator(translator Translator) bool {
|
||||
_, err := adapter.Engine.Where("enable = ?", true).Cols("enable").Update(Translator{Enable: false})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Id(translator.Id).AllCols().Update(translator)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DelTranslator(id string) bool {
|
||||
affected, err := adapter.Engine.Where("id = ?", id).Delete(&Translator{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
|
@ -100,6 +100,12 @@ func initAPI() {
|
|||
beego.Router("/api/update-poster", &controllers.ApiController{}, "POST:UpdatePoster") //Update poster api just for admin.
|
||||
beego.Router("/api/read-poster", &controllers.ApiController{}, "GET:ReadPoster")
|
||||
|
||||
beego.Router("/api/update-translator", &controllers.ApiController{}, "POST:UpdateTranslator") //Update translator api just for admin.
|
||||
beego.Router("/api/add-translator", &controllers.ApiController{}, "POST:AddTranslator") //Add translator api just for admin.
|
||||
beego.Router("/api/del-translator", &controllers.ApiController{}, "POST:DelTranslator") //Delete translator api just for admin.
|
||||
beego.Router("/api/get-translator", &controllers.ApiController{}, "GET:GetTranslator")
|
||||
beego.Router("/api/visible-translator", &controllers.ApiController{}, "GET:VisibleTranslator")
|
||||
|
||||
beego.Router("/api/get-nodes", &controllers.ApiController{}, "GET:GetNodes")
|
||||
beego.Router("/api/get-node", &controllers.ApiController{}, "GET:GetNode")
|
||||
beego.Router("/api/update-node", &controllers.ApiController{}, "POST:UpdateNode") // Update node api just for admin.
|
||||
|
|
|
@ -63,6 +63,7 @@ import AdminPlane from "./admin/AdminPlane";
|
|||
import AdminPoster from "./admin/AdminPoster";
|
||||
import AdminTopic from "./admin/AdminTopic";
|
||||
import AdminSensitive from "./admin/AdminSensitive";
|
||||
import AdminTranslation from "./admin/AdminTranslation";
|
||||
import i18next from "i18next";
|
||||
import "./node.css";
|
||||
import "./i18n";
|
||||
|
@ -406,6 +407,12 @@ class App extends Component {
|
|||
<AdminPoster />
|
||||
</div>
|
||||
</Route>
|
||||
<Route exact path="/admin/translation">
|
||||
<div id={pcBrowser ? "Main" : ""}>
|
||||
{pcBrowser ? <div className="sep20" /> : null}
|
||||
<AdminTranslation />
|
||||
</div>
|
||||
</Route>
|
||||
<Route exact path="/admin/member">
|
||||
<div id={pcBrowser ? "Main" : ""}>
|
||||
{pcBrowser ? <div className="sep20" /> : null}
|
||||
|
|
|
@ -59,6 +59,11 @@ class AdminHomepage extends React.Component {
|
|||
value: "sensitive",
|
||||
image: Setting.getStatic("/static/img/settings.png"),
|
||||
},
|
||||
{
|
||||
label: i18next.t("admin:Translation management"),
|
||||
value: "translation",
|
||||
image: Setting.getStatic("/static/img/settings.png"),
|
||||
},
|
||||
],
|
||||
message: "",
|
||||
};
|
||||
|
|
|
@ -0,0 +1,671 @@
|
|||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import { withRouter, Link } from "react-router-dom";
|
||||
import * as TranslatorBackend from "../backend/TranslatorBackend";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import Select2 from "react-select2-wrapper";
|
||||
import i18n from "i18next";
|
||||
|
||||
class AdminTranslation extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: "",
|
||||
errMessage: "",
|
||||
translators: [],
|
||||
apis: ["Google"],
|
||||
form: {
|
||||
id: "",
|
||||
name: "",
|
||||
translator: "",
|
||||
key: "",
|
||||
enable: false,
|
||||
visible: false,
|
||||
},
|
||||
event: "config",
|
||||
Management_LIST: [
|
||||
{ label: "Config Translator", value: "config" },
|
||||
{ label: "Create", value: "create" },
|
||||
{ label: "Manage", value: "manage" },
|
||||
],
|
||||
color: "",
|
||||
backgroundColor: "",
|
||||
displayColorPicker: false,
|
||||
displayBackgroundColorPicker: false,
|
||||
};
|
||||
this.state.url = "/admin/translation";
|
||||
}
|
||||
|
||||
getAll() {
|
||||
TranslatorBackend.getTranslator().then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
translators: res,
|
||||
},
|
||||
() => {
|
||||
this.getEnableFromList(this.state.translators);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getEnableFromList(translatorArray) {
|
||||
if (translatorArray) {
|
||||
translatorArray.forEach((item) => {
|
||||
if (item.enable) {
|
||||
this.setState({
|
||||
form: item,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
renderSelectPlatform() {
|
||||
if (this.state.translators === null) return;
|
||||
|
||||
return (
|
||||
<Select2
|
||||
value={this.getIndexFromTranslatorId(this.state.form.id)}
|
||||
style={{
|
||||
width: Setting.PcBrowser ? "300px" : "200px",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
data={this.state.translators.map((translatorItem, index) => {
|
||||
return {
|
||||
text: `${translatorItem.name} / ${translatorItem.translator}`,
|
||||
id: index,
|
||||
};
|
||||
})}
|
||||
onSelect={(event) => {
|
||||
const selected = event.target.value;
|
||||
if (selected === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIndex = parseInt(selected);
|
||||
this.setState({
|
||||
form: this.state.translators[selectedIndex],
|
||||
});
|
||||
}}
|
||||
options={{
|
||||
placeholder: i18next.t(
|
||||
"translator:Please select a translator platform"
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSelectAPI() {
|
||||
if (this.state.apis === null) return;
|
||||
|
||||
return (
|
||||
<Select2
|
||||
value={this.state.form.translator}
|
||||
style={{
|
||||
width: Setting.PcBrowser ? "300px" : "200px",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
data={this.state.apis.map((apiItem) => {
|
||||
return { text: apiItem, id: apiItem };
|
||||
})}
|
||||
onSelect={(event) => {
|
||||
const selected = event.target.value;
|
||||
if (selected === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState((prevState) => {
|
||||
prevState.form.translator = selected;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
options={{
|
||||
placeholder: i18next.t("translator:Please select a translator api"),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
getIndexFromTranslatorId(translatorId) {
|
||||
for (let i = 0; i < this.state.translators.length; i++) {
|
||||
if (this.state.translators[i].id === translatorId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
updateTranslator() {
|
||||
this.clearMessage();
|
||||
this.clearErrorMessage();
|
||||
|
||||
if (this.state.form.id === "") {
|
||||
this.setState({
|
||||
errMessage: i18next.t("translator:Please select a translator platform"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.form.key === "") {
|
||||
this.setState({ errMessage: i18next.t("translator:Please input key") });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
(prevState) => {
|
||||
prevState.form.enable = true;
|
||||
return prevState;
|
||||
},
|
||||
() => {
|
||||
TranslatorBackend.updateTranslator(this.state.form).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
message: i18next.t(
|
||||
"translator:Update translator information success"
|
||||
),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errMessage: res?.msg,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
addTranslator() {
|
||||
this.clearMessage();
|
||||
this.clearErrorMessage();
|
||||
|
||||
this.setState((prevState) => {
|
||||
prevState.form.enable = false;
|
||||
return prevState;
|
||||
});
|
||||
|
||||
if (this.state.form.id === "") {
|
||||
this.setState({ errMessage: i18next.t("translator:Please input id") });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.form.name === "") {
|
||||
this.setState({ errMessage: i18next.t("translator:Please input name") });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.form.translator === "") {
|
||||
this.setState({
|
||||
errMessage: i18next.t("translator:Please select a translator api"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.form.key === "") {
|
||||
this.setState({ errMessage: i18next.t("translator:Please input key") });
|
||||
return;
|
||||
}
|
||||
|
||||
TranslatorBackend.addTranslator(this.state.form).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
message: i18next.t("translator:Add translator information success"),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errMessage: i18next.t(`error:${res?.msg}`),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteTranslator(translator) {
|
||||
if (
|
||||
window.confirm(
|
||||
`${i18next.t(`translator:Are you sure to delete translator`)} ${
|
||||
translator.name
|
||||
} ?`
|
||||
)
|
||||
) {
|
||||
if (translator.enable) {
|
||||
alert(i18next.t("translator:Please disable it first"));
|
||||
return;
|
||||
}
|
||||
TranslatorBackend.delTranslator(translator.id).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
message: i18next.t("translator:Delete translator success"),
|
||||
});
|
||||
|
||||
this.setState({
|
||||
translators: this.state.translators.reduce((total, current) => {
|
||||
current.id !== translator.id && total.push(current);
|
||||
return total;
|
||||
}, []),
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
errMessage: i18next.t(`error:${res?.msg}`),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clearMessage() {
|
||||
this.setState({
|
||||
message: "",
|
||||
});
|
||||
}
|
||||
|
||||
clearErrorMessage() {
|
||||
this.setState({
|
||||
errMessage: "",
|
||||
});
|
||||
}
|
||||
|
||||
changeEvent(event) {
|
||||
switch (event) {
|
||||
case "config":
|
||||
this.getAll();
|
||||
break;
|
||||
case "create":
|
||||
this.setState({
|
||||
form: {
|
||||
id: "",
|
||||
name: "",
|
||||
translator: "",
|
||||
key: "",
|
||||
enable: false,
|
||||
visible: false,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case "manage":
|
||||
this.getAll();
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
event: event,
|
||||
message: "",
|
||||
errMessage: "",
|
||||
});
|
||||
}
|
||||
|
||||
renderProblem() {
|
||||
let problems = [];
|
||||
|
||||
if (this.state.errMessage !== "") {
|
||||
problems.push(this.state.errMessage);
|
||||
}
|
||||
|
||||
if (problems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="problem" onClick={() => this.clearErrorMessage()}>
|
||||
{i18next.t(
|
||||
"error:Please resolve the following issues before submitting"
|
||||
)}
|
||||
<ul>
|
||||
{problems.map((problem) => {
|
||||
return <li>{problem}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getAll();
|
||||
}
|
||||
|
||||
renderManagementList(item) {
|
||||
return (
|
||||
<a
|
||||
href="javascript:void(0);"
|
||||
className={this.state.event === item.value ? "tab_current" : "tab"}
|
||||
onClick={() => this.changeEvent(item.value)}
|
||||
>
|
||||
{i18next.t(`translator:${item.label}`)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderTranslators(translator) {
|
||||
const pcBrowser = Setting.PcBrowser;
|
||||
|
||||
return (
|
||||
<div className="cell">
|
||||
<table cellPadding="0" cellSpacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width={pcBrowser ? "60" : "auto"} align="left">
|
||||
<span>{translator?.id}</span>
|
||||
</td>
|
||||
<td width={pcBrowser ? "200" : "auto"} align="center">
|
||||
<span>{translator?.name}</span>
|
||||
</td>
|
||||
<td width={pcBrowser ? "200" : "auto"} align="center">
|
||||
<span>{translator?.translator}</span>
|
||||
</td>
|
||||
<td width={pcBrowser ? "200" : "auto"} align="center">
|
||||
{translator?.enable ? (
|
||||
<span className="positive">
|
||||
{i18next.t("translator:Enable")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="gray">
|
||||
{i18next.t("translator:Disable")}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<a onClick={() => this.deleteTranslator(translator)}>
|
||||
{i18next.t("translator:Delete")}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderFormBox(form) {
|
||||
return (
|
||||
<div>
|
||||
<div className="box">
|
||||
<div className="header">
|
||||
<Link to="/">{Setting.getForumName()}</Link>{" "}
|
||||
<span className="chevron"> › </span>
|
||||
<Link to="/admin">{i18next.t("admin:Backstage management")}</Link>
|
||||
<span className="chevron"> › </span>{" "}
|
||||
{i18next.t("translator:Translator management")}
|
||||
</div>
|
||||
<div className="cell">
|
||||
{this.state.Management_LIST.map((item) => {
|
||||
return this.renderManagementList(item);
|
||||
})}
|
||||
</div>
|
||||
{form}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
switch (this.state.event) {
|
||||
case "config":
|
||||
return this.renderFormBox(
|
||||
<div className="box">
|
||||
{this.renderProblem()}
|
||||
{this.state.message !== "" ? (
|
||||
<div className="message" onClick={() => this.clearMessage()}>
|
||||
<li className="fa fa-exclamation-triangle" />
|
||||
{this.state.message}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="inner">
|
||||
<table cellPadding="5" cellSpacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Translation Platform")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
{this.renderSelectPlatform()}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Key")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
let targetVal = event.target.value;
|
||||
this.setState((prevState) => {
|
||||
prevState.form.key = targetVal;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
value={this.state.form?.key}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Visible")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
type="radio"
|
||||
onClick={() => {
|
||||
this.setState((prevState) => {
|
||||
prevState.form.visible = true;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
checked={this.state.form?.visible}
|
||||
name="visible"
|
||||
/>
|
||||
{i18next.t("tab:show")}{" "}
|
||||
<input
|
||||
type="radio"
|
||||
onClick={() => {
|
||||
this.setState((prevState) => {
|
||||
prevState.form.visible = false;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
checked={!this.state.form?.visible}
|
||||
name="visible"
|
||||
/>
|
||||
{i18next.t("tab:hidden")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
width={Setting.PcBrowser ? "120" : "90"}
|
||||
align="right"
|
||||
></td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
type="submit"
|
||||
className="super normal button"
|
||||
value={i18next.t("translator:Save")}
|
||||
onClick={() => this.updateTranslator()}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "create":
|
||||
return this.renderFormBox(
|
||||
<div className="box">
|
||||
{this.renderProblem()}
|
||||
{this.state.message !== "" ? (
|
||||
<div className="message" onClick={() => this.clearMessage()}>
|
||||
<li className="fa fa-exclamation-triangle" />
|
||||
{this.state.message}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="inner">
|
||||
<table cellPadding="5" cellSpacing="0" border="0" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Translation Platform ID")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
let targetVal = event.target.value;
|
||||
this.setState((prevState) => {
|
||||
prevState.form.id = targetVal;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
value={this.state.form.id}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Translation Platform Name")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
let targetVal = event.target.value;
|
||||
this.setState((prevState) => {
|
||||
prevState.form.name = targetVal;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
value={this.state.form.name}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Translation API")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
{this.renderSelectAPI()}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Key")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
let targetVal = event.target.value;
|
||||
this.setState((prevState) => {
|
||||
prevState.form.key = targetVal;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
value={this.state.form.key}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width={Setting.PcBrowser ? "120" : "90"} align="right">
|
||||
{i18next.t("translator:Visible")}
|
||||
</td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
type="radio"
|
||||
onClick={() => {
|
||||
this.setState((prevState) => {
|
||||
prevState.form.visible = true;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
checked={this.state.form.visible}
|
||||
name="visible"
|
||||
/>
|
||||
{i18next.t("tab:show")}{" "}
|
||||
<input
|
||||
type="radio"
|
||||
onClick={() => {
|
||||
this.setState((prevState) => {
|
||||
prevState.form.visible = false;
|
||||
return prevState;
|
||||
});
|
||||
}}
|
||||
checked={!this.state.form.visible}
|
||||
name="visible"
|
||||
/>
|
||||
{i18next.t("tab:hidden")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
width={Setting.PcBrowser ? "120" : "90"}
|
||||
align="right"
|
||||
></td>
|
||||
<td width="auto" align="left">
|
||||
<input
|
||||
type="submit"
|
||||
className="super normal button"
|
||||
value={i18next.t("translator:Save")}
|
||||
onClick={() => this.addTranslator()}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
case "manage":
|
||||
return this.renderFormBox(
|
||||
<div className="box">
|
||||
{this.renderProblem()}
|
||||
{this.state.message !== "" ? (
|
||||
<div className="message" onClick={() => this.clearMessage()}>
|
||||
<li className="fa fa-exclamation-triangle" />
|
||||
{this.state.message}
|
||||
</div>
|
||||
) : null}
|
||||
<div id="all-translators">
|
||||
{this.state.translators !== null &&
|
||||
this.state.translators.length !== 0 ? (
|
||||
this.state.translators.map((translator) =>
|
||||
this.renderTranslators(translator)
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
className="cell"
|
||||
style={{
|
||||
textAlign: "center",
|
||||
height: "100px",
|
||||
lineHeight: "100px",
|
||||
}}
|
||||
>
|
||||
{this.state.translators === null
|
||||
? i18next.t("loading:Data is loading...")
|
||||
: i18next.t("translator:No translator yet")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(AdminTranslation);
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function updateTranslator(translator) {
|
||||
return fetch(`${Setting.ServerUrl}/api/update-translator`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(translator),
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
|
||||
export function getTranslator() {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-translator`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
|
||||
export function addTranslator(translator) {
|
||||
return fetch(`${Setting.ServerUrl}/api/add-translator`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(translator),
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
|
||||
export function delTranslator(id) {
|
||||
return fetch(`${Setting.ServerUrl}/api/del-translator?id=${id}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
}).then((res) => res.json());
|
||||
}
|
||||
|
||||
export function visibleTranslator() {
|
||||
return fetch(`${Setting.ServerUrl}/api/visible-translator`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}).then((res) => res.json());
|
||||
}
|
|
@ -159,8 +159,6 @@
|
|||
"Favor": "Favor",
|
||||
"hits": "hits",
|
||||
"Ignore": "Ignore",
|
||||
"Translate": "Translate",
|
||||
"Translated from {lang} by": "Translated from {lang} by",
|
||||
"Thank": "Thank",
|
||||
"Thanked": "Thanked",
|
||||
"Total Topics": "Total Topics",
|
||||
|
@ -444,6 +442,34 @@
|
|||
"Save": "Save",
|
||||
"Update poster information success": "Update poster information success"
|
||||
},
|
||||
"translator": {
|
||||
"Translator management": "Translator management",
|
||||
"Translation Platform ID": "Platform ID",
|
||||
"Translation Platform Name": "Platform Name",
|
||||
"Translation Platform": "Translation Platform",
|
||||
"Translation API": "Translation API",
|
||||
"Key": "Key",
|
||||
"Visible": "Visible",
|
||||
"Save": "Save",
|
||||
"Update translator information success": "Update translator information success",
|
||||
"Add translator information success": "Add translator information success",
|
||||
"Delete translator success": "Delete translator success",
|
||||
"Please select a translator platform": "Please select a translator platform",
|
||||
"Please input id": "Please input id",
|
||||
"Please input name": "Please input name",
|
||||
"Please input key": "Please input key",
|
||||
"Please select a translator api": "Please select a translator api",
|
||||
"No translator yet": "No translator yet",
|
||||
"Config Translator": "Config Translator",
|
||||
"Are you sure to delete translator": "Are you sure to delete translator",
|
||||
"Please disable it first": "Please disable it first",
|
||||
"Delete": "Delete",
|
||||
"Enable": "Enable",
|
||||
"Disable": "Disable",
|
||||
"Create": "Create",
|
||||
"Success": "Success",
|
||||
"Manage": "Manage"
|
||||
},
|
||||
"member": {
|
||||
"Watch": "Watch",
|
||||
"member": "member",
|
||||
|
@ -669,6 +695,7 @@
|
|||
"Poster management": "Poster management",
|
||||
"Member management": "Member management",
|
||||
"Sensitive management": "Sensitive management",
|
||||
"Translation management": "Translation management",
|
||||
"Tab management": "Tab management",
|
||||
"Plane management": "Plane management",
|
||||
"Get search result success": "Get search result success",
|
||||
|
@ -763,7 +790,10 @@
|
|||
"Please sign out before sign up": "Please sign out before sign up",
|
||||
"Please sign out before sign in": "Please sign out before sign in",
|
||||
"Adding image failed": "Adding image failed",
|
||||
"Add file failed": "Add file failed"
|
||||
"Add file failed": "Add file failed",
|
||||
"Translator ID existed": "Translator ID existed",
|
||||
"Add translator failed": "Add translator failed",
|
||||
"Delete translator failed": "Delete translator failed"
|
||||
},
|
||||
"search": {
|
||||
"loading...": "loading...",
|
||||
|
|
|
@ -159,8 +159,6 @@
|
|||
"Favor": "加入收藏",
|
||||
"hits": "次点击",
|
||||
"Ignore": "忽略主题",
|
||||
"Translate": "Translate",
|
||||
"Translated from {lang} by": "翻译自 {lang} 通过",
|
||||
"Thank": "感谢",
|
||||
"Thanked": "已感谢",
|
||||
"Total Topics": "主题总数",
|
||||
|
@ -514,6 +512,34 @@
|
|||
"Save": "保存",
|
||||
"Update poster information success": "更新广告信息成功"
|
||||
},
|
||||
"translator": {
|
||||
"Translator management": "翻译管理",
|
||||
"Translation Platform ID": "平台 ID",
|
||||
"Translation Platform Name": "平台名称",
|
||||
"Translation Platform": "翻译平台",
|
||||
"Translation API": "翻译 API",
|
||||
"Key": "Key",
|
||||
"Visible": "可见性",
|
||||
"Save": "保存",
|
||||
"Update translator information success": "更新翻译信息成功",
|
||||
"Add translator information success": "添加翻译信息成功",
|
||||
"Delete translator success": "删除翻译平台成功",
|
||||
"Please select a translator platform": "请选择翻译平台",
|
||||
"Please input id": "请输入 ID",
|
||||
"Please input name": "请输入平台名称",
|
||||
"Please input key": "请输入 key",
|
||||
"Please select a translator api": "请选择翻译 API",
|
||||
"No translator yet": "暂无平台",
|
||||
"Config Translator": "配置翻译",
|
||||
"Are you sure to delete translator": "确定删除翻译平台",
|
||||
"Please disable it first": "请先禁用该平台",
|
||||
"Delete": "删除",
|
||||
"Enable": "启用",
|
||||
"Disable": "禁用",
|
||||
"Create": "新建",
|
||||
"Success": "成功",
|
||||
"Manage": "管理"
|
||||
},
|
||||
"sensitive": {
|
||||
"sensitive management": "敏感词管理",
|
||||
"new sensitive": "增加敏感词"
|
||||
|
@ -678,6 +704,7 @@
|
|||
"Tab management": "类别管理",
|
||||
"Plane management": "位面管理",
|
||||
"Sensitive management": "敏感词管理",
|
||||
"Translation management": "翻译管理",
|
||||
"Get search result success": "获取搜索结果成功",
|
||||
"Sorter": "排序",
|
||||
"Show": "显示",
|
||||
|
@ -763,7 +790,10 @@
|
|||
"Please sign out before sign up": "注册前请先登出",
|
||||
"Please sign out before sign in": "登录前请先登出",
|
||||
"Adding image failed": "添加图片文件失败",
|
||||
"Add file failed": "添加文件失败"
|
||||
"Add file failed": "添加文件失败",
|
||||
"Translator ID existed": "翻译平台 ID 已存在",
|
||||
"Add translator failed": "添加翻译平台失败",
|
||||
"Delete translator failed": "删除翻译平台失败"
|
||||
},
|
||||
"search": {
|
||||
"loading...": "加载中...",
|
||||
|
|
|
@ -21,6 +21,7 @@ import Avatar from "../Avatar";
|
|||
import ReplyBox from "./ReplyBox";
|
||||
import * as FavoritesBackend from "../backend/FavoritesBackend";
|
||||
import * as BalanceBackend from "../backend/BalanceBackend";
|
||||
import * as TranslatorBackend from "../backend/TranslatorBackend";
|
||||
import "../node.css";
|
||||
import Zmage from "react-zmage";
|
||||
import { Link } from "react-router-dom";
|
||||
|
@ -44,6 +45,7 @@ class TopicBox extends React.Component {
|
|||
favoritesStatus: false,
|
||||
defaultTopTopicTime: 10,
|
||||
from: "/",
|
||||
showTranslateBtn: false,
|
||||
translation: {
|
||||
translated: false,
|
||||
from: "",
|
||||
|
@ -64,6 +66,7 @@ class TopicBox extends React.Component {
|
|||
this.getTopic();
|
||||
this.getFavoriteStatus();
|
||||
TopicBackend.addTopicBrowseCount(this.state.topicId);
|
||||
this.renderTranslateButton();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -409,6 +412,18 @@ class TopicBox extends React.Component {
|
|||
);
|
||||
}
|
||||
|
||||
renderTranslateButton() {
|
||||
TranslatorBackend.visibleTranslator().then((res) => {
|
||||
let translateBtn = false;
|
||||
if (res?.data) {
|
||||
translateBtn = true;
|
||||
}
|
||||
this.setState({
|
||||
showTranslateBtn: translateBtn,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderDesktopButtons() {
|
||||
return (
|
||||
<div className="topic_buttons">
|
||||
|
@ -884,23 +899,25 @@ class TopicBox extends React.Component {
|
|||
)}
|
||||
escapeHtml={false}
|
||||
/>
|
||||
<a href="#;" onClick={() => this.translateTopic()}>
|
||||
<p style={{ margin: 15 }}>
|
||||
{this.state.translation.translated ? (
|
||||
<span>
|
||||
{i18next
|
||||
.t("topic:Translated from {lang} by")
|
||||
.replace("{lang}", this.state.translation.from)}
|
||||
<img
|
||||
height={18}
|
||||
src="https://cdn.casbin.org/img/logo_google.svg"
|
||||
></img>
|
||||
</span>
|
||||
) : (
|
||||
<span>{i18next.t("topic:Translate")}</span>
|
||||
)}
|
||||
</p>
|
||||
</a>
|
||||
{this.state.showTranslateBtn ? (
|
||||
<a href="#;" onClick={() => this.translateTopic()}>
|
||||
<p style={{ margin: 15 }}>
|
||||
{this.state.translation.translated ? (
|
||||
<span>
|
||||
{`Translate from ${this.state.translation.from} by `}
|
||||
<img
|
||||
height={18}
|
||||
src="https://cdn.casbin.org/img/logo_google.svg"
|
||||
></img>
|
||||
</span>
|
||||
) : (
|
||||
<span>Translate</span>
|
||||
)}
|
||||
</p>
|
||||
</a>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{this.state.translation.translated ? (
|
||||
<ReactMarkdown
|
||||
renderers={{
|
||||
|
|
Loading…
Reference in New Issue