Add react-i18next integration
This commit is contained in:
parent
ed9ed68d83
commit
5f3ac69f60
281
frontend/package-lock.json
generated
281
frontend/package-lock.json
generated
|
@ -16,6 +16,8 @@
|
|||
"echarts": "^5.3.2",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"fomantic-ui-less": "^2.8.8",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"i18next-http-backend": "^1.4.1",
|
||||
"immer": "^9.0.7",
|
||||
"luxon": "^1.28.0",
|
||||
"maplibre-gl": "^1.15.2",
|
||||
|
@ -30,6 +32,7 @@
|
|||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "^6.15.8",
|
||||
"react-i18next": "^11.18.1",
|
||||
"react-map-gl": "^6.1.17",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-redux": "^7.2.6",
|
||||
|
@ -42,7 +45,8 @@
|
|||
"sass": "^1.43.5",
|
||||
"semantic-ui-react": "^2.0.4",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.7.4"
|
||||
"typescript": "^4.7.4",
|
||||
"yaml-loader": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
|
@ -4162,6 +4166,14 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"dependencies": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
|
@ -6251,6 +6263,14 @@
|
|||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/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==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-to-react": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.7.tgz",
|
||||
|
@ -6385,6 +6405,45 @@
|
|||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "21.8.14",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-21.8.14.tgz",
|
||||
"integrity": "sha512-4Yi+DtexvMm/Yw3Q9fllzY12SgLk+Mcmar+rCAccsOPul/2UmnBzoHbTGn/L48IPkFcmrNaH7xTLboBWIbH6pw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2"
|
||||
}
|
||||
},
|
||||
"node_modules/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==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.6"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next-http-backend": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz",
|
||||
"integrity": "sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "3.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
@ -7020,6 +7079,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/javascript-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="
|
||||
},
|
||||
"node_modules/jest-worker": {
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz",
|
||||
|
@ -7867,6 +7931,25 @@
|
|||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
|
@ -9251,6 +9334,27 @@
|
|||
"react": "^16.8.0 || ^17"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "11.18.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.1.tgz",
|
||||
"integrity": "sha512-S8cl4mvIOSA7OQCE5jNy2yhv705Vwi+7PinpqKIYcBmX/trJtHKqrf6CL67WJSA8crr2JU+oxE9jn9DQIrQezg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.5",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 19.0.0",
|
||||
"react": ">= 16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
@ -10596,6 +10700,11 @@
|
|||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/trough": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
|
||||
|
@ -10971,6 +11080,14 @@
|
|||
"@math.gl/web-mercator": "^3.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vt-pbf": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
||||
|
@ -11010,6 +11127,11 @@
|
|||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.64.4",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.64.4.tgz",
|
||||
|
@ -11275,6 +11397,15 @@
|
|||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -11375,6 +11506,40 @@
|
|||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-loader": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.8.0.tgz",
|
||||
"integrity": "sha512-LjeKnTzVBKWiQBeE2L9ssl6WprqaUIxCSNs5tle8PaDydgu3wVFXTbMfsvF2MSErpy9TDVa092n4q6adYwJaWg==",
|
||||
"dependencies": {
|
||||
"javascript-stringify": "^2.0.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.13"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-loader/node_modules/loader-utils": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
|
||||
"dependencies": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-loader/node_modules/yaml": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz",
|
||||
"integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
@ -14399,6 +14564,14 @@
|
|||
"yaml": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
@ -16002,6 +16175,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"html-to-react": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/html-to-react/-/html-to-react-1.4.7.tgz",
|
||||
|
@ -16100,6 +16281,31 @@
|
|||
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
|
||||
"dev": true
|
||||
},
|
||||
"i18next": {
|
||||
"version": "21.8.14",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-21.8.14.tgz",
|
||||
"integrity": "sha512-4Yi+DtexvMm/Yw3Q9fllzY12SgLk+Mcmar+rCAccsOPul/2UmnBzoHbTGn/L48IPkFcmrNaH7xTLboBWIbH6pw==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2"
|
||||
}
|
||||
},
|
||||
"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.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-1.4.1.tgz",
|
||||
"integrity": "sha512-s4Q9hK2jS29iyhniMP82z+yYY8riGTrWbnyvsSzi5TaF7Le4E7b5deTmtuaRuab9fdDcYXtcwdBgawZG+JCEjA==",
|
||||
"requires": {
|
||||
"cross-fetch": "3.1.5"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
@ -16509,6 +16715,11 @@
|
|||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
|
||||
"dev": true
|
||||
},
|
||||
"javascript-stringify": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "27.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.3.1.tgz",
|
||||
|
@ -17175,6 +17386,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
|
@ -18149,6 +18368,15 @@
|
|||
"integrity": "sha512-prq82ofMbnRyj5wqDe8hsTRcdR25jQ+B8KtCS7BLCzjFHAwNuCjRwzPuP4eYLsEBjEIeYd6try+pdLdw0kPkpg==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-i18next": {
|
||||
"version": "11.18.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.1.tgz",
|
||||
"integrity": "sha512-S8cl4mvIOSA7OQCE5jNy2yhv705Vwi+7PinpqKIYcBmX/trJtHKqrf6CL67WJSA8crr2JU+oxE9jn9DQIrQezg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.14.5",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
@ -19174,6 +19402,11 @@
|
|||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||
"dev": true
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"trough": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
|
||||
|
@ -19458,6 +19691,11 @@
|
|||
"@math.gl/web-mercator": "^3.5.5"
|
||||
}
|
||||
},
|
||||
"void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
|
||||
},
|
||||
"vt-pbf": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
||||
|
@ -19494,6 +19732,11 @@
|
|||
"minimalistic-assert": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.64.4",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.64.4.tgz",
|
||||
|
@ -19672,6 +19915,15 @@
|
|||
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -19740,6 +19992,33 @@
|
|||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||
},
|
||||
"yaml-loader": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.8.0.tgz",
|
||||
"integrity": "sha512-LjeKnTzVBKWiQBeE2L9ssl6WprqaUIxCSNs5tle8PaDydgu3wVFXTbMfsvF2MSErpy9TDVa092n4q6adYwJaWg==",
|
||||
"requires": {
|
||||
"javascript-stringify": "^2.0.1",
|
||||
"loader-utils": "^2.0.0",
|
||||
"yaml": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"loader-utils": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
|
||||
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz",
|
||||
"integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
"echarts": "^5.3.2",
|
||||
"echarts-for-react": "^3.0.2",
|
||||
"fomantic-ui-less": "^2.8.8",
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"i18next-http-backend": "^1.4.1",
|
||||
"immer": "^9.0.7",
|
||||
"luxon": "^1.28.0",
|
||||
"maplibre-gl": "^1.15.2",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"react-dom": "^17.0.2",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hook-form": "^6.15.8",
|
||||
"react-i18next": "^11.18.1",
|
||||
"react-map-gl": "^6.1.17",
|
||||
"react-markdown": "^5.0.3",
|
||||
"react-redux": "^7.2.6",
|
||||
|
@ -41,7 +44,8 @@
|
|||
"sass": "^1.43.5",
|
||||
"semantic-ui-react": "^2.0.4",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.7.4"
|
||||
"typescript": "^4.7.4",
|
||||
"yaml-loader": "^0.8.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
|
|
@ -7,9 +7,11 @@ import {useObservable} from 'rxjs-hooks'
|
|||
import {from} from 'rxjs'
|
||||
import {pluck} from 'rxjs/operators'
|
||||
import {Helmet} from "react-helmet";
|
||||
import {useTranslation} from 'react-i18next'
|
||||
|
||||
import {useConfig} from 'config'
|
||||
import styles from './App.module.less'
|
||||
import {AVAILABLE_LOCALES, setLocale} from 'i18n'
|
||||
|
||||
import {
|
||||
ExportPage,
|
||||
|
@ -58,6 +60,7 @@ function Banner({text, style = 'warning'}: {text: string; style: 'warning' | 'in
|
|||
}
|
||||
|
||||
const App = connect((state) => ({login: state.login}))(function App({login}) {
|
||||
const {t} = useTranslation()
|
||||
const config = useConfig()
|
||||
const apiVersion = useObservable(() => from(api.get('/info')).pipe(pluck('version')))
|
||||
|
||||
|
@ -210,12 +213,6 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
|
|||
Imprint
|
||||
</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Grid.Column>
|
||||
|
||||
<Grid.Column>
|
||||
<Header as="h5">Info</Header>
|
||||
<List>
|
||||
<List.Item>
|
||||
<a
|
||||
href={`https://github.com/openbikesensor/portal${
|
||||
|
@ -224,11 +221,18 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
|
|||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{apiVersion ? `v${apiVersion}` : 'Fetching version...'}
|
||||
Version {apiVersion ? `v${apiVersion}` : 'Fetching version...'}
|
||||
</a>
|
||||
</List.Item>
|
||||
</List>
|
||||
</Grid.Column>
|
||||
|
||||
<Grid.Column>
|
||||
<Header as="h5">{t('App.footer.changeLanguage')}</Header>
|
||||
<List>
|
||||
{AVAILABLE_LOCALES.map(locale => <List.Item key={locale}><a onClick={() => setLocale(locale)}>{t(`locales.${locale}`)}</a></List.Item>)}
|
||||
</List>
|
||||
</Grid.Column>
|
||||
</Grid.Row>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
|
95
frontend/src/i18n.ts
Normal file
95
frontend/src/i18n.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { useState, useEffect, useMemo } from "react";
|
||||
import i18next, { TOptions } from "i18next";
|
||||
import { BehaviorSubject, combineLatest } from "rxjs";
|
||||
import { map, distinctUntilChanged } from "rxjs/operators";
|
||||
import HttpBackend, {
|
||||
BackendOptions,
|
||||
RequestCallback,
|
||||
} from "i18next-http-backend";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
export type AvailableLocales = "en" | "de";
|
||||
|
||||
async function request(
|
||||
_options: BackendOptions,
|
||||
url: string,
|
||||
_payload: any,
|
||||
callback: RequestCallback
|
||||
) {
|
||||
try {
|
||||
const [lng] = url.split("/");
|
||||
const locale = await import(`translations/${lng}.yaml`);
|
||||
callback(null, { status: 200, data: locale });
|
||||
} catch (e) {
|
||||
console.error(`Unable to load locale at ${url}\n`, e);
|
||||
callback(null, { status: 404, data: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
export const AVAILABLE_LOCALES: AvailableLocales[] = ["en", "de"];
|
||||
|
||||
const i18n = i18next.createInstance();
|
||||
|
||||
const options: TOptions = {
|
||||
fallbackLng: "en",
|
||||
|
||||
ns: ["common"],
|
||||
defaultNS: "common",
|
||||
whitelist: AVAILABLE_LOCALES,
|
||||
|
||||
// loading via webpack
|
||||
backend: {
|
||||
loadPath: "{{lng}}/{{ns}}",
|
||||
parse: (data: any) => data,
|
||||
request,
|
||||
},
|
||||
|
||||
load: "languageOnly",
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
},
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(HttpBackend)
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.init({ ...options });
|
||||
|
||||
const locale$ = new BehaviorSubject<AvailableLocales>("en");
|
||||
|
||||
export const translate = i18n.t.bind(i18n);
|
||||
|
||||
export const translate$ = (stringAndData$: [string, any]) =>
|
||||
combineLatest([stringAndData$, locale$.pipe(distinctUntilChanged())]).pipe(
|
||||
map(([stringAndData]) => {
|
||||
if (typeof stringAndData === "string") {
|
||||
return i18n.t(stringAndData);
|
||||
} else {
|
||||
const [string, data] = stringAndData;
|
||||
return i18n.t(string, { data });
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
export const setLocale = (locale: AvailableLocales) => {
|
||||
i18n.changeLanguage(locale);
|
||||
locale$.next(locale);
|
||||
};
|
||||
|
||||
export function useLocale() {
|
||||
const [, reload] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
i18n.on("languageChanged", reload);
|
||||
return () => {
|
||||
i18n.off("languageChanged", reload);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return i18n.language;
|
||||
}
|
||||
|
||||
export default i18n;
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, {Suspense} from 'react'
|
||||
import {Settings} from 'luxon'
|
||||
import ReactDOM from 'react-dom'
|
||||
import 'fomantic-ui-less/semantic.less'
|
||||
|
@ -11,13 +11,16 @@ import 'maplibre-gl/dist/maplibre-gl.css'
|
|||
import {Provider} from 'react-redux'
|
||||
|
||||
import store from './store'
|
||||
import './i18n'
|
||||
|
||||
// TODO: remove
|
||||
Settings.defaultLocale = 'de-DE'
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<Suspense fallback={null}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
|
|
@ -4,6 +4,7 @@ import {Message, Grid, Loader, Header, Item} from 'semantic-ui-react'
|
|||
import {useObservable} from 'rxjs-hooks'
|
||||
import {of, from} from 'rxjs'
|
||||
import {map, switchMap} from 'rxjs/operators'
|
||||
import {useTranslation} from 'react-i18next'
|
||||
|
||||
import api from 'api'
|
||||
import {Stats, Page, Map} from 'components'
|
||||
|
@ -22,9 +23,10 @@ function MostRecentTrack() {
|
|||
[]
|
||||
)
|
||||
|
||||
const {t} = useTranslation()
|
||||
return (
|
||||
<>
|
||||
<Header as="h2">Most recent track</Header>
|
||||
<Header as="h2">{t('HomePage.mostRecentTrack')}</Header>
|
||||
<Loader active={track === null} />
|
||||
{track === undefined ? (
|
||||
<Message>
|
||||
|
|
6
frontend/src/translations/de.yaml
Normal file
6
frontend/src/translations/de.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
HomePage:
|
||||
mostRecentTrack: Neueste Aufzeichnung
|
||||
|
||||
App:
|
||||
footer:
|
||||
changeLanguage: Sprache wechseln
|
10
frontend/src/translations/en.yaml
Normal file
10
frontend/src/translations/en.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
HomePage:
|
||||
mostRecentTrack: Most recent track
|
||||
|
||||
locales:
|
||||
en: English
|
||||
de: Deutsch
|
||||
|
||||
App:
|
||||
footer:
|
||||
changeLanguage: Change language
|
|
@ -207,6 +207,14 @@ module.exports = function (webpackEnv) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
type: 'json',
|
||||
use: [{
|
||||
loader: 'yaml-loader',
|
||||
options: {asJSON: true},
|
||||
}],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: getStyleLoaders(false),
|
||||
|
|
Loading…
Reference in a new issue