feat: add dashboard i18n with next-intl (EN + PT-BR), language selector in header
This commit is contained in:
+5
-1
@@ -1,3 +1,7 @@
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
turbopack: {},
|
||||
@@ -63,4 +67,4 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
export default withNextIntl(nextConfig);
|
||||
|
||||
Generated
+695
-1
@@ -24,6 +24,7 @@
|
||||
"lowdb": "^7.0.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"next": "^16.1.6",
|
||||
"next-intl": "^4.8.3",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"open": "^11.0.0",
|
||||
"ora": "^9.1.0",
|
||||
@@ -950,6 +951,58 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.1.tgz",
|
||||
"integrity": "sha512-jhZbTwda+2tcNrs4kKvxrPLPjx8QsBCLCUgrrJ/S+G9YrGHWLhAyFMMBHJBnBoOwuLHd7L14FgYudviKaxkO2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "3.1.0",
|
||||
"@formatjs/intl-localematcher": "0.8.1",
|
||||
"decimal.js": "^10.6.0",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/fast-memoize": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz",
|
||||
"integrity": "sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.1.tgz",
|
||||
"integrity": "sha512-sSDmSvmmoVQ92XqWb499KrIhv/vLisJU8ITFrx7T7NZHUmMY7EL9xgRowAosaljhqnj/5iufG24QrdzB6X3ItA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "3.1.1",
|
||||
"@formatjs/icu-skeleton-parser": "2.1.1",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.1.tgz",
|
||||
"integrity": "sha512-PSFABlcNefjI6yyk8f7nyX1DC7NHmq6WaCHZLySEXBrXuLOB2f935YsnzuPjlz+ibhb9yWTdPeVX1OVcj24w2Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "3.1.1",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.1.tgz",
|
||||
"integrity": "sha512-xwEuwQFdtSq1UKtQnyTZWC+eHdv7Uygoa+H2k/9uzBVQjDyp9r20LNDNKedWXll7FssT3GRHvqsdJGYSUWqYFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "3.1.0",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -1762,6 +1815,313 @@
|
||||
"resolved": "open-sse",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"node-addon-api": "^7.0.0",
|
||||
"picomatch": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||
"@parcel/watcher-win32-x64": "2.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz",
|
||||
"integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz",
|
||||
"integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz",
|
||||
"integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz",
|
||||
"integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@peculiar/asn1-cms": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz",
|
||||
@@ -1975,6 +2335,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@schummar/icu-type-parser": {
|
||||
"version": "1.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
@@ -1987,6 +2353,172 @@
|
||||
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.13.tgz",
|
||||
"integrity": "sha512-ztXusRuC5NV2w+a6pDhX13CGioMLq8CjX5P4XgVJ21ocqz9t19288Do0y8LklplDtwcEhYGTNdMbkmUT7+lDTg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.13.tgz",
|
||||
"integrity": "sha512-cVifxQUKhaE7qcO/y9Mq6PEhoyvN9tSLzCnnFZ4EIabFHBuLtDDO6a+vLveOy98hAs5Qu1+bb5Nv0oa1Pihe3Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.13.tgz",
|
||||
"integrity": "sha512-t+xxEzZ48enl/wGGy7SRYd7kImWQ/+wvVFD7g5JZo234g6/QnIgZ+YdfIyjHB+ZJI3F7a2IQHS7RNjxF29UkWw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.13.tgz",
|
||||
"integrity": "sha512-VndeGvKmTXFn6AGwjy0Kg8i7HccOCE7Jt/vmZwRxGtOfNZM1RLYRQ7MfDLo6T0h1Bq6eYzps3L5Ma4zBmjOnOg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.13.tgz",
|
||||
"integrity": "sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.13.tgz",
|
||||
"integrity": "sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.13.tgz",
|
||||
"integrity": "sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.13.tgz",
|
||||
"integrity": "sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.13.tgz",
|
||||
"integrity": "sha512-hYXvyVVntqRlYoAIDwNzkS3tL2ijP3rxyWQMNKaxcCxxkCDto/w3meOK/OB6rbQSkNw0qTUcBfU9k+T0ptYdfQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.13.tgz",
|
||||
"integrity": "sha512-XTzKs7c/vYCcjmcwawnQvlHHNS1naJEAzcBckMI5OJlnrcgW8UtcX9NHFYvNjGtXuKv0/9KvqL4fuahdvlNGKw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
@@ -1996,6 +2528,15 @@
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz",
|
||||
"integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
|
||||
@@ -4124,6 +4665,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decimal.js-light": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||
@@ -5769,6 +6316,21 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/icu-minify": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.3.tgz",
|
||||
"integrity": "sha512-65Av7FLosNk7bPbmQx5z5XG2Y3T2GFppcjiXh4z1idHeVgQxlDpAmkGoYI0eFzAvrOnjpWTL5FmPDhsdfRMPEA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -5872,6 +6434,18 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "11.1.2",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.1.2.tgz",
|
||||
"integrity": "sha512-ucSrQmZGAxfiBHfBRXW/k7UC8MaGFlEj4Ry1tKiDcmgwQm1y3EDl40u+4VNHYomxJQMJi9NEI3riDRlth96jKg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@formatjs/ecma402-abstract": "3.1.1",
|
||||
"@formatjs/fast-memoize": "3.1.0",
|
||||
"@formatjs/icu-messageformat-parser": "3.5.1",
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||
@@ -7302,6 +7876,93 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.8.3.tgz",
|
||||
"integrity": "sha512-PvdBDWg+Leh7BR7GJUQbCDVVaBRn37GwDBWc9sv0rVQOJDQ5JU1rVzx9EEGuOGYo0DHAl70++9LQ7HxTawdL7w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-localematcher": "^0.8.1",
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"@swc/core": "^1.15.2",
|
||||
"icu-minify": "^4.8.3",
|
||||
"negotiator": "^1.0.0",
|
||||
"next-intl-swc-plugin-extractor": "^4.8.3",
|
||||
"po-parser": "^2.1.1",
|
||||
"use-intl": "^4.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl-swc-plugin-extractor": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.8.3.tgz",
|
||||
"integrity": "sha512-YcaT+R9z69XkGhpDarVFWUprrCMbxgIQYPUaXoE6LGVnLjGdo8hu3gL6bramDVjNKViYY8a/pXPy7Bna0mXORg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/core": {
|
||||
"version": "1.15.13",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.13.tgz",
|
||||
"integrity": "sha512-0l1gl/72PErwUZuavcRpRAQN9uSst+Nk++niC5IX6lmMWpXoScYx3oq/narT64/sKv/eRiPTaAjBFGDEQiWJIw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.25"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.15.13",
|
||||
"@swc/core-darwin-x64": "1.15.13",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.15.13",
|
||||
"@swc/core-linux-arm64-gnu": "1.15.13",
|
||||
"@swc/core-linux-arm64-musl": "1.15.13",
|
||||
"@swc/core-linux-x64-gnu": "1.15.13",
|
||||
"@swc/core-linux-x64-musl": "1.15.13",
|
||||
"@swc/core-win32-arm64-msvc": "1.15.13",
|
||||
"@swc/core-win32-ia32-msvc": "1.15.13",
|
||||
"@swc/core-win32-x64-msvc": "1.15.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": ">=0.5.17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/helpers": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
|
||||
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@@ -7354,6 +8015,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-machine-id": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz",
|
||||
@@ -7868,6 +8535,12 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/po-parser": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/po-parser/-/po-parser-2.1.1.tgz",
|
||||
"integrity": "sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@@ -9616,7 +10289,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -9769,6 +10442,27 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.8.3.tgz",
|
||||
"integrity": "sha512-nLxlC/RH+le6g3amA508Itnn/00mE+J22ui21QhOWo5V9hCEC43+WtnRAITbJW0ztVZphev5X9gvOf2/Dk9PLA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/amannn"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "^3.1.0",
|
||||
"@schummar/icu-type-parser": "1.21.5",
|
||||
"icu-minify": "^4.8.3",
|
||||
"intl-messageformat": "^11.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"lowdb": "^7.0.1",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"next": "^16.1.6",
|
||||
"next-intl": "^4.8.3",
|
||||
"node-machine-id": "^1.1.12",
|
||||
"open": "^11.0.0",
|
||||
"ora": "^9.1.0",
|
||||
|
||||
+10
-3
@@ -2,6 +2,8 @@ import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "@/shared/components/ThemeProvider";
|
||||
import "@/lib/initCloudSync"; // Auto-initialize cloud sync
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages, getLocale } from "next-intl/server";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
@@ -18,9 +20,12 @@ export const metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
export default async function RootLayout({ children }) {
|
||||
const locale = await getLocale();
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
||||
@@ -37,7 +42,9 @@ export default function RootLayout({ children }) {
|
||||
>
|
||||
Skip to content
|
||||
</a>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export const LOCALES = ["en", "pt-BR"] as const;
|
||||
export type Locale = (typeof LOCALES)[number];
|
||||
export const DEFAULT_LOCALE: Locale = "en";
|
||||
|
||||
export const LANGUAGES: readonly {
|
||||
code: Locale;
|
||||
label: string;
|
||||
name: string;
|
||||
flag: string;
|
||||
}[] = [
|
||||
{ code: "en", label: "EN", name: "English", flag: "🇺🇸" },
|
||||
{ code: "pt-BR", label: "PT", name: "Português", flag: "🇧🇷" },
|
||||
] as const;
|
||||
|
||||
export const LOCALE_COOKIE = "NEXT_LOCALE";
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"loading": "Loading...",
|
||||
"error": "An error occurred",
|
||||
"success": "Success",
|
||||
"confirm": "Are you sure?",
|
||||
"refresh": "Refresh",
|
||||
"close": "Close",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"search": "Search",
|
||||
"back": "Back",
|
||||
"next": "Next",
|
||||
"submit": "Submit",
|
||||
"reset": "Reset",
|
||||
"copy": "Copy",
|
||||
"copied": "Copied!",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"noData": "No data available"
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Home",
|
||||
"dashboard": "Dashboard",
|
||||
"providers": "Providers",
|
||||
"combos": "Combos",
|
||||
"usage": "Usage",
|
||||
"analytics": "Analytics",
|
||||
"costs": "Costs",
|
||||
"health": "Health",
|
||||
"limits": "Limits & Quotas",
|
||||
"cliTools": "CLI Tools",
|
||||
"settings": "Settings",
|
||||
"translator": "Translator",
|
||||
"docs": "Docs",
|
||||
"issues": "Issues",
|
||||
"endpoint": "Endpoint",
|
||||
"apiManager": "API Manager",
|
||||
"logs": "Logs",
|
||||
"auditLog": "Audit Log",
|
||||
"shutdown": "Shutdown",
|
||||
"restart": "Restart",
|
||||
"shutdownConfirm": "Shut down OmniRoute?",
|
||||
"restartConfirm": "Restart OmniRoute?",
|
||||
"version": "v{version}"
|
||||
},
|
||||
"header": {
|
||||
"logout": "Logout",
|
||||
"language": "Language",
|
||||
"providers": "Providers",
|
||||
"providerDescription": "Manage your AI provider connections",
|
||||
"combos": "Combos",
|
||||
"comboDescription": "Model combos with fallback",
|
||||
"usage": "Usage & Analytics",
|
||||
"usageDescription": "Monitor your API usage, token consumption, and request logs",
|
||||
"analytics": "Analytics",
|
||||
"analyticsDescription": "Charts, trends, and evaluation insights",
|
||||
"cliTools": "CLI Tools",
|
||||
"cliToolsDescription": "Configure CLI tools",
|
||||
"home": "Home",
|
||||
"homeDescription": "Welcome to OmniRoute",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDescription": "API endpoint configuration",
|
||||
"settings": "Settings",
|
||||
"settingsDescription": "Manage your preferences",
|
||||
"openaiCompatible": "OpenAI Compatible",
|
||||
"anthropicCompatible": "Anthropic Compatible"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"common": {
|
||||
"save": "Salvar",
|
||||
"cancel": "Cancelar",
|
||||
"delete": "Excluir",
|
||||
"loading": "Carregando...",
|
||||
"error": "Ocorreu um erro",
|
||||
"success": "Sucesso",
|
||||
"confirm": "Tem certeza?",
|
||||
"refresh": "Atualizar",
|
||||
"close": "Fechar",
|
||||
"add": "Adicionar",
|
||||
"edit": "Editar",
|
||||
"search": "Pesquisar",
|
||||
"back": "Voltar",
|
||||
"next": "Próximo",
|
||||
"submit": "Enviar",
|
||||
"reset": "Resetar",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado!",
|
||||
"enabled": "Ativado",
|
||||
"disabled": "Desativado",
|
||||
"active": "Ativo",
|
||||
"inactive": "Inativo",
|
||||
"noData": "Nenhum dado disponível"
|
||||
},
|
||||
"sidebar": {
|
||||
"home": "Início",
|
||||
"dashboard": "Painel",
|
||||
"providers": "Provedores",
|
||||
"combos": "Combos",
|
||||
"usage": "Uso",
|
||||
"analytics": "Análises",
|
||||
"costs": "Custos",
|
||||
"health": "Saúde",
|
||||
"limits": "Limites e Cotas",
|
||||
"cliTools": "Ferramentas CLI",
|
||||
"settings": "Configurações",
|
||||
"translator": "Tradutor",
|
||||
"docs": "Documentação",
|
||||
"issues": "Problemas",
|
||||
"endpoint": "Endpoint",
|
||||
"apiManager": "Gerenciador API",
|
||||
"logs": "Logs",
|
||||
"auditLog": "Log de Auditoria",
|
||||
"shutdown": "Desligar",
|
||||
"restart": "Reiniciar",
|
||||
"shutdownConfirm": "Desligar o OmniRoute?",
|
||||
"restartConfirm": "Reiniciar o OmniRoute?",
|
||||
"version": "v{version}"
|
||||
},
|
||||
"header": {
|
||||
"logout": "Sair",
|
||||
"language": "Idioma",
|
||||
"providers": "Provedores",
|
||||
"providerDescription": "Gerencie suas conexões de provedores de IA",
|
||||
"combos": "Combos",
|
||||
"comboDescription": "Combos de modelos com fallback",
|
||||
"usage": "Uso e Análises",
|
||||
"usageDescription": "Monitore seu uso de API, consumo de tokens e logs de requisições",
|
||||
"analytics": "Análises",
|
||||
"analyticsDescription": "Gráficos, tendências e insights de avaliação",
|
||||
"cliTools": "Ferramentas CLI",
|
||||
"cliToolsDescription": "Configurar ferramentas de linha de comando",
|
||||
"home": "Início",
|
||||
"homeDescription": "Bem-vindo ao OmniRoute",
|
||||
"endpoint": "Endpoint",
|
||||
"endpointDescription": "Configuração de endpoint da API",
|
||||
"settings": "Configurações",
|
||||
"settingsDescription": "Gerencie suas preferências",
|
||||
"openaiCompatible": "Compatível com OpenAI",
|
||||
"anthropicCompatible": "Compatível com Anthropic"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { getRequestConfig } from "next-intl/server";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { LOCALES, DEFAULT_LOCALE, LOCALE_COOKIE } from "./config";
|
||||
import type { Locale } from "./config";
|
||||
|
||||
export default getRequestConfig(async () => {
|
||||
// 1. Try cookie
|
||||
const cookieStore = await cookies();
|
||||
let locale: string = cookieStore.get(LOCALE_COOKIE)?.value || "";
|
||||
|
||||
// 2. Try custom header (set by middleware)
|
||||
if (!locale) {
|
||||
const headerStore = await headers();
|
||||
locale = headerStore.get("x-locale") || "";
|
||||
}
|
||||
|
||||
// 3. Validate & fallback
|
||||
if (!LOCALES.includes(locale as Locale)) {
|
||||
locale = DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
const messages = (await import(`./messages/${locale}.json`)).default;
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages,
|
||||
};
|
||||
});
|
||||
@@ -6,6 +6,8 @@ import Image from "next/image";
|
||||
import PropTypes from "prop-types";
|
||||
import { ThemeToggle } from "@/shared/components";
|
||||
import TokenHealthBadge from "./TokenHealthBadge";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
OAUTH_PROVIDERS,
|
||||
APIKEY_PROVIDERS,
|
||||
@@ -14,7 +16,9 @@ import {
|
||||
ANTHROPIC_COMPATIBLE_PREFIX,
|
||||
} from "@/shared/constants/providers";
|
||||
|
||||
const getPageInfo = (pathname) => {
|
||||
function usePageInfo(pathname: string | null) {
|
||||
const t = useTranslations("header");
|
||||
|
||||
if (!pathname) return { title: "", description: "", breadcrumbs: [] };
|
||||
|
||||
// Provider detail page: /dashboard/providers/[id]
|
||||
@@ -29,7 +33,7 @@ const getPageInfo = (pathname) => {
|
||||
title: providerInfo.name,
|
||||
description: "",
|
||||
breadcrumbs: [
|
||||
{ label: "Providers", href: "/dashboard/providers" },
|
||||
{ label: t("providers"), href: "/dashboard/providers" },
|
||||
{ label: providerInfo.name, image: `/providers/${providerInfo.id}.png` },
|
||||
],
|
||||
};
|
||||
@@ -37,22 +41,22 @@ const getPageInfo = (pathname) => {
|
||||
|
||||
if (providerId.startsWith(OPENAI_COMPATIBLE_PREFIX)) {
|
||||
return {
|
||||
title: "OpenAI Compatible",
|
||||
title: t("openaiCompatible"),
|
||||
description: "",
|
||||
breadcrumbs: [
|
||||
{ label: "Providers", href: "/dashboard/providers" },
|
||||
{ label: "OpenAI Compatible", image: "/providers/oai-cc.png" },
|
||||
{ label: t("providers"), href: "/dashboard/providers" },
|
||||
{ label: t("openaiCompatible"), image: "/providers/oai-cc.png" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (providerId.startsWith(ANTHROPIC_COMPATIBLE_PREFIX)) {
|
||||
return {
|
||||
title: "Anthropic Compatible",
|
||||
title: t("anthropicCompatible"),
|
||||
description: "",
|
||||
breadcrumbs: [
|
||||
{ label: "Providers", href: "/dashboard/providers" },
|
||||
{ label: "Anthropic Compatible", image: "/providers/anthropic-m.png" },
|
||||
{ label: t("providers"), href: "/dashboard/providers" },
|
||||
{ label: t("anthropicCompatible"), image: "/providers/anthropic-m.png" },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -60,40 +64,41 @@ const getPageInfo = (pathname) => {
|
||||
|
||||
if (pathname.includes("/providers"))
|
||||
return {
|
||||
title: "Providers",
|
||||
description: "Manage your AI provider connections",
|
||||
title: t("providers"),
|
||||
description: t("providerDescription"),
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/combos"))
|
||||
return { title: "Combos", description: "Model combos with fallback", breadcrumbs: [] };
|
||||
return { title: t("combos"), description: t("comboDescription"), breadcrumbs: [] };
|
||||
if (pathname.includes("/usage"))
|
||||
return {
|
||||
title: "Usage & Analytics",
|
||||
description: "Monitor your API usage, token consumption, and request logs",
|
||||
title: t("usage"),
|
||||
description: t("usageDescription"),
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/analytics"))
|
||||
return {
|
||||
title: "Analytics",
|
||||
description: "Charts, trends, and evaluation insights",
|
||||
title: t("analytics"),
|
||||
description: t("analyticsDescription"),
|
||||
breadcrumbs: [],
|
||||
};
|
||||
if (pathname.includes("/cli-tools"))
|
||||
return { title: "CLI Tools", description: "Configure CLI tools", breadcrumbs: [] };
|
||||
return { title: t("cliTools"), description: t("cliToolsDescription"), breadcrumbs: [] };
|
||||
if (pathname === "/dashboard")
|
||||
return { title: "Home", description: "Welcome to OmniRoute", breadcrumbs: [] };
|
||||
return { title: t("home"), description: t("homeDescription"), breadcrumbs: [] };
|
||||
if (pathname.includes("/endpoint"))
|
||||
return { title: "Endpoint", description: "API endpoint configuration", breadcrumbs: [] };
|
||||
return { title: t("endpoint"), description: t("endpointDescription"), breadcrumbs: [] };
|
||||
if (pathname.includes("/profile"))
|
||||
return { title: "Settings", description: "Manage your preferences", breadcrumbs: [] };
|
||||
return { title: t("settings"), description: t("settingsDescription"), breadcrumbs: [] };
|
||||
|
||||
return { title: "", description: "", breadcrumbs: [] };
|
||||
};
|
||||
}
|
||||
|
||||
export default function Header({ onMenuClick, showMenuButton = true }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { title, description, breadcrumbs } = getPageInfo(pathname);
|
||||
const t = useTranslations("header");
|
||||
const { title, description, breadcrumbs } = usePageInfo(pathname);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
@@ -175,6 +180,9 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
||||
|
||||
{/* Right actions */}
|
||||
<div className="flex items-center gap-3 ml-auto">
|
||||
{/* Language selector */}
|
||||
<LanguageSelector />
|
||||
|
||||
{/* Theme toggle */}
|
||||
<ThemeToggle />
|
||||
|
||||
@@ -185,7 +193,7 @@ export default function Header({ onMenuClick, showMenuButton = true }) {
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center justify-center p-2 rounded-lg text-text-muted hover:text-red-500 hover:bg-red-500/10 transition-all"
|
||||
title="Logout"
|
||||
title={t("logout")}
|
||||
>
|
||||
<span className="material-symbols-outlined">logout</span>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { LANGUAGES, LOCALE_COOKIE } from "@/i18n/config";
|
||||
import type { Locale } from "@/i18n/config";
|
||||
import { useLocale } from "next-intl";
|
||||
|
||||
/** Persist locale preference in cookie + localStorage (outside component scope for ESLint) */
|
||||
function persistLocale(code: Locale) {
|
||||
document.cookie = `${LOCALE_COOKIE}=${code};path=/;max-age=${365 * 24 * 60 * 60};samesite=lax`;
|
||||
try {
|
||||
localStorage.setItem(LOCALE_COOKIE, code);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
export default function LanguageSelector() {
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const currentLang = LANGUAGES.find((l) => l.code === locale) || LANGUAGES[0];
|
||||
|
||||
// Close dropdown on outside click
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, []);
|
||||
|
||||
const handleSelect = (code: Locale) => {
|
||||
if (code === locale) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
|
||||
persistLocale(code);
|
||||
setOpen(false);
|
||||
router.refresh();
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
{/* Trigger button */}
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-sm font-medium text-text-main hover:bg-surface-hover transition-all border border-transparent hover:border-border"
|
||||
title={currentLang.name}
|
||||
>
|
||||
<span className="text-base leading-none">{currentLang.flag}</span>
|
||||
<span className="text-xs font-semibold tracking-wide">{currentLang.label}</span>
|
||||
<span
|
||||
className={`material-symbols-outlined text-[14px] text-text-muted transition-transform ${open ? "rotate-180" : ""}`}
|
||||
>
|
||||
expand_more
|
||||
</span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown */}
|
||||
{open && (
|
||||
<div className="absolute right-0 top-full mt-1 w-40 rounded-xl border border-border bg-bg shadow-xl z-50 overflow-hidden animate-in fade-in slide-in-from-top-1 duration-150">
|
||||
{LANGUAGES.map((lang) => (
|
||||
<button
|
||||
key={lang.code}
|
||||
onClick={() => handleSelect(lang.code)}
|
||||
className={`w-full flex items-center gap-2.5 px-3 py-2.5 text-sm transition-colors ${
|
||||
lang.code === locale
|
||||
? "bg-primary/10 text-primary font-semibold"
|
||||
: "text-text-main hover:bg-surface-hover"
|
||||
}`}
|
||||
>
|
||||
<span className="text-base leading-none">{lang.flag}</span>
|
||||
<span className="flex-1 text-left">{lang.name}</span>
|
||||
{lang.code === locale && (
|
||||
<span className="material-symbols-outlined text-[16px] text-primary">check</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,31 +10,32 @@ import OmniRouteLogo from "./OmniRouteLogo";
|
||||
import Button from "./Button";
|
||||
import { ConfirmModal } from "./Modal";
|
||||
import CloudSyncStatus from "./CloudSyncStatus";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const navItems = [
|
||||
{ href: "/dashboard", label: "Home", icon: "home", exact: true },
|
||||
{ href: "/dashboard/endpoint", label: "Endpoint", icon: "api" },
|
||||
{ href: "/dashboard/api-manager", label: "API Manager", icon: "vpn_key" },
|
||||
{ href: "/dashboard/providers", label: "Providers", icon: "dns" },
|
||||
{ href: "/dashboard/combos", label: "Combos", icon: "layers" },
|
||||
{ href: "/dashboard/logs", label: "Logs", icon: "description" },
|
||||
{ href: "/dashboard/costs", label: "Costs", icon: "account_balance_wallet" },
|
||||
{ href: "/dashboard/analytics", label: "Analytics", icon: "analytics" },
|
||||
{ href: "/dashboard/limits", label: "Limits & Quotas", icon: "tune" },
|
||||
{ href: "/dashboard/health", label: "Health", icon: "health_and_safety" },
|
||||
{ href: "/dashboard/cli-tools", label: "CLI Tools", icon: "terminal" },
|
||||
// Nav items use i18n keys resolved inside the component
|
||||
const navItemDefs = [
|
||||
{ href: "/dashboard", i18nKey: "home", icon: "home", exact: true },
|
||||
{ href: "/dashboard/endpoint", i18nKey: "endpoint", icon: "api" },
|
||||
{ href: "/dashboard/api-manager", i18nKey: "apiManager", icon: "vpn_key" },
|
||||
{ href: "/dashboard/providers", i18nKey: "providers", icon: "dns" },
|
||||
{ href: "/dashboard/combos", i18nKey: "combos", icon: "layers" },
|
||||
{ href: "/dashboard/logs", i18nKey: "logs", icon: "description" },
|
||||
{ href: "/dashboard/costs", i18nKey: "costs", icon: "account_balance_wallet" },
|
||||
{ href: "/dashboard/analytics", i18nKey: "analytics", icon: "analytics" },
|
||||
{ href: "/dashboard/limits", i18nKey: "limits", icon: "tune" },
|
||||
{ href: "/dashboard/health", i18nKey: "health", icon: "health_and_safety" },
|
||||
{ href: "/dashboard/cli-tools", i18nKey: "cliTools", icon: "terminal" },
|
||||
];
|
||||
|
||||
// Debug items (only show when ENABLE_REQUEST_LOGS=true)
|
||||
const debugItems = [{ href: "/dashboard/translator", label: "Translator", icon: "translate" }];
|
||||
const debugItemDefs = [{ href: "/dashboard/translator", i18nKey: "translator", icon: "translate" }];
|
||||
|
||||
const systemItems = [{ href: "/dashboard/settings", label: "Settings", icon: "settings" }];
|
||||
const systemItemDefs = [{ href: "/dashboard/settings", i18nKey: "settings", icon: "settings" }];
|
||||
|
||||
const helpItems = [
|
||||
{ href: "/docs", label: "Docs", icon: "menu_book" },
|
||||
const helpItemDefs = [
|
||||
{ href: "/docs", i18nKey: "docs", icon: "menu_book" },
|
||||
{
|
||||
href: "https://github.com/diegosouzapw/OmniRoute/issues",
|
||||
label: "Issues",
|
||||
i18nKey: "issues",
|
||||
icon: "bug_report",
|
||||
external: true,
|
||||
},
|
||||
@@ -50,6 +51,8 @@ export default function Sidebar({
|
||||
onToggleCollapse?: any;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("sidebar");
|
||||
const tc = useTranslations("common");
|
||||
const [showShutdownModal, setShowShutdownModal] = useState(false);
|
||||
const [showRestartModal, setShowRestartModal] = useState(false);
|
||||
const [isShuttingDown, setIsShuttingDown] = useState(false);
|
||||
@@ -100,6 +103,13 @@ export default function Sidebar({
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
// Resolve i18n keys → labels
|
||||
const resolveItems = (defs) => defs.map((d) => ({ ...d, label: t(d.i18nKey) }));
|
||||
const navItems = resolveItems(navItemDefs);
|
||||
const debugItems = resolveItems(debugItemDefs);
|
||||
const systemItems = resolveItems(systemItemDefs);
|
||||
const helpItems = resolveItems(helpItemDefs);
|
||||
|
||||
const renderNavLink = (item) => {
|
||||
const active = !item.external && isActive(item.href, item.exact);
|
||||
const className = cn(
|
||||
@@ -271,7 +281,7 @@ export default function Sidebar({
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowRestartModal(true)}
|
||||
title="Restart server"
|
||||
title={t("restart")}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2 rounded-lg font-medium transition-all",
|
||||
"text-amber-500 hover:bg-amber-500/10 border border-amber-500/20 hover:border-amber-500/40",
|
||||
@@ -279,11 +289,11 @@ export default function Sidebar({
|
||||
)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">restart_alt</span>
|
||||
{!collapsed && "Restart"}
|
||||
{!collapsed && t("restart")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowShutdownModal(true)}
|
||||
title="Shutdown server"
|
||||
title={t("shutdown")}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-2 rounded-lg font-medium transition-all",
|
||||
"text-red-500 hover:bg-red-500/10 border border-red-500/20 hover:border-red-500/40",
|
||||
@@ -291,7 +301,7 @@ export default function Sidebar({
|
||||
)}
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">power_settings_new</span>
|
||||
{!collapsed && "Shutdown"}
|
||||
{!collapsed && t("shutdown")}
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -301,10 +311,10 @@ export default function Sidebar({
|
||||
isOpen={showShutdownModal}
|
||||
onClose={() => setShowShutdownModal(false)}
|
||||
onConfirm={handleShutdown}
|
||||
title="Close Proxy"
|
||||
message="Are you sure you want to close the proxy server?"
|
||||
confirmText="Close"
|
||||
cancelText="Cancel"
|
||||
title={t("shutdown")}
|
||||
message={t("shutdownConfirm")}
|
||||
confirmText={t("shutdown")}
|
||||
cancelText={tc("cancel")}
|
||||
variant="danger"
|
||||
loading={isShuttingDown}
|
||||
/>
|
||||
@@ -314,10 +324,10 @@ export default function Sidebar({
|
||||
isOpen={showRestartModal}
|
||||
onClose={() => setShowRestartModal(false)}
|
||||
onConfirm={handleRestart}
|
||||
title="Restart Proxy"
|
||||
message="Are you sure you want to restart the proxy server? It will be back online in a few seconds."
|
||||
confirmText="Restart"
|
||||
cancelText="Cancel"
|
||||
title={t("restart")}
|
||||
message={t("restartConfirm")}
|
||||
confirmText={t("restart")}
|
||||
cancelText={tc("cancel")}
|
||||
variant="warning"
|
||||
loading={isRestarting}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user