diff --git a/backend/package-lock.json b/backend/package-lock.json
index 85b168c1ef..3f555b8dea 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -35,6 +35,8 @@
"query-string": "^7.1.3",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
+ "swagger-jsdoc": "^6.2.5",
+ "swagger-ui-express": "^4.6.0",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
@@ -81,6 +83,46 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@apidevtools/json-schema-ref-parser": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.0.tgz",
+ "integrity": "sha512-teB30tFooE3iQs2HQIKJ02D8UZA1Xy1zaczzhUjJs0CymYxeC0g+y5rCY2p8NHBM6DBUVoR8rSM4kHLj1WE9mQ==",
+ "dependencies": {
+ "@jsdevtools/ono": "^7.1.3",
+ "@types/json-schema": "^7.0.6",
+ "call-me-maybe": "^1.0.1",
+ "js-yaml": "^4.1.0"
+ }
+ },
+ "node_modules/@apidevtools/openapi-schemas": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
+ "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@apidevtools/swagger-methods": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
+ "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
+ },
+ "node_modules/@apidevtools/swagger-parser": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz",
+ "integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==",
+ "dependencies": {
+ "@apidevtools/json-schema-ref-parser": "^9.0.6",
+ "@apidevtools/openapi-schemas": "^2.0.4",
+ "@apidevtools/swagger-methods": "^3.0.2",
+ "@jsdevtools/ono": "^7.1.3",
+ "call-me-maybe": "^1.0.1",
+ "z-schema": "^4.2.3"
+ },
+ "peerDependencies": {
+ "openapi-types": ">=7"
+ }
+ },
"node_modules/@aws-crypto/ie11-detection": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz",
@@ -2571,6 +2613,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
+ "node_modules/@jsdevtools/ono": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
+ },
"node_modules/@juanelas/base64": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.5.tgz",
@@ -3147,8 +3194,7 @@
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
- "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
- "dev": true
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"node_modules/@types/jsonwebtoken": {
"version": "8.5.9",
@@ -3628,8 +3674,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/array-flatten": {
"version": "1.1.1",
@@ -4013,6 +4058,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/call-me-maybe": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
+ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4535,7 +4585,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
"dependencies": {
"esutils": "^2.0.2"
},
@@ -4848,7 +4897,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -6447,7 +6495,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
"dependencies": {
"argparse": "^2.0.1"
},
@@ -9789,6 +9836,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/openapi-types": {
+ "version": "12.0.2",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.0.2.tgz",
+ "integrity": "sha512-GuTo7FyZjOIWVhIhQSWJVaws6A82sWIGyQogxxYBYKZ0NBdyP2CYSIgOwFfSB+UVoPExk/YzFpyYitHS8KVZtA==",
+ "peer": true
+ },
"node_modules/optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -10935,6 +10988,74 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/swagger-jsdoc": {
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.5.tgz",
+ "integrity": "sha512-l+cdsKS2y+QDhrH1TJSUiE0y9XKuf5xaGSatjf0hR/wjTlMpO8WfubBK9d/nASdbHPMtj9iJZLBH2ogBEhL7Sw==",
+ "dependencies": {
+ "commander": "6.2.0",
+ "doctrine": "3.0.0",
+ "glob": "7.1.6",
+ "lodash.mergewith": "^4.6.2",
+ "swagger-parser": "10.0.2",
+ "yaml": "2.0.0-1"
+ },
+ "bin": {
+ "swagger-jsdoc": "bin/swagger-jsdoc.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/swagger-jsdoc/node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/swagger-parser": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz",
+ "integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==",
+ "dependencies": {
+ "@apidevtools/swagger-parser": "10.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/swagger-ui-dist": {
+ "version": "4.15.5",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
+ "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
+ },
+ "node_modules/swagger-ui-express": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz",
+ "integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==",
+ "dependencies": {
+ "swagger-ui-dist": ">=4.11.0"
+ },
+ "engines": {
+ "node": ">= v0.10.32"
+ },
+ "peerDependencies": {
+ "express": ">=4.0.0"
+ }
+ },
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -11520,6 +11641,14 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
+ "node_modules/yaml": {
+ "version": "2.0.0-1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
+ "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/yargs": {
"version": "17.6.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
@@ -11567,6 +11696,31 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/z-schema": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.4.tgz",
+ "integrity": "sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==",
+ "dependencies": {
+ "lodash.get": "^4.4.2",
+ "lodash.isequal": "^4.5.0",
+ "validator": "^13.6.0"
+ },
+ "bin": {
+ "z-schema": "bin/z-schema"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "optionalDependencies": {
+ "commander": "^2.7.1"
+ }
+ },
+ "node_modules/z-schema/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
}
},
"dependencies": {
@@ -11580,6 +11734,40 @@
"@jridgewell/trace-mapping": "^0.3.9"
}
},
+ "@apidevtools/json-schema-ref-parser": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.0.tgz",
+ "integrity": "sha512-teB30tFooE3iQs2HQIKJ02D8UZA1Xy1zaczzhUjJs0CymYxeC0g+y5rCY2p8NHBM6DBUVoR8rSM4kHLj1WE9mQ==",
+ "requires": {
+ "@jsdevtools/ono": "^7.1.3",
+ "@types/json-schema": "^7.0.6",
+ "call-me-maybe": "^1.0.1",
+ "js-yaml": "^4.1.0"
+ }
+ },
+ "@apidevtools/openapi-schemas": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
+ "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ=="
+ },
+ "@apidevtools/swagger-methods": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
+ "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="
+ },
+ "@apidevtools/swagger-parser": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz",
+ "integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==",
+ "requires": {
+ "@apidevtools/json-schema-ref-parser": "^9.0.6",
+ "@apidevtools/openapi-schemas": "^2.0.4",
+ "@apidevtools/swagger-methods": "^3.0.2",
+ "@jsdevtools/ono": "^7.1.3",
+ "call-me-maybe": "^1.0.1",
+ "z-schema": "^4.2.3"
+ }
+ },
"@aws-crypto/ie11-detection": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-2.0.2.tgz",
@@ -13763,6 +13951,11 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
+ "@jsdevtools/ono": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="
+ },
"@juanelas/base64": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@juanelas/base64/-/base64-1.0.5.tgz",
@@ -14266,8 +14459,7 @@
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
- "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
- "dev": true
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
},
"@types/jsonwebtoken": {
"version": "8.5.9",
@@ -14615,8 +14807,7 @@
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"array-flatten": {
"version": "1.1.1",
@@ -14900,6 +15091,11 @@
"get-intrinsic": "^1.0.2"
}
},
+ "call-me-maybe": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
+ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="
+ },
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -15298,7 +15494,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
- "dev": true,
"requires": {
"esutils": "^2.0.2"
}
@@ -15533,8 +15728,7 @@
"esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"etag": {
"version": "1.8.1",
@@ -16735,7 +16929,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
"requires": {
"argparse": "^2.0.1"
}
@@ -19125,6 +19318,12 @@
"mimic-fn": "^2.1.0"
}
},
+ "openapi-types": {
+ "version": "12.0.2",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.0.2.tgz",
+ "integrity": "sha512-GuTo7FyZjOIWVhIhQSWJVaws6A82sWIGyQogxxYBYKZ0NBdyP2CYSIgOwFfSB+UVoPExk/YzFpyYitHS8KVZtA==",
+ "peer": true
+ },
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -19960,6 +20159,55 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
+ "swagger-jsdoc": {
+ "version": "6.2.5",
+ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.5.tgz",
+ "integrity": "sha512-l+cdsKS2y+QDhrH1TJSUiE0y9XKuf5xaGSatjf0hR/wjTlMpO8WfubBK9d/nASdbHPMtj9iJZLBH2ogBEhL7Sw==",
+ "requires": {
+ "commander": "6.2.0",
+ "doctrine": "3.0.0",
+ "glob": "7.1.6",
+ "lodash.mergewith": "^4.6.2",
+ "swagger-parser": "10.0.2",
+ "yaml": "2.0.0-1"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ }
+ }
+ },
+ "swagger-parser": {
+ "version": "10.0.2",
+ "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz",
+ "integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==",
+ "requires": {
+ "@apidevtools/swagger-parser": "10.0.2"
+ }
+ },
+ "swagger-ui-dist": {
+ "version": "4.15.5",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.15.5.tgz",
+ "integrity": "sha512-V3eIa28lwB6gg7/wfNvAbjwJYmDXy1Jo1POjyTzlB6wPcHiGlRxq39TSjYGVjQrUSAzpv+a7nzp7mDxgNy57xA=="
+ },
+ "swagger-ui-express": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.0.tgz",
+ "integrity": "sha512-ZxpQFp1JR2RF8Ar++CyJzEDdvufa08ujNUJgMVTMWPi86CuQeVdBtvaeO/ysrz6dJAYXf9kbVNhWD7JWocwqsA==",
+ "requires": {
+ "swagger-ui-dist": ">=4.11.0"
+ }
+ },
"test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
@@ -20371,6 +20619,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
+ "yaml": {
+ "version": "2.0.0-1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
+ "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ=="
+ },
"yargs": {
"version": "17.6.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz",
@@ -20403,6 +20656,25 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true
+ },
+ "z-schema": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.4.tgz",
+ "integrity": "sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==",
+ "requires": {
+ "commander": "^2.7.1",
+ "lodash.get": "^4.4.2",
+ "lodash.isequal": "^4.5.0",
+ "validator": "^13.6.0"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "optional": true
+ }
+ }
}
}
}
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 15a200783e..6cf75ad47d 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -48,6 +48,8 @@ services:
- ./frontend/public:/app/public
- ./frontend/styles:/app/styles
- ./frontend/components:/app/components
+ - ./frontend/locales:/app/locales
+ - ./frontend/next-i18next.config.js:/app/next-i18next.config.js
env_file: .env
environment:
- NEXT_PUBLIC_ENV=development
diff --git a/frontend/.gitignore b/frontend/.gitignore
index 83e774c8cf..5edd5a7fa3 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -32,3 +32,5 @@ yarn-error.log*
.env.production.local
.vercel
.env.infisical
+
+.vscode
\ No newline at end of file
diff --git a/frontend/components/basic/Layout.tsx b/frontend/components/basic/Layout.tsx
index 56a16cb3de..5a8d4098ce 100644
--- a/frontend/components/basic/Layout.tsx
+++ b/frontend/components/basic/Layout.tsx
@@ -1,38 +1,39 @@
/* eslint-disable no-unexpected-multiline */
/* eslint-disable react-hooks/exhaustive-deps */
-import { useEffect, useState } from 'react';
-import Link from 'next/link';
-import { useRouter } from 'next/router';
+import { useEffect, useMemo, useState } from "react";
+import Link from "next/link";
+import { useRouter } from "next/router";
+import { useTranslation } from "next-i18next";
import {
faBookOpen,
faGear,
faKey,
faMobile,
faPlug,
- faUser
-} from '@fortawesome/free-solid-svg-icons';
-import { faPlus } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+ faUser,
+} from "@fortawesome/free-solid-svg-icons";
+import { faPlus } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import getOrganizations from '~/pages/api/organization/getOrgs';
-import getOrganizationUserProjects from '~/pages/api/organization/GetOrgUserProjects';
-import getOrganizationUsers from '~/pages/api/organization/GetOrgUsers';
-import checkUserAction from '~/pages/api/userActions/checkUserAction';
-import addUserToWorkspace from '~/pages/api/workspace/addUserToWorkspace';
-import createWorkspace from '~/pages/api/workspace/createWorkspace';
-import getWorkspaces from '~/pages/api/workspace/getWorkspaces';
-import uploadKeys from '~/pages/api/workspace/uploadKeys';
+import getOrganizations from "~/pages/api/organization/getOrgs";
+import getOrganizationUserProjects from "~/pages/api/organization/GetOrgUserProjects";
+import getOrganizationUsers from "~/pages/api/organization/GetOrgUsers";
+import checkUserAction from "~/pages/api/userActions/checkUserAction";
+import addUserToWorkspace from "~/pages/api/workspace/addUserToWorkspace";
+import createWorkspace from "~/pages/api/workspace/createWorkspace";
+import getWorkspaces from "~/pages/api/workspace/getWorkspaces";
+import uploadKeys from "~/pages/api/workspace/uploadKeys";
-import NavBarDashboard from '../navigation/NavBarDashboard';
-import onboardingCheck from '../utilities/checks/OnboardingCheck';
-import { tempLocalStorage } from '../utilities/checks/tempLocalStorage';
+import NavBarDashboard from "../navigation/NavBarDashboard";
+import onboardingCheck from "../utilities/checks/OnboardingCheck";
+import { tempLocalStorage } from "../utilities/checks/tempLocalStorage";
import {
decryptAssymmetric,
- encryptAssymmetric
-} from '../utilities/cryptography/crypto';
-import Button from './buttons/Button';
-import AddWorkspaceDialog from './dialog/AddWorkspaceDialog';
-import Listbox from './Listbox';
+ encryptAssymmetric,
+} from "../utilities/cryptography/crypto";
+import Button from "./buttons/Button";
+import AddWorkspaceDialog from "./dialog/AddWorkspaceDialog";
+import Listbox from "./Listbox";
interface LayoutProps {
children: React.ReactNode;
@@ -41,15 +42,17 @@ interface LayoutProps {
export default function Layout({ children }: LayoutProps) {
const router = useRouter();
const [workspaceList, setWorkspaceList] = useState([]);
- const [workspaceMapping, setWorkspaceMapping] = useState([{ '1': '2' }]);
- const [workspaceSelected, setWorkspaceSelected] = useState('∞');
- const [newWorkspaceName, setNewWorkspaceName] = useState('');
+ const [workspaceMapping, setWorkspaceMapping] = useState([{ "1": "2" }]);
+ const [workspaceSelected, setWorkspaceSelected] = useState("∞");
+ const [newWorkspaceName, setNewWorkspaceName] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [totalOnboardingActionsDone, setTotalOnboardingActionsDone] =
useState(0);
+ const { t } = useTranslation();
+
function closeModal() {
setIsOpen(false);
}
@@ -75,35 +78,35 @@ export default function Layout({ children }: LayoutProps) {
if (!currentWorkspaces.includes(workspaceName)) {
const newWorkspace = await createWorkspace({
workspaceName,
- organizationId: tempLocalStorage('orgData.id')
+ organizationId: tempLocalStorage("orgData.id"),
});
const newWorkspaceId = newWorkspace._id;
if (addAllUsers) {
const orgUsers = await getOrganizationUsers({
- orgId: tempLocalStorage('orgData.id')
+ orgId: tempLocalStorage("orgData.id"),
});
orgUsers.map(async (user: any) => {
- if (user.status == 'accepted') {
+ if (user.status == "accepted") {
const result = await addUserToWorkspace(
user.user.email,
newWorkspaceId
);
if (result?.invitee && result?.latestKey) {
- const PRIVATE_KEY = tempLocalStorage('PRIVATE_KEY');
+ const PRIVATE_KEY = tempLocalStorage("PRIVATE_KEY");
// assymmetrically decrypt symmetric key with local private key
const key = decryptAssymmetric({
ciphertext: result.latestKey.encryptedKey,
nonce: result.latestKey.nonce,
publicKey: result.latestKey.sender.publicKey,
- privateKey: PRIVATE_KEY
+ privateKey: PRIVATE_KEY,
});
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: key,
publicKey: result.invitee.publicKey,
- privateKey: PRIVATE_KEY
+ privateKey: PRIVATE_KEY,
}) as { ciphertext: string; nonce: string };
uploadKeys(
@@ -116,11 +119,11 @@ export default function Layout({ children }: LayoutProps) {
}
});
}
- router.push('/dashboard/' + newWorkspaceId + '?Development');
+ router.push("/dashboard/" + newWorkspaceId + "?Development");
setIsOpen(false);
- setNewWorkspaceName('');
+ setNewWorkspaceName("");
} else {
- console.error('A project with this name already exists.');
+ console.error("A project with this name already exists.");
setError(true);
setLoading(false);
}
@@ -131,62 +134,65 @@ export default function Layout({ children }: LayoutProps) {
}
}
- const menuItems = [
- {
- href:
- '/dashboard/' +
- workspaceMapping[workspaceSelected as any] +
- '?Development',
- title: 'Secrets',
- emoji:
- },
- {
- href: '/users/' + workspaceMapping[workspaceSelected as any],
- title: 'Members',
- emoji:
- },
- {
- href: '/integrations/' + workspaceMapping[workspaceSelected as any],
- title: 'Integrations',
- emoji:
- },
- {
- href: '/settings/project/' + workspaceMapping[workspaceSelected as any],
- title: 'Project Settings',
- emoji:
- }
- ];
+ const menuItems = useMemo(
+ () => [
+ {
+ href:
+ "/dashboard/" +
+ workspaceMapping[workspaceSelected as any] +
+ "?Development",
+ title: t("nav:menu.secrets"),
+ emoji: ,
+ },
+ {
+ href: "/users/" + workspaceMapping[workspaceSelected as any],
+ title: t("nav:menu.members"),
+ emoji: ,
+ },
+ {
+ href: "/integrations/" + workspaceMapping[workspaceSelected as any],
+ title: t("nav:menu.integrations"),
+ emoji: ,
+ },
+ {
+ href: "/settings/project/" + workspaceMapping[workspaceSelected as any],
+ title: t("nav:menu.project-settings"),
+ emoji: ,
+ },
+ ],
+ [t, workspaceMapping, workspaceSelected]
+ );
useEffect(() => {
// Put a user in a workspace if they're not in one yet
const putUserInWorkSpace = async () => {
- if (tempLocalStorage('orgData.id') === '') {
+ if (tempLocalStorage("orgData.id") === "") {
const userOrgs = await getOrganizations();
- localStorage.setItem('orgData.id', userOrgs[0]._id);
+ localStorage.setItem("orgData.id", userOrgs[0]._id);
}
const orgUserProjects = await getOrganizationUserProjects({
- orgId: tempLocalStorage('orgData.id')
+ orgId: tempLocalStorage("orgData.id"),
});
const userWorkspaces = orgUserProjects;
if (
userWorkspaces.length == 0 &&
- router.asPath != '/noprojects' &&
- !router.asPath.includes('settings')
+ router.asPath != "/noprojects" &&
+ !router.asPath.includes("settings")
) {
- router.push('/noprojects');
- } else if (router.asPath != '/noprojects') {
+ router.push("/noprojects");
+ } else if (router.asPath != "/noprojects") {
const intendedWorkspaceId = router.asPath
- .split('/')
- [router.asPath.split('/').length - 1].split('?')[0];
+ .split("/")
+ [router.asPath.split("/").length - 1].split("?")[0];
// If a user is not a member of a workspace they are trying to access, just push them to one of theirs
if (
- intendedWorkspaceId != 'heroku' &&
+ intendedWorkspaceId != "heroku" &&
!userWorkspaces
.map((workspace: { _id: string }) => workspace._id)
.includes(intendedWorkspaceId)
) {
- router.push('/dashboard/' + userWorkspaces[0]._id + '?Development');
+ router.push("/dashboard/" + userWorkspaces[0]._id + "?Development");
} else {
setWorkspaceList(
userWorkspaces.map((workspace: any) => workspace.name)
@@ -195,7 +201,7 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries(
userWorkspaces.map((workspace: any) => [
workspace.name,
- workspace._id
+ workspace._id,
])
) as any
);
@@ -203,12 +209,12 @@ export default function Layout({ children }: LayoutProps) {
Object.fromEntries(
userWorkspaces.map((workspace: any) => [
workspace._id,
- workspace.name
+ workspace.name,
])
)[
router.asPath
- .split('/')
- [router.asPath.split('/').length - 1].split('?')[0]
+ .split("/")
+ [router.asPath.split("/").length - 1].split("?")[0]
]
);
}
@@ -224,16 +230,16 @@ export default function Layout({ children }: LayoutProps) {
workspaceMapping[workspaceSelected as any] &&
`${workspaceMapping[workspaceSelected as any]}` !==
router.asPath
- .split('/')
- [router.asPath.split('/').length - 1].split('?')[0]
+ .split("/")
+ [router.asPath.split("/").length - 1].split("?")[0]
) {
router.push(
- '/dashboard/' +
+ "/dashboard/" +
workspaceMapping[workspaceSelected as any] +
- '?Development'
+ "?Development"
);
localStorage.setItem(
- 'projectData.id',
+ "projectData.id",
`${workspaceMapping[workspaceSelected as any]}`
);
}
@@ -257,7 +263,7 @@ export default function Layout({ children }: LayoutProps) {
- PROJECT
+ {t("nav:menu.project")}
{workspaceList.length > 0 ? (
0 &&
menuItems.map(({ href, title, emoji }) => (
- {router.asPath.split('/')[1] === href.split('/')[1] &&
- (['project', 'billing', 'org', 'personal'].includes(
- router.asPath.split('/')[2]
+ {router.asPath.split("/")[1] === href.split("/")[1] &&
+ (["project", "billing", "org", "personal"].includes(
+ router.asPath.split("/")[2]
)
- ? router.asPath.split('/')[2] === href.split('/')[2]
+ ? router.asPath.split("/")[2] === href.split("/")[2]
: true) ? (
{title}
- ) : router.asPath == '/noprojects' ? (
+ ) : router.asPath == "/noprojects" ? (
@@ -323,7 +329,7 @@ export default function Layout({ children }: LayoutProps) {
- {router.asPath.split('/')[1] === 'home' ? (
+ {router.asPath.split("/")[1] === "home" ? (
@@ -334,12 +340,12 @@ export default function Layout({ children }: LayoutProps) {
Infisical Guide
- {' '}
- To use Infisical, please log in through a device with larger
- dimensions.{' '}
+ {` ${t("common:no-mobile")} `}
>
diff --git a/frontend/components/basic/dialog/AddIncidentContactDialog.js b/frontend/components/basic/dialog/AddIncidentContactDialog.js
index fad13cdee2..535004045d 100644
--- a/frontend/components/basic/dialog/AddIncidentContactDialog.js
+++ b/frontend/components/basic/dialog/AddIncidentContactDialog.js
@@ -1,4 +1,5 @@
import { Fragment, useState } from "react";
+import { useTranslation } from "next-i18next";
import { Dialog, Transition } from "@headlessui/react";
import addIncidentContact from "~/pages/api/organization/addIncidentContact";
@@ -14,6 +15,7 @@ const AddIncidentContactDialog = ({
setIncidentContacts,
}) => {
let [incidentContactEmail, setIncidentContactEmail] = useState("");
+ const { t } = useTranslation();
const submit = () => {
setIncidentContacts(
@@ -59,17 +61,16 @@ const AddIncidentContactDialog = ({
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
- Add an Incident Contact
+ {t("section-incident:add-dialog.title")}
- This contact will be notified in the unlikely event of a
- severe incident.
+ {t("section-incident:add-dialog.description")}
diff --git a/frontend/components/basic/dialog/AddProjectMemberDialog.js b/frontend/components/basic/dialog/AddProjectMemberDialog.js
index 0d18bc3ca9..e8dd610de6 100644
--- a/frontend/components/basic/dialog/AddProjectMemberDialog.js
+++ b/frontend/components/basic/dialog/AddProjectMemberDialog.js
@@ -1,5 +1,6 @@
import { Fragment, useState } from "react";
import { useRouter } from "next/router";
+import { Trans, useTranslation } from "next-i18next";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../buttons/Button";
@@ -15,6 +16,7 @@ const AddProjectMemberDialog = ({
setEmail,
}) => {
const router = useRouter();
+ const { t } = useTranslation();
return (
@@ -49,48 +51,55 @@ const AddProjectMemberDialog = ({
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
- Add a member to your project
+ {t("section-members:add-dialog.add-member-to-project")}
) : (
- All the users in your organization are already invited.
+ {t("section-members:add-dialog.already-all-invited")}
)}
{data?.length > 0 ? (
- The user will receive an email with the instructions.
+ {t("section-members:add-dialog.user-will-email")}
-
- router.push("/settings/org/" + router.query.id)
- }
- >
- If you are looking to add users to your org,
-
-
- router.push(
- "/settings/org/" + router.query.id + "?invite"
- )
- }
- >
- click here.
-
+
+ router.push(
+ "/settings/org/" + router.query.id
+ )
+ }
+ />,
+ // eslint-disable-next-line react/jsx-key
+
+ router.push(
+ "/settings/org/" +
+ router.query.id +
+ "?invite"
+ )
+ }
+ />,
+ ]}
+ />
) : (
- Add more users to the organization first.
+ {t("section-members:add-dialog.add-user-org-first")}
)}
@@ -110,7 +119,7 @@ const AddProjectMemberDialog = ({
@@ -120,7 +129,7 @@ const AddProjectMemberDialog = ({
router.push("/settings/org/" + router.query.id)
}
color="mineshaft"
- text="Add Users to Organization"
+ text={t("section-members:add-dialog.add-user-to-org")}
size="md"
/>
)}
diff --git a/frontend/components/basic/dialog/AddServiceTokenDialog.js b/frontend/components/basic/dialog/AddServiceTokenDialog.js
index ddb20cce35..b02a804b70 100644
--- a/frontend/components/basic/dialog/AddServiceTokenDialog.js
+++ b/frontend/components/basic/dialog/AddServiceTokenDialog.js
@@ -1,40 +1,42 @@
-import { Fragment, useState } from 'react';
-import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Dialog, Transition } from '@headlessui/react';
-import nacl from 'tweetnacl';
+import { Fragment, useState } from "react";
+import { useTranslation } from "next-i18next";
+import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Dialog, Transition } from "@headlessui/react";
+import nacl from "tweetnacl";
-import addServiceToken from '~/pages/api/serviceToken/addServiceToken';
-import getLatestFileKey from '~/pages/api/workspace/getLatestFileKey';
+import addServiceToken from "~/pages/api/serviceToken/addServiceToken";
+import getLatestFileKey from "~/pages/api/workspace/getLatestFileKey";
-import { envMapping } from '../../../public/data/frequentConstants';
+import { envMapping } from "../../../public/data/frequentConstants";
import {
decryptAssymmetric,
- encryptAssymmetric
-} from '../../utilities/cryptography/crypto';
-import Button from '../buttons/Button';
-import InputField from '../InputField';
-import ListBox from '../Listbox';
+ encryptAssymmetric,
+} from "../../utilities/cryptography/crypto";
+import Button from "../buttons/Button";
+import InputField from "../InputField";
+import ListBox from "../Listbox";
const expiryMapping = {
- '1 day': 86400,
- '7 days': 604800,
- '1 month': 2592000,
- '6 months': 15552000,
- '12 months': 31104000
+ "1 day": 86400,
+ "7 days": 604800,
+ "1 month": 2592000,
+ "6 months": 15552000,
+ "12 months": 31104000,
};
const AddServiceTokenDialog = ({
isOpen,
closeModal,
workspaceId,
- workspaceName
+ workspaceName,
}) => {
- const [serviceToken, setServiceToken] = useState('');
- const [serviceTokenName, setServiceTokenName] = useState('');
- const [serviceTokenEnv, setServiceTokenEnv] = useState('Development');
- const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState('1 day');
+ const [serviceToken, setServiceToken] = useState("");
+ const [serviceTokenName, setServiceTokenName] = useState("");
+ const [serviceTokenEnv, setServiceTokenEnv] = useState("Development");
+ const [serviceTokenExpiresIn, setServiceTokenExpiresIn] = useState("1 day");
const [serviceTokenCopied, setServiceTokenCopied] = useState(false);
+ const { t } = useTranslation();
const generateServiceToken = async () => {
const latestFileKey = await getLatestFileKey({ workspaceId });
@@ -43,7 +45,7 @@ const AddServiceTokenDialog = ({
ciphertext: latestFileKey.latestKey.encryptedKey,
nonce: latestFileKey.latestKey.nonce,
publicKey: latestFileKey.latestKey.sender.publicKey,
- privateKey: localStorage.getItem('PRIVATE_KEY')
+ privateKey: localStorage.getItem("PRIVATE_KEY"),
});
// generate new public/private key pair
@@ -55,7 +57,7 @@ const AddServiceTokenDialog = ({
const { ciphertext: encryptedKey, nonce } = encryptAssymmetric({
plaintext: key,
publicKey,
- privateKey
+ privateKey,
});
let newServiceToken = await addServiceToken({
@@ -65,16 +67,16 @@ const AddServiceTokenDialog = ({
expiresIn: expiryMapping[serviceTokenExpiresIn],
publicKey,
encryptedKey,
- nonce
+ nonce,
});
- const serviceToken = newServiceToken + ',' + privateKey;
+ const serviceToken = newServiceToken + "," + privateKey;
setServiceToken(serviceToken);
};
function copyToClipboard() {
// Get the text field
- var copyText = document.getElementById('serviceToken');
+ var copyText = document.getElementById("serviceToken");
// Select the text field
copyText.select();
@@ -91,8 +93,8 @@ const AddServiceTokenDialog = ({
const closeAddServiceTokenModal = () => {
closeModal();
- setServiceTokenName('');
- setServiceToken('');
+ setServiceTokenName("");
+ setServiceToken("");
};
return (
@@ -122,27 +124,26 @@ const AddServiceTokenDialog = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
- {serviceToken == '' ? (
+ {serviceToken == "" ? (
- Add a service token for {workspaceName}
+ {t("section-token:add-dialog.title", {
+ target: workspaceName,
+ })}
- Specify the name, environment, and expiry period. When
- a token is generated, you will only be able to see it
- once before it disappears. Make sure to save it
- somewhere.
+ {t("section-token:add-dialog.description")}
@@ -169,14 +170,14 @@ const AddServiceTokenDialog = ({
selected={serviceTokenExpiresIn}
onChange={setServiceTokenExpiresIn}
data={[
- '1 day',
- '7 days',
- '1 month',
- '6 months',
- '12 months'
+ "1 day",
+ "7 days",
+ "1 month",
+ "6 months",
+ "12 months",
]}
- isFull={true}
- text="Expires in: "
+ width="full"
+ text={`${t("common:expired-in")}: `}
/>
@@ -184,10 +185,10 @@ const AddServiceTokenDialog = ({
generateServiceToken()}
color="mineshaft"
- text="Add Service Token"
- textDisabled="Add Service Token"
+ text={t("section-token:add-dialog.add")}
+ textDisabled={t("section-token:add-dialog.add")}
size="md"
- active={serviceTokenName == '' ? false : true}
+ active={serviceTokenName == "" ? false : true}
/>
@@ -198,13 +199,14 @@ const AddServiceTokenDialog = ({
as="h3"
className="text-lg font-medium leading-6 text-gray-400 z-50"
>
- Copy your service token
+ {t("section-token:add-dialog.copy-service-token")}
- Once you close this popup, you will never see your
- service token again
+ {t(
+ "section-token:add-dialog.copy-service-token-description"
+ )}
@@ -234,7 +236,7 @@ const AddServiceTokenDialog = ({
)}
- Click to Copy
+ {t("common.click-to-copy")}
diff --git a/frontend/components/dashboard/DropZone.tsx b/frontend/components/dashboard/DropZone.tsx
index e9c533b058..66be964cc0 100644
--- a/frontend/components/dashboard/DropZone.tsx
+++ b/frontend/components/dashboard/DropZone.tsx
@@ -1,12 +1,13 @@
-import { type ChangeEvent, type DragEvent, useState } from 'react';
-import Image from 'next/image';
-import { faUpload } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { type ChangeEvent, type DragEvent, useState } from "react";
+import Image from "next/image";
+import { useTranslation } from "next-i18next";
+import { faUpload } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import Button from '../basic/buttons/Button';
-import Error from '../basic/Error';
-import parse from '../utilities/file';
-import guidGenerator from '../utilities/randomId';
+import Button from "../basic/buttons/Button";
+import Error from "../basic/Error";
+import parse from "../utilities/file";
+import guidGenerator from "../utilities/randomId";
interface DropZoneProps {
// TODO: change Data type from any
@@ -26,8 +27,10 @@ const DropZone = ({
errorDragAndDrop,
setButtonReady,
keysExist,
- numCurrentRows
+ numCurrentRows,
}: DropZoneProps) => {
+ const { t } = useTranslation();
+
const handleDragEnter = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
@@ -43,7 +46,7 @@ const DropZone = ({
e.stopPropagation();
// set dropEffect to copy i.e copy of the source item
- e.dataTransfer.dropEffect = 'copy';
+ e.dataTransfer.dropEffect = "copy";
};
const [loading, setLoading] = useState(false);
@@ -54,7 +57,7 @@ const DropZone = ({
setTimeout(() => setLoading(false), 5000);
e.preventDefault();
e.stopPropagation();
- e.dataTransfer.dropEffect = 'copy';
+ e.dataTransfer.dropEffect = "copy";
const file = e.dataTransfer.files[0];
const reader = new FileReader();
@@ -69,7 +72,7 @@ const DropZone = ({
pos: numCurrentRows + index,
key: key,
value: keyPairs[key as keyof typeof keyPairs],
- type: 'shared'
+ type: "shared",
};
});
setData(newData);
@@ -96,16 +99,16 @@ const DropZone = ({
reader.onload = (event) => {
if (event.target === null || event.target.result === null) return;
const { result } = event.target;
- if (typeof result === 'string') {
+ if (typeof result === "string") {
const newData = result
- .split('\n')
+ .split("\n")
.map((line: string, index: number) => {
return {
id: guidGenerator(),
pos: numCurrentRows + index,
- key: line.split('=')[0],
- value: line.split('=').slice(1, line.split('=').length).join('='),
- type: 'shared'
+ key: line.split("=")[0],
+ value: line.split("=").slice(1, line.split("=").length).join("="),
+ type: "shared",
};
});
setData(newData);
@@ -149,9 +152,7 @@ const DropZone = ({
icon={faUpload}
className="text-bunker-300 text-3xl mr-6"
/>
-
- Drag and drop your .env file here to add more keys.
-
+ {t("common:drop-zone-keys")}
{errorDragAndDrop ? (
@@ -170,7 +171,7 @@ const DropZone = ({
onDrop={handleDrop}
>
-
Drag and drop your .env file here.
+
{t("common:drop-zone")}
[
[
,
- 'Join Slack Forum',
- 'https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g'
+ t("nav:support.slack"),
+ "https://join.slack.com/t/infisical/shared_invite/zt-1dgg63ln8-G7PCNJdCymAT9YF3j1ewVA",
],
[
,
- 'Read Docs',
- 'https://infisical.com/docs/getting-started/introduction'
+ t("nav:support.docs"),
+ "https://infisical.com/docs/getting-started/introduction",
],
[
,
- 'Open a GitHub Issue',
- 'https://github.com/Infisical/infisical-cli/issues'
+ t("nav:support.issue"),
+ "https://github.com/Infisical/infisical-cli/issues",
],
[
,
- 'Send us an Email',
- 'mailto:support@infisical.com'
- ]
+ t("nav:support.email"),
+ "mailto:support@infisical.com",
+ ],
];
export interface ICurrentOrg {
@@ -68,6 +68,10 @@ export default function Navbar() {
const [orgs, setOrgs] = useState([]);
const [currentOrg, setCurrentOrg] = useState
();
+ const { t } = useTranslation();
+
+ const supportOptionsList = useMemo(() => supportOptions(t), [t]);
+
useEffect(() => {
(async () => {
const userData = await getUser();
@@ -75,16 +79,16 @@ export default function Navbar() {
const orgsData = await getOrganizations();
setOrgs(orgsData);
const currentOrg = await getOrganization({
- orgId: String(localStorage.getItem('orgData.id'))
+ orgId: String(localStorage.getItem("orgData.id")),
});
setCurrentOrg(currentOrg);
})();
}, []);
const closeApp = async () => {
- console.log('Logging out...');
+ console.log("Logging out...");
await logout();
- router.push('/login');
+ router.push("/login");
};
return (
@@ -119,18 +123,17 @@ export default function Navbar() {
leaveTo="transform opacity-0 scale-95"
>
- {supportOptions.map((option) => (
- // eslint-disable-next-line react/jsx-no-target-blank
+ {supportOptionsList.map(([icon, text, url]) => (
- {option[0]}
-
{option[1]}
+ {icon}
+
{text}
))}
@@ -159,11 +162,11 @@ export default function Navbar() {
- SIGNED IN AS
+ {t("nav:user.signed-in-as")}
- router.push('/settings/personal/' + router.query.id)
+ router.push("/settings/personal/" + router.query.id)
}
className="flex flex-row items-center px-1 mx-1 my-1 hover:bg-white/5 cursor-pointer rounded-md"
>
@@ -173,11 +176,11 @@ export default function Navbar() {
- {' '}
+ {" "}
{user?.firstName} {user?.lastName}
- {' '}
+ {" "}
{user?.email}
@@ -190,11 +193,11 @@ export default function Navbar() {
- CURRENT ORGANIZATION
+ {t("nav:user.current-organization")}
- router.push('/settings/org/' + router.query.id)
+ router.push("/settings/org/" + router.query.id)
}
className="flex flex-row items-center px-2 mt-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
>
@@ -217,7 +220,7 @@ export default function Navbar() {
>
- router.push('/settings/billing/' + router.query.id)
+ router.push("/settings/billing/" + router.query.id)
}
className="mt-1 relative flex justify-start cursor-pointer select-none py-2 px-2 rounded-md text-gray-400 hover:bg-white/5 duration-200 hover:text-gray-200"
>
@@ -225,7 +228,7 @@ export default function Navbar() {
className="text-lg pl-1.5 pr-3"
icon={faCoins}
/>
-
Usage & Billing
+
{t("nav:user.usage-billing")}
router.push(
- '/settings/org/' + router.query.id + '?invite'
+ "/settings/org/" + router.query.id + "?invite"
)
}
className="relative flex justify-start cursor-pointer select-none py-2 pl-10 pr-4 rounded-md text-gray-400 hover:bg-primary/100 duration-200 hover:text-black hover:font-semibold mt-1"
@@ -243,26 +246,26 @@ export default function Navbar() {
- Invite Members
+ {t("nav:user.invite")}
{orgs?.length > 1 && (
- OTHER ORGANIZATIONS
+ {t("nav:user.other-organizations")}
{orgs
.filter(
(org: { _id: string }) =>
- org._id != localStorage.getItem('orgData.id')
+ org._id != localStorage.getItem("orgData.id")
)
.map((org: { _id: string; name: string }) => (
{
- localStorage.setItem('orgData.id', org._id);
+ localStorage.setItem("orgData.id", org._id);
router.reload();
}}
className="flex flex-row justify-start items-center hover:bg-white/5 w-full p-1.5 cursor-pointer rounded-md"
@@ -287,8 +290,8 @@ export default function Navbar() {
onClick={closeApp}
className={`${
active
- ? 'bg-red font-semibold text-white'
- : 'text-gray-400'
+ ? "bg-red font-semibold text-white"
+ : "text-gray-400"
} group flex w-full items-center rounded-md px-2 py-2 text-sm`}
>
@@ -296,7 +299,7 @@ export default function Navbar() {
className="text-lg ml-1.5 mr-3"
icon={faRightFromBracket}
/>
- Log Out
+ {t("common:logout")}
)}
diff --git a/frontend/components/utilities/withTranslateProps.ts b/frontend/components/utilities/withTranslateProps.ts
new file mode 100644
index 0000000000..6dc9abd108
--- /dev/null
+++ b/frontend/components/utilities/withTranslateProps.ts
@@ -0,0 +1,52 @@
+import { GetServerSideProps, GetStaticProps } from "next";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
+
+const DefaultNamespaces = ["common", "nav"];
+
+type GetTranslatedStaticProps = (
+ namespaces: string[],
+ getStaticProps?: GetStaticProps
+) => GetStaticProps;
+
+type GetTranslatedServerSideProps = (
+ namespaces: string[],
+ getStaticProps?: GetServerSideProps
+) => GetServerSideProps;
+
+export const getTranslatedStaticProps: GetTranslatedStaticProps =
+ (namespaces, getStaticProps) =>
+ async ({ locale, ...context }) => {
+ let staticProps = { props: {} };
+ if (typeof getStaticProps === "function") {
+ staticProps = (await getStaticProps(context)) as any;
+ }
+ return {
+ ...staticProps,
+ props: {
+ ...(await serverSideTranslations(locale ?? "en", [
+ ...DefaultNamespaces,
+ ...namespaces,
+ ])),
+ ...staticProps.props,
+ },
+ };
+ };
+
+export const getTranslatedServerSideProps: GetTranslatedServerSideProps =
+ (namespaces, getServerSideProps) =>
+ async ({ locale, ...context }) => {
+ let staticProps = { props: {} };
+ if (typeof getServerSideProps === "function") {
+ staticProps = (await getServerSideProps(context)) as any;
+ }
+ return {
+ ...staticProps,
+ props: {
+ ...(await serverSideTranslations(locale ?? "en", [
+ ...DefaultNamespaces,
+ ...namespaces,
+ ])),
+ ...staticProps.props,
+ },
+ };
+ };
diff --git a/frontend/const.js b/frontend/const.js
index f3b285e438..195d54a6de 100644
--- a/frontend/const.js
+++ b/frontend/const.js
@@ -16,3 +16,8 @@ export const publicPaths = [
`/terms`,
`/subprocessors`,
];
+
+export const languageMap = {
+ en: "English",
+ ko: "한국어",
+};
diff --git a/frontend/next-i18next.config.js b/frontend/next-i18next.config.js
new file mode 100644
index 0000000000..5de9c97bc7
--- /dev/null
+++ b/frontend/next-i18next.config.js
@@ -0,0 +1,26 @@
+// @ts-check
+
+/**
+ * @type {import('next-i18next').UserConfig}
+ */
+module.exports = {
+ // https://www.i18next.com/overview/configuration-options#logging
+ debug: process.env.NODE_ENV === "development",
+ i18n: {
+ defaultLocale: "en",
+ locales: ["en", "ko"],
+ },
+ fallbackLng: {
+ default: ["en"],
+ },
+
+ reloadOnPrerender: process.env.NODE_ENV === "development",
+
+ /**
+ * @link https://github.com/i18next/next-i18next#6-advanced-configuration
+ */
+ // saveMissing: false,
+ // strictMode: true,
+ // serializeConfig: false,
+ // react: { useSuspense: false }
+};
diff --git a/frontend/next.config.js b/frontend/next.config.js
index 2ae4b76413..0b87ee4c76 100644
--- a/frontend/next.config.js
+++ b/frontend/next.config.js
@@ -1,4 +1,4 @@
-// next.config.js
+const { i18n } = require("./next-i18next.config.js");
const ContentSecurityPolicy = `
default-src 'self';
@@ -60,4 +60,8 @@ module.exports = {
},
];
},
+ webpack: (config, { isServer, webpack }) => {
+ return config;
+ },
+ i18n,
};
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index b65657f854..69a4ecbef6 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -24,10 +24,12 @@
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
+ "i18next": "^22.4.6",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.2.5",
+ "next-i18next": "^13.0.2",
"posthog-js": "^1.34.0",
"query-string": "^7.1.3",
"react": "^17.0.2",
@@ -36,6 +38,7 @@
"react-dom": "^17.0.2",
"react-github-btn": "^1.4.0",
"react-grid-layout": "^1.3.4",
+ "react-i18next": "^12.1.1",
"react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3",
"react-redux": "^8.0.2",
@@ -393,11 +396,11 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
- "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
+ "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"dependencies": {
- "regenerator-runtime": "^0.13.4"
+ "regenerator-runtime": "^0.13.11"
},
"engines": {
"node": ">=6.9.0"
@@ -1368,14 +1371,14 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "5.45.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz",
- "integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==",
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz",
+ "integrity": "sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "5.45.0",
- "@typescript-eslint/type-utils": "5.45.0",
- "@typescript-eslint/utils": "5.45.0",
+ "@typescript-eslint/scope-manager": "5.47.1",
+ "@typescript-eslint/type-utils": "5.47.1",
+ "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -1400,6 +1403,53 @@
}
}
},
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
+ "integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
+ "integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
+ "integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1491,13 +1541,13 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "5.45.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz",
- "integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==",
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz",
+ "integrity": "sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w==",
"dev": true,
"dependencies": {
- "@typescript-eslint/typescript-estree": "5.45.0",
- "@typescript-eslint/utils": "5.45.0",
+ "@typescript-eslint/typescript-estree": "5.47.1",
+ "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@@ -1517,6 +1567,63 @@
}
}
},
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
+ "integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
+ "integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
+ "integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
"node_modules/@typescript-eslint/type-utils/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1604,16 +1711,16 @@
"dev": true
},
"node_modules/@typescript-eslint/utils": {
- "version": "5.45.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz",
- "integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==",
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.47.1.tgz",
+ "integrity": "sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
- "@typescript-eslint/scope-manager": "5.45.0",
- "@typescript-eslint/types": "5.45.0",
- "@typescript-eslint/typescript-estree": "5.45.0",
+ "@typescript-eslint/scope-manager": "5.47.1",
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/typescript-estree": "5.47.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
@@ -1629,6 +1736,97 @@
"eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
+ "integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
+ "integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
+ "integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
+ "integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.47.1",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -1651,6 +1849,12 @@
"node": ">=4.0"
}
},
+ "node_modules/@typescript-eslint/utils/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.45.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz",
@@ -2401,7 +2605,6 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
"integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==",
"hasInstallScript": true,
- "optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
@@ -4051,6 +4254,14 @@
"react-is": "^16.7.0"
}
},
+ "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-tokenize": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz",
@@ -4113,6 +4324,33 @@
"node": ">=8.0.0"
}
},
+ "node_modules/i18next": {
+ "version": "22.4.6",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
+ "integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
+ "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"
+ }
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.20.6"
+ }
+ },
+ "node_modules/i18next-fs-backend": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz",
+ "integrity": "sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA=="
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -5500,6 +5738,45 @@
}
}
},
+ "node_modules/next-i18next": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-13.0.2.tgz",
+ "integrity": "sha512-aUHyKT2kztMgEP44zDB5KoW8XZUQawIdOYWXcrMH6lxAcS0kBsKX0uKMzGS5XlgLW88gvOVc3D7NdfCznLgyyg==",
+ "funding": [
+ {
+ "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"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/belgattitude"
+ }
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.20.6",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "core-js": "^3",
+ "hoist-non-react-statics": "^3.3.2",
+ "i18next-fs-backend": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "i18next": "^22.0.6",
+ "next": ">= 12.0.0",
+ "react": ">= 17.0.2",
+ "react-i18next": "^12.1.1"
+ }
+ },
"node_modules/node-abi": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz",
@@ -6295,6 +6572,27 @@
"react-dom": ">= 16.3.0"
}
},
+ "node_modules/react-i18next": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz",
+ "integrity": "sha512-mFdieOI0LDy84q3JuZU6Aou1DoWW2fhapcTGeBS8+vWSJuViuoCLQAMYSb0QoHhXS8B0WKUOPpx4cffAP7r/aA==",
+ "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",
@@ -7666,6 +7964,14 @@
"url": "https://opencollective.com/unified"
}
},
+ "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/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7986,11 +8292,11 @@
}
},
"@babel/runtime": {
- "version": "7.18.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
- "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
+ "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
"requires": {
- "regenerator-runtime": "^0.13.4"
+ "regenerator-runtime": "^0.13.11"
}
},
"@babel/runtime-corejs3": {
@@ -8690,14 +8996,14 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"@typescript-eslint/eslint-plugin": {
- "version": "5.45.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz",
- "integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==",
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.47.1.tgz",
+ "integrity": "sha512-r4RZ2Jl9kcQN7K/dcOT+J7NAimbiis4sSM9spvWimsBvDegMhKLA5vri2jG19PmIPbDjPeWzfUPQ2hjEzA4Nmg==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "5.45.0",
- "@typescript-eslint/type-utils": "5.45.0",
- "@typescript-eslint/utils": "5.45.0",
+ "@typescript-eslint/scope-manager": "5.47.1",
+ "@typescript-eslint/type-utils": "5.47.1",
+ "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4",
"ignore": "^5.2.0",
"natural-compare-lite": "^1.4.0",
@@ -8706,6 +9012,32 @@
"tsutils": "^3.21.0"
},
"dependencies": {
+ "@typescript-eslint/scope-manager": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
+ "integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
+ "integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
+ "dev": true
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
+ "integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -8763,17 +9095,48 @@
}
},
"@typescript-eslint/type-utils": {
- "version": "5.45.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz",
- "integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==",
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.47.1.tgz",
+ "integrity": "sha512-/UKOeo8ee80A7/GJA427oIrBi/Gd4osk/3auBUg4Rn9EahFpevVV1mUK8hjyQD5lHPqX397x6CwOk5WGh1E/1w==",
"dev": true,
"requires": {
- "@typescript-eslint/typescript-estree": "5.45.0",
- "@typescript-eslint/utils": "5.45.0",
+ "@typescript-eslint/typescript-estree": "5.47.1",
+ "@typescript-eslint/utils": "5.47.1",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
"dependencies": {
+ "@typescript-eslint/types": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
+ "integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
+ "integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
+ "integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -8830,21 +9193,71 @@
}
},
"@typescript-eslint/utils": {
- "version": "5.45.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz",
- "integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==",
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.47.1.tgz",
+ "integrity": "sha512-l90SdwqfmkuIVaREZ2ykEfCezepCLxzWMo5gVfcJsJCaT4jHT+QjgSkYhs5BMQmWqE9k3AtIfk4g211z/sTMVw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
- "@typescript-eslint/scope-manager": "5.45.0",
- "@typescript-eslint/types": "5.45.0",
- "@typescript-eslint/typescript-estree": "5.45.0",
+ "@typescript-eslint/scope-manager": "5.47.1",
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/typescript-estree": "5.47.1",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0",
"semver": "^7.3.7"
},
"dependencies": {
+ "@typescript-eslint/scope-manager": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.1.tgz",
+ "integrity": "sha512-9hsFDsgUwrdOoW1D97Ewog7DYSHaq4WKuNs0LHF9RiCmqB0Z+XRR4Pf7u7u9z/8CciHuJ6yxNws1XznI3ddjEw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.1.tgz",
+ "integrity": "sha512-CmALY9YWXEpwuu6377ybJBZdtSAnzXLSQcxLSqSQSbC7VfpMu/HLVdrnVJj7ycI138EHqocW02LPJErE35cE9A==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.1.tgz",
+ "integrity": "sha512-4+ZhFSuISAvRi2xUszEj0xXbNTHceV9GbH9S8oAD2a/F9SW57aJNQVOCxG8GPfSWH/X4eOPdMEU2jYVuWKEpWA==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "@typescript-eslint/visitor-keys": "5.47.1",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "5.47.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.1.tgz",
+ "integrity": "sha512-rF3pmut2JCCjh6BLRhNKdYjULMb1brvoaiWDlHfLNVgmnZ0sBVJrs3SyaKE1XoDDnJuAx/hDQryHYmPUuNq0ig==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.47.1",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -8860,6 +9273,12 @@
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
}
}
},
@@ -9400,8 +9819,7 @@
"core-js": {
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.0.tgz",
- "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw==",
- "optional": true
+ "integrity": "sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw=="
},
"core-js-pure": {
"version": "3.26.1",
@@ -10644,6 +11062,14 @@
"react-is": "^16.7.0"
}
},
+ "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-tokenize": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz",
@@ -10699,6 +11125,19 @@
"requires-port": "^1.0.0"
}
},
+ "i18next": {
+ "version": "22.4.6",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
+ "integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
+ "requires": {
+ "@babel/runtime": "^7.20.6"
+ }
+ },
+ "i18next-fs-backend": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.1.1.tgz",
+ "integrity": "sha512-FTnj+UmNgT3YRml5ruRv0jMZDG7odOL/OP5PF5mOqvXud2vHrPOOs68Zdk6iqzL47cnnM0ZVkK2BAvpFeDJToA=="
+ },
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -11600,6 +12039,18 @@
"use-sync-external-store": "1.2.0"
}
},
+ "next-i18next": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/next-i18next/-/next-i18next-13.0.2.tgz",
+ "integrity": "sha512-aUHyKT2kztMgEP44zDB5KoW8XZUQawIdOYWXcrMH6lxAcS0kBsKX0uKMzGS5XlgLW88gvOVc3D7NdfCznLgyyg==",
+ "requires": {
+ "@babel/runtime": "^7.20.6",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "core-js": "^3",
+ "hoist-non-react-statics": "^3.3.2",
+ "i18next-fs-backend": "^2.1.0"
+ }
+ },
"node-abi": {
"version": "3.30.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.30.0.tgz",
@@ -12156,6 +12607,15 @@
"react-resizable": "^3.0.4"
}
},
+ "react-i18next": {
+ "version": "12.1.1",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.1.tgz",
+ "integrity": "sha512-mFdieOI0LDy84q3JuZU6Aou1DoWW2fhapcTGeBS8+vWSJuViuoCLQAMYSb0QoHhXS8B0WKUOPpx4cffAP7r/aA==",
+ "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",
@@ -13126,6 +13586,11 @@
"unist-util-stringify-position": "^3.0.0"
}
},
+ "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=="
+ },
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 12b2e9ec27..73a42518ca 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -28,10 +28,12 @@
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
+ "i18next": "^22.4.6",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.2.5",
+ "next-i18next": "^13.0.2",
"posthog-js": "^1.34.0",
"query-string": "^7.1.3",
"react": "^17.0.2",
@@ -40,6 +42,7 @@
"react-dom": "^17.0.2",
"react-github-btn": "^1.4.0",
"react-grid-layout": "^1.3.4",
+ "react-i18next": "^12.1.1",
"react-mailchimp-subscribe": "^2.1.3",
"react-markdown": "^8.0.3",
"react-redux": "^8.0.2",
diff --git a/frontend/pages/_app.js b/frontend/pages/_app.js
index aeb2050ccb..54ed7d5121 100644
--- a/frontend/pages/_app.js
+++ b/frontend/pages/_app.js
@@ -1,5 +1,6 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
+import { appWithTranslation } from "next-i18next";
import { config } from "@fortawesome/fontawesome-svg-core";
import Layout from "~/components/basic/Layout";
@@ -16,6 +17,15 @@ config.autoAddCss = false;
const App = ({ Component, pageProps, ...appProps }) => {
const router = useRouter();
+ useEffect(() => {
+ const storedLang = localStorage.getItem("lang");
+ if (router.locale ?? "en" !== storedLang ?? "en") {
+ router.push(router.asPath, router.asPath, {
+ locale: storedLang ?? "en",
+ });
+ }
+ }, [router.locale, router.pathname]);
+
useEffect(() => {
// Init for auto capturing
const telemetry = new Telemetry().getInstance();
@@ -52,7 +62,7 @@ const App = ({ Component, pageProps, ...appProps }) => {
);
};
-export default App;
+export default appWithTranslation(App);
{
/*