Refactor product section translations (#125)

This commit is contained in:
Dominik Żegleń 2019-08-26 19:53:22 +02:00 committed by dominik-zeglen
parent c214cc298d
commit 63e4f9bd34
31 changed files with 2120 additions and 896 deletions

File diff suppressed because it is too large Load diff

View file

@ -133,6 +133,10 @@ export const sectionNames = defineMessages({
defaultMessage: "Product Types", defaultMessage: "Product Types",
description: "product types section name" description: "product types section name"
}, },
products: {
defaultMessage: "Products",
description: "products section name"
},
sales: { sales: {
defaultMessage: "Sales", defaultMessage: "Sales",
description: "sales section name" description: "sales section name"

View file

@ -7,6 +7,7 @@ import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
import makeStyles from "@material-ui/styles/makeStyles"; import makeStyles from "@material-ui/styles/makeStyles";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
@ -18,7 +19,6 @@ import SingleAutocompleteSelectField, {
SingleAutocompleteChoiceType SingleAutocompleteChoiceType
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
import i18n from "@saleor/i18n";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ProductDetails_product_attributes_attribute_values } from "@saleor/products/types/ProductDetails"; import { ProductDetails_product_attributes_attribute_values } from "@saleor/products/types/ProductDetails";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
@ -127,21 +127,30 @@ const ProductAttributes: React.FC<ProductAttributesProps> = ({
onChange, onChange,
onMultiChange onMultiChange
}) => { }) => {
const intl = useIntl();
const classes = useStyles({}); const classes = useStyles({});
const [expanded, setExpansionStatus] = React.useState(true); const [expanded, setExpansionStatus] = React.useState(true);
const toggleExpansion = () => setExpansionStatus(!expanded); const toggleExpansion = () => setExpansionStatus(!expanded);
return ( return (
<Card className={classes.card}> <Card className={classes.card}>
<CardTitle title={i18n.t("Attributes")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Attributes",
description: "product attributes, section header"
})}
/>
<CardContent className={classes.cardContent}> <CardContent className={classes.cardContent}>
<div className={classes.expansionBar}> <div className={classes.expansionBar}>
<div className={classes.expansionBarLabelContainer}> <div className={classes.expansionBarLabelContainer}>
<Typography className={classes.expansionBarLabel} variant="caption"> <Typography className={classes.expansionBarLabel} variant="caption">
{i18n.t("{{ number }} Attributes", { <FormattedMessage
context: "number of attribute", defaultMessage="{number} Attributes"
description="number of product attributes"
values={{
number: attributes.length number: attributes.length
})} }}
/>
</Typography> </Typography>
</div> </div>
<IconButton <IconButton
@ -184,7 +193,10 @@ const ProductAttributes: React.FC<ProductAttributesProps> = ({
)} )}
emptyOption emptyOption
name={`attribute:${attribute.label}`} name={`attribute:${attribute.label}`}
label={i18n.t("Value")} label={intl.formatMessage({
defaultMessage: "Value",
description: "attribute value"
})}
value={attribute.value[0]} value={attribute.value[0]}
onChange={event => onChange={event =>
onChange(attribute.id, event.target.value) onChange(attribute.id, event.target.value)
@ -195,7 +207,10 @@ const ProductAttributes: React.FC<ProductAttributesProps> = ({
<MultiAutocompleteSelectField <MultiAutocompleteSelectField
choices={getMultiChoices(attribute.data.values)} choices={getMultiChoices(attribute.data.values)}
displayValues={getMultiDisplayValue(attribute)} displayValues={getMultiDisplayValue(attribute)}
label={i18n.t("Values")} label={intl.formatMessage({
defaultMessage: "Values",
description: "attribute values"
})}
name={`attribute:${attribute.label}`} name={`attribute:${attribute.label}`}
value={attribute.value} value={attribute.value}
onChange={event => onChange={event =>

View file

@ -1,12 +1,12 @@
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import MultiSelectField from "@saleor/components/MultiSelectField"; import MultiSelectField from "@saleor/components/MultiSelectField";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SingleSelectField from "@saleor/components/SingleSelectField"; import SingleSelectField from "@saleor/components/SingleSelectField";
import i18n from "../../../i18n";
interface ProductCategoryAndCollectionsFormProps { interface ProductCategoryAndCollectionsFormProps {
categories?: Array<{ value: string; label: string }>; categories?: Array<{ value: string; label: string }>;
@ -26,15 +26,25 @@ const ProductCategoryAndCollectionsForm = ({
category, category,
loading, loading,
onChange onChange
}: ProductCategoryAndCollectionsFormProps) => ( }: ProductCategoryAndCollectionsFormProps) => {
const intl = useIntl();
return (
<Card> <Card>
<PageHeader title={i18n.t("Organisation")} /> <PageHeader
title={intl.formatMessage({
defaultMessage: "Organization",
description: "product organization, header"
})}
/>
<CardContent> <CardContent>
<SingleSelectField <SingleSelectField
disabled={loading} disabled={loading}
error={!!errors.category} error={!!errors.category}
hint={errors.category} hint={errors.category}
label={i18n.t("Category")} label={intl.formatMessage({
defaultMessage: "Category"
})}
choices={loading ? [] : categories} choices={loading ? [] : categories}
name="category" name="category"
value={category} value={category}
@ -45,7 +55,9 @@ const ProductCategoryAndCollectionsForm = ({
disabled={loading} disabled={loading}
error={!!errors.collections} error={!!errors.collections}
hint={errors.collections} hint={errors.collections}
label={i18n.t("Collections")} label={intl.formatMessage({
defaultMessage: "Collections"
})}
choices={loading ? [] : collections} choices={loading ? [] : collections}
name="collections" name="collections"
value={productCollections} value={productCollections}
@ -53,7 +65,8 @@ const ProductCategoryAndCollectionsForm = ({
/> />
</CardContent> </CardContent>
</Card> </Card>
); );
};
ProductCategoryAndCollectionsForm.displayName = ProductCategoryAndCollectionsForm.displayName =
"ProductCategoryAndCollectionsForm"; "ProductCategoryAndCollectionsForm";
export default ProductCategoryAndCollectionsForm; export default ProductCategoryAndCollectionsForm;

View file

@ -1,5 +1,6 @@
import { ContentState, convertToRaw, RawDraftContentState } from "draft-js"; import { ContentState, convertToRaw, RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
@ -16,6 +17,7 @@ import { SearchCategories_categories_edges_node } from "@saleor/containers/Searc
import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections"; import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
import useFormset from "@saleor/hooks/useFormset"; import useFormset from "@saleor/hooks/useFormset";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import { sectionNames } from "@saleor/intl";
import { import {
getChoices, getChoices,
ProductAttributeValueChoices, ProductAttributeValueChoices,
@ -23,7 +25,6 @@ import {
} from "@saleor/products/utils/data"; } from "@saleor/products/utils/data";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler"; import createSingleAutocompleteSelectHandler from "@saleor/utils/handlers/singleAutocompleteSelectChangeHandler";
import i18n from "../../../i18n";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
import { ProductCreateData_productTypes_edges_node_productAttributes } from "../../types/ProductCreateData"; import { ProductCreateData_productTypes_edges_node_productAttributes } from "../../types/ProductCreateData";
import { import {
@ -96,6 +97,8 @@ export const ProductCreatePage: React.StatelessComponent<
onBack, onBack,
onSubmit onSubmit
}: ProductCreatePageProps) => { }: ProductCreatePageProps) => {
const intl = useIntl();
// Form values // Form values
const { const {
change: changeAttributeData, change: changeAttributeData,
@ -200,7 +203,9 @@ export const ProductCreatePage: React.StatelessComponent<
return ( return (
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Products")}</AppHeader> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.products)}
</AppHeader>
<PageHeader title={header} /> <PageHeader title={header} />
<Grid> <Grid>
<div> <div>
@ -239,9 +244,10 @@ export const ProductCreatePage: React.StatelessComponent<
</> </>
)} )}
<SeoForm <SeoForm
helperText={i18n.t( helperText={intl.formatMessage({
defaultMessage:
"Add search engine title and description to make this product easier to find" "Add search engine title and description to make this product easier to find"
)} })}
title={data.seoTitle} title={data.seoTitle}
titlePlaceholder={data.name} titlePlaceholder={data.name}
description={data.seoDescription} description={data.seoDescription}

View file

@ -9,11 +9,12 @@ import {
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import { RawDraftContentState } from "draft-js"; import { RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import RichTextEditor from "@saleor/components/RichTextEditor"; import RichTextEditor from "@saleor/components/RichTextEditor";
import i18n from "../../../i18n"; import { commonMessages } from "@saleor/intl";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -48,9 +49,14 @@ export const ProductDetailsForm = withStyles(styles, {
errors, errors,
initialDescription, initialDescription,
onChange onChange
}: ProductDetailsFormProps) => ( }: ProductDetailsFormProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("General information")} /> <CardTitle
title={intl.formatMessage(commonMessages.generalInformations)}
/>
<CardContent> <CardContent>
<div className={classes.root}> <div className={classes.root}>
<TextField <TextField
@ -58,7 +64,10 @@ export const ProductDetailsForm = withStyles(styles, {
helperText={errors.name} helperText={errors.name}
disabled={disabled} disabled={disabled}
fullWidth fullWidth
label={i18n.t("Name")} label={intl.formatMessage({
defaultMessage: "Name",
description: "product name"
})}
name="name" name="name"
rows={5} rows={5}
value={data.name} value={data.name}
@ -71,13 +80,14 @@ export const ProductDetailsForm = withStyles(styles, {
error={!!errors.descriptionJson} error={!!errors.descriptionJson}
helperText={errors.descriptionJson} helperText={errors.descriptionJson}
initial={initialDescription} initial={initialDescription}
label={i18n.t("Description")} label={intl.formatMessage(commonMessages.description)}
name="description" name="description"
onChange={onChange} onChange={onChange}
/> />
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductDetailsForm.displayName = "ProductDetailsForm"; ProductDetailsForm.displayName = "ProductDetailsForm";
export default ProductDetailsForm; export default ProductDetailsForm;

View file

@ -8,10 +8,10 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import i18n from "../../../i18n";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -64,9 +64,17 @@ const ProductImageNavigation = withStyles(styles, {
highlighted, highlighted,
images, images,
onRowClick onRowClick
}: ProductImageNavigationProps) => ( }: ProductImageNavigationProps) => {
const intl = useIntl();
return (
<Card className={classes.card}> <Card className={classes.card}>
<CardTitle title={i18n.t("All photos")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "All Photos",
description: "section header"
})}
/>
<CardContent> <CardContent>
{images === undefined ? ( {images === undefined ? (
<Skeleton /> <Skeleton />
@ -76,7 +84,8 @@ const ProductImageNavigation = withStyles(styles, {
<div <div
className={classNames({ className={classNames({
[classes.imageContainer]: true, [classes.imageContainer]: true,
[classes.highlightedImageContainer]: image.id === highlighted [classes.highlightedImageContainer]:
image.id === highlighted
})} })}
onClick={onRowClick(image.id)} onClick={onRowClick(image.id)}
key={image.id} key={image.id}
@ -88,7 +97,8 @@ const ProductImageNavigation = withStyles(styles, {
)} )}
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductImageNavigation.displayName = "ProductImageNavigation"; ProductImageNavigation.displayName = "ProductImageNavigation";
export default ProductImageNavigation; export default ProductImageNavigation;

View file

@ -8,6 +8,7 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
@ -18,7 +19,7 @@ import Grid from "@saleor/components/Grid";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import i18n from "../../../i18n"; import { commonMessages } from "@saleor/intl";
import ProductImageNavigation from "../ProductImageNavigation"; import ProductImageNavigation from "../ProductImageNavigation";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -69,7 +70,10 @@ const ProductImagePage = withStyles(styles, { name: "ProductImagePage" })(
onDelete, onDelete,
onRowClick, onRowClick,
onSubmit onSubmit
}: ProductImagePageProps) => ( }: ProductImagePageProps) => {
const intl = useIntl();
return (
<Form <Form
initial={{ description: image ? image.alt : "" }} initial={{ description: image ? image.alt : "" }}
onSubmit={onSubmit} onSubmit={onSubmit}
@ -79,7 +83,12 @@ const ProductImagePage = withStyles(styles, { name: "ProductImagePage" })(
return ( return (
<Container> <Container>
<AppHeader onBack={onBack}>{product}</AppHeader> <AppHeader onBack={onBack}>{product}</AppHeader>
<PageHeader title={i18n.t("Edit Photo")} /> <PageHeader
title={intl.formatMessage({
defaultMessage: "Edit Photo",
description: "header"
})}
/>
<Grid variant="inverted"> <Grid variant="inverted">
<div> <div>
<ProductImageNavigation <ProductImageNavigation
@ -89,12 +98,20 @@ const ProductImagePage = withStyles(styles, { name: "ProductImagePage" })(
onRowClick={onRowClick} onRowClick={onRowClick}
/> />
<Card> <Card>
<CardTitle title={i18n.t("Photo Information")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Photo Information",
description: "section header"
})}
/>
<CardContent> <CardContent>
<TextField <TextField
name="description" name="description"
label={i18n.t("Description")} label={intl.formatMessage(commonMessages.description)}
helperText={i18n.t("Optional")} helperText={intl.formatMessage({
defaultMessage: "Optional",
description: "field is optional"
})}
disabled={disabled} disabled={disabled}
onChange={change} onChange={change}
value={data.description} value={data.description}
@ -106,7 +123,12 @@ const ProductImagePage = withStyles(styles, { name: "ProductImagePage" })(
</div> </div>
<div> <div>
<Card> <Card>
<CardTitle title={i18n.t("Photo View")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Photo View",
description: "section header"
})}
/>
<CardContent> <CardContent>
{!!image ? ( {!!image ? (
<div className={classes.imageContainer}> <div className={classes.imageContainer}>
@ -130,7 +152,8 @@ const ProductImagePage = withStyles(styles, { name: "ProductImagePage" })(
); );
}} }}
</Form> </Form>
) );
}
); );
ProductImagePage.displayName = "ProductImagePage"; ProductImagePage.displayName = "ProductImagePage";
export default ProductImagePage; export default ProductImagePage;

View file

@ -2,6 +2,7 @@ import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import classNames from "classnames"; import classNames from "classnames";
import { useIntl } from "react-intl";
import { import {
createStyles, createStyles,
@ -12,10 +13,10 @@ import {
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import ImageTile from "@saleor/components/ImageTile"; import ImageTile from "@saleor/components/ImageTile";
import ImageUpload from "@saleor/components/ImageUpload"; import ImageUpload from "@saleor/components/ImageUpload";
import { commonMessages } from "@saleor/intl";
import { ReorderAction } from "@saleor/types"; import { ReorderAction } from "@saleor/types";
import React from "react"; import React from "react";
import { SortableContainer, SortableElement } from "react-sortable-hoc"; import { SortableContainer, SortableElement } from "react-sortable-hoc";
import i18n from "../../../i18n";
import { ProductDetails_product_images } from "../../types/ProductDetails"; import { ProductDetails_product_images } from "../../types/ProductDetails";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -181,27 +182,34 @@ const ProductImages = withStyles(styles, { name: "ProductImages" })(
onImageDelete, onImageDelete,
onImageReorder, onImageReorder,
onImageUpload onImageUpload
}: ProductImagesProps) => ( }: ProductImagesProps) => {
const intl = useIntl();
const upload = React.useRef(null);
return (
<Card className={classes.card}> <Card className={classes.card}>
<CardTitle <CardTitle
title={i18n.t("Images")} title={intl.formatMessage({
defaultMessage: "Images",
description: "section header"
})}
toolbar={ toolbar={
<> <>
<Button <Button
onClick={() => this.upload.click()} onClick={() => upload.current.click()}
disabled={loading} disabled={loading}
variant="text" variant="text"
color="primary" color="primary"
data-tc="button-upload-image" data-tc="button-upload-image"
> >
{i18n.t("Upload image")} {intl.formatMessage(commonMessages.uploadImage)}
</Button> </Button>
<input <input
className={classes.fileField} className={classes.fileField}
id="fileUpload" id="fileUpload"
onChange={event => onImageUpload(event.target.files[0])} onChange={event => onImageUpload(event.target.files[0])}
type="file" type="file"
ref={ref => (this.upload = ref)} ref={upload}
/> />
</> </>
} }
@ -249,7 +257,8 @@ const ProductImages = withStyles(styles, { name: "ProductImages" })(
)} )}
</div> </div>
</Card> </Card>
) );
}
); );
ProductImages.displayName = "ProductImages"; ProductImages.displayName = "ProductImages";
export default ProductImages; export default ProductImages;

View file

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import { FieldType, IFilter } from "@saleor/components/Filter"; import { FieldType, IFilter } from "@saleor/components/Filter";
import FilterBar from "@saleor/components/FilterBar"; import FilterBar from "@saleor/components/FilterBar";
import i18n from "@saleor/i18n";
import { FilterProps } from "@saleor/types"; import { FilterProps } from "@saleor/types";
import { StockAvailability } from "@saleor/types/globalTypes"; import { StockAvailability } from "@saleor/types/globalTypes";
import { ProductListUrlFilters } from "../../urls"; import { ProductListUrlFilters } from "../../urls";
@ -17,44 +17,74 @@ export enum ProductFilterKeys {
stock, stock,
query query
} }
const filterMenu: IFilter = [
const ProductListFilter: React.FC<ProductListFilterProps> = props => {
const intl = useIntl();
const filterMenu: IFilter = [
{ {
children: [], children: [],
data: { data: {
additionalText: i18n.t("is set as"), additionalText: intl.formatMessage({
fieldLabel: i18n.t("Status"), defaultMessage: "is set as",
description: "product status is set as"
}),
fieldLabel: intl.formatMessage({
defaultMessage: "Status",
description: "product status"
}),
options: [ options: [
{ {
label: i18n.t("Visible"), label: intl.formatMessage({
defaultMessage: "Visible",
description: "product is visible"
}),
value: true value: true
}, },
{ {
label: i18n.t("Hidden"), label: intl.formatMessage({
defaultMessage: "Hidden",
description: "product is hidden"
}),
value: false value: false
} }
], ],
type: FieldType.select type: FieldType.select
}, },
label: i18n.t("Visibility"), label: intl.formatMessage({
defaultMessage: "Visibility",
description: "product visibility"
}),
value: ProductFilterKeys.published.toString() value: ProductFilterKeys.published.toString()
}, },
{ {
children: [], children: [],
data: { data: {
fieldLabel: i18n.t("Stock quantity"), fieldLabel: intl.formatMessage({
defaultMessage: "Stock quantity"
}),
options: [ options: [
{ {
label: i18n.t("Available"), label: intl.formatMessage({
defaultMessage: "Available",
description: "product status"
}),
value: StockAvailability.IN_STOCK value: StockAvailability.IN_STOCK
}, },
{ {
label: i18n.t("Out Of Stock"), label: intl.formatMessage({
defaultMessage: "Out Of Stock",
description: "product status"
}),
value: StockAvailability.OUT_OF_STOCK value: StockAvailability.OUT_OF_STOCK
} }
], ],
type: FieldType.select type: FieldType.select
}, },
label: i18n.t("Stock"), label: intl.formatMessage({
defaultMessage: "Stock",
description: "product stock"
}),
value: ProductFilterKeys.stock.toString() value: ProductFilterKeys.stock.toString()
}, },
{ {
@ -62,34 +92,46 @@ const filterMenu: IFilter = [
{ {
children: [], children: [],
data: { data: {
additionalText: i18n.t("equals"), additionalText: intl.formatMessage({
defaultMessage: "equals",
description: "product price"
}),
fieldLabel: null, fieldLabel: null,
type: FieldType.price type: FieldType.price
}, },
label: i18n.t("Specific Price"), label: intl.formatMessage({
defaultMessage: "Specific Price"
}),
value: ProductFilterKeys.priceEqual.toString() value: ProductFilterKeys.priceEqual.toString()
}, },
{ {
children: [], children: [],
data: { data: {
fieldLabel: i18n.t("Range"), fieldLabel: intl.formatMessage({
defaultMessage: "Range"
}),
type: FieldType.rangePrice type: FieldType.rangePrice
}, },
label: i18n.t("Range"), label: intl.formatMessage({
defaultMessage: "Range"
}),
value: ProductFilterKeys.priceRange.toString() value: ProductFilterKeys.priceRange.toString()
} }
], ],
data: { data: {
fieldLabel: i18n.t("Price"), fieldLabel: intl.formatMessage({
defaultMessage: "Price"
}),
type: FieldType.range type: FieldType.range
}, },
label: i18n.t("Price"), label: intl.formatMessage({
defaultMessage: "Price"
}),
value: ProductFilterKeys.price.toString() value: ProductFilterKeys.price.toString()
} }
]; ];
const ProductListFilter: React.FC<ProductListFilterProps> = props => ( return <FilterBar {...props} filterMenu={filterMenu} />;
<FilterBar {...props} filterMenu={filterMenu} /> };
);
ProductListFilter.displayName = "ProductListFilter"; ProductListFilter.displayName = "ProductListFilter";
export default ProductListFilter; export default ProductListFilter;

View file

@ -4,6 +4,7 @@ import { Theme } from "@material-ui/core/styles";
import AddIcon from "@material-ui/icons/Add"; import AddIcon from "@material-ui/icons/Add";
import makeStyles from "@material-ui/styles/makeStyles"; import makeStyles from "@material-ui/styles/makeStyles";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { CategoryDetails_category_products_edges_node } from "@saleor/categories/types/CategoryDetails"; import { CategoryDetails_category_products_edges_node } from "@saleor/categories/types/CategoryDetails";
import ColumnPicker, { import ColumnPicker, {
@ -14,7 +15,7 @@ import PageHeader from "@saleor/components/PageHeader";
import ProductList from "@saleor/components/ProductList"; import ProductList from "@saleor/components/ProductList";
import { ProductListColumns } from "@saleor/config"; import { ProductListColumns } from "@saleor/config";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import i18n from "@saleor/i18n"; import { sectionNames } from "@saleor/intl";
import { FilterPageProps, ListActions, PageListProps } from "@saleor/types"; import { FilterPageProps, ListActions, PageListProps } from "@saleor/types";
import { toggle } from "@saleor/utils/lists"; import { toggle } from "@saleor/utils/lists";
import { ProductListUrlFilters } from "../../urls"; import { ProductListUrlFilters } from "../../urls";
@ -53,6 +54,7 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
onUpdateListSettings, onUpdateListSettings,
...listProps ...listProps
} = props; } = props;
const intl = useIntl();
const classes = useStyles(props); const classes = useStyles(props);
const [selectedColumns, setSelectedColumns] = useStateFromProps( const [selectedColumns, setSelectedColumns] = useStateFromProps(
settings.columns settings.columns
@ -74,22 +76,31 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
const columns: ColumnPickerChoice[] = [ const columns: ColumnPickerChoice[] = [
{ {
label: i18n.t("Published"), label: intl.formatMessage({
defaultMessage: "Published",
description: "product status"
}),
value: "isPublished" as ProductListColumns value: "isPublished" as ProductListColumns
}, },
{ {
label: i18n.t("Price"), label: intl.formatMessage({
defaultMessage: "Price",
description: "product price"
}),
value: "price" as ProductListColumns value: "price" as ProductListColumns
}, },
{ {
label: i18n.t("Type"), label: intl.formatMessage({
defaultMessage: "Type",
description: "product type"
}),
value: "productType" as ProductListColumns value: "productType" as ProductListColumns
} }
]; ];
return ( return (
<Container> <Container>
<PageHeader title={i18n.t("Products")}> <PageHeader title={intl.formatMessage(sectionNames.products)}>
<ColumnPicker <ColumnPicker
className={classes.columnPicker} className={classes.columnPicker}
columns={columns} columns={columns}
@ -105,19 +116,27 @@ export const ProductListPage: React.FC<ProductListPageProps> = props => {
variant="contained" variant="contained"
data-tc="add-product" data-tc="add-product"
> >
{i18n.t("Add product")} <AddIcon /> <FormattedMessage defaultMessage="Add Product" description="button" />
<AddIcon />
</Button> </Button>
</PageHeader> </PageHeader>
<Card> <Card>
<ProductListFilter <ProductListFilter
allTabLabel={i18n.t("All Products")} allTabLabel={intl.formatMessage({
defaultMessage: "All Products",
description: "tab name"
})}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
currentTab={currentTab} currentTab={currentTab}
filterLabel={i18n.t("Select all products where:")} filterLabel={intl.formatMessage({
defaultMessage: "Select all products where:"
})}
filterTabs={filterTabs} filterTabs={filterTabs}
filtersList={filtersList} filtersList={filtersList}
initialSearch={initialSearch} initialSearch={initialSearch}
searchPlaceholder={i18n.t("Search Products...")} searchPlaceholder={intl.formatMessage({
defaultMessage: "Search Products..."
})}
onAll={onAll} onAll={onAll}
onSearchChange={onSearchChange} onSearchChange={onSearchChange}
onFilterAdd={onFilterAdd} onFilterAdd={onFilterAdd}

View file

@ -8,6 +8,7 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
@ -20,7 +21,6 @@ import SingleAutocompleteSelectField, {
SingleAutocompleteChoiceType SingleAutocompleteChoiceType
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import { ChangeEvent } from "@saleor/hooks/useForm"; import { ChangeEvent } from "@saleor/hooks/useForm";
import i18n from "@saleor/i18n";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { FormErrors } from "@saleor/types"; import { FormErrors } from "@saleor/types";
@ -86,9 +86,17 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
onCategoryChange, onCategoryChange,
onCollectionChange, onCollectionChange,
onProductTypeChange onProductTypeChange
}: ProductOrganizationProps) => ( }: ProductOrganizationProps) => {
const intl = useIntl();
return (
<Card className={classes.card}> <Card className={classes.card}>
<CardTitle title={i18n.t("Organize Product")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Organize Product",
description: "section header"
})}
/>
<CardContent> <CardContent>
{canChangeType ? ( {canChangeType ? (
<SingleAutocompleteSelectField <SingleAutocompleteSelectField
@ -97,7 +105,9 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
helperText={errors.productType} helperText={errors.productType}
name="productType" name="productType"
disabled={disabled} disabled={disabled}
label={i18n.t("Product Type")} label={intl.formatMessage({
defaultMessage: "Product Type"
})}
choices={productTypes} choices={productTypes}
value={data.productType} value={data.productType}
onChange={onProductTypeChange} onChange={onProductTypeChange}
@ -106,19 +116,25 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
) : ( ) : (
<> <>
<Typography className={classes.label} variant="caption"> <Typography className={classes.label} variant="caption">
{i18n.t("Product Type")} <FormattedMessage defaultMessage="Product Type" />
</Typography> </Typography>
<Typography>{maybe(() => productType.name, "...")}</Typography> <Typography>{maybe(() => productType.name, "...")}</Typography>
<CardSpacer /> <CardSpacer />
<Typography className={classes.label} variant="caption"> <Typography className={classes.label} variant="caption">
{i18n.t("Product Type")} <FormattedMessage defaultMessage="Product Type" />
</Typography> </Typography>
<Typography> <Typography>
{maybe( {maybe(
() => () =>
productType.hasVariants productType.hasVariants
? i18n.t("Configurable") ? intl.formatMessage({
: i18n.t("Simple"), defaultMessage: "Configurable",
description: "product is configurable"
})
: intl.formatMessage({
defaultMessage: "Simple",
description: "product is not configurable"
}),
"..." "..."
)} )}
</Typography> </Typography>
@ -132,7 +148,9 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
error={!!errors.category} error={!!errors.category}
helperText={errors.category} helperText={errors.category}
disabled={disabled} disabled={disabled}
label={i18n.t("Category")} label={intl.formatMessage({
defaultMessage: "Category"
})}
choices={disabled ? [] : categories} choices={disabled ? [] : categories}
name="category" name="category"
value={data.category} value={data.category}
@ -145,20 +163,25 @@ const ProductOrganization = withStyles(styles, { name: "ProductOrganization" })(
<FormSpacer /> <FormSpacer />
<MultiAutocompleteSelectField <MultiAutocompleteSelectField
displayValues={collectionsInputDisplayValue} displayValues={collectionsInputDisplayValue}
label={i18n.t("Collections")} label={intl.formatMessage({
defaultMessage: "Collections"
})}
choices={disabled ? [] : collections} choices={disabled ? [] : collections}
name="collections" name="collections"
value={data.collections} value={data.collections}
helperText={i18n.t( helperText={intl.formatMessage({
"*Optional. Adding product to collection helps users find it." defaultMessage:
)} "*Optional. Adding product to collection helps users find it.",
description: "field is optional"
})}
onChange={onCollectionChange} onChange={onCollectionChange}
fetchChoices={fetchCollections} fetchChoices={fetchCollections}
data-tc="collections" data-tc="collections"
/> />
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductOrganization.displayName = "ProductOrganization"; ProductOrganization.displayName = "ProductOrganization";
export default ProductOrganization; export default ProductOrganization;

View file

@ -7,11 +7,11 @@ import {
WithStyles WithStyles
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import ControlledCheckbox from "@saleor/components/ControlledCheckbox"; import ControlledCheckbox from "@saleor/components/ControlledCheckbox";
import PriceField from "@saleor/components/PriceField"; import PriceField from "@saleor/components/PriceField";
import i18n from "../../../i18n";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -33,12 +33,22 @@ interface ProductPricingProps extends WithStyles<typeof styles> {
} }
const ProductPricing = withStyles(styles, { name: "ProductPricing" })( const ProductPricing = withStyles(styles, { name: "ProductPricing" })(
({ classes, currency, data, disabled, onChange }: ProductPricingProps) => ( ({ classes, currency, data, disabled, onChange }: ProductPricingProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("Pricing")}> <CardTitle
title={intl.formatMessage({
defaultMessage: "Pricing",
description: "product pricing"
})}
>
<ControlledCheckbox <ControlledCheckbox
name="chargeTaxes" name="chargeTaxes"
label={i18n.t("Charge taxes for this item")} label={intl.formatMessage({
defaultMessage: "Charge taxes for this item"
})}
checked={data.chargeTaxes} checked={data.chargeTaxes}
onChange={onChange} onChange={onChange}
disabled={disabled} disabled={disabled}
@ -48,7 +58,10 @@ const ProductPricing = withStyles(styles, { name: "ProductPricing" })(
<div className={classes.root}> <div className={classes.root}>
<PriceField <PriceField
disabled={disabled} disabled={disabled}
label={i18n.t("Price")} label={intl.formatMessage({
defaultMessage: "Price",
description: "product price"
})}
name="basePrice" name="basePrice"
value={data.basePrice} value={data.basePrice}
currencySymbol={currency} currencySymbol={currency}
@ -57,7 +70,8 @@ const ProductPricing = withStyles(styles, { name: "ProductPricing" })(
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductPricing.displayName = "ProductPricing"; ProductPricing.displayName = "ProductPricing";
export default ProductPricing; export default ProductPricing;

View file

@ -8,9 +8,9 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import i18n from "../../../i18n";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ProductDetails_product } from "../../types/ProductDetails"; import { ProductDetails_product } from "../../types/ProductDetails";
@ -35,15 +35,33 @@ interface ProductStockProps extends WithStyles<typeof styles> {
} }
const ProductStock = withStyles(styles, { name: "ProductStock" })( const ProductStock = withStyles(styles, { name: "ProductStock" })(
({ classes, data, disabled, product, onChange, errors }: ProductStockProps) => ( ({
classes,
data,
disabled,
product,
onChange,
errors
}: ProductStockProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("Inventory")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Inventory",
description: "product stock, section header",
id: "productStockHeader"
})}
/>
<CardContent> <CardContent>
<div className={classes.root}> <div className={classes.root}>
<TextField <TextField
disabled={disabled} disabled={disabled}
name="sku" name="sku"
label={i18n.t("SKU (Stock Keeping Unit)")} label={intl.formatMessage({
defaultMessage: "SKU (Stock Keeping Unit)"
})}
value={data.sku} value={data.sku}
onChange={onChange} onChange={onChange}
error={!!errors.sku} error={!!errors.sku}
@ -52,22 +70,35 @@ const ProductStock = withStyles(styles, { name: "ProductStock" })(
<TextField <TextField
disabled={disabled} disabled={disabled}
name="stockQuantity" name="stockQuantity"
label={i18n.t("Inventory")} label={intl.formatMessage({
defaultMessage: "Inventory",
description: "product stock",
id: "prodictStockInventoryLabel"
})}
value={data.stockQuantity} value={data.stockQuantity}
type="number" type="number"
onChange={onChange} onChange={onChange}
helperText={ helperText={
product product
? i18n.t("Allocated: {{ quantity }}", { ? intl.formatMessage(
quantity: maybe(() => product.variants[0].quantityAllocated) {
}) defaultMessage: "Allocated: {quantity}",
description: "allocated product stock"
},
{
quantity: maybe(
() => product.variants[0].quantityAllocated
)
}
)
: undefined : undefined
} }
/> />
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductStock.displayName = "ProductStock"; ProductStock.displayName = "ProductStock";
export default ProductStock; export default ProductStock;

View file

@ -1,5 +1,6 @@
import { convertFromRaw, RawDraftContentState } from "draft-js"; import { convertFromRaw, RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
@ -15,7 +16,7 @@ import { SearchCategories_categories_edges_node } from "@saleor/containers/Searc
import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections"; import { SearchCollections_collections_edges_node } from "@saleor/containers/SearchCollections/types/SearchCollections";
import useFormset from "@saleor/hooks/useFormset"; import useFormset from "@saleor/hooks/useFormset";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import i18n from "@saleor/i18n"; import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ListActions, UserError } from "@saleor/types"; import { ListActions, UserError } from "@saleor/types";
import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler"; import createMultiAutocompleteSelectHandler from "@saleor/utils/handlers/multiAutocompleteSelectChangeHandler";
@ -107,6 +108,7 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
toggleAll, toggleAll,
toolbar toolbar
}) => { }) => {
const intl = useIntl();
const attributeInput = React.useMemo( const attributeInput = React.useMemo(
() => getAttributeInputFromProduct(product), () => getAttributeInputFromProduct(product),
[product] [product]
@ -188,7 +190,9 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
return ( return (
<> <>
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Products")}</AppHeader> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.products)}
</AppHeader>
<PageHeader title={header} /> <PageHeader title={header} />
<Grid> <Grid>
<div> <div>

View file

@ -1,6 +1,7 @@
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Grid from "@saleor/components/Grid"; import Grid from "@saleor/components/Grid";
@ -9,7 +10,7 @@ import SingleAutocompleteSelectField, {
} from "@saleor/components/SingleAutocompleteSelectField"; } from "@saleor/components/SingleAutocompleteSelectField";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset";
import i18n from "../../../i18n"; import { commonMessages } from "@saleor/intl";
import { ProductVariant_attributes_attribute_values } from "../../types/ProductVariant"; import { ProductVariant_attributes_attribute_values } from "../../types/ProductVariant";
export interface VariantAttributeInputData { export interface VariantAttributeInputData {
@ -67,9 +68,14 @@ const ProductVariantAttributes: React.FC<ProductVariantAttributesProps> = ({
disabled, disabled,
errors, errors,
onChange onChange
}) => ( }) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("General Information")} /> <CardTitle
title={intl.formatMessage(commonMessages.generalInformations)}
/>
<CardContent> <CardContent>
<Grid variant="uniform"> <Grid variant="uniform">
{attributes === undefined ? ( {attributes === undefined ? (
@ -101,6 +107,7 @@ const ProductVariantAttributes: React.FC<ProductVariantAttributesProps> = ({
</Grid> </Grid>
</CardContent> </CardContent>
</Card> </Card>
); );
};
ProductVariantAttributes.displayName = "ProductVariantAttributes"; ProductVariantAttributes.displayName = "ProductVariantAttributes";
export default ProductVariantAttributes; export default ProductVariantAttributes;

View file

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
import CardSpacer from "@saleor/components/CardSpacer"; import CardSpacer from "@saleor/components/CardSpacer";
@ -16,7 +17,6 @@ import {
getVariantAttributeErrors, getVariantAttributeErrors,
getVariantAttributeInputFromProduct getVariantAttributeInputFromProduct
} from "@saleor/products/utils/data"; } from "@saleor/products/utils/data";
import i18n from "../../../i18n";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData"; import { ProductVariantCreateData_product } from "../../types/ProductVariantCreateData";
@ -63,6 +63,7 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
onSubmit, onSubmit,
onVariantClick onVariantClick
}) => { }) => {
const intl = useIntl();
const attributeInput = React.useMemo( const attributeInput = React.useMemo(
() => getVariantAttributeInputFromProduct(product), () => getVariantAttributeInputFromProduct(product),
[product] [product]
@ -150,8 +151,14 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
<SaveButtonBar <SaveButtonBar
disabled={loading || !onSubmit || !hasChanged} disabled={loading || !onSubmit || !hasChanged}
labels={{ labels={{
delete: i18n.t("Remove variant"), delete: intl.formatMessage({
save: i18n.t("Save variant") defaultMessage: "Delete Variant",
description: "button"
}),
save: intl.formatMessage({
defaultMessage: "Save variant",
description: "button"
})
}} }}
state={saveButtonBarState} state={saveButtonBarState}
onCancel={onBack} onCancel={onBack}

View file

@ -11,11 +11,12 @@ import {
WithStyles WithStyles
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton"; } from "@saleor/components/ConfirmButton";
import i18n from "../../../i18n"; import { buttonMessages } from "@saleor/intl";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -50,21 +51,25 @@ const ProductVariantDeleteDialog = withStyles(styles, {
}: ProductVariantDeleteDialogProps) => ( }: ProductVariantDeleteDialogProps) => (
<Dialog onClose={onClose} open={open}> <Dialog onClose={onClose} open={open}>
<DialogTitle> <DialogTitle>
{i18n.t("Delete variant", { context: "title" })} <FormattedMessage
defaultMessage="Delete Variant"
description="dialog header"
/>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {name}?"
"Are you sure you want to remove <strong>{{name}}</strong>?", description="delete product variant"
{ name } values={{
) name
}} }}
/> />
</DialogContentText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>
{i18n.t("Cancel", { context: "button" })} <FormattedMessage {...buttonMessages.cancel} />
</Button> </Button>
<ConfirmButton <ConfirmButton
transitionState={confirmButtonState} transitionState={confirmButtonState}
@ -72,7 +77,10 @@ const ProductVariantDeleteDialog = withStyles(styles, {
variant="contained" variant="contained"
onClick={onConfirm} onClick={onConfirm}
> >
{i18n.t("Delete variant", { context: "button" })} <FormattedMessage
defaultMessage="Delete variant"
description="button"
/>
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View file

@ -11,8 +11,9 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../../i18n"; import { buttonMessages } from "@saleor/intl";
import { ProductImage } from "../../types/ProductImage"; import { ProductImage } from "../../types/ProductImage";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -72,7 +73,10 @@ const ProductVariantImageSelectDialog = withStyles(styles, {
}: ProductVariantImageSelectDialogProps) => ( }: ProductVariantImageSelectDialogProps) => (
<Dialog onClose={onClose} open={open}> <Dialog onClose={onClose} open={open}>
<DialogTitle> <DialogTitle>
{i18n.t("Image selection", { context: "title" })} <FormattedMessage
defaultMessage="Image Selection"
description="dialog header"
/>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<div className={classes.root}> <div className={classes.root}>
@ -97,7 +101,7 @@ const ProductVariantImageSelectDialog = withStyles(styles, {
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>
{i18n.t("Close", { context: "button" })} <FormattedMessage {...buttonMessages.back} />
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View file

@ -9,10 +9,10 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import i18n from "../../../i18n";
import { ProductImage } from "../../types/ProductImage"; import { ProductImage } from "../../types/ProductImage";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -54,10 +54,16 @@ interface ProductVariantImagesProps extends WithStyles<typeof styles> {
export const ProductVariantImages = withStyles(styles, { export const ProductVariantImages = withStyles(styles, {
name: "ProductVariantImages" name: "ProductVariantImages"
})(({ classes, disabled, images, onImageAdd }: ProductVariantImagesProps) => ( })(({ classes, disabled, images, onImageAdd }: ProductVariantImagesProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Images")} title={intl.formatMessage({
defaultMessage: "Images",
description: "section header"
})}
toolbar={ toolbar={
<Button <Button
color="primary" color="primary"
@ -65,7 +71,10 @@ export const ProductVariantImages = withStyles(styles, {
disabled={disabled} disabled={disabled}
onClick={onImageAdd} onClick={onImageAdd}
> >
{i18n.t("Choose photos")} <FormattedMessage
defaultMessage="Choose photos"
description="button"
/>
</Button> </Button>
} }
/> />
@ -83,12 +92,13 @@ export const ProductVariantImages = withStyles(styles, {
)) ))
) : ( ) : (
<Typography className={classes.helpText}> <Typography className={classes.helpText}>
{i18n.t("Select a specific variant image from product images")} <FormattedMessage defaultMessage="Select a specific variant image from product images" />
</Typography> </Typography>
)} )}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
)); );
});
ProductVariantImages.displayName = "ProductVariantImages"; ProductVariantImages.displayName = "ProductVariantImages";
export default ProductVariantImages; export default ProductVariantImages;

View file

@ -12,11 +12,11 @@ import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import TableCellAvatar from "@saleor/components/TableCellAvatar"; import TableCellAvatar from "@saleor/components/TableCellAvatar";
import i18n from "../../../i18n";
import { maybe, renderCollection } from "../../../misc"; import { maybe, renderCollection } from "../../../misc";
import { ProductVariantCreateData_product_variants } from "../../types/ProductVariantCreateData"; import { ProductVariantCreateData_product_variants } from "../../types/ProductVariantCreateData";
import { ProductVariantDetails_productVariant } from "../../types/ProductVariantDetails"; import { ProductVariantDetails_productVariant } from "../../types/ProductVariantDetails";
@ -63,9 +63,17 @@ const ProductVariantNavigation = withStyles(styles, {
variants, variants,
onAdd, onAdd,
onRowClick onRowClick
}: ProductVariantNavigationProps) => ( }: ProductVariantNavigationProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("Variants")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Variants",
description: "section header"
})}
/>
<Table> <Table>
<TableBody> <TableBody>
{renderCollection(variants, variant => ( {renderCollection(variants, variant => (
@ -93,22 +101,32 @@ const ProductVariantNavigation = withStyles(styles, {
<TableRow> <TableRow>
<TableCell colSpan={2}> <TableCell colSpan={2}>
<Button color="primary" onClick={onAdd}> <Button color="primary" onClick={onAdd}>
{i18n.t("Add variant")} <FormattedMessage
defaultMessage="Add variant"
description="button"
/>
</Button> </Button>
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
<TableRow> <TableRow>
<TableCellAvatar className={classes.tabActive} thumbnail={null} /> <TableCellAvatar
className={classes.tabActive}
thumbnail={null}
/>
<TableCell className={classes.textLeft}> <TableCell className={classes.textLeft}>
{i18n.t("New Variant")} <FormattedMessage
defaultMessage="New Variant"
description="variant name"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
)} )}
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) );
}
); );
ProductVariantNavigation.displayName = "ProductVariantNavigation"; ProductVariantNavigation.displayName = "ProductVariantNavigation";
export default ProductVariantNavigation; export default ProductVariantNavigation;

View file

@ -7,10 +7,10 @@ import {
WithStyles WithStyles
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import PriceField from "@saleor/components/PriceField"; import PriceField from "@saleor/components/PriceField";
import i18n from "../../../i18n";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -39,20 +39,34 @@ const ProductVariantPrice = withStyles(styles, { name: "ProductVariantPrice" })(
priceOverride, priceOverride,
loading, loading,
onChange onChange
}: ProductVariantPriceProps) => ( }: ProductVariantPriceProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("Pricing")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Pricing",
description: "product pricing, section header"
})}
/>
<CardContent> <CardContent>
<div className={classes.grid}> <div className={classes.grid}>
<div> <div>
<PriceField <PriceField
error={!!errors.price_override} error={!!errors.price_override}
name="priceOverride" name="priceOverride"
label={i18n.t("Selling price override")} label={intl.formatMessage({
defaultMessage: "Selling price override"
})}
hint={ hint={
errors.price_override errors.price_override
? errors.price_override ? errors.price_override
: i18n.t("Optional") : intl.formatMessage({
defaultMessage: "Optional",
description: "optional field",
id: "productVariantPriceOptionalPriceOverrideField"
})
} }
value={priceOverride} value={priceOverride}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
@ -64,8 +78,18 @@ const ProductVariantPrice = withStyles(styles, { name: "ProductVariantPrice" })(
<PriceField <PriceField
error={!!errors.cost_price} error={!!errors.cost_price}
name="costPrice" name="costPrice"
label={i18n.t("Cost price override")} label={intl.formatMessage({
hint={errors.cost_price ? errors.cost_price : i18n.t("Optional")} defaultMessage: "Cost price override"
})}
hint={
errors.cost_price
? errors.cost_price
: intl.formatMessage({
defaultMessage: "Optional",
description: "optional field",
id: "productVariantPriceOptionalCostPriceField"
})
}
value={costPrice} value={costPrice}
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
onChange={onChange} onChange={onChange}
@ -75,7 +99,8 @@ const ProductVariantPrice = withStyles(styles, { name: "ProductVariantPrice" })(
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductVariantPrice.displayName = "ProductVariantPrice"; ProductVariantPrice.displayName = "ProductVariantPrice";
export default ProductVariantPrice; export default ProductVariantPrice;

View file

@ -8,9 +8,9 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import i18n from "../../../i18n";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -42,9 +42,17 @@ const ProductVariantStock = withStyles(styles, { name: "ProductVariantStock" })(
stockAllocated, stockAllocated,
loading, loading,
onChange onChange
}: ProductVariantStockProps) => ( }: ProductVariantStockProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("Stock")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Stock",
description: "product variant stock, section header"
})}
/>
<CardContent> <CardContent>
<div className={classes.grid}> <div className={classes.grid}>
<div> <div>
@ -52,15 +60,23 @@ const ProductVariantStock = withStyles(styles, { name: "ProductVariantStock" })(
error={!!errors.quantity} error={!!errors.quantity}
name="quantity" name="quantity"
value={quantity} value={quantity}
label={i18n.t("Inventory")} label={intl.formatMessage({
defaultMessage: "Inventory",
description: "product variant stock"
})}
helperText={ helperText={
errors.quantity errors.quantity
? errors.quantity ? errors.quantity
: !!stockAllocated : !!stockAllocated
? i18n.t("Allocated: {{ quantity }}", { ? intl.formatMessage(
context: "variant allocated stock", {
defaultMessage: "Allocated: {quantity}",
description: "variant allocated stock"
},
{
quantity: stockAllocated quantity: stockAllocated
}) }
)
: undefined : undefined
} }
onChange={onChange} onChange={onChange}
@ -74,7 +90,9 @@ const ProductVariantStock = withStyles(styles, { name: "ProductVariantStock" })(
helperText={errors.sku} helperText={errors.sku}
name="sku" name="sku"
value={sku} value={sku}
label={i18n.t("SKU (Stock Keeping Unit)")} label={intl.formatMessage({
defaultMessage: "SKU (Stock Keeping Unit)"
})}
onChange={onChange} onChange={onChange}
disabled={loading} disabled={loading}
fullWidth fullWidth
@ -83,7 +101,8 @@ const ProductVariantStock = withStyles(styles, { name: "ProductVariantStock" })(
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
ProductVariantStock.displayName = "ProductVariantStock"; ProductVariantStock.displayName = "ProductVariantStock";
export default ProductVariantStock; export default ProductVariantStock;

View file

@ -14,6 +14,7 @@ import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Checkbox from "@saleor/components/Checkbox"; import Checkbox from "@saleor/components/Checkbox";
@ -21,7 +22,6 @@ import Money from "@saleor/components/Money";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import StatusLabel from "@saleor/components/StatusLabel"; import StatusLabel from "@saleor/components/StatusLabel";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import i18n from "../../../i18n";
import { renderCollection } from "../../../misc"; import { renderCollection } from "../../../misc";
import { ListActions } from "../../../types"; import { ListActions } from "../../../types";
import { ProductDetails_product_variants } from "../../types/ProductDetails"; import { ProductDetails_product_variants } from "../../types/ProductDetails";
@ -88,10 +88,16 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
}: ProductVariantsProps) => ( }: ProductVariantsProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Variants")} title={intl.formatMessage({
defaultMessage: "Variants",
description: "section header"
})}
toolbar={ toolbar={
<> <>
<Button <Button
@ -100,7 +106,10 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
color="primary" color="primary"
data-tc="button-edit-attributes" data-tc="button-edit-attributes"
> >
{i18n.t("Edit attributes")} <FormattedMessage
defaultMessage="Edit attributes"
description="product variant attributes, button"
/>
</Button> </Button>
<Button <Button
onClick={onVariantAdd} onClick={onVariantAdd}
@ -108,16 +117,17 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
color="primary" color="primary"
data-tc="button-add-variant" data-tc="button-add-variant"
> >
{i18n.t("Add variant")} <FormattedMessage
defaultMessage="Add variant"
description="button"
/>
</Button> </Button>
</> </>
} }
/> />
<CardContent> <CardContent>
<Typography> <Typography>
{i18n.t( <FormattedMessage defaultMessage="Use variants for products that come in a variety of versions for example different sizes or colors" />
"Use variants for products that come in a variety of versions for example different sizes or colors"
)}
</Typography> </Typography>
</CardContent> </CardContent>
<Table className={classes.denseTable}> <Table className={classes.denseTable}>
@ -129,14 +139,27 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}>{i18n.t("Name")}</TableCell> <TableCell className={classes.colName}>
<TableCell className={classes.colStatus}> <FormattedMessage
{i18n.t("Status")} defaultMessage="Name"
description="product variant name"
/>
</TableCell>
<TableCell className={classes.colStatus}>
<FormattedMessage
defaultMessage="Status"
description="product variant status"
/>
</TableCell>
<TableCell className={classes.colSku}>
<FormattedMessage defaultMessage="SKU" />
</TableCell> </TableCell>
<TableCell className={classes.colSku}>{i18n.t("SKU")}</TableCell>
<Hidden smDown> <Hidden smDown>
<TableCell className={classes.colPrice}> <TableCell className={classes.colPrice}>
{i18n.t("Price")} <FormattedMessage
defaultMessage="Price"
description="product variant price"
/>
</TableCell> </TableCell>
</Hidden> </Hidden>
</TableHead> </TableHead>
@ -168,11 +191,19 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
<TableCell className={classes.colStatus}> <TableCell className={classes.colStatus}>
{variant ? ( {variant ? (
<StatusLabel <StatusLabel
status={variant.stockQuantity > 0 ? "success" : "error"} status={
variant.stockQuantity > 0 ? "success" : "error"
}
label={ label={
variant.stockQuantity > 0 variant.stockQuantity > 0
? i18n.t("Available") ? intl.formatMessage({
: i18n.t("Unavailable") defaultMessage: "Available",
description: "product variant status"
})
: intl.formatMessage({
defaultMessage: "Unavailable",
description: "product variant status"
})
} }
/> />
) : ( ) : (
@ -203,7 +234,7 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{i18n.t("This product has no variants")} <FormattedMessage defaultMessage="This product has no variants" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
@ -211,7 +242,8 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })(
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) );
}
); );
ProductVariants.displayName = "ProductVariants"; ProductVariants.displayName = "ProductVariants";
export default ProductVariants; export default ProductVariants;

View file

@ -1,9 +1,10 @@
import { parse as parseQs } from "qs"; import { parse as parseQs } from "qs";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import i18n from "../i18n";
import { import {
productAddPath, productAddPath,
productImagePath, productImagePath,
@ -86,9 +87,12 @@ const ProductVariantCreate: React.StatelessComponent<
); );
}; };
const Component = () => ( const Component = () => {
const intl = useIntl();
return (
<> <>
<WindowTitle title={i18n.t("Products")} /> <WindowTitle title={intl.formatMessage(sectionNames.products)} />
<Switch> <Switch>
<Route exact path={productListPath} component={ProductList} /> <Route exact path={productListPath} component={ProductList} />
<Route exact path={productAddPath} component={ProductCreate} /> <Route exact path={productAddPath} component={ProductCreate} />
@ -108,6 +112,7 @@ const Component = () => (
<Route path={productPath(":id")} component={ProductUpdate} /> <Route path={productPath(":id")} component={ProductUpdate} />
</Switch> </Switch>
</> </>
); );
};
export default Component; export default Component;

View file

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
@ -7,7 +8,6 @@ import useShop from "@saleor/hooks/useShop";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "../../config";
import SearchCategories from "../../containers/SearchCategories"; import SearchCategories from "../../containers/SearchCategories";
import SearchCollections from "../../containers/SearchCollections"; import SearchCollections from "../../containers/SearchCollections";
import i18n from "../../i18n";
import { decimal, getMutationState, maybe } from "../../misc"; import { decimal, getMutationState, maybe } from "../../misc";
import ProductCreatePage, { import ProductCreatePage, {
ProductCreatePageSubmitData ProductCreatePageSubmitData
@ -27,6 +27,7 @@ export const ProductUpdate: React.StatelessComponent<
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const shop = useShop(); const shop = useShop();
const intl = useIntl();
const handleAttributesEdit = undefined; const handleAttributesEdit = undefined;
const handleBack = () => navigate(productListUrl()); const handleBack = () => navigate(productListUrl());
@ -41,7 +42,9 @@ export const ProductUpdate: React.StatelessComponent<
const handleSuccess = (data: ProductCreate) => { const handleSuccess = (data: ProductCreate) => {
if (data.productCreate.errors.length === 0) { if (data.productCreate.errors.length === 0) {
notify({ notify({
text: i18n.t("Product created") text: intl.formatMessage({
defaultMessage: "Product created"
})
}); });
navigate(productUrl(data.productCreate.product.id)); navigate(productUrl(data.productCreate.product.id));
} else { } else {
@ -109,7 +112,12 @@ export const ProductUpdate: React.StatelessComponent<
); );
return ( return (
<> <>
<WindowTitle title={i18n.t("Create product")} /> <WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Product",
description: "window title"
})}
/>
<ProductCreatePage <ProductCreatePage
currency={maybe(() => shop.defaultCurrency)} currency={maybe(() => shop.defaultCurrency)}
categories={maybe( categories={maybe(
@ -127,7 +135,10 @@ export const ProductUpdate: React.StatelessComponent<
)} )}
fetchCategories={searchCategory} fetchCategories={searchCategory}
fetchCollections={searchCollection} fetchCollections={searchCollection}
header={i18n.t("New Product")} header={intl.formatMessage({
defaultMessage: "New Product",
description: "page header"
})}
productTypes={maybe(() => productTypes={maybe(() =>
data.productTypes.edges.map(edge => edge.node) data.productTypes.edges.map(edge => edge.node)
)} )}

View file

@ -1,10 +1,10 @@
import DialogContentText from "@material-ui/core/DialogContentText"; import DialogContentText from "@material-ui/core/DialogContentText";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import i18n from "../../i18n";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../misc";
import ProductImagePage from "../components/ProductImagePage"; import ProductImagePage from "../components/ProductImagePage";
import { import {
@ -32,6 +32,7 @@ export const ProductImage: React.StatelessComponent<ProductImageProps> = ({
}) => { }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl();
const handleBack = () => navigate(productUrl(productId)); const handleBack = () => navigate(productUrl(productId));
const handleUpdateSuccess = (data: ProductImageUpdate) => { const handleUpdateSuccess = (data: ProductImageUpdate) => {
@ -103,19 +104,15 @@ export const ProductImage: React.StatelessComponent<ProductImageProps> = ({
} }
onConfirm={handleDelete} onConfirm={handleDelete}
open={params.action === "remove"} open={params.action === "remove"}
title={i18n.t("Remove image", { title={intl.formatMessage({
context: "modal title" defaultMessage: "Remove Image",
description: "dialog header"
})} })}
variant="delete" variant="delete"
confirmButtonState={deleteTransitionState} confirmButtonState={deleteTransitionState}
> >
<DialogContentText> <DialogContentText>
{i18n.t( <FormattedMessage defaultMessage="Are you sure you want to remove this image?" />
"Are you sure you want to remove this image?",
{
context: "modal content"
}
)}
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
</> </>

View file

@ -3,6 +3,7 @@ import DialogContentText from "@material-ui/core/DialogContentText";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog"; import DeleteFilterTabDialog from "@saleor/components/DeleteFilterTabDialog";
@ -19,7 +20,6 @@ import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import i18n from "@saleor/i18n";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import ProductListPage from "../../components/ProductListPage"; import ProductListPage from "../../components/ProductListPage";
@ -67,6 +67,8 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
const { updateListSettings, settings } = useListSettings<ProductListColumns>( const { updateListSettings, settings } = useListSettings<ProductListColumns>(
ListViews.PRODUCT_LIST ListViews.PRODUCT_LIST
); );
const intl = useIntl();
const tabs = getFilterTabs(); const tabs = getFilterTabs();
const currentTab = const currentTab =
@ -155,7 +157,9 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
if (data.productBulkDelete.errors.length === 0) { if (data.productBulkDelete.errors.length === 0) {
closeModal(); closeModal();
notify({ notify({
text: i18n.t("Products removed") text: intl.formatMessage({
defaultMessage: "Products removed"
})
}); });
reset(); reset();
refetch(); refetch();
@ -166,7 +170,10 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
if (data.productBulkPublish.errors.length === 0) { if (data.productBulkPublish.errors.length === 0) {
closeModal(); closeModal();
notify({ notify({
text: i18n.t("Changed publication status") text: intl.formatMessage({
defaultMessage: "Changed publication status",
description: "product status update notification"
})
}); });
reset(); reset();
refetch(); refetch();
@ -235,13 +242,19 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
openModal("unpublish", listElements) openModal("unpublish", listElements)
} }
> >
{i18n.t("Unpublish")} <FormattedMessage
defaultMessage="Unpublish"
description="unpublish product, button"
/>
</Button> </Button>
<Button <Button
color="primary" color="primary"
onClick={() => openModal("publish", listElements)} onClick={() => openModal("publish", listElements)}
> >
{i18n.t("Publish")} <FormattedMessage
defaultMessage="Publish"
description="publish product, button"
/>
</Button> </Button>
<IconButton <IconButton
color="primary" color="primary"
@ -274,22 +287,29 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
variables: { ids: params.ids } variables: { ids: params.ids }
}) })
} }
title={i18n.t("Remove products")} title={intl.formatMessage({
defaultMessage: "Delete Products",
description: "dialog header"
})}
variant="delete" variant="delete"
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {counter, plural,
"Are you sure you want to remove <strong>{{ number }}</strong> products?", one {this product}
{ other {{displayQuantity} products}
number: maybe( }?"
() => params.ids.length.toString(), description="dialog content"
"..." values={{
) counter: maybe(() => params.ids.length),
} displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
open={params.action === "publish"} open={params.action === "publish"}
@ -303,21 +323,28 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
} }
}) })
} }
title={i18n.t("Publish products")} title={intl.formatMessage({
defaultMessage: "Publish Products",
description: "dialog header"
})}
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to publish {counter, plural,
"Are you sure you want to publish <strong>{{ number }}</strong> products?", one {this product}
{ other {{displayQuantity} products}
number: maybe( }?"
() => params.ids.length.toString(), description="dialog content"
"..." values={{
) counter: maybe(() => params.ids.length),
} displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
open={params.action === "unpublish"} open={params.action === "unpublish"}
@ -331,21 +358,28 @@ export const ProductList: React.StatelessComponent<ProductListProps> = ({
} }
}) })
} }
title={i18n.t("Unpublish products")} title={intl.formatMessage({
defaultMessage: "Unpublish Products",
description: "dialog header"
})}
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to unpublish {counter, plural,
"Are you sure you want to unpublish <strong>{{ number }}</strong> products?", one {this product}
{ other {{displayQuantity} products}
number: maybe( }?"
() => params.ids.length.toString(), description="dialog content"
"..." values={{
) counter: maybe(() => params.ids.length),
} displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<SaveFilterTabDialog <SaveFilterTabDialog
open={params.action === "save-search"} open={params.action === "save-search"}

View file

@ -2,6 +2,7 @@ import DialogContentText from "@material-ui/core/DialogContentText";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import placeholderImg from "@assets/images/placeholder255x255.png"; import placeholderImg from "@assets/images/placeholder255x255.png";
import ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
@ -9,10 +10,10 @@ import { WindowTitle } from "@saleor/components/WindowTitle";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import { commonMessages } from "@saleor/intl";
import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config"; import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config";
import SearchCategories from "../../../containers/SearchCategories"; import SearchCategories from "../../../containers/SearchCategories";
import SearchCollections from "../../../containers/SearchCollections"; import SearchCollections from "../../../containers/SearchCollections";
import i18n from "../../../i18n";
import { getMutationState, maybe } from "../../../misc"; import { getMutationState, maybe } from "../../../misc";
import { productTypeUrl } from "../../../productTypes/urls"; import { productTypeUrl } from "../../../productTypes/urls";
import ProductUpdatePage from "../../components/ProductUpdatePage"; import ProductUpdatePage from "../../components/ProductUpdatePage";
@ -53,6 +54,7 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids params.ids
); );
const intl = useIntl();
const openModal = (action: ProductUrlDialog) => const openModal = (action: ProductUrlDialog) =>
navigate( navigate(
@ -73,12 +75,18 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
> >
{({ data, loading, refetch }) => { {({ data, loading, refetch }) => {
const handleDelete = () => { const handleDelete = () => {
notify({ text: i18n.t("Product removed") }); notify({
text: intl.formatMessage({
defaultMessage: "Product removed"
})
});
navigate(productListUrl()); navigate(productListUrl());
}; };
const handleUpdate = (data: ProductUpdateMutationResult) => { const handleUpdate = (data: ProductUpdateMutationResult) => {
if (data.productUpdate.errors.length === 0) { if (data.productUpdate.errors.length === 0) {
notify({ text: i18n.t("Saved changes") }); notify({
text: intl.formatMessage(commonMessages.savedChanges)
});
} else { } else {
const attributeError = data.productUpdate.errors.find( const attributeError = data.productUpdate.errors.find(
err => err.field === "attributes" err => err.field === "attributes"
@ -103,7 +111,7 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
}; };
const handleImageDeleteSuccess = () => const handleImageDeleteSuccess = () =>
notify({ notify({
text: i18n.t("Image successfully deleted") text: intl.formatMessage(commonMessages.savedChanges)
}); });
const handleVariantAdd = () => const handleVariantAdd = () =>
navigate(productVariantAddUrl(id)); navigate(productVariantAddUrl(id));
@ -276,18 +284,20 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
confirmButtonState={deleteTransitionState} confirmButtonState={deleteTransitionState}
onConfirm={() => deleteProduct.mutate({ id })} onConfirm={() => deleteProduct.mutate({ id })}
variant="delete" variant="delete"
title={i18n.t("Remove product")} title={intl.formatMessage({
defaultMessage: "Delete Product",
description: "dialog header"
})}
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {name}?"
"Are you sure you want to remove <strong>{{ name }}</strong>?", description="delete product"
{ values={{
name: product ? product.name : undefined name: product ? product.name : undefined
}
)
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
open={params.action === "remove-variants"} open={params.action === "remove-variants"}
@ -301,21 +311,28 @@ export const ProductUpdate: React.StatelessComponent<ProductUpdateProps> = ({
}) })
} }
variant="delete" variant="delete"
title={i18n.t("Remove product variants")} title={intl.formatMessage({
defaultMessage: "Delete Product Variants",
description: "dialog header"
})}
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {counter, plural,
"Are you sure you want to remove <strong>{{ number }}</strong> variants?", one {this variant}
{ other {{displayQuantity} variants}
number: maybe( }?"
() => params.ids.length.toString(), description="dialog content"
"..." values={{
) counter: maybe(() => params.ids.length),
} displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
</> </>
); );

View file

@ -1,10 +1,11 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import placeholderImg from "@assets/images/placeholder255x255.png"; import placeholderImg from "@assets/images/placeholder255x255.png";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import i18n from "../../i18n"; import { commonMessages } from "@saleor/intl";
import { decimal, getMutationState, maybe } from "../../misc"; import { decimal, getMutationState, maybe } from "../../misc";
import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog"; import ProductVariantDeleteDialog from "../components/ProductVariantDeleteDialog";
import ProductVariantPage, { import ProductVariantPage, {
@ -33,6 +34,7 @@ export const ProductVariant: React.StatelessComponent<ProductUpdateProps> = ({
}) => { }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl();
return ( return (
<TypedProductVariantQuery <TypedProductVariantQuery
@ -44,12 +46,16 @@ export const ProductVariant: React.StatelessComponent<ProductUpdateProps> = ({
const variant = data ? data.productVariant : undefined; const variant = data ? data.productVariant : undefined;
const handleBack = () => navigate(productUrl(productId)); const handleBack = () => navigate(productUrl(productId));
const handleDelete = () => { const handleDelete = () => {
notify({ text: i18n.t("Variant removed") }); notify({
text: intl.formatMessage({
defaultMessage: "Variant removed"
})
});
navigate(productUrl(productId)); navigate(productUrl(productId));
}; };
const handleUpdate = (data: VariantUpdate) => { const handleUpdate = (data: VariantUpdate) => {
if (!maybe(() => data.productVariantUpdate.errors.length)) { if (!maybe(() => data.productVariantUpdate.errors.length)) {
notify({ text: i18n.t("Changes saved") }); notify({ text: intl.formatMessage(commonMessages.savedChanges) });
} }
}; };

View file

@ -1,10 +1,10 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
import useShop from "@saleor/hooks/useShop"; import useShop from "@saleor/hooks/useShop";
import i18n from "../../i18n";
import { decimal, getMutationState, maybe } from "../../misc"; import { decimal, getMutationState, maybe } from "../../misc";
import ProductVariantCreatePage, { import ProductVariantCreatePage, {
ProductVariantCreatePageSubmitData ProductVariantCreatePageSubmitData
@ -24,6 +24,7 @@ export const ProductVariant: React.StatelessComponent<ProductUpdateProps> = ({
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const shop = useShop(); const shop = useShop();
const intl = useIntl();
return ( return (
<TypedProductVariantCreateQuery <TypedProductVariantCreateQuery
@ -34,7 +35,11 @@ export const ProductVariant: React.StatelessComponent<ProductUpdateProps> = ({
{({ data, loading: productLoading }) => { {({ data, loading: productLoading }) => {
const handleCreateSuccess = (data: VariantCreate) => { const handleCreateSuccess = (data: VariantCreate) => {
if (data.productVariantCreate.errors.length === 0) { if (data.productVariantCreate.errors.length === 0) {
notify({ text: i18n.t("Product created") }); notify({
text: intl.formatMessage({
defaultMessage: "Product created"
})
});
navigate( navigate(
productVariantEditUrl( productVariantEditUrl(
productId, productId,
@ -81,7 +86,12 @@ export const ProductVariant: React.StatelessComponent<ProductUpdateProps> = ({
); );
return ( return (
<> <>
<WindowTitle title={i18n.t("Create variant")} /> <WindowTitle
title={intl.formatMessage({
defaultMessage: "Create Variant",
description: "window title"
})}
/>
<ProductVariantCreatePage <ProductVariantCreatePage
currencySymbol={maybe(() => shop.defaultCurrency)} currencySymbol={maybe(() => shop.defaultCurrency)}
errors={maybe( errors={maybe(
@ -89,7 +99,10 @@ export const ProductVariant: React.StatelessComponent<ProductUpdateProps> = ({
variantCreateResult.data.productVariantCreate.errors, variantCreateResult.data.productVariantCreate.errors,
[] []
)} )}
header={i18n.t("Add Variant")} header={intl.formatMessage({
defaultMessage: "Add Variant",
description: "header"
})}
loading={disableForm} loading={disableForm}
product={maybe(() => data.product)} product={maybe(() => data.product)}
onBack={handleBack} onBack={handleBack}