localization
This commit is contained in:
parent
1ba27e4f40
commit
3e3b7d7b2b
@ -11,7 +11,7 @@ use Core\Objects\Router\Router;
|
||||
class Account extends TemplateDocument {
|
||||
public function __construct(Router $router, string $templateName) {
|
||||
parent::__construct($router, $templateName);
|
||||
$this->languageModules = ["general", "account"];
|
||||
$this->languageModules[] = "account";
|
||||
$this->title = "Account";
|
||||
$this->searchable = false;
|
||||
$this->enableCSP();
|
||||
|
@ -34,7 +34,7 @@ abstract class Document {
|
||||
$this->domain = $this->getSettings()->getBaseUrl();
|
||||
$this->logger = new Logger("Document", $this->getSQL());
|
||||
$this->searchable = false;
|
||||
$this->languageModules = [];
|
||||
$this->languageModules = ["general"];
|
||||
}
|
||||
|
||||
public abstract function getTitle(): string;
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"title" => "Einloggen",
|
||||
"description" => "Loggen Sie sich in Ihren Account ein",
|
||||
"login_title" => "Einloggen",
|
||||
"login_description" => "Loggen Sie sich in Ihren Account ein",
|
||||
"form_title" => "Bitte geben Sie ihre Daten ein",
|
||||
"username" => "Benutzername",
|
||||
"username_or_email" => "Benutzername oder E-Mail",
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
return [
|
||||
"something_went_wrong" => "Etwas ist schief gelaufen",
|
||||
"error_occurred" => "Ein Fehler ist aufgetreten",
|
||||
"retry" => "Erneut versuchen",
|
||||
"Go back" => "Zurück",
|
||||
"submitting" => "Übermittle",
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
return [
|
||||
"something_went_wrong" => "Something went wrong",
|
||||
"error_occurred" => "An error occurred",
|
||||
"retry" => "Retry",
|
||||
"go_back" => "Go Back",
|
||||
"submitting" => "Submitting",
|
||||
|
@ -12,9 +12,6 @@
|
||||
{% if site.recaptcha.enabled %}
|
||||
<script src="https://www.google.com/recaptcha/api.js?render={{ site.recaptcha.key }}" nonce="{{ site.csp.nonce }}"></script>
|
||||
{% endif %}
|
||||
<script nonce="{{ site.csp.nonce }}">
|
||||
window.languageEntries = {{ site.language.entries|json_encode()|raw }};
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
|
@ -7,6 +7,10 @@
|
||||
{% block head %}
|
||||
<title>{{ site.title }}</title>
|
||||
{% endblock %}
|
||||
<script nonce="{{ site.csp.nonce }}">
|
||||
window.languageCode = {{ site.language.code|json_encode()|raw }};
|
||||
window.languageEntries = {{ site.language.entries|json_encode()|raw }};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
{% block body %}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -11,19 +11,24 @@ import Sidebar from "./elements/sidebar";
|
||||
import LoginForm from "./views/login";
|
||||
import {Alert} from "@material-ui/lab";
|
||||
import {Button} from "@material-ui/core";
|
||||
import {Locale} from "shared/locale";
|
||||
import { LocaleContext } from "shared/locale";
|
||||
|
||||
const L = (key) => {
|
||||
return "<nope>";
|
||||
}
|
||||
|
||||
export default class AdminDashboard extends React.Component {
|
||||
|
||||
static contextType = LocaleContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.api = new API();
|
||||
this.locale = Locale.getInstance();
|
||||
this.state = {
|
||||
loaded: false,
|
||||
dialog: { onClose: () => this.hideDialog() },
|
||||
info: { },
|
||||
error: null,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
@ -40,6 +45,7 @@ export default class AdminDashboard extends React.Component {
|
||||
}
|
||||
|
||||
onInit() {
|
||||
// return;
|
||||
this.setState({ ...this.state, loaded: false, error: null });
|
||||
this.api.getLanguageEntries("general").then(data => {
|
||||
if (data.success) {
|
||||
@ -68,10 +74,6 @@ export default class AdminDashboard extends React.Component {
|
||||
this.onInit();
|
||||
}
|
||||
|
||||
onUpdateLocale() {
|
||||
this.setState({ ...this.state, locale: currentLocale })
|
||||
}
|
||||
|
||||
onLogin(username, password, rememberMe, callback) {
|
||||
this.setState({ ...this.state, error: "" });
|
||||
return this.api.login(username, password, rememberMe).then((res) => {
|
||||
@ -126,14 +128,14 @@ export default class AdminDashboard extends React.Component {
|
||||
|
||||
if (!this.state.loaded) {
|
||||
if (this.state.error) {
|
||||
return <Alert severity={"error"} title={"An error occurred"}>
|
||||
return <Alert severity={"error"} title={L("general.error_occurred")}>
|
||||
<div>{this.state.error}</div>
|
||||
<Button type={"button"} variant={"outlined"} onClick={() => this.onInit()}>
|
||||
Retry
|
||||
</Button>
|
||||
</Alert>
|
||||
} else {
|
||||
return <b>Loading… <Icon icon={"spinner"}/></b>
|
||||
return <b>{L("general.loading")}… <Icon icon={"spinner"}/></b>
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,8 +143,6 @@ export default class AdminDashboard extends React.Component {
|
||||
showDialog: this.showDialog.bind(this),
|
||||
api: this.api,
|
||||
info: this.state.info,
|
||||
locale: this.locale,
|
||||
onUpdateLocale: this.onUpdateLocale.bind(this),
|
||||
onLogout: this.onLogout.bind(this),
|
||||
onLogin: this.onLogin.bind(this),
|
||||
onTotp2FA: this.onTotp2FA.bind(this),
|
||||
@ -179,7 +179,7 @@ export default class AdminDashboard extends React.Component {
|
||||
<Dialog {...this.state.dialog}/>
|
||||
</section>
|
||||
</div>
|
||||
<Footer />
|
||||
<Footer info={this.state.info} />
|
||||
</BrowserRouter>
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Footer() {
|
||||
export default function Footer(props) {
|
||||
|
||||
return (
|
||||
<footer className={"main-footer"}>
|
||||
Theme: <strong>Copyright © 2014-2019 <a href={"https://adminlte.io"}>AdminLTE.io</a>. <b>Version</b> 3.0.3</strong>
|
||||
CMS: <strong><a href={"https://git.romanh.de/Projekte/web-base"}>WebBase</a></strong>. <b>Version</b> 1.2.6
|
||||
CMS: <strong><a href={"https://git.romanh.de/Projekte/web-base"}>WebBase</a></strong>. <b>Version</b> {props.info.version}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, {useState} from 'react';
|
||||
import {L} from "shared/locale";
|
||||
import React, {useCallback, useContext, useState} from 'react';
|
||||
import {Box} from "@material-ui/core";
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
languageFlag: {
|
||||
@ -15,17 +15,16 @@ export default function LanguageSelection(props) {
|
||||
|
||||
const api = props.api;
|
||||
const classes = useStyles();
|
||||
let [languages, setLanguages] = useState(null);
|
||||
const [languages, setLanguages] = useState(null);
|
||||
const {translate: L, setLanguageByCode} = useContext(LocaleContext);
|
||||
|
||||
const onSetLanguage = (code) => {
|
||||
api.setLanguageByCode(code).then((res) => {
|
||||
if (res.success) {
|
||||
props.onUpdateLocale();
|
||||
} else {
|
||||
const onSetLanguage = useCallback((code) => {
|
||||
setLanguageByCode(api, code).then((res) => {
|
||||
if (!res.success) {
|
||||
alert(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
let flags = [];
|
||||
if (languages === null) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import React from "react";
|
||||
import {createRoot} from "react-dom/client";
|
||||
import AdminDashboard from "./App";
|
||||
import {LocaleProvider} from "shared/locale";
|
||||
|
||||
const root = createRoot(document.getElementById('admin-panel'));
|
||||
root.render(<AdminDashboard />);
|
||||
root.render(<LocaleProvider>
|
||||
<AdminDashboard />
|
||||
</LocaleProvider>);
|
||||
|
@ -11,13 +11,13 @@ import {
|
||||
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
import {Alert} from '@material-ui/lab';
|
||||
import React, {useCallback, useEffect, useState} from "react";
|
||||
import React, {useCallback, useContext, useEffect, useState} from "react";
|
||||
import {Navigate} from "react-router-dom";
|
||||
import {L} from "shared/locale";
|
||||
import ReplayIcon from '@material-ui/icons/Replay';
|
||||
import LanguageSelection from "../elements/language-selection";
|
||||
import {decodeText, encodeText, getParameter, removeParameter} from "shared/util";
|
||||
import Icon from "shared/elements/icon";
|
||||
import {LocaleContext} from "shared/locale";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
paper: {
|
||||
@ -81,24 +81,26 @@ export default function LoginForm(props) {
|
||||
let [tfaState, set2FAState] = useState(0); // 0: not sent, 1: sent, 2: retry
|
||||
let [tfaError, set2FAError] = useState("");
|
||||
let [error, setError] = useState("");
|
||||
let [loaded, setLoaded] = useState(0);
|
||||
let [loaded, setLoaded] = useState(false);
|
||||
|
||||
const {translate: L, currentLocale, requestModules} = useContext(LocaleContext);
|
||||
|
||||
const getNextUrl = () => {
|
||||
return getParameter("next") || "/admin";
|
||||
}
|
||||
|
||||
const onUpdateLocale = useCallback(() => {
|
||||
api.getLanguageEntries(["general", "account"]).then(data => {
|
||||
setLoaded(loaded + 1);
|
||||
requestModules(api, ["general", "account"], currentLocale).then(data => {
|
||||
setLoaded(true);
|
||||
if (!data.success) {
|
||||
alert(data.msg);
|
||||
}
|
||||
});
|
||||
}, [loaded]);
|
||||
}, [currentLocale]);
|
||||
|
||||
useEffect(() => {
|
||||
onUpdateLocale();
|
||||
}, []);
|
||||
}, [currentLocale]);
|
||||
|
||||
const onLogin = useCallback(() => {
|
||||
if (!isLoggingIn) {
|
||||
@ -294,7 +296,7 @@ export default function LoginForm(props) {
|
||||
</>
|
||||
}
|
||||
|
||||
if (loaded === 0) {
|
||||
if (!loaded) {
|
||||
return <b>{L("general.loading")}… <Icon icon={"spinner"}/></b>
|
||||
}
|
||||
|
||||
@ -309,7 +311,7 @@ export default function LoginForm(props) {
|
||||
</div>
|
||||
<form className={classes.form} onSubmit={(e) => e.preventDefault()}>
|
||||
{ createForm() }
|
||||
<LanguageSelection api={api} onUpdateLocale={onUpdateLocale} />
|
||||
<LanguageSelection api={api} />
|
||||
</form>
|
||||
</div>
|
||||
</Container>
|
||||
|
@ -1,5 +1,3 @@
|
||||
import {Locale} from "./locale";
|
||||
|
||||
export default class API {
|
||||
constructor() {
|
||||
this.loggedIn = false;
|
||||
@ -139,68 +137,18 @@ export default class API {
|
||||
}
|
||||
|
||||
async setLanguage(params) {
|
||||
let res = await this.apiCall("language/set", params);
|
||||
if (res.success) {
|
||||
Locale.getInstance().setLocale(res.language.code);
|
||||
return await this.apiCall("language/set", params);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async setLanguageByCode(code) {
|
||||
return this.setLanguage({ code: code });
|
||||
}
|
||||
|
||||
async setLanguageByName(name) {
|
||||
return this.setLanguage({ name: name });
|
||||
}
|
||||
|
||||
async getLanguageEntries(modules, code=null, useCache=true) {
|
||||
async getLanguageEntries(modules, code=null, useCache=false) {
|
||||
if (!Array.isArray(modules)) {
|
||||
modules = [modules];
|
||||
}
|
||||
|
||||
let locale = Locale.getInstance();
|
||||
if (code === null) {
|
||||
code = locale.currentLocale;
|
||||
if (code === null && this.loggedIn) {
|
||||
code = this.user.language.code;
|
||||
}
|
||||
return this.apiCall("language/getEntries", {code: code, modules: modules});
|
||||
}
|
||||
|
||||
if (code === null) {
|
||||
return { success: false, msg: "No locale selected currently" };
|
||||
}
|
||||
/*
|
||||
|
||||
let languageEntries = {};
|
||||
if (useCache) {
|
||||
// remove cached modules from request array
|
||||
for (const module of [...modules]) {
|
||||
let moduleEntries = locale.getModule(code, module);
|
||||
if (moduleEntries) {
|
||||
modules.splice(modules.indexOf(module), 1);
|
||||
languageEntries = {...languageEntries, [module]: moduleEntries};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modules.length > 0) {
|
||||
let data = await this.apiCall("language/getEntries", { code: code, modules: modules });
|
||||
|
||||
if (useCache) {
|
||||
if (data && data.success) {
|
||||
// insert into cache
|
||||
for (const [module, entries] of Object.entries(data.entries)) {
|
||||
locale.loadModule(code, module, entries);
|
||||
}
|
||||
data.entries = {...data.entries, ...languageEntries};
|
||||
data.cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} else {
|
||||
return { success: true, msg: "", entries: languageEntries, code: code, cached: true };
|
||||
}
|
||||
}
|
||||
} */
|
||||
};
|
@ -1,18 +1,19 @@
|
||||
// application-wide global variables // translation cache
|
||||
class Locale {
|
||||
import React from 'react';
|
||||
import {createContext, useCallback, useState} from "react";
|
||||
|
||||
constructor() {
|
||||
this.entries = {};
|
||||
this.currentLocale = "en_US";
|
||||
}
|
||||
const LocaleContext = React.createContext(null);
|
||||
|
||||
translate(key) {
|
||||
function LocaleProvider(props) {
|
||||
|
||||
if (this.currentLocale) {
|
||||
if (this.entries.hasOwnProperty(this.currentLocale)) {
|
||||
const [entries, setEntries] = useState(window.languageEntries || {});
|
||||
const [currentLocale, setCurrentLocale] = useState(window.languageCode || "en_US");
|
||||
|
||||
const translate = useCallback((key) => {
|
||||
if (currentLocale) {
|
||||
if (entries.hasOwnProperty(currentLocale)) {
|
||||
let [module, variable] = key.split(".");
|
||||
if (module && variable && this.entries[this.currentLocale].hasOwnProperty(module)) {
|
||||
let translation = this.entries[this.currentLocale][module][variable];
|
||||
if (module && variable && entries[currentLocale].hasOwnProperty(module)) {
|
||||
let translation = entries[currentLocale][module][variable];
|
||||
if (translation) {
|
||||
return translation;
|
||||
}
|
||||
@ -21,47 +22,112 @@ class Locale {
|
||||
}
|
||||
|
||||
return "[" + key + "]";
|
||||
}
|
||||
}, [currentLocale, entries]);
|
||||
|
||||
setLocale(code) {
|
||||
this.currentLocale = code;
|
||||
if (!this.entries.hasOwnProperty(code)) {
|
||||
this.entries[code] = {};
|
||||
const loadModule = useCallback((code, module, newEntries) => {
|
||||
let _entries = {...entries};
|
||||
if (!_entries.hasOwnProperty(code)) {
|
||||
_entries[code] = {};
|
||||
}
|
||||
}
|
||||
|
||||
loadModule(code, module, newEntries) {
|
||||
if (!this.entries.hasOwnProperty(code)) {
|
||||
this.entries[code] = {};
|
||||
}
|
||||
if (this.entries[code].hasOwnProperty(module)) {
|
||||
this.entries[code][module] = {...this.entries[code][module], ...newEntries};
|
||||
if (_entries[code].hasOwnProperty(module)) {
|
||||
_entries[code][module] = {..._entries[code][module], ...newEntries};
|
||||
} else {
|
||||
this.entries[code][module] = newEntries;
|
||||
}
|
||||
_entries[code][module] = newEntries;
|
||||
}
|
||||
setEntries(_entries);
|
||||
}, [entries]);
|
||||
|
||||
hasModule(code, module) {
|
||||
return this.entries.hasOwnProperty(code) && !!this.entries[code][module];
|
||||
}
|
||||
const loadModules = useCallback((code, modules) => {
|
||||
setEntries({...entries, [code]: { ...entries[code], ...modules }});
|
||||
}, [entries]);
|
||||
|
||||
getModule(code, module) {
|
||||
if (this.hasModule(code, module)) {
|
||||
return this.entries[code][module];
|
||||
const hasModule = useCallback((code, module) => {
|
||||
return entries.hasOwnProperty(code) && !!entries[code][module];
|
||||
}, [entries]);
|
||||
|
||||
const getModule = useCallback((code, module) => {
|
||||
if (hasModule(code, module)) {
|
||||
return entries[code][module];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [entries]);
|
||||
|
||||
/** API HOOKS **/
|
||||
const setLanguage = useCallback(async (api, params) => {
|
||||
let res = await api.setLanguage(params);
|
||||
if (res.success) {
|
||||
setCurrentLocale(res.language.code)
|
||||
}
|
||||
return res;
|
||||
}, []);
|
||||
|
||||
const setLanguageByName = useCallback((api, name) => {
|
||||
return setLanguage(api, {name: name});
|
||||
}, [setLanguage]);
|
||||
|
||||
const setLanguageByCode = useCallback((api, code) => {
|
||||
return setLanguage(api, {code: code});
|
||||
}, [setLanguage]);
|
||||
|
||||
const requestModules = useCallback(async (api, modules, code=null, useCache=true) => {
|
||||
if (!Array.isArray(modules)) {
|
||||
modules = [modules];
|
||||
}
|
||||
|
||||
static getInstance() {
|
||||
return INSTANCE;
|
||||
if (code === null) {
|
||||
code = currentLocale;
|
||||
if (code === null && api.loggedIn) {
|
||||
code = api.user.language.code;
|
||||
}
|
||||
}
|
||||
|
||||
if (code === null) {
|
||||
return { success: false, msg: "No locale selected currently" };
|
||||
}
|
||||
|
||||
let languageEntries = {};
|
||||
if (useCache) {
|
||||
// remove cached modules from request array
|
||||
for (const module of [...modules]) {
|
||||
let moduleEntries = getModule(code, module);
|
||||
if (moduleEntries) {
|
||||
modules.splice(modules.indexOf(module), 1);
|
||||
languageEntries = {...languageEntries, [module]: moduleEntries};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modules.length > 0) {
|
||||
let data = await api.apiCall("language/getEntries", { code: code, modules: modules });
|
||||
|
||||
if (useCache) {
|
||||
if (data && data.success) {
|
||||
// insert into cache
|
||||
loadModules(code, data.entries);
|
||||
data.entries = {...data.entries, ...languageEntries};
|
||||
data.cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} else {
|
||||
return { success: true, msg: "", entries: languageEntries, code: code, cached: true };
|
||||
}
|
||||
}, [currentLocale, getModule, loadModules]);
|
||||
|
||||
const ctx = {
|
||||
currentLocale: currentLocale,
|
||||
translate: translate,
|
||||
requestModules: requestModules,
|
||||
setLanguageByCode: setLanguageByCode,
|
||||
};
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={ctx}>
|
||||
{props.children}
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
let INSTANCE = new Locale();
|
||||
|
||||
function L(key) {
|
||||
return Locale.getInstance().translate(key);
|
||||
}
|
||||
|
||||
export { L, Locale };
|
||||
export {LocaleContext, LocaleProvider};
|
Loading…
Reference in New Issue
Block a user