feat: can specify available UI languages for an organization (#1306)

This commit is contained in:
Yaodong Yu
2022-11-19 22:11:19 +08:00
committed by GitHub
parent b98ce19211
commit 19ba37e0c2
17 changed files with 75 additions and 31 deletions

View File

@ -58,6 +58,7 @@ func initBuiltInOrganization() bool {
PhonePrefix: "86", PhonePrefix: "86",
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")), DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
Tags: []string{}, Tags: []string{},
Languages: []string{"en", "zh", "es", "fr", "de", "ja", "ko", "ru"},
AccountItems: []*AccountItem{ AccountItems: []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"}, {Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"}, {Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},

View File

@ -45,6 +45,7 @@ type Organization struct {
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"` DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"` IsProfilePublic bool `json:"isProfilePublic"`

View File

@ -636,7 +636,7 @@ class App extends Component {
{ {
this.renderAccount() this.renderAccount()
} }
<SelectLanguageBox /> {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div> </div>
</Header> </Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: "stretch"}}> <Layout style={{backgroundColor: "#f5f5f5", alignItems: "stretch"}}>
@ -680,7 +680,7 @@ class App extends Component {
{ {
this.renderAccount() this.renderAccount()
} }
<SelectLanguageBox /> {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div> </div>
</Header> </Header>
{ {

View File

@ -59,12 +59,6 @@
height: 70px; /* Footer height */ height: 70px; /* Footer height */
} }
#language-box-corner {
position: absolute;
top: 75px;
right: 0;
}
.language-box { .language-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg"); background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px; background-size: 25px, 25px;

View File

@ -255,6 +255,31 @@ class OrganizationEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Languages"), i18next.t("general:Languages - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}}
value={this.state.organization.languages}
onChange={(value => {
this.updateOrganizationField("languages", value);
})} >
{
[
{value: "en", label: "English"},
{value: "zh", label: "简体中文"},
{value: "es", label: "Español"},
{value: "fr", label: "Français"},
{value: "de", label: "Deutsch"},
{value: "ja", label: "日本語"},
{value: "ko", label: "한국어"},
{value: "ru", label: "Русский"},
].map((item, index) => <Option key={index} value={item.value}>{item.label}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} :

View File

@ -37,6 +37,7 @@ class OrganizationListPage extends BaseListPage {
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`, defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "", defaultApplication: "",
tags: [], tags: [],
languages: ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
masterPassword: "", masterPassword: "",
enableSoftDeletion: false, enableSoftDeletion: false,
isProfilePublic: true, isProfilePublic: true,

View File

@ -28,28 +28,45 @@ class SelectLanguageBox extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
}; };
} }
items = [
this.getItem("English", "en", flagIcon("US", "English")),
this.getItem("简体中文", "zh", flagIcon("CN", "简体中文")),
this.getItem("Español", "es", flagIcon("ES", "Español")),
this.getItem("Français", "fr", flagIcon("FR", "Français")),
this.getItem("Deutsch", "de", flagIcon("DE", "Deutsch")),
this.getItem("日本語", "ja", flagIcon("JP", "日本語")),
this.getItem("한국어", "ko", flagIcon("KR", "한국어")),
this.getItem("Русский", "ru", flagIcon("RU", "Русский")),
];
getOrganizationLanguages(languages) {
const select = [];
for (const language of languages) {
this.items.map((item, index) => item.key === language ? select.push(item) : null);
}
return select;
}
getItem(label, key, icon) {
return {key, icon, label};
}
render() { render() {
const languageItems = this.getOrganizationLanguages(this.state.languages);
const menu = ( const menu = (
<Menu onClick={(e) => { <Menu items={languageItems} onClick={(e) => {
Setting.changeLanguage(e.key); Setting.setLanguage(e.key);
}}> }}>
<Menu.Item key="en" icon={flagIcon("US", "English")}>English</Menu.Item>
<Menu.Item key="zh" icon={flagIcon("CN", "简体中文")}>简体中文</Menu.Item>
<Menu.Item key="es" icon={flagIcon("ES", "Español")}>Español</Menu.Item>
<Menu.Item key="fr" icon={flagIcon("FR", "Français")}>Français</Menu.Item>
<Menu.Item key="de" icon={flagIcon("DE", "Deutsch")}>Deutsch</Menu.Item>
<Menu.Item key="ja" icon={flagIcon("JP", "日本語")}>日本語</Menu.Item>
<Menu.Item key="ko" icon={flagIcon("KR", "한국어")}>한국어</Menu.Item>
<Menu.Item key="ru" icon={flagIcon("RU", "Русский")}>Русский</Menu.Item>
</Menu> </Menu>
); );
return ( return (
<Dropdown overlay={menu} > <Dropdown overlay={menu} >
<div className="language-box" id={this.props.id} style={this.props.style} /> <div className="language-box" style={{display: languageItems.length === 0 ? "none" : null, ...this.props.style}} />
</Dropdown> </Dropdown>
); );
} }

View File

@ -552,13 +552,6 @@ export function setLanguage(language) {
i18next.changeLanguage(language); i18next.changeLanguage(language);
} }
export function changeLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
// window.location.reload(true);
}
export function getAcceptLanguage() { export function getAcceptLanguage() {
return i18next.language + ";q=0.9,en;q=0.8"; return i18next.language + ";q=0.9,en;q=0.8";
} }

View File

@ -28,9 +28,7 @@ import i18next from "i18next";
import CustomGithubCorner from "../CustomGithubCorner"; import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput"; import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox"; import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next";
import {CaptchaModal} from "../common/CaptchaModal"; import {CaptchaModal} from "../common/CaptchaModal";
import {withRouter} from "react-router-dom";
const {TabPane} = Tabs; const {TabPane} = Tabs;
@ -800,7 +798,7 @@ class LoginPage extends React.Component {
{/* {*/} {/* {*/}
{/* this.state.clientId !== null ? "Redirect" : null*/} {/* this.state.clientId !== null ? "Redirect" : null*/}
{/* }*/} {/* }*/}
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} /> <SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
{ {
this.renderSignedInBox() this.renderSignedInBox()
} }
@ -817,4 +815,4 @@ class LoginPage extends React.Component {
} }
} }
export default withTranslation()(withRouter(LoginPage)); export default LoginPage;

View File

@ -645,7 +645,7 @@ class SignupPage extends React.Component {
{ {
Setting.renderLogo(application) Setting.renderLogo(application)
} }
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} /> <SelectLanguageBox languages={application.organizationObj.languages} style={{top: "55px", right: "5px", position: "absolute"}} />
{ {
this.renderForm(application) this.renderForm(application)
} }

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Ist aktiviert - Tooltip", "Is enabled - Tooltip": "Ist aktiviert - Tooltip",
"LDAPs": "LDAPs", "LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip", "LDAPs - Tooltip": "LDAPs - Tooltip",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name", "Last name": "Last name",
"Logo": "Logo", "Logo": "Logo",
"Logo - Tooltip": "App's image tag", "Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Is enabled - Tooltip", "Is enabled - Tooltip": "Is enabled - Tooltip",
"LDAPs": "LDAPs", "LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip", "LDAPs - Tooltip": "LDAPs - Tooltip",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name", "Last name": "Last name",
"Logo": "Logo", "Logo": "Logo",
"Logo - Tooltip": "Logo - Tooltip", "Logo - Tooltip": "Logo - Tooltip",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Est activé - infobulle", "Is enabled - Tooltip": "Est activé - infobulle",
"LDAPs": "LDAPs", "LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Infobulle", "LDAPs - Tooltip": "LDAPs - Infobulle",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name", "Last name": "Last name",
"Logo": "Logo", "Logo": "Logo",
"Logo - Tooltip": "App's image tag", "Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "有効にする - ツールチップ", "Is enabled - Tooltip": "有効にする - ツールチップ",
"LDAPs": "LDAP", "LDAPs": "LDAP",
"LDAPs - Tooltip": "LDAP - ツールチップ", "LDAPs - Tooltip": "LDAP - ツールチップ",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name", "Last name": "Last name",
"Logo": "Logo", "Logo": "Logo",
"Logo - Tooltip": "App's image tag", "Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Is enabled - Tooltip", "Is enabled - Tooltip": "Is enabled - Tooltip",
"LDAPs": "LDAPs", "LDAPs": "LDAPs",
"LDAPs - Tooltip": "LDAPs - Tooltip", "LDAPs - Tooltip": "LDAPs - Tooltip",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Last name", "Last name": "Last name",
"Logo": "Logo", "Logo": "Logo",
"Logo - Tooltip": "App's image tag", "Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "Включено - Подсказка", "Is enabled - Tooltip": "Включено - Подсказка",
"LDAPs": "LDAPы", "LDAPs": "LDAPы",
"LDAPs - Tooltip": "LDAPs - Подсказки", "LDAPs - Tooltip": "LDAPs - Подсказки",
"Languages": "Languages",
"Languages - Tooltip": "Languages - Tooltip",
"Last name": "Фамилия", "Last name": "Фамилия",
"Logo": "Логотип", "Logo": "Логотип",
"Logo - Tooltip": "App's image tag", "Logo - Tooltip": "App's image tag",

View File

@ -170,6 +170,8 @@
"Is enabled - Tooltip": "是否启用", "Is enabled - Tooltip": "是否启用",
"LDAPs": "LDAP", "LDAPs": "LDAP",
"LDAPs - Tooltip": "LDAPs", "LDAPs - Tooltip": "LDAPs",
"Languages": "语言",
"Languages - Tooltip": "可选语言",
"Last name": "姓氏", "Last name": "姓氏",
"Logo": "Logo", "Logo": "Logo",
"Logo - Tooltip": "应用程序向外展示的图标", "Logo - Tooltip": "应用程序向外展示的图标",