Fixes to lang selection now apply immediately

Moved lang list to separate file
Cleaned up some lang workarounds
This commit is contained in:
Jamie Curnow
2021-07-27 11:06:31 +10:00
parent d2554c6cb2
commit ae00ab09e4
18 changed files with 137 additions and 117 deletions

View File

@@ -2,7 +2,7 @@ import React, { ReactNode } from "react";
import cn from "classnames"; import cn from "classnames";
export interface ButtonProps { export interface ButtonProps extends React.ButtonHTMLAttributes<any> {
/** /**
* Child elements within * Child elements within
*/ */
@@ -60,6 +60,7 @@ export const Button: React.FC<ButtonProps> = ({
href, href,
target, target,
onClick, onClick,
...rest
}) => { }) => {
const classes = [ const classes = [
"btn", "btn",
@@ -99,7 +100,8 @@ export const Button: React.FC<ButtonProps> = ({
<button <button
className={cn(classes, className)} className={cn(classes, className)}
aria-label="Button" aria-label="Button"
onClick={onClick}> onClick={onClick}
{...rest}>
{children} {children}
</button> </button>
); );

View File

@@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import { useHealthState } from "context"; import { useHealthState } from "context";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const FixedFooterWrapper = styled.div` const FixedFooterWrapper = styled.div`
@@ -29,10 +29,10 @@ function Footer({ fixed }: Props) {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="link-secondary"> className="link-secondary">
<FormattedMessage {intl.formatMessage({
id="footer.userguide" id: "footer.userguide",
defaultMessage="User Guide" defaultMessage: "User Guide",
/> })}
</a> </a>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@@ -41,10 +41,10 @@ function Footer({ fixed }: Props) {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="link-secondary"> className="link-secondary">
<FormattedMessage {intl.formatMessage({
id="footer.changelog" id: "footer.changelog",
defaultMessage="Change Log" defaultMessage: "Change Log",
/> })}
</a> </a>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">
@@ -53,10 +53,10 @@ function Footer({ fixed }: Props) {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="link-secondary"> className="link-secondary">
<FormattedMessage {intl.formatMessage({
id="footer.github" id: "footer.github",
defaultMessage="Github" defaultMessage: "Github",
/> })}
</a> </a>
</li> </li>
</ul> </ul>
@@ -64,20 +64,22 @@ function Footer({ fixed }: Props) {
<div className="col-12 col-lg-auto mt-3 mt-lg-0"> <div className="col-12 col-lg-auto mt-3 mt-lg-0">
<ul className="list-inline list-inline-dots mb-0"> <ul className="list-inline list-inline-dots mb-0">
<li className="list-inline-item"> <li className="list-inline-item">
<FormattedMessage {intl.formatMessage(
id="footer.copyright" {
defaultMessage="Copyright © {year} jc21.com." id: "footer.copyright",
values={{ year: new Date().getFullYear() }} defaultMessage: "Copyright © {year} jc21.com",
/>{" "} },
{ year: new Date().getFullYear() },
)}{" "}
<a <a
className="link-secondary" className="link-secondary"
href="https://preview.tabler.io/" href="https://preview.tabler.io/"
target="_blank" target="_blank"
rel="noreferrer"> rel="noreferrer">
<FormattedMessage {intl.formatMessage({
id="footer.theme" id: "footer.theme",
defaultMessage="Theme by Tabler" defaultMessage: "Theme by Tabler",
/> })}
</a> </a>
</li> </li>
<li className="list-inline-item"> <li className="list-inline-item">

View File

@@ -1,8 +1,8 @@
import React, { useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { Button, Dropdown, Flag } from "components"; import { Button, Dropdown, Flag } from "components";
import { useLocaleState } from "context"; import { useLocaleState } from "context";
import { changeLocale, getFlagCodeForLocale, getLocale, intl } from "locale"; import { changeLocale, getFlagCodeForLocale, intl } from "locale";
export interface LocalPickerProps { export interface LocalPickerProps {
/** /**
@@ -15,11 +15,16 @@ export const LocalePicker: React.FC<LocalPickerProps> = ({
onChange, onChange,
...rest ...rest
}) => { }) => {
const dropRef = useRef(null);
const { locale, setLocale } = useLocaleState(); const { locale, setLocale } = useLocaleState();
// const [locale, setLocale] = useState(getLocale());
const [localeShown, setLocaleShown] = useState(false); const [localeShown, setLocaleShown] = useState(false);
const options = [
["us", "en-US"],
["de", "de-DE"],
["ir", "fa-IR"],
];
const handleOnChange = (e: any) => { const handleOnChange = (e: any) => {
changeLocale(e.currentTarget.rel); changeLocale(e.currentTarget.rel);
setLocale(e.currentTarget.rel); setLocale(e.currentTarget.rel);
@@ -27,15 +32,25 @@ export const LocalePicker: React.FC<LocalPickerProps> = ({
onChange && onChange(locale); onChange && onChange(locale);
}; };
const options = [ const handleClickOutside = (event: any) => {
["us", "en-US"], if (
["de", "de-DE"], dropRef.current &&
["ir", "fa-IR"], // @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
]; !dropRef.current.contains(event.target)
) {
setLocaleShown(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return ( return (
<div className="dropdown" {...rest}> <div className="dropdown" {...rest} ref={dropRef}>
<Button <Button
type="button"
shape="ghost" shape="ghost"
onClick={(e: any) => { onClick={(e: any) => {
setLocaleShown(!localeShown); setLocaleShown(!localeShown);

View File

@@ -1,7 +1,12 @@
import React, { lazy, Suspense } from "react"; import React, { lazy, Suspense } from "react";
import { SiteWrapper, SuspenseLoader } from "components"; import { SiteWrapper, SuspenseLoader } from "components";
import { useAuthState, useHealthState, UserProvider } from "context"; import {
useAuthState,
useLocaleState,
useHealthState,
UserProvider,
} from "context";
import { BrowserRouter, Switch, Route } from "react-router-dom"; import { BrowserRouter, Switch, Route } from "react-router-dom";
const AccessLists = lazy(() => import("pages/AccessLists")); const AccessLists = lazy(() => import("pages/AccessLists"));
@@ -17,6 +22,7 @@ const Users = lazy(() => import("pages/Users"));
function Router() { function Router() {
const { health } = useHealthState(); const { health } = useHealthState();
const { authenticated } = useAuthState(); const { authenticated } = useAuthState();
const { locale } = useLocaleState();
const Spinner = <SuspenseLoader />; const Spinner = <SuspenseLoader />;
if (health.loading) { if (health.loading) {
@@ -42,7 +48,7 @@ function Router() {
return ( return (
<UserProvider> <UserProvider>
<BrowserRouter> <BrowserRouter>
<SiteWrapper> <SiteWrapper key={`locale-${locale}`}>
<Suspense fallback={Spinner}> <Suspense fallback={Spinner}>
<Switch> <Switch>
<Route path="/hosts"> <Route path="/hosts">

View File

@@ -3,16 +3,17 @@ import { createIntl, createIntlCache } from "react-intl";
import langDe from "./lang/de.json"; import langDe from "./lang/de.json";
import langEn from "./lang/en.json"; import langEn from "./lang/en.json";
import langFa from "./lang/fa.json"; import langFa from "./lang/fa.json";
import langList from "./lang/lang-list.json";
const loadMessages = (locale?: string) => { const loadMessages = (locale?: string) => {
locale = locale || "en"; locale = locale || "en";
switch (locale.substr(0, 2)) { switch (locale.substr(0, 2)) {
case "de": case "de":
return Object.assign({}, langEn, langDe); return Object.assign({}, langList, langEn, langDe);
case "fa": case "fa":
return Object.assign({}, langEn, langFa); return Object.assign({}, langList, langEn, langFa);
default: default:
return langEn; return Object.assign({}, langList, langEn);
} }
}; };

View File

@@ -11,42 +11,12 @@
"column.description": { "column.description": {
"defaultMessage": "Beschreibung" "defaultMessage": "Beschreibung"
}, },
"column.id": {
"defaultMessage": "ID"
},
"column.name": {
"defaultMessage": "Name"
},
"dashboard.title": {
"defaultMessage": "Dashboard"
},
"footer.changelog": { "footer.changelog": {
"defaultMessage": "Änderungen" "defaultMessage": "Änderungen"
}, },
"footer.copyright": {
"defaultMessage": "Copyright © {year} jc21.com."
},
"footer.github": {
"defaultMessage": "Github"
},
"footer.theme": {
"defaultMessage": "Theme by Tabler"
},
"footer.userguide": { "footer.userguide": {
"defaultMessage": "Handbuch" "defaultMessage": "Handbuch"
}, },
"hosts.title": {
"defaultMessage": "Hosts"
},
"locale-de-DE": {
"defaultMessage": "Deutsch"
},
"locale-en-US": {
"defaultMessage": "Englisch"
},
"locale-fa-IR": {
"defaultMessage": "Persisch"
},
"login.login": { "login.login": {
"defaultMessage": "Einloggen" "defaultMessage": "Einloggen"
}, },
@@ -68,18 +38,12 @@
"user.email": { "user.email": {
"defaultMessage": "E-Mail" "defaultMessage": "E-Mail"
}, },
"user.name": {
"defaultMessage": "Name"
},
"user.nickname": { "user.nickname": {
"defaultMessage": "Benutzername" "defaultMessage": "Benutzername"
}, },
"user.password": { "user.password": {
"defaultMessage": "Passwort" "defaultMessage": "Passwort"
}, },
"users.admin": {
"defaultMessage": "Administrator"
},
"users.standard": { "users.standard": {
"defaultMessage": "Normaler Benutzer" "defaultMessage": "Normaler Benutzer"
}, },

View File

@@ -38,15 +38,6 @@
"hosts.title": { "hosts.title": {
"defaultMessage": "Hosts" "defaultMessage": "Hosts"
}, },
"locale-de-DE": {
"defaultMessage": "Deutsche"
},
"locale-en-US": {
"defaultMessage": "English"
},
"locale-fa-IR": {
"defaultMessage": "Persian"
},
"login.login": { "login.login": {
"defaultMessage": "Sign in" "defaultMessage": "Sign in"
}, },

View File

@@ -0,0 +1,11 @@
{
"locale-de-DE": {
"defaultMessage": "Deutsch"
},
"locale-en-US": {
"defaultMessage": "English"
},
"locale-fa-IR": {
"defaultMessage": "فارسی"
}
}

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const Root = styled.div` const Root = styled.div`
@@ -15,10 +15,10 @@ function AccessLists() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage {intl.formatMessage({
id="accesslists.title" id: "accesslists.title",
defaultMessage="Access Lists" defaultMessage: "Access Lists",
/> })}
</h3> </h3>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const Root = styled.div` const Root = styled.div`
@@ -15,7 +15,10 @@ function AuditLog() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage id="auditlog.title" defaultMessage="Audit Log" /> {intl.formatMessage({
id: "auditlog.title",
defaultMessage: "Audit Log",
})}
</h3> </h3>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const Root = styled.div` const Root = styled.div`
@@ -15,10 +15,10 @@ function Certificates() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage {intl.formatMessage({
id="certificates.title" id: "certificates.title",
defaultMessage="Certificates" defaultMessage: "Certificates",
/> })}
</h3> </h3>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const Root = styled.div` const Root = styled.div`
@@ -15,7 +15,10 @@ function Dashboard() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage id="dashboard.title" defaultMessage="Dashboard" /> {intl.formatMessage({
id: "dashboard.title",
defaultMessage: "Dashboard",
})}
</h3> </h3>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const Root = styled.div` const Root = styled.div`
@@ -15,7 +15,10 @@ function Hosts() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage id="hosts.title" defaultMessage="Hosts" /> {intl.formatMessage({
id: "hosts.title",
defaultMessage: "Hosts",
})}
</h3> </h3>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,9 @@
import React, { useEffect, useRef, useState, ChangeEvent } from "react"; import React, { useEffect, useRef, useState, ChangeEvent } from "react";
import { Alert, Button } from "components"; import { Alert, Button } from "components";
import { LocalePicker } from "components";
import { useAuthState } from "context"; import { useAuthState } from "context";
import { intl } from "locale"; import { intl } from "locale";
import { FormattedMessage } from "react-intl";
import logo from "../../img/logo-text-vertical-grey.png"; import logo from "../../img/logo-text-vertical-grey.png";
@@ -50,10 +50,19 @@ function Login() {
autoComplete="off" autoComplete="off"
onSubmit={onSubmit}> onSubmit={onSubmit}>
<div className="card-body"> <div className="card-body">
<div className="row mb-4">
<div className="col" />
<div className="col col-md-2">
<LocalePicker />
</div>
</div>
{errorMessage ? <Alert type="danger">{errorMessage}</Alert> : null} {errorMessage ? <Alert type="danger">{errorMessage}</Alert> : null}
<div className="mb-3"> <div className="mb-3">
<label className="form-label"> <label className="form-label">
<FormattedMessage id="user.email" defaultMessage="Email" /> {intl.formatMessage({
id: "user.email",
defaultMessage: "Email",
})}
</label> </label>
<input <input
ref={emailRef} ref={emailRef}
@@ -73,7 +82,10 @@ function Login() {
</div> </div>
<div className="mb-2"> <div className="mb-2">
<label className="form-label"> <label className="form-label">
<FormattedMessage id="user.password" defaultMessage="Password" /> {intl.formatMessage({
id: "user.password",
defaultMessage: "Password",
})}
</label> </label>
<div className="input-group input-group-flat"> <div className="input-group input-group-flat">
<input <input
@@ -95,8 +107,15 @@ function Login() {
</div> </div>
</div> </div>
<div className="form-footer"> <div className="form-footer">
<Button color="cyan" loading={loading} className="w-100"> <Button
<FormattedMessage id="login.login" defaultMessage="Sign in" /> color="cyan"
loading={loading}
className="w-100"
type="submit">
{intl.formatMessage({
id: "login.login",
defaultMessage: "Sign in",
})}
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -4,7 +4,6 @@ import { SettingsResponse, requestSettings } from "api/npm";
import { Table } from "components"; import { Table } from "components";
import { SuspenseLoader } from "components"; import { SuspenseLoader } from "components";
import { intl } from "locale"; import { intl } from "locale";
import { FormattedMessage } from "react-intl";
import { useInterval } from "rooks"; import { useInterval } from "rooks";
import styled from "styled-components"; import styled from "styled-components";
@@ -59,7 +58,10 @@ function Settings() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage id="settings.title" defaultMessage="Settings" /> {intl.formatMessage({
id: "settings.title",
defaultMessage: "Settings",
})}
</h3> </h3>
</div> </div>
<Table <Table

View File

@@ -13,7 +13,6 @@ function Setup() {
const { refreshHealth } = useHealthState(); const { refreshHealth } = useHealthState();
const { login } = useAuthState(); const { login } = useAuthState();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [renderCount, setRenderCount] = useState(0);
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@@ -93,11 +92,7 @@ function Setup() {
</h2> </h2>
</div> </div>
<div className="col col-md-2"> <div className="col col-md-2">
<LocalePicker <LocalePicker />
onChange={() => {
setRenderCount(renderCount + 1);
}}
/>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl"; import { intl } from "locale";
import styled from "styled-components"; import styled from "styled-components";
const Root = styled.div` const Root = styled.div`
@@ -15,7 +15,10 @@ function Users() {
<div className="card-status-top bg-cyan" /> <div className="card-status-top bg-cyan" />
<div className="card-header"> <div className="card-header">
<h3 className="card-title"> <h3 className="card-title">
<FormattedMessage id="users.title" defaultMessage="Users" /> {intl.formatMessage({
id: "users.title",
defaultMessage: "Users",
})}
</h3> </h3>
</div> </div>
</div> </div>

View File

@@ -13,7 +13,7 @@ describe('UI Setup and Login', () => {
cy.get('input[name="nickname"]').type('Cypress'); cy.get('input[name="nickname"]').type('Cypress');
cy.get('input[name="email"]').type('cypress@example.com'); cy.get('input[name="email"]').type('cypress@example.com');
cy.get('input[name="password"]').type('changeme'); cy.get('input[name="password"]').type('changeme');
cy.get('form button:first').click(); cy.get('form button:last').click();
cy.get('.navbar-nav .avatar').should('be.visible'); cy.get('.navbar-nav .avatar').should('be.visible');
// logout: // logout:
cy.clearLocalStorage(); cy.clearLocalStorage();
@@ -23,7 +23,7 @@ describe('UI Setup and Login', () => {
cy.visit('/'); cy.visit('/');
cy.get('input[name="email"]').type('cypress@example.com'); cy.get('input[name="email"]').type('cypress@example.com');
cy.get('input[name="password"]').type('changeme'); cy.get('input[name="password"]').type('changeme');
cy.get('form button:first').click(); cy.get('form button:last').click();
cy.get('.navbar-nav .avatar').should('be.visible'); cy.get('.navbar-nav .avatar').should('be.visible');
// logout: // logout:
cy.clearLocalStorage(); cy.clearLocalStorage();