Merge pull request #105 from mirumee/next/intl

Use react-intl
This commit is contained in:
Dominik Żegleń 2019-08-29 15:01:41 +02:00 committed by GitHub
commit 6a10582c6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
333 changed files with 21241 additions and 8778 deletions

2
.gitignore vendored
View file

@ -19,8 +19,8 @@
!.tx !.tx
*.css *.css
*.log *.log
*.pot
*.pyc *.pyc
*.mo
local_settings.py local_settings.py
__pycache__/ __pycache__/
build/ build/

View file

@ -9,3 +9,4 @@ All notable, unreleased changes to this project will be documented in this file.
- Fix configure menu section - #109 by @benekex2 - Fix configure menu section - #109 by @benekex2
- Add switch to make attribute available in product list as a column - #99 by @dominik-zeglen - Add switch to make attribute available in product list as a column - #99 by @dominik-zeglen
- Add tc tags for E2E testing - #134 by @dominik-zeglen - Add tc tags for E2E testing - #134 by @dominik-zeglen
- Use react-intl - #105 by @dominik-zeglen

48
babel.config.js Normal file
View file

@ -0,0 +1,48 @@
module.exports = api => {
const isTest = api.env("test");
const isStorybook = api.env("storybook");
const ignore =
isTest || isStorybook
? []
: ["**/*.test.ts", "**/*.test.tsx", "src/storybook"];
const presets = [
[
"@babel/preset-env",
{
corejs: "3.2.1",
modules: isTest ? "auto" : false,
useBuiltIns: "usage"
}
],
"@babel/preset-react",
"@babel/preset-typescript"
];
const plugins = [
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-class-properties",
[
"@babel/plugin-proposal-decorators",
{
decoratorsBeforeExport: true
}
],
"@babel/plugin-proposal-object-rest-spread",
"react-intl-auto",
[
"react-intl",
{
extractFromFormatMessageCall: true,
messagesDir: "build/locale/"
}
]
];
return {
presets,
plugins,
ignore
};
};

8316
locale/messages.pot Normal file

File diff suppressed because it is too large Load diff

308
package-lock.json generated
View file

@ -97,6 +97,24 @@
"@apollographql/graphql-language-service-types": "^2.0.0" "@apollographql/graphql-language-service-types": "^2.0.0"
} }
}, },
"@babel/cli": {
"version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.5.5.tgz",
"integrity": "sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==",
"dev": true,
"requires": {
"chokidar": "^2.0.4",
"commander": "^2.8.1",
"convert-source-map": "^1.1.0",
"fs-readdir-recursive": "^1.1.0",
"glob": "^7.0.0",
"lodash": "^4.17.13",
"mkdirp": "^0.5.1",
"output-file-sync": "^2.0.0",
"slash": "^2.0.0",
"source-map": "^0.5.0"
}
},
"@babel/code-frame": { "@babel/code-frame": {
"version": "7.5.5", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
@ -435,6 +453,16 @@
"@babel/plugin-syntax-json-strings": "^7.2.0" "@babel/plugin-syntax-json-strings": "^7.2.0"
} }
}, },
"@babel/plugin-proposal-numeric-separator": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.2.0.tgz",
"integrity": "sha512-DohMOGDrZiMKS7LthjUZNNcWl8TAf5BZDwZAH4wpm55FuJTHgfqPGdibg7rZDmont/8Yg0zA03IgT6XLeP+4sg==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-syntax-numeric-separator": "^7.2.0"
}
},
"@babel/plugin-proposal-object-rest-spread": { "@babel/plugin-proposal-object-rest-spread": {
"version": "7.5.5", "version": "7.5.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz",
@ -520,6 +548,15 @@
"@babel/helper-plugin-utils": "^7.0.0" "@babel/helper-plugin-utils": "^7.0.0"
} }
}, },
"@babel/plugin-syntax-numeric-separator": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.2.0.tgz",
"integrity": "sha512-DroeVNkO/BnGpL2R7+ZNZqW+E24aR/4YWxP3Qb15d6lPU8KDzF8HlIUIRCOJRn4X77/oyW4mJY+7FHfY82NLtQ==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.0.0"
}
},
"@babel/plugin-syntax-object-rest-spread": { "@babel/plugin-syntax-object-rest-spread": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz",
@ -1225,6 +1262,11 @@
"tslib": "^1" "tslib": "^1"
} }
}, },
"@formatjs/intl-relativetimeformat": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-2.6.3.tgz",
"integrity": "sha512-sb3PcbTSNQfnL4HM2XKedt8Oopf2EwaoxyjeMbgvje6x1zQuf1oTAidLHT65Dkox7hfSql8ZjpAqWqnHJsLN9w=="
},
"@heroku-cli/color": { "@heroku-cli/color": {
"version": "1.1.14", "version": "1.1.14",
"resolved": "https://registry.npmjs.org/@heroku-cli/color/-/color-1.1.14.tgz", "resolved": "https://registry.npmjs.org/@heroku-cli/color/-/color-1.1.14.tgz",
@ -2820,11 +2862,10 @@
"hoist-non-react-statics": "^3.3.0" "hoist-non-react-statics": "^3.3.0"
} }
}, },
"@types/i18next": { "@types/invariant": {
"version": "8.4.6", "version": "2.2.30",
"resolved": "https://registry.npmjs.org/@types/i18next/-/i18next-8.4.6.tgz", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.30.tgz",
"integrity": "sha512-ZiSCqW8j9/gQCYixz1nMhyCprSGh3rwdyX+FHAzEN+bMCmc7yCYjNutl6jvMYSxSIGeL0CLEXPM8Nlk2lE0t5w==", "integrity": "sha512-98fB+yo7imSD2F7PF7GIpELNgtLNgo5wjivu0W5V4jx+KVVJxo6p/qN4zdzSTBWy4/sN3pPyXwnhRSD28QX+ag=="
"dev": true
}, },
"@types/istanbul-lib-coverage": { "@types/istanbul-lib-coverage": {
"version": "2.0.1", "version": "2.0.1",
@ -4827,6 +4868,42 @@
} }
} }
}, },
"babel-plugin-react-intl": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-4.1.12.tgz",
"integrity": "sha512-dCw7LjfDCS03ugW0Oz1XG8J/OgAnEDsKqcyfzlDb4JL6aLiYz87XvM9sIdbMAdPZXaHLhj9GyK8CueyMIm66NQ==",
"dev": true,
"requires": {
"@babel/core": "^7.4.5",
"@babel/helper-plugin-utils": "^7.0.0",
"@types/babel__core": "^7.1.2",
"fs-extra": "^8.0.1",
"intl-messageformat-parser": "^3.0.7"
},
"dependencies": {
"fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
}
}
},
"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==",
"dev": true,
"requires": {
"@babel/types": "^7.5.5",
"murmurhash3js": "^3.0.1"
}
},
"babel-plugin-syntax-async-functions": { "babel-plugin-syntax-async-functions": {
"version": "6.13.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
@ -7126,9 +7203,10 @@
} }
}, },
"core-js": { "core-js": {
"version": "1.2.7", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.2.1.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" "integrity": "sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==",
"dev": true
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.2.0", "version": "3.2.0",
@ -8893,6 +8971,13 @@
"promise": "^7.1.1", "promise": "^7.1.1",
"setimmediate": "^1.0.5", "setimmediate": "^1.0.5",
"ua-parser-js": "^0.7.18" "ua-parser-js": "^0.7.18"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY="
}
} }
}, },
"figgy-pudding": { "figgy-pudding": {
@ -9284,6 +9369,12 @@
"universalify": "^0.1.0" "universalify": "^0.1.0"
} }
}, },
"fs-readdir-recursive": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz",
"integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==",
"dev": true
},
"fs-write-stream-atomic": { "fs-write-stream-atomic": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@ -9899,6 +9990,15 @@
"assert-plus": "^1.0.0" "assert-plus": "^1.0.0"
} }
}, },
"gettext-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/gettext-parser/-/gettext-parser-1.1.0.tgz",
"integrity": "sha1-LFpmONiTk0ubVQN9CtgstwBLJnk=",
"dev": true,
"requires": {
"encoding": "^0.1.11"
}
},
"git-parse": { "git-parse": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/git-parse/-/git-parse-1.0.3.tgz", "resolved": "https://registry.npmjs.org/git-parse/-/git-parse-1.0.3.tgz",
@ -10247,6 +10347,12 @@
} }
} }
}, },
"has-color": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz",
"integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=",
"dev": true
},
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -10809,21 +10915,6 @@
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
}, },
"i18next": {
"version": "11.10.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-11.10.2.tgz",
"integrity": "sha512-1rowdX8PqrvsdFhYb3v0A/LlIHLQL1HTa4ia29IzhvNAg2fesNV7R1jXibWLmLQdz3FfTB8RuqSqDEjIawXruA=="
},
"i18next-browser-languagedetector": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-2.2.4.tgz",
"integrity": "sha512-wPbtH18FdOuB245I8Bhma5/XSDdN/HpYlX+wga1eMy+slhaFQSnrWX6fp+aYSL2eEuj0RlfHeEVz6Fo/lxAj6A=="
},
"i18next-xhr-backend": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-1.5.1.tgz",
"integrity": "sha512-9OLdC/9YxDvTFcgsH5t2BHCODHEotHCa6h7Ly0EUlUC7Y2GS09UeoHOGj3gWKQ3HCqXz8NlH4gOrK3NNc9vPuw=="
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -11103,6 +11194,30 @@
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
}, },
"intl-format-cache": {
"version": "4.1.10",
"resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-4.1.10.tgz",
"integrity": "sha512-7NqorxPNPuhbtwlXe71Dbjh9NlLvkoUymCI2AS/cyIsG7wYr27x9E/h4P16vftHwsTOjiDIjM/oGukddxgcz3A=="
},
"intl-locales-supported": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/intl-locales-supported/-/intl-locales-supported-1.4.5.tgz",
"integrity": "sha512-D7oriM5x46rd7kNlSW0f9noIBegFr3ReIM6xlMpwH4lfIPD/zvBelPlCjR10IK16boGJG9lKccOvRAM8wzpbrA=="
},
"intl-messageformat": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-6.1.5.tgz",
"integrity": "sha512-Ae/3PwnShCkDtnPvx9FLlBj1xiooa5NeeFZBizOQZZ/iaLt8IvgkPCdadkOF3f++FWDOBVtP5RszhMkJQKqmng==",
"requires": {
"intl-format-cache": "^4.1.10",
"intl-messageformat-parser": "^3.0.7"
}
},
"intl-messageformat-parser": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-3.0.7.tgz",
"integrity": "sha512-L16VbbV3NFaiZV65XwOIH9fBe52TS2EkOR0k8Y4ratsgTE7KPEbcUCUrz/iEQwJo7BcWY4ohkZbeYZRgAiPR1Q=="
},
"into-stream": { "into-stream": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz",
@ -13800,6 +13915,12 @@
"integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
"dev": true "dev": true
}, },
"murmurhash3js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/murmurhash3js/-/murmurhash3js-3.0.1.tgz",
"integrity": "sha1-Ppg+W0fCoG9DpxMXTn5DXKBEuZg=",
"dev": true
},
"mustache": { "mustache": {
"version": "2.3.2", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz", "resolved": "https://registry.npmjs.org/mustache/-/mustache-2.3.2.tgz",
@ -14100,6 +14221,41 @@
"integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==", "integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==",
"dev": true "dev": true
}, },
"nomnom": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz",
"integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=",
"dev": true,
"requires": {
"chalk": "~0.4.0",
"underscore": "~1.6.0"
},
"dependencies": {
"ansi-styles": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz",
"integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=",
"dev": true
},
"chalk": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz",
"integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=",
"dev": true,
"requires": {
"ansi-styles": "~1.0.0",
"has-color": "~0.1.0",
"strip-ansi": "~0.1.0"
}
},
"strip-ansi": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz",
"integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=",
"dev": true
}
}
},
"normalize-package-data": { "normalize-package-data": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@ -14551,6 +14707,17 @@
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true "dev": true
}, },
"output-file-sync": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz",
"integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.11",
"is-plain-obj": "^1.1.0",
"mkdirp": "^0.5.1"
}
},
"p-cancelable": { "p-cancelable": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz",
@ -15110,6 +15277,16 @@
"ts-pnp": "^1.1.2" "ts-pnp": "^1.1.2"
} }
}, },
"po2json": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
"integrity": "sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=",
"dev": true,
"requires": {
"gettext-parser": "1.1.0",
"nomnom": "1.8.1"
}
},
"polished": { "polished": {
"version": "3.4.1", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/polished/-/polished-3.4.1.tgz", "resolved": "https://registry.npmjs.org/polished/-/polished-3.4.1.tgz",
@ -16077,6 +16254,47 @@
"once": "^1.4.0" "once": "^1.4.0"
} }
}, },
"react-intl": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/react-intl/-/react-intl-3.1.8.tgz",
"integrity": "sha512-kUQvTfDvMpgvwDldsmTy/XQPPgaSquh3+mL0iEspKkGqtAM9J9p25jNy8+cP0w/Y5LfWQFniUjlEdCDznoTZ/Q==",
"requires": {
"@formatjs/intl-relativetimeformat": "^2.6.3",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/invariant": "^2.2.30",
"@types/react": "^16.0.0",
"hoist-non-react-statics": "^3.3.0",
"intl-format-cache": "^4.1.10",
"intl-locales-supported": "^1.4.5",
"intl-messageformat": "^6.1.5",
"intl-messageformat-parser": "^3.0.7",
"invariant": "^2.1.1",
"react": "^16.3.0",
"shallow-equal": "^1.1.0"
}
},
"react-intl-po": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-intl-po/-/react-intl-po-2.2.2.tgz",
"integrity": "sha512-BvW4gyohm4Ac0sSN6dBJB3+PeIjsns+IE9JqT/7NolBRGNc+fCKzWa5e1eZdhu9nY+7W1t0sWqhrt+LXgXb6Iw==",
"dev": true,
"requires": {
"chalk": "^2.3.2",
"commander": "^2.15.1",
"glob": "^7.1.2",
"mkdirp": "^0.5.1",
"po2json": "^0.4.5",
"ramda": "^0.25.0"
},
"dependencies": {
"ramda": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz",
"integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==",
"dev": true
}
}
},
"react-is": { "react-is": {
"version": "16.9.0", "version": "16.9.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz",
@ -16848,9 +17066,9 @@
"dev": true "dev": true
}, },
"rimraf": { "rimraf": {
"version": "2.6.3", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.0.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "integrity": "sha512-4Liqw7ccABzsWV5BzeZeGRSq7KWIgQYzOcmRDEwSX4WAawlQpcAFXZ1Kid72XYrjSnK5yxOS6Gez/iGusYE/Pw==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "^7.1.3" "glob": "^7.1.3"
@ -17243,8 +17461,7 @@
"shallow-equal": { "shallow-equal": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.0.tgz", "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.0.tgz",
"integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA==", "integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA=="
"dev": true
}, },
"shallowequal": { "shallowequal": {
"version": "1.1.0", "version": "1.1.0",
@ -18881,6 +19098,14 @@
"dev": true, "dev": true,
"requires": { "requires": {
"core-js": "^1.0.0" "core-js": "^1.0.0"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
"dev": true
}
} }
}, },
"pify": { "pify": {
@ -19022,6 +19247,14 @@
"dev": true, "dev": true,
"requires": { "requires": {
"core-js": "^1.0.0" "core-js": "^1.0.0"
},
"dependencies": {
"core-js": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
"dev": true
}
} }
}, },
"dedent": { "dedent": {
@ -19375,19 +19608,6 @@
} }
} }
}, },
"ts-loader": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-5.4.5.tgz",
"integrity": "sha512-XYsjfnRQCBum9AMRZpk2rTYSVpdZBpZK+kDh0TeT3kxmQNBDVIeUjdPjY5RZry4eIAb8XHc4gYSUiUWPYvzSRw==",
"dev": true,
"requires": {
"chalk": "^2.3.0",
"enhanced-resolve": "^4.0.0",
"loader-utils": "^1.0.2",
"micromatch": "^3.1.4",
"semver": "^5.0.1"
}
},
"ts-node": { "ts-node": {
"version": "8.3.0", "version": "8.3.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz",
@ -19603,6 +19823,12 @@
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=",
"dev": true "dev": true
}, },
"underscore": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
},
"unicode-canonical-property-names-ecmascript": { "unicode-canonical-property-names-ecmascript": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",

View file

@ -39,9 +39,6 @@
"fuzzaldrin": "^2.1.0", "fuzzaldrin": "^2.1.0",
"graphql": "^14.4.2", "graphql": "^14.4.2",
"graphql-tag": "^2.10.1", "graphql-tag": "^2.10.1",
"i18next": "^11.10.2",
"i18next-browser-languagedetector": "^2.2.4",
"i18next-xhr-backend": "^1.5.1",
"is-url": "^1.2.4", "is-url": "^1.2.4",
"jss": "^9.8.7", "jss": "^9.8.7",
"keycode": "^2.2.0", "keycode": "^2.2.0",
@ -56,6 +53,7 @@
"react-helmet": "^5.2.1", "react-helmet": "^5.2.1",
"react-infinite-scroller": "^1.2.4", "react-infinite-scroller": "^1.2.4",
"react-inlinesvg": "^0.8.4", "react-inlinesvg": "^0.8.4",
"react-intl": "^3.1.8",
"react-jss": "^8.6.1", "react-jss": "^8.6.1",
"react-moment": "^0.7.9", "react-moment": "^0.7.9",
"react-router": "^5.0.1", "react-router": "^5.0.1",
@ -70,12 +68,15 @@
"use-react-router": "^1.0.7" "use-react-router": "^1.0.7"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.4", "@babel/core": "^7.5.4",
"@babel/plugin-proposal-class-properties": "^7.5.0", "@babel/plugin-proposal-class-properties": "^7.5.0",
"@babel/plugin-proposal-decorators": "^7.4.4", "@babel/plugin-proposal-decorators": "^7.4.4",
"@babel/plugin-proposal-numeric-separator": "^7.2.0",
"@babel/plugin-proposal-object-rest-spread": "^7.5.4", "@babel/plugin-proposal-object-rest-spread": "^7.5.4",
"@babel/preset-env": "^7.5.4", "@babel/preset-env": "^7.5.4",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@babel/runtime": "^7.5.4", "@babel/runtime": "^7.5.4",
"@storybook/addon-storyshots": "^5.1.9", "@storybook/addon-storyshots": "^5.1.9",
"@storybook/react": "^5.1.9", "@storybook/react": "^5.1.9",
@ -84,7 +85,6 @@
"@types/draft-js": "^0.10.34", "@types/draft-js": "^0.10.34",
"@types/enzyme": "^3.10.2", "@types/enzyme": "^3.10.2",
"@types/fuzzaldrin": "^2.1.2", "@types/fuzzaldrin": "^2.1.2",
"@types/i18next": "^8.4.6",
"@types/jest": "^23.3.14", "@types/jest": "^23.3.14",
"@types/lodash-es": "^4.17.3", "@types/lodash-es": "^4.17.3",
"@types/moment-timezone": "^0.5.12", "@types/moment-timezone": "^0.5.12",
@ -104,7 +104,10 @@
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.6.0", "babel-jest": "^23.6.0",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-react-intl": "^4.1.12",
"babel-plugin-react-intl-auto": "^2.2.0",
"codecov": "^3.5.0", "codecov": "^3.5.0",
"core-js": "^3.2.1",
"enzyme": "^3.10.0", "enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.3.5", "enzyme-to-json": "^3.3.5",
@ -114,11 +117,12 @@
"jest": "^24.8.0", "jest": "^24.8.0",
"jest-file": "^1.0.0", "jest-file": "^1.0.0",
"plop": "^2.4.0", "plop": "^2.4.0",
"react-intl-po": "^2.2.2",
"react-test-renderer": "^16.8.6", "react-test-renderer": "^16.8.6",
"regenerator-runtime": "^0.11.1", "regenerator-runtime": "^0.11.1",
"rimraf": "^2.7.0",
"testcafe": "^1.3.3", "testcafe": "^1.3.3",
"ts-jest": "^23.10.5", "ts-jest": "^23.10.5",
"ts-loader": "^5.4.5",
"tsconfig-paths-webpack-plugin": "^3.2.0", "tsconfig-paths-webpack-plugin": "^3.2.0",
"tslint": "^5.18.0", "tslint": "^5.18.0",
"tslint-config-prettier": "^1.18.0", "tslint-config-prettier": "^1.18.0",
@ -130,30 +134,9 @@
"optionalDependencies": { "optionalDependencies": {
"fsevents": "^1.2.9" "fsevents": "^1.2.9"
}, },
"babel": {
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
[
"@babel/plugin-proposal-decorators",
{
"decoratorsBeforeExport": true
}
],
"@babel/plugin-proposal-object-rest-spread"
]
},
"jest": { "jest": {
"globals": {
"ts-jest": {
"tsConfig": "tsconfig.json"
}
},
"transform": { "transform": {
"^.+\\.tsx?$": "ts-jest", "^.+\\.(jsx?|tsx?)$": "babel-jest",
"^.+\\.(png|svg|jpe?g)$": "jest-file" "^.+\\.(png|svg|jpe?g)$": "jest-file"
}, },
"testRegex": ".*\\.test\\.tsx?$", "testRegex": ".*\\.test\\.tsx?$",
@ -173,11 +156,15 @@
}, },
"scripts": { "scripts": {
"build": "webpack -p", "build": "webpack -p",
"extract-json-messages": "rimraf build/locale && babel src 'src/**/*.{ts,tsx}' -o build/dashboard.bundle.js",
"extract-pot-messages": "rip json2pot \"build/locale/**/*.json\" -c description -o locale/messages.pot",
"extract-messages": "npm run extract-json-messages && npm run extract-pot-messages",
"build-messages": "rip po2json 'locale/**/*.po' -m 'build/locale/**/*.json' -o 'locale' -c 'description'",
"build-types": "apollo client:codegen --target=typescript types --globalTypesFile=src/types/globalTypes.ts", "build-types": "apollo client:codegen --target=typescript types --globalTypesFile=src/types/globalTypes.ts",
"generate-component": "plop --plopfile .plop/plopfile.js", "generate-component": "plop --plopfile .plop/plopfile.js",
"lint": "tslint 'src/**/*.{ts,tsx}'", "lint": "tslint 'src/**/*.{ts,tsx}'",
"lint-fix": "tslint 'src/**/*.{ts,tsx}' --fix", "lint-fix": "tslint 'src/**/*.{ts,tsx}' --fix",
"start": "webpack-dev-server --open", "start": "webpack-dev-server --open -d",
"storybook": "start-storybook -p 3000 -c src/storybook/", "storybook": "start-storybook -p 3000 -c src/storybook/",
"build-storybook": "build-storybook -c src/storybook/ -o build/storybook", "build-storybook": "build-storybook -c src/storybook/ -o build/storybook",
"test": "jest src/", "test": "jest src/",

54
react-intl.d.ts vendored Normal file
View file

@ -0,0 +1,54 @@
declare module "react-intl" {
import * as ReactIntl from "node_modules/react-intl";
export * from "node_modules/react-intl";
export interface MessageDescriptor {
description?: string;
defaultMessage: string;
id?: string;
}
type Messages<Names extends keyof any = string> = Record<
Names,
MessageDescriptor
>;
type PrimitiveType = string | number | boolean | null | undefined | Date;
type FormatXMLElementFn = (...args: any[]) => string | object;
export interface IntlFormatters
extends Omit<ReactIntl.IntlFormatters, "formatMessage"> {
formatMessage(
descriptor: MessageDescriptor,
values?: Record<string, PrimitiveType>
): string;
formatMessage(
descriptor: MessageDescriptor,
values?: Record<
string,
PrimitiveType | React.ReactElement | FormatXMLElementFn
>
): string | React.ReactNodeArray;
}
export interface FormattedMessageProps<
V extends Record<string, any> = Record<string, React.ReactNode>
> extends MessageDescriptor {
values?: V;
tagName?: React.ElementType<any>;
children?(...nodes: React.ReactNodeArray): React.ReactNode;
}
export function defineMessages<Names extends keyof any>(
messageDescriptors: Messages<Names>
): Messages<Names>;
export interface IntlShape extends ReactIntl.IntlConfig, IntlFormatters {
formatters: ReactIntl.Formatters;
}
export class FormattedMessage<
TValues extends Record<string, any> = Record<
string,
PrimitiveType | React.ReactElement | FormatXMLElementFn
>
> extends React.Component<FormattedMessageProps<TValues>> {}
export function useIntl(): IntlShape;
}

View file

@ -1,13 +1,13 @@
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 { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import i18n from "../../../i18n";
export interface AttributeBulkDeleteDialogProps { export interface AttributeBulkDeleteDialogProps {
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
quantity: string; quantity: number;
open: boolean; open: boolean;
onConfirm: () => void; onConfirm: () => void;
onClose: () => void; onClose: () => void;
@ -15,26 +15,36 @@ export interface AttributeBulkDeleteDialogProps {
const AttributeBulkDeleteDialog: React.StatelessComponent< const AttributeBulkDeleteDialog: React.StatelessComponent<
AttributeBulkDeleteDialogProps AttributeBulkDeleteDialogProps
> = ({ confirmButtonState, quantity, onClose, onConfirm, open }) => ( > = ({ confirmButtonState, quantity, onClose, onConfirm, open }) => {
const intl = useIntl();
return (
<ActionDialog <ActionDialog
open={open} open={open}
confirmButtonState={confirmButtonState} confirmButtonState={confirmButtonState}
onClose={onClose} onClose={onClose}
onConfirm={onConfirm} onConfirm={onConfirm}
title={i18n.t("Remove attributes")} title={intl.formatMessage({
defaultMessage: "Delete attributes",
description: "dialog title"
})}
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>{{ quantity }}</strong> attributes?", one {this attribute}
{ other {{displayQuantity} attributes}
quantity }?"
} description="dialog content"
) values={{
counter: quantity,
displayQuantity: <strong>{quantity}</strong>
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
); );
};
AttributeBulkDeleteDialog.displayName = "AttributeBulkDeleteDialog"; AttributeBulkDeleteDialog.displayName = "AttributeBulkDeleteDialog";
export default AttributeBulkDeleteDialog; export default AttributeBulkDeleteDialog;

View file

@ -1,9 +1,9 @@
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 { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import i18n from "@saleor/i18n";
export interface AttributeDeleteDialogProps { export interface AttributeDeleteDialogProps {
confirmButtonState: ConfirmButtonTransitionState; confirmButtonState: ConfirmButtonTransitionState;
@ -19,27 +19,33 @@ const AttributeDeleteDialog: React.FC<AttributeDeleteDialogProps> = ({
onClose, onClose,
onConfirm, onConfirm,
open open
}) => ( }) => {
const intl = useIntl();
return (
<ActionDialog <ActionDialog
open={open} open={open}
onClose={onClose} onClose={onClose}
confirmButtonState={confirmButtonState} confirmButtonState={confirmButtonState}
onConfirm={onConfirm} onConfirm={onConfirm}
variant="delete" variant="delete"
title={i18n.t("Remove attribute")} title={intl.formatMessage({
defaultMessage: "Delete attribute",
description: "dialog title"
})}
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {attributeName}?"
"Are you sure you want to remove <strong>{{ name }}</strong>?", description="dialog content"
{ values={{
name attributeName: <strong>{name}</strong>
}
)
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
); );
};
AttributeDeleteDialog.displayName = "AttributeDeleteDialog"; AttributeDeleteDialog.displayName = "AttributeDeleteDialog";
export default AttributeDeleteDialog; export default AttributeDeleteDialog;

View file

@ -2,13 +2,14 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
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 slugify from "slugify"; import slugify from "slugify";
import CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import ControlledSwitch from "@saleor/components/ControlledSwitch"; import ControlledSwitch from "@saleor/components/ControlledSwitch";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import SingleSelectField from "@saleor/components/SingleSelectField"; import SingleSelectField from "@saleor/components/SingleSelectField";
import i18n from "@saleor/i18n"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "@saleor/types"; import { FormErrors } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
import { AttributePageFormData } from "../AttributePage"; import { AttributePageFormData } from "../AttributePage";
@ -21,31 +22,44 @@ export interface AttributeDetailsProps {
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
} }
const inputTypeChoices = [
{
label: i18n.t("Dropdown"),
value: AttributeInputTypeEnum.DROPDOWN
},
{
label: i18n.t("Multiple Select"),
value: AttributeInputTypeEnum.MULTISELECT
}
];
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
canChangeType, canChangeType,
data, data,
disabled, disabled,
errors, errors,
onChange onChange
}) => ( }) => {
const intl = useIntl();
const inputTypeChoices = [
{
label: intl.formatMessage({
defaultMessage: "Dropdown",
description: "product attribute type"
}),
value: AttributeInputTypeEnum.DROPDOWN
},
{
label: intl.formatMessage({
defaultMessage: "Multiple Select",
description: "product attribute type"
}),
value: AttributeInputTypeEnum.MULTISELECT
}
];
return (
<Card> <Card>
<CardTitle title={i18n.t("General Information")} /> <CardTitle
title={intl.formatMessage(commonMessages.generalInformations)}
/>
<CardContent> <CardContent>
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.name} error={!!errors.name}
label={i18n.t("Default Label")} label={intl.formatMessage({
defaultMessage: "Default Label",
description: "attribute's label"
})}
name={"name" as keyof AttributePageFormData} name={"name" as keyof AttributePageFormData}
fullWidth fullWidth
helperText={errors.name} helperText={errors.name}
@ -56,14 +70,19 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
<TextField <TextField
disabled={disabled} disabled={disabled}
error={!!errors.slug} error={!!errors.slug}
label={i18n.t("Attribute Code")} label={intl.formatMessage({
defaultMessage: "Attribute Code",
description: "attribute's slug short code label"
})}
name={"slug" as keyof AttributePageFormData} name={"slug" as keyof AttributePageFormData}
placeholder={slugify(data.name).toLowerCase()} placeholder={slugify(data.name).toLowerCase()}
fullWidth fullWidth
helperText={ helperText={
errors.slug || errors.slug ||
i18n.t("This is used internally. Make sure you dont use spaces", { intl.formatMessage({
context: "slug input" defaultMessage:
"This is used internally. Make sure you dont use spaces",
description: "attribute slug input field helper text"
}) })
} }
value={data.slug} value={data.slug}
@ -75,8 +94,9 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
disabled={disabled || !canChangeType} disabled={disabled || !canChangeType}
error={!!errors.inputType} error={!!errors.inputType}
hint={errors.inputType} hint={errors.inputType}
label={i18n.t("Catalog Input type for Store Owner", { label={intl.formatMessage({
context: "attribute input type" defaultMessage: "Catalog Input type for Store Owner",
description: "attribute's editor component"
})} })}
name="inputType" name="inputType"
onChange={onChange} onChange={onChange}
@ -85,14 +105,16 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({
<FormSpacer /> <FormSpacer />
<ControlledSwitch <ControlledSwitch
checked={data.valueRequired} checked={data.valueRequired}
label={i18n.t("Value Required", { label={intl.formatMessage({
context: "attribute must have value" defaultMessage: "Value Required",
description: "check to require attribute to have value"
})} })}
name={"valueRequired" as keyof AttributePageFormData} name={"valueRequired" as keyof AttributePageFormData}
onChange={onChange} onChange={onChange}
/> />
</CardContent> </CardContent>
</Card> </Card>
); );
};
AttributeDetails.displayName = "AttributeDetails"; AttributeDetails.displayName = "AttributeDetails";
export default AttributeDetails; export default AttributeDetails;

View file

@ -6,15 +6,15 @@ import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
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 Checkbox from "@saleor/components/Checkbox"; import Checkbox from "@saleor/components/Checkbox";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import i18n from "@saleor/i18n"; import { translateBoolean } from "@saleor/intl";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps } from "@saleor/types";
import { translateBoolean } from "@saleor/utils/i18n";
import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
export interface AttributeListProps extends ListProps, ListActions { export interface AttributeListProps extends ListProps, ListActions {
@ -71,6 +71,7 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
toolbar toolbar
}) => { }) => {
const classes = useStyles({}); const classes = useStyles({});
const intl = useIntl();
return ( return (
<Table> <Table>
@ -83,23 +84,31 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colSlug}> <TableCell className={classes.colSlug}>
{i18n.t("Attribute Code", { context: "attribute slug" })} <FormattedMessage defaultMessage="Attribute Code" />
</TableCell> </TableCell>
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
{i18n.t("Default Label", { context: "attribute name" })} <FormattedMessage
defaultMessage="Default Label"
description="attribute's label'"
/>
</TableCell> </TableCell>
<TableCell className={classes.colVisible}> <TableCell className={classes.colVisible}>
{i18n.t("Visible", { context: "attribute visibility" })} <FormattedMessage
defaultMessage="Visible"
description="attribute is visible"
/>
</TableCell> </TableCell>
<TableCell className={classes.colSearchable}> <TableCell className={classes.colSearchable}>
{i18n.t("Searchable", { <FormattedMessage
context: "attribute can be searched in dashboard" defaultMessage="Searchable"
})} description="attribute can be searched in dashboard"
/>
</TableCell> </TableCell>
<TableCell className={classes.colFaceted}> <TableCell className={classes.colFaceted}>
{i18n.t("Use in faceted search", { <FormattedMessage
context: "attribute can be searched in storefront" defaultMessage="Use in faceted search"
})} description="attribute can be searched in storefront"
/>
</TableCell> </TableCell>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
@ -145,21 +154,21 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
</TableCell> </TableCell>
<TableCell className={classes.colVisible}> <TableCell className={classes.colVisible}>
{attribute ? ( {attribute ? (
translateBoolean(attribute.visibleInStorefront) translateBoolean(attribute.visibleInStorefront, intl)
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}
</TableCell> </TableCell>
<TableCell className={classes.colSearchable}> <TableCell className={classes.colSearchable}>
{attribute ? ( {attribute ? (
translateBoolean(attribute.filterableInDashboard) translateBoolean(attribute.filterableInDashboard, intl)
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}
</TableCell> </TableCell>
<TableCell className={classes.colFaceted}> <TableCell className={classes.colFaceted}>
{attribute ? ( {attribute ? (
translateBoolean(attribute.filterableInStorefront) translateBoolean(attribute.filterableInStorefront, intl)
) : ( ) : (
<Skeleton /> <Skeleton />
)} )}
@ -170,7 +179,7 @@ const AttributeList: React.StatelessComponent<AttributeListProps> = ({
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{i18n.t("No attributes found")} <FormattedMessage defaultMessage="No attributes found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )

View file

@ -2,10 +2,11 @@ import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import AddIcon from "@material-ui/icons/Add"; import AddIcon from "@material-ui/icons/Add";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { sectionNames } from "@saleor/intl";
import Container from "../../../components/Container"; import Container from "../../../components/Container";
import PageHeader from "../../../components/PageHeader"; import PageHeader from "../../../components/PageHeader";
import i18n from "../../../i18n";
import { ListActions, PageListProps } from "../../../types"; import { ListActions, PageListProps } from "../../../types";
import { AttributeList_attributes_edges_node } from "../../types/AttributeList"; import { AttributeList_attributes_edges_node } from "../../types/AttributeList";
import AttributeList from "../AttributeList/AttributeList"; import AttributeList from "../AttributeList/AttributeList";
@ -17,17 +18,25 @@ export interface AttributeListPageProps extends PageListProps, ListActions {
const AttributeListPage: React.FC<AttributeListPageProps> = ({ const AttributeListPage: React.FC<AttributeListPageProps> = ({
onAdd, onAdd,
...listProps ...listProps
}) => ( }) => {
const intl = useIntl();
return (
<Container> <Container>
<PageHeader title={i18n.t("Attributes")}> <PageHeader title={intl.formatMessage(sectionNames.attributes)}>
<Button onClick={onAdd} color="primary" variant="contained"> <Button onClick={onAdd} color="primary" variant="contained">
{i18n.t("Add attribute")} <AddIcon /> <FormattedMessage
defaultMessage="Add attribute"
description="button"
/>
<AddIcon />
</Button> </Button>
</PageHeader> </PageHeader>
<Card> <Card>
<AttributeList {...listProps} /> <AttributeList {...listProps} />
</Card> </Card>
</Container> </Container>
); );
};
AttributeListPage.displayName = "AttributeListPage"; AttributeListPage.displayName = "AttributeListPage";
export default AttributeListPage; export default AttributeListPage;

View file

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import slugify from "slugify"; import slugify from "slugify";
import AppHeader from "@saleor/components/AppHeader"; import AppHeader from "@saleor/components/AppHeader";
@ -9,7 +10,7 @@ import Form from "@saleor/components/Form";
import Grid from "@saleor/components/Grid"; 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 i18n from "@saleor/i18n"; import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { ReorderAction, UserError } from "@saleor/types"; import { ReorderAction, UserError } from "@saleor/types";
import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; import { AttributeInputTypeEnum } from "@saleor/types/globalTypes";
@ -62,6 +63,7 @@ const AttributePage: React.FC<AttributePageProps> = ({
onValueReorder, onValueReorder,
onValueUpdate onValueUpdate
}) => { }) => {
const intl = useIntl();
const initialForm: AttributePageFormData = const initialForm: AttributePageFormData =
attribute === null attribute === null
? { ? {
@ -109,12 +111,15 @@ const AttributePage: React.FC<AttributePageProps> = ({
<Form errors={errors} initial={initialForm} onSubmit={handleSubmit}> <Form errors={errors} initial={initialForm} onSubmit={handleSubmit}>
{({ change, errors: formErrors, data, submit }) => ( {({ change, errors: formErrors, data, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Attributes")}</AppHeader> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.attributes)}
</AppHeader>
<PageHeader <PageHeader
title={ title={
attribute === null attribute === null
? i18n.t("Create New Attribute", { ? intl.formatMessage({
context: "page title" defaultMessage: "Create New Attribute",
description: "page title"
}) })
: maybe(() => attribute.name) : maybe(() => attribute.name)
} }

View file

@ -3,13 +3,14 @@ import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
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";
import ControlledSwitch from "@saleor/components/ControlledSwitch"; import ControlledSwitch from "@saleor/components/ControlledSwitch";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import Hr from "@saleor/components/Hr"; import Hr from "@saleor/components/Hr";
import i18n from "@saleor/i18n"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "@saleor/types"; import { FormErrors } from "@saleor/types";
import { AttributePageFormData } from "../AttributePage"; import { AttributePageFormData } from "../AttributePage";
@ -25,12 +26,19 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
errors, errors,
disabled, disabled,
onChange onChange
}) => ( }) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("Properties")} /> <CardTitle title={intl.formatMessage(commonMessages.properties)} />
<CardContent> <CardContent>
{/* <Typography variant="subtitle1"> {/* <Typography variant="subtitle1">
{i18n.t("General Properties")} <FormattedMessage
defaultMessage="General Properties"
description="attribute general properties section"
/>
</Typography> </Typography>
<Hr /> <Hr />
<CardSpacer /> <CardSpacer />
@ -40,11 +48,16 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
disabled={disabled} disabled={disabled}
label={ label={
<> <>
<span>{i18n.t("Variant Attribute")}</span> <FormattedMessage
defaultMessage="Variant Attribute"
description="attribute is variant-only"
/>
<Typography variant="caption"> <Typography variant="caption">
{i18n.t( <FormattedMessage
"If enabled, you'll be able to use this attribute to create product variants" defaultMessage="If enabled, you'll be able to use this attribute to create product variants"
)}
/>
</Typography> </Typography>
</> </>
} }
@ -52,14 +65,20 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
/> */} /> */}
<Typography variant="subtitle1"> <Typography variant="subtitle1">
{i18n.t("Storefront Properties")} <FormattedMessage
defaultMessage="Storefront Properties"
description="attribute properties regarding storefront"
/>
</Typography> </Typography>
<Hr /> <Hr />
<ControlledSwitch <ControlledSwitch
name={"filterableInStorefront" as keyof AttributePageFormData} name={"filterableInStorefront" as keyof AttributePageFormData}
checked={data.filterableInStorefront} checked={data.filterableInStorefront}
disabled={disabled} disabled={disabled}
label={i18n.t("Use in faceted navigation")} label={intl.formatMessage({
defaultMessage: "Use in faceted navigation",
description: "attribute is filterable in storefront"
})}
onChange={onChange} onChange={onChange}
/> />
{data.filterableInStorefront && ( {data.filterableInStorefront && (
@ -69,7 +88,10 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
fullWidth fullWidth
helperText={errors.storefrontSearchPosition} helperText={errors.storefrontSearchPosition}
name={"storefrontSearchPosition" as keyof AttributePageFormData} name={"storefrontSearchPosition" as keyof AttributePageFormData}
label={i18n.t("Position in faceted navigation")} label={intl.formatMessage({
defaultMessage: "Position in faceted navigation",
description: "attribute position in storefront filters"
})}
value={data.storefrontSearchPosition} value={data.storefrontSearchPosition}
onChange={onChange} onChange={onChange}
/> />
@ -79,15 +101,18 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
name={"visibleInStorefront" as keyof AttributePageFormData} name={"visibleInStorefront" as keyof AttributePageFormData}
checked={data.visibleInStorefront} checked={data.visibleInStorefront}
disabled={disabled} disabled={disabled}
label={i18n.t("Visible on Product Page in Storefront", { label={intl.formatMessage({
context: "attribute" defaultMessage: "Visible on Product Page in Storefront",
description: "attribute"
})} })}
onChange={onChange} onChange={onChange}
/> />
<CardSpacer /> <CardSpacer />
<Typography variant="subtitle1"> <Typography variant="subtitle1">
{i18n.t("Dashboard Properties")} <FormattedMessage
defaultMessage="Dashboard Properties"
description="attribute properties regarding dashboard"
/>
</Typography> </Typography>
<Hr /> <Hr />
<CardSpacer /> <CardSpacer />
@ -95,12 +120,13 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
name={"filterableInDashboard" as keyof AttributePageFormData} name={"filterableInDashboard" as keyof AttributePageFormData}
checked={data.filterableInDashboard} checked={data.filterableInDashboard}
disabled={disabled} disabled={disabled}
label={i18n.t("Use in Filtering")} label={intl.formatMessage({
defaultMessage: "Use in Filtering",
description: "use attribute in filtering"
})}
secondLabel={ secondLabel={
<Typography variant="caption"> <Typography variant="caption">
{i18n.t( <FormattedMessage defaultMessage="If enabled, youll be able to use this attribute to filter products in product list." />
"If enabled, youll be able to use this attribute to filter products in product list."
)}
</Typography> </Typography>
} }
onChange={onChange} onChange={onChange}
@ -110,18 +136,20 @@ const AttributeProperties: React.FC<AttributePropertiesProps> = ({
name={"availableInGrid" as keyof AttributePageFormData} name={"availableInGrid" as keyof AttributePageFormData}
checked={data.availableInGrid} checked={data.availableInGrid}
disabled={disabled} disabled={disabled}
label={i18n.t("Add to Column Options")} label={intl.formatMessage({
defaultMessage: "Add to Column Options",
description: "add attribute as column in product list table"
})}
secondLabel={ secondLabel={
<Typography variant="caption"> <Typography variant="caption">
{i18n.t( <FormattedMessage defaultMessage="If enable this attribute can be used as a column in product table." />
"If enable this attribute can be used as a column in product table."
)}
</Typography> </Typography>
} }
onChange={onChange} onChange={onChange}
/> />
</CardContent> </CardContent>
</Card> </Card>
); );
};
AttributeProperties.displayName = "AttributeProperties"; AttributeProperties.displayName = "AttributeProperties";
export default AttributeProperties; export default AttributeProperties;

View file

@ -1,9 +1,9 @@
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 { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton"; import { ConfirmButtonTransitionState } from "@saleor/components/ConfirmButton";
import i18n from "@saleor/i18n";
export interface AttributeValueDeleteDialogProps { export interface AttributeValueDeleteDialogProps {
attributeName: string; attributeName: string;
@ -23,30 +23,43 @@ const AttributeValueDeleteDialog: React.FC<AttributeValueDeleteDialogProps> = ({
onClose, onClose,
onConfirm, onConfirm,
open open
}) => ( }) => {
const intl = useIntl();
return (
<ActionDialog <ActionDialog
open={open} open={open}
onClose={onClose} onClose={onClose}
confirmButtonState={confirmButtonState} confirmButtonState={confirmButtonState}
onConfirm={onConfirm} onConfirm={onConfirm}
variant="delete" variant="delete"
title={i18n.t("Remove attribute value")} title={intl.formatMessage({
defaultMessage: "Delete attribute value",
description: "dialog title"
})}
> >
<DialogContentText> <DialogContentText>
{useName {useName ? (
? i18n.t( <FormattedMessage
'Are you sure you want to remove "{{ name }}" value? If you remove it you wont be able to assign it to any of the products with "{{ attributeName }}" attribute.', defaultMessage='Are you sure you want to delete "{ name }" value? If you delete it you wont be able to assign it to any of the products with "{ attributeName }" attribute.'
{ values={{
attributeName, attributeName,
name name
} }}
) />
: i18n.t('Are you sure you want to remove "{{ name }}" value?', { ) : (
<FormattedMessage
defaultMessage='Are you sure you want to delete "{ name }" value?'
description="delete attribute value"
values={{
name name
})} }}
/>
)}
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
); );
};
AttributeValueDeleteDialog.displayName = "AttributeValueDeleteDialog"; AttributeValueDeleteDialog.displayName = "AttributeValueDeleteDialog";
export default AttributeValueDeleteDialog; export default AttributeValueDeleteDialog;

View file

@ -5,13 +5,14 @@ import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle"; import DialogTitle from "@material-ui/core/DialogTitle";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton"; } from "@saleor/components/ConfirmButton";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors"; import useModalDialogErrors from "@saleor/hooks/useModalDialogErrors";
import i18n from "@saleor/i18n"; import { buttonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { UserError } from "@saleor/types"; import { UserError } from "@saleor/types";
import { AttributeDetails_attribute_values } from "../../types/AttributeDetails"; import { AttributeDetails_attribute_values } from "../../types/AttributeDetails";
@ -40,6 +41,7 @@ const AttributeValueEditDialog: React.StatelessComponent<
onSubmit, onSubmit,
open open
}) => { }) => {
const intl = useIntl();
const initialForm: AttributeValueEditDialogFormData = { const initialForm: AttributeValueEditDialogFormData = {
name: maybe(() => attributeValue.name, "") name: maybe(() => attributeValue.name, "")
}; };
@ -48,13 +50,17 @@ const AttributeValueEditDialog: React.StatelessComponent<
return ( return (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
<DialogTitle> <DialogTitle>
{attributeValue === null {attributeValue === null ? (
? i18n.t("Add Value", { <FormattedMessage
context: "add attribute value" defaultMessage="Add Value"
}) description="add attribute value"
: i18n.t("Edit Value", { />
context: "edit attribute value" ) : (
})} <FormattedMessage
defaultMessage="Edit Value"
description="edit attribute value"
/>
)}
</DialogTitle> </DialogTitle>
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form errors={errors} initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, submit }) => ( {({ change, data, errors: formErrors, submit }) => (
@ -67,8 +73,9 @@ const AttributeValueEditDialog: React.StatelessComponent<
fullWidth fullWidth
helperText={formErrors.name} helperText={formErrors.name}
name={"name" as keyof AttributeValueEditDialogFormData} name={"name" as keyof AttributeValueEditDialogFormData}
label={i18n.t("Name", { label={intl.formatMessage({
context: "attribute name" defaultMessage: "Name",
description: "attribute name"
})} })}
value={data.name} value={data.name}
onChange={change} onChange={change}
@ -76,7 +83,7 @@ const AttributeValueEditDialog: React.StatelessComponent<
</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}
@ -84,7 +91,7 @@ const AttributeValueEditDialog: React.StatelessComponent<
variant="contained" variant="contained"
onClick={submit} onClick={submit}
> >
{i18n.t("Save")} <FormattedMessage {...buttonMessages.save} />
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</> </>

View file

@ -9,6 +9,7 @@ import TableRow from "@material-ui/core/TableRow";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
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 CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
@ -16,7 +17,6 @@ import {
SortableTableBody, SortableTableBody,
SortableTableRow SortableTableRow
} from "@saleor/components/SortableTable"; } from "@saleor/components/SortableTable";
import i18n from "@saleor/i18n";
import { maybe, renderCollection, stopPropagation } from "@saleor/misc"; import { maybe, renderCollection, stopPropagation } from "@saleor/misc";
import { ReorderAction } from "@saleor/types"; import { ReorderAction } from "@saleor/types";
import { AttributeDetailsFragment_values } from "../../types/AttributeDetailsFragment"; import { AttributeDetailsFragment_values } from "../../types/AttributeDetailsFragment";
@ -63,14 +63,21 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
values values
}) => { }) => {
const classes = useStyles({}); const classes = useStyles({});
const intl = useIntl();
return ( return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Attribute Values")} title={intl.formatMessage({
defaultMessage: "Attribute Values",
description: "section header"
})}
toolbar={ toolbar={
<Button color="primary" variant="text" onClick={onValueAdd}> <Button color="primary" variant="text" onClick={onValueAdd}>
{i18n.t("Add value", { context: "button" })} <FormattedMessage
defaultMessage="Add value"
description="add attribute value button"
/>
</Button> </Button>
} }
/> />
@ -79,10 +86,16 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
<TableRow> <TableRow>
<TableCell className={classes.columnDrag} /> <TableCell className={classes.columnDrag} />
<TableCell className={classes.columnAdmin}> <TableCell className={classes.columnAdmin}>
{i18n.t("Admin")} <FormattedMessage
defaultMessage="Admin"
description="attribute values list: slug column header"
/>
</TableCell> </TableCell>
<TableCell className={classes.columnStore}> <TableCell className={classes.columnStore}>
{i18n.t("Default Store View")} <FormattedMessage
defaultMessage="Default Store View"
description="attribute values list: name column header"
/>
</TableCell> </TableCell>
<TableCell /> <TableCell />
</TableRow> </TableRow>
@ -116,7 +129,12 @@ const AttributeValues: React.FC<AttributeValuesProps> = ({
), ),
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={2}>{i18n.t("No values found")}</TableCell> <TableCell colSpan={2}>
<FormattedMessage
defaultMessage="No values found"
description="No attribute values found"
/>
</TableCell>
</TableRow> </TableRow>
) )
)} )}

View file

@ -2,8 +2,9 @@ import { parse as parseQs } from "qs";
import React from "react"; import React from "react";
import { Route, RouteComponentProps, Switch } from "react-router-dom"; import { Route, RouteComponentProps, Switch } from "react-router-dom";
import { sectionNames } from "@saleor/intl";
import { useIntl } from "react-intl";
import { WindowTitle } from "../components/WindowTitle"; import { WindowTitle } from "../components/WindowTitle";
import i18n from "../i18n";
import { import {
attributeAddPath, attributeAddPath,
AttributeAddUrlQueryParams, AttributeAddUrlQueryParams,
@ -42,14 +43,18 @@ const AttributeDetails: React.FC<RouteComponentProps<{ id: string }>> = ({
); );
}; };
export const AttributeSection: React.FC = () => ( export const AttributeSection: React.FC = () => {
const intl = useIntl();
return (
<> <>
<WindowTitle title={i18n.t("Attributes")} /> <WindowTitle title={intl.formatMessage(sectionNames.attributes)} />
<Switch> <Switch>
<Route exact path={attributeListPath} component={AttributeList} /> <Route exact path={attributeListPath} component={AttributeList} />
<Route exact path={attributeAddPath} component={AttributeCreate} /> <Route exact path={attributeAddPath} component={AttributeCreate} />
<Route path={attributePath(":id")} component={AttributeDetails} /> <Route path={attributePath(":id")} component={AttributeDetails} />
</Switch> </Switch>
</> </>
); );
};
export default AttributeSection; export default AttributeSection;

View file

@ -1,9 +1,9 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import slugify from "slugify"; import slugify from "slugify";
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 "@saleor/i18n";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ReorderEvent, UserError } from "@saleor/types"; import { ReorderEvent, UserError } from "@saleor/types";
import { import {
@ -42,6 +42,7 @@ function areValuesEqual(
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => { const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl();
const [values, setValues] = React.useState< const [values, setValues] = React.useState<
AttributeValueEditDialogFormData[] AttributeValueEditDialogFormData[]
@ -75,7 +76,11 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
}; };
const handleCreate = (data: AttributeCreate) => { const handleCreate = (data: AttributeCreate) => {
if (data.attributeCreate.errors.length === 0) { if (data.attributeCreate.errors.length === 0) {
notify({ text: i18n.t("Successfully created attribute") }); notify({
text: intl.formatMessage({
defaultMessage: "Successfully created attribute"
})
});
navigate(attributeUrl(data.attributeCreate.attribute.id)); navigate(attributeUrl(data.attributeCreate.attribute.id));
} }
}; };
@ -84,10 +89,15 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
setValueErrors([ setValueErrors([
{ {
field: "name", field: "name",
message: i18n.t("A value named {{ name }} already exists", { message: intl.formatMessage(
context: "value edit error", {
defaultMessage: "A value named { name } already exists",
description: "attribute value edit error"
},
{
name: input.name name: input.name
}) }
)
} }
]); ]);
} else { } else {
@ -100,10 +110,15 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ params }) => {
setValueErrors([ setValueErrors([
{ {
field: "name", field: "name",
message: i18n.t("A value named {{ name }} already exists", { message: intl.formatMessage(
context: "value edit error", {
defaultMessage: "A value named { name } already exists",
description: "attribute value edit error"
},
{
name: input.name name: input.name
}) }
)
} }
]); ]);
} else { } else {

View file

@ -1,8 +1,9 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
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 "@saleor/i18n"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ReorderEvent } from "@saleor/types"; import { ReorderEvent } from "@saleor/types";
import { move } from "@saleor/utils/lists"; import { move } from "@saleor/utils/lists";
@ -40,6 +41,7 @@ interface AttributeDetailsProps {
const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => { const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl();
const closeModal = () => const closeModal = () =>
navigate( navigate(
@ -63,39 +65,51 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = ({ id, params }) => {
const handleDelete = (data: AttributeDelete) => { const handleDelete = (data: AttributeDelete) => {
if (data.attributeDelete.errors.length === 0) { if (data.attributeDelete.errors.length === 0) {
notify({ text: i18n.t("Attribute removed") }); notify({
text: intl.formatMessage({
defaultMessage: "Attribute deleted"
})
});
navigate(attributeListUrl()); navigate(attributeListUrl());
} }
}; };
const handleValueDelete = (data: AttributeValueDelete) => { const handleValueDelete = (data: AttributeValueDelete) => {
if (data.attributeValueDelete.errors.length === 0) { if (data.attributeValueDelete.errors.length === 0) {
notify({ text: i18n.t("Value removed") }); notify({
text: intl.formatMessage({
defaultMessage: "Value deleted",
description: "attribute value deleted"
})
});
closeModal(); closeModal();
} }
}; };
const handleUpdate = (data: AttributeUpdate) => { const handleUpdate = (data: AttributeUpdate) => {
if (data.attributeUpdate.errors.length === 0) { if (data.attributeUpdate.errors.length === 0) {
notify({ text: i18n.t("Saved changes") }); notify({ text: intl.formatMessage(commonMessages.savedChanges) });
} }
}; };
const handleValueUpdate = (data: AttributeValueUpdate) => { const handleValueUpdate = (data: AttributeValueUpdate) => {
if (data.attributeValueUpdate.errors.length === 0) { if (data.attributeValueUpdate.errors.length === 0) {
notify({ text: i18n.t("Saved changes") }); notify({ text: intl.formatMessage(commonMessages.savedChanges) });
closeModal(); closeModal();
} }
}; };
const handleValueCreate = (data: AttributeValueCreate) => { const handleValueCreate = (data: AttributeValueCreate) => {
if (data.attributeValueCreate.errors.length === 0) { if (data.attributeValueCreate.errors.length === 0) {
notify({ text: i18n.t("Added new value") }); notify({
text: intl.formatMessage({
defaultMessage: "Added new value",
description: "added new attribute value"
})
});
closeModal(); closeModal();
} }
}; };
const handleValueReorderMutation = (data: AttributeValueReorder) => { const handleValueReorderMutation = (data: AttributeValueReorder) => {
if (data.attributeReorderValues.errors.length !== 0) { if (data.attributeReorderValues.errors.length !== 0) {
notify({ notify({
text: i18n.t("Error: {{ errorMessage }}", { text: data.attributeReorderValues.errors[0].message
errorMessage: data.attributeReorderValues.errors[0].message
})
}); });
} }
}; };

View file

@ -1,6 +1,7 @@
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 { useIntl } from "react-intl";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useNotifier from "@saleor/hooks/useNotifier"; import useNotifier from "@saleor/hooks/useNotifier";
@ -9,7 +10,6 @@ import usePaginator, {
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { PAGINATE_BY } from "../../../config"; import { PAGINATE_BY } from "../../../config";
import useBulkActions from "../../../hooks/useBulkActions"; import useBulkActions from "../../../hooks/useBulkActions";
import i18n from "../../../i18n";
import { getMutationState, maybe } from "../../../misc"; import { getMutationState, maybe } from "../../../misc";
import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog"; import AttributeBulkDeleteDialog from "../../components/AttributeBulkDeleteDialog";
import AttributeListPage from "../../components/AttributeListPage"; import AttributeListPage from "../../components/AttributeListPage";
@ -35,6 +35,7 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids params.ids
); );
const intl = useIntl();
const closeModal = () => const closeModal = () =>
navigate( navigate(
@ -71,7 +72,10 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
if (data.attributeBulkDelete.errors.length === 0) { if (data.attributeBulkDelete.errors.length === 0) {
closeModal(); closeModal();
notify({ notify({
text: i18n.t("Attributes removed") text: intl.formatMessage({
defaultMessage: "Attributes successfully delete",
description: "deleted multiple attributes"
})
}); });
reset(); reset();
refetch(); refetch();
@ -116,12 +120,15 @@ const AttributeList: React.FC<AttributeListProps> = ({ params }) => {
/> />
<AttributeBulkDeleteDialog <AttributeBulkDeleteDialog
confirmButtonState={bulkDeleteMutationState} confirmButtonState={bulkDeleteMutationState}
open={params.action === "remove"} open={
params.action === "remove" &&
maybe(() => params.ids.length > 0)
}
onConfirm={() => onConfirm={() =>
attributeBulkDelete({ variables: { ids: params.ids } }) attributeBulkDelete({ variables: { ids: params.ids } })
} }
onClose={closeModal} onClose={closeModal}
quantity={maybe(() => params.ids.length.toString(), "...")} quantity={maybe(() => params.ids.length)}
/> />
</> </>
); );

View file

@ -9,6 +9,7 @@ import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import { FormattedMessage, useIntl } from "react-intl";
import backgroundArt from "@assets/images/login-background.svg"; import backgroundArt from "@assets/images/login-background.svg";
import saleorDarkLogo from "@assets/images/logo-dark.svg"; import saleorDarkLogo from "@assets/images/logo-dark.svg";
@ -17,7 +18,7 @@ import { ControlledCheckbox } from "@saleor/components/ControlledCheckbox";
import Form from "@saleor/components/Form"; import Form from "@saleor/components/Form";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import useTheme from "@saleor/hooks/useTheme"; import useTheme from "@saleor/hooks/useTheme";
import i18n from "@saleor/i18n"; import { commonMessages } from "@saleor/intl";
export interface FormData { export interface FormData {
email: string; email: string;
@ -117,6 +118,7 @@ export interface LoginCardProps extends WithStyles<typeof styles> {
const LoginCard = withStyles(styles, { name: "LoginCard" })( const LoginCard = withStyles(styles, { name: "LoginCard" })(
({ classes, error, disableLoginButton, onSubmit }: LoginCardProps) => { ({ classes, error, disableLoginButton, onSubmit }: LoginCardProps) => {
const { isDark } = useTheme(); const { isDark } = useTheme();
const intl = useIntl();
return ( return (
<Form <Form
@ -136,21 +138,16 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
/> />
{error && ( {error && (
<div className={classes.panel}> <div className={classes.panel}>
<Typography <Typography variant="caption">
variant="caption" <FormattedMessage defaultMessage="Sorry, your username and/or password are incorrect. Please try again." />
dangerouslySetInnerHTML={{ </Typography>
__html: i18n.t(
"Sorry, your username and/or password are incorrect. <br />Please try again."
)
}}
/>
</div> </div>
)} )}
<TextField <TextField
autoFocus autoFocus
fullWidth fullWidth
autoComplete="username" autoComplete="username"
label={i18n.t("Email", { context: "form" })} label={intl.formatMessage(commonMessages.email)}
name="email" name="email"
onChange={handleChange} onChange={handleChange}
value={data.email} value={data.email}
@ -162,7 +159,9 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
<TextField <TextField
fullWidth fullWidth
autoComplete="current-password" autoComplete="current-password"
label={i18n.t("Password")} label={intl.formatMessage({
defaultMessage: "Password"
})}
name="password" name="password"
onChange={handleChange} onChange={handleChange}
type="password" type="password"
@ -173,7 +172,10 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
<div className={classes.buttonContainer}> <div className={classes.buttonContainer}>
<ControlledCheckbox <ControlledCheckbox
checked={data.rememberMe} checked={data.rememberMe}
label={i18n.t("Remember me")} label={intl.formatMessage({
defaultMessage: "Remember me",
description: "login"
})}
name="rememberMe" name="rememberMe"
onChange={handleChange} onChange={handleChange}
/> />
@ -187,7 +189,10 @@ const LoginCard = withStyles(styles, { name: "LoginCard" })(
type="submit" type="submit"
data-tc="submit" data-tc="submit"
> >
{i18n.t("Login")} <FormattedMessage
defaultMessage="Login"
description="button"
/>
</Button> </Button>
</div> </div>
{/* <FormSpacer /> {/* <FormSpacer />

View file

@ -1,10 +1,6 @@
import { import { Theme } from "@material-ui/core/styles";
createStyles,
Theme,
withStyles,
WithStyles
} from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import makeStyles from "@material-ui/styles/makeStyles";
import React from "react"; import React from "react";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
@ -15,12 +11,12 @@ import Hr from "@saleor/components/Hr";
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 Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import i18n from "../../../i18n"; import { commonMessages } from "@saleor/intl";
import { FormattedMessage, useIntl } from "react-intl";
import { CategoryDetails_category_backgroundImage } from "../../types/CategoryDetails"; import { CategoryDetails_category_backgroundImage } from "../../types/CategoryDetails";
import { FormData } from "../CategoryUpdatePage"; import { FormData } from "../CategoryUpdatePage";
const styles = (theme: Theme) => const useStyles = makeStyles((theme: Theme) => ({
createStyles({
fileField: { fileField: {
display: "none" display: "none"
}, },
@ -41,9 +37,9 @@ const styles = (theme: Theme) =>
position: "relative", position: "relative",
width: 148 width: 148
} }
}); }));
export interface CategoryBackgroundProps extends WithStyles<typeof styles> { export interface CategoryBackgroundProps {
data: FormData; data: FormData;
image: CategoryDetails_category_backgroundImage; image: CategoryDetails_category_backgroundImage;
onChange: (event: React.ChangeEvent<any>) => void; onChange: (event: React.ChangeEvent<any>) => void;
@ -51,43 +47,37 @@ export interface CategoryBackgroundProps extends WithStyles<typeof styles> {
onImageUpload: (file: File) => void; onImageUpload: (file: File) => void;
} }
export const CategoryBackground = withStyles(styles)( const CategoryBackground: React.FC<CategoryBackgroundProps> = props => {
class CategoryBackgroundComponent extends React.Component< const classes = useStyles(props);
CategoryBackgroundProps, const intl = useIntl();
{} const anchor = React.useRef<HTMLInputElement>();
> {
imgInputAnchor = React.createRef<HTMLInputElement>();
clickImgInput = () => this.imgInputAnchor.current.click(); const { data, onImageUpload, image, onChange, onImageDelete } = props;
const handleImageUploadButtonClick = () => anchor.current.click();
render() {
const {
classes,
data,
onImageUpload,
image,
onChange,
onImageDelete
} = this.props;
return ( return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Background image (optional)")} title={intl.formatMessage({
defaultMessage: "Background image (optional)",
description: "section header"
})}
toolbar={ toolbar={
<> <>
<Button <Button
variant="text" variant="text"
color="primary" color="primary"
onClick={this.clickImgInput} onClick={handleImageUploadButtonClick}
> >
{i18n.t("Upload image")} <FormattedMessage {...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={this.imgInputAnchor} ref={anchor}
/> />
</> </>
} }
@ -114,8 +104,11 @@ export const CategoryBackground = withStyles(styles)(
<CardContent> <CardContent>
<TextField <TextField
name="backgroundImageAlt" name="backgroundImageAlt"
label={i18n.t("Description")} label={intl.formatMessage(commonMessages.description)}
helperText={i18n.t("Optional")} helperText={intl.formatMessage({
defaultMessage: "(Optional)",
description: "field is optional"
})}
value={data.backgroundImageAlt} value={data.backgroundImageAlt}
onChange={onChange} onChange={onChange}
fullWidth fullWidth
@ -126,8 +119,6 @@ export const CategoryBackground = withStyles(styles)(
)} )}
</Card> </Card>
); );
} };
}
);
CategoryBackground.displayName = "CategoryBackground"; CategoryBackground.displayName = "CategoryBackground";
export default CategoryBackground; export default CategoryBackground;

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";
@ -9,7 +10,7 @@ import Form from "@saleor/components/Form";
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 SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import i18n from "../../../i18n"; import { sectionNames } from "@saleor/intl";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
import CategoryDetailsForm from "../../components/CategoryDetailsForm"; import CategoryDetailsForm from "../../components/CategoryDetailsForm";
@ -43,7 +44,9 @@ export const CategoryCreatePage: React.StatelessComponent<
onBack, onBack,
errors: userErrors, errors: userErrors,
saveButtonBarState saveButtonBarState
}) => ( }) => {
const intl = useIntl();
return (
<Form <Form
onSubmit={onSubmit} onSubmit={onSubmit}
initial={initialData} initial={initialData}
@ -52,8 +55,15 @@ export const CategoryCreatePage: React.StatelessComponent<
> >
{({ data, change, errors, submit, hasChanged }) => ( {({ data, change, errors, submit, hasChanged }) => (
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Categories")}</AppHeader> <AppHeader onBack={onBack}>
<PageHeader title={i18n.t("Add Category")} /> {intl.formatMessage(sectionNames.categories)}
</AppHeader>
<PageHeader
title={intl.formatMessage({
defaultMessage: "Create New Category",
description: "page header"
})}
/>
<div> <div>
<CategoryDetailsForm <CategoryDetailsForm
disabled={disabled} disabled={disabled}
@ -63,9 +73,10 @@ export const CategoryCreatePage: React.StatelessComponent<
/> />
<CardSpacer /> <CardSpacer />
<SeoForm <SeoForm
helperText={i18n.t( helperText={intl.formatMessage({
"Add search engine title and description to make this product easier to find" defaultMessage:
)} "Add search engine title and description to make this category easier to find"
})}
title={data.seoTitle} title={data.seoTitle}
titlePlaceholder={data.name} titlePlaceholder={data.name}
description={data.seoDescription} description={data.seoDescription}
@ -77,9 +88,6 @@ export const CategoryCreatePage: React.StatelessComponent<
<SaveButtonBar <SaveButtonBar
onCancel={onBack} onCancel={onBack}
onSave={submit} onSave={submit}
labels={{
save: i18n.t("Save category")
}}
state={saveButtonBarState} state={saveButtonBarState}
disabled={disabled || !hasChanged} disabled={disabled || !hasChanged}
/> />
@ -87,6 +95,7 @@ export const CategoryCreatePage: React.StatelessComponent<
</Container> </Container>
)} )}
</Form> </Form>
); );
};
CategoryCreatePage.displayName = "CategoryCreatePage"; CategoryCreatePage.displayName = "CategoryCreatePage";
export default CategoryCreatePage; export default CategoryCreatePage;

View file

@ -11,8 +11,9 @@ 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 i18n from "../../../i18n"; import { buttonMessages } from "@saleor/intl";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -36,27 +37,33 @@ const CategoryDeleteDialog = withStyles(styles, {
name: "CategoryDeleteDialog" name: "CategoryDeleteDialog"
})(({ classes, name, open, onConfirm, onClose }: CategoryDeleteDialogProps) => ( })(({ classes, name, open, onConfirm, onClose }: CategoryDeleteDialogProps) => (
<Dialog onClose={onClose} open={open}> <Dialog onClose={onClose} open={open}>
<DialogTitle>{i18n.t("Delete category", { context: "title" })}</DialogTitle> <DialogTitle>
<FormattedMessage
defaultMessage="Delete category"
description="dialog title"
/>
</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {categoryName}?"
"Are you sure you want to remove <strong>{{name}}</strong>?", description="delete category"
{ name } values={{
) categoryName: <strong>{name}</strong>
}} }}
/> />
</DialogContentText>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>
{i18n.t("Cancel", { context: "button" })} <FormattedMessage {...buttonMessages.cancel} />
</Button> </Button>
<Button <Button
className={classes.deleteButton} className={classes.deleteButton}
variant="contained" variant="contained"
onClick={onConfirm} onClick={onConfirm}
> >
{i18n.t("Delete category", { context: "button" })} <FormattedMessage {...buttonMessages.save} />
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View file

@ -4,11 +4,12 @@ import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
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";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { CategoryDetails_category } from "../../types/CategoryDetails"; import { CategoryDetails_category } from "../../types/CategoryDetails";
@ -40,15 +41,21 @@ export const CategoryDetailsForm = withStyles(styles, {
onChange, onChange,
errors errors
}: CategoryDetailsFormProps) => { }: CategoryDetailsFormProps) => {
const intl = useIntl();
return ( return (
<Card> <Card>
<CardTitle title={i18n.t("General information")} /> <CardTitle
title={intl.formatMessage(commonMessages.generalInformations)}
/>
<CardContent> <CardContent>
<> <>
<div> <div>
<TextField <TextField
classes={{ root: classes.root }} classes={{ root: classes.root }}
label={i18n.t("Name")} label={intl.formatMessage({
defaultMessage: "Category Name"
})}
name="name" name="name"
disabled={disabled} disabled={disabled}
value={data && data.name} value={data && data.name}
@ -62,7 +69,9 @@ export const CategoryDetailsForm = withStyles(styles, {
disabled={disabled} disabled={disabled}
error={!!errors.descriptionJson} error={!!errors.descriptionJson}
helperText={errors.descriptionJson} helperText={errors.descriptionJson}
label={i18n.t("Description")} label={intl.formatMessage({
defaultMessage: "Category Description"
})}
initial={maybe(() => JSON.parse(category.descriptionJson))} initial={maybe(() => JSON.parse(category.descriptionJson))}
name="description" name="description"
onChange={onChange} onChange={onChange}

View file

@ -12,13 +12,13 @@ import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter"; import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
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";
import Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import i18n from "@saleor/i18n";
import { renderCollection } from "@saleor/misc"; import { renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps } from "@saleor/types";
@ -87,14 +87,23 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
onPreviousPage, onPreviousPage,
onUpdateListSettings, onUpdateListSettings,
onRowClick onRowClick
}: CategoryListProps) => ( }: CategoryListProps) => {
const intl = useIntl();
return (
<Card> <Card>
{!isRoot && ( {!isRoot && (
<CardTitle <CardTitle
title={i18n.t("All Subcategories")} title={intl.formatMessage({
defaultMessage: "All Subcategories",
description: "section header"
})}
toolbar={ toolbar={
<Button color="primary" variant="text" onClick={onAdd}> <Button color="primary" variant="text" onClick={onAdd}>
{i18n.t("Add subcategory")} <FormattedMessage
defaultMessage="Add subcategory"
description="button"
/>
</Button> </Button>
} }
/> />
@ -109,13 +118,19 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
{i18n.t("Category Name", { context: "object" })} <FormattedMessage defaultMessage="Category Name" />
</TableCell> </TableCell>
<TableCell className={classes.colSubcategories}> <TableCell className={classes.colSubcategories}>
{i18n.t("Subcategories", { context: "object" })} <FormattedMessage
defaultMessage="Subcategories"
description="number of subcategories"
/>
</TableCell> </TableCell>
<TableCell className={classes.colProducts}> <TableCell className={classes.colProducts}>
{i18n.t("No. Products", { context: "object" }).replace(" ", "\xa0")} <FormattedMessage
defaultMessage="No. of Products"
description="number of products"
/>
</TableCell> </TableCell>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
@ -123,7 +138,9 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
<TablePagination <TablePagination
colSpan={numberOfColumns} colSpan={numberOfColumns}
settings={settings} settings={settings}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false} hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : false
}
onNextPage={onNextPage} onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings} onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={ hasPreviousPage={
@ -182,9 +199,11 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{isRoot {isRoot ? (
? i18n.t("No categories found") <FormattedMessage defaultMessage="No categories found" />
: i18n.t("No subcategories found")} ) : (
<FormattedMessage defaultMessage="No subcategories found" />
)}
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
@ -192,7 +211,8 @@ const CategoryList = withStyles(styles, { name: "CategoryList" })(
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) );
}
); );
CategoryList.displayName = "CategoryList"; CategoryList.displayName = "CategoryList";
export default CategoryList; export default CategoryList;

View file

@ -1,10 +1,11 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import AddIcon from "@material-ui/icons/Add"; import AddIcon from "@material-ui/icons/Add";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import Container from "@saleor/components/Container"; import Container from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import i18n from "@saleor/i18n"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import { ListActions, PageListProps } from "@saleor/types";
import CategoryList from "../CategoryList"; import CategoryList from "../CategoryList";
@ -36,11 +37,17 @@ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
}) => ( }) => {
const intl = useIntl();
return (
<Container> <Container>
<PageHeader title={i18n.t("Categories")}> <PageHeader title={intl.formatMessage(sectionNames.categories)}>
<Button color="primary" variant="contained" onClick={onAdd}> <Button color="primary" variant="contained" onClick={onAdd}>
{i18n.t("Add category")} <AddIcon /> <FormattedMessage
defaultMessage="Add category"
description="button"
/>
<AddIcon />
</Button> </Button>
</PageHeader> </PageHeader>
<CategoryList <CategoryList
@ -61,6 +68,7 @@ export const CategoryListPage: React.StatelessComponent<CategoryTableProps> = ({
toolbar={toolbar} toolbar={toolbar}
/> />
</Container> </Container>
); );
};
CategoryListPage.displayName = "CategoryListPage"; CategoryListPage.displayName = "CategoryListPage";
export default CategoryListPage; export default CategoryListPage;

View file

@ -13,13 +13,13 @@ import TableFooter from "@material-ui/core/TableFooter";
import TableHead from "@material-ui/core/TableHead"; import TableHead from "@material-ui/core/TableHead";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
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 TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import i18n from "../../../i18n"; import { maybe, renderCollection } from "@saleor/misc";
import { maybe, renderCollection } from "../../../misc";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -61,13 +61,22 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
onNextPage, onNextPage,
onPreviousPage, onPreviousPage,
onRowClick onRowClick
}: ProductListProps) => ( }: ProductListProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Products")} title={intl.formatMessage({
defaultMessage: "Products",
description: "section header"
})}
toolbar={ toolbar={
<Button variant="text" color="primary" onClick={onAddProduct}> <Button variant="text" color="primary" onClick={onAddProduct}>
{i18n.t("Add product")} <FormattedMessage
defaultMessage="Add product"
description="button"
/>
</Button> </Button>
} }
/> />
@ -76,9 +85,17 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
<TableRow> <TableRow>
{(products === undefined || products.length > 0) && <TableCell />} {(products === undefined || products.length > 0) && <TableCell />}
<TableCell className={classes.textLeft}> <TableCell className={classes.textLeft}>
{i18n.t("Name", { context: "object" })} <FormattedMessage
defaultMessage="Name"
description="product name"
/>
</TableCell>
<TableCell>
<FormattedMessage
defaultMessage="Type"
description="product type"
/>
</TableCell> </TableCell>
<TableCell>{i18n.t("Type", { context: "object" })}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
@ -123,14 +140,17 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
), ),
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={3}>{i18n.t("No products found")}</TableCell> <TableCell colSpan={3}>
<FormattedMessage defaultMessage="No products found" />
</TableCell>
</TableRow> </TableRow>
) )
)} )}
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) );
}
); );
ProductList.displayName = "CategoryProductList"; ProductList.displayName = "CategoryProductList";
export default ProductList; export default ProductList;

View file

@ -1,10 +1,10 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
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 ProductList from "@saleor/components/ProductList"; import ProductList from "@saleor/components/ProductList";
import i18n from "../../../i18n";
import { ListActions, PageListProps } from "../../../types"; import { ListActions, PageListProps } from "../../../types";
import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails"; import { CategoryDetails_category_products_edges_node } from "../../types/CategoryDetails";
@ -29,13 +29,27 @@ export const CategoryProductsCard: React.StatelessComponent<
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
}) => ( }) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Products in {{ categoryName }}", { categoryName })} title={intl.formatMessage(
{
defaultMessage: "Products in {categoryName}",
description: "section header"
},
{
categoryName
}
)}
toolbar={ toolbar={
<Button color="primary" variant="text" onClick={onAdd}> <Button color="primary" variant="text" onClick={onAdd}>
{i18n.t("Add product")} <FormattedMessage
defaultMessage="Add product"
description="button"
/>
</Button> </Button>
} }
/> />
@ -57,7 +71,8 @@ export const CategoryProductsCard: React.StatelessComponent<
toolbar={toolbar} toolbar={toolbar}
/> />
</Card> </Card>
); );
};
CategoryProductsCard.displayName = "CategoryProductsCard"; CategoryProductsCard.displayName = "CategoryProductsCard";
export default CategoryProductsCard; export default CategoryProductsCard;

View file

@ -1,5 +1,6 @@
import { RawDraftContentState } from "draft-js"; import { RawDraftContentState } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage, 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";
@ -10,7 +11,7 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import { Tab, TabContainer } from "@saleor/components/Tab"; import { Tab, TabContainer } from "@saleor/components/Tab";
import i18n from "../../../i18n"; import { sectionNames } from "@saleor/intl";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { TabListActions, UserError } from "../../../types"; import { TabListActions, UserError } from "../../../types";
import CategoryDetailsForm from "../../components/CategoryDetailsForm"; import CategoryDetailsForm from "../../components/CategoryDetailsForm";
@ -96,6 +97,7 @@ export const CategoryUpdatePage: React.StatelessComponent<
toggle, toggle,
toggleAll toggleAll
}: CategoryUpdatePageProps) => { }: CategoryUpdatePageProps) => {
const intl = useIntl();
const initialData: FormData = category const initialData: FormData = category
? { ? {
backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""), backgroundImageAlt: maybe(() => category.backgroundImage.alt, ""),
@ -120,7 +122,9 @@ export const CategoryUpdatePage: React.StatelessComponent<
> >
{({ data, change, errors, submit, hasChanged }) => ( {({ data, change, errors, submit, hasChanged }) => (
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Categories")}</AppHeader> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.categories)}
</AppHeader>
<PageHeader title={category ? category.name : undefined} /> <PageHeader title={category ? category.name : undefined} />
<CategoryDetailsForm <CategoryDetailsForm
category={category} category={category}
@ -139,9 +143,10 @@ export const CategoryUpdatePage: React.StatelessComponent<
/> />
<CardSpacer /> <CardSpacer />
<SeoForm <SeoForm
helperText={i18n.t( helperText={intl.formatMessage({
defaultMessage:
"Add search engine title and description to make this category easier to find" "Add search engine title and description to make this category easier to find"
)} })}
title={data.seoTitle} title={data.seoTitle}
titlePlaceholder={data.name} titlePlaceholder={data.name}
description={data.seoDescription} description={data.seoDescription}
@ -156,13 +161,19 @@ export const CategoryUpdatePage: React.StatelessComponent<
isActive={currentTab === CategoryPageTab.categories} isActive={currentTab === CategoryPageTab.categories}
changeTab={changeTab} changeTab={changeTab}
> >
{i18n.t("Subcategories")} <FormattedMessage
defaultMessage="Subcategories"
description="number of subcategories in category"
/>
</CategoriesTab> </CategoriesTab>
<ProductsTab <ProductsTab
isActive={currentTab === CategoryPageTab.products} isActive={currentTab === CategoryPageTab.products}
changeTab={changeTab} changeTab={changeTab}
> >
{i18n.t("Products")} <FormattedMessage
defaultMessage="Products"
description="number of products in category"
/>
</ProductsTab> </ProductsTab>
</TabContainer> </TabContainer>
<CardSpacer /> <CardSpacer />
@ -204,9 +215,6 @@ export const CategoryUpdatePage: React.StatelessComponent<
onCancel={onBack} onCancel={onBack}
onDelete={onDelete} onDelete={onDelete}
onSave={submit} onSave={submit}
labels={{
delete: i18n.t("Delete category")
}}
state={saveButtonBarState} state={saveButtonBarState}
disabled={disabled || !hasChanged} disabled={disabled || !hasChanged}
/> />

View file

@ -1,8 +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 {
categoryAddPath, categoryAddPath,
categoryListPath, categoryListPath,
@ -56,9 +58,12 @@ const CategoryList: React.StatelessComponent<RouteComponentProps<{}>> = ({
return <CategoryListComponent params={params} />; return <CategoryListComponent params={params} />;
}; };
const Component = () => ( const Component = () => {
const intl = useIntl();
return (
<> <>
<WindowTitle title={i18n.t("Categories")} /> <WindowTitle title={intl.formatMessage(sectionNames.categories)} />
<Switch> <Switch>
<Route exact path={categoryListPath} component={CategoryList} /> <Route exact path={categoryListPath} component={CategoryList} />
<Route exact path={categoryAddPath()} component={CategoryCreate} /> <Route exact path={categoryAddPath()} component={CategoryCreate} />
@ -66,6 +71,7 @@ const Component = () => (
<Route path={categoryPath(":id")} component={CategoryDetails} /> <Route path={categoryPath(":id")} component={CategoryDetails} />
</Switch> </Switch>
</> </>
); );
};
export default Component; export default Component;

View file

@ -1,9 +1,9 @@
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 i18n from "../../i18n";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../misc";
import CategoryCreatePage from "../components/CategoryCreatePage"; import CategoryCreatePage from "../components/CategoryCreatePage";
import { TypedCategoryCreateMutation } from "../mutations"; import { TypedCategoryCreateMutation } from "../mutations";
@ -19,10 +19,15 @@ export const CategoryCreateView: React.StatelessComponent<
> = ({ parentId }) => { > = ({ parentId }) => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl();
const handleSuccess = (data: CategoryCreate) => { const handleSuccess = (data: CategoryCreate) => {
if (data.categoryCreate.errors.length === 0) { if (data.categoryCreate.errors.length === 0) {
notify({ text: i18n.t("Category created") }); notify({
text: intl.formatMessage({
defaultMessage: "Category created"
})
});
navigate(categoryUrl(data.categoryCreate.category.id)); navigate(categoryUrl(data.categoryCreate.category.id));
} }
}; };
@ -42,7 +47,12 @@ export const CategoryCreateView: React.StatelessComponent<
return ( return (
<> <>
<WindowTitle title={i18n.t("Create category")} /> <WindowTitle
title={intl.formatMessage({
defaultMessage: "Create category",
description: "window title"
})}
/>
<CategoryCreatePage <CategoryCreatePage
saveButtonBarState={formTransitionState} saveButtonBarState={formTransitionState}
errors={errors} errors={errors}

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 ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import { WindowTitle } from "@saleor/components/WindowTitle"; import { WindowTitle } from "@saleor/components/WindowTitle";
@ -11,8 +12,8 @@ import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { commonMessages } from "@saleor/intl";
import { PAGINATE_BY } from "../../config"; import { PAGINATE_BY } from "../../config";
import i18n from "../../i18n";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../misc";
import { TypedProductBulkDeleteMutation } from "../../products/mutations"; import { TypedProductBulkDeleteMutation } from "../../products/mutations";
import { productBulkDelete } from "../../products/types/productBulkDelete"; import { productBulkDelete } from "../../products/types/productBulkDelete";
@ -59,12 +60,13 @@ export const CategoryDetails: React.StatelessComponent<
const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions( const { isSelected, listElements, reset, toggle, toggleAll } = useBulkActions(
params.ids params.ids
); );
const intl = useIntl();
const handleCategoryDelete = (data: CategoryDelete) => { const handleCategoryDelete = (data: CategoryDelete) => {
if (data.categoryDelete.errors.length === 0) { if (data.categoryDelete.errors.length === 0) {
notify({ notify({
text: i18n.t("Category deleted", { text: intl.formatMessage({
context: "notification" defaultMessage: "Category deleted"
}) })
}); });
navigate(categoryListUrl()); navigate(categoryListUrl());
@ -140,7 +142,7 @@ export const CategoryDetails: React.StatelessComponent<
if (data.categoryBulkDelete.errors.length === 0) { if (data.categoryBulkDelete.errors.length === 0) {
closeModal(); closeModal();
notify({ notify({
text: i18n.t("Categories removed") text: intl.formatMessage(commonMessages.savedChanges)
}); });
refetch(); refetch();
reset(); reset();
@ -151,7 +153,7 @@ export const CategoryDetails: React.StatelessComponent<
if (data.productBulkDelete.errors.length === 0) { if (data.productBulkDelete.errors.length === 0) {
closeModal(); closeModal();
notify({ notify({
text: i18n.t("Products removed") text: intl.formatMessage(commonMessages.savedChanges)
}); });
refetch(); refetch();
reset(); reset();
@ -319,32 +321,36 @@ export const CategoryDetails: React.StatelessComponent<
deleteCategory({ variables: { id } }) deleteCategory({ variables: { id } })
} }
open={params.action === "delete"} open={params.action === "delete"}
title={i18n.t("Delete category", { title={intl.formatMessage({
context: "modal title" defaultMessage: "Delete category",
description: "dialog title"
})} })}
variant="delete" variant="delete"
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {categoryName}?"
"Are you sure you want to remove <strong>{{ categoryName }}</strong>? <br /> ", values={{
{ categoryName: (
categoryName: maybe( <strong>
() => data.category.name {maybe(
), () => data.category.name,
context: "modal message" "..."
} )}
</strong>
) )
}} }}
/> />
</DialogContentText>
<DialogContentText> <DialogContentText>
{i18n.t( <FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
"Remember that this will also remove all products assigned to this category."
)}
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
open={params.action === "delete-categories"} open={
params.action === "delete-categories" &&
maybe(() => params.ids.length > 0)
}
confirmButtonState={ confirmButtonState={
categoryBulkDeleteMutationState categoryBulkDeleteMutationState
} }
@ -354,27 +360,32 @@ export const CategoryDetails: React.StatelessComponent<
variables: { ids: params.ids } variables: { ids: params.ids }
}) })
} }
title={i18n.t("Remove categories")} title={intl.formatMessage({
defaultMessage: "Delete categories",
description: "dialog title"
})}
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> categories?", one {this attribute}
{ other {{displayQuantity} categories}
number: maybe( }?"
() => values={{
params.ids.length.toString(), counter: maybe(
"..." () => params.ids.length
) ),
} displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
<DialogContentText> <DialogContentText>
{i18n.t( <FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
"Remember that this will also remove all products assigned to this category."
)}
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
@ -388,23 +399,31 @@ export const CategoryDetails: React.StatelessComponent<
variables: { ids: params.ids } variables: { ids: params.ids }
}) })
} }
title={i18n.t("Remove products")} title={intl.formatMessage({
defaultMessage: "Delete products",
description: "dialog title"
})}
variant="delete" variant="delete"
> >
<DialogContentText {" "}
dangerouslySetInnerHTML={{ <DialogContentText>
__html: i18n.t( <FormattedMessage
"Are you sure you want to remove <strong>{{ number }}</strong> products?", defaultMessage="Are you sure you want to delete {counter, plural,
{ one {this attribute}
number: maybe( other {{displayQuantity} products}
() => }?"
params.ids.length.toString(), values={{
"..." counter: maybe(
) () => params.ids.length
} ),
displayQuantity: (
<strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
</> </>
); );

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 ActionDialog from "@saleor/components/ActionDialog"; import ActionDialog from "@saleor/components/ActionDialog";
import useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
@ -10,7 +11,6 @@ import useNavigator from "@saleor/hooks/useNavigator";
import usePaginator, { import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
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 { CategoryListPage } from "../components/CategoryListPage/CategoryListPage"; import { CategoryListPage } from "../components/CategoryListPage/CategoryListPage";
@ -39,6 +39,8 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
const { updateListSettings, settings } = useListSettings( const { updateListSettings, settings } = useListSettings(
ListViews.CATEGORY_LIST ListViews.CATEGORY_LIST
); );
const intl = useIntl();
const paginationState = createPaginationState(settings.rowNumber, params); const paginationState = createPaginationState(settings.rowNumber, params);
return ( return (
<TypedRootCategoriesQuery displayLoader variables={paginationState}> <TypedRootCategoriesQuery displayLoader variables={paginationState}>
@ -124,26 +126,26 @@ export const CategoryList: React.StatelessComponent<CategoryListProps> = ({
}) })
} }
open={params.action === "delete"} open={params.action === "delete"}
title={i18n.t("Remove categories")} title={intl.formatMessage({
defaultMessage: "Delete categories",
description: "dialog title"
})}
variant="delete" variant="delete"
> >
<DialogContentText <FormattedMessage
dangerouslySetInnerHTML={{ defaultMessage="Are you sure you want to delete {counter, plural,
__html: i18n.t( one {this attribute}
"Are you sure you want to remove <strong>{{ number }}</strong> categories?", other {{displayQuantity} categories}
{ }?"
number: maybe( values={{
() => params.ids.length.toString(), counter: maybe(() => params.ids.length),
"..." displayQuantity: (
) <strong>{maybe(() => params.ids.length)}</strong>
}
) )
}} }}
/> />
<DialogContentText> <DialogContentText>
{i18n.t( <FormattedMessage defaultMessage="Remember this will also delete all products assigned to this category." />
"Remember that this will also remove all products assigned to this category."
)}
</DialogContentText> </DialogContentText>
</ActionDialog> </ActionDialog>
</> </>

View file

@ -2,6 +2,7 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
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";
@ -14,7 +15,7 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import VisibilityCard from "@saleor/components/VisibilityCard"; import VisibilityCard from "@saleor/components/VisibilityCard";
import i18n from "../../../i18n"; import { commonMessages, sectionNames } from "@saleor/intl";
import { UserError } from "../../../types"; import { UserError } from "../../../types";
import CollectionDetails from "../CollectionDetails/CollectionDetails"; import CollectionDetails from "../CollectionDetails/CollectionDetails";
import { CollectionImage } from "../CollectionImage/CollectionImage"; import { CollectionImage } from "../CollectionImage/CollectionImage";
@ -63,14 +64,20 @@ const CollectionCreatePage: React.StatelessComponent<
saveButtonBarState, saveButtonBarState,
onBack, onBack,
onSubmit onSubmit
}: CollectionCreatePageProps) => ( }: CollectionCreatePageProps) => {
const intl = useIntl();
return (
<Form errors={errors} initial={initialForm} onSubmit={onSubmit}> <Form errors={errors} initial={initialForm} onSubmit={onSubmit}>
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, errors: formErrors, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Collections")}</AppHeader> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.collections)}
</AppHeader>
<PageHeader <PageHeader
title={i18n.t("Add collection", { title={intl.formatMessage({
context: "page title" defaultMessage: "Add collection",
description: "page header"
})} })}
/> />
<Grid> <Grid>
@ -122,12 +129,10 @@ const CollectionCreatePage: React.StatelessComponent<
description={data.seoDescription} description={data.seoDescription}
disabled={disabled} disabled={disabled}
descriptionPlaceholder="" descriptionPlaceholder=""
helperText={i18n.t( helperText={intl.formatMessage({
"Add search engine title and description to make this collection easier to find", defaultMessage:
{ "Add search engine title and description to make this collection easier to find"
context: "help text" })}
}
)}
title={data.seoTitle} title={data.seoTitle}
titlePlaceholder={data.name} titlePlaceholder={data.name}
onChange={change} onChange={change}
@ -137,9 +142,7 @@ const CollectionCreatePage: React.StatelessComponent<
<div> <div>
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Availability", { title={intl.formatMessage(commonMessages.availability)}
context: "collection status"
})}
/> />
<CardContent> <CardContent>
<VisibilityCard <VisibilityCard
@ -162,6 +165,7 @@ const CollectionCreatePage: React.StatelessComponent<
</Container> </Container>
)} )}
</Form> </Form>
); );
};
CollectionCreatePage.displayName = "CollectionCreatePage"; CollectionCreatePage.displayName = "CollectionCreatePage";
export default CollectionCreatePage; export default CollectionCreatePage;

View file

@ -4,13 +4,14 @@ import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles";
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";
import { maybe } from "../../../misc"; import { maybe } from "@saleor/misc";
import { FormErrors } from "../../../types"; import { FormErrors } from "@saleor/types";
import { CollectionDetails_collection } from "../../types/CollectionDetails"; import { CollectionDetails_collection } from "../../types/CollectionDetails";
const styles = createStyles({ const styles = createStyles({
@ -38,13 +39,21 @@ const CollectionDetails = withStyles(styles, { name: "CollectionDetails" })(
data, data,
onChange, onChange,
errors errors
}: CollectionDetailsProps) => ( }: CollectionDetailsProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle title={i18n.t("General information")} /> <CardTitle
title={intl.formatMessage(commonMessages.generalInformations)}
/>
<CardContent> <CardContent>
<TextField <TextField
classes={{ root: classes.name }} classes={{ root: classes.name }}
label={i18n.t("Name")} label={intl.formatMessage({
defaultMessage: "Name",
description: "collection name"
})}
name="name" name="name"
disabled={disabled} disabled={disabled}
value={data.name} value={data.name}
@ -57,14 +66,15 @@ const CollectionDetails = withStyles(styles, { name: "CollectionDetails" })(
error={!!errors.descriptionJson} error={!!errors.descriptionJson}
helperText={errors.descriptionJson} helperText={errors.descriptionJson}
initial={maybe(() => JSON.parse(collection.descriptionJson))} initial={maybe(() => JSON.parse(collection.descriptionJson))}
label={i18n.t("Description")} label={intl.formatMessage(commonMessages.description)}
name="description" name="description"
disabled={disabled} disabled={disabled}
onChange={onChange} onChange={onChange}
/> />
</CardContent> </CardContent>
</Card> </Card>
) );
}
); );
CollectionDetails.displayName = "CollectionDetails"; CollectionDetails.displayName = "CollectionDetails";
export default CollectionDetails; export default CollectionDetails;

View file

@ -1,5 +1,6 @@
import { RawDraftContentState } from "draft-js"; import { 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";
@ -12,7 +13,7 @@ import PageHeader from "@saleor/components/PageHeader";
import SaveButtonBar from "@saleor/components/SaveButtonBar"; import SaveButtonBar from "@saleor/components/SaveButtonBar";
import SeoForm from "@saleor/components/SeoForm"; import SeoForm from "@saleor/components/SeoForm";
import VisibilityCard from "@saleor/components/VisibilityCard"; import VisibilityCard from "@saleor/components/VisibilityCard";
import i18n from "../../../i18n"; import { sectionNames } from "@saleor/intl";
import { maybe } from "../../../misc"; import { maybe } from "../../../misc";
import { ListActions, PageListProps } from "../../../types"; import { ListActions, PageListProps } from "../../../types";
import { CollectionDetails_collection } from "../../types/CollectionDetails"; import { CollectionDetails_collection } from "../../types/CollectionDetails";
@ -57,6 +58,8 @@ const CollectionDetailsPage: React.StatelessComponent<
onSubmit, onSubmit,
...collectionProductsProps ...collectionProductsProps
}: CollectionDetailsPageProps) => { }: CollectionDetailsPageProps) => {
const intl = useIntl();
return ( return (
<Form <Form
initial={{ initial={{
@ -74,7 +77,9 @@ const CollectionDetailsPage: React.StatelessComponent<
> >
{({ change, data, errors: formErrors, hasChanged, submit }) => ( {({ change, data, errors: formErrors, hasChanged, submit }) => (
<Container> <Container>
<AppHeader onBack={onBack}>{i18n.t("Collections")}</AppHeader> <AppHeader onBack={onBack}>
{intl.formatMessage(sectionNames.collections)}
</AppHeader>
<PageHeader title={maybe(() => collection.name)} /> <PageHeader title={maybe(() => collection.name)} />
<Grid> <Grid>
<div> <div>
@ -104,12 +109,10 @@ const CollectionDetailsPage: React.StatelessComponent<
description={data.seoDescription} description={data.seoDescription}
disabled={disabled} disabled={disabled}
descriptionPlaceholder="" descriptionPlaceholder=""
helperText={i18n.t( helperText={intl.formatMessage({
"Add search engine title and description to make this collection easier to find", defaultMessage:
{ "Add search engine title and description to make this collection easier to find"
context: "help text" })}
}
)}
title={data.seoTitle} title={data.seoTitle}
titlePlaceholder={maybe(() => collection.name)} titlePlaceholder={maybe(() => collection.name)}
onChange={change} onChange={change}
@ -128,8 +131,9 @@ const CollectionDetailsPage: React.StatelessComponent<
disabled={disabled} disabled={disabled}
name="isFeatured" name="isFeatured"
onChange={change} onChange={change}
label={i18n.t("Feature on Homepage", { label={intl.formatMessage({
context: "button" defaultMessage: "Feature on Homepage",
description: "switch button"
})} })}
/> />
</VisibilityCard> </VisibilityCard>

View file

@ -6,6 +6,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 { FormattedMessage, useIntl } from "react-intl";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
@ -15,7 +16,7 @@ import Hr from "@saleor/components/Hr";
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 Skeleton from "@saleor/components/Skeleton"; import Skeleton from "@saleor/components/Skeleton";
import i18n from "../../../i18n"; import { commonMessages } from "@saleor/intl";
import { CollectionDetails_collection_backgroundImage } from "../../types/CollectionDetails"; import { CollectionDetails_collection_backgroundImage } from "../../types/CollectionDetails";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -51,7 +52,7 @@ const styles = (theme: Theme) =>
} }
}); });
export interface CollectionImageProps extends WithStyles<typeof styles> { export interface CollectionImageProps {
data: { data: {
backgroundImageAlt: string; backgroundImageAlt: string;
}; };
@ -62,42 +63,41 @@ export interface CollectionImageProps extends WithStyles<typeof styles> {
} }
export const CollectionImage = withStyles(styles)( export const CollectionImage = withStyles(styles)(
class CollectionImageComponent extends React.Component< ({
CollectionImageProps,
{}
> {
imgInputAnchor = React.createRef<HTMLInputElement>();
clickImgInput = () => this.imgInputAnchor.current.click();
render() {
const {
classes, classes,
data, data,
onImageUpload, onImageUpload,
image, image,
onChange, onChange,
onImageDelete onImageDelete
} = this.props; }: CollectionImageProps & WithStyles<typeof styles>) => {
const anchor = React.useRef<HTMLInputElement>();
const intl = useIntl();
const handleImageUploadButtonClick = () => anchor.current.click();
return ( return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Background image (optional)")} title={intl.formatMessage({
defaultMessage: "Background image (optional)",
description: "section header"
})}
toolbar={ toolbar={
<> <>
<Button <Button
variant="text" variant="text"
color="primary" color="primary"
onClick={this.clickImgInput} onClick={handleImageUploadButtonClick}
> >
{i18n.t("Upload image")} <FormattedMessage {...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={this.imgInputAnchor} ref={anchor}
/> />
</> </>
} }
@ -123,8 +123,11 @@ export const CollectionImage = withStyles(styles)(
<CardContent> <CardContent>
<TextField <TextField
name="backgroundImageAlt" name="backgroundImageAlt"
label={i18n.t("Description")} label={intl.formatMessage(commonMessages.description)}
helperText={i18n.t("Optional")} helperText={intl.formatMessage({
defaultMessage: "(Optional)",
description: "field is optional"
})}
value={data.backgroundImageAlt} value={data.backgroundImageAlt}
onChange={onChange} onChange={onChange}
fullWidth fullWidth
@ -136,7 +139,6 @@ export const CollectionImage = withStyles(styles)(
</Card> </Card>
); );
} }
}
); );
CollectionImage.displayName = "CollectionImage"; CollectionImage.displayName = "CollectionImage";
export default CollectionImage; export default CollectionImage;

View file

@ -11,13 +11,13 @@ import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter"; import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import Checkbox from "@saleor/components/Checkbox"; import Checkbox from "@saleor/components/Checkbox";
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 TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import i18n from "@saleor/i18n";
import { maybe, renderCollection } from "@saleor/misc"; import { maybe, renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps } from "@saleor/types";
import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import { CollectionList_collections_edges_node } from "../../types/CollectionList";
@ -68,7 +68,10 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
}: CollectionListProps) => ( }: CollectionListProps) => {
const intl = useIntl();
return (
<Card> <Card>
<Table> <Table>
<TableHead <TableHead
@ -80,15 +83,16 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
{i18n.t("Category Name", { context: "table cell" })} <FormattedMessage defaultMessage="Category Name" />
</TableCell> </TableCell>
<TableCell className={classes.colProducts}> <TableCell className={classes.colProducts}>
{i18n <FormattedMessage defaultMessage="No. of Products" />
.t("No. Products", { context: "table cell" })
.replace(" ", "\xa0")}
</TableCell> </TableCell>
<TableCell className={classes.colAvailability}> <TableCell className={classes.colAvailability}>
{i18n.t("Availability", { context: "table cell" })} <FormattedMessage
defaultMessage="Availability"
description="collection availability"
/>
</TableCell> </TableCell>
</TableHead> </TableHead>
<TableFooter> <TableFooter>
@ -96,7 +100,9 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
<TablePagination <TablePagination
colSpan={numberOfColumns} colSpan={numberOfColumns}
settings={settings} settings={settings}
hasNextPage={pageInfo && !disabled ? pageInfo.hasNextPage : false} hasNextPage={
pageInfo && !disabled ? pageInfo.hasNextPage : false
}
onNextPage={onNextPage} onNextPage={onNextPage}
onUpdateListSettings={onUpdateListSettings} onUpdateListSettings={onUpdateListSettings}
hasPreviousPage={ hasPreviousPage={
@ -110,7 +116,9 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
{renderCollection( {renderCollection(
collections, collections,
collection => { collection => {
const isSelected = collection ? isChecked(collection.id) : false; const isSelected = collection
? isChecked(collection.id)
: false;
return ( return (
<TableRow <TableRow
className={classes.tableRow} className={classes.tableRow}
@ -143,11 +151,19 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
{maybe( {maybe(
() => ( () => (
<StatusLabel <StatusLabel
status={collection.isPublished ? "success" : "error"} status={
collection.isPublished ? "success" : "error"
}
label={ label={
collection.isPublished collection.isPublished
? i18n.t("Published") ? intl.formatMessage({
: i18n.t("Not published") defaultMessage: "Published",
description: "collection is published"
})
: intl.formatMessage({
defaultMessage: "Not published",
description: "collection is not published"
})
} }
/> />
), ),
@ -160,7 +176,7 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{i18n.t("No collections found")} <FormattedMessage defaultMessage="No collections found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
@ -168,7 +184,8 @@ const CollectionList = withStyles(styles, { name: "CollectionList" })(
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) );
}
); );
CollectionList.displayName = "CollectionList"; CollectionList.displayName = "CollectionList";
export default CollectionList; export default CollectionList;

View file

@ -1,10 +1,11 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import AddIcon from "@material-ui/icons/Add"; import AddIcon from "@material-ui/icons/Add";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { Container } from "@saleor/components/Container"; import { Container } from "@saleor/components/Container";
import PageHeader from "@saleor/components/PageHeader"; import PageHeader from "@saleor/components/PageHeader";
import i18n from "@saleor/i18n"; import { sectionNames } from "@saleor/intl";
import { ListActions, PageListProps } from "@saleor/types"; import { ListActions, PageListProps } from "@saleor/types";
import { CollectionList_collections_edges_node } from "../../types/CollectionList"; import { CollectionList_collections_edges_node } from "../../types/CollectionList";
import CollectionList from "../CollectionList/CollectionList"; import CollectionList from "../CollectionList/CollectionList";
@ -17,21 +18,28 @@ const CollectionListPage: React.StatelessComponent<CollectionListPageProps> = ({
disabled, disabled,
onAdd, onAdd,
...listProps ...listProps
}) => ( }) => {
const intl = useIntl();
return (
<Container> <Container>
<PageHeader title={i18n.t("Collections", { context: "page title" })}> <PageHeader title={intl.formatMessage(sectionNames.collections)}>
<Button <Button
color="primary" color="primary"
disabled={disabled} disabled={disabled}
variant="contained" variant="contained"
onClick={onAdd} onClick={onAdd}
> >
{i18n.t("Add collection", { context: "button" })} <FormattedMessage
defaultMessage="Add collection"
description="button"
/>
<AddIcon /> <AddIcon />
</Button> </Button>
</PageHeader> </PageHeader>
<CollectionList disabled={disabled} {...listProps} /> <CollectionList disabled={disabled} {...listProps} />
</Container> </Container>
); );
};
CollectionListPage.displayName = "CollectionListPage"; CollectionListPage.displayName = "CollectionListPage";
export default CollectionListPage; export default CollectionListPage;

View file

@ -14,6 +14,7 @@ import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
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 CardTitle from "@saleor/components/CardTitle"; import CardTitle from "@saleor/components/CardTitle";
import Checkbox from "@saleor/components/Checkbox"; import Checkbox from "@saleor/components/Checkbox";
@ -24,7 +25,6 @@ import TableCellAvatar, {
} from "@saleor/components/TableCellAvatar"; } from "@saleor/components/TableCellAvatar";
import TableHead from "@saleor/components/TableHead"; import TableHead from "@saleor/components/TableHead";
import TablePagination from "@saleor/components/TablePagination"; import TablePagination from "@saleor/components/TablePagination";
import i18n from "../../../i18n";
import { maybe, renderCollection } from "../../../misc"; import { maybe, renderCollection } from "../../../misc";
import { ListActions, PageListProps } from "../../../types"; import { ListActions, PageListProps } from "../../../types";
import { CollectionDetails_collection } from "../../types/CollectionDetails"; import { CollectionDetails_collection } from "../../types/CollectionDetails";
@ -83,14 +83,23 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
toggle, toggle,
toggleAll, toggleAll,
toolbar toolbar
}: CollectionProductsProps) => ( }: CollectionProductsProps) => {
const intl = useIntl();
return (
<Card> <Card>
<CardTitle <CardTitle
title={ title={
!!collection ? ( !!collection ? (
i18n.t("Products in {{ collectionName }}", { intl.formatMessage(
collectionName: collection.name {
}) defaultMessage: "Products in {name}",
description: "products in collection"
},
{
name: maybe(() => collection.name, "...")
}
)
) : ( ) : (
<Skeleton /> <Skeleton />
) )
@ -102,9 +111,10 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
color="primary" color="primary"
onClick={onAdd} onClick={onAdd}
> >
{i18n.t("Assign product", { <FormattedMessage
context: "button" defaultMessage="Assign product"
})} description="button"
/>
</Button> </Button>
} }
/> />
@ -113,20 +123,31 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
colSpan={numberOfColumns} colSpan={numberOfColumns}
selected={selected} selected={selected}
disabled={disabled} disabled={disabled}
items={maybe(() => collection.products.edges.map(edge => edge.node))} items={maybe(() =>
collection.products.edges.map(edge => edge.node)
)}
toggleAll={toggleAll} toggleAll={toggleAll}
toolbar={toolbar} toolbar={toolbar}
> >
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
<span className={classes.colNameLabel}> <span className={classes.colNameLabel}>
{i18n.t("Name", { context: "table header" })} <FormattedMessage
defaultMessage="Name"
description="product name"
/>
</span> </span>
</TableCell> </TableCell>
<TableCell className={classes.colType}> <TableCell className={classes.colType}>
{i18n.t("Type", { context: "table header" })} <FormattedMessage
defaultMessage="Type"
description="product type"
/>
</TableCell> </TableCell>
<TableCell className={classes.colPublished}> <TableCell className={classes.colPublished}>
{i18n.t("Published", { context: "table header" })} <FormattedMessage
defaultMessage="Published"
description="product is published"
/>
</TableCell> </TableCell>
<TableCell className={classes.colActions} /> <TableCell className={classes.colActions} />
</TableHead> </TableHead>
@ -181,8 +202,14 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
<StatusLabel <StatusLabel
label={ label={
product.isPublished product.isPublished
? i18n.t("Published") ? intl.formatMessage({
: i18n.t("Not published") defaultMessage: "Published",
description: "product is published"
})
: intl.formatMessage({
defaultMessage: "Not published",
description: "product is not published"
})
} }
status={product.isPublished ? "success" : "error"} status={product.isPublished ? "success" : "error"}
/> />
@ -205,7 +232,7 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
<TableRow> <TableRow>
<TableCell /> <TableCell />
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{i18n.t("No products found")} <FormattedMessage defaultMessage="No products found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )
@ -213,7 +240,8 @@ const CollectionProducts = withStyles(styles, { name: "CollectionProducts" })(
</TableBody> </TableBody>
</Table> </Table>
</Card> </Card>
) );
}
); );
CollectionProducts.displayName = "CollectionProducts"; CollectionProducts.displayName = "CollectionProducts";
export default CollectionProducts; export default CollectionProducts;

View file

@ -31,8 +31,7 @@ import {
} from "../types/UnassignCollectionProduct"; } from "../types/UnassignCollectionProduct";
interface CollectionUpdateOperationsProps { interface CollectionUpdateOperationsProps {
children: ( children: (props: {
props: {
updateCollectionWithHomepage: PartialMutationProviderOutput< updateCollectionWithHomepage: PartialMutationProviderOutput<
CollectionUpdateWithHomepage, CollectionUpdateWithHomepage,
CollectionUpdateWithHomepageVariables CollectionUpdateWithHomepageVariables
@ -53,8 +52,7 @@ interface CollectionUpdateOperationsProps {
RemoveCollection, RemoveCollection,
RemoveCollectionVariables RemoveCollectionVariables
>; >;
} }) => React.ReactNode;
) => React.ReactNode;
onUpdate: (data: CollectionUpdate) => void; onUpdate: (data: CollectionUpdate) => void;
onProductAssign: (data: CollectionAssignProduct) => void; onProductAssign: (data: CollectionAssignProduct) => void;
onProductUnassign: (data: UnassignCollectionProduct) => void; onProductUnassign: (data: UnassignCollectionProduct) => void;

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 {
collectionAddPath, collectionAddPath,
collectionListPath, collectionListPath,
@ -39,14 +40,18 @@ const CollectionDetails: React.StatelessComponent<
); );
}; };
const Component = () => ( const Component = () => {
const intl = useIntl();
return (
<> <>
<WindowTitle title={i18n.t("Collections")} /> <WindowTitle title={intl.formatMessage(sectionNames.collections)} />
<Switch> <Switch>
<Route exact path={collectionListPath} component={CollectionList} /> <Route exact path={collectionListPath} component={CollectionList} />
<Route exact path={collectionAddPath} component={CollectionCreate} /> <Route exact path={collectionAddPath} component={CollectionCreate} />
<Route path={collectionPath(":id")} component={CollectionDetails} /> <Route path={collectionPath(":id")} component={CollectionDetails} />
</Switch> </Switch>
</> </>
); );
};
export default Component; export default Component;

View file

@ -1,9 +1,9 @@
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 i18n from "../../i18n";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../misc";
import { CollectionCreateInput } from "../../types/globalTypes"; import { CollectionCreateInput } from "../../types/globalTypes";
import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage"; import CollectionCreatePage from "../components/CollectionCreatePage/CollectionCreatePage";
@ -11,15 +11,16 @@ import { TypedCollectionCreateMutation } from "../mutations";
import { CreateCollection } from "../types/CreateCollection"; import { CreateCollection } from "../types/CreateCollection";
import { collectionListUrl, collectionUrl } from "../urls"; import { collectionListUrl, collectionUrl } from "../urls";
export const CollectionCreate: React.StatelessComponent<{}> = () => { export const CollectionCreate: React.FC = () => {
const navigate = useNavigator(); const navigate = useNavigator();
const notify = useNotifier(); const notify = useNotifier();
const intl = useIntl();
const handleCollectionCreateSuccess = (data: CreateCollection) => { const handleCollectionCreateSuccess = (data: CreateCollection) => {
if (data.collectionCreate.errors.length === 0) { if (data.collectionCreate.errors.length === 0) {
notify({ notify({
text: i18n.t("Created collection", { text: intl.formatMessage({
context: "notification" defaultMessage: "Created collection"
}) })
}); });
navigate(collectionUrl(data.collectionCreate.collection.id)); navigate(collectionUrl(data.collectionCreate.collection.id));
@ -45,7 +46,12 @@ export const CollectionCreate: React.StatelessComponent<{}> = () => {
); );
return ( return (
<> <>
<WindowTitle title={i18n.t("Create collection")} /> <WindowTitle
title={intl.formatMessage({
defaultMessage: "Create collection",
description: "window title"
})}
/>
<CollectionCreatePage <CollectionCreatePage
errors={maybe(() => data.collectionCreate.errors, [])} errors={maybe(() => data.collectionCreate.errors, [])}
onBack={() => navigate(collectionListUrl())} onBack={() => navigate(collectionListUrl())}

View file

@ -1,6 +1,7 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
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 AssignProductDialog from "@saleor/components/AssignProductDialog"; import AssignProductDialog from "@saleor/components/AssignProductDialog";
@ -11,9 +12,9 @@ import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import { commonMessages } from "@saleor/intl";
import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config"; import { DEFAULT_INITIAL_SEARCH_DATA, PAGINATE_BY } from "../../config";
import SearchProducts from "../../containers/SearchProducts"; import SearchProducts from "../../containers/SearchProducts";
import i18n from "../../i18n";
import { getMutationState, maybe } from "../../misc"; import { getMutationState, maybe } from "../../misc";
import { productUrl } from "../../products/urls"; import { productUrl } from "../../products/urls";
import { CollectionInput } from "../../types/globalTypes"; import { CollectionInput } from "../../types/globalTypes";
@ -47,6 +48,7 @@ export const CollectionDetails: React.StatelessComponent<
params.ids params.ids
); );
const paginate = usePaginator(); const paginate = usePaginator();
const intl = useIntl();
const closeModal = () => const closeModal = () =>
navigate( navigate(
@ -77,9 +79,7 @@ export const CollectionDetails: React.StatelessComponent<
const handleCollectionUpdate = (data: CollectionUpdate) => { const handleCollectionUpdate = (data: CollectionUpdate) => {
if (data.collectionUpdate.errors.length === 0) { if (data.collectionUpdate.errors.length === 0) {
notify({ notify({
text: i18n.t("Updated collection", { text: intl.formatMessage(commonMessages.savedChanges)
context: "notification"
})
}); });
navigate(collectionUrl(id)); navigate(collectionUrl(id));
} else { } else {
@ -98,8 +98,8 @@ export const CollectionDetails: React.StatelessComponent<
const handleProductAssign = (data: CollectionAssignProduct) => { const handleProductAssign = (data: CollectionAssignProduct) => {
if (data.collectionAddProducts.errors.length === 0) { if (data.collectionAddProducts.errors.length === 0) {
notify({ notify({
text: i18n.t("Added product to collection", { text: intl.formatMessage({
context: "notification" defaultMessage: "Added product to collection"
}) })
}); });
navigate(collectionUrl(id), true); navigate(collectionUrl(id), true);
@ -109,8 +109,8 @@ export const CollectionDetails: React.StatelessComponent<
const handleProductUnassign = (data: UnassignCollectionProduct) => { const handleProductUnassign = (data: UnassignCollectionProduct) => {
if (data.collectionRemoveProducts.errors.length === 0) { if (data.collectionRemoveProducts.errors.length === 0) {
notify({ notify({
text: i18n.t("Removed product from collection", { text: intl.formatMessage({
context: "notification" defaultMessage: "Deleted product from collection"
}) })
}); });
reset(); reset();
@ -121,8 +121,8 @@ export const CollectionDetails: React.StatelessComponent<
const handleCollectionRemove = (data: RemoveCollection) => { const handleCollectionRemove = (data: RemoveCollection) => {
if (data.collectionDelete.errors.length === 0) { if (data.collectionDelete.errors.length === 0) {
notify({ notify({
text: i18n.t("Removed collection", { text: intl.formatMessage({
context: "notification" defaultMessage: "Deleted collection"
}) })
}); });
navigate(collectionListUrl()); navigate(collectionListUrl());
@ -272,7 +272,10 @@ export const CollectionDetails: React.StatelessComponent<
) )
} }
> >
{i18n.t("Unassign")} <FormattedMessage
defaultMessage="Unassign"
description="unassign product from collection, button"
/>
</Button> </Button>
} }
isChecked={isSelected} isChecked={isSelected}
@ -308,25 +311,24 @@ export const CollectionDetails: React.StatelessComponent<
onClose={closeModal} onClose={closeModal}
onConfirm={() => removeCollection.mutate({ id })} onConfirm={() => removeCollection.mutate({ id })}
open={params.action === "remove"} open={params.action === "remove"}
title={i18n.t("Remove collection", { title={intl.formatMessage({
context: "modal title" defaultMessage: "Delete Collection",
description: "dialog title"
})} })}
variant="delete" variant="delete"
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {collectionName}?"
"Are you sure you want to remove <strong>{{ collectionName }}</strong>?", values={{
{ collectionName: (
collectionName: maybe( <strong>
() => data.collection.name, {maybe(() => data.collection.name, "...")}
"..." </strong>
),
context: "modal"
}
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
confirmButtonState={unassignTransitionState} confirmButtonState={unassignTransitionState}
@ -339,24 +341,25 @@ export const CollectionDetails: React.StatelessComponent<
}) })
} }
open={params.action === "unassign"} open={params.action === "unassign"}
title={i18n.t("Unassign products from collection", { title={intl.formatMessage({
context: "modal title" defaultMessage: "Unassign products from collection",
description: "dialog title"
})} })}
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to unassign {counter, plural,
"Are you sure you want to unassign <strong>{{ number }}</strong> products?", one {this product}
{ other {{displayQuantity} products}
context: "modal", }?"
number: maybe( values={{
() => params.ids.length.toString(), counter: maybe(() => params.ids.length),
"..." displayQuantity: (
) <strong>{maybe(() => params.ids.length)}</strong>
}
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
confirmButtonState={imageRemoveTransitionState} confirmButtonState={imageRemoveTransitionState}
@ -370,15 +373,14 @@ export const CollectionDetails: React.StatelessComponent<
}) })
} }
open={params.action === "removeImage"} open={params.action === "removeImage"}
title={i18n.t("Remove image", { title={intl.formatMessage({
context: "modal title" defaultMessage: "Delete image",
description: "dialog title"
})} })}
variant="delete" variant="delete"
> >
<DialogContentText> <DialogContentText>
{i18n.t( <FormattedMessage defaultMessage="Are you sure you want to delete collection's image?" />
"Are you sure you want to remove collection's image?"
)}
</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 useBulkActions from "@saleor/hooks/useBulkActions"; import useBulkActions from "@saleor/hooks/useBulkActions";
@ -12,7 +13,7 @@ import useNotifier from "@saleor/hooks/useNotifier";
import usePaginator, { import usePaginator, {
createPaginationState createPaginationState
} from "@saleor/hooks/usePaginator"; } from "@saleor/hooks/usePaginator";
import i18n from "@saleor/i18n"; import { commonMessages } from "@saleor/intl";
import { getMutationState, maybe } from "@saleor/misc"; import { getMutationState, maybe } from "@saleor/misc";
import { ListViews } from "@saleor/types"; import { ListViews } from "@saleor/types";
import CollectionListPage from "../components/CollectionListPage/CollectionListPage"; import CollectionListPage from "../components/CollectionListPage/CollectionListPage";
@ -47,6 +48,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
const { updateListSettings, settings } = useListSettings( const { updateListSettings, settings } = useListSettings(
ListViews.COLLECTION_LIST ListViews.COLLECTION_LIST
); );
const intl = useIntl();
const closeModal = () => const closeModal = () =>
navigate( navigate(
@ -79,7 +81,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
const handleCollectionBulkDelete = (data: CollectionBulkDelete) => { const handleCollectionBulkDelete = (data: CollectionBulkDelete) => {
if (data.collectionBulkDelete.errors.length === 0) { if (data.collectionBulkDelete.errors.length === 0) {
notify({ notify({
text: i18n.t("Removed collections") text: intl.formatMessage(commonMessages.savedChanges)
}); });
refetch(); refetch();
reset(); reset();
@ -90,7 +92,7 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
const handleCollectionBulkPublish = (data: CollectionBulkPublish) => { const handleCollectionBulkPublish = (data: CollectionBulkPublish) => {
if (data.collectionBulkPublish.errors.length === 0) { if (data.collectionBulkPublish.errors.length === 0) {
notify({ notify({
text: i18n.t("Changed publication status") text: intl.formatMessage(commonMessages.savedChanges)
}); });
refetch(); refetch();
reset(); reset();
@ -147,13 +149,19 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
openModal("unpublish", listElements) openModal("unpublish", listElements)
} }
> >
{i18n.t("Unpublish")} <FormattedMessage
defaultMessage="Unpublish"
description="unpublish collections"
/>
</Button> </Button>
<Button <Button
color="primary" color="primary"
onClick={() => openModal("publish", listElements)} onClick={() => openModal("publish", listElements)}
> >
{i18n.t("Publish")} <FormattedMessage
defaultMessage="Publish"
description="publish collections"
/>
</Button> </Button>
<IconButton <IconButton
color="primary" color="primary"
@ -169,7 +177,10 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
toggleAll={toggleAll} toggleAll={toggleAll}
/> />
<ActionDialog <ActionDialog
open={params.action === "publish"} open={
params.action === "publish" &&
maybe(() => params.ids.length > 0)
}
onClose={closeModal} onClose={closeModal}
confirmButtonState={bulkPublishTransitionState} confirmButtonState={bulkPublishTransitionState}
onConfirm={() => onConfirm={() =>
@ -181,24 +192,33 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
}) })
} }
variant="default" variant="default"
title={i18n.t("Publish collections")} title={intl.formatMessage({
defaultMessage: "Publish collections",
description: "dialog title"
})}
> >
<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> collections?", one {this collection}
{ other {{displayQuantity} collections}
number: maybe( }?"
() => params.ids.length.toString(), 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" &&
maybe(() => params.ids.length > 0)
}
onClose={closeModal} onClose={closeModal}
confirmButtonState={bulkPublishTransitionState} confirmButtonState={bulkPublishTransitionState}
onConfirm={() => onConfirm={() =>
@ -210,24 +230,33 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
}) })
} }
variant="default" variant="default"
title={i18n.t("Unpublish collections")} title={intl.formatMessage({
defaultMessage: "Unpublish collections",
description: "dialog title"
})}
> >
<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> collections?", one {this collection}
{ other {{displayQuantity} collections}
number: maybe( }?"
() => params.ids.length.toString(), values={{
"..." counter: maybe(() => params.ids.length),
) displayQuantity: (
} <strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
<ActionDialog <ActionDialog
open={params.action === "remove"} open={
params.action === "remove" &&
maybe(() => params.ids.length > 0)
}
onClose={closeModal} onClose={closeModal}
confirmButtonState={bulkDeleteTransitionState} confirmButtonState={bulkDeleteTransitionState}
onConfirm={() => onConfirm={() =>
@ -238,21 +267,27 @@ export const CollectionList: React.StatelessComponent<CollectionListProps> = ({
}) })
} }
variant="delete" variant="delete"
title={i18n.t("Remove collections")} title={intl.formatMessage({
defaultMessage: "Delete collections",
description: "dialog title"
})}
> >
<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> collections?", one {this collection}
{ other {{displayQuantity} collections}
number: maybe( }?"
() => params.ids.length.toString(), values={{
"..." counter: maybe(() => params.ids.length),
) displayQuantity: (
} <strong>
{maybe(() => params.ids.length)}
</strong>
) )
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
</> </>
); );

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, useIntl } from "react-intl";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
} from "../ConfirmButton/ConfirmButton"; } from "../ConfirmButton/ConfirmButton";
@ -50,13 +51,16 @@ const ActionDialog = withStyles(styles, { name: "ActionDialog" })(
variant, variant,
onConfirm, onConfirm,
onClose onClose
}: ActionDialogProps) => ( }: ActionDialogProps) => {
const intl = useIntl();
return (
<Dialog onClose={onClose} open={open}> <Dialog onClose={onClose} open={open}>
<DialogTitle>{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
<DialogContent>{children}</DialogContent> <DialogContent>{children}</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}
@ -69,12 +73,13 @@ const ActionDialog = withStyles(styles, { name: "ActionDialog" })(
> >
{confirmButtonLabel || {confirmButtonLabel ||
(variant === "delete" (variant === "delete"
? i18n.t("Delete", { context: "button" }) ? intl.formatMessage(buttonMessages.delete)
: i18n.t("Confirm", { context: "button" }))} : intl.formatMessage(buttonMessages.confirm))}
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
) );
}
); );
ActionDialog.displayName = "ActionDialog"; ActionDialog.displayName = "ActionDialog";
export default ActionDialog; export default ActionDialog;

View file

@ -6,9 +6,10 @@ 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 { AddressTypeInput } from "@saleor/customers/types"; import { AddressTypeInput } from "@saleor/customers/types";
import i18n from "@saleor/i18n"; import { commonMessages } from "@saleor/intl";
import { FormErrors } from "@saleor/types"; import { FormErrors } from "@saleor/types";
import FormSpacer from "../FormSpacer"; import FormSpacer from "../FormSpacer";
import SingleAutocompleteSelectField, { import SingleAutocompleteSelectField, {
@ -44,7 +45,10 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
errors, errors,
onChange, onChange,
onCountryChange onCountryChange
}: AddressEditProps) => ( }: AddressEditProps) => {
const intl = useIntl();
return (
<> <>
<div className={classes.root}> <div className={classes.root}>
<div> <div>
@ -52,7 +56,7 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.firstName} error={!!errors.firstName}
helperText={errors.firstName} helperText={errors.firstName}
label={i18n.t("First name")} label={intl.formatMessage(commonMessages.firstName)}
name="firstName" name="firstName"
onChange={onChange} onChange={onChange}
value={data.firstName} value={data.firstName}
@ -64,7 +68,7 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.lastName} error={!!errors.lastName}
helperText={errors.lastName} helperText={errors.lastName}
label={i18n.t("Last name")} label={intl.formatMessage(commonMessages.lastName)}
name="lastName" name="lastName"
onChange={onChange} onChange={onChange}
value={data.lastName} value={data.lastName}
@ -79,7 +83,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.companyName} error={!!errors.companyName}
helperText={errors.companyName} helperText={errors.companyName}
label={i18n.t("Company")} label={intl.formatMessage({
defaultMessage: "Company"
})}
name="companyName" name="companyName"
onChange={onChange} onChange={onChange}
value={data.companyName} value={data.companyName}
@ -92,7 +98,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
error={!!errors.phone} error={!!errors.phone}
fullWidth fullWidth
helperText={errors.phone} helperText={errors.phone}
label={i18n.t("Phone")} label={intl.formatMessage({
defaultMessage: "Phone"
})}
name="phone" name="phone"
value={data.phone} value={data.phone}
onChange={onChange} onChange={onChange}
@ -104,7 +112,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.streetAddress1} error={!!errors.streetAddress1}
helperText={errors.streetAddress1} helperText={errors.streetAddress1}
label={i18n.t("Address line 1")} label={intl.formatMessage({
defaultMessage: "Address line 1"
})}
name="streetAddress1" name="streetAddress1"
onChange={onChange} onChange={onChange}
value={data.streetAddress1} value={data.streetAddress1}
@ -115,7 +125,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.streetAddress2} error={!!errors.streetAddress2}
helperText={errors.streetAddress2} helperText={errors.streetAddress2}
label={i18n.t("Address line 2")} label={intl.formatMessage({
defaultMessage: "Address line 2"
})}
name="streetAddress2" name="streetAddress2"
onChange={onChange} onChange={onChange}
value={data.streetAddress2} value={data.streetAddress2}
@ -128,7 +140,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.city} error={!!errors.city}
helperText={errors.city} helperText={errors.city}
label={i18n.t("City")} label={intl.formatMessage({
defaultMessage: "City"
})}
name="city" name="city"
onChange={onChange} onChange={onChange}
value={data.city} value={data.city}
@ -140,7 +154,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.postalCode} error={!!errors.postalCode}
helperText={errors.postalCode} helperText={errors.postalCode}
label={i18n.t("ZIP / Postal code")} label={intl.formatMessage({
defaultMessage: "ZIP / Postal code"
})}
name="postalCode" name="postalCode"
onChange={onChange} onChange={onChange}
value={data.postalCode} value={data.postalCode}
@ -157,7 +173,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
displayValue={countryDisplayValue} displayValue={countryDisplayValue}
error={!!errors.country} error={!!errors.country}
helperText={errors.country} helperText={errors.country}
label={i18n.t("Country")} label={intl.formatMessage({
defaultMessage: "Country"
})}
name="country" name="country"
onChange={onCountryChange} onChange={onCountryChange}
value={data.country} value={data.country}
@ -172,7 +190,9 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
disabled={disabled} disabled={disabled}
error={!!errors.countryArea} error={!!errors.countryArea}
helperText={errors.countryArea} helperText={errors.countryArea}
label={i18n.t("Country area")} label={intl.formatMessage({
defaultMessage: "Country area"
})}
name="countryArea" name="countryArea"
onChange={onChange} onChange={onChange}
value={data.countryArea} value={data.countryArea}
@ -181,7 +201,8 @@ const AddressEdit = withStyles(styles, { name: "AddressEdit" })(
</div> </div>
</div> </div>
</> </>
) );
}
); );
AddressEdit.displayName = "AddressEdit"; AddressEdit.displayName = "AddressEdit";
export default AddressEdit; export default AddressEdit;

View file

@ -17,6 +17,7 @@ import {
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import { FormattedMessage, useIntl } from "react-intl";
import { RouteComponentProps, withRouter } from "react-router"; import { RouteComponentProps, withRouter } from "react-router";
import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg"; import saleorDarkLogoSmall from "@assets/images/logo-dark-small.svg";
@ -27,14 +28,13 @@ import useLocalStorage from "@saleor/hooks/useLocalStorage";
import useNavigator from "@saleor/hooks/useNavigator"; import useNavigator from "@saleor/hooks/useNavigator";
import useTheme from "@saleor/hooks/useTheme"; import useTheme from "@saleor/hooks/useTheme";
import useUser from "@saleor/hooks/useUser"; import useUser from "@saleor/hooks/useUser";
import i18n from "@saleor/i18n";
import ArrowDropdown from "@saleor/icons/ArrowDropdown"; import ArrowDropdown from "@saleor/icons/ArrowDropdown";
import Container from "../Container"; import Container from "../Container";
import AppActionContext from "./AppActionContext"; import AppActionContext from "./AppActionContext";
import AppHeaderContext from "./AppHeaderContext"; import AppHeaderContext from "./AppHeaderContext";
import { appLoaderHeight, drawerWidth, drawerWidthExpanded } from "./consts"; import { appLoaderHeight, drawerWidth, drawerWidthExpanded } from "./consts";
import MenuList from "./MenuList"; import MenuList from "./MenuList";
import menuStructure from "./menuStructure"; import createMenuStructure from "./menuStructure";
import ResponsiveDrawer from "./ResponsiveDrawer"; import ResponsiveDrawer from "./ResponsiveDrawer";
import ThemeSwitch from "./ThemeSwitch"; import ThemeSwitch from "./ThemeSwitch";
@ -108,9 +108,7 @@ const styles = (theme: Theme) =>
}, },
isMenuSmallDark: { isMenuSmallDark: {
"&:hover": { "&:hover": {
background: `linear-gradient(0deg, rgba(25, 195, 190, 0.1), rgba(25, 195, 190, 0.1)), ${ background: `linear-gradient(0deg, rgba(25, 195, 190, 0.1), rgba(25, 195, 190, 0.1)), ${theme.palette.background.paper}`
theme.palette.background.paper
}`
}, },
border: `solid 1px #252728`, border: `solid 1px #252728`,
transition: `background ${theme.transitions.duration.shorter}ms` transition: `background ${theme.transitions.duration.shorter}ms`
@ -277,6 +275,9 @@ const AppLayout = withStyles(styles, {
const anchor = React.useRef<HTMLDivElement>(); const anchor = React.useRef<HTMLDivElement>();
const { logout, user } = useUser(); const { logout, user } = useUser();
const navigate = useNavigator(); const navigate = useNavigator();
const intl = useIntl();
const menuStructure = createMenuStructure(intl);
const handleLogout = () => { const handleLogout = () => {
close(); close();
@ -430,9 +431,10 @@ const AppLayout = withStyles(styles, {
className={classes.userMenuItem} className={classes.userMenuItem}
onClick={handleLogout} onClick={handleLogout}
> >
{i18n.t("Log out", { <FormattedMessage
context: "button" defaultMessage="Log out"
})} description="button"
/>
</MenuItem> </MenuItem>
</Menu> </Menu>
</ClickAwayListener> </ClickAwayListener>

View file

@ -8,13 +8,17 @@ import Typography from "@material-ui/core/Typography";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import { FormattedMessage, useIntl } from "react-intl";
import { matchPath } from "react-router"; import { matchPath } from "react-router";
import configureIcon from "@assets/images/menu-configure-icon.svg"; import configureIcon from "@assets/images/menu-configure-icon.svg";
import useTheme from "@saleor/hooks/useTheme"; import useTheme from "@saleor/hooks/useTheme";
import { sectionNames } from "@saleor/intl";
import { User } from "../../auth/types/User"; import { User } from "../../auth/types/User";
import { configurationMenu, configurationMenuUrl } from "../../configuration"; import {
import i18n from "../../i18n"; configurationMenuUrl,
createConfigurationMenu
} from "../../configuration";
import { createHref } from "../../misc"; import { createHref } from "../../misc";
import { orderDraftListUrl, orderListUrl } from "../../orders/urls"; import { orderDraftListUrl, orderListUrl } from "../../orders/urls";
import MenuNested from "./MenuNested"; import MenuNested from "./MenuNested";
@ -167,6 +171,7 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
isActive: false, isActive: false,
label: null label: null
}); });
const intl = useIntl();
const handleSubMenu = itemLabel => { const handleSubMenu = itemLabel => {
setActiveSubMenu({ setActiveSubMenu({
@ -300,7 +305,7 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
); );
})} })}
{renderConfigure && {renderConfigure &&
configurationMenu.filter(menuItem => createConfigurationMenu(intl).filter(menuItem =>
user.permissions user.permissions
.map(perm => perm.code) .map(perm => perm.code)
.includes(menuItem.permission) .includes(menuItem.permission)
@ -323,7 +328,7 @@ const MenuList = withStyles(styles, { name: "MenuList" })(
[classes.menuListItemTextHide]: !isMenuSmall [classes.menuListItemTextHide]: !isMenuSmall
})} })}
> >
{i18n.t("Configuration")} <FormattedMessage {...sectionNames.configuration} />
</Typography> </Typography>
</div> </div>
</a> </a>

View file

@ -2,7 +2,6 @@ import { categoryListUrl } from "../../categories/urls";
import { collectionListUrl } from "../../collections/urls"; import { collectionListUrl } from "../../collections/urls";
import { customerListUrl } from "../../customers/urls"; import { customerListUrl } from "../../customers/urls";
import { saleListUrl, voucherListUrl } from "../../discounts/urls"; import { saleListUrl, voucherListUrl } from "../../discounts/urls";
import i18n from "../../i18n";
import { orderDraftListUrl, orderListUrl } from "../../orders/urls"; import { orderDraftListUrl, orderListUrl } from "../../orders/urls";
import { productListUrl } from "../../products/urls"; import { productListUrl } from "../../products/urls";
import { languageListUrl } from "../../translations/urls"; import { languageListUrl } from "../../translations/urls";
@ -14,6 +13,8 @@ import discountsIcon from "@assets/images/menu-discounts-icon.svg";
import homeIcon from "@assets/images/menu-home-icon.svg"; import homeIcon from "@assets/images/menu-home-icon.svg";
import ordersIcon from "@assets/images/menu-orders-icon.svg"; import ordersIcon from "@assets/images/menu-orders-icon.svg";
import translationIcon from "@assets/images/menu-translation-icon.svg"; import translationIcon from "@assets/images/menu-translation-icon.svg";
import { commonMessages, sectionNames } from "@saleor/intl";
import { IntlShape } from "react-intl";
export interface IMenuItem { export interface IMenuItem {
ariaLabel: string; ariaLabel: string;
@ -24,11 +25,12 @@ export interface IMenuItem {
url?: string; url?: string;
} }
const menuStructure: IMenuItem[] = [ function createMenuStructure(intl: IntlShape): IMenuItem[] {
return [
{ {
ariaLabel: "home", ariaLabel: "home",
icon: homeIcon, icon: homeIcon,
label: i18n.t("Home", { context: "Menu label" }), label: intl.formatMessage(sectionNames.home),
url: "/" url: "/"
}, },
{ {
@ -36,22 +38,22 @@ const menuStructure: IMenuItem[] = [
children: [ children: [
{ {
ariaLabel: "products", ariaLabel: "products",
label: i18n.t("Products", { context: "Menu label" }), label: intl.formatMessage(sectionNames.products),
url: productListUrl() url: productListUrl()
}, },
{ {
ariaLabel: "categories", ariaLabel: "categories",
label: i18n.t("Categories", { context: "Menu label" }), label: intl.formatMessage(sectionNames.categories),
url: categoryListUrl() url: categoryListUrl()
}, },
{ {
ariaLabel: "collections", ariaLabel: "collections",
label: i18n.t("Collections", { context: "Menu label" }), label: intl.formatMessage(sectionNames.collections),
url: collectionListUrl() url: collectionListUrl()
} }
], ],
icon: catalogIcon, icon: catalogIcon,
label: i18n.t("Catalog", { context: "Menu label" }), label: intl.formatMessage(commonMessages.catalog),
permission: PermissionEnum.MANAGE_PRODUCTS permission: PermissionEnum.MANAGE_PRODUCTS
}, },
{ {
@ -59,25 +61,25 @@ const menuStructure: IMenuItem[] = [
children: [ children: [
{ {
ariaLabel: "orders", ariaLabel: "orders",
label: i18n.t("Orders", { context: "Menu label" }), label: intl.formatMessage(sectionNames.orders),
permission: PermissionEnum.MANAGE_ORDERS, permission: PermissionEnum.MANAGE_ORDERS,
url: orderListUrl() url: orderListUrl()
}, },
{ {
ariaLabel: "order drafts", ariaLabel: "order drafts",
label: i18n.t("Drafts", { context: "Menu label" }), label: intl.formatMessage(commonMessages.drafts),
permission: PermissionEnum.MANAGE_ORDERS, permission: PermissionEnum.MANAGE_ORDERS,
url: orderDraftListUrl() url: orderDraftListUrl()
} }
], ],
icon: ordersIcon, icon: ordersIcon,
label: i18n.t("Orders", { context: "Menu label" }), label: intl.formatMessage(sectionNames.orders),
permission: PermissionEnum.MANAGE_ORDERS permission: PermissionEnum.MANAGE_ORDERS
}, },
{ {
ariaLabel: "customers", ariaLabel: "customers",
icon: customerIcon, icon: customerIcon,
label: i18n.t("Customers", { context: "Menu label" }), label: intl.formatMessage(sectionNames.customers),
permission: PermissionEnum.MANAGE_USERS, permission: PermissionEnum.MANAGE_USERS,
url: customerListUrl() url: customerListUrl()
}, },
@ -87,25 +89,27 @@ const menuStructure: IMenuItem[] = [
children: [ children: [
{ {
ariaLabel: "sales", ariaLabel: "sales",
label: i18n.t("Sales", { context: "Menu label" }), label: intl.formatMessage(sectionNames.sales),
url: saleListUrl() url: saleListUrl()
}, },
{ {
ariaLabel: "vouchers", ariaLabel: "vouchers",
label: i18n.t("Vouchers", { context: "Menu label" }), label: intl.formatMessage(sectionNames.vouchers),
url: voucherListUrl() url: voucherListUrl()
} }
], ],
icon: discountsIcon, icon: discountsIcon,
label: i18n.t("Discounts", { context: "Menu label" }), label: intl.formatMessage(commonMessages.discounts),
permission: PermissionEnum.MANAGE_DISCOUNTS permission: PermissionEnum.MANAGE_DISCOUNTS
}, },
{ {
ariaLabel: "translations", ariaLabel: "translations",
icon: translationIcon, icon: translationIcon,
label: i18n.t("Translations", { context: "Menu label" }), label: intl.formatMessage(sectionNames.translations),
permission: PermissionEnum.MANAGE_TRANSLATIONS, permission: PermissionEnum.MANAGE_TRANSLATIONS,
url: languageListUrl url: languageListUrl
} }
]; ];
export default menuStructure; }
export default createMenuStructure;

View file

@ -11,14 +11,15 @@ import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
} from "@saleor/components/ConfirmButton"; } from "@saleor/components/ConfirmButton";
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import useSearchQuery from "@saleor/hooks/useSearchQuery"; import useSearchQuery from "@saleor/hooks/useSearchQuery";
import { buttonMessages } from "@saleor/intl";
import { SearchCategories_categories_edges_node } from "../../containers/SearchCategories/types/SearchCategories"; import { SearchCategories_categories_edges_node } from "../../containers/SearchCategories/types/SearchCategories";
import i18n from "../../i18n";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
export interface FormData { export interface FormData {
@ -85,6 +86,7 @@ const AssignCategoriesDialog = withStyles(styles, {
onFetch, onFetch,
onSubmit onSubmit
}: AssignCategoriesDialogProps) => { }: AssignCategoriesDialogProps) => {
const intl = useIntl();
const [query, onQueryChange] = useSearchQuery(onFetch); const [query, onQueryChange] = useSearchQuery(onFetch);
const [selectedCategories, setSelectedCategories] = React.useState< const [selectedCategories, setSelectedCategories] = React.useState<
SearchCategories_categories_edges_node[] SearchCategories_categories_edges_node[]
@ -100,17 +102,22 @@ const AssignCategoriesDialog = withStyles(styles, {
fullWidth fullWidth
maxWidth="sm" maxWidth="sm"
> >
<DialogTitle>{i18n.t("Assign Categories")}</DialogTitle> <DialogTitle>
<FormattedMessage
defaultMessage="Assign Categories"
description="dialog header"
/>
</DialogTitle>
<DialogContent className={classes.overflow}> <DialogContent className={classes.overflow}>
<TextField <TextField
name="query" name="query"
value={query} value={query}
onChange={onQueryChange} onChange={onQueryChange}
label={i18n.t("Search Categories", { label={intl.formatMessage({
context: "category search input label" defaultMessage: "Search Categories"
})} })}
placeholder={i18n.t("Search by category name, etc...", { placeholder={intl.formatMessage({
context: "category search input placeholder" defaultMessage: "Search by category name, etc..."
})} })}
fullWidth fullWidth
InputProps={{ InputProps={{
@ -156,7 +163,7 @@ const AssignCategoriesDialog = withStyles(styles, {
</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}
@ -165,7 +172,10 @@ const AssignCategoriesDialog = withStyles(styles, {
type="submit" type="submit"
onClick={handleSubmit} onClick={handleSubmit}
> >
{i18n.t("Assign categories", { context: "button" })} <FormattedMessage
defaultMessage="Assign categories"
description="button"
/>
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View file

@ -11,9 +11,10 @@ import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import useSearchQuery from "@saleor/hooks/useSearchQuery"; import useSearchQuery from "@saleor/hooks/useSearchQuery";
import i18n from "@saleor/i18n"; import { buttonMessages } from "@saleor/intl";
import { SearchCollections_collections_edges_node } from "../../containers/SearchCollections/types/SearchCollections"; import { SearchCollections_collections_edges_node } from "../../containers/SearchCollections/types/SearchCollections";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
import ConfirmButton, { import ConfirmButton, {
@ -85,6 +86,7 @@ const AssignCollectionDialog = withStyles(styles, {
onFetch, onFetch,
onSubmit onSubmit
}: AssignCollectionDialogProps) => { }: AssignCollectionDialogProps) => {
const intl = useIntl();
const [query, onQueryChange] = useSearchQuery(onFetch); const [query, onQueryChange] = useSearchQuery(onFetch);
const [selectedCollections, setSelectedCollections] = React.useState< const [selectedCollections, setSelectedCollections] = React.useState<
SearchCollections_collections_edges_node[] SearchCollections_collections_edges_node[]
@ -100,17 +102,22 @@ const AssignCollectionDialog = withStyles(styles, {
fullWidth fullWidth
maxWidth="sm" maxWidth="sm"
> >
<DialogTitle>{i18n.t("Assign Collection")}</DialogTitle> <DialogTitle>
<FormattedMessage
defaultMessage="Assign Collection"
description="dialog header"
/>
</DialogTitle>
<DialogContent className={classes.overflow}> <DialogContent className={classes.overflow}>
<TextField <TextField
name="query" name="query"
value={query} value={query}
onChange={onQueryChange} onChange={onQueryChange}
label={i18n.t("Search Collection", { label={intl.formatMessage({
context: "product search input label" defaultMessage: "Search Collection"
})} })}
placeholder={i18n.t("Search by collection name, etc...", { placeholder={intl.formatMessage({
context: "product search input placeholder" defaultMessage: "Search by collection name, etc..."
})} })}
fullWidth fullWidth
InputProps={{ InputProps={{
@ -157,7 +164,7 @@ const AssignCollectionDialog = withStyles(styles, {
</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}
@ -166,7 +173,10 @@ const AssignCollectionDialog = withStyles(styles, {
type="submit" type="submit"
onClick={handleSubmit} onClick={handleSubmit}
> >
{i18n.t("Assign collections", { context: "button" })} <FormattedMessage
defaultMessage="Assign collections"
description="button"
/>
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View file

@ -11,6 +11,7 @@ import TableCell from "@material-ui/core/TableCell";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import ConfirmButton, { import ConfirmButton, {
ConfirmButtonTransitionState ConfirmButtonTransitionState
@ -18,7 +19,7 @@ import ConfirmButton, {
import FormSpacer from "@saleor/components/FormSpacer"; import FormSpacer from "@saleor/components/FormSpacer";
import TableCellAvatar from "@saleor/components/TableCellAvatar"; import TableCellAvatar from "@saleor/components/TableCellAvatar";
import useSearchQuery from "@saleor/hooks/useSearchQuery"; import useSearchQuery from "@saleor/hooks/useSearchQuery";
import i18n from "@saleor/i18n"; import { buttonMessages } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { SearchProducts_products_edges_node } from "../../containers/SearchProducts/types/SearchProducts"; import { SearchProducts_products_edges_node } from "../../containers/SearchProducts/types/SearchProducts";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
@ -88,6 +89,7 @@ const AssignProductDialog = withStyles(styles, {
onFetch, onFetch,
onSubmit onSubmit
}: AssignProductDialogProps & WithStyles<typeof styles>) => { }: AssignProductDialogProps & WithStyles<typeof styles>) => {
const intl = useIntl();
const [query, onQueryChange] = useSearchQuery(onFetch); const [query, onQueryChange] = useSearchQuery(onFetch);
const [selectedProducts, setSelectedProducts] = React.useState< const [selectedProducts, setSelectedProducts] = React.useState<
SearchProducts_products_edges_node[] SearchProducts_products_edges_node[]
@ -103,21 +105,24 @@ const AssignProductDialog = withStyles(styles, {
fullWidth fullWidth
maxWidth="sm" maxWidth="sm"
> >
<DialogTitle>{i18n.t("Assign Product")}</DialogTitle> <DialogTitle>
<FormattedMessage
defaultMessage="Assign Product"
description="dialog header"
/>
</DialogTitle>
<DialogContent> <DialogContent>
<TextField <TextField
name="query" name="query"
value={query} value={query}
onChange={onQueryChange} onChange={onQueryChange}
label={i18n.t("Search Products", { label={intl.formatMessage({
context: "product search input label" defaultMessage: "Search Products"
})}
placeholder={intl.formatMessage({
defaultMessage:
"Search by product name, attribute, product type etc..."
})} })}
placeholder={i18n.t(
"Search by product name, attribute, product type etc...",
{
context: "product search input placeholder"
}
)}
fullWidth fullWidth
InputProps={{ InputProps={{
autoComplete: "off", autoComplete: "off",
@ -168,7 +173,7 @@ const AssignProductDialog = withStyles(styles, {
</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}
@ -177,7 +182,10 @@ const AssignProductDialog = withStyles(styles, {
type="submit" type="submit"
onClick={handleSubmit} onClick={handleSubmit}
> >
{i18n.t("Assign products", { context: "button" })} <FormattedMessage
defaultMessage="Assign products"
description="button"
/>
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View file

@ -11,8 +11,9 @@ import TextField from "@material-ui/core/TextField";
import ArrowBack from "@material-ui/icons/ArrowBack"; import ArrowBack from "@material-ui/icons/ArrowBack";
import Downshift from "downshift"; import Downshift from "downshift";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import { import {
getMenuItemByPath, getMenuItemByPath,
IMenu, IMenu,
@ -153,7 +154,7 @@ const AutocompleteSelectMenu = withStyles(styles, {
} }
> >
<ArrowBack className={classes.menuBack} /> <ArrowBack className={classes.menuBack} />
{i18n.t("Back")} <FormattedMessage {...buttonMessages.back} />
</MenuItem> </MenuItem>
)} )}
{(menuPath.length {(menuPath.length
@ -176,7 +177,7 @@ const AutocompleteSelectMenu = withStyles(styles, {
</> </>
) : ( ) : (
<MenuItem disabled component="div"> <MenuItem disabled component="div">
{i18n.t("No results")} <FormattedMessage defaultMessage="No results" />
</MenuItem> </MenuItem>
)} )}
</Paper> </Paper>

View file

@ -5,8 +5,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 } from "react-intl";
import i18n from "@saleor/i18n";
interface ColumnPickerButtonProps { interface ColumnPickerButtonProps {
active: boolean; active: boolean;
@ -51,9 +50,10 @@ const ColumnPickerButton: React.FC<ColumnPickerButtonProps> = props => {
onClick={onClick} onClick={onClick}
variant="outlined" variant="outlined"
> >
{i18n.t("Columns", { <FormattedMessage
context: "select visible columns button" defaultMessage="Columns"
})} description="select visible columns button"
/>
<ArrowDropDownIcon <ArrowDropDownIcon
color="primary" color="primary"
className={classNames(classes.icon, { className={classNames(classes.icon, {

View file

@ -6,9 +6,10 @@ import Typography from "@material-ui/core/Typography";
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 } from "react-intl";
import useElementScroll from "@saleor/hooks/useElementScroll"; import useElementScroll from "@saleor/hooks/useElementScroll";
import i18n from "@saleor/i18n"; import { buttonMessages } from "@saleor/intl";
import { isSelected } from "@saleor/utils/lists"; import { isSelected } from "@saleor/utils/lists";
import ControlledCheckbox from "../ControlledCheckbox"; import ControlledCheckbox from "../ControlledCheckbox";
import Hr from "../Hr"; import Hr from "../Hr";
@ -74,14 +75,14 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
<Card> <Card>
<CardContent> <CardContent>
<Typography color="textSecondary"> <Typography color="textSecondary">
{i18n.t( <FormattedMessage
"{{ numberOfSelected }} columns selected out of {{ numberOfTotal }}", defaultMessage="{numberOfSelected} columns selected out of {numberOfTotal}"
{ description="pick columns to display"
context: "pick columns to display", values={{
numberOfSelected: selectedColumns.length, numberOfSelected: selectedColumns.length,
numberOfTotal: columns.length numberOfTotal: columns.length
} }}
)} />
</Typography> </Typography>
</CardContent> </CardContent>
<Hr /> <Hr />
@ -109,14 +110,14 @@ const ColumnPickerContent: React.FC<ColumnPickerContentProps> = props => {
> >
<div className={classes.actionBar}> <div className={classes.actionBar}>
<Button color="default" onClick={onReset}> <Button color="default" onClick={onReset}>
{i18n.t("Reset")} <FormattedMessage defaultMessage="Reset" description="button" />
</Button> </Button>
<div> <div>
<Button color="default" onClick={onCancel}> <Button color="default" onClick={onCancel}>
{i18n.t("Cancel")} <FormattedMessage {...buttonMessages.cancel} />
</Button> </Button>
<Button color="primary" variant="contained" onClick={onSave}> <Button color="primary" variant="contained" onClick={onSave}>
{i18n.t("Save")} <FormattedMessage {...buttonMessages.save} />
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -8,10 +8,10 @@ import {
WithStyles WithStyles
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import CheckIcon from "@material-ui/icons/Check"; import CheckIcon from "@material-ui/icons/Check";
import { buttonMessages } from "@saleor/intl";
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";
export type ConfirmButtonTransitionState = export type ConfirmButtonTransitionState =
| "loading" | "loading"
@ -170,14 +170,11 @@ const ConfirmButton = withStyles(styles)(
displayCompletedActionState displayCompletedActionState
})} })}
> >
{transitionState === "error" && displayCompletedActionState {transitionState === "error" && displayCompletedActionState ? (
? i18n.t("Error", { <FormattedMessage defaultMessage="Error" description="button" />
context: "button" ) : (
}) children || <FormattedMessage {...buttonMessages.confirm} />
: children || )}
i18n.t("Confirm", {
context: "button"
})}
</span> </span>
</Button> </Button>
); );

View file

@ -16,10 +16,10 @@ import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage } 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 { maybe, renderCollection } from "../../misc"; import { maybe, renderCollection } from "../../misc";
import { CountryFragment } from "../../taxes/types/CountryFragment"; import { CountryFragment } from "../../taxes/types/CountryFragment";
@ -99,7 +99,10 @@ const CountryList = withStyles(styles, {
title={title} title={title}
toolbar={ toolbar={
<Button color="primary" onClick={onCountryAssign}> <Button color="primary" onClick={onCountryAssign}>
{i18n.t("Assign countries")} <FormattedMessage
defaultMessage="Assign countries"
description="button"
/>
</Button> </Button>
} }
/> />
@ -110,10 +113,13 @@ const CountryList = withStyles(styles, {
<TableCell <TableCell
className={classNames(classes.wideColumn, classes.toLeft)} className={classNames(classes.wideColumn, classes.toLeft)}
> >
{i18n.t("{{ number }} Countries", { <FormattedMessage
context: "number of countries", defaultMessage="{number} Countries"
description="number of countries"
values={{
number: maybe(() => countries.length.toString(), "...") number: maybe(() => countries.length.toString(), "...")
})} }}
/>
</TableCell> </TableCell>
<TableCell <TableCell
className={classNames(classes.textRight, classes.iconCell)} className={classNames(classes.textRight, classes.iconCell)}

View file

@ -18,7 +18,7 @@ export class DateProvider extends React.Component<{}, DateProviderState> {
componentDidMount() { componentDidMount() {
this.intervalId = window.setInterval( this.intervalId = window.setInterval(
() => this.setState({ date: Date.now() }), () => this.setState({ date: Date.now() }),
10_000 10000
); );
} }

View file

@ -1,7 +1,7 @@
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 i18n from "../../i18n";
import ActionDialog from "../ActionDialog"; import ActionDialog from "../ActionDialog";
import { ConfirmButtonTransitionState } from "../ConfirmButton"; import { ConfirmButtonTransitionState } from "../ConfirmButton";
@ -19,28 +19,31 @@ const DeleteFilterTabDialog: React.FC<DeleteFilterTabDialogProps> = ({
onSubmit, onSubmit,
open, open,
tabName tabName
}) => ( }) => {
const intl = useIntl();
return (
<ActionDialog <ActionDialog
open={open} open={open}
confirmButtonState={confirmButtonState} confirmButtonState={confirmButtonState}
onClose={onClose} onClose={onClose}
onConfirm={onSubmit} onConfirm={onSubmit}
title={i18n.t("Delete Search", { title={intl.formatMessage({
context: "modal title custom search delete" defaultMessage: "Delete Search",
description: "custom search delete, dialog header"
})} })}
variant="delete" variant="delete"
> >
<DialogContentText <DialogContentText>
dangerouslySetInnerHTML={{ <FormattedMessage
__html: i18n.t( defaultMessage="Are you sure you want to delete {name} search tab?"
"Are you sure you want to delete <strong>{{ name }}</strong> search tab?", values={{
{ name: <strong>{tabName}</strong>
name: tabName
}
)
}} }}
/> />
</DialogContentText>
</ActionDialog> </ActionDialog>
); );
};
DeleteFilterTabDialog.displayName = "DeleteFilterTabDialog"; DeleteFilterTabDialog.displayName = "DeleteFilterTabDialog";
export default DeleteFilterTabDialog; export default DeleteFilterTabDialog;

View file

@ -2,9 +2,7 @@ import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent"; import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n";
interface ErrorMessageCardProps { interface ErrorMessageCardProps {
message: string; message: string;
} }
@ -15,7 +13,7 @@ const ErrorMessageCard: React.StatelessComponent<ErrorMessageCardProps> = ({
<Card> <Card>
<CardContent> <CardContent>
<Typography variant="h5" component="h2"> <Typography variant="h5" component="h2">
{i18n.t("Error", { context: "title" })} <FormattedMessage defaultMessage="Error" description="header" />
</Typography> </Typography>
<Typography variant="body2">{message}</Typography> <Typography variant="body2">{message}</Typography>
</CardContent> </CardContent>

View file

@ -8,9 +8,9 @@ import {
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import { FormattedMessage } from "react-intl";
import notFoundImage from "@assets/images/what.svg"; import notFoundImage from "@assets/images/what.svg";
import i18n from "../../i18n";
export interface ErrorPageProps extends WithStyles<typeof styles> { export interface ErrorPageProps extends WithStyles<typeof styles> {
onBack: () => void; onBack: () => void;
@ -68,14 +68,16 @@ const ErrorPage = withStyles(styles, { name: "NotFoundPage" })(
<div className={classes.innerContainer}> <div className={classes.innerContainer}>
<div> <div>
<Typography className={classes.upperHeader} variant="h4"> <Typography className={classes.upperHeader} variant="h4">
{i18n.t("Ooops!...")} <FormattedMessage defaultMessage="Ooops!..." />
</Typography> </Typography>
<Typography className={classes.bottomHeader} variant="h3"> <Typography className={classes.bottomHeader} variant="h3">
{i18n.t("Error")} <FormattedMessage defaultMessage="Error" />
</Typography> </Typography>
<Typography>{i18n.t("We've encountered a problem...")}</Typography>
<Typography> <Typography>
{i18n.t("Don't worry, everything is gonna be fine")} <FormattedMessage defaultMessage="We've encountered a problem..." />
</Typography>
<Typography>
<FormattedMessage defaultMessage="Don't worry, everything is gonna be fine" />
</Typography> </Typography>
</div> </div>
<div> <div>
@ -85,7 +87,10 @@ const ErrorPage = withStyles(styles, { name: "NotFoundPage" })(
variant="contained" variant="contained"
onClick={onBack} onClick={onBack}
> >
{i18n.t("Back to home", { context: "button" })} <FormattedMessage
defaultMessage="Back to home"
description="button"
/>
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -2,7 +2,7 @@ import Button from "@material-ui/core/Button";
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles"; import { createStyles, withStyles, WithStyles } 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 i18n from "../../i18n"; import { FormattedMessage } from "react-intl";
const styles = createStyles({ const styles = createStyles({
fileUploadField: { fileUploadField: {
@ -42,7 +42,10 @@ const FileUpload = withStyles(styles, { name: "FileUpload" })(
value={value} value={value}
/> />
<Button disabled={disabled} onClick={() => this.upload.click()}> <Button disabled={disabled} onClick={() => this.upload.click()}>
{i18n.t("Upload")} <FormattedMessage
defaultMessage="Upload"
description="upload file, button"
/>
</Button> </Button>
</div> </div>
) )

View file

@ -13,9 +13,9 @@ import Typography from "@material-ui/core/Typography";
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown"; import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import { FilterContent } from "."; import { FilterContent } from ".";
import i18n from "../../i18n";
import { FilterContentSubmitData } from "./FilterContent"; import { FilterContentSubmitData } from "./FilterContent";
import { IFilter } from "./types"; import { IFilter } from "./types";
@ -103,7 +103,10 @@ const Filter = withStyles(styles, { name: "Filter" })(
onClick={() => setFilterMenuOpened(!isFilterMenuOpened)} onClick={() => setFilterMenuOpened(!isFilterMenuOpened)}
> >
<Typography className={classes.addFilterText}> <Typography className={classes.addFilterText}>
{i18n.t("Add Filter")} <FormattedMessage
defaultMessage="Add Filter"
description="button"
/>
</Typography> </Typography>
<ArrowDropDownIcon <ArrowDropDownIcon
color="primary" color="primary"

View file

@ -1,9 +1,9 @@
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
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 { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
import i18n from "../../i18n";
import { getMenuItemByValue, isLeaf, walkToRoot } from "../../utils/menu"; import { getMenuItemByValue, isLeaf, walkToRoot } from "../../utils/menu";
import FormSpacer from "../FormSpacer"; import FormSpacer from "../FormSpacer";
import SingleSelectField from "../SingleSelectField"; import SingleSelectField from "../SingleSelectField";
@ -45,6 +45,7 @@ const FilterContent: React.FC<FilterContentProps> = ({
filters, filters,
onSubmit onSubmit
}) => { }) => {
const intl = useIntl();
const [menuValue, setMenuValue] = React.useState<string>(""); const [menuValue, setMenuValue] = React.useState<string>("");
const [filterValue, setFilterValue] = React.useState<string | string[]>(""); const [filterValue, setFilterValue] = React.useState<string | string[]>("");
const classes = useStyles({}); const classes = useStyles({});
@ -72,7 +73,9 @@ const FilterContent: React.FC<FilterContentProps> = ({
} }
}} }}
value={menus ? menus[0].value : menuValue} value={menus ? menus[0].value : menuValue}
placeholder={i18n.t("Select Filter...")} placeholder={intl.formatMessage({
defaultMessage: "Select Filter..."
})}
/> />
{menus && {menus &&
menus.map( menus.map(
@ -95,7 +98,9 @@ const FilterContent: React.FC<FilterContentProps> = ({
? menuValue ? menuValue
: menus[filterItemIndex - 1].label.toString() : menus[filterItemIndex - 1].label.toString()
} }
placeholder={i18n.t("Select Filter...")} placeholder={intl.formatMessage({
defaultMessage: "Select Filter..."
})}
/> />
</React.Fragment> </React.Fragment>
) )
@ -124,7 +129,10 @@ const FilterContent: React.FC<FilterContentProps> = ({
}) })
} }
> >
{i18n.t("Add filter")} <FormattedMessage
defaultMessage="Add filter"
description="button"
/>
</Button> </Button>
</> </>
)} )}

View file

@ -2,8 +2,8 @@ import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/styles"; import { makeStyles } from "@material-ui/styles";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import i18n from "../../i18n";
import Calendar from "../../icons/Calendar"; import Calendar from "../../icons/Calendar";
import FormSpacer from "../FormSpacer"; import FormSpacer from "../FormSpacer";
import PriceField from "../PriceField"; import PriceField from "../PriceField";
@ -41,6 +41,7 @@ const FilterElement: React.FC<FilterElementProps> = ({
onChange, onChange,
value value
}) => { }) => {
const intl = useIntl();
const classes = useStyles({}); const classes = useStyles({});
if (filter.data.type === FieldType.date) { if (filter.data.type === FieldType.date) {
@ -62,7 +63,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
} else if (filter.data.type === FieldType.rangeDate) { } else if (filter.data.type === FieldType.rangeDate) {
return ( return (
<> <>
<Typography>{i18n.t("from")}</Typography> <Typography>
<FormattedMessage defaultMessage="from" />
</Typography>
<TextField <TextField
className={className} className={className}
fullWidth fullWidth
@ -77,7 +80,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
}} }}
/> />
<FormSpacer /> <FormSpacer />
<Typography>{i18n.t("to")}</Typography> <Typography>
<FormattedMessage defaultMessage="to" />
</Typography>
<TextField <TextField
className={className} className={className}
fullWidth fullWidth
@ -96,7 +101,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
} else if (filter.data.type === FieldType.range) { } else if (filter.data.type === FieldType.range) {
return ( return (
<> <>
<Typography>{i18n.t("from")}</Typography> <Typography>
<FormattedMessage defaultMessage="from" />
</Typography>
<TextField <TextField
className={className} className={className}
fullWidth fullWidth
@ -110,7 +117,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
}} }}
/> />
<FormSpacer /> <FormSpacer />
<Typography>{i18n.t("to")}</Typography> <Typography>
<FormattedMessage defaultMessage="to" />
</Typography>
<TextField <TextField
className={className} className={className}
fullWidth fullWidth
@ -128,7 +137,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
} else if (filter.data.type === FieldType.rangePrice) { } else if (filter.data.type === FieldType.rangePrice) {
return ( return (
<> <>
<Typography>{i18n.t("from")}</Typography> <Typography>
<FormattedMessage defaultMessage="from" />
</Typography>
<PriceField <PriceField
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
className={className} className={className}
@ -141,7 +152,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
}} }}
/> />
<FormSpacer /> <FormSpacer />
<Typography>{i18n.t("to")}</Typography> <Typography>
<FormattedMessage defaultMessage="to" />
</Typography>
<PriceField <PriceField
currencySymbol={currencySymbol} currencySymbol={currencySymbol}
className={className} className={className}
@ -169,7 +182,9 @@ const FilterElement: React.FC<FilterElementProps> = ({
} }
}} }}
value={value as string} value={value as string}
placeholder={i18n.t("Select Filter...")} placeholder={intl.formatMessage({
defaultMessage: "Select Filter..."
})}
onChange={event => onChange(event.target.value)} onChange={event => onChange(event.target.value)}
/> />
); );

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import i18n from "../../i18n";
import { FilterProps } from "../../types"; import { FilterProps } from "../../types";
import Debounce from "../Debounce"; import Debounce from "../Debounce";
import { IFilter } from "../Filter/types"; import { IFilter } from "../Filter/types";
@ -28,6 +28,7 @@ const FilterBar: React.FC<FilterBarProps> = ({
onTabChange, onTabChange,
onFilterDelete onFilterDelete
}) => { }) => {
const intl = useIntl();
const [search, setSearch] = React.useState(initialSearch); const [search, setSearch] = React.useState(initialSearch);
React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]); React.useEffect(() => setSearch(initialSearch), [currentTab, initialSearch]);
@ -47,7 +48,9 @@ const FilterBar: React.FC<FilterBarProps> = ({
{isCustom && ( {isCustom && (
<FilterTab <FilterTab
onClick={() => undefined} onClick={() => undefined}
label={i18n.t("Custom Filter")} label={intl.formatMessage({
defaultMessage: "Custom Filter"
})}
/> />
)} )}
</FilterTabs> </FilterTabs>

View file

@ -4,8 +4,7 @@ import CardHeader from "@material-ui/core/CardHeader";
import IconButton from "@material-ui/core/IconButton"; import IconButton from "@material-ui/core/IconButton";
import RefreshIcon from "@material-ui/icons/Refresh"; import RefreshIcon from "@material-ui/icons/Refresh";
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import i18n from "../../i18n";
export interface FilterCardProps { export interface FilterCardProps {
handleClear(); handleClear();
@ -14,7 +13,10 @@ export interface FilterCardProps {
const FilterCard: React.StatelessComponent<FilterCardProps> = ({ const FilterCard: React.StatelessComponent<FilterCardProps> = ({
children, children,
handleClear handleClear
}) => ( }) => {
const intl = useIntl();
return (
<Card> <Card>
<form> <form>
<CardHeader <CardHeader
@ -23,11 +25,14 @@ const FilterCard: React.StatelessComponent<FilterCardProps> = ({
<RefreshIcon /> <RefreshIcon />
</IconButton> </IconButton>
} }
title={i18n.t("Filters")} title={intl.formatMessage({
defaultMessage: "Filters"
})}
/> />
<CardContent>{children}</CardContent> <CardContent>{children}</CardContent>
</form> </form>
</Card> </Card>
); );
};
FilterCard.displayName = "FilterCard"; FilterCard.displayName = "FilterCard";
export default FilterCard; export default FilterCard;

View file

@ -8,8 +8,7 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
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 ImageIcon from "../../icons/Image"; import ImageIcon from "../../icons/Image";
import Dropzone from "../Dropzone"; import Dropzone from "../Dropzone";
@ -95,9 +94,10 @@ export const ImageUpload = withStyles(styles, { name: "ImageUpload" })(
<input {...getInputProps()} className={classes.fileField} /> <input {...getInputProps()} className={classes.fileField} />
<ImageIcon className={classes.photosIcon} /> <ImageIcon className={classes.photosIcon} />
<Typography className={classes.uploadText} variant="body1"> <Typography className={classes.uploadText} variant="body1">
{i18n.t("Drop here to upload", { <FormattedMessage
context: "image upload" defaultMessage="Drop here to upload"
})} description="image upload"
/>
</Typography> </Typography>
</div> </div>
</div> </div>

View file

@ -15,8 +15,8 @@ import Typography from "@material-ui/core/Typography";
import ArrowDropDown from "@material-ui/icons/ArrowDropDown"; import ArrowDropDown from "@material-ui/icons/ArrowDropDown";
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 { LanguageCodeEnum } from "../../types/globalTypes"; import { LanguageCodeEnum } from "../../types/globalTypes";
import { ShopInfo_shop_languages } from "../Shop/types/ShopInfo"; import { ShopInfo_shop_languages } from "../Shop/types/ShopInfo";
@ -110,11 +110,14 @@ const LanguageSwitch = withStyles(styles, { name: "LanguageSwitch" })(
onLanguageChange(lang.code); onLanguageChange(lang.code);
}} }}
> >
{i18n.t("{{ languageName }} - {{ languageCode }}", { <FormattedMessage
context: "button", defaultMessage="{languageName} - {languageCode}"
description="button"
values={{
languageCode: lang.code, languageCode: lang.code,
languageName: lang.language languageName: lang.language
})} }}
/>
</MenuItem> </MenuItem>
</Menu> </Menu>
))} ))}

View file

@ -8,7 +8,8 @@ import {
} from "@material-ui/core/styles"; } from "@material-ui/core/styles";
import TextField, { StandardTextFieldProps } from "@material-ui/core/TextField"; import TextField, { StandardTextFieldProps } from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import i18n from "../../i18n"; import { FormattedMessage } from "react-intl";
import Chip from "../Chip"; import Chip from "../Chip";
interface ListFieldState { interface ListFieldState {
@ -105,7 +106,7 @@ const ListField = withStyles(styles)(
color="primary" color="primary"
onClick={this.handleValueAdd} onClick={this.handleValueAdd}
> >
{i18n.t("Add", { context: "button" })} <FormattedMessage defaultMessage="Add" description="button" />
</Button> </Button>
) )
}} }}

View file

@ -1,11 +1,44 @@
import React from "react"; import React from "react";
import { IntlProvider } from "react-intl";
export const LocaleContext = React.createContext<string>("en"); export type LocaleContextType = string;
export const LocaleContext = React.createContext<LocaleContextType>("en");
const { Consumer: LocaleConsumer, Provider } = LocaleContext; const { Consumer: LocaleConsumer, Provider: RawLocaleProvider } = LocaleContext;
const LocaleProvider = ({ children }) => { enum Locale {
return <Provider value={navigator.language}>{children}</Provider>; EN = "en",
EN_GB = "en-gb",
EN_US = "en-us"
}
type LocaleMessages = Record<string, string>;
const localeData: Record<Locale, LocaleMessages> = {
[Locale.EN]: {},
[Locale.EN_GB]: {},
[Locale.EN_US]: {}
}; };
export { LocaleConsumer, LocaleProvider }; function getMatchingLocale(): Locale {
const localeEntries = Object.entries(Locale);
for (const preferredLocale of navigator.languages) {
for (const localeEntry of localeEntries) {
if (localeEntry[1].toLowerCase() === preferredLocale.toLowerCase()) {
return Locale[localeEntry[0]];
}
}
}
}
const LocaleProvider: React.FC = ({ children }) => {
const [locale] = React.useState(getMatchingLocale());
return (
<IntlProvider locale={locale} messages={localeData[locale]} key={locale}>
<RawLocaleProvider value={locale}>{children}</RawLocaleProvider>
</IntlProvider>
);
};
export { LocaleConsumer, LocaleProvider, RawLocaleProvider };

View file

@ -1,6 +1,5 @@
import React from "react"; import React from "react";
import { useIntl } from "react-intl";
import i18n from "../../i18n";
import { LocaleConsumer } from "../Locale"; import { LocaleConsumer } from "../Locale";
import IMoney from "../Money"; import IMoney from "../Money";
@ -18,29 +17,48 @@ const formatMoney = (money: IMoney, locale: string) =>
export const MoneyRange: React.StatelessComponent<MoneyRangeProps> = ({ export const MoneyRange: React.StatelessComponent<MoneyRangeProps> = ({
from, from,
to to
}) => ( }) => {
const intl = useIntl();
return (
<LocaleConsumer> <LocaleConsumer>
{locale => {locale =>
from && to from && to
? i18n.t("{{ fromMoney }} - {{ toMoney }}", { ? intl.formatMessage(
context: "money", {
defaultMessage: "{fromMoney} - {toMoney}",
description: "money"
},
{
fromMoney: formatMoney(from, locale), fromMoney: formatMoney(from, locale),
toMoney: formatMoney(to, locale) toMoney: formatMoney(to, locale)
}) }
)
: from && !to : from && !to
? i18n.t("from {{ money }}", { ? intl.formatMessage(
context: "money", {
defaultMessage: "from {money}",
description: "money"
},
{
money: formatMoney(from, locale) money: formatMoney(from, locale)
}) }
)
: !from && to : !from && to
? i18n.t("to {{ money }}", { ? intl.formatMessage(
context: "money", {
defaultMessage: "to {money}",
description: "money"
},
{
money: formatMoney(to, locale) money: formatMoney(to, locale)
}) }
)
: "-" : "-"
} }
</LocaleConsumer> </LocaleConsumer>
); );
};
MoneyRange.displayName = "MoneyRange"; MoneyRange.displayName = "MoneyRange";
export default MoneyRange; export default MoneyRange;

View file

@ -13,12 +13,12 @@ import Typography from "@material-ui/core/Typography";
import CloseIcon from "@material-ui/icons/Close"; import CloseIcon from "@material-ui/icons/Close";
import Downshift, { ControllerStateAndHelpers } from "downshift"; import Downshift, { ControllerStateAndHelpers } from "downshift";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import { compareTwoStrings } from "string-similarity"; import { compareTwoStrings } from "string-similarity";
import { fade } from "@material-ui/core/styles/colorManipulator"; import { fade } from "@material-ui/core/styles/colorManipulator";
import Checkbox from "@saleor/components/Checkbox"; import Checkbox from "@saleor/components/Checkbox";
import Debounce, { DebounceProps } from "@saleor/components/Debounce"; import Debounce, { DebounceProps } from "@saleor/components/Debounce";
import i18n from "@saleor/i18n";
import ArrowDropdownIcon from "@saleor/icons/ArrowDropdown"; import ArrowDropdownIcon from "@saleor/icons/ArrowDropdown";
import Hr from "../Hr"; import Hr from "../Hr";
@ -244,10 +244,13 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
data-tc="multiautocomplete-select-option" data-tc="multiautocomplete-select-option"
> >
<span className={classes.menuItemLabel}> <span className={classes.menuItemLabel}>
{i18n.t("Add new value: {{ value }}", { <FormattedMessage
context: "add custom option", defaultMessage="Add new value: {value}"
description="add custom option to select input"
values={{
value: inputValue value: inputValue
})} }}
/>
</span> </span>
</MenuItem> </MenuItem>
)} )}
@ -259,7 +262,7 @@ export const MultiAutocompleteSelectFieldComponent = withStyles(styles, {
component="div" component="div"
data-tc="multiautocomplete-select-no-options" data-tc="multiautocomplete-select-no-options"
> >
{i18n.t("No results found")} <FormattedMessage defaultMessage="No results found" />
</MenuItem> </MenuItem>
) )
)} )}

View file

@ -11,8 +11,8 @@ 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 i18n from "../../i18n";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -110,7 +110,9 @@ export const MultiSelectField = withStyles(styles, {
); );
}) })
) : ( ) : (
<MenuItem disabled={true}>{i18n.t("No results found")}</MenuItem> <MenuItem disabled={true}>
<FormattedMessage defaultMessage="No results found" />
</MenuItem>
)} )}
</Select> </Select>
{hint && <FormHelperText>{hint}</FormHelperText>} {hint && <FormHelperText>{hint}</FormHelperText>}

View file

@ -8,9 +8,9 @@ import {
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import React from "react"; import React from "react";
import SVG from "react-inlinesvg"; import SVG from "react-inlinesvg";
import { FormattedMessage } from "react-intl";
import notFoundImage from "@assets/images/not-found-404.svg"; import notFoundImage from "@assets/images/not-found-404.svg";
import i18n from "@saleor/i18n";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -65,12 +65,14 @@ const NotFoundPage = withStyles(styles, { name: "NotFoundPage" })(
<div className={classes.innerContainer}> <div className={classes.innerContainer}>
<div> <div>
<Typography className={classes.header} variant="h3"> <Typography className={classes.header} variant="h3">
{i18n.t("Ooops!...")} <FormattedMessage defaultMessage="Ooops!..." />
</Typography> </Typography>
<Typography className={classes.header} variant="h4"> <Typography className={classes.header} variant="h4">
{i18n.t("Something's missing")} <FormattedMessage defaultMessage="Something's missing" />
</Typography>
<Typography>
<FormattedMessage defaultMessage="Sorry, the page was not found" />
</Typography> </Typography>
<Typography>{i18n.t("Sorry, the page was not found")}</Typography>
</div> </div>
<div> <div>
<Button <Button
@ -79,7 +81,10 @@ const NotFoundPage = withStyles(styles, { name: "NotFoundPage" })(
variant="contained" variant="contained"
onClick={onBack} onClick={onBack}
> >
{i18n.t("Go back to dashboard", { context: "button" })} <FormattedMessage
defaultMessage="Go back to dashboard"
description="button"
/>
</Button> </Button>
</div> </div>
</div> </div>

View file

@ -10,12 +10,12 @@ import TableCell from "@material-ui/core/TableCell";
import TableFooter from "@material-ui/core/TableFooter"; import TableFooter from "@material-ui/core/TableFooter";
import TableRow from "@material-ui/core/TableRow"; import TableRow from "@material-ui/core/TableRow";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import TableCellAvatar, { import TableCellAvatar, {
AVATAR_MARGIN AVATAR_MARGIN
} from "@saleor/components/TableCellAvatar"; } from "@saleor/components/TableCellAvatar";
import { ProductListColumns } from "@saleor/config"; import { ProductListColumns } from "@saleor/config";
import i18n from "@saleor/i18n";
import { maybe, renderCollection } from "@saleor/misc"; import { maybe, renderCollection } from "@saleor/misc";
import { ListActions, ListProps } from "@saleor/types"; import { ListActions, ListProps } from "@saleor/types";
import { isSelected } from "@saleor/utils/lists"; import { isSelected } from "@saleor/utils/lists";
@ -97,6 +97,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
onUpdateListSettings, onUpdateListSettings,
onRowClick onRowClick
}: ProductListProps) => { }: ProductListProps) => {
const intl = useIntl();
const displayColumn = React.useCallback( const displayColumn = React.useCallback(
(column: ProductListColumns) => (column: ProductListColumns) =>
isSelected(column, settings.columns, (a, b) => a === b), isSelected(column, settings.columns, (a, b) => a === b),
@ -124,22 +125,28 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
> >
<TableCell className={classes.colName}> <TableCell className={classes.colName}>
<span className={classes.colNameHeader}> <span className={classes.colNameHeader}>
{i18n.t("Name", { context: "object" })} <FormattedMessage defaultMessage="Name" description="product" />
</span> </span>
</TableCell> </TableCell>
{displayColumn("productType") && ( {displayColumn("productType") && (
<TableCell className={classes.colType}> <TableCell className={classes.colType}>
{i18n.t("Type", { context: "object" })} <FormattedMessage defaultMessage="Type" description="product" />
</TableCell> </TableCell>
)} )}
{displayColumn("isPublished") && ( {displayColumn("isPublished") && (
<TableCell className={classes.colPublished}> <TableCell className={classes.colPublished}>
{i18n.t("Published", { context: "object" })} <FormattedMessage
defaultMessage="Published"
description="product status"
/>
</TableCell> </TableCell>
)} )}
{displayColumn("price") && ( {displayColumn("price") && (
<TableCell className={classes.colPrice}> <TableCell className={classes.colPrice}>
{i18n.t("Price", { context: "object" })} <FormattedMessage
defaultMessage="Price"
description="product"
/>
</TableCell> </TableCell>
)} )}
</TableHead> </TableHead>
@ -205,11 +212,13 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
<StatusLabel <StatusLabel
label={ label={
product.isAvailable product.isAvailable
? i18n.t("Published", { ? intl.formatMessage({
context: "product status" defaultMessage: "Published",
description: "product status"
}) })
: i18n.t("Not published", { : intl.formatMessage({
context: "product status" defaultMessage: "Not published",
description: "product status"
}) })
} }
status={product.isAvailable ? "success" : "error"} status={product.isAvailable ? "success" : "error"}
@ -237,7 +246,7 @@ export const ProductList = withStyles(styles, { name: "ProductList" })(
() => ( () => (
<TableRow> <TableRow>
<TableCell colSpan={numberOfColumns}> <TableCell colSpan={numberOfColumns}>
{i18n.t("No products found")} <FormattedMessage defaultMessage="No products found" />
</TableCell> </TableCell>
</TableRow> </TableRow>
) )

View file

@ -8,8 +8,7 @@ import RadioGroup from "@material-ui/core/RadioGroup";
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles"; import { createStyles, withStyles, WithStyles } 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";
const styles = createStyles({ const styles = createStyles({
formControl: { formControl: {
@ -83,7 +82,9 @@ export const RadioGroupField = withStyles(styles, {
/> />
)) ))
) : ( ) : (
<MenuItem disabled={true}>{i18n.t("No results found")}</MenuItem> <MenuItem disabled={true}>
<FormattedMessage defaultMessage="No results found" />
</MenuItem>
)} )}
</RadioGroup> </RadioGroup>
{hint && <FormHelperText>{hint}</FormHelperText>} {hint && <FormHelperText>{hint}</FormHelperText>}

View file

@ -13,8 +13,7 @@ import {
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import { ContentState } from "draft-js"; import { ContentState } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n";
interface ImageEntityProps { interface ImageEntityProps {
children: React.ReactNode; children: React.ReactNode;
@ -88,7 +87,10 @@ const ImageEntity = withStyles(styles, {
}} }}
color="primary" color="primary"
> >
{i18n.t("Replace")} <FormattedMessage
defaultMessage="Replace"
description="replace image, button"
/>
</Button> </Button>
<IconButton onClick={() => onRemove(entityKey)}> <IconButton onClick={() => onRemove(entityKey)}>
<DeleteIcon color="primary" /> <DeleteIcon color="primary" />

View file

@ -6,8 +6,9 @@ import DialogTitle from "@material-ui/core/DialogTitle";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import { AtomicBlockUtils, EditorState, EntityInstance } from "draft-js"; import { AtomicBlockUtils, EditorState, EntityInstance } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import Form from "../Form"; import Form from "../Form";
interface ImageSourceProps { interface ImageSourceProps {
@ -21,16 +22,19 @@ interface ImageSourceProps {
onClose: () => void; onClose: () => void;
} }
class ImageSource extends React.Component<ImageSourceProps> { const ImageSource: React.FC<ImageSourceProps> = ({
submit = (href: string) => {
const {
editorState, editorState,
entity, entity,
entityKey, entityKey,
entityType, entityType,
onComplete onComplete,
} = this.props; onClose
}) => {
const intl = useIntl();
const initial = entity ? entity.getData().href : "";
const handleSubmit = (href: string) => {
if (href) { if (href) {
const content = editorState.getCurrentContent(); const content = editorState.getCurrentContent();
if (entity) { if (entity) {
@ -60,34 +64,37 @@ class ImageSource extends React.Component<ImageSourceProps> {
} }
}; };
render() {
const { entity, onClose } = this.props;
const initial = entity ? entity.getData().href : "";
return ( return (
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
<Form <Form
initial={{ href: initial }} initial={{ href: initial }}
onSubmit={({ href }) => this.submit(href)} onSubmit={({ href }) => handleSubmit(href)}
> >
{({ data, change, submit }) => ( {({ data, change, submit }) => (
<> <>
<DialogTitle>{i18n.t("Add Image Link")}</DialogTitle> <DialogTitle>
<FormattedMessage
defaultMessage="Add Image Link"
description="dialog header"
/>
</DialogTitle>
<DialogContent> <DialogContent>
<TextField <TextField
name="href" name="href"
fullWidth fullWidth
label={i18n.t("Image URL")} label={intl.formatMessage({
defaultMessage: "Image URL"
})}
value={data.href} value={data.href}
onChange={change} onChange={change}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>
{i18n.t("Cancel", { context: "button" })} <FormattedMessage {...buttonMessages.cancel} />
</Button> </Button>
<Button onClick={submit} color="primary" variant="contained"> <Button onClick={submit} color="primary" variant="contained">
{i18n.t("Save", { context: "button" })} <FormattedMessage {...buttonMessages.save} />
</Button> </Button>
</DialogActions> </DialogActions>
</> </>
@ -95,6 +102,6 @@ class ImageSource extends React.Component<ImageSourceProps> {
</Form> </Form>
</Dialog> </Dialog>
); );
} };
}
export default ImageSource; export default ImageSource;

View file

@ -14,8 +14,9 @@ import Typography from "@material-ui/core/Typography";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import { ContentState } from "draft-js"; import { ContentState } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import Link from "../Link"; import Link from "../Link";
interface LinkEntityProps { interface LinkEntityProps {
@ -107,7 +108,7 @@ const LinkEntity = withStyles(styles, {
}} }}
color="primary" color="primary"
> >
{i18n.t("Edit")} <FormattedMessage {...buttonMessages.edit} />
</Button> </Button>
<IconButton onClick={() => onRemove(entityKey)}> <IconButton onClick={() => onRemove(entityKey)}>
<DeleteIcon color="primary" /> <DeleteIcon color="primary" />

View file

@ -6,8 +6,9 @@ import DialogTitle from "@material-ui/core/DialogTitle";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import { EditorState, EntityInstance, RichUtils } from "draft-js"; import { EditorState, EntityInstance, RichUtils } from "draft-js";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import Form from "../Form"; import Form from "../Form";
interface LinkSourceProps { interface LinkSourceProps {
@ -20,10 +21,17 @@ interface LinkSourceProps {
onClose: () => void; onClose: () => void;
} }
class LinkSource extends React.Component<LinkSourceProps> { const LinkSource: React.FC<LinkSourceProps> = ({
submit = (url: string) => { editorState,
const { editorState, entityType, onComplete } = this.props; entity,
entityType,
onComplete,
onClose
}) => {
const intl = useIntl();
const initial = entity ? entity.getData().url : "";
const handleSubmit = (url: string) => {
if (url) { if (url) {
const content = editorState.getCurrentContent(); const content = editorState.getCurrentContent();
const contentWithEntity = content.createEntity( const contentWithEntity = content.createEntity(
@ -47,34 +55,37 @@ class LinkSource extends React.Component<LinkSourceProps> {
} }
}; };
render() {
const { entity, onClose } = this.props;
const initial = entity ? entity.getData().url : "";
return ( return (
<Dialog onClose={onClose} open={true} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={true} fullWidth maxWidth="sm">
<Form <Form
initial={{ url: initial }} initial={{ url: initial }}
onSubmit={({ url }) => this.submit(url)} onSubmit={({ url }) => handleSubmit(url)}
> >
{({ data, change, submit }) => ( {({ data, change, submit }) => (
<> <>
<DialogTitle>{i18n.t("Add or Edit Link")}</DialogTitle> <DialogTitle>
<FormattedMessage
defaultMessage="Add or Edit Link"
description="button"
/>
</DialogTitle>
<DialogContent> <DialogContent>
<TextField <TextField
name="url" name="url"
fullWidth fullWidth
label={i18n.t("URL Linked")} label={intl.formatMessage({
defaultMessage: "URL Linked"
})}
value={data.url} value={data.url}
onChange={change} onChange={change}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={onClose}> <Button onClick={onClose}>
{i18n.t("Cancel", { context: "button" })} <FormattedMessage {...buttonMessages.cancel} />
</Button> </Button>
<Button onClick={submit} color="secondary" variant="contained"> <Button onClick={submit} color="secondary" variant="contained">
{i18n.t("Save", { context: "button" })} <FormattedMessage {...buttonMessages.save} />
</Button> </Button>
</DialogActions> </DialogActions>
</> </>
@ -82,6 +93,6 @@ class LinkSource extends React.Component<LinkSourceProps> {
</Form> </Form>
</Dialog> </Dialog>
); );
} };
}
export default LinkSource; export default LinkSource;

View file

@ -3,8 +3,8 @@ import Select from "@material-ui/core/Select";
import { Theme } from "@material-ui/core/styles"; import { Theme } from "@material-ui/core/styles";
import { createStyles, makeStyles, useTheme } from "@material-ui/styles"; import { createStyles, makeStyles, useTheme } from "@material-ui/styles";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n";
import { ListSettings } from "../../types"; import { ListSettings } from "../../types";
const useStyles = makeStyles( const useStyles = makeStyles(
@ -51,7 +51,9 @@ const RowNumberSelect: React.FC<RowNumberSelectProps> = ({
const classes = useStyles({ theme }); const classes = useStyles({ theme });
return ( return (
<div className={className}> <div className={className}>
<span className={classes.label}>{i18n.t("No of Rows:")}</span> <span className={classes.label}>
<FormattedMessage defaultMessage="No of Rows:" />
</span>
<Select <Select
className={classes.select} className={classes.select}
value={settings.rowNumber} value={settings.rowNumber}

View file

@ -8,9 +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 useWindowScroll from "@saleor/hooks/useWindowScroll"; import useWindowScroll from "@saleor/hooks/useWindowScroll";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import { maybe } from "../../misc"; import { maybe } from "../../misc";
import AppActionContext from "../AppLayout/AppActionContext"; import AppActionContext from "../AppLayout/AppActionContext";
import ConfirmButton, { import ConfirmButton, {
@ -82,6 +83,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
onSave, onSave,
...props ...props
}: SaveButtonBarProps) => { }: SaveButtonBarProps) => {
const intl = useIntl();
const scrollPosition = useWindowScroll(); const scrollPosition = useWindowScroll();
const scrolledToBottom = const scrolledToBottom =
scrollPosition.y + window.innerHeight >= document.body.scrollHeight; scrollPosition.y + window.innerHeight >= document.body.scrollHeight;
@ -107,7 +109,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
> >
{labels && labels.delete {labels && labels.delete
? labels.delete ? labels.delete
: i18n.t("Remove")} : intl.formatMessage(buttonMessages.delete)}
</Button> </Button>
)} )}
<div className={classes.spacer} /> <div className={classes.spacer} />
@ -119,9 +121,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
> >
{maybe( {maybe(
() => labels.cancel, () => labels.cancel,
i18n.t("Cancel", { intl.formatMessage(buttonMessages.cancel)
context: "button"
})
)} )}
</Button> </Button>
<ConfirmButton <ConfirmButton
@ -132,9 +132,7 @@ export const SaveButtonBar = withStyles(styles, { name: "SaveButtonBar" })(
> >
{maybe( {maybe(
() => labels.save, () => labels.save,
i18n.t("Save", { intl.formatMessage(buttonMessages.save)
context: "button"
})
)} )}
</ConfirmButton> </ConfirmButton>
</Container> </Container>

View file

@ -5,8 +5,9 @@ import DialogContent from "@material-ui/core/DialogContent";
import DialogTitle from "@material-ui/core/DialogTitle"; import DialogTitle from "@material-ui/core/DialogTitle";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import i18n from "../../i18n"; import { buttonMessages } from "@saleor/intl";
import ConfirmButton, { ConfirmButtonTransitionState } from "../ConfirmButton"; import ConfirmButton, { ConfirmButtonTransitionState } from "../ConfirmButton";
import Form from "../Form"; import Form from "../Form";
@ -31,6 +32,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
onSubmit, onSubmit,
open open
}) => { }) => {
const intl = useIntl();
const [errors, setErrors] = React.useState(false); const [errors, setErrors] = React.useState(false);
const handleErrors = data => { const handleErrors = data => {
if (data.name.length) { if (data.name.length) {
@ -44,9 +46,10 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
return ( return (
<Dialog onClose={onClose} open={open} fullWidth maxWidth="sm"> <Dialog onClose={onClose} open={open} fullWidth maxWidth="sm">
<DialogTitle> <DialogTitle>
{i18n.t("Save Custom Search", { <FormattedMessage
context: "save filter tab" defaultMessage="Save Custom Search"
})} description="save filter tab, header"
/>
</DialogTitle> </DialogTitle>
<Form initial={initialForm} onSubmit={handleErrors}> <Form initial={initialForm} onSubmit={handleErrors}>
{({ change, data, submit }) => ( {({ change, data, submit }) => (
@ -54,8 +57,9 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
<DialogContent> <DialogContent>
<TextField <TextField
fullWidth fullWidth
label={i18n.t("Search Name", { label={intl.formatMessage({
context: "save search" defaultMessage: "Search Name",
description: "save search tab"
})} })}
name={"name" as keyof SaveFilterTabDialogFormData} name={"name" as keyof SaveFilterTabDialogFormData}
value={data.name} value={data.name}
@ -66,7 +70,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
</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}
@ -74,7 +78,7 @@ const SaveFilterTabDialog: React.FC<SaveFilterTabDialogProps> = ({
variant="contained" variant="contained"
onClick={submit} onClick={submit}
> >
{i18n.t("Save")} <FormattedMessage {...buttonMessages.save} />
</ConfirmButton> </ConfirmButton>
</DialogActions> </DialogActions>
</> </>

View file

@ -11,8 +11,8 @@ import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import i18n from "../../i18n";
import CardTitle from "../CardTitle"; import CardTitle from "../CardTitle";
import FormSpacer from "../FormSpacer"; import FormSpacer from "../FormSpacer";
@ -88,16 +88,22 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
titlePlaceholder, titlePlaceholder,
onChange onChange
}: SeoFormProps) => { }: SeoFormProps) => {
const intl = useIntl();
const [expanded, setExpansionStatus] = React.useState(false); const [expanded, setExpansionStatus] = React.useState(false);
const toggleExpansion = () => setExpansionStatus(!expanded); const toggleExpansion = () => setExpansionStatus(!expanded);
return ( return (
<Card> <Card>
<CardTitle <CardTitle
title={i18n.t("Search Engine Preview")} title={intl.formatMessage({
defaultMessage: "Search Engine Preview"
})}
toolbar={ toolbar={
<Button color="primary" variant="text" onClick={toggleExpansion}> <Button color="primary" variant="text" onClick={toggleExpansion}>
{i18n.t("Edit website SEO")} <FormattedMessage
defaultMessage="Edit website SEO"
description="button"
/>
</Button> </Button>
} }
/> />
@ -116,19 +122,24 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
label={ label={
<div className={classes.labelContainer}> <div className={classes.labelContainer}>
<div className={classes.label}> <div className={classes.label}>
{i18n.t("Search engine title")} <FormattedMessage defaultMessage="Search engine title" />
</div> </div>
<span> <span>
{i18n.t("{{ letters }} of {{ maxLetters }} characters", { <FormattedMessage
letters: title.length, defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
maxLetters: 70 description="character limit"
})} values={{
maxCharacters: 70,
numberOfCharacters: title.length
}}
/>
</span> </span>
</div> </div>
} }
helperText={i18n.t( helperText={intl.formatMessage({
defaultMessage:
"If empty, the preview shows what will be autogenerated." "If empty, the preview shows what will be autogenerated."
)} })}
value={title.slice(0, 69)} value={title.slice(0, 69)}
disabled={loading || disabled} disabled={loading || disabled}
placeholder={titlePlaceholder} placeholder={titlePlaceholder}
@ -141,19 +152,24 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
label={ label={
<div className={classes.labelContainer}> <div className={classes.labelContainer}>
<div className={classes.label}> <div className={classes.label}>
{i18n.t("Search engine description")} <FormattedMessage defaultMessage="Search engine description" />
</div> </div>
<span> <span>
{i18n.t("{{ letters }} of {{ maxLetters }} characters", { <FormattedMessage
letters: description.length, defaultMessage="{numberOfCharacters} of {maxCharacters} characters"
maxLetters: 300 description="character limit"
})} values={{
maxCharacters: 300,
numberOfCharacters: description.length
}}
/>
</span> </span>
</div> </div>
} }
helperText={i18n.t( helperText={intl.formatMessage({
defaultMessage:
"If empty, the preview shows what will be autogenerated." "If empty, the preview shows what will be autogenerated."
)} })}
value={description ? description.slice(0, 299) : undefined} value={description ? description.slice(0, 299) : undefined}
onChange={onChange} onChange={onChange}
disabled={loading || disabled} disabled={loading || disabled}
@ -170,9 +186,4 @@ const SeoForm = withStyles(styles, { name: "SeoForm" })(
} }
); );
SeoForm.displayName = "SeoForm"; SeoForm.displayName = "SeoForm";
SeoForm.defaultProps = {
helperText: i18n.t(
"Add search engine title and description to make this product easier to find"
)
};
export default SeoForm; export default SeoForm;

View file

@ -13,10 +13,10 @@ import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import Downshift from "downshift"; import Downshift from "downshift";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import { compareTwoStrings } from "string-similarity"; import { compareTwoStrings } from "string-similarity";
import useStateFromProps from "@saleor/hooks/useStateFromProps"; import useStateFromProps from "@saleor/hooks/useStateFromProps";
import i18n from "../../i18n";
import ArrowDropdownIcon from "../../icons/ArrowDropdown"; import ArrowDropdownIcon from "../../icons/ArrowDropdown";
import Debounce, { DebounceProps } from "../Debounce"; import Debounce, { DebounceProps } from "../Debounce";
@ -177,7 +177,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
data-tc="singleautocomplete-select-option" data-tc="singleautocomplete-select-option"
> >
<Typography color="textSecondary"> <Typography color="textSecondary">
{i18n.t("None")} <FormattedMessage defaultMessage="None" />
</Typography> </Typography>
</MenuItem> </MenuItem>
)} )}
@ -220,10 +220,13 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
})} })}
data-tc="singleautocomplete-select-option" data-tc="singleautocomplete-select-option"
> >
{i18n.t("Add new value: {{ value }}", { <FormattedMessage
context: "add custom option", defaultMessage="Add new value: {value}"
description="add custom select input option"
values={{
value: inputValue value: inputValue
})} }}
/>
</MenuItem> </MenuItem>
)} )}
</> </>
@ -233,7 +236,7 @@ const SingleAutocompleteSelectFieldComponent = withStyles(styles, {
component="div" component="div"
data-tc="singleautocomplete-select-no-options" data-tc="singleautocomplete-select-no-options"
> >
{i18n.t("No results found")} <FormattedMessage defaultMessage="No results found" />
</MenuItem> </MenuItem>
)} )}
</Paper> </Paper>

View file

@ -7,8 +7,7 @@ import Select, { SelectProps } from "@material-ui/core/Select";
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles"; import { createStyles, withStyles, WithStyles } 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";
const styles = createStyles({ const styles = createStyles({
formControl: { formControl: {
@ -83,7 +82,9 @@ export const SingleSelectField = withStyles(styles, {
</MenuItem> </MenuItem>
)) ))
) : ( ) : (
<MenuItem disabled={true}>{i18n.t("No results found")}</MenuItem> <MenuItem disabled={true}>
<FormattedMessage defaultMessage="No results found" />
</MenuItem>
)} )}
</Select> </Select>
{hint && <FormHelperText>{hint}</FormHelperText>} {hint && <FormHelperText>{hint}</FormHelperText>}

View file

@ -6,8 +6,8 @@ import Typography from "@material-ui/core/Typography";
import ClearIcon from "@material-ui/icons/Clear"; import ClearIcon from "@material-ui/icons/Clear";
import { createStyles, makeStyles, useTheme } from "@material-ui/styles"; import { createStyles, makeStyles, useTheme } from "@material-ui/styles";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n";
import Filter, { FilterContentSubmitData, IFilter } from "../Filter"; import Filter, { FilterContentSubmitData, IFilter } from "../Filter";
import Hr from "../Hr"; import Hr from "../Hr";
import Link from "../Link"; import Link from "../Link";
@ -163,9 +163,19 @@ export const FilterChips: React.FC<FilterChipProps> = ({
))} ))}
</div> </div>
{isCustomSearch ? ( {isCustomSearch ? (
<Link onClick={onFilterSave}>{i18n.t("Save Custom Search")}</Link> <Link onClick={onFilterSave}>
<FormattedMessage
defaultMessage="Save Custom Search"
description="button"
/>
</Link>
) : ( ) : (
<Link onClick={onFilterDelete}>{i18n.t("Delete Search")}</Link> <Link onClick={onFilterDelete}>
<FormattedMessage
defaultMessage="Delete Search"
description="button"
/>
</Link>
)} )}
</div> </div>
) : ( ) : (

View file

@ -1,5 +1,4 @@
export { default } from "./FilterTabs"; export { default } from "./FilterTabs";
export { Filter } from "./FilterChips";
export * from "./FilterTabs"; export * from "./FilterTabs";
export * from "./FilterTab"; export * from "./FilterTab";
export * from "./FilterChips"; export * from "./FilterChips";

View file

@ -13,10 +13,10 @@ import TableRow from "@material-ui/core/TableRow";
import Typography from "@material-ui/core/Typography"; import Typography from "@material-ui/core/Typography";
import classNames from "classnames"; import classNames from "classnames";
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import { Node } from "../../types"; import { Node } from "../../types";
import i18n from "../../i18n";
import Checkbox from "../Checkbox"; import Checkbox from "../Checkbox";
export interface TableHeadProps extends MuiTableHeadProps { export interface TableHeadProps extends MuiTableHeadProps {
@ -129,9 +129,12 @@ const TableHead = withStyles(styles, {
<div className={classes.container}> <div className={classes.container}>
{selected && ( {selected && (
<Typography> <Typography>
{i18n.t("Selected {{ number }} items", { <FormattedMessage
defaultMessage="Selected {number} items"
values={{
number: selected number: selected
})} }}
/>
</Typography> </Typography>
)} )}
<div className={classes.spacer} /> <div className={classes.spacer} />

View file

@ -11,8 +11,7 @@ import {
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import PersonIcon from "@material-ui/icons/Person"; import PersonIcon from "@material-ui/icons/Person";
import React from "react"; import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import i18n from "../../i18n";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
createStyles({ createStyles({
@ -70,7 +69,10 @@ export const Timeline = withStyles(styles, { name: "Timeline" })(
); );
export const TimelineAddNote = withStyles(styles, { name: "TimelineAddNote" })( export const TimelineAddNote = withStyles(styles, { name: "TimelineAddNote" })(
({ classes, message, onChange, onSubmit }: TimelineAddNoteProps) => ( ({ classes, message, onChange, onSubmit }: TimelineAddNoteProps) => {
const intl = useIntl();
return (
<div className={classes.noteRoot}> <div className={classes.noteRoot}>
<CardContent className={classes.noteTitle}> <CardContent className={classes.noteTitle}>
<Avatar <Avatar
@ -81,7 +83,9 @@ export const TimelineAddNote = withStyles(styles, { name: "TimelineAddNote" })(
</Avatar> </Avatar>
<TextField <TextField
className={classes.input} className={classes.input}
placeholder={i18n.t("Leave your note here...")} placeholder={intl.formatMessage({
defaultMessage: "Leave your note here..."
})}
onChange={onChange} onChange={onChange}
value={message} value={message}
name="message" name="message"
@ -90,9 +94,10 @@ export const TimelineAddNote = withStyles(styles, { name: "TimelineAddNote" })(
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
<Button color="primary" onClick={onSubmit}> <Button color="primary" onClick={onSubmit}>
{i18n.t("Send", { <FormattedMessage
context: "add order note" defaultMessage="Send"
})} description="add order note, button"
/>
</Button> </Button>
) )
}} }}
@ -100,7 +105,8 @@ export const TimelineAddNote = withStyles(styles, { name: "TimelineAddNote" })(
/> />
</CardContent> </CardContent>
</div> </div>
) );
}
); );
Timeline.displayName = "Timeline"; Timeline.displayName = "Timeline";
export default Timeline; export default Timeline;

View file

@ -8,12 +8,12 @@ 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 ControlledSwitch from "@saleor/components/ControlledSwitch"; import ControlledSwitch from "@saleor/components/ControlledSwitch";
import { FormSpacer } from "@saleor/components/FormSpacer"; import { FormSpacer } from "@saleor/components/FormSpacer";
import useDateLocalize from "@saleor/hooks/useDateLocalize"; import useDateLocalize from "@saleor/hooks/useDateLocalize";
import i18n from "../../i18n";
import { DateContext } from "../Date/DateContext"; import { DateContext } from "../Date/DateContext";
const styles = (theme: Theme) => const styles = (theme: Theme) =>
@ -54,11 +54,17 @@ export const VisibilityCard = withStyles(styles, {
disabled, disabled,
onChange onChange
}: VisibilityCardProps) => { }: VisibilityCardProps) => {
const intl = useIntl();
const localizeDate = useDateLocalize(); const localizeDate = useDateLocalize();
const dateNow = React.useContext(DateContext); const dateNow = React.useContext(DateContext);
return ( return (
<Card> <Card>
<CardTitle title={i18n.t("Visibility")} /> <CardTitle
title={intl.formatMessage({
defaultMessage: "Visibility",
description: "section header"
})}
/>
<CardContent> <CardContent>
<div <div
className={ className={
@ -69,18 +75,32 @@ export const VisibilityCard = withStyles(styles, {
> >
<ControlledSwitch <ControlledSwitch
name="isPublished" name="isPublished"
label={i18n.t("Visible")} label={intl.formatMessage({
uncheckedLabel={i18n.t("Hidden")} defaultMessage: "Visible"
})}
uncheckedLabel={intl.formatMessage({
defaultMessage: "Hidden"
})}
secondLabel={ secondLabel={
publicationDate publicationDate
? isPublished ? isPublished
? i18n.t("since {{ date }}", { ? intl.formatMessage(
{
defaultMessage: "since {date}"
},
{
date: localizeDate(publicationDate) date: localizeDate(publicationDate)
}) }
)
: Date.parse(publicationDate) > dateNow : Date.parse(publicationDate) > dateNow
? i18n.t("will be visible from {{ date }}", { ? intl.formatMessage(
{
defaultMessage: "will be visible from {date}"
},
{
date: localizeDate(publicationDate) date: localizeDate(publicationDate)
}) }
)
: null : null
: null : null
} }
@ -94,7 +114,10 @@ export const VisibilityCard = withStyles(styles, {
<TextField <TextField
error={!!errors.publicationDate} error={!!errors.publicationDate}
disabled={disabled} disabled={disabled}
label={i18n.t("Publish on")} label={intl.formatMessage({
defaultMessage: "Publish on",
description: "publish on date"
})}
name="publicationDate" name="publicationDate"
type="date" type="date"
fullWidth={true} fullWidth={true}

View file

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import i18n from "../../i18n"; import { FormattedMessage } from "react-intl";
export interface Weight { export interface Weight {
unit: string; unit: string;
@ -9,10 +9,13 @@ export interface WeightProps {
weight: Weight; weight: Weight;
} }
const Weight: React.StatelessComponent<WeightProps> = ({ weight }) => const Weight: React.StatelessComponent<WeightProps> = ({ weight }) => (
i18n.t("{{ value }} {{ unit }}", { <FormattedMessage
context: "weight", defaultMessage="{value} {unit}"
...weight description="weight"
}); values={weight}
/>
);
Weight.displayName = "Weight"; Weight.displayName = "Weight";
export default Weight; export default Weight;

View file

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { FormattedMessage } from "react-intl";
import i18n from "../../i18n";
import { Weight } from "../Weight"; import { Weight } from "../Weight";
export interface WeightRangeProps { export interface WeightRangeProps {
@ -8,28 +8,32 @@ export interface WeightRangeProps {
to?: Weight; to?: Weight;
} }
const WeightRange: React.StatelessComponent<WeightRangeProps> = ({ const WeightRange: React.FC<WeightRangeProps> = ({ from, to }) =>
from, from && to ? (
to <FormattedMessage
}) => defaultMessage="{fromValue} {fromUnit} - {toValue} {toUnit}"
from && to description="weight"
? i18n.t("{{ fromValue }} {{ fromUnit }} - {{ toValue }} {{ toUnit }}", { values={{
context: "weight",
fromUnit: from.unit, fromUnit: from.unit,
fromValue: from.value, fromValue: from.value,
toUnit: to.unit, toUnit: to.unit,
toValue: to.value toValue: to.value
}) }}
: from && !to />
? i18n.t("from {{ value }} {{ unit }}", { ) : from && !to ? (
context: "weight", <FormattedMessage
...from defaultMessage="from {value} {unit}"
}) description="weight"
: !from && to values={from}
? i18n.t("to {{ value }} {{ unit }}", { />
context: "weight", ) : !from && to ? (
...to <FormattedMessage
}) defaultMessage="to {value} {unit}"
: "-"; description="weight"
values={to}
/>
) : (
<span>-</span>
);
WeightRange.displayName = "WeightRange"; WeightRange.displayName = "WeightRange";
export default WeightRange; export default WeightRange;

View file

@ -8,12 +8,13 @@ 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 { useIntl } from "react-intl";
import { IconProps } from "@material-ui/core/Icon"; import { IconProps } from "@material-ui/core/Icon";
import { sectionNames } from "@saleor/intl";
import { User } from "../auth/types/User"; import { User } from "../auth/types/User";
import Container from "../components/Container"; import Container from "../components/Container";
import PageHeader from "../components/PageHeader"; import PageHeader from "../components/PageHeader";
import i18n from "../i18n";
import { PermissionEnum } from "../types/globalTypes"; import { PermissionEnum } from "../types/globalTypes";
export interface MenuItem { export interface MenuItem {
@ -68,7 +69,7 @@ const styles = (theme: Theme) =>
} }
}); });
export interface ConfigurationPageProps extends WithStyles<typeof styles> { export interface ConfigurationPageProps {
menu: MenuItem[]; menu: MenuItem[];
user: User; user: User;
onSectionClick: (sectionName: string) => void; onSectionClick: (sectionName: string) => void;
@ -76,13 +77,24 @@ export interface ConfigurationPageProps extends WithStyles<typeof styles> {
export const ConfigurationPage = withStyles(styles, { export const ConfigurationPage = withStyles(styles, {
name: "ConfigurationPage" name: "ConfigurationPage"
})(({ classes, menu, user, onSectionClick }: ConfigurationPageProps) => ( })(
({
classes,
menu,
user,
onSectionClick
}: ConfigurationPageProps & WithStyles<typeof styles>) => {
const intl = useIntl();
return (
<Container> <Container>
<PageHeader title={i18n.t("Configuration")} /> <PageHeader title={intl.formatMessage(sectionNames.configuration)} />
<div className={classes.root}> <div className={classes.root}>
{menu {menu
.filter(menuItem => .filter(menuItem =>
user.permissions.map(perm => perm.code).includes(menuItem.permission) user.permissions
.map(perm => perm.code)
.includes(menuItem.permission)
) )
.map((menuItem, menuItemIndex) => ( .map((menuItem, menuItemIndex) => (
<Card <Card
@ -93,7 +105,10 @@ export const ConfigurationPage = withStyles(styles, {
<CardContent className={classes.cardContent}> <CardContent className={classes.cardContent}>
<div className={classes.icon}>{menuItem.icon}</div> <div className={classes.icon}>{menuItem.icon}</div>
<div> <div>
<Typography className={classes.sectionTitle} color="primary"> <Typography
className={classes.sectionTitle}
color="primary"
>
{menuItem.title} {menuItem.title}
</Typography> </Typography>
<Typography className={classes.sectionDescription}> <Typography className={classes.sectionDescription}>
@ -105,6 +120,8 @@ export const ConfigurationPage = withStyles(styles, {
))} ))}
</div> </div>
</Container> </Container>
)); );
}
);
ConfigurationPage.displayName = "ConfigurationPage"; ConfigurationPage.displayName = "ConfigurationPage";
export default ConfigurationPage; export default ConfigurationPage;

View file

@ -1,10 +1,10 @@
import React from "react"; import React from "react";
import { IntlShape, useIntl } from "react-intl";
import { attributeListUrl } from "@saleor/attributes/urls"; import { attributeListUrl } from "@saleor/attributes/urls";
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 useUser from "@saleor/hooks/useUser"; import useUser from "@saleor/hooks/useUser";
import i18n from "@saleor/i18n";
import Navigation from "@saleor/icons/Navigation"; import Navigation from "@saleor/icons/Navigation";
import Pages from "@saleor/icons/Pages"; import Pages from "@saleor/icons/Pages";
import ProductTypes from "@saleor/icons/ProductTypes"; import ProductTypes from "@saleor/icons/ProductTypes";
@ -12,6 +12,7 @@ import ShippingMethods from "@saleor/icons/ShippingMethods";
import SiteSettings from "@saleor/icons/SiteSettings"; import SiteSettings from "@saleor/icons/SiteSettings";
import StaffMembers from "@saleor/icons/StaffMembers"; import StaffMembers from "@saleor/icons/StaffMembers";
import Taxes from "@saleor/icons/Taxes"; import Taxes from "@saleor/icons/Taxes";
import { sectionNames } from "@saleor/intl";
import { maybe } from "@saleor/misc"; import { maybe } from "@saleor/misc";
import { menuListUrl } from "@saleor/navigation/urls"; import { menuListUrl } from "@saleor/navigation/urls";
import { pageListUrl } from "@saleor/pages/urls"; import { pageListUrl } from "@saleor/pages/urls";
@ -23,76 +24,103 @@ import { taxSection } from "@saleor/taxes/urls";
import { PermissionEnum } from "@saleor/types/globalTypes"; import { PermissionEnum } from "@saleor/types/globalTypes";
import ConfigurationPage, { MenuItem } from "./ConfigurationPage"; import ConfigurationPage, { MenuItem } from "./ConfigurationPage";
export const configurationMenu: MenuItem[] = [ export function createConfigurationMenu(intl: IntlShape): MenuItem[] {
return [
{ {
description: i18n.t("Determine attributes used to create product types"), description: intl.formatMessage({
defaultMessage: "Determine attributes used to create product types",
id: "configurationMenuAttributes"
}),
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />, icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_PRODUCTS, permission: PermissionEnum.MANAGE_PRODUCTS,
title: i18n.t("Attributes"), title: intl.formatMessage(sectionNames.attributes),
url: attributeListUrl() url: attributeListUrl()
}, },
{ {
description: i18n.t("Define types of products you sell"), description: intl.formatMessage({
defaultMessage: "Define types of products you sell",
id: "configurationMenuProductTypes"
}),
icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />, icon: <ProductTypes fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_PRODUCTS, permission: PermissionEnum.MANAGE_PRODUCTS,
title: i18n.t("Product Types"), title: intl.formatMessage(sectionNames.productTypes),
url: productTypeListUrl() url: productTypeListUrl()
}, },
{ {
description: i18n.t("Manage your employees and their permissions"), description: intl.formatMessage({
defaultMessage: "Manage your employees and their permissions",
id: "configurationMenuStaff"
}),
icon: <StaffMembers fontSize="inherit" viewBox="0 0 44 44" />, icon: <StaffMembers fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_STAFF, permission: PermissionEnum.MANAGE_STAFF,
title: i18n.t("Staff Members"), title: intl.formatMessage(sectionNames.staff),
url: staffListUrl() url: staffListUrl()
}, },
{ {
description: i18n.t("Manage how you ship out orders."), description: intl.formatMessage({
defaultMessage: "Manage how you ship out orders",
id: "configurationMenuShipping"
}),
icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />, icon: <ShippingMethods fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_SHIPPING, permission: PermissionEnum.MANAGE_SHIPPING,
title: i18n.t("Shipping Methods"), title: intl.formatMessage(sectionNames.shipping),
url: shippingZonesListUrl() url: shippingZonesListUrl()
}, },
{ {
description: i18n.t("Manage how your store charges tax"), description: intl.formatMessage({
defaultMessage: "Manage how your store charges tax",
id: "configurationMenuTaxes"
}),
icon: <Taxes fontSize="inherit" viewBox="0 0 44 44" />, icon: <Taxes fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_PRODUCTS, permission: PermissionEnum.MANAGE_PRODUCTS,
title: i18n.t("Taxes"), title: intl.formatMessage(sectionNames.taxes),
url: taxSection url: taxSection
}, },
{ {
description: i18n.t("Define how users can navigate through your store"), description: intl.formatMessage({
defaultMessage: "Define how users can navigate through your store",
id: "configurationMenuNavigation"
}),
icon: <Navigation fontSize="inherit" viewBox="0 0 44 44" />, icon: <Navigation fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_MENUS, permission: PermissionEnum.MANAGE_MENUS,
title: i18n.t("Navigation"), title: intl.formatMessage(sectionNames.navigation),
url: menuListUrl() url: menuListUrl()
}, },
{ {
description: i18n.t("View and update your site settings"), description: intl.formatMessage({
defaultMessage: "View and update your site settings",
id: "configurationMenuSiteSettings"
}),
icon: <SiteSettings fontSize="inherit" viewBox="0 0 44 44" />, icon: <SiteSettings fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_SETTINGS, permission: PermissionEnum.MANAGE_SETTINGS,
title: i18n.t("Site Settings"), title: intl.formatMessage(sectionNames.siteSettings),
url: siteSettingsUrl() url: siteSettingsUrl()
}, },
{ {
description: i18n.t("Manage and add additional pages"), description: intl.formatMessage({
defaultMessage: "Manage and add additional pages",
id: "configurationMenuPages"
}),
icon: <Pages fontSize="inherit" viewBox="0 0 44 44" />, icon: <Pages fontSize="inherit" viewBox="0 0 44 44" />,
permission: PermissionEnum.MANAGE_PAGES, permission: PermissionEnum.MANAGE_PAGES,
title: i18n.t("Pages"), title: intl.formatMessage(sectionNames.pages),
url: pageListUrl() url: pageListUrl()
} }
]; ];
}
export const configurationMenuUrl = "/configuration/"; export const configurationMenuUrl = "/configuration/";
export const ConfigurationSection: React.StatelessComponent = () => { export const ConfigurationSection: React.FC = () => {
const navigate = useNavigator(); const navigate = useNavigator();
const user = useUser(); const user = useUser();
const intl = useIntl();
return ( return (
<> <>
<WindowTitle title={i18n.t("Configuration")} /> <WindowTitle title={intl.formatMessage(sectionNames.configuration)} />
<ConfigurationPage <ConfigurationPage
menu={configurationMenu} menu={createConfigurationMenu(intl)}
user={maybe(() => user.user)} user={maybe(() => user.user)}
onSectionClick={navigate} onSectionClick={navigate}
/> />

View file

@ -5,12 +5,13 @@ import CardContent from "@material-ui/core/CardContent";
import { createStyles, withStyles, WithStyles } from "@material-ui/core/styles"; import { createStyles, withStyles, WithStyles } 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 AddressFormatter from "@saleor/components/AddressFormatter"; import AddressFormatter from "@saleor/components/AddressFormatter";
import CardMenu from "@saleor/components/CardMenu"; import CardMenu from "@saleor/components/CardMenu";
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 { buttonMessages } from "@saleor/intl";
import { AddressTypeEnum } from "../../../types/globalTypes"; import { AddressTypeEnum } from "../../../types/globalTypes";
import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses"; import { CustomerAddresses_user_addresses } from "../../types/CustomerAddresses";
@ -51,22 +52,35 @@ const CustomerAddress = withStyles(styles, { name: "CustomerAddress" })(
onEdit, onEdit,
onRemove, onRemove,
onSetAsDefault onSetAsDefault
}: CustomerAddressProps & WithStyles<typeof styles>) => ( }: CustomerAddressProps & WithStyles<typeof styles>) => {
const intl = useIntl();
return (
<Card className={classes.card}> <Card className={classes.card}>
<CardTitle <CardTitle
title={ title={
address ? ( address ? (
<> <>
{i18n.t("Address {{ addressNumber }}", { <FormattedMessage
defaultMessage="Address {addressNumber}"
description="addres card header"
values={{
addressNumber addressNumber
})} }}
/>
<Typography variant="caption"> <Typography variant="caption">
{isDefaultBillingAddress && isDefaultShippingAddress {isDefaultBillingAddress && isDefaultShippingAddress
? i18n.t("Default Address") ? intl.formatMessage({
defaultMessage: "Default Address"
})
: isDefaultShippingAddress : isDefaultShippingAddress
? i18n.t("Default Shipping Address") ? intl.formatMessage({
defaultMessage: "Default Shipping Address"
})
: isDefaultBillingAddress : isDefaultBillingAddress
? i18n.t("Default Billing Address") ? intl.formatMessage({
defaultMessage: "Default Billing Address"
})
: null} : null}
</Typography> </Typography>
</> </>
@ -80,14 +94,16 @@ const CustomerAddress = withStyles(styles, { name: "CustomerAddress" })(
disabled={disabled} disabled={disabled}
menuItems={[ menuItems={[
{ {
label: i18n.t("Set as default shipping address", { label: intl.formatMessage({
context: "button" defaultMessage: "Set as default shipping address",
description: "button"
}), }),
onSelect: () => onSetAsDefault(AddressTypeEnum.SHIPPING) onSelect: () => onSetAsDefault(AddressTypeEnum.SHIPPING)
}, },
{ {
label: i18n.t("Set as default billing address", { label: intl.formatMessage({
context: "button" defaultMessage: "Set as default billing address",
description: "button"
}), }),
onSelect: () => onSetAsDefault(AddressTypeEnum.BILLING) onSelect: () => onSetAsDefault(AddressTypeEnum.BILLING)
} }
@ -101,15 +117,16 @@ const CustomerAddress = withStyles(styles, { name: "CustomerAddress" })(
<div className={classes.actionsContainer}> <div className={classes.actionsContainer}>
<CardActions className={classes.actions}> <CardActions className={classes.actions}>
<Button color="primary" disabled={disabled} onClick={onEdit}> <Button color="primary" disabled={disabled} onClick={onEdit}>
{i18n.t("Edit")} <FormattedMessage {...buttonMessages.edit} />
</Button> </Button>
<Button color="primary" disabled={disabled} onClick={onRemove}> <Button color="primary" disabled={disabled} onClick={onRemove}>
{i18n.t("Delete")} <FormattedMessage {...buttonMessages.remove} />
</Button> </Button>
</CardActions> </CardActions>
</div> </div>
</Card> </Card>
) );
}
); );
CustomerAddress.displayName = "CustomerAddress"; CustomerAddress.displayName = "CustomerAddress";
export default CustomerAddress; export default CustomerAddress;

Some files were not shown because too many files have changed in this diff Show more