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,
+  },
 });


Reply via email to