diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aa68ab6d..7da007d9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -210,9 +210,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.42.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.42.1.tgz", - "integrity": "sha512-ToN3oFc0nsxNUYVF5P0ztLgbC4UPPjPtA9aKYhkOKQaZASpOUo6ISXyQLP66ctVwlDc+j6Jv0uK5IFALkiXztg==", + "version": "6.43.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.0.tgz", + "integrity": "sha512-V7ZCLQO3Jus9hzh2jVCCPW3mO4IBMr43O37PqSUYautJSnnJF41YlgLw21x0fLJTYvJ+Vkm6Gp+qKGH9pltgXA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.6.0", @@ -610,9 +610,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.129.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", - "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", + "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", "dev": true, "license": "MIT", "funding": { @@ -620,9 +620,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", - "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", + "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", "cpu": [ "arm64" ], @@ -637,9 +637,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", "cpu": [ "arm64" ], @@ -654,9 +654,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", - "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", + "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", "cpu": [ "x64" ], @@ -671,9 +671,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", - "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", "cpu": [ "x64" ], @@ -688,9 +688,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", - "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", "cpu": [ "arm" ], @@ -705,9 +705,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", - "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", "cpu": [ "arm64" ], @@ -725,9 +725,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", - "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", "cpu": [ "arm64" ], @@ -745,9 +745,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", - "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", "cpu": [ "ppc64" ], @@ -765,9 +765,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", - "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", "cpu": [ "s390x" ], @@ -785,9 +785,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", - "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", "cpu": [ "x64" ], @@ -805,9 +805,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", - "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", "cpu": [ "x64" ], @@ -825,9 +825,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", - "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", + "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", "cpu": [ "arm64" ], @@ -842,9 +842,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", - "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", + "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", "cpu": [ "wasm32" ], @@ -861,9 +861,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", - "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", "cpu": [ "arm64" ], @@ -878,9 +878,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", - "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", "cpu": [ "x64" ], @@ -2715,14 +2715,14 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", - "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", + "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.129.0", - "@rolldown/pluginutils": "1.0.0" + "@oxc-project/types": "=0.130.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -2731,27 +2731,27 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0", - "@rolldown/binding-darwin-arm64": "1.0.0", - "@rolldown/binding-darwin-x64": "1.0.0", - "@rolldown/binding-freebsd-x64": "1.0.0", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", - "@rolldown/binding-linux-arm64-gnu": "1.0.0", - "@rolldown/binding-linux-arm64-musl": "1.0.0", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0", - "@rolldown/binding-linux-s390x-gnu": "1.0.0", - "@rolldown/binding-linux-x64-gnu": "1.0.0", - "@rolldown/binding-linux-x64-musl": "1.0.0", - "@rolldown/binding-openharmony-arm64": "1.0.0", - "@rolldown/binding-wasm32-wasi": "1.0.0", - "@rolldown/binding-win32-arm64-msvc": "1.0.0", - "@rolldown/binding-win32-x64-msvc": "1.0.0" + "@rolldown/binding-android-arm64": "1.0.1", + "@rolldown/binding-darwin-arm64": "1.0.1", + "@rolldown/binding-darwin-x64": "1.0.1", + "@rolldown/binding-freebsd-x64": "1.0.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", + "@rolldown/binding-linux-arm64-gnu": "1.0.1", + "@rolldown/binding-linux-arm64-musl": "1.0.1", + "@rolldown/binding-linux-ppc64-gnu": "1.0.1", + "@rolldown/binding-linux-s390x-gnu": "1.0.1", + "@rolldown/binding-linux-x64-gnu": "1.0.1", + "@rolldown/binding-linux-x64-musl": "1.0.1", + "@rolldown/binding-openharmony-arm64": "1.0.1", + "@rolldown/binding-wasm32-wasi": "1.0.1", + "@rolldown/binding-win32-arm64-msvc": "1.0.1", + "@rolldown/binding-win32-x64-msvc": "1.0.1" } }, "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", - "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -2964,16 +2964,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", - "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", - "rolldown": "1.0.0", + "rolldown": "1.0.1", "tinyglobby": "^0.2.16" }, "bin": { diff --git a/frontend/src/api/axios-init.js b/frontend/src/api/axios-init.js index 2ea235c5..3055e883 100644 --- a/frontend/src/api/axios-init.js +++ b/frontend/src/api/axios-init.js @@ -51,7 +51,12 @@ export function setupAxios() { axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; - const basePath = window.X_UI_BASE_PATH; + // Read base path from window object or fallback to meta tag (for Cloudflare Rocket Loader compatibility) + let basePath = window.X_UI_BASE_PATH; + if (!basePath) { + const metaTag = document.querySelector('meta[name="base-path"]'); + basePath = metaTag ? metaTag.getAttribute('content') : null; + } if (typeof basePath === 'string' && basePath !== '' && basePath !== '/') { axios.defaults.baseURL = basePath; } diff --git a/frontend/src/entries/api-docs.js b/frontend/src/entries/api-docs.js index 852bbc41..c2baff6c 100644 --- a/frontend/src/entries/api-docs.js +++ b/frontend/src/entries/api-docs.js @@ -4,7 +4,7 @@ import 'ant-design-vue/dist/reset.css'; import { setupAxios } from '@/api/axios-init.js'; import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import ApiDocsPage from '@/pages/api-docs/ApiDocsPage.vue'; @@ -16,4 +16,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(ApiDocsPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(ApiDocsPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/inbounds.js b/frontend/src/entries/inbounds.js index 8342f476..dd9b8170 100644 --- a/frontend/src/entries/inbounds.js +++ b/frontend/src/entries/inbounds.js @@ -4,7 +4,7 @@ import 'ant-design-vue/dist/reset.css'; import { setupAxios } from '@/api/axios-init.js'; import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import InboundsPage from '@/pages/inbounds/InboundsPage.vue'; @@ -16,4 +16,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(InboundsPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(InboundsPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/index.js b/frontend/src/entries/index.js index 8e14d2ae..33593f31 100644 --- a/frontend/src/entries/index.js +++ b/frontend/src/entries/index.js @@ -6,7 +6,7 @@ import { setupAxios } from '@/api/axios-init.js'; // Importing useTheme triggers the boot side-effect that applies the // stored theme to / before Vue mounts. import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import IndexPage from '@/pages/index/IndexPage.vue'; @@ -18,4 +18,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(IndexPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(IndexPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/login.js b/frontend/src/entries/login.js index 6b8b0fc2..33c1e629 100644 --- a/frontend/src/entries/login.js +++ b/frontend/src/entries/login.js @@ -6,18 +6,18 @@ import { setupAxios } from '@/api/axios-init.js'; // Importing this module triggers the boot side-effect that applies the // stored theme to / before Vue renders anything. import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import LoginPage from '@/pages/login/LoginPage.vue'; setupAxios(); applyDocumentTitle(); -// Toasts attach to a #message div the page provides — keeps theme -// styling in sync with the rest of the panel. const messageContainer = document.getElementById('message'); if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(LoginPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(LoginPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/nodes.js b/frontend/src/entries/nodes.js index 819fe9a9..92e60a15 100644 --- a/frontend/src/entries/nodes.js +++ b/frontend/src/entries/nodes.js @@ -4,7 +4,7 @@ import 'ant-design-vue/dist/reset.css'; import { setupAxios } from '@/api/axios-init.js'; import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import NodesPage from '@/pages/nodes/NodesPage.vue'; @@ -16,4 +16,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(NodesPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(NodesPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/settings.js b/frontend/src/entries/settings.js index 4a32bb7e..ca3e6f29 100644 --- a/frontend/src/entries/settings.js +++ b/frontend/src/entries/settings.js @@ -6,7 +6,7 @@ import { setupAxios } from '@/api/axios-init.js'; // Importing useTheme triggers the boot side-effect that applies the // stored theme to / before Vue mounts. import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import SettingsPage from '@/pages/settings/SettingsPage.vue'; @@ -18,4 +18,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(SettingsPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(SettingsPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/subpage.js b/frontend/src/entries/subpage.js index 159aee1b..a2b0d8bb 100644 --- a/frontend/src/entries/subpage.js +++ b/frontend/src/entries/subpage.js @@ -7,7 +7,7 @@ import 'ant-design-vue/dist/reset.css'; // with the parsed traffic/quota/expiry view-model and the rendered // share links — the SPA reads those at mount. import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import SubPage from '@/pages/sub/SubPage.vue'; const messageContainer = document.getElementById('message'); @@ -15,4 +15,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(SubPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(SubPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/entries/xray.js b/frontend/src/entries/xray.js index ba203da0..a5b1ebcd 100644 --- a/frontend/src/entries/xray.js +++ b/frontend/src/entries/xray.js @@ -4,7 +4,7 @@ import 'ant-design-vue/dist/reset.css'; import { setupAxios } from '@/api/axios-init.js'; import '@/composables/useTheme.js'; -import { i18n } from '@/i18n/index.js'; +import { i18n, readyI18n } from '@/i18n/index.js'; import { applyDocumentTitle } from '@/utils'; import XrayPage from '@/pages/xray/XrayPage.vue'; @@ -16,4 +16,6 @@ if (messageContainer) { message.config({ getContainer: () => messageContainer }); } -createApp(XrayPage).use(Antd).use(i18n).mount('#app'); +readyI18n().then(() => { + createApp(XrayPage).use(Antd).use(i18n).mount('#app'); +}); diff --git a/frontend/src/i18n/index.js b/frontend/src/i18n/index.js index 1e21d215..7b13784c 100644 --- a/frontend/src/i18n/index.js +++ b/frontend/src/i18n/index.js @@ -1,93 +1,54 @@ -// vue-i18n setup. Locale files live in web/translation/*.json — the same -// directory the Go binary embeds, so SPA + Telegram bot + subscription -// page all read from a single source. -// -// Usage in a component: -// import { useI18n } from 'vue-i18n'; -// const { t } = useI18n(); -// ... -// {{ t('pages.inbounds.email') }} -// -// Or via the global helper exposed on the app: -// {{ $t('pages.inbounds.email') }} -// -// The locale follows the `lang` cookie that LanguageManager already -// reads/writes — switching language anywhere in the app continues to -// trigger a full page reload (matches legacy ergonomics), so we don't -// need a runtime locale switcher here. - import { createI18n } from 'vue-i18n'; import { LanguageManager } from '@/utils'; +import enUS from '../../../web/translation/en-US.json'; -// Lazy-loaded locales — Vite splits each one into its own chunk. We -// eager-load only the active language plus the en-US fallback so the -// initial page payload stays small (the inbounds bundle was sitting -// at ~700kB gzipped with all 13 locales eager; now ~480kB). -// -// LanguageManager.setLanguage() does a full reload on change, so -// "lazy" here effectively means "load only what this page needs for -// its lifetime." const FALLBACK = 'en-US'; -const lazyModules = import.meta.glob('../../../web/translation/*.json'); -const eagerModules = import.meta.glob('../../../web/translation/*.json', { eager: true }); +const lazyModules = import.meta.glob([ + '../../../web/translation/*.json', + '!../../../web/translation/en-US.json', +]); function moduleKeyFor(code) { return `../../../web/translation/${code}.json`; } -// Resolve the active locale via LanguageManager so the cookie set on -// the legacy panel keeps working after a user upgrades. Falls back -// to en-US when the cookie names a language we don't have. let active = LanguageManager.getLanguage(); -if (!Object.prototype.hasOwnProperty.call(lazyModules, moduleKeyFor(active))) { +if (active !== FALLBACK && !Object.prototype.hasOwnProperty.call(lazyModules, moduleKeyFor(active))) { active = FALLBACK; } -const messages = {}; -// Eagerly include the active locale + the fallback (when distinct) -// so the very first render has strings ready. Vite still emits these -// as their own chunks so the user pays for at most two locales. -for (const code of new Set([active, FALLBACK])) { - const mod = eagerModules[moduleKeyFor(code)]; - if (mod) messages[code] = mod.default || mod; -} - export const i18n = createI18n({ legacy: false, - // `composition` mode (legacy: false) so `useI18n()` works in - //