2 Commits

Author SHA1 Message Date
GL.iNet-Yongping.Xie cbe724f425 fix: keep i18n messages reactive after language switch
Form validation messages and imperative t() toasts (e.g. delete-user
success) no longer stay stuck in the previous language.

Signed-off-by: GL.iNet-Yongping.Xie <yongping.xie@gl-inet.com>
2026-04-23 00:05:19 -07:00
GL.iNet-Yongping.Xie 53b7ee4e9e fix: render modal Confirm/Cancel buttons in all locales
Route button labels through the project's vue-i18n; the upstream
fallback only knew en/zh and crashed the footer in de/es/fr/ja/ko.

Signed-off-by: GL.iNet-Yongping.Xie <yongping.xie@gl-inet.com>
2026-04-22 21:01:27 -07:00
31 changed files with 114 additions and 69 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
package server
const RttysVersion = "5.2.0"
const KVMCloudVersion = "v2.5.0"
const KVMCloudVersion = "v2.6.0"
var (
GitCommit = ""
+31
View File
@@ -0,0 +1,31 @@
<!--
* @Description: Wraps gl-web-main's BaseModal with project-i18n defaults for
* okText / cancelText. The upstream BaseModal falls back to its own internal
* i18n table when these props are omitted, but that table only contains
* zh/en — any other locale throws at render time and the entire footer slot
* fails to mount, so Confirm/Cancel buttons disappear in de/es/fr/ja/ko.
* Defaulting the props here routes labels through the project's vue-i18n,
* which knows every supported locale.
-->
<template>
<BaseModal
:cancelText="cancelText"
:okText="okText"
>
<template v-for="(_, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>
</BaseModal>
</template>
<script setup lang="ts">
import { BaseModal } from 'gl-web-main/components'
withDefaults(defineProps<{
okText?: string
cancelText?: string
}>(), {
okText: 'common.confirm',
cancelText: 'common.cancel',
})
</script>
+2 -1
View File
@@ -38,4 +38,5 @@ export default function useLanguage () {
return {currentLang, setLanguage, isZh, t: i18n.global.t, currentLangLabel}
}
export const t = i18n.global.t
export const t = ((...args: any[]) =>
(i18n.global.t as any)(...args)) as typeof i18n.global.t
+1
View File
@@ -12,6 +12,7 @@
"success": "Erfolgreich",
"failed": "Fehlgeschlagen",
"cancel": "Abbrechen",
"confirm": "Bestätigen",
"ok": "OK",
"close": "Schließen",
"about": "Über",
+1
View File
@@ -12,6 +12,7 @@
"success": "Success",
"failed": "Failed",
"cancel": "Cancel",
"confirm": "Confirm",
"ok": "OK",
"close": "Close",
"about": "About",
+1
View File
@@ -12,6 +12,7 @@
"success": "Éxito",
"failed": "Error",
"cancel": "Cancelar",
"confirm": "Confirmar",
"ok": "Aceptar",
"close": "Cerrar",
"about": "Acerca de",
+1
View File
@@ -12,6 +12,7 @@
"success": "Succès",
"failed": "Échec",
"cancel": "Annuler",
"confirm": "Confirmer",
"ok": "OK",
"close": "Fermer",
"about": "À propos",
+1
View File
@@ -12,6 +12,7 @@
"success": "成功",
"failed": "失敗",
"cancel": "キャンセル",
"confirm": "確認",
"ok": "OK",
"close": "閉じる",
"about": "このサービスについて",
+1
View File
@@ -12,6 +12,7 @@
"success": "성공",
"failed": "실패",
"cancel": "취소",
"confirm": "확인",
"ok": "확인",
"close": "닫기",
"about": "정보",
+1
View File
@@ -12,6 +12,7 @@
"success": "成功",
"failed": "失败",
"cancel": "取消",
"confirm": "确认",
"ok": "确定",
"close": "关闭",
"about": "关于",
@@ -40,8 +40,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -65,7 +65,7 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
ip: [{
trigger: 'blur',
validator: async (_: any, value: string) => {
@@ -86,7 +86,7 @@ const formRules: FormRules = {
return Promise.reject(t('device.portNotCorrect'))
},
}],
}
}))
/** 提交 */
const handleApply: OnBeforeOk = (done) => {
formRef.value.validate().then(() => {
@@ -41,7 +41,8 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue'
import { BaseInfo, BaseModal, BaseRadioButtonsCompact } from 'gl-web-main/components'
import { BaseInfo, BaseRadioButtonsCompact } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { getAddDeviceScriptInfoApi } from '@/api/device'
import { copyText } from 'gl-web-main'
import { message } from 'ant-design-vue'
@@ -191,10 +192,10 @@ esac
exit 0
EOF
# 4. Add execution permissions
# 5. Add execution permissions
chmod +x "$SCRIPT_FILE"
# 5. Execute restart once
# 6. Execute restart once
"$SCRIPT_FILE" restart
`
@@ -38,8 +38,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -62,11 +62,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
name: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
}
}))
/** 提交 */
const handleApply: OnBeforeOk = (done) => {
@@ -26,7 +26,7 @@
</template>
<script setup lang="ts">
import { BaseModal } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
const props = defineProps<{ open: boolean, type: string, res: any }>()
@@ -80,7 +80,7 @@ import BaseTable from '@/components/base/baseTable.vue'
import { t } from '@/hooks/useLanguage'
import { DeviceInfo, ExecuteCommandFormData } from '@/models/device'
import { useFakeUpgradeProgress } from 'gl-web-main'
import { BaseModal } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { TableColumnType } from 'ant-design-vue'
import { computed, reactive, watch } from 'vue'
import CmdResDetailDialog from './cmdResDetailDialog.vue'
@@ -30,8 +30,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk, useValidateInfo } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -55,12 +55,12 @@ const state = reactive<{formData: { description: string }}>({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
description: [
{ required: true, message: t('device.requiredDescription'), trigger: 'change' },
{ max: 256, message: t('common.maxLength', { length: 256 }), trigger: 'change' },
],
}
}))
/** 提交 */
const handleApply: OnBeforeOk = (done) => {
@@ -63,8 +63,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk, useValidateInfo } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { DeviceInfo, ExecuteCommandFormData } from '@/models/device'
@@ -91,10 +91,10 @@ const state = reactive<{formData: ExecuteCommandFormData}>({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
username: [{ required: true, message: t('device.requiredUsername'), trigger: 'change' }],
cmd: [{ required: true, message: t('device.requiredCommand'), trigger: 'change' }],
}
}))
/** 提交 */
const handleApply: OnBeforeOk = (done) => {
@@ -49,8 +49,9 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseInfo, BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import { BaseInfo } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -76,11 +77,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
groupId: [
{ required: true, message: t('device.requiredDeviceGroup'), trigger: 'change' },
],
}
}))
const getDeviceGroupListOptions = async () => {
const res = await reqDeviceGroupListOptions()
+1 -1
View File
@@ -44,7 +44,7 @@ import '@xterm/xterm/css/xterm.css'
import OverlayAddon from './components/xterm-addon-overlay'
import ContextMenu from './components/contextMenu.vue'
import RttyKeyboard from './components/rttyKeyboard.vue'
import { BaseModal } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { message, Modal } from 'ant-design-vue'
import { t } from '@/hooks/useLanguage'
@@ -96,7 +96,8 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'
import { BaseInfo, BaseModal } from 'gl-web-main/components'
import { BaseInfo } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, macAddressFormatter, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance, TableColumnType } from 'ant-design-vue'
@@ -131,11 +132,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
name: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
}
}))
/** 获取用户组下拉选项 */
const getUserGroupListOptions = async () => {
@@ -69,7 +69,8 @@
<script setup lang="ts">
import { computed, h, reactive, watch } from 'vue'
import { BaseInfo, BaseModal } from 'gl-web-main/components'
import { BaseInfo } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { baseCustomModal, macAddressFormatter, OnBeforeOk } from 'gl-web-main'
import BaseLoadingContainer from '@/components/base/baseLoadingContainer.vue'
import { useDeviceStore } from '@/stores/modules/device'
@@ -47,8 +47,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -75,11 +75,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
name: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
}
}))
/** 获取用户组下拉选项 */
const getUserGroupListOptions = async () => {
@@ -66,7 +66,8 @@
<script setup lang="ts">
import { computed, h, reactive, watch } from 'vue'
import { BaseInfo, BaseModal } from 'gl-web-main/components'
import { BaseInfo } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { baseCustomModal, macAddressFormatter, PageLink } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { message, TableColumnType } from 'ant-design-vue'
@@ -32,8 +32,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { FormInstance } from 'ant-design-vue'
import { reqDisable2fa } from '@/api/personal'
@@ -54,12 +54,12 @@ const state = reactive({
},
})
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
code: [
{ required: true, message: t('personalCenter.enterVerifyCode'), trigger: 'change' },
{ len: 6, message: t('personalCenter.enterVerifyCode'), trigger: 'change' },
],
}
}))
watch(() => props.open, (open) => {
if (open) state.formData.code = ''
@@ -31,8 +31,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { FormInstance, message } from 'ant-design-vue'
import { reqUpdateProfile } from '@/api/personal'
@@ -54,11 +54,11 @@ const state = reactive({
},
})
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
displayName: [
{ max: 200, message: t('common.maxLength', { length: 200 }), trigger: 'change' },
],
}
}))
watch(() => props.open, (open) => {
if (open) {
@@ -53,8 +53,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { FormInstance } from 'ant-design-vue'
import QRCode from 'qrcode'
@@ -81,12 +81,12 @@ const state = reactive({
},
})
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
code: [
{ required: true, message: t('personalCenter.enterVerifyCode'), trigger: 'change' },
{ len: 6, message: t('personalCenter.enterVerifyCode'), trigger: 'change' },
],
}
}))
const setup = async () => {
state.loading = true
@@ -80,8 +80,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -113,7 +113,7 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
username: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
@@ -130,7 +130,7 @@ const formRules: FormRules = {
return Promise.reject(t('login.confirmPasswordValidateError'))
},
}],
}
}))
/** 获取用户组下拉选项 */
const getUserGroupListOptions = async () => {
@@ -40,8 +40,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance, message } from 'ant-design-vue'
@@ -65,11 +65,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
name: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
}
}))
/** 提交 */
const handleApply: OnBeforeOk = (done) => {
@@ -40,8 +40,9 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseInfo, BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import { BaseInfo } from 'gl-web-main/components'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance } from 'ant-design-vue'
@@ -66,11 +67,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
deviceGroupIds: [
{ required: true, message: t('device.requiredDeviceGroup'), trigger: 'change' },
],
}
}))
const getDeviceGroupListOptions = async () => {
const res = await reqDeviceGroupListOptions()
@@ -95,8 +95,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance, Tooltip } from 'ant-design-vue'
@@ -131,7 +131,7 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
username: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
@@ -144,7 +144,7 @@ const formRules: FormRules = {
return Promise.reject(t('login.confirmPasswordValidateError'))
},
}],
}
}))
/** 获取用户组下拉选项 */
const getUserGroupListOptions = async () => {
@@ -40,8 +40,8 @@
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { BaseModal } from 'gl-web-main/components'
import { computed, reactive, ref, watch } from 'vue'
import BaseModal from '@/components/base/baseModalI18n.vue'
import { FormRules, OnBeforeOk } from 'gl-web-main'
import { t } from '@/hooks/useLanguage'
import { FormInstance, message } from 'ant-design-vue'
@@ -66,11 +66,11 @@ const state = reactive({
})
/** 表单验证 */
const formRules: FormRules = {
const formRules = computed<FormRules>(() => ({
name: [
{ required: true, message: t('device.requiredDeviceGroupName'), trigger: 'change' },
],
}
}))
/** 提交 */
const handleApply: OnBeforeOk = (done) => {