Added more languages and picker

This commit is contained in:
Jamie Curnow
2021-07-26 19:09:37 +10:00
parent 2326a95d2a
commit f6220887f9
16 changed files with 432 additions and 125 deletions

View File

@@ -63,7 +63,7 @@
"format": "yarn prettier -- --write",
"lint:fix": "eslint --fix --ext .ts --ext .tsx .",
"locale-extract": "formatjs extract 'src/**/*.tsx' --out-file src/locale/src/en.json",
"locale-compile": "formatjs compile-folder src/locale/src src/locale/lang --ast"
"locale-compile": "formatjs compile-folder src/locale/src src/locale/lang"
},
"browserslist": {
"production": [

View File

@@ -55,6 +55,10 @@
rel="stylesheet"
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler.min.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler-flags.min.css"
/>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

View File

@@ -1,18 +1,20 @@
import React from "react";
import Router from "components/Router";
import { AuthProvider, HealthProvider } from "context";
import { AuthProvider, HealthProvider, LocaleProvider } from "context";
import { intl } from "locale";
import { RawIntlProvider } from "react-intl";
function App() {
return (
<RawIntlProvider value={intl}>
<LocaleProvider>
<HealthProvider>
<AuthProvider>
<Router />
</AuthProvider>
</HealthProvider>
</LocaleProvider>
</RawIntlProvider>
);
}

View File

@@ -45,6 +45,7 @@ export const DropdownItem: React.FC<DropdownItemProps> = ({
icon,
href,
onClick,
...rest
}) => {
return divider ? (
<div className={cn("dropdown-divider", className)} />
@@ -57,7 +58,8 @@ export const DropdownItem: React.FC<DropdownItemProps> = ({
className,
)}
href={href}
onClick={onClick}>
onClick={onClick}
{...rest}>
{icon && <span className="dropdown-item-icon">{icon}</span>}
{children}
</a>

View File

@@ -0,0 +1,28 @@
import React from "react";
import cn from "classnames";
export interface FlagProps {
/**
* Additional Class
*/
className?: string;
/**
* Country code of flag
*/
country: string;
/**
* Size of the flag
*/
size?: string;
}
export const Flag: React.FC<FlagProps> = ({ className, country, size }) => {
const classes = [
`flag-country-${country.toLowerCase()}`,
{
[`flag-${size}`]: !!size,
},
];
return <span className={cn("flag", classes, className)} />;
};

View File

@@ -0,0 +1 @@
export * from "./Flag";

View File

@@ -0,0 +1,67 @@
import React, { useState } from "react";
import { Button, Dropdown, Flag } from "components";
import { useLocaleState } from "context";
import { changeLocale, getFlagCodeForLocale, getLocale, intl } from "locale";
export interface LocalPickerProps {
/**
* On click handler
*/
onChange?: any;
}
export const LocalePicker: React.FC<LocalPickerProps> = ({
onChange,
...rest
}) => {
const { locale, setLocale } = useLocaleState();
// const [locale, setLocale] = useState(getLocale());
const [localeShown, setLocaleShown] = useState(false);
const handleOnChange = (e: any) => {
changeLocale(e.currentTarget.rel);
setLocale(e.currentTarget.rel);
setLocaleShown(false);
onChange && onChange(locale);
};
const options = [
["us", "en-US"],
["de", "de-DE"],
["ir", "fa-IR"],
];
return (
<div className="dropdown" {...rest}>
<Button
shape="ghost"
onClick={(e: any) => {
setLocaleShown(!localeShown);
e.preventDefault();
}}
iconOnly>
<Flag country={getFlagCodeForLocale(locale)} />
</Button>
<Dropdown
className="dropdown-menu-end dropdown-menu-card"
show={localeShown}>
{options.map((item) => {
return (
<Dropdown.Item
key={`locale-${item[0]}`}
rel={item[1]}
icon={<Flag country={item[0]} />}
onClick={handleOnChange}>
{intl.formatMessage({
id: `locale-${item[1]}`,
defaultMessage: item[1],
})}
</Dropdown.Item>
);
})}
</Dropdown>
</div>
);
};

View File

@@ -2,9 +2,9 @@ import React, { ReactNode } from "react";
import { Footer } from "components";
import { Avatar, Dropdown, Navigation } from "components";
import { LocalePicker } from "components";
import { useAuthState, useUserState } from "context";
import { intl } from "locale";
import { FormattedMessage } from "react-intl";
import styled from "styled-components";
import { NavMenu } from "./NavMenu";
@@ -45,16 +45,20 @@ function SiteWrapper({ children }: Props) {
defaultMessage: "Standard User",
})
}
buttons={[<LocalePicker key="lp1" />]}
profileItems={[
<Dropdown.Item key="m1-2">
<FormattedMessage
id="profile.title"
defaultMessage="Profile settings"
/>
{intl.formatMessage({
id: "profile.title",
defaultMessage: "Profile settings",
})}
</Dropdown.Item>,
<Dropdown.Item divider key="m1-4" />,
<Dropdown.Item key="m1-6" onClick={logout}>
<FormattedMessage id="profile.logout" defaultMessage="Logout" />
{intl.formatMessage({
id: "profile.logout",
defaultMessage: "Logout",
})}
</Dropdown.Item>,
]}
/>

View File

@@ -5,9 +5,11 @@ export * from "./Badge";
export * from "./Button";
export * from "./ButtonList";
export * from "./Dropdown";
export * from "./Flag";
export * from "./Footer";
export * from "./Loader";
export * from "./Loading";
export * from "./LocalePicker";
export * from "./Navigation";
export * from "./NavMenu";
export * from "./Router";

View File

@@ -0,0 +1,43 @@
import React, { ReactNode, useState } from "react";
import { getLocale } from "locale";
// Context
export interface LocaleContextType {
setLocale: (locale: string) => void;
locale?: string;
}
const initalValue = null;
const LocaleContext = React.createContext<LocaleContextType | null>(
initalValue,
);
// Provider
interface Props {
children?: ReactNode;
}
function LocaleProvider({ children }: Props) {
const [locale, setLocaleValue] = useState(getLocale());
const setLocale = async (locale: string) => {
setLocaleValue(locale);
};
const value = { locale, setLocale };
return (
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
);
}
function useLocaleState() {
const context = React.useContext(LocaleContext);
if (!context) {
throw new Error(`useLocaleState must be used within a LocaleProvider`);
}
return context;
}
export { LocaleProvider, useLocaleState };
export default LocaleContext;

View File

@@ -1,3 +1,4 @@
export * from "./AuthContext";
export * from "./HealthContext";
export * from "./LocaleContext";
export * from "./UserContext";

View File

@@ -1,25 +1,53 @@
import { createIntl, createIntlCache } from "react-intl";
import langDe from "./lang/de.json";
import langEn from "./lang/en.json";
import langFa from "./lang/fa.json";
const loadMessages = (locale: string) => {
switch (locale) {
/*
case 'fr':
return import("./lang/fr.json");
*/
const loadMessages = (locale?: string) => {
locale = locale || "en";
switch (locale.substr(0, 2)) {
case "de":
return Object.assign({}, langEn, langDe);
case "fa":
return Object.assign({}, langEn, langFa);
default:
return langEn;
}
};
export const initialLocale = "en-US";
export const cache = createIntlCache();
export const getFlagCodeForLocale = (locale?: string) => {
switch (locale) {
case "de-DE":
case "de":
return "de";
case "fa-IR":
case "fa":
return "ir";
default:
return "us";
}
};
const initialMessages = loadMessages(initialLocale);
export const getLocale = () => {
let loc = window.localStorage.getItem("locale");
if (!loc) {
loc = document.documentElement.lang;
}
return loc;
};
export const intl = createIntl(
// @ts-ignore messages file typings are correct
{ locale: initialLocale, messages: initialMessages },
const cache = createIntlCache();
const initialMessages = loadMessages(getLocale());
export let intl = createIntl(
{ locale: getLocale(), messages: initialMessages },
cache,
);
export const changeLocale = (locale: string): void => {
const messages = loadMessages(locale);
intl = createIntl({ locale, messages }, cache);
window.localStorage.setItem("locale", locale);
document.documentElement.lang = locale;
};

View File

@@ -0,0 +1,20 @@
{
"accesslists.title": {
"defaultMessage": "Zugriffslisten"
},
"auditlog.title": {
"defaultMessage": "Audit-Log"
},
"setup.create": {
"defaultMessage": "Benutzerkonto erstellen"
},
"setup.title": {
"defaultMessage": "Erstellen Sie Ihr erstes Konto"
},
"user.nickname": {
"defaultMessage": "Spitzname"
},
"user.password": {
"defaultMessage": "Passwort"
}
}

View File

@@ -38,6 +38,15 @@
"hosts.title": {
"defaultMessage": "Hosts"
},
"locale-de-DE": {
"defaultMessage": "Deutsche"
},
"locale-en-US": {
"defaultMessage": "English"
},
"locale-fa-IR": {
"defaultMessage": "Persian"
},
"login.login": {
"defaultMessage": "Sign in"
},
@@ -51,7 +60,7 @@
"defaultMessage": "Settings"
},
"setup.create": {
"defaultMessage": "Create Account"
"defaultMessage": "Sign up"
},
"setup.title": {
"defaultMessage": "Create your first Account"

View File

@@ -0,0 +1,77 @@
{
"accesslists.title": {
"defaultMessage": "دسترسی به لیست ها"
},
"auditlog.title": {
"defaultMessage": "گزارش حسابرسی"
},
"certificates.title": {
"defaultMessage": "گواهینامه ها"
},
"column.description": {
"defaultMessage": "شرح"
},
"column.id": {
"defaultMessage": "شناسه"
},
"column.name": {
"defaultMessage": "نام"
},
"dashboard.title": {
"defaultMessage": "داشبورد"
},
"footer.changelog": {
"defaultMessage": "ورود به سیستم را تغییر دهید"
},
"footer.copyright": {
"defaultMessage": "حق چاپ © حق چاپ © {year} jc21.com"
},
"footer.theme": {
"defaultMessage": "قالب توسط Tabler"
},
"footer.userguide": {
"defaultMessage": "راهنمای کاربر"
},
"hosts.title": {
"defaultMessage": "میزبان"
},
"login.login": {
"defaultMessage": "ورود"
},
"profile.logout": {
"defaultMessage": "خروج"
},
"profile.title": {
"defaultMessage": "تنظیمات نمایه"
},
"settings.title": {
"defaultMessage": "تنظیمات"
},
"setup.create": {
"defaultMessage": "ثبت نام"
},
"setup.title": {
"defaultMessage": "اولین حساب خود را ایجاد کنید"
},
"user.email": {
"defaultMessage": "پست الکترونیک"
},
"user.name": {
"defaultMessage": "نام"
},
"user.nickname": {
"defaultMessage": "کنیه"
},
"user.password": {
"defaultMessage": "کلمه عبور"
},
"users.admin": {
"defaultMessage": "مدیر"
},
"users.standard": {
"defaultMessage": "کاربر استاندارد"
},
"users.title": {
"defaultMessage": "کاربران"
}
}

View File

@@ -2,9 +2,9 @@ import React, { useEffect, useRef, useState, ChangeEvent } from "react";
import { createUser } from "api/npm";
import { Alert, Button } from "components";
import { LocalePicker } from "components";
import { useAuthState, useHealthState } from "context";
import { intl } from "locale";
import { FormattedMessage } from "react-intl";
import logo from "../../img/logo-text-vertical-grey.png";
@@ -13,6 +13,7 @@ function Setup() {
const { refreshHealth } = useHealthState();
const { login } = useAuthState();
const [loading, setLoading] = useState(false);
const [renderCount, setRenderCount] = useState(0);
const [errorMessage, setErrorMessage] = useState("");
const [formData, setFormData] = useState({
@@ -82,12 +83,24 @@ function Setup() {
autoComplete="off"
onSubmit={onSubmit}>
<div className="card-body">
<h2 className="card-title text-center mb-4">
<FormattedMessage
id="setup.title"
defaultMessage="Create your first Account"
/>
<div className="row mb-4">
<div className="col">
<h2 className="card-title">
{intl.formatMessage({
id: "setup.title",
defaultMessage: "Create your first Account",
})}
</h2>
</div>
<div className="col col-md-2">
<LocalePicker
onChange={() => {
setRenderCount(renderCount + 1);
}}
/>
</div>
</div>
{errorMessage ? (
<Alert type="danger" className="text-center">
{errorMessage}
@@ -96,7 +109,10 @@ function Setup() {
<div className="mb-3">
<label className="form-label">
<FormattedMessage id="user.name" defaultMessage="Name" />
{intl.formatMessage({
id: "user.name",
defaultMessage: "Name",
})}
</label>
<input
ref={nameRef}
@@ -114,10 +130,10 @@ function Setup() {
</div>
<div className="mb-3">
<label className="form-label">
<FormattedMessage
id="user.nickname"
defaultMessage="Nickname"
/>
{intl.formatMessage({
id: "user.nickname",
defaultMessage: "Nickname",
})}
</label>
<input
onChange={onChange}
@@ -134,7 +150,10 @@ function Setup() {
</div>
<div className="mb-3">
<label className="form-label">
<FormattedMessage id="user.email" defaultMessage="Email" />
{intl.formatMessage({
id: "user.email",
defaultMessage: "Email",
})}
</label>
<input
type="email"
@@ -153,10 +172,10 @@ function Setup() {
</div>
<div className="mb-3">
<label className="form-label">
<FormattedMessage
id="user.password"
defaultMessage="Password"
/>
{intl.formatMessage({
id: "user.password",
defaultMessage: "Password",
})}
</label>
<input
type="password"
@@ -177,10 +196,10 @@ function Setup() {
</div>
<div className="form-footer">
<Button color="cyan" loading={loading} className="w-100">
<FormattedMessage
id="setup.create"
defaultMessage="Create Account"
/>
{intl.formatMessage({
id: "setup.create",
defaultMessage: "Create Account",
})}
</Button>
</div>
</div>