Allow users to change language

This commit is contained in:
dominik-zeglen 2019-10-16 17:18:29 +02:00
parent cce276db3a
commit 54dad7cae5
18 changed files with 194 additions and 47 deletions

6
package-lock.json generated
View file

@ -4953,9 +4953,9 @@
}
},
"babel-plugin-react-intl-auto": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-intl-auto/-/babel-plugin-react-intl-auto-2.2.0.tgz",
"integrity": "sha512-L6IS4NQCr+uGw8yOJ+tBfm5R0UhrM2mZyhN+X7jCsnEhTcWopkWe7geLm7AzJC2SWFXnr7phwXlJbN4erwKRrA==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-intl-auto/-/babel-plugin-react-intl-auto-2.3.0.tgz",
"integrity": "sha512-y9QSRHkuN2eAPlvhvvJCbWjxeXhd0kbFLksLkMgaqX7VAiWON04rTSfq4PVbvgfvs5MHdLLbGrxBEWQ129QVIA==",
"dev": true,
"requires": {
"@babel/types": "^7.5.5",

View file

@ -104,7 +104,7 @@
"babel-jest": "^23.6.0",
"babel-loader": "^8.0.6",
"babel-plugin-react-intl": "^4.1.12",
"babel-plugin-react-intl-auto": "^2.2.0",
"babel-plugin-react-intl-auto": "^2.3.0",
"codecov": "^3.5.0",
"core-js": "^3.2.1",
"enzyme": "^3.10.0",

View file

@ -29,6 +29,7 @@ import useNavigator from "@saleor/hooks/useNavigator";
import useTheme from "@saleor/hooks/useTheme";
import useUser from "@saleor/hooks/useUser";
import ArrowDropdown from "@saleor/icons/ArrowDropdown";
import { staffMemberDetailsUrl } from "@saleor/staff/urls";
import Container from "../Container";
import AppActionContext from "./AppActionContext";
import AppHeaderContext from "./AppHeaderContext";
@ -287,10 +288,15 @@ const AppLayout = withStyles(styles, {
const menuStructure = createMenuStructure(intl);
const handleLogout = () => {
close();
setMenuState(false);
logout();
};
const handleViewerProfile = () => {
setMenuState(false);
navigate(staffMemberDetailsUrl(user.id));
};
const handleMenuItemClick = (
url: string,
event: React.MouseEvent<any>
@ -434,6 +440,15 @@ const AppLayout = withStyles(styles, {
mouseEvent="onClick"
>
<Menu>
<MenuItem
className={classes.userMenuItem}
onClick={handleViewerProfile}
>
<FormattedMessage
defaultMessage="Account Settings"
description="button"
/>
</MenuItem>
<MenuItem
className={classes.userMenuItem}
onClick={handleLogout}

View file

@ -20,7 +20,7 @@ export const Date: React.FC<DateProps> = ({ date, plain }) => {
return (
<LocaleConsumer>
{locale => (
{({ locale }) => (
<Consumer>
{currentDate =>
plain ? (

View file

@ -27,7 +27,7 @@ export const DateTime: React.StatelessComponent<DateTimeProps> = ({
<TimezoneConsumer>
{tz => (
<LocaleConsumer>
{locale => (
{({ locale }) => (
<Consumer>
{currentDate =>
plain ? (

View file

@ -43,15 +43,6 @@ import locale_VI from "@locale/vi.json";
import locale_ZH_HANS from "@locale/zh-Hans.json";
import locale_ZH_HANT from "@locale/zh-Hant.json";
const defaultLocale = "en";
export type LocaleContextType = string;
export const LocaleContext = React.createContext<LocaleContextType>(
defaultLocale
);
const { Consumer: LocaleConsumer, Provider: RawLocaleProvider } = LocaleContext;
export enum Locale {
AR = "ar",
AZ = "az",
@ -144,6 +135,51 @@ const localeData: Record<Locale, LocaleMessages> = {
[Locale.ZH_HANT]: locale_ZH_HANT
};
export const localeNames: Record<Locale, string> = {
[Locale.AR]: "العربيّة",
[Locale.AZ]: "Azərbaycanca",
[Locale.BG]: "български",
[Locale.BN]: "বাংলা",
[Locale.CA]: "català",
[Locale.CS]: "česky",
[Locale.DA]: "dansk",
[Locale.DE]: "Deutsch",
[Locale.EL]: "Ελληνικά",
[Locale.EN]: "English",
[Locale.ES]: "español",
[Locale.ES_CO]: "español de Colombia",
[Locale.ET]: "eesti",
[Locale.FA]: "فارسی",
[Locale.FR]: "français",
[Locale.HI]: "Hindi",
[Locale.HU]: "Magyar",
[Locale.HY]: "հայերեն",
[Locale.ID]: "Bahasa Indonesia",
[Locale.IS]: "Íslenska",
[Locale.IT]: "italiano",
[Locale.JA]: "日本語",
[Locale.KO]: "한국어",
[Locale.MN]: "Mongolian",
[Locale.NB]: "norsk (bokmål)",
[Locale.NL]: "Nederlands",
[Locale.PL]: "polski",
[Locale.PT]: "Português",
[Locale.PT_BR]: "Português Brasileiro",
[Locale.RO]: "Română",
[Locale.RU]: "Русский",
[Locale.SK]: "Slovensky",
[Locale.SL]: "Slovenščina",
[Locale.SQ]: "shqip",
[Locale.SR]: "српски",
[Locale.SV]: "svenska",
[Locale.TH]: "ภาษาไทย",
[Locale.TR]: "Türkçe",
[Locale.UK]: "Українська",
[Locale.VI]: "Tiếng Việt",
[Locale.ZH_HANS]: "简体中文",
[Locale.ZH_HANT]: "繁體中文"
};
export function getMatchingLocale(languages: readonly string[]): Locale {
const localeEntries = Object.entries(Locale);
@ -158,8 +194,21 @@ export function getMatchingLocale(languages: readonly string[]): Locale {
return undefined;
}
const defaultLocale = Locale.EN;
export interface LocaleContextType {
locale: Locale;
setLocale: (locale: Locale) => void;
}
export const LocaleContext = React.createContext<LocaleContextType>({
locale: defaultLocale,
setLocale: () => undefined
});
const { Consumer: LocaleConsumer, Provider: RawLocaleProvider } = LocaleContext;
const LocaleProvider: React.FC = ({ children }) => {
const [locale] = React.useState(
const [locale, setLocale] = React.useState(
getMatchingLocale(navigator.languages) || defaultLocale
);
@ -175,7 +224,14 @@ const LocaleProvider: React.FC = ({ children }) => {
}}
key={locale}
>
<RawLocaleProvider value={locale}>{children}</RawLocaleProvider>
<RawLocaleProvider
value={{
locale,
setLocale
}}
>
{children}
</RawLocaleProvider>
</IntlProvider>
);
};

View file

@ -12,7 +12,7 @@ export interface MoneyProps {
export const Money: React.StatelessComponent<MoneyProps> = ({ money }) => (
<LocaleConsumer>
{locale => {
{({ locale }) => {
return money.amount.toLocaleString(locale, {
currency: money.currency,
style: "currency"

View file

@ -1,5 +1,6 @@
import React from "react";
import { useIntl } from "react-intl";
import { LocaleConsumer } from "../Locale";
import IMoney from "../Money";
@ -22,7 +23,7 @@ export const MoneyRange: React.StatelessComponent<MoneyRangeProps> = ({
return (
<LocaleConsumer>
{locale =>
{({ locale }) =>
from && to
? intl.formatMessage(
{

View file

@ -8,7 +8,7 @@ interface PercentProps {
const Percent: React.StatelessComponent<PercentProps> = ({ amount }) => (
<LocaleConsumer>
{locale => {
{({ locale }) => {
return (amount / 100).toLocaleString(locale, {
maximumFractionDigits: 2,
style: "percent"

View file

@ -4,7 +4,7 @@ import { useContext } from "react";
import { LocaleContext } from "@saleor/components/Locale";
function useDateLocalize(): (date: string) => string {
const locale = useContext(LocaleContext);
const { locale } = useContext(LocaleContext);
return (date: string) =>
moment(date)

View file

@ -3,7 +3,7 @@ import { useContext } from "react";
import { LocaleContext } from "@saleor/components/Locale";
function useLocale() {
const themeInfo = useContext(LocaleContext);
return themeInfo;
const localeInfo = useContext(LocaleContext);
return localeInfo;
}
export default useLocale;

View file

@ -9,13 +9,16 @@ import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import Container from "@saleor/components/Container";
import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid";
import { localeNames } from "@saleor/components/Locale";
import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar";
import { ShopInfo_shop_permissions } from "@saleor/components/Shop/types/ShopInfo";
import useLocale from "@saleor/hooks/useLocale";
import { sectionNames } from "@saleor/intl";
import { getUserName, maybe } from "../../../misc";
import { PermissionEnum } from "../../../types/globalTypes";
import { StaffMemberDetails_user } from "../../types/StaffMemberDetails";
import StaffPreferences from "../StaffPreferences";
import StaffProperties from "../StaffProperties/StaffProperties";
interface FormData {
@ -29,6 +32,7 @@ interface FormData {
export interface StaffDetailsPageProps {
canEditAvatar: boolean;
canEditPreferences: boolean;
canEditStatus: boolean;
canRemove: boolean;
disabled: boolean;
@ -44,6 +48,7 @@ export interface StaffDetailsPageProps {
const StaffDetailsPage: React.StatelessComponent<StaffDetailsPageProps> = ({
canEditAvatar,
canEditPreferences,
canEditStatus,
canRemove,
disabled,
@ -57,6 +62,7 @@ const StaffDetailsPage: React.StatelessComponent<StaffDetailsPageProps> = ({
onSubmit
}: StaffDetailsPageProps) => {
const intl = useIntl();
const { locale, setLocale } = useLocale();
const initialForm: FormData = {
email: maybe(() => staffMember.email, ""),
@ -75,6 +81,7 @@ const StaffDetailsPage: React.StatelessComponent<StaffDetailsPageProps> = ({
lastName: maybe(() => staffMember.lastName, ""),
permissions: maybe(() => staffMember.permissions, []).map(perm => perm.code)
};
return (
<Form initial={initialForm} onSubmit={onSubmit} confirmLeave>
{({ data, change, hasChanged, submit }) => (
@ -95,26 +102,31 @@ const StaffDetailsPage: React.StatelessComponent<StaffDetailsPageProps> = ({
onImageDelete={onImageDelete}
/>
</div>
{canEditStatus && (
<div>
<AccountPermissions
data={data}
disabled={disabled}
permissions={permissions}
onChange={change}
/>
<CardSpacer />
<AccountStatus
data={data}
disabled={disabled}
label={intl.formatMessage({
defaultMessage: "User is active",
description: "checkbox label"
})}
onChange={change}
/>
</div>
)}
<div>
{canEditPreferences && (
<StaffPreferences locale={locale} onLocaleChange={setLocale} />
)}
{canEditStatus && (
<>
<AccountPermissions
data={data}
disabled={disabled}
permissions={permissions}
onChange={change}
/>
<CardSpacer />
<AccountStatus
data={data}
disabled={disabled}
label={intl.formatMessage({
defaultMessage: "User is active",
description: "checkbox label"
})}
onChange={change}
/>
</>
)}
</div>
</Grid>
<SaveButtonBar
disabled={disabled || !hasChanged}

View file

@ -0,0 +1,52 @@
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle";
import { Locale, localeNames } from "@saleor/components/Locale";
import SingleAutocompleteSelectField from "@saleor/components/SingleAutocompleteSelectField";
interface StaffPreferencesProps {
locale: Locale;
onLocaleChange: (locale: Locale) => void;
}
const StaffPreferences: React.StatelessComponent<StaffPreferencesProps> = ({
locale,
onLocaleChange
}) => {
const intl = useIntl();
return (
<Card>
<CardTitle
title={intl.formatMessage({
defaultMessage: "Preferences",
description: "section header"
})}
/>
<CardContent>
<SingleAutocompleteSelectField
choices={Object.values(Locale).map(locale => ({
label: localeNames[locale],
value: locale
}))}
displayValue={localeNames[locale]}
helperText={intl.formatMessage({
defaultMessage:
"Selecting this will change the language of your dashboard"
})}
label={intl.formatMessage({
defaultMessage: "Preferred Language"
})}
name="locale"
value={locale}
onChange={event => onLocaleChange(event.target.value)}
/>
</CardContent>
</Card>
);
};
StaffPreferences.displayName = "StaffPreferences";
export default StaffPreferences;

View file

@ -0,0 +1,2 @@
export { default } from "./StaffPreferences";
export * from "./StaffPreferences";

View file

@ -126,6 +126,7 @@ export const StaffDetails: React.StatelessComponent<OrderListProps> = ({
/>
<StaffDetailsPage
canEditAvatar={isUserSameAsViewer}
canEditPreferences={isUserSameAsViewer}
canEditStatus={!isUserSameAsViewer}
canRemove={!isUserSameAsViewer}
disabled={loading}

View file

@ -1,15 +1,20 @@
import React from "react";
import { IntlProvider } from "react-intl";
import { RawLocaleProvider } from "@saleor/components/Locale";
import { Locale, RawLocaleProvider } from "@saleor/components/Locale";
import { Provider as DateProvider } from "../components/Date/DateContext";
import { MessageManager } from "../components/messages";
import ThemeProvider from "../components/Theme";
import { TimezoneProvider } from "../components/Timezone";
export const Decorator = storyFn => (
<IntlProvider defaultLocale="en" locale="en">
<RawLocaleProvider value="en">
<IntlProvider defaultLocale={Locale.EN} locale={Locale.EN}>
<RawLocaleProvider
value={{
locale: Locale.EN,
setLocale: () => undefined
}}
>
<DateProvider value={+new Date("2018-08-07T14:30:44+00:00")}>
<TimezoneProvider value="America/New_York">
<ThemeProvider isDefaultDark={false}>

View file

@ -11,6 +11,7 @@ import Decorator from "../../Decorator";
const props: Omit<StaffDetailsPageProps, "classes"> = {
canEditAvatar: false,
canEditPreferences: false,
canEditStatus: true,
canRemove: true,
disabled: false,
@ -45,5 +46,6 @@ storiesOf("Views / Staff / Staff member details", module)
canEditStatus={false}
canRemove={false}
canEditAvatar={true}
canEditPreferences={true}
/>
));

View file

@ -526,6 +526,7 @@ export default (colors: IThemeColors): Theme =>
body2: {
fontSize: "1rem"
},
fontFamily,
h4: {
color: colors.font.default
},