mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-14 19:45:02 +00:00
React translations
This commit is contained in:
parent
5f862f55d9
commit
09f05ac8d0
2
frontend/dist/assets/browser-ponyfill-CwETHAJL.js
vendored
Normal file
2
frontend/dist/assets/browser-ponyfill-CwETHAJL.js
vendored
Normal file
File diff suppressed because one or more lines are too long
99
frontend/dist/assets/index-C7ZkpjP3.js
vendored
Normal file
99
frontend/dist/assets/index-C7ZkpjP3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
99
frontend/dist/assets/index-DAILjWej.js
vendored
99
frontend/dist/assets/index-DAILjWej.js
vendored
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -13,8 +13,8 @@
|
|||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
|
||||||
<title>Vite App</title>
|
<title>Vite App</title>
|
||||||
<script type="module" crossorigin src="/assets/index-DAILjWej.js"></script>
|
<script type="module" crossorigin src="/assets/index-C7ZkpjP3.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DxdJ27yt.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CEZrmp6h.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
1561
frontend/dist/locales/ar-AR/translation.json
vendored
Normal file
1561
frontend/dist/locales/ar-AR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/az-AZ/translation.json
vendored
Normal file
1561
frontend/dist/locales/az-AZ/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/bg-BG/translation.json
vendored
Normal file
1561
frontend/dist/locales/bg-BG/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ca-CA/translation.json
vendored
Normal file
1561
frontend/dist/locales/ca-CA/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/cs-CZ/translation.json
vendored
Normal file
1561
frontend/dist/locales/cs-CZ/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/da-DK/translation.json
vendored
Normal file
1561
frontend/dist/locales/da-DK/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/de-DE/translation.json
vendored
Normal file
1561
frontend/dist/locales/de-DE/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/el-GR/translation.json
vendored
Normal file
1561
frontend/dist/locales/el-GR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1620
frontend/dist/locales/en-GB/translation.json
vendored
Normal file
1620
frontend/dist/locales/en-GB/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/en-US/translation.json
vendored
Normal file
1561
frontend/dist/locales/en-US/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/dist/locales/en/translation.json
vendored
Normal file
1
frontend/dist/locales/en/translation.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
1561
frontend/dist/locales/es-ES/translation.json
vendored
Normal file
1561
frontend/dist/locales/es-ES/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/eu-ES/translation.json
vendored
Normal file
1561
frontend/dist/locales/eu-ES/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/fa-IR/translation.json
vendored
Normal file
1561
frontend/dist/locales/fa-IR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/fr-FR/translation.json
vendored
Normal file
1561
frontend/dist/locales/fr-FR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ga-IE/translation.json
vendored
Normal file
1561
frontend/dist/locales/ga-IE/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/hi-IN/translation.json
vendored
Normal file
1561
frontend/dist/locales/hi-IN/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/hr-HR/translation.json
vendored
Normal file
1561
frontend/dist/locales/hr-HR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/hu-HU/translation.json
vendored
Normal file
1561
frontend/dist/locales/hu-HU/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/id-ID/translation.json
vendored
Normal file
1561
frontend/dist/locales/id-ID/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/it-IT/translation.json
vendored
Normal file
1561
frontend/dist/locales/it-IT/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ja-JP/translation.json
vendored
Normal file
1561
frontend/dist/locales/ja-JP/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ko-KR/translation.json
vendored
Normal file
1561
frontend/dist/locales/ko-KR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ml-ML/translation.json
vendored
Normal file
1561
frontend/dist/locales/ml-ML/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/nl-NL/translation.json
vendored
Normal file
1561
frontend/dist/locales/nl-NL/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/no-NB/translation.json
vendored
Normal file
1561
frontend/dist/locales/no-NB/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/pl-PL/translation.json
vendored
Normal file
1561
frontend/dist/locales/pl-PL/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/pt-BR/translation.json
vendored
Normal file
1561
frontend/dist/locales/pt-BR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/pt-PT/translation.json
vendored
Normal file
1561
frontend/dist/locales/pt-PT/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ro-RO/translation.json
vendored
Normal file
1561
frontend/dist/locales/ro-RO/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/ru-RU/translation.json
vendored
Normal file
1561
frontend/dist/locales/ru-RU/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/sk-SK/translation.json
vendored
Normal file
1561
frontend/dist/locales/sk-SK/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/sl-SI/translation.json
vendored
Normal file
1561
frontend/dist/locales/sl-SI/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/sr-LATN-RS/translation.json
vendored
Normal file
1561
frontend/dist/locales/sr-LATN-RS/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/sv-SE/translation.json
vendored
Normal file
1561
frontend/dist/locales/sv-SE/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/th-TH/translation.json
vendored
Normal file
1561
frontend/dist/locales/th-TH/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/tr-TR/translation.json
vendored
Normal file
1561
frontend/dist/locales/tr-TR/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/uk-UA/translation.json
vendored
Normal file
1561
frontend/dist/locales/uk-UA/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/vi-VN/translation.json
vendored
Normal file
1561
frontend/dist/locales/vi-VN/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/zh-BO/translation.json
vendored
Normal file
1561
frontend/dist/locales/zh-BO/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/zh-CN/translation.json
vendored
Normal file
1561
frontend/dist/locales/zh-CN/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/dist/locales/zh-TW/translation.json
vendored
Normal file
1561
frontend/dist/locales/zh-TW/translation.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
109
frontend/package-lock.json
generated
109
frontend/package-lock.json
generated
@ -22,9 +22,13 @@
|
|||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"i18next": "^25.2.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-i18next": "^15.5.2",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.8",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
@ -2693,6 +2697,14 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.6.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css.escape": {
|
"node_modules/css.escape": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||||
@ -3353,6 +3365,14 @@
|
|||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"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/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
@ -3367,6 +3387,52 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18next": {
|
||||||
|
"version": "25.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz",
|
||||||
|
"integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==",
|
||||||
|
"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.27.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-browser-languagedetector": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.23.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/i18next-http-backend": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||||
@ -4036,7 +4102,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"whatwg-url": "^5.0.0"
|
"whatwg-url": "^5.0.0"
|
||||||
},
|
},
|
||||||
@ -4056,22 +4121,19 @@
|
|||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/node-fetch/node_modules/webidl-conversions": {
|
"node_modules/node-fetch/node_modules/webidl-conversions": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause"
|
||||||
"optional": true
|
|
||||||
},
|
},
|
||||||
"node_modules/node-fetch/node_modules/whatwg-url": {
|
"node_modules/node-fetch/node_modules/whatwg-url": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "~0.0.3",
|
"tr46": "~0.0.3",
|
||||||
"webidl-conversions": "^3.0.0"
|
"webidl-conversions": "^3.0.0"
|
||||||
@ -4711,6 +4773,31 @@
|
|||||||
"react": ">= 16.8 || 18.0.0"
|
"react": ">= 16.8 || 18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-i18next": {
|
||||||
|
"version": "15.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz",
|
||||||
|
"integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.25.0",
|
||||||
|
"html-parse-stringify": "^3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"i18next": ">= 23.2.3",
|
||||||
|
"react": ">= 16.8.0",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
|
||||||
@ -5430,7 +5517,7 @@
|
|||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@ -5678,6 +5765,14 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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/web-vitals": {
|
"node_modules/web-vitals": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz",
|
||||||
|
@ -18,9 +18,13 @@
|
|||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
|
"i18next": "^25.2.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"pdfjs-dist": "^3.11.174",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-i18next": "^15.5.2",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
"tailwindcss": "^4.1.8",
|
"tailwindcss": "^4.1.8",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
|
1561
frontend/public/locales/ar-AR/translation.json
Normal file
1561
frontend/public/locales/ar-AR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/az-AZ/translation.json
Normal file
1561
frontend/public/locales/az-AZ/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/bg-BG/translation.json
Normal file
1561
frontend/public/locales/bg-BG/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ca-CA/translation.json
Normal file
1561
frontend/public/locales/ca-CA/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/cs-CZ/translation.json
Normal file
1561
frontend/public/locales/cs-CZ/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/da-DK/translation.json
Normal file
1561
frontend/public/locales/da-DK/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/de-DE/translation.json
Normal file
1561
frontend/public/locales/de-DE/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/el-GR/translation.json
Normal file
1561
frontend/public/locales/el-GR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1620
frontend/public/locales/en-GB/translation.json
Normal file
1620
frontend/public/locales/en-GB/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/en-US/translation.json
Normal file
1561
frontend/public/locales/en-US/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1
frontend/public/locales/en/translation.json
Normal file
1
frontend/public/locales/en/translation.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
1561
frontend/public/locales/es-ES/translation.json
Normal file
1561
frontend/public/locales/es-ES/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/eu-ES/translation.json
Normal file
1561
frontend/public/locales/eu-ES/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/fa-IR/translation.json
Normal file
1561
frontend/public/locales/fa-IR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/fr-FR/translation.json
Normal file
1561
frontend/public/locales/fr-FR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ga-IE/translation.json
Normal file
1561
frontend/public/locales/ga-IE/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/hi-IN/translation.json
Normal file
1561
frontend/public/locales/hi-IN/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/hr-HR/translation.json
Normal file
1561
frontend/public/locales/hr-HR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/hu-HU/translation.json
Normal file
1561
frontend/public/locales/hu-HU/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/id-ID/translation.json
Normal file
1561
frontend/public/locales/id-ID/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/it-IT/translation.json
Normal file
1561
frontend/public/locales/it-IT/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ja-JP/translation.json
Normal file
1561
frontend/public/locales/ja-JP/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ko-KR/translation.json
Normal file
1561
frontend/public/locales/ko-KR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ml-ML/translation.json
Normal file
1561
frontend/public/locales/ml-ML/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/nl-NL/translation.json
Normal file
1561
frontend/public/locales/nl-NL/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/no-NB/translation.json
Normal file
1561
frontend/public/locales/no-NB/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/pl-PL/translation.json
Normal file
1561
frontend/public/locales/pl-PL/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/pt-BR/translation.json
Normal file
1561
frontend/public/locales/pt-BR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/pt-PT/translation.json
Normal file
1561
frontend/public/locales/pt-PT/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ro-RO/translation.json
Normal file
1561
frontend/public/locales/ro-RO/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/ru-RU/translation.json
Normal file
1561
frontend/public/locales/ru-RU/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/sk-SK/translation.json
Normal file
1561
frontend/public/locales/sk-SK/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/sl-SI/translation.json
Normal file
1561
frontend/public/locales/sl-SI/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/sr-LATN-RS/translation.json
Normal file
1561
frontend/public/locales/sr-LATN-RS/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/sv-SE/translation.json
Normal file
1561
frontend/public/locales/sv-SE/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/th-TH/translation.json
Normal file
1561
frontend/public/locales/th-TH/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/tr-TR/translation.json
Normal file
1561
frontend/public/locales/tr-TR/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/uk-UA/translation.json
Normal file
1561
frontend/public/locales/uk-UA/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/vi-VN/translation.json
Normal file
1561
frontend/public/locales/vi-VN/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/zh-BO/translation.json
Normal file
1561
frontend/public/locales/zh-BO/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/zh-CN/translation.json
Normal file
1561
frontend/public/locales/zh-CN/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
1561
frontend/public/locales/zh-TW/translation.json
Normal file
1561
frontend/public/locales/zh-TW/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { Card, Group, Text, Stack, Image, Badge, Button, Box, Flex, ThemeIcon } from "@mantine/core";
|
import { Card, Group, Text, Stack, Image, Badge, Button, Box, Flex, ThemeIcon } from "@mantine/core";
|
||||||
import { Dropzone, MIME_TYPES } from "@mantine/dropzone";
|
import { Dropzone, MIME_TYPES } from "@mantine/dropzone";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
||||||
|
|
||||||
import { GlobalWorkerOptions } from "pdfjs-dist";
|
import { GlobalWorkerOptions, getDocument } from "pdfjs-dist";
|
||||||
GlobalWorkerOptions.workerSrc = "/pdf.worker.js";
|
GlobalWorkerOptions.workerSrc = "/pdf.worker.js";
|
||||||
|
|
||||||
export interface FileWithUrl extends File {
|
export interface FileWithUrl extends File {
|
||||||
@ -63,6 +64,7 @@ interface FileCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FileCard({ file, onRemove, onDoubleClick }: FileCardProps) {
|
function FileCard({ file, onRemove, onDoubleClick }: FileCardProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const thumb = usePdfThumbnail(file);
|
const thumb = usePdfThumbnail(file);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -120,7 +122,7 @@ function FileCard({ file, onRemove, onDoubleClick }: FileCardProps) {
|
|||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
mt={4}
|
mt={4}
|
||||||
>
|
>
|
||||||
Remove
|
{t("delete", "Remove")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
@ -142,6 +144,7 @@ const FileManager: React.FC<FileManagerProps> = ({
|
|||||||
setPdfFile,
|
setPdfFile,
|
||||||
setCurrentView,
|
setCurrentView,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleDrop = (uploadedFiles: File[]) => {
|
const handleDrop = (uploadedFiles: File[]) => {
|
||||||
setFiles((prevFiles) => (allowMultiple ? [...prevFiles, ...uploadedFiles] : uploadedFiles));
|
setFiles((prevFiles) => (allowMultiple ? [...prevFiles, ...uploadedFiles] : uploadedFiles));
|
||||||
};
|
};
|
||||||
@ -171,13 +174,13 @@ const FileManager: React.FC<FileManagerProps> = ({
|
|||||||
>
|
>
|
||||||
<Group justify="center" gap="xl" style={{ pointerEvents: "none" }}>
|
<Group justify="center" gap="xl" style={{ pointerEvents: "none" }}>
|
||||||
<Text size="md">
|
<Text size="md">
|
||||||
Drag PDF files here or click to select
|
{t("fileChooser.dragAndDropPDF", "Drag PDF files here or click to select")}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Dropzone>
|
</Dropzone>
|
||||||
{files.length === 0 ? (
|
{files.length === 0 ? (
|
||||||
<Text c="dimmed" ta="center">
|
<Text c="dimmed" ta="center">
|
||||||
No files uploaded yet.
|
{t("noFileSelected", "No files uploaded yet.")}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Box>
|
<Box>
|
||||||
|
71
frontend/src/components/LanguageSelector.module.css
Normal file
71
frontend/src/components/LanguageSelector.module.css
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* Language selector grid responsive layout */
|
||||||
|
.languageGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageItem {
|
||||||
|
border-right: 2px solid var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageItem:nth-child(4n) {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive breakpoints */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.languageGrid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageItem:nth-child(4n) {
|
||||||
|
border-right: 2px solid var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageItem:nth-child(2n) {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 601px) and (max-width: 900px) {
|
||||||
|
.languageGrid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageItem:nth-child(4n) {
|
||||||
|
border-right: 2px solid var(--mantine-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.languageItem:nth-child(3n) {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark theme support */
|
||||||
|
[data-mantine-color-scheme="dark"] .languageItem {
|
||||||
|
border-right-color: var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .languageItem:nth-child(4n) {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .languageItem:nth-child(2n) {
|
||||||
|
border-right-color: var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-mantine-color-scheme="dark"] .languageItem:nth-child(3n) {
|
||||||
|
border-right-color: var(--mantine-color-dark-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive text visibility */
|
||||||
|
.languageText {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.languageText {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
125
frontend/src/components/LanguageSelector.tsx
Normal file
125
frontend/src/components/LanguageSelector.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Menu, Button, ScrollArea, useMantineTheme, useMantineColorScheme } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { supportedLanguages } from '../i18n';
|
||||||
|
import LanguageIcon from '@mui/icons-material/Language';
|
||||||
|
import styles from './LanguageSelector.module.css';
|
||||||
|
|
||||||
|
const LanguageSelector: React.FC = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
|
||||||
|
const languageOptions = Object.entries(supportedLanguages)
|
||||||
|
.sort(([, nameA], [, nameB]) => nameA.localeCompare(nameB))
|
||||||
|
.map(([code, name]) => ({
|
||||||
|
value: code,
|
||||||
|
label: name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const handleLanguageChange = (value: string) => {
|
||||||
|
i18n.changeLanguage(value);
|
||||||
|
setOpened(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentLanguage = supportedLanguages[i18n.language as keyof typeof supportedLanguages] ||
|
||||||
|
supportedLanguages['en-GB'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
opened={opened}
|
||||||
|
onChange={setOpened}
|
||||||
|
width={600}
|
||||||
|
position="bottom-start"
|
||||||
|
offset={8}
|
||||||
|
>
|
||||||
|
<Menu.Target>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
size="sm"
|
||||||
|
leftSection={<LanguageIcon style={{ fontSize: 18 }} />}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
border: 'none',
|
||||||
|
color: colorScheme === 'dark' ? theme.colors.gray[3] : theme.colors.gray[7],
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 500,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className={styles.languageText}>
|
||||||
|
{currentLanguage}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</Menu.Target>
|
||||||
|
|
||||||
|
<Menu.Dropdown
|
||||||
|
style={{
|
||||||
|
padding: '12px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
|
||||||
|
border: colorScheme === 'dark' ? `1px solid ${theme.colors.dark[4]}` : `1px solid ${theme.colors.gray[3]}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScrollArea h={190} type="scroll">
|
||||||
|
<div className={styles.languageGrid}>
|
||||||
|
{languageOptions.map((option) => (
|
||||||
|
<div
|
||||||
|
key={option.value}
|
||||||
|
className={styles.languageItem}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
size="sm"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => handleLanguageChange(option.value)}
|
||||||
|
styles={{
|
||||||
|
root: {
|
||||||
|
borderRadius: '4px',
|
||||||
|
minHeight: '32px',
|
||||||
|
padding: '4px 8px',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
backgroundColor: option.value === i18n.language ? (
|
||||||
|
colorScheme === 'dark' ? theme.colors.blue[8] : theme.colors.blue[1]
|
||||||
|
) : 'transparent',
|
||||||
|
color: option.value === i18n.language ? (
|
||||||
|
colorScheme === 'dark' ? theme.colors.blue[2] : theme.colors.blue[7]
|
||||||
|
) : (
|
||||||
|
colorScheme === 'dark' ? theme.colors.gray[3] : theme.colors.gray[7]
|
||||||
|
),
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: option.value === i18n.language ? (
|
||||||
|
colorScheme === 'dark' ? theme.colors.blue[7] : theme.colors.blue[2]
|
||||||
|
) : (
|
||||||
|
colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: '13px',
|
||||||
|
fontWeight: option.value === i18n.language ? 600 : 400,
|
||||||
|
textAlign: 'left',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LanguageSelector;
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||||||
import {
|
import {
|
||||||
Paper, Button, Group, Text, Stack, Center, Checkbox, ScrollArea, Box, Tooltip, ActionIcon, Notification
|
Paper, Button, Group, Text, Stack, Center, Checkbox, ScrollArea, Box, Tooltip, ActionIcon, Notification
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import UndoIcon from "@mui/icons-material/Undo";
|
import UndoIcon from "@mui/icons-material/Undo";
|
||||||
import RedoIcon from "@mui/icons-material/Redo";
|
import RedoIcon from "@mui/icons-material/Redo";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
@ -28,6 +29,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
downloadUrl,
|
downloadUrl,
|
||||||
setDownloadUrl,
|
setDownloadUrl,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [selectedPages, setSelectedPages] = useState<number[]>([]);
|
const [selectedPages, setSelectedPages] = useState<number[]>([]);
|
||||||
const [status, setStatus] = useState<string | null>(null);
|
const [status, setStatus] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -61,20 +63,20 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Example action handlers (replace with real API calls)
|
// Example action handlers (replace with real API calls)
|
||||||
const handleRotateLeft = () => setStatus("Rotated left: " + selectedPages.join(", "));
|
const handleRotateLeft = () => setStatus(t("pageEditor.rotatedLeft", "Rotated left: ") + selectedPages.join(", "));
|
||||||
const handleRotateRight = () => setStatus("Rotated right: " + selectedPages.join(", "));
|
const handleRotateRight = () => setStatus(t("pageEditor.rotatedRight", "Rotated right: ") + selectedPages.join(", "));
|
||||||
const handleDelete = () => setStatus("Deleted: " + selectedPages.join(", "));
|
const handleDelete = () => setStatus(t("pageEditor.deleted", "Deleted: ") + selectedPages.join(", "));
|
||||||
const handleMoveLeft = () => setStatus("Moved left: " + selectedPages.join(", "));
|
const handleMoveLeft = () => setStatus(t("pageEditor.movedLeft", "Moved left: ") + selectedPages.join(", "));
|
||||||
const handleMoveRight = () => setStatus("Moved right: " + selectedPages.join(", "));
|
const handleMoveRight = () => setStatus(t("pageEditor.movedRight", "Moved right: ") + selectedPages.join(", "));
|
||||||
const handleSplit = () => setStatus("Split at: " + selectedPages.join(", "));
|
const handleSplit = () => setStatus(t("pageEditor.splitAt", "Split at: ") + selectedPages.join(", "));
|
||||||
const handleInsertPageBreak = () => setStatus("Inserted page break at: " + selectedPages.join(", "));
|
const handleInsertPageBreak = () => setStatus(t("pageEditor.insertedPageBreak", "Inserted page break at: ") + selectedPages.join(", "));
|
||||||
const handleAddFile = () => setStatus("Add file not implemented in demo");
|
const handleAddFile = () => setStatus(t("pageEditor.addFileNotImplemented", "Add file not implemented in demo"));
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return (
|
return (
|
||||||
<Paper shadow="xs" radius="md" p="md">
|
<Paper shadow="xs" radius="md" p="md">
|
||||||
<Center>
|
<Center>
|
||||||
<Text color="dimmed">No PDF loaded. Please upload a PDF to edit.</Text>
|
<Text color="dimmed">{t("pageEditor.noPdfLoaded", "No PDF loaded. Please upload a PDF to edit.")}</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
@ -85,14 +87,14 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
<Group align="flex-start" gap="lg">
|
<Group align="flex-start" gap="lg">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<Stack w={180} gap="xs">
|
<Stack w={180} gap="xs">
|
||||||
<Text fw={600} size="lg">PDF Multitool</Text>
|
<Text fw={600} size="lg">{t("pageEditor.title", "PDF Multitool")}</Text>
|
||||||
<Button onClick={selectAll} fullWidth variant="light">Select All</Button>
|
<Button onClick={selectAll} fullWidth variant="light">{t("multiTool.selectAll", "Select All")}</Button>
|
||||||
<Button onClick={deselectAll} fullWidth variant="light">Deselect All</Button>
|
<Button onClick={deselectAll} fullWidth variant="light">{t("multiTool.deselectAll", "Deselect All")}</Button>
|
||||||
<Button onClick={handleUndo} leftSection={<UndoIcon fontSize="small" />} fullWidth disabled={undoStack.length === 0}>Undo</Button>
|
<Button onClick={handleUndo} leftSection={<UndoIcon fontSize="small" />} fullWidth disabled={undoStack.length === 0}>{t("multiTool.undo", "Undo")}</Button>
|
||||||
<Button onClick={handleRedo} leftSection={<RedoIcon fontSize="small" />} fullWidth disabled={redoStack.length === 0}>Redo</Button>
|
<Button onClick={handleRedo} leftSection={<RedoIcon fontSize="small" />} fullWidth disabled={redoStack.length === 0}>{t("multiTool.redo", "Redo")}</Button>
|
||||||
<Button onClick={handleAddFile} leftSection={<AddIcon fontSize="small" />} fullWidth>Add File</Button>
|
<Button onClick={handleAddFile} leftSection={<AddIcon fontSize="small" />} fullWidth>{t("multiTool.addFile", "Add File")}</Button>
|
||||||
<Button onClick={handleInsertPageBreak} leftSection={<ContentCutIcon fontSize="small" />} fullWidth>Insert Page Break</Button>
|
<Button onClick={handleInsertPageBreak} leftSection={<ContentCutIcon fontSize="small" />} fullWidth>{t("multiTool.insertPageBreak", "Insert Page Break")}</Button>
|
||||||
<Button onClick={handleSplit} leftSection={<ContentCutIcon fontSize="small" />} fullWidth>Split</Button>
|
<Button onClick={handleSplit} leftSection={<ContentCutIcon fontSize="small" />} fullWidth>{t("multiTool.split", "Split")}</Button>
|
||||||
<Button
|
<Button
|
||||||
component="a"
|
component="a"
|
||||||
href={downloadUrl || "#"}
|
href={downloadUrl || "#"}
|
||||||
@ -103,7 +105,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
variant="light"
|
variant="light"
|
||||||
disabled={!downloadUrl}
|
disabled={!downloadUrl}
|
||||||
>
|
>
|
||||||
Download All
|
{t("multiTool.downloadAll", "Download All")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
component="a"
|
component="a"
|
||||||
@ -115,7 +117,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
variant="light"
|
variant="light"
|
||||||
disabled={!downloadUrl || selectedPages.length === 0}
|
disabled={!downloadUrl || selectedPages.length === 0}
|
||||||
>
|
>
|
||||||
Download Selected
|
{t("multiTool.downloadSelected", "Download Selected")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="red"
|
color="red"
|
||||||
@ -123,34 +125,34 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
onClick={() => setFile && setFile(null)}
|
onClick={() => setFile && setFile(null)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
Close PDF
|
{t("pageEditor.closePdf", "Close PDF")}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Main multitool area */}
|
{/* Main multitool area */}
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Group mb="sm">
|
<Group mb="sm">
|
||||||
<Tooltip label="Rotate Left">
|
<Tooltip label={t("multiTool.rotateLeft", "Rotate Left")}>
|
||||||
<ActionIcon onClick={handleRotateLeft} disabled={selectedPages.length === 0} color="blue" variant="light">
|
<ActionIcon onClick={handleRotateLeft} disabled={selectedPages.length === 0} color="blue" variant="light">
|
||||||
<RotateLeftIcon />
|
<RotateLeftIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Rotate Right">
|
<Tooltip label={t("multiTool.rotateRight", "Rotate Right")}>
|
||||||
<ActionIcon onClick={handleRotateRight} disabled={selectedPages.length === 0} color="blue" variant="light">
|
<ActionIcon onClick={handleRotateRight} disabled={selectedPages.length === 0} color="blue" variant="light">
|
||||||
<RotateRightIcon />
|
<RotateRightIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Delete">
|
<Tooltip label={t("delete", "Delete")}>
|
||||||
<ActionIcon onClick={handleDelete} disabled={selectedPages.length === 0} color="red" variant="light">
|
<ActionIcon onClick={handleDelete} disabled={selectedPages.length === 0} color="red" variant="light">
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Move Left">
|
<Tooltip label={t("multiTool.moveLeft", "Move Left")}>
|
||||||
<ActionIcon onClick={handleMoveLeft} disabled={selectedPages.length === 0} color="gray" variant="light">
|
<ActionIcon onClick={handleMoveLeft} disabled={selectedPages.length === 0} color="gray" variant="light">
|
||||||
<ArrowBackIosNewIcon />
|
<ArrowBackIosNewIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label="Move Right">
|
<Tooltip label={t("multiTool.moveRight", "Move Right")}>
|
||||||
<ActionIcon onClick={handleMoveRight} disabled={selectedPages.length === 0} color="gray" variant="light">
|
<ActionIcon onClick={handleMoveRight} disabled={selectedPages.length === 0} color="gray" variant="light">
|
||||||
<ArrowForwardIosIcon />
|
<ArrowForwardIosIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
@ -163,7 +165,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedPages.includes(page)}
|
checked={selectedPages.includes(page)}
|
||||||
onChange={() => togglePage(page)}
|
onChange={() => togglePage(page)}
|
||||||
label={`Page ${page}`}
|
label={t("page", "Page") + ` ${page}`}
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
w={60}
|
w={60}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Box, Text, Stack, Button, TextInput, Group } from "@mantine/core";
|
import { Box, Text, Stack, Button, TextInput, Group } from "@mantine/core";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Tool = {
|
type Tool = {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
@ -17,6 +18,7 @@ interface ToolPickerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ToolPicker: React.FC<ToolPickerProps> = ({ selectedToolKey, onSelect, toolRegistry }) => {
|
const ToolPicker: React.FC<ToolPickerProps> = ({ selectedToolKey, onSelect, toolRegistry }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
const filteredTools = Object.entries(toolRegistry).filter(([_, { name }]) =>
|
const filteredTools = Object.entries(toolRegistry).filter(([_, { name }]) =>
|
||||||
@ -26,7 +28,7 @@ const ToolPicker: React.FC<ToolPickerProps> = ({ selectedToolKey, onSelect, tool
|
|||||||
return (
|
return (
|
||||||
<Box >
|
<Box >
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder="Search tools..."
|
placeholder={t("toolPicker.searchPlaceholder", "Search tools...")}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.currentTarget.value)}
|
onChange={(e) => setSearch(e.currentTarget.value)}
|
||||||
mb="md"
|
mb="md"
|
||||||
@ -35,7 +37,7 @@ const ToolPicker: React.FC<ToolPickerProps> = ({ selectedToolKey, onSelect, tool
|
|||||||
<Stack align="flex-start">
|
<Stack align="flex-start">
|
||||||
{filteredTools.length === 0 ? (
|
{filteredTools.length === 0 ? (
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
No tools found
|
{t("toolPicker.noToolsFound", "No tools found")}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
filteredTools.map(([id, { icon, name }]) => (
|
filteredTools.map(([id, { icon, name }]) => (
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useEffect, useState, useRef } from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import { Paper, Stack, Text, ScrollArea, Loader, Center, Button, Group, NumberInput, useMantineTheme } from "@mantine/core";
|
import { Paper, Stack, Text, ScrollArea, Loader, Center, Button, Group, NumberInput, useMantineTheme } from "@mantine/core";
|
||||||
import { getDocument, GlobalWorkerOptions } from "pdfjs-dist";
|
import { getDocument, GlobalWorkerOptions } from "pdfjs-dist";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew";
|
||||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||||
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
import FirstPageIcon from "@mui/icons-material/FirstPage";
|
||||||
@ -25,6 +26,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
sidebarsVisible,
|
sidebarsVisible,
|
||||||
setSidebarsVisible,
|
setSidebarsVisible,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const theme = useMantineTheme();
|
const theme = useMantineTheme();
|
||||||
const [numPages, setNumPages] = useState<number>(0);
|
const [numPages, setNumPages] = useState<number>(0);
|
||||||
const [pageImages, setPageImages] = useState<string[]>([]);
|
const [pageImages, setPageImages] = useState<string[]>([]);
|
||||||
@ -176,13 +178,13 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
{!pdfFile ? (
|
{!pdfFile ? (
|
||||||
<Center style={{ flex: 1 }}>
|
<Center style={{ flex: 1 }}>
|
||||||
<Stack align="center">
|
<Stack align="center">
|
||||||
<Text c="dimmed">No PDF loaded. Click to upload a PDF.</Text>
|
<Text c="dimmed">{t("viewer.noPdfLoaded", "No PDF loaded. Click to upload a PDF.")}</Text>
|
||||||
<Button
|
<Button
|
||||||
component="label"
|
component="label"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="blue"
|
color="blue"
|
||||||
>
|
>
|
||||||
Choose PDF
|
{t("viewer.choosePdf", "Choose PDF")}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="application/pdf"
|
accept="application/pdf"
|
||||||
@ -209,7 +211,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
>
|
>
|
||||||
<Stack gap="xl" align="center" >
|
<Stack gap="xl" align="center" >
|
||||||
{pageImages.length === 0 && (
|
{pageImages.length === 0 && (
|
||||||
<Text color="dimmed">No pages to display.</Text>
|
<Text color="dimmed">{t("viewer.noPagesToDisplay", "No pages to display.")}</Text>
|
||||||
)}
|
)}
|
||||||
{dualPage
|
{dualPage
|
||||||
? Array.from({ length: Math.ceil(pageImages.length / 2) }).map((_, i) => (
|
? Array.from({ length: Math.ceil(pageImages.length / 2) }).map((_, i) => (
|
||||||
@ -372,7 +374,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={() => setDualPage(v => !v)}
|
onClick={() => setDualPage(v => !v)}
|
||||||
style={{ minWidth: 36 }}
|
style={{ minWidth: 36 }}
|
||||||
title={dualPage ? "Single Page View" : "Dual Page View"}
|
title={dualPage ? t("viewer.singlePageView", "Single Page View") : t("viewer.dualPageView", "Dual Page View")}
|
||||||
>
|
>
|
||||||
{dualPage ? <DescriptionIcon fontSize="small" /> : <ViewWeekIcon fontSize="small" />}
|
{dualPage ? <DescriptionIcon fontSize="small" /> : <ViewWeekIcon fontSize="small" />}
|
||||||
</Button>
|
</Button>
|
||||||
@ -383,7 +385,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={() => setSidebarsVisible(!sidebarsVisible)}
|
onClick={() => setSidebarsVisible(!sidebarsVisible)}
|
||||||
style={{ minWidth: 36 }}
|
style={{ minWidth: 36 }}
|
||||||
title={sidebarsVisible ? "Hide Sidebars" : "Show Sidebars"}
|
title={sidebarsVisible ? t("viewer.hideSidebars", "Hide Sidebars") : t("viewer.showSidebars", "Show Sidebars")}
|
||||||
>
|
>
|
||||||
<ViewSidebarIcon
|
<ViewSidebarIcon
|
||||||
fontSize="small"
|
fontSize="small"
|
||||||
@ -401,7 +403,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={() => setZoom(z => Math.max(0.1, z - 0.1))}
|
onClick={() => setZoom(z => Math.max(0.1, z - 0.1))}
|
||||||
style={{ minWidth: 32, padding: 0 }}
|
style={{ minWidth: 32, padding: 0 }}
|
||||||
title="Zoom out"
|
title={t("viewer.zoomOut", "Zoom out")}
|
||||||
>−</Button>
|
>−</Button>
|
||||||
<span style={{ minWidth: 40, textAlign: "center" }}>{Math.round(zoom * 100)}%</span>
|
<span style={{ minWidth: 40, textAlign: "center" }}>{Math.round(zoom * 100)}%</span>
|
||||||
<Button
|
<Button
|
||||||
@ -411,7 +413,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
radius="xl"
|
radius="xl"
|
||||||
onClick={() => setZoom(z => Math.min(5, z + 0.1))}
|
onClick={() => setZoom(z => Math.min(5, z + 0.1))}
|
||||||
style={{ minWidth: 32, padding: 0 }}
|
style={{ minWidth: 32, padding: 0 }}
|
||||||
title="Zoom in"
|
title={t("viewer.zoomIn", "Zoom in")}
|
||||||
>+</Button>
|
>+</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
20
frontend/src/hooks/useTranslation.ts
Normal file
20
frontend/src/hooks/useTranslation.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Re-export react-i18next hook with our custom types
|
||||||
|
export { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
// You can add custom hooks here later if needed
|
||||||
|
// For example, a hook that returns commonly used translations
|
||||||
|
import { useTranslation as useI18nTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const useCommonTranslations = () => {
|
||||||
|
const { t } = useI18nTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
submit: t('genericSubmit'),
|
||||||
|
selectPdf: t('pdfPrompt'),
|
||||||
|
selectPdfs: t('multiPdfPrompt'),
|
||||||
|
selectImages: t('imgPrompt'),
|
||||||
|
loading: t('loading', 'Loading...'), // fallback if not found
|
||||||
|
error: t('error._value', 'Error'),
|
||||||
|
success: t('success', 'Success'),
|
||||||
|
};
|
||||||
|
};
|
87
frontend/src/i18n.ts
Normal file
87
frontend/src/i18n.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import i18n from 'i18next';
|
||||||
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
import Backend from 'i18next-http-backend';
|
||||||
|
|
||||||
|
// Define supported languages (based on your existing translations)
|
||||||
|
export const supportedLanguages = {
|
||||||
|
'en-GB': 'English (UK)',
|
||||||
|
'en-US': 'English (US)',
|
||||||
|
'ar-AR': 'العربية',
|
||||||
|
'az-AZ': 'Azərbaycan Dili',
|
||||||
|
'bg-BG': 'Български',
|
||||||
|
'ca-CA': 'Català',
|
||||||
|
'cs-CZ': 'Česky',
|
||||||
|
'da-DK': 'Dansk',
|
||||||
|
'de-DE': 'Deutsch',
|
||||||
|
'el-GR': 'Ελληνικά',
|
||||||
|
'es-ES': 'Español',
|
||||||
|
'eu-ES': 'Euskara',
|
||||||
|
'fa-IR': 'فارسی',
|
||||||
|
'fr-FR': 'Français',
|
||||||
|
'ga-IE': 'Gaeilge',
|
||||||
|
'hi-IN': 'हिंदी',
|
||||||
|
'hr-HR': 'Hrvatski',
|
||||||
|
'hu-HU': 'Magyar',
|
||||||
|
'id-ID': 'Bahasa Indonesia',
|
||||||
|
'it-IT': 'Italiano',
|
||||||
|
'ja-JP': '日本語',
|
||||||
|
'ko-KR': '한국어',
|
||||||
|
'ml-ML': 'മലയാളം',
|
||||||
|
'nl-NL': 'Nederlands',
|
||||||
|
'no-NB': 'Norsk',
|
||||||
|
'pl-PL': 'Polski',
|
||||||
|
'pt-BR': 'Português (Brasil)',
|
||||||
|
'pt-PT': 'Português',
|
||||||
|
'ro-RO': 'Română',
|
||||||
|
'ru-RU': 'Русский',
|
||||||
|
'sk-SK': 'Slovensky',
|
||||||
|
'sl-SI': 'Slovenščina',
|
||||||
|
'sr-LATN-RS': 'Srpski',
|
||||||
|
'sv-SE': 'Svenska',
|
||||||
|
'th-TH': 'ไทย',
|
||||||
|
'tr-TR': 'Türkçe',
|
||||||
|
'uk-UA': 'Українська',
|
||||||
|
'vi-VN': 'Tiếng Việt',
|
||||||
|
'zh-BO': 'བོད་ཡིག',
|
||||||
|
'zh-CN': '简体中文',
|
||||||
|
'zh-TW': '繁體中文',
|
||||||
|
};
|
||||||
|
|
||||||
|
// RTL languages (based on your existing language.direction property)
|
||||||
|
export const rtlLanguages = ['ar-AR', 'fa-IR'];
|
||||||
|
|
||||||
|
i18n
|
||||||
|
.use(Backend)
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
fallbackLng: 'en-GB',
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // React already escapes values
|
||||||
|
},
|
||||||
|
|
||||||
|
backend: {
|
||||||
|
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
||||||
|
},
|
||||||
|
|
||||||
|
detection: {
|
||||||
|
order: ['localStorage', 'navigator', 'htmlTag'],
|
||||||
|
caches: ['localStorage'],
|
||||||
|
},
|
||||||
|
|
||||||
|
react: {
|
||||||
|
useSuspense: false, // Set to false to avoid suspense issues with SSR
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set document direction based on language
|
||||||
|
i18n.on('languageChanged', (lng) => {
|
||||||
|
const isRTL = rtlLanguages.includes(lng);
|
||||||
|
document.documentElement.dir = isRTL ? 'rtl' : 'ltr';
|
||||||
|
document.documentElement.lang = lng;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
@ -5,6 +5,7 @@ import { ColorSchemeScript, MantineProvider, mantineHtmlProps } from '@mantine/c
|
|||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import './i18n'; // Initialize i18next
|
||||||
|
|
||||||
|
|
||||||
const container = document.getElementById('root');
|
const container = document.getElementById('root');
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user