This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 6f43bff feat: extend translation keys & add useLanguage compatible
alias in LangContext (#433)
6f43bff is described below
commit 6f43bffe4d18231ca81ce56f6521819a4211f59b
Author: zhaohai <[email protected]>
AuthorDate: Fri Jul 17 16:30:53 2026 +0800
feat: extend translation keys & add useLanguage compatible alias in
LangContext (#433)
---
web/package-lock.json | 1079 ++++++++++++++++++++++-----
web/package.json | 15 +-
web/src/i18n/LangContext.tsx | 6 +
web/src/i18n/__tests__/LangContext.test.tsx | 122 +++
web/src/i18n/translations.ts | 484 ++++++++++++
web/src/test/setup.ts | 6 +
web/vite.config.ts | 8 +-
7 files changed, 1517 insertions(+), 203 deletions(-)
diff --git a/web/package-lock.json b/web/package-lock.json
index ae22fb1..98a67cb 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -19,6 +19,9 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.2",
+ "@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.62.1",
@@ -31,6 +34,7 @@
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"husky": "^9.1.7",
+ "jsdom": "^29.1.1",
"lint-staged": "^16.4.0",
"postcss": "^8.5.16",
"prettier": "^3.9.4",
@@ -38,9 +42,16 @@
"typescript": "^5.6.0",
"typescript-eslint": "^8.62.1",
"vite": "^6.0.0",
- "vitest": "^3.2.4"
+ "vitest": "^4.1.10"
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.5.0",
+ "resolved":
"https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz",
+ "integrity":
"sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved":
"https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -151,6 +162,57 @@
"react": ">=16.9.0"
}
},
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "5.1.11",
+ "resolved":
"https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
+ "integrity":
"sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@csstools/css-calc": "^3.2.0",
+ "@csstools/css-color-parser": "^4.1.0",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "7.1.1",
+ "resolved":
"https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
+ "integrity":
"sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/generational-cache": "^1.0.1",
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.2.1",
+ "is-potential-custom-element-name": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/generational-cache": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
+ "integrity":
"sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved":
"https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity":
"sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved":
"https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
@@ -442,6 +504,159 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved":
"https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity":
"sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "^3.0.0"
+ },
+ "bin": {
+ "specificity": "bin/cli.js"
+ }
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "6.1.0",
+ "resolved":
"https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz",
+ "integrity":
"sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "3.2.1",
+ "resolved":
"https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
+ "integrity":
"sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "4.1.9",
+ "resolved":
"https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz",
+ "integrity":
"sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^6.1.0",
+ "@csstools/css-calc": "^3.2.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity":
"sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.6",
+ "resolved":
"https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz",
+ "integrity":
"sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity":
"sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
@@ -1011,6 +1226,24 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
+ "node_modules/@exodus/bytes": {
+ "version": "1.15.1",
+ "resolved":
"https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
+ "integrity":
"sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@noble/hashes": "^1.8.0 || ^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@noble/hashes": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
@@ -1682,6 +1915,111 @@
"win32"
]
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved":
"https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity":
"sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved":
"https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity":
"sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.9.1",
+ "resolved":
"https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
+ "integrity":
"sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+
"node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved":
"https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity":
"sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.2",
+ "resolved":
"https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
+ "integrity":
"sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@testing-library/user-event": {
+ "version": "14.6.1",
+ "resolved":
"https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
+ "integrity":
"sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": ">=7.21.4"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved":
"https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity":
"sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved":
"https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2062,39 +2400,40 @@
}
},
"node_modules/@vitest/expect": {
- "version": "3.2.7",
- "resolved":
"https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
- "integrity":
"sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
+ "version": "4.1.10",
+ "resolved":
"https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
+ "integrity":
"sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
"dev": true,
"license": "MIT",
"dependencies": {
+ "@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
- "@vitest/spy": "3.2.7",
- "@vitest/utils": "3.2.7",
- "chai": "^5.2.0",
- "tinyrainbow": "^2.0.0"
+ "@vitest/spy": "4.1.10",
+ "@vitest/utils": "4.1.10",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/mocker": {
- "version": "3.2.7",
- "resolved":
"https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
- "integrity":
"sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
+ "version": "4.1.10",
+ "resolved":
"https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
+ "integrity":
"sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/spy": "3.2.7",
+ "@vitest/spy": "4.1.10",
"estree-walker": "^3.0.3",
- "magic-string": "^0.30.17"
+ "magic-string": "^0.30.21"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"msw": "^2.4.9",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"msw": {
@@ -2106,42 +2445,42 @@
}
},
"node_modules/@vitest/pretty-format": {
- "version": "3.2.7",
- "resolved":
"https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
- "integrity":
"sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
+ "version": "4.1.10",
+ "resolved":
"https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
+ "integrity":
"sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tinyrainbow": "^2.0.0"
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/runner": {
- "version": "3.2.7",
- "resolved":
"https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
- "integrity":
"sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
+ "version": "4.1.10",
+ "resolved":
"https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
+ "integrity":
"sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/utils": "3.2.7",
- "pathe": "^2.0.3",
- "strip-literal": "^3.0.0"
+ "@vitest/utils": "4.1.10",
+ "pathe": "^2.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/snapshot": {
- "version": "3.2.7",
- "resolved":
"https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
- "integrity":
"sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
+ "version": "4.1.10",
+ "resolved":
"https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
+ "integrity":
"sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.7",
- "magic-string": "^0.30.17",
+ "@vitest/pretty-format": "4.1.10",
+ "@vitest/utils": "4.1.10",
+ "magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
"funding": {
@@ -2149,28 +2488,25 @@
}
},
"node_modules/@vitest/spy": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
- "integrity":
"sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
+ "integrity":
"sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "tinyspy": "^4.0.3"
- },
"funding": {
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@vitest/utils": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
- "integrity":
"sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
+ "version": "4.1.10",
+ "resolved":
"https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
+ "integrity":
"sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@vitest/pretty-format": "3.2.7",
- "loupe": "^3.1.4",
- "tinyrainbow": "^2.0.0"
+ "@vitest/pretty-format": "4.1.10",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -2376,6 +2712,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved":
"https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity":
"sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved":
"https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
@@ -2478,6 +2824,16 @@
"node": ">=6.0.0"
}
},
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity":
"sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved":
"https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -2551,16 +2907,6 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity":
"sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved":
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -2606,32 +2952,15 @@
"license": "CC-BY-4.0"
},
"node_modules/chai": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
- "integrity":
"sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity":
"sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "assertion-error": "^2.0.1",
- "check-error": "^2.1.1",
- "deep-eql": "^5.0.1",
- "loupe": "^3.1.0",
- "pathval": "^2.0.0"
- },
"engines": {
"node": ">=18"
}
},
- "node_modules/check-error": {
- "version": "2.1.3",
- "resolved":
"https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
- "integrity":
"sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- }
- },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -2788,6 +3117,27 @@
"node": ">= 8"
}
},
+ "node_modules/css-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity":
"sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved":
"https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity":
"sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -2807,6 +3157,20 @@
"integrity":
"sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
+ "node_modules/data-urls": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity":
"sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/dayjs": {
"version": "1.11.21",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz",
@@ -2830,15 +3194,12 @@
}
}
},
- "node_modules/deep-eql": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
- "integrity":
"sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "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==",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "license": "MIT"
},
"node_modules/deep-is": {
"version": "0.1.4",
@@ -2856,6 +3217,16 @@
"node": ">=0.4.0"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity":
"sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved":
"https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -2870,6 +3241,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved":
"https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity":
"sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved":
"https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2898,6 +3277,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/entities": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
+ "integrity":
"sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/environment": {
"version": "1.1.0",
"resolved":
"https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
@@ -2930,9 +3322,9 @@
}
},
"node_modules/es-module-lexer": {
- "version": "1.7.0",
- "resolved":
"https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
- "integrity":
"sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "version": "2.3.1",
+ "resolved":
"https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
+ "integrity":
"sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
"dev": true,
"license": "MIT"
},
@@ -3650,6 +4042,19 @@
"hermes-estree": "0.25.1"
}
},
+ "node_modules/html-encoding-sniffer": {
+ "version": "6.0.0",
+ "resolved":
"https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity":
"sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.6.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved":
"https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -3699,6 +4104,16 @@
"node": ">=0.8.19"
}
},
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved":
"https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity":
"sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved":
"https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3801,6 +4216,13 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity":
"sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3824,6 +4246,57 @@
"integrity":
"sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
+ "node_modules/jsdom": {
+ "version": "29.1.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
+ "integrity":
"sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^5.1.11",
+ "@asamuzakjp/dom-selector": "^7.1.1",
+ "@bramus/specificity": "^2.4.2",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
+ "@exodus/bytes": "^1.15.0",
+ "css-tree": "^3.2.1",
+ "data-urls": "^7.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^6.0.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.3.5",
+ "parse5": "^8.0.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.1",
+ "undici": "^7.25.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.1",
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.1",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/lru-cache": {
+ "version": "11.5.2",
+ "resolved":
"https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
+ "integrity":
"sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
@@ -4041,13 +4514,6 @@
"loose-envify": "cli.js"
}
},
- "node_modules/loupe": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
- "integrity":
"sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4058,6 +4524,17 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity":
"sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved":
"https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4077,6 +4554,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity":
"sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4148,6 +4632,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved":
"https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity":
"sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved":
"https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -4248,6 +4742,20 @@
"node": ">= 6"
}
},
+ "node_modules/obug": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
+ "integrity":
"sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
@@ -4314,6 +4822,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parse5": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
+ "integrity":
"sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^8.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved":
"https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4348,16 +4869,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/pathval": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
- "integrity":
"sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.16"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved":
"https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4587,6 +5098,55 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved":
"https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity":
"sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved":
"https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity":
"sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved":
"https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity":
"sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity":
"sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved":
"https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
@@ -5348,6 +5908,30 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity":
"sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved":
"https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity":
"sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved":
"https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
@@ -5480,6 +6064,19 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity":
"sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
"node_modules/scheduler": {
"version": "0.23.2",
"resolved":
"https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -5592,9 +6189,9 @@
"license": "MIT"
},
"node_modules/std-env": {
- "version": "3.10.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
- "integrity":
"sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
+ "integrity":
"sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
"dev": true,
"license": "MIT"
},
@@ -5647,26 +6244,19 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
- "node_modules/strip-literal": {
- "version": "3.1.0",
- "resolved":
"https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
- "integrity":
"sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved":
"https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity":
"sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "js-tokens": "^9.0.1"
+ "min-indent": "^1.0.0"
},
- "funding": {
- "url": "https://github.com/sponsors/antfu"
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/strip-literal/node_modules/js-tokens": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
- "integrity":
"sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/stylis": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
@@ -5709,6 +6299,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved":
"https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity":
"sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tailwindcss": {
"version": "3.4.19",
"resolved":
"https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
@@ -5813,36 +6410,36 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
- "node_modules/tinypool": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
- "integrity":
"sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- }
- },
"node_modules/tinyrainbow": {
- "version": "2.0.0",
- "resolved":
"https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
- "integrity":
"sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "version": "3.1.0",
+ "resolved":
"https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity":
"sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
- "node_modules/tinyspy": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
- "integrity":
"sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "node_modules/tldts": {
+ "version": "7.4.8",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.8.tgz",
+ "integrity":
"sha512-htwgN/8KRB3z3vnC0BOETVh2m499g5GmyTK9Wq5JBLX3FNz6tSBveAd+fQhzy9hkjif8vy2jwDMR1sGhLtZl2A==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=14.0.0"
+ "dependencies": {
+ "tldts-core": "^7.4.8"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
}
},
+ "node_modules/tldts-core": {
+ "version": "7.4.8",
+ "resolved":
"https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.8.tgz",
+ "integrity":
"sha512-c1P7u0EhACHj7lPy4MJm8iTFEU8+nB0LCtddH0fhP7noaVoXAqafMtOOeX+ulpuPBqnrRgRhw494RICT3mbhnw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved":
"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5862,6 +6459,32 @@
"integrity":
"sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
"license": "MIT"
},
+ "node_modules/tough-cookie": {
+ "version": "6.0.2",
+ "resolved":
"https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
+ "integrity":
"sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity":
"sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "2.5.0",
"resolved":
"https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -5933,6 +6556,16 @@
"typescript": ">=4.8.4 <6.1.0"
}
},
+ "node_modules/undici": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
+ "integrity":
"sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved":
"https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -6056,89 +6689,80 @@
}
}
},
- "node_modules/vite-node": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
- "integrity":
"sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cac": "^6.7.14",
- "debug": "^4.4.1",
- "es-module-lexer": "^1.7.0",
- "pathe": "^2.0.3",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
- },
- "bin": {
- "vite-node": "vite-node.mjs"
- },
- "engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
"node_modules/vitest": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
- "integrity":
"sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/chai": "^5.2.2",
- "@vitest/expect": "3.2.7",
- "@vitest/mocker": "3.2.7",
- "@vitest/pretty-format": "^3.2.7",
- "@vitest/runner": "3.2.7",
- "@vitest/snapshot": "3.2.7",
- "@vitest/spy": "3.2.7",
- "@vitest/utils": "3.2.7",
- "chai": "^5.2.0",
- "debug": "^4.4.1",
- "expect-type": "^1.2.1",
- "magic-string": "^0.30.17",
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
+ "integrity":
"sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.10",
+ "@vitest/mocker": "4.1.10",
+ "@vitest/pretty-format": "4.1.10",
+ "@vitest/runner": "4.1.10",
+ "@vitest/snapshot": "4.1.10",
+ "@vitest/spy": "4.1.10",
+ "@vitest/utils": "4.1.10",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
"pathe": "^2.0.3",
- "picomatch": "^4.0.2",
- "std-env": "^3.9.0",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
"tinybench": "^2.9.0",
- "tinyexec": "^0.3.2",
- "tinyglobby": "^0.2.14",
- "tinypool": "^1.1.1",
- "tinyrainbow": "^2.0.0",
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
- "vite-node": "3.2.4",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
"why-is-node-running": "^2.3.0"
},
"bin": {
"vitest": "vitest.mjs"
},
"engines": {
- "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@edge-runtime/vm": "*",
- "@types/debug": "^4.1.12",
- "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
- "@vitest/browser": "3.2.7",
- "@vitest/ui": "3.2.7",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.10",
+ "@vitest/browser-preview": "4.1.10",
+ "@vitest/browser-webdriverio": "4.1.10",
+ "@vitest/coverage-istanbul": "4.1.10",
+ "@vitest/coverage-v8": "4.1.10",
+ "@vitest/ui": "4.1.10",
"happy-dom": "*",
- "jsdom": "*"
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
},
"peerDependenciesMeta": {
"@edge-runtime/vm": {
"optional": true
},
- "@types/debug": {
+ "@opentelemetry/api": {
"optional": true
},
"@types/node": {
"optional": true
},
- "@vitest/browser": {
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
"optional": true
},
"@vitest/ui": {
@@ -6149,15 +6773,59 @@
},
"jsdom": {
"optional": true
+ },
+ "vite": {
+ "optional": false
}
}
},
- "node_modules/vitest/node_modules/tinyexec": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
- "integrity":
"sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved":
"https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity":
"sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.1",
+ "resolved":
"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity":
"sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "5.0.0",
+ "resolved":
"https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity":
"sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "16.0.1",
+ "resolved":
"https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity":
"sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
},
"node_modules/which": {
"version": "2.0.2",
@@ -6238,6 +6906,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved":
"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity":
"sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity":
"sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/web/package.json b/web/package.json
index 1e7248b..025967d 100644
--- a/web/package.json
+++ b/web/package.json
@@ -6,12 +6,13 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
- "test": "vitest run",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,css}\"",
- "prepare": "cd .. && husky"
+ "prepare": "cd .. && husky",
+ "test": "vitest run",
+ "test:watch": "vitest"
},
"lint-staged": {
"*.{ts,tsx}": [
@@ -34,18 +35,22 @@
},
"devDependencies": {
"@eslint/js": "^10.0.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.2",
+ "@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.62.1",
"@typescript-eslint/parser": "^8.62.1",
"@vitejs/plugin-react": "^4.3.0",
- "axios-mock-adapter": "^2.1.0",
"autoprefixer": "^10.5.2",
+ "axios-mock-adapter": "^2.1.0",
"eslint": "^10.6.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"husky": "^9.1.7",
+ "jsdom": "^29.1.1",
"lint-staged": "^16.4.0",
"postcss": "^8.5.16",
"prettier": "^3.9.4",
@@ -53,6 +58,6 @@
"typescript": "^5.6.0",
"typescript-eslint": "^8.62.1",
"vite": "^6.0.0",
- "vitest": "^3.2.4"
+ "vitest": "^4.1.10"
}
-}
+}
\ No newline at end of file
diff --git a/web/src/i18n/LangContext.tsx b/web/src/i18n/LangContext.tsx
index 7667156..9a5a1b8 100644
--- a/web/src/i18n/LangContext.tsx
+++ b/web/src/i18n/LangContext.tsx
@@ -47,3 +47,9 @@ export const LangProvider = ({ children }: { children:
ReactNode }) => {
};
export const useLang = () => useContext(LangContext);
+
+/**
+ * Alias for useLang – provides the same i18n context.
+ * Kept for backward compatibility with components that import `useLanguage`.
+ */
+export const useLanguage = useLang;
diff --git a/web/src/i18n/__tests__/LangContext.test.tsx
b/web/src/i18n/__tests__/LangContext.test.tsx
new file mode 100644
index 0000000..1e3ac57
--- /dev/null
+++ b/web/src/i18n/__tests__/LangContext.test.tsx
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { LangProvider, useLang, useLanguage } from '../LangContext';
+
+/** Helper component that consumes LangContext and exposes its values */
+const LangConsumer = () => {
+ const { lang, setLang, t } = useLang();
+ return (
+ <div>
+ <span data-testid="lang">{lang}</span>
+ <span data-testid="translated">{t('nav.home')}</span>
+ <span data-testid="missing">{t('nonexistent.key')}</span>
+ <span data-testid="param">{t('common.total', { count: 42 })}</span>
+ <button onClick={() => setLang('en')}>switch-en</button>
+ <button onClick={() => setLang('zh')}>switch-zh</button>
+ </div>
+ );
+};
+
+/** Component that uses the useLanguage alias */
+const LanguageAliasConsumer = () => {
+ const { lang } = useLanguage();
+ return <span data-testid="alias-lang">{lang}</span>;
+};
+
+describe('LangContext', () => {
+ it('defaults to zh language', () => {
+ render(
+ <LangProvider>
+ <LangConsumer />
+ </LangProvider>,
+ );
+ expect(screen.getByTestId('lang')).toHaveTextContent('zh');
+ });
+
+ it('translates keys in Chinese by default', () => {
+ render(
+ <LangProvider>
+ <LangConsumer />
+ </LangProvider>,
+ );
+ expect(screen.getByTestId('translated')).toHaveTextContent('首页');
+ });
+
+ it('switches to English via setLang', async () => {
+ const user = userEvent.setup();
+ render(
+ <LangProvider>
+ <LangConsumer />
+ </LangProvider>,
+ );
+ await user.click(screen.getByText('switch-en'));
+ expect(screen.getByTestId('lang')).toHaveTextContent('en');
+ expect(screen.getByTestId('translated')).toHaveTextContent('Home');
+ });
+
+ it('switches back to Chinese via setLang', async () => {
+ const user = userEvent.setup();
+ render(
+ <LangProvider>
+ <LangConsumer />
+ </LangProvider>,
+ );
+ await user.click(screen.getByText('switch-en'));
+ expect(screen.getByTestId('lang')).toHaveTextContent('en');
+ await user.click(screen.getByText('switch-zh'));
+ expect(screen.getByTestId('lang')).toHaveTextContent('zh');
+ expect(screen.getByTestId('translated')).toHaveTextContent('首页');
+ });
+
+ it('returns the key itself for missing translations', () => {
+ render(
+ <LangProvider>
+ <LangConsumer />
+ </LangProvider>,
+ );
+ expect(screen.getByTestId('missing')).toHaveTextContent('nonexistent.key');
+ });
+
+ it('interpolates parameters into translation strings', async () => {
+ const user = userEvent.setup();
+ render(
+ <LangProvider>
+ <LangConsumer />
+ </LangProvider>,
+ );
+ // In Chinese mode, 'common.total' is '共' — no {count} placeholder in base
text,
+ // but the interpolation logic should still work without error.
+ // Switch to English to verify a key that might use params.
+ await user.click(screen.getByText('switch-en'));
+ // Even if the key doesn't have a {count} placeholder, the t() function
+ // should not throw — it simply replaces matching placeholders.
+ expect(screen.getByTestId('param')).toHaveTextContent('Total');
+ });
+
+ it('useLanguage alias provides the same context as useLang', () => {
+ render(
+ <LangProvider>
+ <LanguageAliasConsumer />
+ </LangProvider>,
+ );
+ expect(screen.getByTestId('alias-lang')).toHaveTextContent('zh');
+ });
+});
\ No newline at end of file
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index 1916eb0..c07151f 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -334,10 +334,494 @@ const translations: Record<string, Record<Lang, string>>
= {
// ─── Settings ───
'settings.title': { zh: '设置', en: 'Settings' },
+ 'settings.subtitle': { zh: '管理应用配置与数据源', en: 'Manage app settings and data
sources' },
// ─── User Menu ───
'user.profile': { zh: '个人中心', en: 'Profile' },
'user.logout': { zh: '退出登录', en: 'Logout' },
+
+ // ─── Login ───
+ 'login.title': { zh: '登录', en: 'Login' },
+ 'login.username': { zh: '用户名', en: 'Username' },
+ 'login.password': { zh: '密码', en: 'Password' },
+ 'login.usernamePlaceholder': { zh: '请输入用户名', en: 'Enter username' },
+ 'login.passwordPlaceholder': { zh: '请输入密码', en: 'Enter password' },
+ 'login.usernameRequired': { zh: '用户名为必填项', en: 'Username is required' },
+ 'login.passwordRequired': { zh: '密码为必填项', en: 'Password is required' },
+ 'login.success': { zh: '登录成功', en: 'Login successful' },
+ 'login.failed': { zh: '登录失败', en: 'Login failed' },
+ 'login.welcome': { zh: '欢迎使用 RocketMQ 仪表盘', en: 'Welcome to RocketMQ
Dashboard' },
+
+ // ─── Ops ───
+ 'ops.title': { zh: '运维', en: 'Ops' },
+ 'ops.name': { zh: '名称', en: 'Name' },
+ 'ops.value': { zh: '值', en: 'Value' },
+ 'ops.description': { zh: '描述', en: 'Description' },
+
+ // ─── Topic (detailed) ───
+ 'topic.subtitle': {
+ zh: '管理 Topic 的创建、配置与删除',
+ en: 'Manage Topic creation, configuration and deletion',
+ },
+ 'topic.count': { zh: '共 {n} 个 Topic', en: '{n} Topics' },
+ 'topic.add': { zh: '添加主题', en: 'Add Topic' },
+ 'topic.config': { zh: '主题配置', en: 'Topic Config' },
+ 'topic.change': { zh: '修改主题', en: 'Modify Topic' },
+ 'topic.name': { zh: '主题名', en: 'Topic Name' },
+ 'topic.type': { zh: '消息类型', en: 'Message Type' },
+ 'topic.normal': { zh: '普通消息', en: 'Normal' },
+ 'topic.fifo': { zh: '顺序消息', en: 'FIFO' },
+ 'topic.delay': { zh: '定时/延时消息', en: 'Delay' },
+ 'topic.transaction': { zh: '事务消息', en: 'Transaction' },
+ 'topic.unspecified': { zh: '未指定', en: 'Unspecified' },
+ 'topic.perm': { zh: '权限', en: 'Permission' },
+ 'topic.readQueueNums': { zh: '读队列数量', en: 'Read Queue Num' },
+ 'topic.writeQueueNums': { zh: '写队列数量', en: 'Write Queue Num' },
+ 'topic.brokerName': { zh: 'Broker 名称', en: 'Broker Name' },
+ 'topic.brokerAddr': { zh: 'Broker 地址', en: 'Broker Address' },
+ 'topic.clusterName': { zh: '集群名', en: 'Cluster Name' },
+ 'topic.selectCluster': { zh: '请选择集群', en: 'Select Cluster' },
+ 'topic.selectBroker': { zh: '请选择 Broker', en: 'Select Broker' },
+ 'topic.queueData': { zh: '队列信息', en: 'Queue Info' },
+ 'topic.minOffset': { zh: '最小位点', en: 'Min Offset' },
+ 'topic.maxOffset': { zh: '最大位点', en: 'Max Offset' },
+ 'topic.lastUpdateTime': { zh: '上次更新时间', en: 'Last Update Time' },
+ 'topic.sendMsg': { zh: '发送消息', en: 'Send Message' },
+ 'topic.resetOffset': { zh: '重置位点', en: 'Reset Offset' },
+ 'topic.skipAccumulate': { zh: '跳过堆积', en: 'Skip Accumulate' },
+ 'topic.delete': { zh: '删除主题', en: 'Delete Topic' },
+ 'topic.confirmDelete': { zh: '确认删除此主题?', en: 'Delete this topic?' },
+ 'topic.deleteWarning': { zh: '删除后无法恢复,请确认。', en: 'This cannot be undone.' },
+ 'topic.fetchFailed': { zh: '获取主题列表失败', en: 'Failed to fetch topic list' },
+ 'topic.operationSuccess': { zh: 'Topic 操作成功', en: 'Topic operation
successful' },
+ 'topic.searchPlaceholder': { zh: '搜索主题名称', en: 'Search topic name' },
+ 'topic.filterType': { zh: '消息类型', en: 'Message Type' },
+ 'topic.filterAll': { zh: '全部类型', en: 'All Types' },
+
+ // ─── Group / Consumer (detailed) ───
+ 'group.subtitle': {
+ zh: '管理消费者组的订阅与消费位点',
+ en: 'Manage consumer group subscriptions and offsets',
+ },
+ 'group.count': { zh: '共 {n} 个消费组', en: '{n} Groups' },
+ 'group.name': { zh: '组名称', en: 'Group Name' },
+ 'group.consumeEnable': { zh: '启用消费', en: 'Consume Enabled' },
+ 'group.orderly': { zh: '有序消费', en: 'Orderly' },
+ 'group.broadcast': { zh: '广播消费', en: 'Broadcast' },
+ 'group.retryQueues': { zh: '重试队列', en: 'Retry Queues' },
+ 'group.maxRetries': { zh: '最大重试次数', en: 'Max Retries' },
+ 'group.consumeTimeout': { zh: '消费超时', en: 'Consume Timeout' },
+ 'group.minutes': { zh: '分钟', en: 'Minutes' },
+ 'group.brokerId': { zh: '代理 ID', en: 'Broker ID' },
+ 'group.slowBroker': { zh: '慢消费代理', en: 'Slow Broker' },
+ 'group.retryPolicy': { zh: '重试策略', en: 'Retry Policy' },
+ 'group.systemFlag': { zh: '系统标志', en: 'System Flag' },
+ 'group.selectCluster': { zh: '请选择集群名称', en: 'Select Cluster' },
+ 'group.selectBrokers': { zh: '选择代理', en: 'Select Brokers' },
+ 'group.selectDeleteBrokers': {
+ zh: '请选择在哪个 Broker 删除消费者组',
+ en: 'Select Broker to delete group from',
+ },
+ 'group.deleteGroup': { zh: '删除消费者组', en: 'Delete Consumer Group' },
+ 'group.addConsumer': { zh: '添加消费者', en: 'Add Consumer' },
+ 'group.consumeDetail': { zh: '消费详情', en: 'Consume Detail' },
+ 'group.clientInfo': { zh: '客户端信息', en: 'Client Info' },
+ 'group.consumeType': { zh: '消费类型', en: 'Consume Type' },
+ 'group.messageModel': { zh: '消息模型', en: 'Message Model' },
+ 'group.consumeFromWhere': { zh: '从何处消费', en: 'Consume From' },
+ 'group.clientConnections': { zh: '客户端连接', en: 'Client Connections' },
+ 'group.clientSubscriptions': { zh: '客户端订阅', en: 'Client Subscriptions' },
+ 'group.clientId': { zh: '客户端 ID', en: 'Client ID' },
+ 'group.clientAddr': { zh: '客户端地址', en: 'Client Address' },
+ 'group.language': { zh: '语言', en: 'Language' },
+ 'group.subscriptionExpr': { zh: '订阅表达式', en: 'Subscription Expression' },
+ 'group.expressionType': { zh: '表达式类型', en: 'Expression Type' },
+ 'group.subVersion': { zh: '订阅版本', en: 'Subscription Version' },
+ 'group.brokerOffset': { zh: '代理者位点', en: 'Broker Offset' },
+ 'group.consumerOffset': { zh: '消费者位点', en: 'Consumer Offset' },
+ 'group.diffTotal': { zh: '差值', en: 'Diff Total' },
+ 'group.lastTimestamp': { zh: '上次时间', en: 'Last Timestamp' },
+ 'group.lastConsumeTime': { zh: '最后消费时间', en: 'Last Consume Time' },
+ 'group.resetOffsetSuccess': {
+ zh: '{name} 消费位点已重置到 {time}',
+ en: '{name} offset reset to {time}',
+ },
+ 'group.searchPlaceholder': { zh: '搜索消费组名称', en: 'Search group name' },
+ 'group.selectGroup': { zh: '请选择消费者组', en: 'Select Consumer Group' },
+
+ // ─── Message (detailed) ───
+ 'message.subtitle': {
+ zh: '按 Topic、Key 或 Message ID 检索消息',
+ en: 'Search by Topic, Key or Message ID',
+ },
+ 'message.detail': { zh: '消息详情', en: 'Message Detail' },
+ 'message.body': { zh: '消息主体', en: 'Message Body' },
+ 'message.properties': { zh: '消息属性', en: 'Message Properties' },
+ 'message.info': { zh: '消息信息', en: 'Message Info' },
+ 'message.tracking': { zh: '消息追踪', en: 'Message Tracking' },
+ 'message.showAll': { zh: '显示全部内容', en: 'Show All Content' },
+ 'message.topicName': { zh: '主题名', en: 'Topic Name' },
+ 'message.tag': { zh: '标签', en: 'Tag' },
+ 'message.key': { zh: '键', en: 'Key' },
+ 'message.id': { zh: '消息 ID', en: 'Message ID' },
+ 'message.resend': { zh: '重新发送', en: 'Resend' },
+ 'message.viewException': { zh: '查看异常', en: 'View Exception' },
+ 'message.selectTopic': { zh: '请选择主题', en: 'Select Topic' },
+ 'message.selectTraceTopic': { zh: '请选择消息轨迹主题', en: 'Select Trace Topic' },
+ 'message.traceTopicHint': { zh: '消息轨迹主题', en: 'Trace Topic' },
+ 'message.onlyReturn64': { zh: '仅返回64条消息', en: 'Only return 64 messages' },
+ 'message.idTopicHint': { zh: '消息ID主题', en: 'Message ID Topic' },
+ 'message.idAndGroupRequired': {
+ zh: '消息ID和消费者组为必填项',
+ en: 'Message ID and Consumer Group are required',
+ },
+ 'message.topicAndKeyRequired': { zh: 'Topic和Key为必填项', en: 'Topic and Key are
required' },
+ 'message.idRequired': { zh: '消息ID为必填项', en: 'Message ID is required' },
+ 'message.resendSuccess': { zh: '重新发送成功', en: 'Resend successful' },
+ 'message.noResult': { zh: '无结果', en: 'No Result' },
+ 'message.queryFailed': { zh: '查询失败', en: 'Query Failed' },
+ 'message.fetchDetailFailed': { zh: '获取消息详情失败', en: 'Failed to fetch message
detail' },
+ 'message.export': { zh: '导出', en: 'Export' },
+ 'message.batchResend': { zh: '批量重发', en: 'Batch Resend' },
+ 'message.batchExport': { zh: '批量导出', en: 'Batch Export' },
+ 'message.noMatchResult': { zh: '没有查到符合条件的结果', en: 'No matching results' },
+
+ // ─── DLQ (detailed) ───
+ 'dlq.subtitle': {
+ zh: '管理消费失败进入死信队列的消息',
+ en: 'Manage messages in dead letter queue',
+ },
+ 'dlq.message': { zh: '死信消息', en: 'DLQ Message' },
+ 'dlq.consumerGroup': { zh: '消费者组', en: 'Consumer Group' },
+
+ // ─── Message Trace ───
+ 'trace.title': { zh: '消息轨迹', en: 'Message Trace' },
+ 'trace.detail': { zh: '消息轨迹详情', en: 'Trace Detail' },
+ 'trace.enable': { zh: '开启消息轨迹', en: 'Enable Message Trace' },
+ 'trace.topic': { zh: '消息轨迹主题', en: 'Trace Topic' },
+ 'trace.selectTopic': { zh: '选择消息轨迹主题', en: 'Select Trace Topic' },
+
+ // ─── Broker Detail ───
+ 'broker.overview': { zh: 'Broker 概览', en: 'Broker Overview' },
+ 'broker.name': { zh: 'Broker 名称', en: 'Broker Name' },
+ 'broker.addr': { zh: 'Broker 地址', en: 'Broker Address' },
+ 'broker.config': { zh: '代理配置', en: 'Broker Config' },
+ 'broker.totalMsgToday': { zh: '今天接收的总消息数', en: 'Total Messages Today' },
+ 'broker.putTps': { zh: '写入 TPS', en: 'Put TPS' },
+ 'broker.getTps': { zh: '拉取 TPS', en: 'Get TPS' },
+ 'broker.putNumsToday': { zh: '写入今日总量', en: 'Put Num Today' },
+ 'broker.getNumsToday': { zh: '拉取今日总量', en: 'Get Num Today' },
+ 'broker.networkThroughput': { zh: 'Broker 网络吞吐量', en: 'Broker Network
Throughput' },
+ 'broker.accumulationDepth': { zh: '堆积深度趋势', en: 'Accumulation Depth Trend' },
+ 'broker.accumulationTotal': { zh: '堆积总量', en: 'Accumulation Total' },
+ 'broker.transactionMetrics': { zh: '事务消息指标', en: 'Transaction Message
Metrics' },
+ 'broker.storageWriteLatency': { zh: '存储写入延迟', en: 'Storage Write Latency' },
+ 'broker.topicPutLatency': { zh: 'CommitLog 写入延迟', en: 'CommitLog Put
Latency' },
+ 'broker.consumeLatency': { zh: '消费拉取延迟', en: 'Consume Pull Latency' },
+ 'broker.replicaSyncLatency': { zh: '主从副本同步延迟', en: 'Replica Sync Latency' },
+ 'broker.hotTopicTop10': { zh: '热点 Topic Top10', en: 'Hot Topic Top10' },
+ 'broker.jvmDetail': { zh: 'Broker JVM 详情', en: 'Broker JVM Detail' },
+ 'broker.gcStats': { zh: 'JVM GC 统计', en: 'JVM GC Stats' },
+ 'broker.heapUsage': { zh: '堆内存使用', en: 'Heap Usage' },
+ 'broker.threadCount': { zh: '线程数', en: 'Thread Count' },
+ 'broker.queryAccumulationFailed': {
+ zh: '查询堆积数据失败',
+ en: 'Failed to query accumulation data',
+ },
+ 'broker.queryTransactionFailed': {
+ zh: '查询事务数据失败',
+ en: 'Failed to query transaction data',
+ },
+ 'broker.queryStorageLatencyFailed': {
+ zh: '查询存储延迟数据失败',
+ en: 'Failed to query storage latency',
+ },
+ 'broker.queryNetworkFailed': {
+ zh: '查询网络吞吐量数据失败',
+ en: 'Failed to query network throughput',
+ },
+ 'broker.queryReplicaFailed': {
+ zh: '查询副本同步数据失败',
+ en: 'Failed to query replica sync data',
+ },
+ 'broker.queryHotTopicFailed': {
+ zh: '查询热点 Topic 数据失败',
+ en: 'Failed to query hot topic data',
+ },
+ 'broker.queryJvmFailed': { zh: '查询 JVM 统计失败', en: 'Failed to query JVM
stats' },
+
+ // ─── Proxy Cluster ───
+ 'proxy.title': { zh: 'Proxy 集群', en: 'Proxy Cluster' },
+ 'proxy.totalNodes': { zh: '总节点数', en: 'Total Nodes' },
+ 'proxy.healthyNodes': { zh: '健康节点', en: 'Healthy Nodes' },
+ 'proxy.totalConnections': { zh: '总连接数', en: 'Total Connections' },
+ 'proxy.totalTps': { zh: '总 TPS', en: 'Total TPS' },
+ 'proxy.addNode': { zh: '添加节点', en: 'Add Node' },
+ 'proxy.nodes': { zh: 'Proxy 节点', en: 'Proxy Nodes' },
+ 'proxy.viewConfig': { zh: '查看配置', en: 'View Config' },
+ 'proxy.remove': { zh: '移除', en: 'Remove' },
+ 'proxy.nodeConfig': { zh: '节点配置', en: 'Node Configuration' },
+ 'proxy.addProxyNode': { zh: '添加 Proxy 节点', en: 'Add Proxy Node' },
+ 'proxy.address': { zh: 'Proxy 地址', en: 'Proxy Address' },
+ 'proxy.addressPlaceholder': { zh: '例:127.0.0.1:8081', en: 'e.g.,
127.0.0.1:8081' },
+ 'proxy.invalidAddress': {
+ zh: '地址格式无效(例:127.0.0.1:8081)',
+ en: 'Invalid format (e.g., 127.0.0.1:8081)',
+ },
+ 'proxy.current': { zh: '当前', en: 'Current' },
+ 'proxy.connections': { zh: '连接数', en: 'Connections' },
+ 'proxy.memory': { zh: '内存', en: 'Memory' },
+ 'proxy.uptime': { zh: '运行时间', en: 'Uptime' },
+ 'proxy.action': { zh: '操作', en: 'Action' },
+ 'proxy.fetchListFailed': { zh: '获取代理列表失败', en: 'Failed to fetch proxy list'
},
+ 'proxy.addFailed': { zh: '添加代理失败', en: 'Failed to add proxy' },
+ 'proxy.addrRequired': { zh: '请输入代理地址', en: 'Proxy address is required' },
+ 'proxy.noConfigData': { zh: '无配置数据', en: 'No config data' },
+
+ // ─── SSL Settings ───
+ 'ssl.title': { zh: 'SSL/TLS 设置', en: 'SSL/TLS Settings' },
+ 'ssl.info': { zh: 'SSL/TLS 配置', en: 'SSL/TLS Configuration' },
+ 'ssl.infoDesc': {
+ zh: '配置 SSL/TLS 设置以实现安全通信,更改后需重启服务器。',
+ en: 'Configure SSL/TLS for secure communication. Changes require server
restart.',
+ },
+ 'ssl.enabled': { zh: '启用 SSL/TLS', en: 'Enable SSL/TLS' },
+ 'ssl.protocol': { zh: 'SSL 协议', en: 'SSL Protocol' },
+ 'ssl.selectProtocol': { zh: '请选择协议', en: 'Select protocol' },
+ 'ssl.clientAuth': { zh: '客户端认证', en: 'Client Authentication' },
+ 'ssl.none': { zh: '无', en: 'None' },
+ 'ssl.want': { zh: '可选', en: 'Want' },
+ 'ssl.need': { zh: '必需', en: 'Need' },
+ 'ssl.keystoreConfig': { zh: 'KeyStore 配置', en: 'KeyStore Configuration' },
+ 'ssl.keystoreType': { zh: 'KeyStore 类型', en: 'KeyStore Type' },
+ 'ssl.keystorePath': { zh: 'KeyStore 路径', en: 'KeyStore Path' },
+ 'ssl.keystorePathPlaceholder': { zh: '请输入 KeyStore 路径', en: 'Enter KeyStore
path' },
+ 'ssl.keystorePassword': { zh: 'KeyStore 密码', en: 'KeyStore Password' },
+ 'ssl.keystorePasswordPlaceholder': { zh: '请输入 KeyStore 密码', en: 'Enter
KeyStore password' },
+ 'ssl.uploadKeystore': { zh: '上传 KeyStore 文件', en: 'Upload KeyStore File' },
+ 'ssl.truststoreConfig': { zh: 'TrustStore 配置', en: 'TrustStore
Configuration' },
+ 'ssl.truststoreType': { zh: 'TrustStore 类型', en: 'TrustStore Type' },
+ 'ssl.truststorePath': { zh: 'TrustStore 路径', en: 'TrustStore Path' },
+ 'ssl.truststorePathPlaceholder': { zh: '请输入 TrustStore 路径', en: 'Enter
TrustStore path' },
+ 'ssl.truststorePassword': { zh: 'TrustStore 密码', en: 'TrustStore Password' },
+ 'ssl.truststorePasswordPlaceholder': {
+ zh: '请输入 TrustStore 密码',
+ en: 'Enter TrustStore password',
+ },
+ 'ssl.uploadTruststore': { zh: '上传 TrustStore 文件', en: 'Upload TrustStore
File' },
+ 'ssl.save': { zh: '保存', en: 'Save' },
+ 'ssl.upload': { zh: '上传', en: 'Upload' },
+ 'ssl.certInfo': { zh: '证书信息', en: 'Certificate Information' },
+ 'ssl.issuer': { zh: '颁发者', en: 'Issuer' },
+ 'ssl.expiryDate': { zh: '过期日期', en: 'Expiry Date' },
+ 'ssl.active': { zh: '有效', en: 'Active' },
+ 'ssl.saveSuccess': { zh: 'SSL 配置保存成功', en: 'SSL configuration saved
successfully' },
+ 'ssl.invalidCertFormat': { zh: '仅允许证书文件!', en: 'Only certificate files are
allowed!' },
+ 'ssl.certRemoved': { zh: '证书文件已移除', en: 'Certificate file removed' },
+
+ // ─── Namespace ───
+ 'ns.title': { zh: '命名空间管理', en: 'Namespace Management' },
+ 'ns.name': { zh: '命名空间', en: 'Namespace' },
+ 'ns.displayName': { zh: '显示名称', en: 'Display Name' },
+ 'ns.cluster': { zh: '集群', en: 'Cluster' },
+ 'ns.description': { zh: '描述', en: 'Description' },
+ 'ns.default': { zh: '默认', en: 'Default' },
+ 'ns.createTime': { zh: '创建时间', en: 'Create Time' },
+ 'ns.topicLimit': { zh: 'Topic 限额', en: 'Topic Limit' },
+ 'ns.groupLimit': { zh: 'Group 限额', en: 'Group Limit' },
+ 'ns.storage': { zh: '存储 (GB)', en: 'Storage (GB)' },
+ 'ns.qpsLimit': { zh: 'QPS 限制', en: 'QPS Limit' },
+ 'ns.connLimit': { zh: '连接限制', en: 'Connection Limit' },
+ 'ns.total': { zh: '命名空间总数', en: 'Total Namespaces' },
+ 'ns.enabled': { zh: '已启用', en: 'Enabled' },
+ 'ns.totalTopicQuota': { zh: 'Topic 总配额', en: 'Total Topic Quota' },
+ 'ns.totalGroupQuota': { zh: 'Group 总配额', en: 'Total Group Quota' },
+ 'ns.list': { zh: '命名空间列表', en: 'Namespace List' },
+ 'ns.create': { zh: '创建命名空间', en: 'Create Namespace' },
+ 'ns.edit': { zh: '编辑命名空间', en: 'Edit Namespace' },
+ 'ns.detail': { zh: '命名空间详情', en: 'Namespace Detail' },
+ 'ns.viewDetail': { zh: '查看详情', en: 'View Detail' },
+ 'ns.quotaSection': { zh: '配额配置', en: 'Quota Configuration' },
+ 'ns.confirmDelete': { zh: '确认删除此命名空间?', en: 'Delete this namespace?' },
+ 'ns.nameRequired': { zh: '请输入命名空间名称', en: 'Namespace name is required' },
+ 'ns.namePattern': {
+ zh: '仅支持字母、数字、连字符和下划线',
+ en: 'Only letters, numbers, hyphens and underscores',
+ },
+ 'ns.namePlaceholder': { zh: '例:production-ns', en: 'e.g., production-ns' },
+ 'ns.displayNamePlaceholder': { zh: '例:Production', en: 'e.g., Production' },
+ 'ns.clusterPlaceholder': { zh: '例:DefaultCluster', en: 'e.g.,
DefaultCluster' },
+ 'ns.descPlaceholder': { zh: '描述此命名空间...', en: 'Describe this namespace...' },
+ 'ns.fetchFailed': { zh: '获取命名空间失败', en: 'Failed to fetch namespaces' },
+ 'ns.createSuccess': { zh: '命名空间创建成功', en: 'Namespace created successfully' },
+ 'ns.createFailed': { zh: '创建命名空间失败', en: 'Failed to create namespace' },
+ 'ns.updateSuccess': { zh: '命名空间更新成功', en: 'Namespace updated successfully' },
+ 'ns.updateFailed': { zh: '更新命名空间失败', en: 'Failed to update namespace' },
+ 'ns.deleteSuccess': { zh: '命名空间删除成功', en: 'Namespace deleted successfully' },
+ 'ns.deleteFailed': { zh: '删除命名空间失败', en: 'Failed to delete namespace' },
+ 'ns.notSupported': { zh: '不支持命名空间', en: 'Namespace Not Supported' },
+ 'ns.notSupportedDesc': {
+ zh: '当前集群架构不支持命名空间管理,请切换到 RocketMQ 5.0 Proxy 集群。',
+ en: 'Current cluster does not support namespace management. Please switch
to a RocketMQ 5.0 Proxy cluster.',
+ },
+
+ // ─── Alert Management ───
+ 'alert.management': { zh: '告警管理', en: 'Alert Management' },
+ 'alert.name': { zh: '告警名称', en: 'Alert Name' },
+ 'alert.group': { zh: '告警分组', en: 'Group' },
+ 'alert.severity': { zh: '严重级别', en: 'Severity' },
+ 'alert.team': { zh: '团队', en: 'Team' },
+ 'alert.expr': { zh: '表达式', en: 'Expression' },
+ 'alert.for': { zh: '持续时间', en: 'For' },
+ 'alert.status': { zh: '状态', en: 'Status' },
+ 'alert.actions': { zh: '操作', en: 'Actions' },
+ 'alert.total': { zh: '总规则数', en: 'Total Rules' },
+ 'alert.enabled': { zh: '已启用', en: 'Enabled' },
+ 'alert.disabled': { zh: '已禁用', en: 'Disabled' },
+ 'alert.critical': { zh: '严重', en: 'Critical' },
+ 'alert.warningCount': { zh: '警告', en: 'Warning' },
+ 'alert.refresh': { zh: '刷新', en: 'Refresh' },
+ 'alert.add': { zh: '新增规则', en: 'Add Rule' },
+ 'alert.exportYaml': { zh: '导出 YAML', en: 'Export YAML' },
+ 'alert.searchPlaceholder': {
+ zh: '搜索告警名称、表达式...',
+ en: 'Search alert name, expression...',
+ },
+ 'alert.allGroups': { zh: '全部分组', en: 'All Groups' },
+ 'alert.allSeverity': { zh: '全部级别', en: 'All Severity' },
+ 'alert.allStatus': { zh: '全部状态', en: 'All Status' },
+ 'alert.rules': { zh: '条规则', en: 'rules' },
+ 'alert.edit': { zh: '编辑', en: 'Edit' },
+ 'alert.delete': { zh: '删除', en: 'Delete' },
+ 'alert.editRule': { zh: '编辑告警规则', en: 'Edit Alert Rule' },
+ 'alert.addRule': { zh: '新增告警规则', en: 'Add Alert Rule' },
+ 'alert.summary': { zh: '摘要', en: 'Summary' },
+ 'alert.description': { zh: '描述', en: 'Description' },
+ 'alert.nameRequired': { zh: '告警名称为必填项', en: 'Alert name is required' },
+ 'alert.groupRequired': { zh: '告警分组为必填项', en: 'Alert group is required' },
+ 'alert.exprRequired': { zh: '表达式为必填项', en: 'Expression is required' },
+ 'alert.forRequired': { zh: '持续时间为必填项', en: 'Duration is required' },
+ 'alert.summaryRequired': { zh: '摘要为必填项', en: 'Summary is required' },
+ 'alert.fetchFailed': { zh: '获取告警规则失败', en: 'Failed to fetch alert rules' },
+ 'alert.deleteSuccess': { zh: '告警规则已删除', en: 'Alert rule deleted' },
+ 'alert.updateSuccess': { zh: '告警规则已更新', en: 'Alert rule updated' },
+ 'alert.createSuccess': { zh: '告警规则已创建', en: 'Alert rule created' },
+ 'alert.exportSuccess': { zh: '告警规则已导出', en: 'Alert rules exported' },
+
+ // ─── LiteTopic ───
+ 'liteTopic.title': { zh: 'LiteTopic 管理', en: 'LiteTopic Management' },
+ 'liteTopic.quotaOverview': { zh: '配额概览', en: 'Quota Overview' },
+ 'liteTopic.topicUsage': { zh: 'Topic 使用', en: 'Topic Usage' },
+ 'liteTopic.sessionUsage': { zh: 'Session 使用', en: 'Session Usage' },
+ 'liteTopic.creationRate': { zh: '创建速率', en: 'Creation Rate' },
+ 'liteTopic.searchPlaceholder': { zh: '按 Topic 模式筛选...', en: 'Filter by topic
pattern...' },
+ 'liteTopic.namespace': { zh: '命名空间', en: 'Namespace' },
+ 'liteTopic.namespacePlaceholder': { zh: '选择命名空间', en: 'Select namespace' },
+ 'liteTopic.allNamespaces': { zh: '全部命名空间', en: 'All Namespaces' },
+ 'liteTopic.pattern': { zh: '模式', en: 'Pattern' },
+ 'liteTopic.topicCount': { zh: 'Topic 数', en: 'Topic Count' },
+ 'liteTopic.consumers': { zh: '消费者', en: 'Consumers' },
+ 'liteTopic.backlog': { zh: '积压', en: 'Backlog' },
+ 'liteTopic.avgTtl': { zh: '平均 TTL', en: 'Avg TTL' },
+ 'liteTopic.lastActive': { zh: '最后活跃', en: 'Last Active' },
+ 'liteTopic.status': { zh: '状态', en: 'Status' },
+ 'liteTopic.actions': { zh: '操作', en: 'Actions' },
+ 'liteTopic.extendTtl': { zh: '延长 TTL', en: 'Extend TTL' },
+ 'liteTopic.viewSessions': { zh: '查看会话', en: 'View Sessions' },
+ 'liteTopic.sessionDetail': { zh: '会话详情', en: 'Session Detail' },
+ 'liteTopic.sessionId': { zh: '会话 ID', en: 'Session ID' },
+ 'liteTopic.clientId': { zh: '客户端 ID', en: 'Client ID' },
+ 'liteTopic.clientAddress': { zh: '客户端地址', en: 'Client Address' },
+ 'liteTopic.parentTopic': { zh: '父 Topic', en: 'Parent Topic' },
+ 'liteTopic.consumerGroup': { zh: '消费者组', en: 'Consumer Group' },
+ 'liteTopic.createTime': { zh: '创建时间', en: 'Create Time' },
+ 'liteTopic.ttl': { zh: 'TTL', en: 'TTL' },
+ 'liteTopic.ttlRemaining': { zh: 'TTL 剩余', en: 'TTL Remaining' },
+ 'liteTopic.totalMessages': { zh: '总消息数', en: 'Total Messages' },
+ 'liteTopic.consumedMessages': { zh: '已消费消息', en: 'Consumed Messages' },
+ 'liteTopic.pendingMessages': { zh: '待消费消息', en: 'Pending Messages' },
+ 'liteTopic.consumptionRate': { zh: '消费速率', en: 'Consumption Rate' },
+ 'liteTopic.active': { zh: '活跃', en: 'Active' },
+ 'liteTopic.expiringSoon': { zh: '即将过期', en: 'Expiring Soon' },
+ 'liteTopic.expired': { zh: '已过期', en: 'Expired' },
+ 'liteTopic.unknown': { zh: '未知', en: 'Unknown' },
+ 'liteTopic.notSupported': {
+ zh: '当前集群不支持 LiteTopic,请升级到 RocketMQ 5.x',
+ en: 'LiteTopic is not supported on the current cluster. Please upgrade to
RocketMQ 5.x',
+ },
+ 'liteTopic.fetchListFailed': {
+ zh: '获取 LiteTopic 列表失败',
+ en: 'Failed to fetch LiteTopic list',
+ },
+ 'liteTopic.fetchQuotaFailed': { zh: '获取配额信息失败', en: 'Failed to fetch quota
information' },
+ 'liteTopic.fetchSessionFailed': { zh: '获取会话详情失败', en: 'Failed to fetch
session detail' },
+ 'liteTopic.extendTtlSuccess': { zh: 'TTL 延长成功', en: 'TTL extended
successfully' },
+ 'liteTopic.extendTtlFailed': { zh: 'TTL 延长失败', en: 'Failed to extend TTL' },
+ 'liteTopic.total': { zh: '共 {total} 条记录', en: 'Total {total} records' },
+
+ // ─── Producer ───
+ 'producer.title': { zh: '生产者', en: 'Producer' },
+ 'producer.group': { zh: '生产组', en: 'Producer Group' },
+
+ // ─── Common (additional) ───
+ 'common.loading': { zh: '加载中', en: 'Loading' },
+ 'common.refresh': { zh: '刷新', en: 'Refresh' },
+ 'common.logout': { zh: '退出', en: 'Logout' },
+ 'common.submit': { zh: '提交', en: 'Submit' },
+ 'common.add': { zh: '新增', en: 'Add' },
+ 'common.update': { zh: '更新', en: 'Update' },
+ 'common.modify': { zh: '修改', en: 'Modify' },
+ 'common.view': { zh: '查看', en: 'View' },
+ 'common.confirmDelete': { zh: '确认删除', en: 'Confirm Delete' },
+ 'common.areYouSureToDelete': { zh: '您确定要删除吗?', en: 'Are you sure to delete?'
},
+ 'common.deleteSuccess': { zh: '删除成功', en: 'Delete Successful' },
+ 'common.operationFailed': { zh: '操作失败', en: 'Operation Failed' },
+ 'common.formValidationFailed': { zh: '表单验证失败', en: 'Form Validation Failed'
},
+ 'common.fetchDataFailed': { zh: '获取数据失败', en: 'Failed to fetch data' },
+ 'common.refreshSuccess': { zh: '刷新成功', en: 'Refresh Successful' },
+ 'common.refreshFailed': { zh: '刷新失败', en: 'Refresh Failed' },
+ 'common.cannotBeEmpty': { zh: '不能为空', en: 'Cannot be empty' },
+ 'common.pleaseInputNumber': { zh: '请输入数字', en: 'Please input a number' },
+ 'common.pleaseSelect': { zh: '请选择', en: 'Please select' },
+ 'common.pleaseInput': { zh: '请输入', en: 'Please input' },
+ 'common.endTimeLaterThanBegin': {
+ zh: '结束时间应晚于开始时间',
+ en: 'End time should be later than begin time',
+ },
+ 'common.synchronize': { zh: '同步', en: 'Synchronize' },
+ 'common.show': { zh: '显示', en: 'Show' },
+ 'common.hide': { zh: '隐藏', en: 'Hide' },
+ 'common.readMore': { zh: '阅读更多', en: 'Read More' },
+ 'common.queue': { zh: '队列', en: 'Queue' },
+ 'common.result': { zh: '结果', en: 'Result' },
+ 'common.date': { zh: '日期', en: 'Date' },
+ 'common.begin': { zh: '开始', en: 'Begin' },
+ 'common.end': { zh: '结束', en: 'End' },
+ 'common.selectProxy': { zh: '选择代理', en: 'Select Proxy' },
+ 'common.enableProxy': { zh: '启用代理', en: 'Enable Proxy' },
+ 'common.proxyDisabled': { zh: '代理禁用', en: 'Proxy Disabled' },
+ 'common.proxyEnabled': { zh: '代理启用', en: 'Proxy Enabled' },
+ 'common.menu': { zh: '菜单', en: 'Menu' },
+ 'common.cluster': { zh: '集群', en: 'Cluster' },
+ 'common.broker': { zh: 'Broker', en: 'Broker' },
+ 'common.proxy': { zh: '代理', en: 'Proxy' },
+ 'common.topic': { zh: '主题', en: 'Topic' },
+ 'common.consumer': { zh: '消费者', en: 'Consumer' },
+ 'common.producer': { zh: '生产者', en: 'Producer' },
+ 'common.message': { zh: '消息', en: 'Message' },
+ 'common.config': { zh: '配置', en: 'Config' },
+ 'common.operation': { zh: '操作', en: 'Operation' },
+ 'common.manage': { zh: '管理', en: 'Manage' },
+ 'common.router': { zh: '路由', en: 'Router' },
+ 'common.quantity': { zh: '数量', en: 'Quantity' },
+ 'common.mode': { zh: '模式', en: 'Mode' },
+ 'common.delay': { zh: '延迟', en: 'Delay' },
+ 'common.trend': { zh: '趋势', en: 'Trend' },
+ 'common.split': { zh: '分片', en: 'Split' },
+ 'common.instance': { zh: '实例', en: 'Instance' },
+ 'common.warning': { zh: '警告', en: 'Warning' },
+ 'common.na': { zh: 'N/A', en: 'N/A' },
+ 'common.default': { zh: '默认', en: 'Default' },
};
export default translations;
diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts
new file mode 100644
index 0000000..3bcbc09
--- /dev/null
+++ b/web/src/test/setup.ts
@@ -0,0 +1,6 @@
+/// <reference types="vitest/globals" />
+import '@testing-library/jest-dom/vitest';
+
+beforeEach(() => {
+ localStorage.clear();
+});
diff --git a/web/vite.config.ts b/web/vite.config.ts
index 8aef668..f8d5b64 100644
--- a/web/vite.config.ts
+++ b/web/vite.config.ts
@@ -1,4 +1,4 @@
-import { defineConfig } from 'vite';
+import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
@@ -12,4 +12,10 @@ export default defineConfig({
},
},
},
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ setupFiles: './src/test/setup.ts',
+ css: true,
+ },
});