diff --git a/frontend/package.json b/frontend/package.json
index 3336077c..07853ca3 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -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": [
diff --git a/frontend/public/index.html b/frontend/public/index.html
index cd475284..3199d60e 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -55,6 +55,10 @@
rel="stylesheet"
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler.min.css"
/>
+
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index f8dcf294..c05c53cd 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -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 (
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/frontend/src/components/Dropdown/DropdownItem.tsx b/frontend/src/components/Dropdown/DropdownItem.tsx
index 8ba5a479..db29acae 100644
--- a/frontend/src/components/Dropdown/DropdownItem.tsx
+++ b/frontend/src/components/Dropdown/DropdownItem.tsx
@@ -45,6 +45,7 @@ export const DropdownItem: React.FC = ({
icon,
href,
onClick,
+ ...rest
}) => {
return divider ? (
@@ -57,7 +58,8 @@ export const DropdownItem: React.FC = ({
className,
)}
href={href}
- onClick={onClick}>
+ onClick={onClick}
+ {...rest}>
{icon && {icon}}
{children}
diff --git a/frontend/src/components/Flag/Flag.tsx b/frontend/src/components/Flag/Flag.tsx
new file mode 100644
index 00000000..a52db3d8
--- /dev/null
+++ b/frontend/src/components/Flag/Flag.tsx
@@ -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 = ({ className, country, size }) => {
+ const classes = [
+ `flag-country-${country.toLowerCase()}`,
+ {
+ [`flag-${size}`]: !!size,
+ },
+ ];
+
+ return ;
+};
diff --git a/frontend/src/components/Flag/index.ts b/frontend/src/components/Flag/index.ts
new file mode 100644
index 00000000..62845cc1
--- /dev/null
+++ b/frontend/src/components/Flag/index.ts
@@ -0,0 +1 @@
+export * from "./Flag";
diff --git a/frontend/src/components/LocalePicker.tsx b/frontend/src/components/LocalePicker.tsx
new file mode 100644
index 00000000..30d44d54
--- /dev/null
+++ b/frontend/src/components/LocalePicker.tsx
@@ -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 = ({
+ 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 (
+
+
+
+ {options.map((item) => {
+ return (
+ }
+ onClick={handleOnChange}>
+ {intl.formatMessage({
+ id: `locale-${item[1]}`,
+ defaultMessage: item[1],
+ })}
+
+ );
+ })}
+
+
+ );
+};
diff --git a/frontend/src/components/SiteWrapper.tsx b/frontend/src/components/SiteWrapper.tsx
index b1baf755..0132cb0f 100644
--- a/frontend/src/components/SiteWrapper.tsx
+++ b/frontend/src/components/SiteWrapper.tsx
@@ -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={[]}
profileItems={[
-
+ {intl.formatMessage({
+ id: "profile.title",
+ defaultMessage: "Profile settings",
+ })}
,
,
-
+ {intl.formatMessage({
+ id: "profile.logout",
+ defaultMessage: "Logout",
+ })}
,
]}
/>
diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts
index 0aae858f..0afd41e0 100644
--- a/frontend/src/components/index.ts
+++ b/frontend/src/components/index.ts
@@ -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";
diff --git a/frontend/src/context/LocaleContext.tsx b/frontend/src/context/LocaleContext.tsx
new file mode 100644
index 00000000..f23f0785
--- /dev/null
+++ b/frontend/src/context/LocaleContext.tsx
@@ -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(
+ 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 (
+ {children}
+ );
+}
+
+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;
diff --git a/frontend/src/context/index.ts b/frontend/src/context/index.ts
index bc8b3f26..5e327e35 100644
--- a/frontend/src/context/index.ts
+++ b/frontend/src/context/index.ts
@@ -1,3 +1,4 @@
export * from "./AuthContext";
export * from "./HealthContext";
+export * from "./LocaleContext";
export * from "./UserContext";
diff --git a/frontend/src/locale/IntlProvider.tsx b/frontend/src/locale/IntlProvider.tsx
index db422e7b..3c721c3b 100644
--- a/frontend/src/locale/IntlProvider.tsx
+++ b/frontend/src/locale/IntlProvider.tsx
@@ -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;
+};
diff --git a/frontend/src/locale/src/de.json b/frontend/src/locale/src/de.json
new file mode 100644
index 00000000..c0f410af
--- /dev/null
+++ b/frontend/src/locale/src/de.json
@@ -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"
+ }
+}
diff --git a/frontend/src/locale/src/en.json b/frontend/src/locale/src/en.json
index 1bcbfac8..979aadd5 100644
--- a/frontend/src/locale/src/en.json
+++ b/frontend/src/locale/src/en.json
@@ -1,80 +1,89 @@
{
- "accesslists.title": {
- "defaultMessage": "Access Lists"
- },
- "auditlog.title": {
- "defaultMessage": "Audit Log"
- },
- "certificates.title": {
- "defaultMessage": "Certificates"
- },
- "column.description": {
- "defaultMessage": "Description"
- },
- "column.id": {
- "defaultMessage": "ID"
- },
- "column.name": {
- "defaultMessage": "Name"
- },
- "dashboard.title": {
- "defaultMessage": "Dashboard"
- },
- "footer.changelog": {
- "defaultMessage": "Change Log"
- },
- "footer.copyright": {
- "defaultMessage": "Copyright © {year} jc21.com."
- },
- "footer.github": {
- "defaultMessage": "Github"
- },
- "footer.theme": {
- "defaultMessage": "Theme by Tabler"
- },
- "footer.userguide": {
- "defaultMessage": "User Guide"
- },
- "hosts.title": {
- "defaultMessage": "Hosts"
- },
- "login.login": {
- "defaultMessage": "Sign in"
- },
- "profile.logout": {
- "defaultMessage": "Logout"
- },
- "profile.title": {
- "defaultMessage": "Profile settings"
- },
- "settings.title": {
- "defaultMessage": "Settings"
- },
- "setup.create": {
- "defaultMessage": "Create Account"
- },
- "setup.title": {
- "defaultMessage": "Create your first Account"
- },
- "user.email": {
- "defaultMessage": "Email"
- },
- "user.name": {
- "defaultMessage": "Name"
- },
- "user.nickname": {
- "defaultMessage": "Nickname"
- },
- "user.password": {
- "defaultMessage": "Password"
- },
- "users.admin": {
- "defaultMessage": "Administrator"
- },
- "users.standard": {
- "defaultMessage": "Standard User"
- },
- "users.title": {
- "defaultMessage": "Users"
- }
+ "accesslists.title": {
+ "defaultMessage": "Access Lists"
+ },
+ "auditlog.title": {
+ "defaultMessage": "Audit Log"
+ },
+ "certificates.title": {
+ "defaultMessage": "Certificates"
+ },
+ "column.description": {
+ "defaultMessage": "Description"
+ },
+ "column.id": {
+ "defaultMessage": "ID"
+ },
+ "column.name": {
+ "defaultMessage": "Name"
+ },
+ "dashboard.title": {
+ "defaultMessage": "Dashboard"
+ },
+ "footer.changelog": {
+ "defaultMessage": "Change Log"
+ },
+ "footer.copyright": {
+ "defaultMessage": "Copyright © {year} jc21.com."
+ },
+ "footer.github": {
+ "defaultMessage": "Github"
+ },
+ "footer.theme": {
+ "defaultMessage": "Theme by Tabler"
+ },
+ "footer.userguide": {
+ "defaultMessage": "User Guide"
+ },
+ "hosts.title": {
+ "defaultMessage": "Hosts"
+ },
+ "locale-de-DE": {
+ "defaultMessage": "Deutsche"
+ },
+ "locale-en-US": {
+ "defaultMessage": "English"
+ },
+ "locale-fa-IR": {
+ "defaultMessage": "Persian"
+ },
+ "login.login": {
+ "defaultMessage": "Sign in"
+ },
+ "profile.logout": {
+ "defaultMessage": "Logout"
+ },
+ "profile.title": {
+ "defaultMessage": "Profile settings"
+ },
+ "settings.title": {
+ "defaultMessage": "Settings"
+ },
+ "setup.create": {
+ "defaultMessage": "Sign up"
+ },
+ "setup.title": {
+ "defaultMessage": "Create your first Account"
+ },
+ "user.email": {
+ "defaultMessage": "Email"
+ },
+ "user.name": {
+ "defaultMessage": "Name"
+ },
+ "user.nickname": {
+ "defaultMessage": "Nickname"
+ },
+ "user.password": {
+ "defaultMessage": "Password"
+ },
+ "users.admin": {
+ "defaultMessage": "Administrator"
+ },
+ "users.standard": {
+ "defaultMessage": "Standard User"
+ },
+ "users.title": {
+ "defaultMessage": "Users"
+ }
}
diff --git a/frontend/src/locale/src/fa.json b/frontend/src/locale/src/fa.json
new file mode 100644
index 00000000..f912b8d8
--- /dev/null
+++ b/frontend/src/locale/src/fa.json
@@ -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": "کاربران"
+ }
+}
diff --git a/frontend/src/pages/Setup/index.tsx b/frontend/src/pages/Setup/index.tsx
index 41907591..a93dfecf 100644
--- a/frontend/src/pages/Setup/index.tsx
+++ b/frontend/src/pages/Setup/index.tsx
@@ -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}>
-
-
-
+
+
+
+ {intl.formatMessage({
+ id: "setup.title",
+ defaultMessage: "Create your first Account",
+ })}
+
+
+
+ {
+ setRenderCount(renderCount + 1);
+ }}
+ />
+
+
+
{errorMessage ? (
{errorMessage}
@@ -96,7 +109,10 @@ function Setup() {