From 96c6358d4e5567dd7b764cf3c1eaea5a1457a007 Mon Sep 17 00:00:00 2001 From: Reckless_Satoshi <90936742+Reckless-Satoshi@users.noreply.github.com> Date: Tue, 5 Apr 2022 14:25:53 +0000 Subject: [PATCH] Internationalization (#85) * Implement react i18n-next * Add XHR i18n backend, include currencies_dict in APP * Implement i18n 2/9. Add site description * Implement i18n 3/9 TradeBox. Fix explicit pricing when amount range is enabled. * Implement i18n 4/9 MakerPage. * Implement i18n 5/9 OrderPage * Implement i18n 6/9 Chat * Implement i18n 9/9 Book, Bottom Bar, Profile, Misc, Info * Add Contributing translation guidelines --- frontend/package-lock.json | 203 +++++++--- frontend/package.json | 8 +- frontend/src/components/App.js | 30 +- frontend/src/components/BookPage.js | 115 +++--- frontend/src/components/BottomBar.js | 194 +++++----- frontend/src/components/Chat.js | 18 +- frontend/src/components/InfoDialog.js | 137 +++---- frontend/src/components/MakerPage.js | 150 ++++---- frontend/src/components/OrderPage.js | 142 +++---- frontend/src/components/PaymentIcons.js | 1 - frontend/src/components/PaymentText.js | 7 +- frontend/src/components/TradeBox.js | 263 +++++++------ frontend/src/components/UnsafeAlert.js | 33 +- frontend/src/components/UserGenPage.js | 42 +-- .../src/components/autocompletePayments.js | 6 +- frontend/src/components/i18n.js | 45 +++ frontend/src/locales/CONTRIBUTING.MD | 35 ++ frontend/src/locales/de.json | 354 ++++++++++++++++++ frontend/src/locales/en.json | 354 ++++++++++++++++++ frontend/src/locales/es.json | 354 ++++++++++++++++++ frontend/src/locales/ru.json | 354 ++++++++++++++++++ frontend/src/locales/zh.json | 354 ++++++++++++++++++ frontend/static/frontend/main.js | 30 +- frontend/templates/frontend/index.html | 2 + 24 files changed, 2592 insertions(+), 639 deletions(-) create mode 100644 frontend/src/components/i18n.js create mode 100644 frontend/src/locales/CONTRIBUTING.MD create mode 100644 frontend/src/locales/de.json create mode 100644 frontend/src/locales/en.json create mode 100644 frontend/src/locales/es.json create mode 100644 frontend/src/locales/ru.json create mode 100644 frontend/src/locales/zh.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 26724235..f07e6d28 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2657,9 +2657,9 @@ } }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" }, "ansi-styles": { "version": "3.2.1", @@ -2966,9 +2966,9 @@ } }, "bplist-parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.0.tgz", - "integrity": "sha512-zgmaRvT6AN1JpPPV+S0a1/FAtoxSreYDccZGIqEMSvZl9DMe70mJ7MFzpxa1X+gHVdkToE2haRUHHMiW1OdejA==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", "requires": { "big-integer": "1.6.x" } @@ -3398,6 +3398,14 @@ "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.4.25.tgz", "integrity": "sha512-1sF/6cit7MYfmxrqNiVN0ijLGv10xtV8egAUwUgIW6Q/Y6d7SuuVw5TOBnG7qIFqrNjxaPNoIAZmx7yOEuZvDA==" }, + "cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "requires": { + "node-fetch": "2.6.7" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3415,14 +3423,15 @@ "integrity": "sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA=" }, "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "requires": { "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" } }, "css-tree": { @@ -3444,9 +3453,9 @@ } }, "css-what": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", - "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" }, "csstype": { "version": "2.6.19", @@ -3580,33 +3589,36 @@ } }, "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", + "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", "requires": { "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" - } } }, "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" + }, + "domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "requires": { + "domelementtype": "^2.2.0" + } }, "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" } }, "ee-first": { @@ -4273,6 +4285,19 @@ } } }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "requires": { + "void-elements": "3.1.0" + } + }, "http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -4296,6 +4321,48 @@ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" }, + "i18next": { + "version": "21.6.14", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.6.14.tgz", + "integrity": "sha512-XL6WyD+xlwQwbieXRlXhKWoLb/rkch50/rA+vl6untHnJ+aYnkQ0YDZciTWE78PPhOpbi2gR0LTJCJpiAhA+uQ==", + "requires": { + "@babel/runtime": "^7.17.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz", + "integrity": "sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } + } + }, + "i18next-browser-languagedetector": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.4.tgz", + "integrity": "sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==", + "requires": { + "@babel/runtime": "^7.14.6" + } + }, + "i18next-http-backend": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.0.tgz", + "integrity": "sha512-wsvx7E/CT1pHmBM99Vu57YLJpsrHbVjxGxf25EIJ/6oTjsvCkZZ6c3SA4TejcK5jIHfv9oLxQX8l+DFKZHZ0Gg==", + "requires": { + "cross-fetch": "3.1.5" + } + }, + "i18next-xhr-backend": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-3.2.2.tgz", + "integrity": "sha512-OtRf2Vo3IqAxsttQbpjYnmMML12IMB5e0fc5B7qKJFLScitYaXa1OhMX0n0X/3vrfFlpHL9Ro/H+ps4Ej2j7QQ==", + "requires": { + "@babel/runtime": "^7.5.5" + } + }, "image-size": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", @@ -5751,9 +5818,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, "mixin-deep": { "version": "1.3.2", @@ -5839,9 +5906,9 @@ } }, "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { "whatwg-url": "^5.0.0" } @@ -5881,11 +5948,11 @@ } }, "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", + "integrity": "sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==", "requires": { - "boolbase": "~1.0.0" + "boolbase": "^1.0.0" } }, "nullthrows": { @@ -6143,9 +6210,9 @@ } }, "plist": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.4.tgz", - "integrity": "sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==", "requires": { "base64-js": "^1.5.1", "xmlbuilder": "^9.0.7" @@ -6355,6 +6422,16 @@ "scheduler": "^0.20.2" } }, + "react-i18next": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.16.2.tgz", + "integrity": "sha512-1iuZduvARUelL5ux663FvIoDZExwFO+9QtRAAt4uvs1/aun4cUZt8XBrVg7iiDgNls9cOSORAhE7Ri5KA9RMvg==", + "requires": { + "@babel/runtime": "^7.14.5", + "html-escaper": "^2.0.2", + "html-parse-stringify": "^3.0.1" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -6601,11 +6678,11 @@ } }, "react-native-svg": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.1.1.tgz", - "integrity": "sha512-NIAJ8jCnXGCqGWXkkJ1GTzO4a3Md5at5sagYV8Vh4MXYnL4z5Rh428Wahjhh+LIjx40EE5xM5YtwyJBqOIba2Q==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-12.3.0.tgz", + "integrity": "sha512-ESG1g1j7/WLD7X3XRFTQHVv0r6DpbHNNcdusngAODIxG88wpTWUZkhcM3A2HJTb+BbXTFDamHv7FwtRKWQ/ALg==", "requires": { - "css-select": "^2.1.0", + "css-select": "^4.2.1", "css-tree": "^1.0.0-alpha.39" } }, @@ -7345,13 +7422,24 @@ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" }, "simple-plist": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.0.tgz", - "integrity": "sha512-uYWpeGFtZtVt2NhG4AHgpwx323zxD85x42heMJBan1qAiqqozIlaGrwrEt6kRjXWRWIXsuV1VLCvVmZan2B5dg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", + "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", "requires": { "bplist-creator": "0.1.0", - "bplist-parser": "0.3.0", - "plist": "^3.0.4" + "bplist-parser": "0.3.1", + "plist": "^3.0.5" + }, + "dependencies": { + "plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==", + "requires": { + "base64-js": "^1.5.1", + "xmlbuilder": "^9.0.7" + } + } } }, "sisteransi": { @@ -7993,6 +8081,11 @@ "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==" }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" + }, "walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/frontend/package.json b/frontend/package.json index 73e16d2a..a7dd8d1f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,16 +33,22 @@ "@mui/x-data-grid": "^5.2.2", "country-flag-icons": "^1.4.25", "date-fns": "^2.28.0", + "i18next": "^21.6.14", + "i18next-browser-languagedetector": "^6.1.4", + "i18next-http-backend": "^1.4.0", + "i18next-xhr-backend": "^3.2.2", "material-ui-image": "^3.3.2", "react-countdown": "^2.3.2", + "react-i18next": "^11.16.2", "react-native": "^0.66.4", - "react-native-svg": "^12.1.1", + "react-native-svg": "^12.3.0", "react-qr-code": "^2.0.3", "react-qr-reader": "^2.2.1", "react-responsive": "^9.0.0-beta.6", "react-router-dom": "^5.2.0", "react-world-flags": "^1.4.0", "reconnecting-websocket": "^4.4.0", + "simple-plist": "^1.3.1", "websocket": "^1.0.34" } } diff --git a/frontend/src/components/App.js b/frontend/src/components/App.js index 4d89def1..100c285b 100644 --- a/frontend/src/components/App.js +++ b/frontend/src/components/App.js @@ -5,6 +5,9 @@ import { CssBaseline, IconButton} from "@mui/material"; import { ThemeProvider, createTheme } from '@mui/material/styles'; import UnsafeAlert from "./UnsafeAlert"; +import { I18nextProvider } from "react-i18next"; +import i18n from "./i18n"; + import DarkModeIcon from '@mui/icons-material/DarkMode'; import LightModeIcon from '@mui/icons-material/LightMode'; @@ -12,18 +15,11 @@ export default class App extends Component { constructor(props) { super(props); this.state = { - nickname: null, - token: null, dark: false, } } - setAppState=(newState)=>{ - this.setState(newState) - } - - lightTheme = createTheme({ - }); + lightTheme = createTheme({}); darkTheme = createTheme({ palette: { @@ -36,14 +32,16 @@ export default class App extends Component { render() { return ( - - - this.setState({dark:!this.state.dark})}> - {this.state.dark ? :} - - - - + + + + this.setState({dark:!this.state.dark})}> + {this.state.dark ? :} + + + + + ); } } diff --git a/frontend/src/components/BookPage.js b/frontend/src/components/BookPage.js index a45b5961..27410bcd 100644 --- a/frontend/src/components/BookPage.js +++ b/frontend/src/components/BookPage.js @@ -1,22 +1,23 @@ -import React, { Component , useState } from "react"; +import React, { Component } from "react"; +import { withTranslation, Trans} from "react-i18next"; import { Badge, Tooltip, Paper, Button, ListItemButton, Typography, Grid, Select, MenuItem, FormControl, FormHelperText, ListItemText, ListItemAvatar, IconButton} from "@mui/material"; import { Link } from 'react-router-dom' import { DataGrid } from '@mui/x-data-grid'; +import currencyDict from '../../static/assets/currencies.json'; + import MediaQuery from 'react-responsive' import Image from 'material-ui-image' import getFlags from './getFlags' import PaymentText from './PaymentText' -export default class BookPage extends Component { +class BookPage extends Component { constructor(props) { super(props); this.state = { orders: new Array({id:0,}), - currencies_dict: {"0":"ANY"}, loading: true, pageSize: 6, }; - this.getCurrencyDict() this.getOrderDetails(this.props.type, this.props.currency) } @@ -50,21 +51,13 @@ export default class BookPage extends Component { }) this.getOrderDetails(this.props.type, currency); } - - getCurrencyDict() { - fetch('/static/assets/currencies.json') - .then((response) => response.json()) - .then((data) => - this.setState({ - currencies_dict: data - })); - } getCurrencyCode(val){ + const { t } = this.props; if (val){ - return val == 0 ? 'ANY' : this.state.currencies_dict[val.toString()] + return val == 0 ? t('ANY_currency') : currencyDict[val.toString()] }else{ - return 'ANY' + return t('ANY_currency') } } @@ -94,6 +87,7 @@ export default class BookPage extends Component { } bookListTableDesktop=()=>{ + const { t } = this.props; return (
{return ( - +
); } }, - { field: 'type', headerName: 'Is', width: 60 }, - { field: 'amount', headerName: 'Amount', type: 'number', width: 90, + { field: 'type', headerName: t("Is"), width: 60 }, + { field: 'amount', headerName: t("Amount"), type: 'number', width: 90, renderCell: (params) => {return (
{this.amountToString(params.row.amount,params.row.has_range, params.row.min_amount, params.row.max_amount)}
)}}, - { field: 'currency', headerName: 'Currency', width: 100, + { field: 'currency', headerName: t("Currency"), width: 100, renderCell: (params) => {return (
{params.row.currency+" "}{getFlags(params.row.currency)}
) }}, - { field: 'payment_method', headerName: 'Payment Method', width: 180 , + { field: 'payment_method', headerName: t("Payment Method"), width: 180 , renderCell: (params) => {return ( -
+
)} }, - { field: 'price', headerName: 'Price', type: 'number', width: 140, + { field: 'price', headerName: t("Price"), type: 'number', width: 140, renderCell: (params) => {return (
{this.pn(params.row.price) + " " +params.row.currency+ "/BTC" }
)} }, - { field: 'premium', headerName: 'Premium', type: 'number', width: 100, + { field: 'premium', headerName: t("Premium"), type: 'number', width: 100, renderCell: (params) => {return (
{parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }
)} }, @@ -172,7 +166,7 @@ export default class BookPage extends Component { } bookListTablePhone=()=>{ - + const { t } = this.props; return (
{return ( - +
); } }, - { field: 'type', headerName: 'Is', width: 60, hide:'true'}, - { field: 'amount', headerName: 'Amount', type: 'number', width: 84, + { field: 'type', headerName: t("Is"), width: 60, hide:'true'}, + { field: 'amount', headerName: t("Amount"), type: 'number', width: 84, renderCell: (params) => {return ( - +
{this.amountToString(params.row.amount,params.row.has_range, params.row.min_amount, params.row.max_amount)}
)} }, - { field: 'currency', headerName: 'Currency', width: 85, + { field: 'currency', headerName: t("Currency"), width: 85, renderCell: (params) => {return ( //
{params.row.currency+" "}{getFlags(params.row.currency)}
//
)} }, - { field: 'payment_method', headerName: 'Payment Method', width: 180, hide:'true'}, - { field: 'payment_icons', headerName: 'Pay', width: 75 , + { field: 'payment_method', headerName: t("Payment Method"), width: 180, hide:'true'}, + { field: 'payment_icons', headerName: t("Pay"), width: 75 , renderCell: (params) => {return ( -
+
)} }, - { field: 'price', headerName: 'Price', type: 'number', width: 140, hide:'true', + { field: 'price', headerName: t("Price"), type: 'number', width: 140, hide:'true', renderCell: (params) => {return (
{this.pn(params.row.price) + " " +params.row.currency+ "/BTC" }
)} }, - { field: 'premium', headerName: 'Premium', type: 'number', width: 85, + { field: 'premium', headerName: t("Premium"), type: 'number', width: 85, renderCell: (params) => {return (
{parseFloat(parseFloat(params.row.premium).toFixed(4))+"%" }
@@ -255,6 +249,7 @@ export default class BookPage extends Component { } render() { + const { t } = this.props; return ( {/* @@ -264,21 +259,21 @@ export default class BookPage extends Component { - I want to + {t("I want to")} @@ -286,21 +281,21 @@ export default class BookPage extends Component { - and {this.props.type == 0 ? ' receive' : (this.props.type == 1 ? ' pay with' : ' use' )} + {this.props.type == 0 ? t("and receive") : (this.props.type == 1 ? t("and pay with") : t("and use") )} @@ -309,7 +304,15 @@ export default class BookPage extends Component { { this.state.not_found ? "" : - You are {this.props.type == 0 ? selling : (this.props.type == 1 ? buying :" looking at all ")} BTC for {this.props.currencyCode} + {this.props.type == 0 ? + t("You are SELLING BTC for {{currencyCode}}",{currencyCode:this.props.currencyCode}) + : + (this.props.type == 1 ? + t("You are BUYING BTC for {{currencyCode}}",{currencyCode:this.props.currencyCode}) + : + t("You are looking at all") + ) + } } @@ -318,15 +321,19 @@ export default class BookPage extends Component { ( - No orders found to {this.props.type == 0 ? ' sell ' :' buy ' } BTC for {this.props.currencyCode} + {this.props.type == 0 ? + t("No orders found to sell BTC for {{currencyCode}}",{currencyCode:this.props.currencyCode}) + : + t("No orders found to buy BTC for {{currencyCode}}",{currencyCode:this.props.currencyCode}) + }
- + - Be the first one to create an order + {t("Be the first one to create an order")}

@@ -350,10 +357,12 @@ export default class BookPage extends Component { }
); }; -} \ No newline at end of file +} + +export default withTranslation()(BookPage); \ No newline at end of file diff --git a/frontend/src/components/BottomBar.js b/frontend/src/components/BottomBar.js index 6faf0207..818579d1 100644 --- a/frontend/src/components/BottomBar.js +++ b/frontend/src/components/BottomBar.js @@ -1,7 +1,9 @@ import React, { Component } from 'react' +import { withTranslation, Trans} from "react-i18next"; import {FormControlLabel, Link, Switch, CircularProgress, Badge, Tooltip, TextField, ListItemAvatar, Button, Avatar,Paper, Grid, IconButton, Typography, Select, MenuItem, List, ListItemText, ListItem, ListItemIcon, ListItemButton, Divider, Dialog, DialogContent} from "@mui/material"; import MediaQuery from 'react-responsive' import { Link as LinkRouter } from 'react-router-dom' +import Flags from 'country-flag-icons/react/3x2' // Icons import SettingsIcon from '@mui/icons-material/Settings'; @@ -54,7 +56,7 @@ function getCookie(name) { return cookieValue; } -export default class BottomBar extends Component { +class BottomBar extends Component { constructor(props) { super(props); this.state = { @@ -104,7 +106,7 @@ export default class BottomBar extends Component { }; StatsDialog =() =>{ - + const { t } = this.props; return( - Stats For Nerds + {t("Stats For Nerds")} - + @@ -154,7 +156,7 @@ export default class BottomBar extends Component { - + {this.state.robosats_running_commit_hash.slice(0, 12)+"..."} @@ -164,13 +166,13 @@ export default class BottomBar extends Component { - + - + @@ -178,12 +180,12 @@ export default class BottomBar extends Component { - Made with + {t("Made with")+" "} - and + {" "+t("and")+" "}
} - secondary="... somewhere on Earth!"/> + secondary={t("... somewhere on Earth!")}/> @@ -200,6 +202,7 @@ export default class BottomBar extends Component { }; CommunityDialog =() =>{ + const { t } = this.props; return( - Community + {t("Community")} -

Support is only offered via public channels. - Join our Telegram community if you have - questions or want to hang out with other cool robots. - Please, use our Github Issues if you find a bug or want - to see new features! -

+

{t("Support is only offered via public channels. Join our Telegram community if you have questions or want to hang out with other cool robots. Please, use our Github Issues if you find a bug or want to see new features!")}

- + - + - - + + + + + + + + + + + + + + + - + @@ -285,6 +295,7 @@ export default class BottomBar extends Component { } dialogProfile =() =>{ + const { t } = this.props; return( - Your Profile + {t("Your Profile")} - + {this.props.nickname ?
@@ -325,12 +336,12 @@ export default class BottomBar extends Component { - + : - + } @@ -338,17 +349,17 @@ export default class BottomBar extends Component { - + {this.props.token ? + navigator.clipboard.writeText(this.props.token)}> @@ -356,7 +367,7 @@ export default class BottomBar extends Component { }} /> : - 'Cannot remember'} + t("Cannot remember")} @@ -367,7 +378,7 @@ export default class BottomBar extends Component { this.setState({showRewards: !this.state.showRewards})}/>} - label="Rewards and compensations" + label={t("Rewards and compensations")} /> @@ -376,15 +387,14 @@ export default class BottomBar extends Component { - + + navigator.clipboard.writeText('http://'+this.getHost()+'/ref/'+this.state.referral_code)}> @@ -399,13 +409,13 @@ export default class BottomBar extends Component { {!this.state.openClaimRewards ? - + {this.state.earned_rewards+" Sats"} - + @@ -416,8 +426,7 @@ export default class BottomBar extends Component { { @@ -426,7 +435,7 @@ export default class BottomBar extends Component { /> - + @@ -441,7 +450,7 @@ export default class BottomBar extends Component { {this.state.withdrawn?
- There it goes, thank you!🥇 + {t("There it goes, thank you!🥇")}
:""} @@ -454,6 +463,7 @@ export default class BottomBar extends Component { } bottomBarDesktop =()=>{ + const { t } = this.props; return( @@ -465,9 +475,9 @@ bottomBarDesktop =()=>{
- 0 ? true: false} title="You can claim satoshis!"> + 0 ? true: false} title={t("You can claim satoshis!")}> 0 & !this.state.profileShown & this.props.avatarLoaded) ? true: false} - title="You have an active order"> + title={t("You have an active order")}> 0 & !this.state.profileShown) ? "": null} color="primary"> { primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.num_public_buy_orders} - secondary="Public Buy Orders" /> + secondary={t("Public Buy Orders")} /> @@ -508,7 +518,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.num_public_sell_orders} - secondary="Public Sell Orders" /> + secondary={t("Public Sell Orders")} /> @@ -521,7 +531,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.active_robots_today} - secondary="Today Active Robots" /> + secondary={t("Today Active Robots")}/> @@ -534,7 +544,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.last_day_nonkyc_btc_premium+"%"} - secondary="24h Avg Premium" /> + secondary={t("24h Avg Premium")} /> @@ -547,23 +557,16 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={(this.state.maker_fee + this.state.taker_fee)*100} - secondary="Trade Fee" /> + secondary={t("Trade Fee")} /> - + - + { - + @@ -587,7 +590,30 @@ bottomBarDesktop =()=>{ ) } - + handleChangeLang=(e)=>{ + const { i18n} = this.props; + console.log(i18n) + i18n.changeLanguage(e.target.value) + } + LangSelect = () => { + const { i18n} = this.props; + return( + + ) + } + handleClickOpenExchangeSummary = () => { this.getInfo(); this.setState({openExchangeSummary: true}); @@ -597,6 +623,7 @@ bottomBarDesktop =()=>{ }; exchangeSummaryDialog =() =>{ + const { t } = this.props; return( { aria-describedby="exchange-summary-description" > - Exchange Summary + {t("Exchange Summary")} @@ -615,7 +642,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.num_public_buy_orders} - secondary="Public buy orders" /> + secondary={t("Public buy orders")} /> @@ -627,7 +654,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.num_public_sell_orders} - secondary="Public sell orders" /> + secondary={t("Public sell orders")} /> @@ -639,7 +666,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={pn(this.state.book_liquidity)+" Sats"} - secondary="Book liquidity" /> + secondary={t("Book liquidity")}/> @@ -651,7 +678,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.active_robots_today} - secondary="Today active robots" /> + secondary={t("Today active robots")} /> @@ -663,7 +690,7 @@ bottomBarDesktop =()=>{ primaryTypographyProps={{fontSize: '14px'}} secondaryTypographyProps={{fontSize: '12px'}} primary={this.state.last_day_nonkyc_btc_premium+"%"} - secondary="24h non-KYC average premium" /> + secondary={t("24h non-KYC bitcoin premium")} /> @@ -676,7 +703,7 @@ bottomBarDesktop =()=>{ + secondary={t("Maker fee")}> {(this.state.maker_fee*100).toFixed(3)}% @@ -684,7 +711,7 @@ bottomBarDesktop =()=>{ + secondary={t("Taker fee")}> {(this.state.taker_fee*100).toFixed(3)}% @@ -698,6 +725,7 @@ bottomBarDesktop =()=>{ } bottomBarPhone =()=>{ + const { t } = this.props; return( @@ -708,9 +736,9 @@ bottomBarPhone =()=>{
- 0 ? true: false} title="You can claim satoshis!"> + 0 ? true: false} title={t("You can claim satoshis!")}> 0 & !this.state.profileShown & this.props.avatarLoaded) ? true: false} - title="You have an active order"> + title={t("You have an active order")}> 0 & !this.state.profileShown) ? "": null} color="primary"> { - + @@ -739,7 +767,7 @@ bottomBarPhone =()=>{ - + @@ -749,7 +777,7 @@ bottomBarPhone =()=>{ - + @@ -759,7 +787,7 @@ bottomBarPhone =()=>{ - + @@ -770,17 +798,10 @@ bottomBarPhone =()=>{ - + - + { - + @@ -819,3 +840,4 @@ bottomBarPhone =()=>{ ) } } +export default withTranslation()(BottomBar); \ No newline at end of file diff --git a/frontend/src/components/Chat.js b/frontend/src/components/Chat.js index 9b34aadf..6003e257 100644 --- a/frontend/src/components/Chat.js +++ b/frontend/src/components/Chat.js @@ -1,8 +1,9 @@ import React, { Component } from 'react'; +import { withTranslation, Trans} from "react-i18next"; import {Button, Link, Badge, TextField, Grid, Container, Card, CardHeader, Paper, Avatar, FormHelperText, Typography} from "@mui/material"; import ReconnectingWebSocket from 'reconnecting-websocket'; -export default class Chat extends Component { +class Chat extends Component { constructor(props) { super(props); } @@ -79,6 +80,7 @@ export default class Chat extends Component { } render() { + const { t } = this.props; return ( @@ -86,7 +88,7 @@ export default class Chat extends Component { - You: {this.state.connected ? 'connected': 'disconnected'} + {t("You")+": "}{this.state.connected ? t("connected"): t("disconnected")} @@ -94,7 +96,7 @@ export default class Chat extends Component { - Peer: {this.state.peer_connected ? 'connected': 'disconnected'} + {t("Peer")+": "}{this.state.peer_connected ? t("connected"): t("disconnected")} @@ -142,10 +144,10 @@ export default class Chat extends Component { { this.setState({ value: e.target.value }); @@ -155,14 +157,16 @@ export default class Chat extends Component { /> - + - The chat has no memory: if you leave, messages are lost. Learn easy PGP encryption. + {t("The chat has no memory: if you leave, messages are lost.")} {t("Learn easy PGP encryption.")} ) } } + +export default withTranslation()(Chat); \ No newline at end of file diff --git a/frontend/src/components/InfoDialog.js b/frontend/src/components/InfoDialog.js index c89f3afc..53476d3b 100644 --- a/frontend/src/components/InfoDialog.js +++ b/frontend/src/components/InfoDialog.js @@ -1,11 +1,12 @@ - -import {Typography, Link, DialogActions, DialogContent, Button, Grid} from "@mui/material" import React, { Component } from 'react' +import { withTranslation, Trans} from "react-i18next"; +import {Typography, Link, DialogActions, DialogContent, Button, Grid} from "@mui/material" import Image from 'material-ui-image' import MediaQuery from 'react-responsive' -export default class InfoDialog extends Component { +class InfoDialog extends Component { render() { + const { t } = this.props; return (
@@ -13,13 +14,13 @@ export default class InfoDialog extends Component { - What is RoboSats? + {t("What is RoboSats?")} -

It is a BTC/FIAT peer-to-peer exchange over lightning.
It simplifies - matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.

+

{t("It is a BTC/FIAT peer-to-peer exchange over lightning.")}
+ {t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}

-

RoboSats is an open source project (GitHub). +

{t("RoboSats is an open source project ")} {t("(GitHub).")}

@@ -35,130 +36,80 @@ export default class InfoDialog extends Component {
- What is RoboSats? + {t("What is RoboSats?")} -

It is a BTC/FIAT peer-to-peer exchange over lightning. It simplifies - matchmaking and minimizes the need for trust. RoboSats focuses in privacy and speed.

+

{t("It is a BTC/FIAT peer-to-peer exchange over lightning.")+" "} {t("It simplifies matchmaking and minimizes the need of trust. RoboSats focuses in privacy and speed.")}

-

RoboSats is an open source project (GitHub). +

{t("RoboSats is an open source project ")} {t("(GitHub).")}

- How does it work? + {t("How does it work?")} -

AnonymousAlice01 wants to sell bitcoin. She posts a sell order. - BafflingBob02 wants to buy bitcoin and he takes Alice's order. - Both have to post a small bond using lightning to prove they are real - robots. Then, Alice posts the trade collateral also using a lightning - hold invoice. RoboSats locks the invoice until Alice confirms she - received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, - Bob!

+

{t("AnonymousAlice01 wants to sell bitcoin. She posts a sell order. BafflingBob02 wants to buy bitcoin and he takes Alice's order. Both have to post a small bond using lightning to prove they are real robots. Then, Alice posts the trade collateral also using a lightning hold invoice. RoboSats locks the invoice until Alice confirms she received the fiat, then the satoshis are released to Bob. Enjoy your satoshis, Bob!")}

-

At no point, AnonymousAlice01 and BafflingBob02 have to entrust the - bitcoin funds to each other. In case they have a conflict, RoboSats staff - will help resolving the dispute. You can find a step-by-step - description of the trade pipeline in How it works - You can also check the full guide in How to use

+

{t("At no point, AnonymousAlice01 and BafflingBob02 have to entrust the bitcoin funds to each other. In case they have a conflict, RoboSats staff will help resolving the dispute.")} + {t("You can find a step-by-step description of the trade pipeline in ")} + {t("How it works")} + {t("You can also check the full guide in ")} + {t("How to use")}

- What payment methods are accepted? + {t("What payment methods are accepted?")} -

Basically all of them as long as they are fast. You can write down your preferred payment - method(s). You will have to match with a peer who also accepts that method. The step to - exchange fiat has a expiry time of 24 hours before a dispute is automatically - open. We highly recommend using instant fiat payment rails.

+

{t("All of them as long as they are fast. You can write down your preferred payment method(s). You will have to match with a peer who also accepts that method. The step to exchange fiat has a expiry time of 24 hours before a dispute is automatically open. We highly recommend using instant fiat payment rails.")}

- Are there trade limits? + {t("Are there trade limits?")} -

Maximum single trade size is 800,000 Satoshis to minimize lightning - routing failure. There is no limits to the number of trades per day. A robot - can only have one order at a time. However, you can use multiple - robots simultaneously in different browsers (remember to back up your robot tokens!).

+

{t("Maximum single trade size is {{maxAmount}} Satoshis to minimize lightning routing failure. There is no limits to the number of trades per day. A robot can only have one order at a time. However, you can use multiple robots simultaneously in different browsers (remember to back up your robot tokens!).", {maxAmount: '800,000'})}

- Is RoboSats private? + {t("Is RoboSats private?")} -

RoboSats will never ask you for your name, country or ID. RoboSats does - not custody your funds, and doesn't care who you are. For best anonymity - use Tor Browser and access the .onion hidden service.

- -

Your trading peer is the only one who can potentially guess - anything about you. Keep your chat short and concise. Avoid - providing non-essential information other than strictly necessary - for the fiat payment.

+

{t("RoboSats will never ask you for your name, country or ID. RoboSats does not custody your funds and does not care who you are. RoboSats does not collect or custody any personal data. For best anonymity use Tor Browser and access the .onion hidden service.")}

+

{t("Your trading peer is the only one who can potentially guess anything about you. Keep your chat short and concise. Avoid providing non-essential information other than strictly necessary for the fiat payment.")}

- What are the risks? + {t("What are the risks?")} -

This is an experimental application, things could go wrong. - Trade small amounts!

- -

The seller faces the same charge-back risk as with any - other peer-to-peer service. Paypal or credit cards are - not recommended.

+

{t("This is an experimental application, things could go wrong. Trade small amounts!")}

+

{t("The seller faces the same charge-back risk as with any other peer-to-peer service. Paypal or credit cards are not recommended.")}

- What is the trust model? + {t("What is the trust model?")} -

The buyer and the seller never have to trust each other. - Some trust on RoboSats is needed since linking the - seller's hold invoice and buyer payment is not atomic (yet). - In addition, disputes are solved by the RoboSats staff. -

- -

To be totally clear. Trust requirements are minimized. However, there is still - one way RoboSats could run away with your satoshis: by not releasing - the satoshis to the buyer. It could be argued that such move is not in RoboSats' - interest as it would damage the reputation for a small payout. - However, you should hesitate and only trade small quantities at a - time. For large amounts use an onchain escrow service such as Bisq -

- -

You can build more trust on RoboSats by - inspecting the source code.

+

{t("The buyer and the seller never have to trust each other. Some trust on RoboSats is needed since linking the seller's hold invoice and buyer payment is not atomic (yet). In addition, disputes are solved by the RoboSats staff.")}

+

{t("To be totally clear. Trust requirements are minimized. However, there is still one way RoboSats could run away with your satoshis: by not releasing the satoshis to the buyer. It could be argued that such move is not in RoboSats' interest as it would damage the reputation for a small payout. However, you should hesitate and only trade small quantities at a time. For large amounts use an onchain escrow service such as Bisq")}

+

{t("You can build more trust on RoboSats by inspecting the source code.")} {t("Project source code")}

- What happens if RoboSats suddenly disappears? + {t("What happens if RoboSats suddenly disappears?")} -

Your sats will return to you. Any hold invoice that is not - settled would be automatically returned even if RoboSats goes down - forever. This is true for both, locked bonds and trading escrows. However, - there is a small window between the seller confirms FIAT RECEIVED and the moment - the buyer receives the satoshis when the funds could be permanently lost if - RoboSats disappears. This window is about 1 second long. Make sure to have enough - inbound liquidity to avoid routing failures. If you have any problem, reach out - trough the RoboSats public channels. -

+

{t("Your sats will return to you. Any hold invoice that is not settled would be automatically returned even if RoboSats goes down forever. This is true for both, locked bonds and trading escrows. However, there is a small window between the seller confirms FIAT RECEIVED and the moment the buyer receives the satoshis when the funds could be permanently lost if RoboSats disappears. This window is about 1 second long. Make sure to have enough inbound liquidity to avoid routing failures. If you have any problem, reach out trough the RoboSats public channels.")}

- Is RoboSats legal in my country? + {t("Is RoboSats legal in my country?")} -

In many countries using RoboSats is no different than using Ebay - or Craiglist. Your regulation may vary. It is your responsibility - to comply. -

+

{t("In many countries using RoboSats is no different than using Ebay or Craiglist. Your regulation may vary. It is your responsibility to comply.")}

- Disclaimer + {t("Disclaimer")} -

This lightning application is provided as is. It is in active - development: trade with the utmost caution. There is no private - support. Support is only offered via public channels - (Telegram). RoboSats will never contact you. - RoboSats will definitely never ask for your robot token. -

+

{t("This lightning application is provided as is. It is in active development: trade with the utmost caution. There is no private support. Support is only offered via public channels ")}{t("(Telegram)")}{t(". RoboSats will never contact you. RoboSats will definitely never ask for your robot token.")}

- +
) } -} \ No newline at end of file +} + +export default withTranslation()(InfoDialog); \ No newline at end of file diff --git a/frontend/src/components/MakerPage.js b/frontend/src/components/MakerPage.js index 532bf685..633a9508 100644 --- a/frontend/src/components/MakerPage.js +++ b/frontend/src/components/MakerPage.js @@ -1,13 +1,18 @@ import React, { Component } from 'react'; +import { withTranslation, Trans} from "react-i18next"; import { InputAdornment, LinearProgress, Link, Checkbox, Slider, Box, Tab, Tabs, SliderThumb, Tooltip, Paper, Button , Grid, Typography, TextField, Select, FormHelperText, MenuItem, FormControl, Radio, FormControlLabel, RadioGroup} from "@mui/material" import { LocalizationProvider, TimePicker} from '@mui/lab'; import DateFnsUtils from "@date-io/date-fns"; import { Link as LinkRouter } from 'react-router-dom' import { styled } from '@mui/material/styles'; + import getFlags from './getFlags'; import AutocompletePayments from './autocompletePayments'; + + import LockIcon from '@mui/icons-material/Lock'; import HourglassTopIcon from '@mui/icons-material/HourglassTop'; +import currencyDict from '../../static/assets/currencies.json'; function getCookie(name) { let cookieValue = null; @@ -36,7 +41,7 @@ function pn(x) { return parts.join("."); } -export default class MakerPage extends Component { +class MakerPage extends Component { defaultCurrency = 1; defaultCurrencyCode = 'USD'; defaultPaymentMethod = "not specified"; @@ -57,7 +62,6 @@ export default class MakerPage extends Component { payment_method: this.defaultPaymentMethod, premium: 0, satoshis: null, - currencies_dict: {"1":"USD"}, showAdvanced: false, allowBondless: false, publicExpiryTime: new Date(0, 0, 0, 23, 59), @@ -69,7 +73,6 @@ export default class MakerPage extends Component { maxAmount: null, loadingLimits: false, } - this.getCurrencyDict() } getLimits() { @@ -173,11 +176,14 @@ export default class MakerPage extends Component { } handlePremiumChange=(e)=>{ + const { t } = this.props; + var max = 999; + var min = -100; if(e.target.value > 999){ - var bad_premium = "Must be less than 999%" + var bad_premium = t("Must be less than {{max}}%", {max:max}) } - if(e.target.value < -100){ - var bad_premium = "Must be more than -100%" + if(e.target.value <= -100){ + var bad_premium = t("Must be more than {{min}}%", {min:min}) } this.setState({ @@ -187,11 +193,12 @@ export default class MakerPage extends Component { } handleSatoshisChange=(e)=>{ + const { t } = this.props; if(e.target.value > this.maxTradeSats){ - var bad_sats = "Must be less than " + pn(this.maxTradeSats) + var bad_sats = t("Must be less than {{maxSats}",{maxSats: pn(this.maxTradeSats)}) } if(e.target.value < this.minTradeSats){ - var bad_sats = "Must be more than "+pn(this.minTradeSats) + var bad_sats = t("Must be more than {{minSats}}",{minSats: pn(this.minTradeSats)}) } this.setState({ @@ -207,10 +214,12 @@ export default class MakerPage extends Component { } handleClickExplicit=(e)=>{ - this.setState({ - is_explicit: true, - }); - this.handleSatoshisChange(); + if(!this.state.enableAmountRange){ + this.setState({ + is_explicit: true, + }); + this.handleSatoshisChange(); + } } handleCreateOfferButtonPressed=()=>{ @@ -240,18 +249,8 @@ export default class MakerPage extends Component { & (data.id ? this.props.history.push('/order/' + data.id) :""))); } - getCurrencyDict() { - fetch('/static/assets/currencies.json') - .then((response) => response.json()) - .then((data) => - this.setState({ - currencies_dict: data - })); - - } - getCurrencyCode(val){ - return this.state.currencies_dict[val.toString()] + return currencyDict[val.toString()] } handleInputBondSizeChange = (event) => { @@ -259,25 +258,26 @@ export default class MakerPage extends Component { }; StandardMakerOptions = () => { + const { t } = this.props; return(
- Buy or Sell Bitcoin? + {t("Buy or Sell Bitcoin?")} } - label="Buy" + label={t("Buy")} labelPlacement="Top" /> } - label="Sell" + label={t("Sell")} labelPlacement="Top" /> @@ -287,13 +287,13 @@ export default class MakerPage extends Component {
- + - {Object.entries(this.state.currencies_dict) + {Object.entries(currencyDict) .map( ([key, value]) =>
{getFlags(value)}{" "+value}
)} @@ -323,13 +323,15 @@ export default class MakerPage extends Component { - + @@ -338,25 +340,25 @@ export default class MakerPage extends Component {
- Choose a Pricing Method + {t("Choose a Pricing Method")}
- + } - label="Relative" + label={t("Relative")} labelPlacement="Top" onClick={this.handleClickRelative} /> - + } - label="Explicit" + label={t("Explicit")} labelPlacement="Top" onClick={this.handleClickExplicit} /> @@ -369,20 +371,18 @@ export default class MakerPage extends Component {
@@ -390,9 +390,8 @@ export default class MakerPage extends Component { sx={{width:240}} error={this.state.badPremium} helperText={this.state.badPremium} - label="Premium over Market (%)" + label={t("Premium over Market (%)")} type="number" - // defaultValue={this.defaultPremium} inputProps={{ min: -100, max: 999, @@ -492,10 +491,10 @@ export default class MakerPage extends Component { } rangeText =()=> { - + const { t } = this.props; return (
- From + {t("From")} - to + {t("to")} { + const { t } = this.props; return( @@ -530,10 +530,10 @@ export default class MakerPage extends Component { - +
- this.setState({enableAmountRange:e.target.checked}) & (e.target.checked ? this.getLimits() : null)}/> - {this.state.enableAmountRange & this.state.minAmount != null? : "Enable Amount Range"} + this.setState({enableAmountRange:e.target.checked, is_explicit: false}) & (e.target.checked ? this.getLimits() : null)}/> + {this.state.enableAmountRange & this.state.minAmount != null? : t("Enable Amount Range")}
@@ -583,7 +583,7 @@ export default class MakerPage extends Component { ) }} renderInput={(props) => } - label="Public Duration (HH:mm)" + label={t("Public Duration (HH:mm)")} value={this.state.publicExpiryTime} onChange={this.handleChangePublicDuration} minTime={new Date(0, 0, 0, 0, 10)} @@ -594,10 +594,10 @@ export default class MakerPage extends Component { - +
- Fidelity Bond Size + {t("Fidelity Bond Size")}
@@ -618,9 +618,9 @@ export default class MakerPage extends Component {
- + Allow bondless taker (info)} + label={t("Allow bondless takers")} control={ { const [value, setValue] = React.useState(this.state.showAdvanced); - + const { t } = this.props; const handleChange = (event, newValue) => { this.setState({showAdvanced:newValue}) setValue(newValue); @@ -649,8 +649,8 @@ export default class MakerPage extends Component { - - + + @@ -666,6 +666,7 @@ export default class MakerPage extends Component { ) } render() { + const { t } = this.props; return ( {/* @@ -685,11 +686,11 @@ export default class MakerPage extends Component { (this.state.is_explicit & (this.state.badSatoshis != null || this.state.satoshis == null)) || (!this.state.is_explicit & this.state.badPremium != null)) ? - -
+ +
: - + }
@@ -701,22 +702,37 @@ export default class MakerPage extends Component { : ""}
- Create a BTC {this.state.type==0 ? "buy":"sell"} order for {this.state.enableAmountRange & this.state.minAmount != null? - " "+this.state.minAmount+"-"+this.state.maxAmount : pn(this.state.amount)} {this.state.currencyCode} - {this.state.is_explicit ? " of " + pn(this.state.satoshis) + " Satoshis" : - (this.state.premium == 0 ? " at market price" : - (this.state.premium > 0 ? " at a " + this.state.premium + "% premium":" at a " + -this.state.premium + "% discount") + {this.state.type==0 ? + t("Create a BTC buy order for ") + : + t("Create a BTC sell order for ") + } + {this.state.enableAmountRange & this.state.minAmount != null? + this.state.minAmount+"-"+this.state.maxAmount + : + pn(this.state.amount)} + {" " + this.state.currencyCode} + {this.state.is_explicit ? + t(" of {{satoshis}} Satoshis",{satoshis: pn(this.state.satoshis)}) + : + (this.state.premium == 0 ? t(" at market price") : + (this.state.premium > 0 ? + t(" at a {{premium}}% premium", {premium: this.state.premium}) + : + t(" at a {{discount}}% discount", {discount: -this.state.premium})) ) }
); } -} \ No newline at end of file +} + +export default withTranslation()(MakerPage); \ No newline at end of file diff --git a/frontend/src/components/OrderPage.js b/frontend/src/components/OrderPage.js index 0713e33e..98aee4f0 100644 --- a/frontend/src/components/OrderPage.js +++ b/frontend/src/components/OrderPage.js @@ -1,7 +1,9 @@ import React, { Component } from "react"; +import { withTranslation, Trans} from "react-i18next"; import {TextField,Chip, Tooltip, Badge, Tab, Tabs, Alert, Paper, CircularProgress, Button , Grid, Typography, List, ListItem, ListItemIcon, ListItemText, ListItemAvatar, Avatar, Divider, Box, LinearProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" import Countdown, { zeroPad, calcTimeDelta } from 'react-countdown'; import MediaQuery from 'react-responsive' +import currencyDict from '../../static/assets/currencies.json'; import PaymentText from './PaymentText' import TradeBox from "./TradeBox"; @@ -13,6 +15,7 @@ import NumbersIcon from '@mui/icons-material/Numbers'; import PriceChangeIcon from '@mui/icons-material/PriceChange'; import PaymentsIcon from '@mui/icons-material/Payments'; import ArticleIcon from '@mui/icons-material/Article'; +import { t } from "i18next"; function getCookie(name) { let cookieValue = null; @@ -38,13 +41,12 @@ function pn(x) { return parts.join("."); } -export default class OrderPage extends Component { +class OrderPage extends Component { constructor(props) { super(props); this.state = { is_explicit: false, delay: 60000, // Refresh every 60 seconds by default - currencies_dict: {"1":"USD"}, total_secs_exp: 300, loading: true, openCancel: false, @@ -53,7 +55,6 @@ export default class OrderPage extends Component { showContractBox: 1, }; this.orderId = this.props.match.params.orderId; - this.getCurrencyDict(); this.getOrderDetails(); // Refresh delays according to Order status @@ -127,9 +128,10 @@ export default class OrderPage extends Component { // Countdown Renderer callback with condition countdownRenderer = ({ total, hours, minutes, seconds, completed }) => { + const { t } = this.props; if (completed) { // Render a completed state - return ( The order has expired); + return ( {t("The order has expired")}); } else { var col = 'inherit' @@ -148,13 +150,14 @@ export default class OrderPage extends Component { // Countdown Renderer callback with condition countdownPenaltyRenderer = ({ minutes, seconds, completed }) => { + const { t } = this.props; if (completed) { // Render a completed state - return ( Penalty lifted, good to go!); + return ( {t("Penalty lifted, good to go!")}); } else { return ( - You cannot take an order yet! Wait {zeroPad(minutes)}m {zeroPad(seconds)}s + {t("You cannot take an order yet! Wait {{timeMin}}m {{timeSec}}s",{timeMin: zeroPad(minutes), timeSec: zeroPad(seconds) })} ); } }; @@ -168,27 +171,29 @@ export default class OrderPage extends Component { } amountHelperText=()=>{ + const { t } = this.props; if(this.state.takeAmount < this.state.min_amount & this.state.takeAmount != ""){ - return "Too low" + return t("Too low") }else if (this.state.takeAmount > this.state.max_amount & this.state.takeAmount != ""){ - return "Too high" + return t("Too high") }else{ return null } } takeOrderButton = () => { + const { t } = this.props; if(this.state.has_range){ return(
- + this.state.max_amount) & this.state.takeAmount != "" } helperText={this.amountHelperText()} - label={"Amount "+this.state.currencyCode} + label={t("Amount {{currencyCode}}", {currencyCode: this.state.currencyCode})} size="small" type="number" required="true" @@ -204,11 +209,11 @@ export default class OrderPage extends Component {
this.state.max_amount || this.state.takeAmount == "" || this.state.takeAmount == null) ? '':'none'}}> - + @@ -217,7 +222,7 @@ export default class OrderPage extends Component {
@@ -229,7 +234,7 @@ export default class OrderPage extends Component { ) @@ -243,8 +248,8 @@ export default class OrderPage extends Component { return ( ); } else{ return( -
- +
+
) } }; @@ -274,7 +279,6 @@ export default class OrderPage extends Component { takeOrder=()=>{ this.setState({loading:true}) - console.log(this.state.takeAmount) const requestOptions = { method: 'POST', headers: {'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken'),}, @@ -287,15 +291,6 @@ export default class OrderPage extends Component { .then((response) => response.json()) .then((data) => this.completeSetState(data)); } - - getCurrencyDict() { - fetch('/static/assets/currencies.json') - .then((response) => response.json()) - .then((data) => - this.setState({ - currencies_dict: data - })); - } // set delay to the one matching the order status. If null order status, delay goes to 9999999. setDelay = (status)=>{ @@ -303,7 +298,7 @@ export default class OrderPage extends Component { } getCurrencyCode(val){ - let code = val ? this.state.currencies_dict[val.toString()] : "" + let code = val ? currencyDict[val.toString()] : "" return code } @@ -330,6 +325,7 @@ export default class OrderPage extends Component { }; CancelDialog =() =>{ + const { t } = this.props; return( - {"Cancel the order?"} + {t("Cancel the order?")} - If the order is cancelled now you will lose your bond. + {t("If the order is cancelled now you will lose your bond.")} - - + + ) @@ -361,6 +357,7 @@ export default class OrderPage extends Component { }; InactiveMakerDialog =() =>{ + const { t } = this.props; return( - {"The maker is away"} + {t("The maker is away")} - By taking this order you risk wasting your time. - If the maker does not proceed in time, you will be compensated in satoshis for 50% of the maker bond. + {t("By taking this order you risk wasting your time. If the maker does not proceed in time, you will be compensated in satoshis for 50% of the maker bond.")} - - + + ) @@ -406,6 +402,7 @@ export default class OrderPage extends Component { }; CollaborativeCancelDialog =() =>{ + const { t } = this.props; return( - {"Collaborative cancel the order?"} + {t("Collaborative cancel the order?")} - The trade escrow has been posted. The order can be cancelled only if both, maker and - taker, agree to cancel. + {t("The trade escrow has been posted. The order can be cancelled only if both, maker and taker, agree to cancel.")} - - + + ) } BackButton = () => { + const { t } = this.props; // If order has expired, show back button. if (this.state.status == 5){ return( - + )} return(null) } CancelButton = () => { - + const { t } = this.props; // If maker and Waiting for Bond. Or if taker and Waiting for bond. // Simply allow to cancel without showing the cancel dialog. if ((this.state.is_maker & [0,1].includes(this.state.status)) || this.state.is_taker & this.state.status == 3){ return( - + )} // If the order does not yet have an escrow deposited. Show dialog @@ -458,7 +455,7 @@ export default class OrderPage extends Component {
- +
)} @@ -469,7 +466,7 @@ export default class OrderPage extends Component { return( - + )} @@ -485,19 +482,20 @@ export default class OrderPage extends Component { } orderBox=()=>{ + const { t } = this.props; return( - Order Box + {t("Order Box")} - + - + {this.state.is_participant ? @@ -515,9 +513,9 @@ export default class OrderPage extends Component { <> - + - + : "" } - + - + - : + : } @@ -550,10 +548,10 @@ export default class OrderPage extends Component { {this.state.has_range & this.state.amount == null ? + +"-" + parseFloat(Number(this.state.max_amount).toPrecision(2)) +" "+this.state.currencyCode} secondary={t("Amount range")}/> : + +" "+this.state.currencyCode} secondary={t("Amount")}/> } @@ -563,7 +561,7 @@ export default class OrderPage extends Component { - } secondary={this.state.currency==1000 ? "Swap destination":"Accepted payment methods"}/> + } secondary={this.state.currency==1000 ? t("Swap destination"):t("Accepted payment methods")}/> @@ -573,12 +571,12 @@ export default class OrderPage extends Component { {this.state.price_now? - + : (this.state.is_explicit ? - + : - + ) } @@ -588,14 +586,14 @@ export default class OrderPage extends Component { - + - + @@ -620,7 +618,7 @@ export default class OrderPage extends Component { - {this.state.is_maker ? this.state.taker_nick : this.state.maker_nick} is asking for a collaborative cancel + {t("{{nickname}} is asking for a collaborative cancel", {nickname: this.state.is_maker ? this.state.taker_nick : this.state.maker_nick})} @@ -632,7 +630,7 @@ export default class OrderPage extends Component { - You asked for a collaborative cancellation + {t("You asked for a collaborative cancellation")} @@ -654,7 +652,7 @@ export default class OrderPage extends Component { - + } @@ -684,7 +682,7 @@ export default class OrderPage extends Component { } doubleOrderPagePhone=()=>{ - + const { t } = this.props; const [value, setValue] = React.useState(this.state.showContractBox); const handleChange = (event, newValue) => { @@ -696,8 +694,8 @@ export default class OrderPage extends Component { - - + + @@ -715,13 +713,15 @@ export default class OrderPage extends Component { } orderDetailsPage (){ + const { t } = this.props; return( this.state.bad_request ?
- {this.state.bad_request}
+ {/* IMPLEMENT I18N for bad_request */} + {t(this.state.bad_request)}
- +
: (this.state.is_participant ? @@ -750,3 +750,5 @@ export default class OrderPage extends Component { ); } } + +export default withTranslation()(OrderPage); \ No newline at end of file diff --git a/frontend/src/components/PaymentIcons.js b/frontend/src/components/PaymentIcons.js index 83df738c..73b564b8 100644 --- a/frontend/src/components/PaymentIcons.js +++ b/frontend/src/components/PaymentIcons.js @@ -1,4 +1,3 @@ -import { TextField } from '@mui/material'; import React, { Component } from 'react'; import DashboardCustomizeIcon from '@mui/icons-material/DashboardCustomize'; diff --git a/frontend/src/components/PaymentText.js b/frontend/src/components/PaymentText.js index bc8a853b..385ac1a0 100644 --- a/frontend/src/components/PaymentText.js +++ b/frontend/src/components/PaymentText.js @@ -1,7 +1,6 @@ -import PaymentIcon from './PaymentIcons' import React, { Component } from 'react' +import PaymentIcon from './PaymentIcons' import {Tooltip} from "@mui/material" -import { intlFormat } from 'date-fns'; const someMethods = [ {name: "Revolut",icon:'revolut'}, @@ -67,9 +66,9 @@ export default class PaymentText extends Component { // Adds a Custom icon if there are words that do not match var chars_left = custom_methods.replace(' ','') chars_left = chars_left.replace(' ','') - + if(chars_left.length > 0){rows.push( - +
diff --git a/frontend/src/components/TradeBox.js b/frontend/src/components/TradeBox.js index d34fc9fb..770188d5 100644 --- a/frontend/src/components/TradeBox.js +++ b/frontend/src/components/TradeBox.js @@ -1,4 +1,5 @@ import React, { Component } from "react"; +import { withTranslation, Trans} from "react-i18next"; import { IconButton, Box, Link, Paper, Rating, Button, Tooltip, CircularProgress, Grid, Typography, TextField, List, ListItem, ListItemText, Divider, ListItemIcon, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "@mui/material" import QRCode from "react-qr-code"; import Countdown, { zeroPad} from 'react-countdown'; @@ -40,7 +41,7 @@ function pn(x) { return parts.join("."); } -export default class TradeBox extends Component { +class TradeBox extends Component { constructor(props) { super(props); this.state = { @@ -53,6 +54,8 @@ export default class TradeBox extends Component { } } + + Sound = ({soundFileName}) => ( // Four filenames: "locked-invoice", "taker-found", "open-chat", "successful"