Przeglądaj źródła

国际化整体基本完成

Huanyi 1 tydzień temu
rodzic
commit
9bfde896b6
50 zmienionych plików z 1856 dodań i 584 usunięć
  1. 2 0
      src/api/system/dict/data/types.ts
  2. 2 0
      src/api/system/menu/types.ts
  3. 1 1
      src/assets/styles/variables.module.scss
  4. 17 4
      src/components/Breadcrumb/index.vue
  5. 11 1
      src/components/DictTag/index.vue
  6. 85 6
      src/components/Editor/index.vue
  7. 15 12
      src/components/FileUpload/index.vue
  8. 5 2
      src/components/IconSelect/index.vue
  9. 14 11
      src/components/ImageUpload/index.vue
  10. 34 34
      src/components/Process/submitVerify.vue
  11. 18 15
      src/components/RoleSelect/index.vue
  12. 1 1
      src/components/TopNav/index.vue
  13. 20 17
      src/components/UserSelect/index.vue
  14. 13 3
      src/lang/en_US.ts
  15. 0 159
      src/lang/modules/README.md
  16. 119 0
      src/lang/modules/components/en_US.ts
  17. 119 0
      src/lang/modules/components/zh_CN.ts
  18. 36 0
      src/lang/modules/setting/applet/en_US.ts
  19. 36 0
      src/lang/modules/setting/applet/zh_CN.ts
  20. 6 0
      src/lang/modules/setting/index.ts
  21. 6 0
      src/lang/modules/setting/index_en.ts
  22. 5 1
      src/lang/modules/system/index.ts
  23. 5 1
      src/lang/modules/system/index_en.ts
  24. 92 0
      src/lang/modules/system/menu/en_US.ts
  25. 92 0
      src/lang/modules/system/menu/zh_CN.ts
  26. 75 0
      src/lang/modules/system/profile/en_US.ts
  27. 75 0
      src/lang/modules/system/profile/zh_CN.ts
  28. 5 0
      src/lang/modules/tool/en_US.ts
  29. 180 0
      src/lang/modules/tool/gen/en_US.ts
  30. 180 0
      src/lang/modules/tool/gen/zh_CN.ts
  31. 5 0
      src/lang/modules/tool/zh_CN.ts
  32. 15 5
      src/lang/zh_CN.ts
  33. 5 3
      src/layout/components/Navbar.vue
  34. 15 2
      src/layout/components/Sidebar/SidebarItem.vue
  35. 11 1
      src/layout/components/TagsView/index.vue
  36. 2 2
      src/router/index.ts
  37. 22 0
      src/utils/i18n.ts
  38. 102 14
      src/views/setting/applet/index.vue
  39. 36 7
      src/views/system/dict/data.vue
  40. 116 77
      src/views/system/menu/index.vue
  41. 19 2
      src/views/system/role/index.vue
  42. 12 12
      src/views/system/user/profile/index.vue
  43. 12 10
      src/views/system/user/profile/onlineDevice.vue
  44. 18 16
      src/views/system/user/profile/resetPwd.vue
  45. 17 15
      src/views/system/user/profile/userInfo.vue
  46. 19 16
      src/views/tool/gen/basicInfoForm.vue
  47. 32 30
      src/views/tool/gen/editTable.vue
  48. 69 48
      src/views/tool/gen/genInfoForm.vue
  49. 19 17
      src/views/tool/gen/importTable.vue
  50. 41 39
      src/views/tool/gen/index.vue

+ 2 - 0
src/api/system/dict/data/types.ts

@@ -18,6 +18,8 @@ export interface DictDataForm {
   dictType?: string;
   dictCode: string | undefined;
   dictLabel: string;
+  dictLabelZh?: string;
+  dictLabelEn?: string;
   dictValue: string;
   cssClass: string;
   listClass: ElTagType;

+ 2 - 0
src/api/system/menu/types.ts

@@ -53,6 +53,8 @@ export interface MenuForm {
   children?: MenuForm[];
   menuId?: string | number;
   menuName: string;
+  menuNameZh?: string;
+  menuNameEn?: string;
   orderNum: number;
   path: string;
   component?: string;

+ 1 - 1
src/assets/styles/variables.module.scss

@@ -117,7 +117,7 @@ $--color-warning: #e6a23c;
 $--color-danger: #f56c6c;
 $--color-info: #909399;
 
-$base-sidebar-width: 200px;
+$base-sidebar-width: 240px;
 
 // the :export directive is the magic sauce for webpack
 // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass

+ 17 - 4
src/components/Breadcrumb/index.vue

@@ -1,9 +1,9 @@
 <template>
   <el-breadcrumb class="app-breadcrumb" separator="/">
     <transition-group name="breadcrumb">
-      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
-        <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta?.title }}</span>
-        <a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
+      <el-breadcrumb-item v-for="(item, index) in parsedLevelList" :key="item.path">
+        <span v-if="item.redirect === 'noRedirect' || index == parsedLevelList.length - 1" class="no-redirect">{{ item.displayTitle }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.displayTitle }}</a>
       </el-breadcrumb-item>
     </transition-group>
   </el-breadcrumb>
@@ -12,12 +12,25 @@
 <script setup lang="ts">
 import { RouteLocationMatched } from 'vue-router';
 import { usePermissionStore } from '@/store/modules/permission';
+import { parseI18nName } from '@/utils/i18n';
+import { useI18n } from 'vue-i18n';
 
 const route = useRoute();
 const router = useRouter();
 const permissionStore = usePermissionStore();
+const { locale } = useI18n();
 const levelList = ref<RouteLocationMatched[]>([]);
 
+// 使用计算属性缓存解析后的面包屑列表,添加 locale 依赖确保语言切换时更新
+const parsedLevelList = computed(() => {
+  // 访问 locale.value 以建立响应式依赖
+  const _ = locale.value;
+  return levelList.value.map(item => ({
+    ...item,
+    displayTitle: parseI18nName(item.meta?.title)
+  }));
+});
+
 const getBreadcrumb = () => {
   // only show routes with meta.title
   let matched = [];
@@ -35,7 +48,7 @@ const getBreadcrumb = () => {
   }
   // 判断是否为首页
   if (!isDashboard(matched[0])) {
-    matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched);
+    matched = [{ path: '/index', meta: { title: '{"zh_CN":"首页","en_US":"Dashboard"}' } }].concat(matched);
   }
   levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false);
 };

+ 11 - 1
src/components/DictTag/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <template v-for="(item, index) in options">
+    <template v-for="(item, index) in parsedOptions">
       <template v-if="values.includes(item.value)">
         <span
           v-if="(item.elTagType === 'default' || item.elTagType === '') && (item.elTagClass === '' || item.elTagClass == null)"
@@ -37,6 +37,8 @@
 </template>
 
 <script setup lang="ts">
+import { parseI18nName } from '@/utils/i18n';
+
 interface Props {
   options: Array<DictDataOption>;
   value: number | string | Array<number | string>;
@@ -48,6 +50,14 @@ const props = withDefaults(defineProps<Props>(), {
   separator: ','
 });
 
+// 解析国际化标签
+const parsedOptions = computed(() => {
+  return props.options.map(option => ({
+    ...option,
+    label: parseI18nName(option.label)
+  }));
+});
+
 const values = computed(() => {
   if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
   return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator);

+ 85 - 6
src/components/Editor/index.vue

@@ -32,6 +32,9 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
 import { QuillEditor, Quill } from '@vueup/vue-quill';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 defineEmits(['update:modelValue']);
 
@@ -52,6 +55,81 @@ const props = defineProps({
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
+// 动态注入编辑器工具栏的国际化样式
+const injectEditorStyles = () => {
+  const styleId = 'quill-i18n-styles';
+  let styleEl = document.getElementById(styleId);
+  
+  if (!styleEl) {
+    styleEl = document.createElement('style');
+    styleEl.id = styleId;
+    document.head.appendChild(styleEl);
+  }
+  
+  styleEl.textContent = `
+    .ql-snow .ql-tooltip[data-mode='link']::before {
+      content: '${t('components.editor.toolbar.link')}';
+    }
+    .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
+      border-right: 0;
+      content: '${t('components.editor.toolbar.save')}';
+      padding-right: 0;
+    }
+    .ql-snow .ql-tooltip[data-mode='video']::before {
+      content: '${t('components.editor.toolbar.video')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item::before {
+      content: '${t('components.editor.toolbar.text')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
+      content: '${t('components.editor.toolbar.heading1')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
+      content: '${t('components.editor.toolbar.heading2')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
+      content: '${t('components.editor.toolbar.heading3')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
+      content: '${t('components.editor.toolbar.heading4')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
+      content: '${t('components.editor.toolbar.heading5')}';
+    }
+    .ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
+    .ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
+      content: '${t('components.editor.toolbar.heading6')}';
+    }
+    .ql-snow .ql-picker.ql-font .ql-picker-label::before,
+    .ql-snow .ql-picker.ql-font .ql-picker-item::before {
+      content: '${t('components.editor.toolbar.normalFont')}';
+    }
+    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
+    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
+      content: '${t('components.editor.toolbar.serifFont')}';
+    }
+    .ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
+    .ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
+      content: '${t('components.editor.toolbar.monospaceFont')}';
+    }
+  `;
+};
+
+onMounted(() => {
+  injectEditorStyles();
+});
+
+// 监听语言切换,重新注入样式
+watch(() => proxy?.$i18n.locale, () => {
+  injectEditorStyles();
+});
+
 const upload = reactive<UploadOption>({
   headers: globalHeaders(),
   url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
@@ -90,7 +168,7 @@ const options = ref<any>({
       }
     }
   },
-  placeholder: '请输入内容',
+  placeholder: t('components.editor.placeholder'),
   readOnly: props.readOnly
 });
 
@@ -116,6 +194,7 @@ watch(
   { immediate: true }
 );
 
+
 // 图片上传成功返回图片地址
 const handleUploadSuccess = (res: any) => {
   // 如果上传成功
@@ -130,7 +209,7 @@ const handleUploadSuccess = (res: any) => {
     quill.setSelection(length + 1);
     proxy?.$modal.closeLoading();
   } else {
-    proxy?.$modal.msgError('图片插入失败');
+    proxy?.$modal.msgError(t('components.editor.imageUploadFailed'));
     proxy?.$modal.closeLoading();
   }
 };
@@ -141,24 +220,24 @@ const handleBeforeUpload = (file: any) => {
   const isJPG = type.includes(file.type);
   //检验文件格式
   if (!isJPG) {
-    proxy?.$modal.msgError(`图片格式错误!`);
+    proxy?.$modal.msgError(t('components.editor.imageFormatError'));
     return false;
   }
   // 校检文件大小
   if (props.fileSize) {
     const isLt = file.size / 1024 / 1024 < props.fileSize;
     if (!isLt) {
-      proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
+      proxy?.$modal.msgError(t('components.editor.fileSizeExceed', { size: props.fileSize }));
       return false;
     }
   }
-  proxy?.$modal.loading('正在上传文件,请稍候...');
+  proxy?.$modal.loading(t('components.editor.uploading'));
   return true;
 };
 
 // 图片失败拦截
 const handleUploadError = (err: any) => {
-  proxy?.$modal.msgError('上传文件失败');
+  proxy?.$modal.msgError(t('components.editor.uploadFailed'));
 };
 </script>
 

+ 15 - 12
src/components/FileUpload/index.vue

@@ -17,18 +17,18 @@
       v-if="!disabled"
     >
       <!-- 上传按钮 -->
-      <el-button type="primary">选取文件</el-button>
+      <el-button type="primary">{{ $t('components.fileUpload.selectFile') }}</el-button>
     </el-upload>
     <!-- 上传提示 -->
     <div v-if="showTip && !disabled" class="el-upload__tip">
-      请上传
+      {{ $t('components.fileUpload.uploadTip') }}
       <template v-if="fileSize">
-        大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
+        {{ $t('components.fileUpload.sizeLimit') }} <b style="color: #f56c6c">{{ fileSize }}MB</b>
       </template>
       <template v-if="fileType">
-        格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
+        {{ $t('components.fileUpload.formatLimit') }} <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
       </template>
-      的文件
+      {{ $t('components.fileUpload.theFile') }}
     </div>
     <!-- 文件列表 -->
     <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
@@ -37,7 +37,7 @@
           <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
         </el-link>
         <div class="ele-upload-list__item-content-action">
-          <el-button type="danger" v-if="!disabled" link @click="handleDelete(index)">删除</el-button>
+          <el-button type="danger" v-if="!disabled" link @click="handleDelete(index)">{{ $t('components.fileUpload.delete') }}</el-button>
         </div>
       </li>
     </transition-group>
@@ -48,6 +48,9 @@
 import { propTypes } from '@/utils/propTypes';
 import { delOss, listByIds } from '@/api/system/oss';
 import { globalHeaders } from '@/utils/request';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 const props = defineProps({
   modelValue: {
@@ -124,36 +127,36 @@ const handleBeforeUpload = (file: any) => {
     const fileExt = fileName[fileName.length - 1];
     const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
     if (!isTypeOk) {
-      proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`);
+      proxy?.$modal.msgError(t('components.fileUpload.formatError', { types: props.fileType.join('/') }));
       return false;
     }
   }
   // 校检文件名是否包含特殊字符
   if (file.name.includes(',')) {
-    proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
+    proxy?.$modal.msgError(t('components.imageUpload.fileNameError'));
     return false;
   }
   // 校检文件大小
   if (props.fileSize) {
     const isLt = file.size / 1024 / 1024 < props.fileSize;
     if (!isLt) {
-      proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
+      proxy?.$modal.msgError(t('components.fileUpload.sizeLimitExceed', { size: props.fileSize }));
       return false;
     }
   }
-  proxy?.$modal.loading('正在上传文件,请稍候...');
+  proxy?.$modal.loading(t('components.imageUpload.uploading'));
   number.value++;
   return true;
 };
 
 // 文件个数超出
 const handleExceed = () => {
-  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
+  proxy?.$modal.msgError(t('components.imageUpload.uploadLimitExceed', { limit: props.limit }));
 };
 
 // 上传失败
 const handleUploadError = () => {
-  proxy?.$modal.msgError('上传文件失败');
+  proxy?.$modal.msgError(t('components.fileUpload.formatError', { types: props.fileType.join('/') }));
 };
 
 // 上传成功回调

+ 5 - 2
src/components/IconSelect/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="relative" :style="{ 'width': width }">
-    <el-input v-model="modelValue" readonly placeholder="点击选择图标" @click="visible = !visible">
+    <el-input v-model="modelValue" readonly :placeholder="t('components.iconSelect.placeholder')" @click="visible = !visible">
       <template #prepend>
         <svg-icon :icon-class="modelValue" />
       </template>
@@ -14,7 +14,7 @@
         </div>
       </template>
 
-      <el-input v-model="filterValue" class="p-2" placeholder="搜索图标" clearable @input="filterIcons" />
+      <el-input v-model="filterValue" class="p-2" :placeholder="t('components.iconSelect.search')" clearable @input="filterIcons" />
 
       <el-scrollbar height="w-[200px]">
         <ul class="icon-list">
@@ -32,6 +32,9 @@
 <script setup lang="ts">
 import icons from '@/components/IconSelect/requireIcons';
 import { propTypes } from '@/utils/propTypes';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 const props = defineProps({
   modelValue: propTypes.string.isRequired,

+ 14 - 11
src/components/ImageUpload/index.vue

@@ -24,17 +24,17 @@
     </el-upload>
     <!-- 上传提示 -->
     <div v-if="showTip" class="el-upload__tip">
-      请上传
+      {{ $t('components.imageUpload.uploadTip') }}
       <template v-if="fileSize">
-        大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
+        {{ $t('components.imageUpload.sizeLimit') }} <b style="color: #f56c6c">{{ fileSize }}MB</b>
       </template>
       <template v-if="fileType">
-        格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
+        {{ $t('components.imageUpload.formatLimit') }} <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
       </template>
-      的文件
+      {{ $t('components.imageUpload.theFile') }}
     </div>
 
-    <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
+    <el-dialog v-model="dialogVisible" :title="$t('components.imageUpload.preview')" width="800px" append-to-body>
       <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
     </el-dialog>
   </div>
@@ -46,6 +46,9 @@ import { OssVO } from '@/api/system/oss/types';
 import { propTypes } from '@/utils/propTypes';
 import { globalHeaders } from '@/utils/request';
 import { compressAccurately } from 'image-conversion';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 const props = defineProps({
   modelValue: {
@@ -140,35 +143,35 @@ const handleBeforeUpload = (file: any) => {
     isImg = file.type.indexOf('image') > -1;
   }
   if (!isImg) {
-    proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`);
+    proxy?.$modal.msgError(t('components.imageUpload.formatError', { types: props.fileType.join('/') }));
     return false;
   }
   if (file.name.includes(',')) {
-    proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
+    proxy?.$modal.msgError(t('components.imageUpload.fileNameError'));
     return false;
   }
   if (props.fileSize) {
     const isLt = file.size / 1024 / 1024 < props.fileSize;
     if (!isLt) {
-      proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
+      proxy?.$modal.msgError(t('components.imageUpload.avatarSizeLimitExceed', { size: props.fileSize }));
       return false;
     }
   }
 
   //压缩图片,开启压缩并且大于指定的压缩大小时才压缩
   if (props.compressSupport && file.size / 1024 > props.compressTargetSize) {
-    proxy?.$modal.loading('正在上传图片,请稍候...');
+    proxy?.$modal.loading(t('components.imageUpload.uploading'));
     number.value++;
     return compressAccurately(file, props.compressTargetSize);
   } else {
-    proxy?.$modal.loading('正在上传图片,请稍候...');
+    proxy?.$modal.loading(t('components.imageUpload.uploading'));
     number.value++;
   }
 };
 
 // 文件个数超出
 const handleExceed = () => {
-  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
+  proxy?.$modal.msgError(t('components.imageUpload.uploadLimitExceed', { limit: props.limit }));
 };
 
 // 上传成功回调

+ 34 - 34
src/components/Process/submitVerify.vue

@@ -1,45 +1,45 @@
 <template>
   <el-dialog v-model="dialog.visible" :title="dialog.title" width="50%" draggable :before-close="cancel" center :close-on-click-modal="false">
     <el-form v-loading="loading" :model="form" label-width="120px">
-      <el-form-item label="消息提醒">
+      <el-form-item :label="$t('components.process.messageReminder')">
         <el-checkbox-group v-model="form.messageType">
-          <el-checkbox value="1" name="type" disabled>站内信</el-checkbox>
-          <el-checkbox value="2" name="type">邮件</el-checkbox>
-          <el-checkbox value="3" name="type">短信</el-checkbox>
+          <el-checkbox value="1" name="type" disabled>{{ $t('components.process.internalMessage') }}</el-checkbox>
+          <el-checkbox value="2" name="type">{{ $t('components.process.email') }}</el-checkbox>
+          <el-checkbox value="3" name="type">{{ $t('components.process.sms') }}</el-checkbox>
         </el-checkbox-group>
       </el-form-item>
-      <el-form-item label="附件">
+      <el-form-item :label="$t('components.process.attachment')">
         <fileUpload v-model="form.fileId" :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']" :file-size="20" />
       </el-form-item>
-      <el-form-item label="抄送" v-if="buttonObj.copy">
+      <el-form-item :label="$t('components.process.copy')" v-if="buttonObj.copy">
         <el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
         <el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
           {{ user.userName }}
         </el-tag>
       </el-form-item>
-      <el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" label="下一步审批人" prop="assigneeMap">
+      <el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" :label="$t('components.process.nextApprover')" prop="assigneeMap">
         <div v-for="(item, index) in nestNodeList" :key="index" style="margin-bottom: 5px; width: 500px">
           <span>【{{ item.nodeName }}】:</span>
           <el-input v-if="false" v-model="form.assigneeMap[item.nodeCode]" />
-          <el-input placeholder="请选择审批人" readonly v-model="nickName[item.nodeCode]">
+          <el-input :placeholder="$t('components.process.selectApproverPlaceholder')" readonly v-model="nickName[item.nodeCode]">
             <template v-slot:append>
-              <el-button @click="choosePeople(item)" icon="search">选择</el-button>
+              <el-button @click="choosePeople(item)" icon="search">{{ $t('components.process.choose') }}</el-button>
             </template>
           </el-input>
         </div>
       </el-form-item>
-      <el-form-item v-if="task.flowStatus === 'waiting'" label="审批意见">
+      <el-form-item v-if="task.flowStatus === 'waiting'" :label="$t('components.process.approvalOpinion')">
         <el-input v-model="form.message" type="textarea" resize="none" />
       </el-form-item>
     </el-form>
     <template #footer>
       <span class="dialog-footer">
-        <el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask"> 提交 </el-button>
+        <el-button :disabled="buttonDisabled" type="primary" @click="handleCompleteTask">{{ $t('components.process.submit') }}</el-button>
         <el-button v-if="task.flowStatus === 'waiting' && buttonObj.trust" :disabled="buttonDisabled" type="primary" @click="openDelegateTask">
-          委托
+          {{ $t('components.process.delegate') }}
         </el-button>
         <el-button v-if="task.flowStatus === 'waiting' && buttonObj.transfer" :disabled="buttonDisabled" type="primary" @click="openTransferTask">
-          转办
+          {{ $t('components.process.transfer') }}
         </el-button>
         <el-button
           v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0 && buttonObj.addSign"
@@ -47,7 +47,7 @@
           type="primary"
           @click="openMultiInstanceUser"
         >
-          加签
+          {{ $t('components.process.addSign') }}
         </el-button>
         <el-button
           v-if="task.flowStatus === 'waiting' && Number(task.nodeRatio) > 0 && buttonObj.subSign"
@@ -55,7 +55,7 @@
           type="primary"
           @click="handleTaskUser"
         >
-          减签
+          {{ $t('components.process.reduceSign') }}
         </el-button>
         <el-button
           v-if="task.flowStatus === 'waiting' && buttonObj.termination"
@@ -63,12 +63,12 @@
           type="danger"
           @click="handleTerminationTask"
         >
-          终止
+          {{ $t('components.process.terminate') }}
         </el-button>
         <el-button v-if="task.flowStatus === 'waiting' && buttonObj.back" :disabled="buttonDisabled" type="danger" @click="handleBackProcessOpen">
-          退回
+          {{ $t('components.process.back') }}
         </el-button>
-        <el-button :disabled="buttonDisabled" @click="cancel">取消</el-button>
+        <el-button :disabled="buttonDisabled" @click="cancel">{{ $t('components.process.cancel') }}</el-button>
       </span>
     </template>
     <!-- 抄送 -->
@@ -83,47 +83,47 @@
     <UserSelect ref="porUserRef" :data="form.assigneeMap[nodeCode]" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
 
     <!-- 驳回开始 -->
-    <el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
+    <el-dialog v-model="backVisible" draggable :title="$t('components.process.backTitle')" width="40%" :close-on-click-modal="false">
       <el-form v-if="task.flowStatus === 'waiting'" v-loading="backLoading" :model="backForm" label-width="120px">
-        <el-form-item label="驳回节点">
-          <el-select v-model="backForm.nodeCode" clearable placeholder="请选择" style="width: 300px">
+        <el-form-item :label="$t('components.process.backNode')">
+          <el-select v-model="backForm.nodeCode" clearable :placeholder="$t('components.process.selectNode')" style="width: 300px">
             <el-option v-for="item in taskNodeList" :key="item.nodeCode" :label="item.nodeName" :value="item.nodeCode" />
           </el-select>
         </el-form-item>
-        <el-form-item label="消息提醒">
+        <el-form-item :label="$t('components.process.messageReminder')">
           <el-checkbox-group v-model="backForm.messageType">
-            <el-checkbox label="1" name="type" disabled>站内信</el-checkbox>
-            <el-checkbox label="2" name="type">邮件</el-checkbox>
-            <el-checkbox label="3" name="type">短信</el-checkbox>
+            <el-checkbox label="1" name="type" disabled>{{ $t('components.process.internalMessage') }}</el-checkbox>
+            <el-checkbox label="2" name="type">{{ $t('components.process.email') }}</el-checkbox>
+            <el-checkbox label="3" name="type">{{ $t('components.process.sms') }}</el-checkbox>
           </el-checkbox-group>
         </el-form-item>
-        <el-form-item v-if="task.flowStatus === 'waiting'" label="附件">
+        <el-form-item v-if="task.flowStatus === 'waiting'" :label="$t('components.process.attachment')">
           <fileUpload
             v-model="backForm.fileId"
             :file-type="['png', 'jpg', 'jpeg', 'doc', 'docx', 'xlsx', 'xls', 'ppt', 'txt', 'pdf']"
             :file-size="20"
           />
         </el-form-item>
-        <el-form-item label="审批意见">
+        <el-form-item :label="$t('components.process.approvalOpinion')">
           <el-input v-model="backForm.message" type="textarea" resize="none" />
         </el-form-item>
       </el-form>
       <template #footer>
         <div class="dialog-footer" style="float: right; padding-bottom: 20px">
-          <el-button :disabled="backButtonDisabled" type="primary" @click="handleBackProcess">确认</el-button>
-          <el-button :disabled="backButtonDisabled" @click="backVisible = false">取消</el-button>
+          <el-button :disabled="backButtonDisabled" type="primary" @click="handleBackProcess">{{ $t('components.process.confirm') }}</el-button>
+          <el-button :disabled="backButtonDisabled" @click="backVisible = false">{{ $t('components.process.cancel') }}</el-button>
         </div>
       </template>
     </el-dialog>
     <!-- 驳回结束 -->
-    <el-dialog v-model="deleteSignatureVisible" draggable title="减签人员" width="700px" height="400px" append-to-body :close-on-click-modal="false">
+    <el-dialog v-model="deleteSignatureVisible" draggable :title="$t('components.process.reduceSignTitle')" width="700px" height="400px" append-to-body :close-on-click-modal="false">
       <div>
         <el-table :data="deleteUserList" border>
-          <el-table-column prop="nodeName" label="任务名称" />
-          <el-table-column prop="nickName" label="办理人" />
-          <el-table-column label="操作" align="center" width="160">
+          <el-table-column prop="nodeName" :label="$t('components.process.taskName')" />
+          <el-table-column prop="nickName" :label="$t('components.process.handler')" />
+          <el-table-column :label="$t('components.process.operation')" align="center" width="160">
             <template #default="scope">
-              <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">删除 </el-button>
+              <el-button type="danger" size="small" icon="Delete" @click="deleteMultiInstanceUser(scope.row)">{{ $t('components.process.delete') }} </el-button>
             </template>
           </el-table-column>
         </el-table>

+ 18 - 15
src/components/RoleSelect/index.vue

@@ -5,16 +5,16 @@
         <div v-show="showSearch" class="mb-[10px]">
           <el-card shadow="hover">
             <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-              <el-form-item label="角色名称" prop="roleName">
-                <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" />
+              <el-form-item :label="$t('components.roleSelect.roleName')" prop="roleName">
+                <el-input v-model="queryParams.roleName" :placeholder="$t('components.roleSelect.roleNamePlaceholder')" clearable @keyup.enter="handleQuery" />
               </el-form-item>
-              <el-form-item label="权限字符" prop="roleKey">
-                <el-input v-model="queryParams.roleKey" placeholder="请输入权限字符" clearable @keyup.enter="handleQuery" />
+              <el-form-item :label="$t('components.roleSelect.roleKey')" prop="roleKey">
+                <el-input v-model="queryParams.roleKey" :placeholder="$t('components.roleSelect.roleKeyPlaceholder')" clearable @keyup.enter="handleQuery" />
               </el-form-item>
 
               <el-form-item>
-                <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                <el-button type="primary" icon="Search" @click="handleQuery">{{ $t('components.roleSelect.search') }}</el-button>
+                <el-button icon="Refresh" @click="resetQuery">{{ $t('components.roleSelect.reset') }}</el-button>
               </el-form-item>
             </el-form>
           </el-card>
@@ -42,16 +42,16 @@
           @checkbox-change="handleCheckboxChange"
         >
           <vxe-column type="checkbox" width="50" align="center" />
-          <vxe-column v-if="false" key="roleId" label="角色编号" />
-          <vxe-column field="roleName" title="角色名称" />
-          <vxe-column field="roleKey" title="权限字符" />
-          <vxe-column field="roleSort" title="显示顺序" width="100" />
-          <vxe-column title="状态" align="center" width="100">
+          <vxe-column v-if="false" key="roleId" :label="$t('components.roleSelect.roleId')" />
+          <vxe-column field="roleName" :title="$t('components.roleSelect.roleName')" />
+          <vxe-column field="roleKey" :title="$t('components.roleSelect.roleKey')" />
+          <vxe-column field="roleSort" :title="$t('components.roleSelect.roleSort')" width="100" />
+          <vxe-column :title="$t('components.roleSelect.status')" align="center" width="100">
             <template #default="scope">
               <dict-tag :options="sys_normal_disable" :value="scope.row.status"></dict-tag>
             </template>
           </vxe-column>
-          <vxe-column field="createTime" title="创建时间" align="center">
+          <vxe-column field="createTime" :title="$t('components.roleSelect.createTime')" align="center">
             <template #default="scope">
               <span>{{ proxy.parseTime(scope.row.createTime) }}</span>
             </template>
@@ -67,8 +67,8 @@
         />
       </el-card>
       <template #footer>
-        <el-button @click="close">取消</el-button>
-        <el-button type="primary" @click="confirm">确定</el-button>
+        <el-button @click="close">{{ $t('components.roleSelect.cancel') }}</el-button>
+        <el-button type="primary" @click="confirm">{{ $t('components.roleSelect.confirm') }}</el-button>
       </template>
     </el-dialog>
   </div>
@@ -79,6 +79,9 @@ import { RoleVO, RoleQuery } from '@/api/system/role/types';
 import { VxeTableInstance } from 'vxe-table';
 import useDialog from '@/hooks/useDialog';
 import api from '@/api/system/role';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 interface PropType {
   modelValue?: RoleVO[] | RoleVO | undefined;
   multiple?: boolean;
@@ -103,7 +106,7 @@ const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
 const selectRoleList = ref<RoleVO[]>([]);
 
 const roleDialog = useDialog({
-  title: '角色选择'
+  title: t('components.roleSelect.title')
 });
 
 const queryFormRef = ref<ElFormInstance>();

+ 1 - 1
src/components/TopNav/index.vue

@@ -9,7 +9,7 @@
 
     <!-- 顶部菜单超出数量折叠 -->
     <el-sub-menu v-if="topMenus.length > visibleNumber" :style="{ '--theme': theme }" index="more">
-      <template #title>更多菜单</template>
+      <template #title>{{ $t('components.topNav.moreMenu') }}</template>
       <template v-for="(item, index) in topMenus">
         <el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path"
           ><svg-icon :icon-class="item.meta ? item.meta.icon : ''" /> {{ item.meta?.title }}</el-menu-item

+ 20 - 17
src/components/UserSelect/index.vue

@@ -5,7 +5,7 @@
         <!-- 部门树 -->
         <el-col :lg="4" :xs="24" style="">
           <el-card shadow="hover">
-            <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
+            <el-input v-model="deptName" :placeholder="$t('components.userSelect.deptPlaceholder')" prefix-icon="Search" clearable />
             <el-tree
               ref="deptTreeRef"
               class="mt-2"
@@ -25,15 +25,15 @@
             <div v-show="showSearch" class="mb-[10px]">
               <el-card shadow="hover">
                 <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-                  <el-form-item label="用户名称" prop="userName">
-                    <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
+                  <el-form-item :label="$t('components.userSelect.userName')" prop="userName">
+                    <el-input v-model="queryParams.userName" :placeholder="$t('components.userSelect.userNamePlaceholder')" clearable @keyup.enter="handleQuery" />
                   </el-form-item>
-                  <el-form-item label="手机号码" prop="phonenumber">
-                    <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable @keyup.enter="handleQuery" />
+                  <el-form-item :label="$t('components.userSelect.phonenumber')" prop="phonenumber">
+                    <el-input v-model="queryParams.phonenumber" :placeholder="$t('components.userSelect.phonenumberPlaceholder')" clearable @keyup.enter="handleQuery" />
                   </el-form-item>
                   <el-form-item>
-                    <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-                    <el-button icon="Refresh" @click="() => resetQuery()">重置</el-button>
+                    <el-button type="primary" icon="Search" @click="handleQuery">{{ $t('components.userSelect.search') }}</el-button>
+                    <el-button icon="Refresh" @click="() => resetQuery()">{{ $t('components.userSelect.reset') }}</el-button>
                   </el-form-item>
                 </el-form>
               </el-card>
@@ -60,18 +60,18 @@
               @checkbox-change="handleCheckboxChange"
             >
               <vxe-column type="checkbox" width="50" align="center" />
-              <vxe-column key="userId" title="用户编号" align="center" field="userId" />
-              <vxe-column key="userName" title="用户名称" align="center" field="userName" />
-              <vxe-column key="nickName" title="用户昵称" align="center" field="nickName" />
-              <vxe-column key="deptName" title="部门" align="center" field="deptName" />
-              <vxe-column key="phonenumber" title="手机号码" align="center" field="phonenumber" width="120" />
-              <vxe-column key="status" title="状态" align="center">
+              <vxe-column key="userId" :title="$t('components.userSelect.userId')" align="center" field="userId" />
+              <vxe-column key="userName" :title="$t('components.userSelect.userName')" align="center" field="userName" />
+              <vxe-column key="nickName" :title="$t('components.userSelect.nickName')" align="center" field="nickName" />
+              <vxe-column key="deptName" :title="$t('components.userSelect.dept')" align="center" field="deptName" />
+              <vxe-column key="phonenumber" :title="$t('components.userSelect.phonenumber')" align="center" field="phonenumber" width="120" />
+              <vxe-column key="status" :title="$t('components.userSelect.status')" align="center">
                 <template #default="scope">
                   <dict-tag :options="sys_normal_disable" :value="scope.row.status"></dict-tag>
                 </template>
               </vxe-column>
 
-              <vxe-column title="创建时间" align="center" width="160">
+              <vxe-column :title="$t('components.userSelect.createTime')" align="center" width="160">
                 <template #default="scope">
                   <span>{{ scope.row.createTime }}</span>
                 </template>
@@ -90,8 +90,8 @@
       </el-row>
 
       <template #footer>
-        <el-button @click="close">取消</el-button>
-        <el-button type="primary" @click="confirm">确定</el-button>
+        <el-button @click="close">{{ $t('components.userSelect.cancel') }}</el-button>
+        <el-button type="primary" @click="confirm">{{ $t('components.userSelect.confirm') }}</el-button>
       </template>
     </el-dialog>
   </div>
@@ -103,6 +103,9 @@ import { UserQuery, UserVO } from '@/api/system/user/types';
 import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
 import { VxeTableInstance } from 'vxe-table';
 import useDialog from '@/hooks/useDialog';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 interface PropType {
   modelValue?: UserVO[] | UserVO | undefined;
@@ -135,7 +138,7 @@ const queryFormRef = ref<ElFormInstance>();
 const tableRef = ref<VxeTableInstance<UserVO>>();
 
 const userDialog = useDialog({
-  title: '用户选择'
+  title: t('components.userSelect.title')
 });
 
 const queryParams = ref<UserQuery>({

+ 13 - 3
src/lang/en_US.ts

@@ -2,6 +2,8 @@
 import components from './modules/components/en_US';
 import system from './modules/system/index_en';
 import monitor from './modules/monitor/index_en';
+import setting from './modules/setting/index_en';
+import tool from './modules/tool/en_US';
 
 export default {
   // 路由国际化
@@ -84,13 +86,21 @@ export default {
     document: 'Document',
     message: 'Message',
     layoutSize: 'Layout Size',
-    selectTenant: 'Select Tenant',
+    selectTenant: 'Select Company',
     layoutSetting: 'Layout Setting',
     personalCenter: 'Personal Center',
-    logout: 'Logout'
+    logout: 'Logout',
+    logoutConfirm: 'Are you sure you want to log out?',
+    logoutTitle: 'Confirm',
+    confirm: 'Confirm',
+    cancel: 'Cancel'
   },
   // 系统管理模块(包含用户、角色等)
   ...system,
   // 监控管理模块
-  ...monitor
+  ...monitor,
+  // 设置管理模块
+  ...setting,
+  // 工具模块
+  ...tool
 };

+ 0 - 159
src/lang/modules/README.md

@@ -1,159 +0,0 @@
-# 国际化翻译模块化管理
-
-## 目录结构
-
-```
-lang/
-├── modules/              # 模块化翻译文件目录
-│   ├── system/          # 系统管理模块
-│   │   ├── user/        # 用户管理翻译
-│   │   │   ├── zh_CN.ts
-│   │   │   └── en_US.ts
-│   │   ├── role/        # 角色管理翻译
-│   │   │   ├── zh_CN.ts
-│   │   │   └── en_US.ts
-│   │   ├── index.ts     # 中文模块索引
-│   │   └── index_en.ts  # 英文模块索引
-│   ├── components/      # 公共组件翻译
-│   │   ├── zh_CN.ts
-│   │   └── en_US.ts
-│   └── ...              # 其他业务模块
-├── zh_CN.ts             # 中文主语言文件
-├── en_US.ts             # 英文主语言文件
-└── index.ts             # i18n 配置入口
-```
-
-## 使用规范
-
-### 1. 创建新模块翻译
-
-#### A. 系统管理模块下的子模块
-
-在 `modules/system/` 目录下创建:
-
-1. 创建子模块文件夹,如 `modules/system/dept/`
-2. 创建语言文件:`zh_CN.ts` 和 `en_US.ts`
-3. 更新 `modules/system/index.ts` 和 `index_en.ts`
-
-**示例:** `modules/system/dept/zh_CN.ts`
-```typescript
-// 部门管理模块 - 中文翻译
-export default {
-  button: {
-    add: '新增部门',
-    edit: '编辑部门',
-    delete: '删除部门'
-  },
-  form: {
-    deptName: '部门名称',
-    leader: '负责人'
-  }
-};
-```
-
-**更新 `modules/system/index.ts`:**
-```typescript
-import user from './user/zh_CN';
-import role from './role/zh_CN';
-import dept from './dept/zh_CN';  // 新增
-
-export default {
-  user,
-  role,
-  dept  // 新增
-};
-```
-
-#### B. 其他业务模块
-
-在 `modules/` 目录下创建独立模块:
-
-1. 创建模块文件夹,如 `modules/setting/`
-2. 创建语言文件和索引文件
-3. 在主语言文件中导入
-
-### 2. 在主语言文件中导入
-
-**当前结构(使用扩展运算符):**
-
-```typescript
-// zh_CN.ts
-import components from './modules/components/zh_CN';
-import system from './modules/system/index';
-
-export default {
-  // ... 其他配置
-  components,
-  ...system  // 展开 system 模块,包含 user、role 等
-};
-```
-
-这样 `user` 和 `role` 会直接暴露在根级别,保持向后兼容。
-
-### 3. 在组件中使用
-
-```vue
-<script setup>
-import { useI18n } from 'vue-i18n';
-const { t } = useI18n();
-</script>
-
-<template>
-  <el-button>{{ t('role.button.add') }}</el-button>
-  <el-form-item :label="t('role.form.roleName')">
-    ...
-  </el-form-item>
-</template>
-```
-
-## 优势
-
-### ✅ 模块化管理
-- 每个功能模块的翻译独立维护
-- 文件结构清晰,易于查找和修改
-
-### ✅ 更好的协作
-- 不同开发人员可以同时修改不同模块的翻译文件
-- 减少 Git 冲突的可能性
-
-### ✅ 按需加载
-- 未来可以实现翻译文件的懒加载
-- 减小初始加载体积
-
-### ✅ 易于维护
-- 翻译内容与业务模块对应
-- 便于新增语言支持
-
-## 注意事项
-
-1. **保持结构一致**:中英文翻译文件的结构必须完全一致
-2. **避免重复键名**:不同模块的键名可以相同,但同一模块内不能重复
-3. **命名规范**:使用小驼峰命名法(camelCase)
-4. **注释说明**:在翻译文件顶部添加模块说明注释
-
-## 现有模块
-
-### 系统管理模块 (system)
-- **user** - 用户管理(用户列表、新增编辑、角色分配等)
-- **role** - 角色管理(角色列表、权限配置、数据权限等)
-
-### 公共模块
-- **components** - 公共组件(RightToolbar 等)
-
-## 待添加模块
-
-根据系统功能,后续可添加:
-
-### system 模块下
-- dept - 部门管理
-- menu - 菜单管理
-- post - 岗位管理
-- dict - 字典管理
-- config - 参数配置
-- notice - 通知公告
-- log - 日志管理
-
-### 其他业务模块
-- monitor - 系统监控
-- tool - 系统工具
-- ...

+ 119 - 0
src/lang/modules/components/en_US.ts

@@ -5,5 +5,124 @@ export default {
     showSearch: 'Show Search',
     refresh: 'Refresh',
     columnSetting: 'Show/Hide Columns'
+  },
+  iconSelect: {
+    placeholder: 'Click to select icon',
+    search: 'Search icon'
+  },
+  fileUpload: {
+    selectFile: 'Select File',
+    uploadTip: 'Please upload',
+    sizeLimit: 'size no more than',
+    formatLimit: 'format',
+    theFile: 'file',
+    delete: 'Delete',
+    sizeLimitExceed: 'Upload file size cannot exceed {size}MB!',
+    formatError: 'File format is incorrect, please upload {types} format file!'
+  },
+  imageUpload: {
+    uploadImage: 'Upload',
+    sizeLimit: 'size no more than',
+    numberLimit: 'quantity limit',
+    uploadTip: 'Please upload',
+    formatLimit: 'format',
+    theFile: 'file',
+    preview: 'Preview',
+    uploadLimitExceed: 'Upload file quantity cannot exceed {limit}!',
+    sizeLimitExceed: 'Upload image size cannot exceed {size}MB!',
+    formatError: 'File format is incorrect, please upload {types} image format file!',
+    fileNameError: 'File name is incorrect, cannot contain comma!',
+    avatarSizeLimitExceed: 'Upload avatar image size cannot exceed {size} MB!',
+    uploading: 'Uploading image, please wait...'
+  },
+  userSelect: {
+    title: 'User Selection',
+    deptPlaceholder: 'Please enter department name',
+    userName: 'User Name',
+    userNamePlaceholder: 'Please enter user name',
+    phonenumber: 'Phone Number',
+    phonenumberPlaceholder: 'Please enter phone number',
+    search: 'Search',
+    reset: 'Reset',
+    userId: 'User ID',
+    nickName: 'Nickname',
+    dept: 'Department',
+    status: 'Status',
+    createTime: 'Create Time',
+    cancel: 'Cancel',
+    confirm: 'Confirm'
+  },
+  topNav: {
+    moreMenu: 'More Menu'
+  },
+  roleSelect: {
+    title: 'Role Selection',
+    roleName: 'Role Name',
+    roleNamePlaceholder: 'Please enter role name',
+    roleKey: 'Permission String',
+    roleKeyPlaceholder: 'Please enter permission string',
+    search: 'Search',
+    reset: 'Reset',
+    roleId: 'Role ID',
+    roleSort: 'Display Order',
+    status: 'Status',
+    createTime: 'Create Time',
+    cancel: 'Cancel',
+    confirm: 'Confirm'
+  },
+  editor: {
+    placeholder: 'Please enter content',
+    imageUploadSuccess: 'Image inserted successfully',
+    imageUploadFailed: 'Image insertion failed',
+    imageFormatError: 'Image format error!',
+    fileSizeExceed: 'Upload file size cannot exceed {size} MB!',
+    uploading: 'Uploading file, please wait...',
+    uploadFailed: 'File upload failed',
+    // Toolbar text
+    toolbar: {
+      link: 'Enter link address:',
+      save: 'Save',
+      video: 'Enter video address:',
+      text: 'Normal',
+      heading1: 'Heading 1',
+      heading2: 'Heading 2',
+      heading3: 'Heading 3',
+      heading4: 'Heading 4',
+      heading5: 'Heading 5',
+      heading6: 'Heading 6',
+      normalFont: 'Normal',
+      serifFont: 'Serif',
+      monospaceFont: 'Monospace'
+    }
+  },
+  process: {
+    messageReminder: 'Message Reminder',
+    internalMessage: 'Internal Message',
+    email: 'Email',
+    sms: 'SMS',
+    attachment: 'Attachment',
+    copy: 'CC',
+    nextApprover: 'Next Approver',
+    selectApprover: 'Please select approver',
+    selectApproverPlaceholder: 'Please select approver',
+    choose: 'Select',
+    approvalOpinion: 'Approval Opinion',
+    submit: 'Submit',
+    delegate: 'Delegate',
+    transfer: 'Transfer',
+    addSign: 'Add Sign',
+    reduceSign: 'Reduce Sign',
+    terminate: 'Terminate',
+    back: 'Back',
+    cancel: 'Cancel',
+    backNode: 'Back to Node',
+    selectNode: 'Please select',
+    confirm: 'Confirm',
+    backTitle: 'Reject',
+    reduceSignTitle: 'Reduce Signers',
+    taskName: 'Task Name',
+    handler: 'Handler',
+    operation: 'Actions',
+    delete: 'Delete'
   }
 };

+ 119 - 0
src/lang/modules/components/zh_CN.ts

@@ -5,5 +5,124 @@ export default {
     showSearch: '显示搜索',
     refresh: '刷新',
     columnSetting: '显示/隐藏列'
+  },
+  iconSelect: {
+    placeholder: '点击选择图标',
+    search: '搜索图标'
+  },
+  fileUpload: {
+    selectFile: '选取文件',
+    uploadTip: '请上载',
+    sizeLimit: '大小不超过',
+    formatLimit: '格式为',
+    theFile: '的文件',
+    delete: '删除',
+    sizeLimitExceed: '上传文件大小不能超过 {size}MB!',
+    formatError: '文件格式不正确, 请上传{types}格式文件!'
+  },
+  imageUpload: {
+    uploadImage: '上传',
+    sizeLimit: '大小不超过',
+    numberLimit: '数量限制',
+    uploadTip: '请上传',
+    formatLimit: '格式为',
+    theFile: '的文件',
+    preview: '预览',
+    uploadLimitExceed: '上传文件数量不能超过 {limit} 个!',
+    sizeLimitExceed: '上传图片大小不能超过 {size}MB!',
+    formatError: '文件格式不正确, 请上传{types}图片格式文件!',
+    fileNameError: '文件名不正确,不能包含英文逗号!',
+    avatarSizeLimitExceed: '上传头像图片大小不能超过 {size} MB!',
+    uploading: '正在上传图片,请稍候...'
+  },
+  userSelect: {
+    title: '用户选择',
+    deptPlaceholder: '请输入部门名称',
+    userName: '用户名称',
+    userNamePlaceholder: '请输入用户名称',
+    phonenumber: '手机号码',
+    phonenumberPlaceholder: '请输入手机号码',
+    search: '搜索',
+    reset: '重置',
+    userId: '用户编号',
+    nickName: '用户昵称',
+    dept: '部门',
+    status: '状态',
+    createTime: '创建时间',
+    cancel: '取消',
+    confirm: '确定'
+  },
+  topNav: {
+    moreMenu: '更多菜单'
+  },
+  roleSelect: {
+    title: '角色选择',
+    roleName: '角色名称',
+    roleNamePlaceholder: '请输入角色名称',
+    roleKey: '权限字符',
+    roleKeyPlaceholder: '请输入权限字符',
+    search: '搜索',
+    reset: '重置',
+    roleId: '角色编号',
+    roleSort: '显示顺序',
+    status: '状态',
+    createTime: '创建时间',
+    cancel: '取消',
+    confirm: '确定'
+  },
+  editor: {
+    placeholder: '请输入内容',
+    imageUploadSuccess: '图片插入成功',
+    imageUploadFailed: '图片插入失败',
+    imageFormatError: '图片格式错误!',
+    fileSizeExceed: '上传文件大小不能超过 {size} MB!',
+    uploading: '正在上传文件,请稍候...',
+    uploadFailed: '上传文件失败',
+    // 工具栏文本
+    toolbar: {
+      link: '请输入链接地址:',
+      save: '保存',
+      video: '请输入视频地址:',
+      text: '文本',
+      heading1: '标题1',
+      heading2: '标题2',
+      heading3: '标题3',
+      heading4: '标题4',
+      heading5: '标题5',
+      heading6: '标题6',
+      normalFont: '标准字体',
+      serifFont: '衬线字体',
+      monospaceFont: '等宽字体'
+    }
+  },
+  process: {
+    messageReminder: '消息提醒',
+    internalMessage: '站内信',
+    email: '邮件',
+    sms: '短信',
+    attachment: '附件',
+    copy: '抄送',
+    nextApprover: '下一步审批人',
+    selectApprover: '请选择审批人',
+    selectApproverPlaceholder: '请选择审批人',
+    choose: '选择',
+    approvalOpinion: '审批意见',
+    submit: '提交',
+    delegate: '委托',
+    transfer: '转办',
+    addSign: '加签',
+    reduceSign: '减签',
+    terminate: '终止',
+    back: '退回',
+    cancel: '取消',
+    backNode: '驳回节点',
+    selectNode: '请选择',
+    confirm: '确认',
+    backTitle: '驳回',
+    reduceSignTitle: '减签人员',
+    taskName: '任务名称',
+    handler: '办理人',
+    operation: '操作',
+    delete: '删除'
   }
 };

+ 36 - 0
src/lang/modules/setting/applet/en_US.ts

@@ -0,0 +1,36 @@
+// Applet Settings Module - English Translation
+export default {
+  // Page Title
+  title: 'Applet Settings',
+
+  // Tabs
+  tab: {
+    userAgreement: 'User Agreement',
+    privacyAgreement: 'Privacy Agreement'
+  },
+
+  // Language Labels
+  lang: {
+    chinese: 'Chinese',
+    english: 'English'
+  },
+
+  // Buttons
+  button: {
+    save: 'Save'
+  },
+
+  // Messages
+  message: {
+    saveSuccess: 'Saved successfully',
+    saveFailed: 'Save failed',
+    fetchFailed: 'Failed to fetch data',
+    noData: 'No data available'
+  },
+
+  // Editor Placeholders
+  editor: {
+    userAgreementPlaceholder: 'Please enter user agreement content',
+    privacyAgreementPlaceholder: 'Please enter privacy Agreement content'
+  }
+};

+ 36 - 0
src/lang/modules/setting/applet/zh_CN.ts

@@ -0,0 +1,36 @@
+// 小程序设置模块 - 中文翻译
+export default {
+  // 页面标题
+  title: '小程序设置',
+
+  // 标签页
+  tab: {
+    userAgreement: '用户协议',
+    privacyAgreement: '隐私协议'
+  },
+
+  // 语言标签
+  lang: {
+    chinese: '中文',
+    english: '英文'
+  },
+
+  // 按钮
+  button: {
+    save: '保存'
+  },
+
+  // 消息
+  message: {
+    saveSuccess: '保存成功',
+    saveFailed: '保存失败',
+    fetchFailed: '获取数据失败',
+    noData: '暂无数据'
+  },
+
+  // 编辑器占位符
+  editor: {
+    userAgreementPlaceholder: '请输入用户协议内容',
+    privacyAgreementPlaceholder: '请输入隐私协议内容'
+  }
+};

+ 6 - 0
src/lang/modules/setting/index.ts

@@ -0,0 +1,6 @@
+// 设置管理模块 - 统一导出
+import applet from './applet/zh_CN';
+
+export default {
+  applet
+};

+ 6 - 0
src/lang/modules/setting/index_en.ts

@@ -0,0 +1,6 @@
+// Settings Module - Unified Export (English)
+import applet from './applet/en_US';
+
+export default {
+  applet
+};

+ 5 - 1
src/lang/modules/system/index.ts

@@ -5,6 +5,8 @@ import post from './post/zh_CN';
 import dept from './dept/zh_CN';
 import tenant from './tenant/zh_CN';
 import tenantPackage from './tenantPackage/zh_CN';
+import menu from './menu/zh_CN';
+import profile from './profile/zh_CN';
 
 export default {
   user,
@@ -12,5 +14,7 @@ export default {
   post,
   dept,
   tenant,
-  tenantPackage
+  tenantPackage,
+  menu,
+  profile
 };

+ 5 - 1
src/lang/modules/system/index_en.ts

@@ -5,6 +5,8 @@ import post from './post/en_US';
 import dept from './dept/en_US';
 import tenant from './tenant/en_US';
 import tenantPackage from './tenantPackage/en_US';
+import menu from './menu/en_US';
+import profile from './profile/en_US';
 
 export default {
   user,
@@ -12,5 +14,7 @@ export default {
   post,
   dept,
   tenant,
-  tenantPackage
+  tenantPackage,
+  menu,
+  profile
 };

+ 92 - 0
src/lang/modules/system/menu/en_US.ts

@@ -0,0 +1,92 @@
+// Menu Management Module - English Translation
+export default {
+  // Search Form
+  search: {
+    menuName: 'Menu Name',
+    menuNamePlaceholder: 'Please enter menu name',
+    status: 'Status',
+    statusPlaceholder: 'Menu status',
+    search: 'Search',
+    reset: 'Reset'
+  },
+  // Button Operations
+  button: {
+    add: 'Add',
+    edit: 'Edit',
+    delete: 'Delete',
+    cascadeDelete: 'Cascade Delete',
+    submit: 'Submit',
+    cancel: 'Cancel'
+  },
+  // Table Columns
+  table: {
+    menuName: 'Menu Name',
+    icon: 'Icon',
+    sort: 'Sort',
+    perms: 'Permissions',
+    component: 'Component',
+    status: 'Status',
+    createTime: 'Create Time',
+    operation: 'Actions'
+  },
+  // Form
+  form: {
+    parentMenu: 'Parent Menu',
+    parentMenuPlaceholder: 'Select parent menu',
+    mainMenu: 'Main Menu',
+    menuType: 'Menu Type',
+    directory: 'Directory',
+    menu: 'Menu',
+    button: 'Button',
+    menuIcon: 'Menu Icon',
+    zhName: 'Chinese Name',
+    zhNamePlaceholder: 'Please enter Chinese name',
+    enName: 'English Name',
+    enNamePlaceholder: 'Please enter English name',
+    sort: 'Display Order',
+    isFrame: 'External Link',
+    isFrameTip: 'If selected, the route address needs to start with `http(s)://`',
+    yes: 'Yes',
+    no: 'No',
+    path: 'Route Path',
+    pathTip: 'The route path to access, such as: `user`, if external address needs internal access, start with `http(s)://`',
+    pathPlaceholder: 'Please enter route path',
+    component: 'Component Path',
+    componentTip: 'The component path to access, such as: `system/user/index`, default under `views` directory',
+    componentPlaceholder: 'Please enter component path',
+    perms: 'Permission String',
+    permsTip: 'Permission character defined in controller, such as: @SaCheckPermission(\'system:user:list\')',
+    permsPlaceholder: 'Please enter permission identifier',
+    queryParam: 'Route Parameters',
+    queryParamTip: 'Default parameters passed to the route, such as: `{"id": 1, "name": "ry"}`',
+    queryParamPlaceholder: 'Please enter route parameters',
+    isCache: 'Keep Alive',
+    isCacheTip: 'If selected, it will be cached by `keep-alive`, need to match component `name` with route',
+    cache: 'Cache',
+    noCache: 'No Cache',
+    visible: 'Display Status',
+    visibleTip: 'If hidden, the route will not appear in the sidebar, but can still be accessed',
+    status: 'Menu Status',
+    statusTip: 'If disabled, the route will not appear in the sidebar and cannot be accessed'
+  },
+  // Dialog Titles
+  dialog: {
+    add: 'Add Menu',
+    edit: 'Edit Menu',
+    cascadeDelete: 'Cascade Delete Menu'
+  },
+  // Validation Rules
+  rule: {
+    zhNameRequired: 'Chinese name cannot be empty',
+    enNameRequired: 'English name cannot be empty',
+    sortRequired: 'Menu order cannot be empty',
+    pathRequired: 'Route path cannot be empty'
+  },
+  // Messages
+  message: {
+    deleteConfirm: 'Are you sure you want to delete the menu named "{name}"?',
+    deleteSuccess: 'Deleted successfully',
+    operationSuccess: 'Operation successful',
+    loading: 'Loading, please wait'
+  }
+};

+ 92 - 0
src/lang/modules/system/menu/zh_CN.ts

@@ -0,0 +1,92 @@
+// 菜单管理模块 - 中文翻译
+export default {
+  // 搜索表单
+  search: {
+    menuName: '菜单名称',
+    menuNamePlaceholder: '请输入菜单名称',
+    status: '状态',
+    statusPlaceholder: '菜单状态',
+    search: '搜索',
+    reset: '重置'
+  },
+  // 按钮操作
+  button: {
+    add: '新增',
+    edit: '修改',
+    delete: '删除',
+    cascadeDelete: '级联删除',
+    submit: '确 定',
+    cancel: '取 消'
+  },
+  // 表格列
+  table: {
+    menuName: '菜单名称',
+    icon: '图标',
+    sort: '排序',
+    perms: '权限标识',
+    component: '组件路径',
+    status: '状态',
+    createTime: '创建时间',
+    operation: '操作'
+  },
+  // 表单
+  form: {
+    parentMenu: '上级菜单',
+    parentMenuPlaceholder: '选择上级菜单',
+    mainMenu: '主类目',
+    menuType: '菜单类型',
+    directory: '目录',
+    menu: '菜单',
+    button: '按钮',
+    menuIcon: '菜单图标',
+    zhName: '中文名称',
+    zhNamePlaceholder: '请输入中文名称',
+    enName: '英文名称',
+    enNamePlaceholder: '请输入英文名称',
+    sort: '显示排序',
+    isFrame: '是否外链',
+    isFrameTip: '选择是外链则路由地址需要以`http(s)://`开头',
+    yes: '是',
+    no: '否',
+    path: '路由地址',
+    pathTip: '访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头',
+    pathPlaceholder: '请输入路由地址',
+    component: '组件路径',
+    componentTip: '访问的组件路径,如:`system/user/index`,默认在`views`目录下',
+    componentPlaceholder: '请输入组件路径',
+    perms: '权限字符',
+    permsTip: '控制器中定义的权限字符,如:@SaCheckPermission(\'system:user:list\')',
+    permsPlaceholder: '请输入权限标识',
+    queryParam: '路由参数',
+    queryParamTip: '访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`',
+    queryParamPlaceholder: '请输入路由参数',
+    isCache: '是否缓存',
+    isCacheTip: '选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致',
+    cache: '缓存',
+    noCache: '不缓存',
+    visible: '显示状态',
+    visibleTip: '选择隐藏则路由将不会出现在侧边栏,但仍然可以访问',
+    status: '菜单状态',
+    statusTip: '选择停用则路由将不会出现在侧边栏,也不能被访问'
+  },
+  // 对话框标题
+  dialog: {
+    add: '添加菜单',
+    edit: '修改菜单',
+    cascadeDelete: '级联删除菜单'
+  },
+  // 验证规则
+  rule: {
+    zhNameRequired: '中文名称不能为空',
+    enNameRequired: '英文名称不能为空',
+    sortRequired: '菜单顺序不能为空',
+    pathRequired: '路由地址不能为空'
+  },
+  // 提示信息
+  message: {
+    deleteConfirm: '是否确认删除名称为"{name}"的数据项?',
+    deleteSuccess: '删除成功',
+    operationSuccess: '操作成功',
+    loading: '加载中,请稍候'
+  }
+};

+ 75 - 0
src/lang/modules/system/profile/en_US.ts

@@ -0,0 +1,75 @@
+// Personal Center Module - English Translation
+export default {
+  // Card Titles
+  card: {
+    personalInfo: 'Personal Info',
+    basicInfo: 'Basic Information'
+  },
+  // User Information Fields
+  info: {
+    userName: 'User Name',
+    phonenumber: 'Phone Number',
+    email: 'Email',
+    dept: 'Department',
+    role: 'Role',
+    createTime: 'Create Time'
+  },
+  // Tabs
+  tab: {
+    userInfo: 'Basic Info',
+    resetPwd: 'Change Password',
+    thirdParty: 'Third Party',
+    onlineDevice: 'Online Devices'
+  },
+  // User Info Form
+  userInfoForm: {
+    nickName: 'Nickname',
+    sex: 'Gender',
+    male: 'Male',
+    female: 'Female',
+    save: 'Save',
+    close: 'Close'
+  },
+  // Reset Password Form
+  resetPwdForm: {
+    oldPassword: 'Old Password',
+    oldPasswordPlaceholder: 'Please enter old password',
+    newPassword: 'New Password',
+    newPasswordPlaceholder: 'Please enter new password',
+    confirmPassword: 'Confirm Password',
+    confirmPasswordPlaceholder: 'Please confirm new password',
+    save: 'Save',
+    close: 'Close'
+  },
+  // Online Device Table
+  onlineDevice: {
+    deviceType: 'Device Type',
+    ipaddr: 'Host',
+    loginLocation: 'Login Location',
+    os: 'Operating System',
+    browser: 'Browser',
+    loginTime: 'Login Time',
+    operation: 'Actions',
+    delete: 'Delete',
+    deleteConfirm: 'After deleting the device, re-verification is required to log in on this device',
+    deleteSuccess: 'Deleted successfully'
+  },
+  // Validation Rules
+  rule: {
+    nickNameRequired: 'Nickname cannot be empty',
+    emailRequired: 'Email address cannot be empty',
+    emailFormat: 'Please enter a valid email address',
+    phonenumberRequired: 'Phone number cannot be empty',
+    phonenumberFormat: 'Please enter a valid phone number',
+    oldPasswordRequired: 'Old password cannot be empty',
+    newPasswordRequired: 'New password cannot be empty',
+    passwordLength: 'Length must be between 6 and 20 characters',
+    passwordPattern: 'Cannot contain illegal characters: < > " \' \\ |',
+    confirmPasswordRequired: 'Confirm password cannot be empty',
+    passwordMismatch: 'The two passwords entered do not match'
+  },
+  // Messages
+  message: {
+    updateSuccess: 'Updated successfully'
+  }
+};

+ 75 - 0
src/lang/modules/system/profile/zh_CN.ts

@@ -0,0 +1,75 @@
+// 个人中心模块 - 中文翻译
+export default {
+  // 卡片标题
+  card: {
+    personalInfo: '个人信息',
+    basicInfo: '基本资料'
+  },
+  // 用户信息字段
+  info: {
+    userName: '用户名称',
+    phonenumber: '手机号码',
+    email: '用户邮箱',
+    dept: '所属部门',
+    role: '所属角色',
+    createTime: '创建日期'
+  },
+  // 标签页
+  tab: {
+    userInfo: '基本资料',
+    resetPwd: '修改密码',
+    thirdParty: '第三方应用',
+    onlineDevice: '在线设备'
+  },
+  // 基本资料表单
+  userInfoForm: {
+    nickName: '用户昵称',
+    sex: '性别',
+    male: '男',
+    female: '女',
+    save: '保存',
+    close: '关闭'
+  },
+  // 修改密码表单
+  resetPwdForm: {
+    oldPassword: '旧密码',
+    oldPasswordPlaceholder: '请输入旧密码',
+    newPassword: '新密码',
+    newPasswordPlaceholder: '请输入新密码',
+    confirmPassword: '确认密码',
+    confirmPasswordPlaceholder: '请确认新密码',
+    save: '保存',
+    close: '关闭'
+  },
+  // 在线设备表格
+  onlineDevice: {
+    deviceType: '设备类型',
+    ipaddr: '主机',
+    loginLocation: '登录地点',
+    os: '操作系统',
+    browser: '浏览器',
+    loginTime: '登录时间',
+    operation: '操作',
+    delete: '删除',
+    deleteConfirm: '删除设备后,在该设备登录需要重新进行验证',
+    deleteSuccess: '删除成功'
+  },
+  // 验证规则
+  rule: {
+    nickNameRequired: '用户昵称不能为空',
+    emailRequired: '邮箱地址不能为空',
+    emailFormat: '请输入正确的邮箱地址',
+    phonenumberRequired: '手机号码不能为空',
+    phonenumberFormat: '请输入正确的手机号码',
+    oldPasswordRequired: '旧密码不能为空',
+    newPasswordRequired: '新密码不能为空',
+    passwordLength: '长度在 6 到 20 个字符',
+    passwordPattern: '不能包含非法字符:< > " \' \\ |',
+    confirmPasswordRequired: '确认密码不能为空',
+    passwordMismatch: '两次输入的密码不一致'
+  },
+  // 消息
+  message: {
+    updateSuccess: '修改成功'
+  }
+};

+ 5 - 0
src/lang/modules/tool/en_US.ts

@@ -0,0 +1,5 @@
+import gen from './gen/en_US';
+
+export default {
+  gen
+};

+ 180 - 0
src/lang/modules/tool/gen/en_US.ts

@@ -0,0 +1,180 @@
+// Code Generation Module - English Translation
+export default {
+  // Page Title
+  title: 'Code Generation',
+
+  // Search Form
+  search: {
+    dataSource: 'Data Source',
+    dataSourcePlaceholder: 'Please select/enter data source name',
+    all: 'All',
+    tableName: 'Table Name',
+    tableNamePlaceholder: 'Please enter table name',
+    tableComment: 'Table Comment',
+    tableCommentPlaceholder: 'Please enter table comment',
+    createTime: 'Create Time',
+    startDate: 'Start Date',
+    endDate: 'End Date',
+    search: 'Search',
+    reset: 'Reset'
+  },
+
+  // Buttons
+  button: {
+    generate: 'Generate',
+    import: 'Import',
+    edit: 'Edit',
+    delete: 'Delete',
+    preview: 'Preview',
+    sync: 'Sync',
+    generateCode: 'Generate Code',
+    confirm: 'Confirm',
+    cancel: 'Cancel'
+  },
+
+  // Table Columns
+  table: {
+    index: 'No.',
+    dataSource: 'Data Source',
+    tableName: 'Table Name',
+    tableComment: 'Table Comment',
+    entity: 'Entity',
+    createTime: 'Create Time',
+    updateTime: 'Update Time',
+    operation: 'Actions'
+  },
+
+  // Tooltip
+  tooltip: {
+    preview: 'Preview',
+    edit: 'Edit',
+    delete: 'Delete',
+    sync: 'Sync',
+    generateCode: 'Generate Code'
+  },
+
+  // Dialog
+  dialog: {
+    previewTitle: 'Code Preview',
+    importTitle: 'Import Table',
+    copy: 'Copy'
+  },
+
+  // Messages
+  message: {
+    selectData: 'Please select the data to generate',
+    generateSuccess: 'Successfully generated to custom path: ',
+    syncConfirm: 'Are you sure to force sync "{tableName}" table structure?',
+    syncSuccess: 'Sync successful',
+    deleteConfirm: 'Are you sure to delete the data item with table ID "{tableIds}"?',
+    deleteSuccess: 'Delete successful',
+    copySuccess: 'Copy successful',
+    selectTable: 'Please select the table to import',
+    validationFailed: 'Form validation failed, please check and resubmit'
+  },
+
+  // Edit Page
+  edit: {
+    // Tabs
+    tabs: {
+      basic: 'Basic Info',
+      column: 'Column Info',
+      genInfo: 'Generation Info'
+    },
+    // Basic Info Form
+    basic: {
+      tableName: 'Table Name',
+      tableNamePlaceholder: 'Please enter table name',
+      tableComment: 'Table Comment',
+      tableCommentPlaceholder: 'Please enter table comment',
+      className: 'Entity Class Name',
+      classNamePlaceholder: 'Please enter entity class name',
+      functionAuthor: 'Author',
+      functionAuthorPlaceholder: 'Please enter author',
+      remark: 'Remark',
+      tableNameRequired: 'Please enter table name',
+      tableCommentRequired: 'Please enter table comment',
+      classNameRequired: 'Please enter entity class name',
+      functionAuthorRequired: 'Please enter author'
+    },
+    // Column Info Table
+    column: {
+      index: 'No.',
+      columnName: 'Column Name',
+      columnComment: 'Column Comment',
+      columnType: 'Physical Type',
+      javaType: 'Java Type',
+      javaField: 'Java Field',
+      isInsert: 'Insert',
+      isEdit: 'Edit',
+      isList: 'List',
+      isQuery: 'Query',
+      queryType: 'Query Type',
+      isRequired: 'Required',
+      htmlType: 'Display Type',
+      dictType: 'Dict Type',
+      dictTypePlaceholder: 'Please select'
+    },
+    // HTML Type Options
+    htmlType: {
+      input: 'Input',
+      textarea: 'Textarea',
+      select: 'Select',
+      radio: 'Radio',
+      checkbox: 'Checkbox',
+      datetime: 'Datetime',
+      imageUpload: 'Image Upload',
+      fileUpload: 'File Upload',
+      editor: 'Rich Text Editor'
+    },
+    // Generation Info Form
+    genInfo: {
+      tplCategory: 'Generation Template',
+      tplCategoryCrud: 'Single Table (CRUD)',
+      tplCategoryTree: 'Tree Table (CRUD)',
+      packageName: 'Package Path',
+      packageNameTip: 'Which java package to generate, e.g. com.ruoyi.system',
+      moduleName: 'Module Name',
+      moduleNameTip: 'Can be understood as subsystem name, e.g. system',
+      businessName: 'Business Name',
+      businessNameTip: 'Can be understood as function English name, e.g. user',
+      functionName: 'Function Name',
+      functionNameTip: 'Used as class description, e.g. User',
+      parentMenu: 'Parent Menu',
+      parentMenuTip: 'Assign to specified menu, e.g. System Management',
+      parentMenuPlaceholder: 'Select parent menu',
+      genType: 'Generation Method',
+      genTypeTip: 'Default is zip download, custom path is also available',
+      genTypeZip: 'Zip Package',
+      genTypeCustom: 'Custom Path',
+      genPath: 'Custom Path',
+      genPathTip: 'Fill in disk absolute path, if not filled, generate to current Web project',
+      recentPath: 'Recent Path Quick Select',
+      restorePath: 'Restore default generation base path',
+      otherInfo: 'Other Info',
+      treeCode: 'Tree Code Field',
+      treeCodeTip: 'Tree display code field name, e.g.: dept_id',
+      treeCodePlaceholder: 'Please select',
+      treeParentCode: 'Tree Parent Code Field',
+      treeParentCodeTip: 'Tree display parent code field name, e.g.: parent_Id',
+      treeName: 'Tree Name Field',
+      treeNameTip: 'Tree node display name field name, e.g.: dept_name',
+      subInfo: 'Association Info',
+      subTableName: 'Associated Sub-table Name',
+      subTableNameTip: 'Associated sub-table name, e.g.: sys_user',
+      subTableNamePlaceholder: 'Please select',
+      subTableFkName: 'Sub-table Foreign Key Name',
+      subTableFkNameTip: 'Sub-table foreign key name, e.g.: user_id',
+      tplCategoryRequired: 'Please select generation template',
+      packageNameRequired: 'Please enter package path',
+      moduleNameRequired: 'Please enter module name',
+      businessNameRequired: 'Please enter business name',
+      functionNameRequired: 'Please enter function name'
+    },
+    // Buttons
+    button: {
+      submit: 'Submit',
+      return: 'Return'
+    }
+  }
+};

+ 180 - 0
src/lang/modules/tool/gen/zh_CN.ts

@@ -0,0 +1,180 @@
+// 代码生成模块 - 中文翻译
+export default {
+  // 页面标题
+  title: '代码生成',
+
+  // 搜索表单
+  search: {
+    dataSource: '数据源',
+    dataSourcePlaceholder: '请选择/输入数据源名称',
+    all: '全部',
+    tableName: '表名称',
+    tableNamePlaceholder: '请输入表名称',
+    tableComment: '表描述',
+    tableCommentPlaceholder: '请输入表描述',
+    createTime: '创建时间',
+    startDate: '开始日期',
+    endDate: '结束日期',
+    search: '搜索',
+    reset: '重置'
+  },
+
+  // 按钮
+  button: {
+    generate: '生成',
+    import: '导入',
+    edit: '修改',
+    delete: '删除',
+    preview: '预览',
+    sync: '同步',
+    generateCode: '生成代码',
+    confirm: '确 定',
+    cancel: '取 消'
+  },
+
+  // 表格列
+  table: {
+    index: '序号',
+    dataSource: '数据源',
+    tableName: '表名称',
+    tableComment: '表描述',
+    entity: '实体',
+    createTime: '创建时间',
+    updateTime: '更新时间',
+    operation: '操作'
+  },
+
+  // 提示信息
+  tooltip: {
+    preview: '预览',
+    edit: '编辑',
+    delete: '删除',
+    sync: '同步',
+    generateCode: '生成代码'
+  },
+
+  // 对话框
+  dialog: {
+    previewTitle: '代码预览',
+    importTitle: '导入表',
+    copy: '复制'
+  },
+
+  // 消息提示
+  message: {
+    selectData: '请选择要生成的数据',
+    generateSuccess: '成功生成到自定义路径:',
+    syncConfirm: '确认要强制同步"{tableName}"表结构吗?',
+    syncSuccess: '同步成功',
+    deleteConfirm: '是否确认删除表编号为"{tableIds}"的数据项?',
+    deleteSuccess: '删除成功',
+    copySuccess: '复制成功',
+    selectTable: '请选择要导入的表',
+    validationFailed: '表单校验未通过,请重新检查提交内容'
+  },
+
+  // 编辑页面
+  edit: {
+    // 标签页
+    tabs: {
+      basic: '基本信息',
+      column: '字段信息',
+      genInfo: '生成信息'
+    },
+    // 基本信息表单
+    basic: {
+      tableName: '表名称',
+      tableNamePlaceholder: '请输入表名称',
+      tableComment: '表描述',
+      tableCommentPlaceholder: '请输入表描述',
+      className: '实体类名称',
+      classNamePlaceholder: '请输入实体类名称',
+      functionAuthor: '作者',
+      functionAuthorPlaceholder: '请输入作者',
+      remark: '备注',
+      tableNameRequired: '请输入表名称',
+      tableCommentRequired: '请输入表描述',
+      classNameRequired: '请输入实体类名称',
+      functionAuthorRequired: '请输入作者'
+    },
+    // 字段信息表格
+    column: {
+      index: '序号',
+      columnName: '字段列名',
+      columnComment: '字段描述',
+      columnType: '物理类型',
+      javaType: 'Java类型',
+      javaField: 'java属性',
+      isInsert: '插入',
+      isEdit: '编辑',
+      isList: '列表',
+      isQuery: '查询',
+      queryType: '查询方式',
+      isRequired: '必填',
+      htmlType: '显示类型',
+      dictType: '字典类型',
+      dictTypePlaceholder: '请选择'
+    },
+    // 显示类型选项
+    htmlType: {
+      input: '文本框',
+      textarea: '文本域',
+      select: '下拉框',
+      radio: '单选框',
+      checkbox: '复选框',
+      datetime: '日期控件',
+      imageUpload: '图片上传',
+      fileUpload: '文件上传',
+      editor: '富文本控件'
+    },
+    // 生成信息表单
+    genInfo: {
+      tplCategory: '生成模板',
+      tplCategoryCrud: '单表(增删改查)',
+      tplCategoryTree: '树表(增删改查)',
+      packageName: '生成包路径',
+      packageNameTip: '生成在哪个java包下,例如 com.ruoyi.system',
+      moduleName: '生成模块名',
+      moduleNameTip: '可理解为子系统名,例如 system',
+      businessName: '生成业务名',
+      businessNameTip: '可理解为功能英文名,例如 user',
+      functionName: '生成功能名',
+      functionNameTip: '用作类描述,例如 用户',
+      parentMenu: '上级菜单',
+      parentMenuTip: '分配到指定菜单下,例如 系统管理',
+      parentMenuPlaceholder: '选择上级菜单',
+      genType: '生成代码方式',
+      genTypeTip: '默认为zip压缩包下载,也可以自定义生成路径',
+      genTypeZip: 'zip压缩包',
+      genTypeCustom: '自定义路径',
+      genPath: '自定义路径',
+      genPathTip: '填写磁盘绝对路径,若不填写,则生成到当前Web项目下',
+      recentPath: '最近路径快速选择',
+      restorePath: '恢复默认的生成基础路径',
+      otherInfo: '其他信息',
+      treeCode: '树编码字段',
+      treeCodeTip: '树显示的编码字段名, 如:dept_id',
+      treeCodePlaceholder: '请选择',
+      treeParentCode: '树父编码字段',
+      treeParentCodeTip: '树显示的父编码字段名, 如:parent_Id',
+      treeName: '树名称字段',
+      treeNameTip: '树节点的显示名称字段名, 如:dept_name',
+      subInfo: '关联信息',
+      subTableName: '关联子表的表名',
+      subTableNameTip: '关联子表的表名, 如:sys_user',
+      subTableNamePlaceholder: '请选择',
+      subTableFkName: '子表关联的外键名',
+      subTableFkNameTip: '子表关联的外键名, 如:user_id',
+      tplCategoryRequired: '请选择生成模板',
+      packageNameRequired: '请输入生成包路径',
+      moduleNameRequired: '请输入生成模块名',
+      businessNameRequired: '请输入生成业务名',
+      functionNameRequired: '请输入生成功能名'
+    },
+    // 按钮
+    button: {
+      submit: '提交',
+      return: '返回'
+    }
+  }
+};

+ 5 - 0
src/lang/modules/tool/zh_CN.ts

@@ -0,0 +1,5 @@
+import gen from './gen/zh_CN';
+
+export default {
+  gen
+};

+ 15 - 5
src/lang/zh_CN.ts

@@ -2,6 +2,8 @@
 import components from './modules/components/zh_CN';
 import system from './modules/system/index';
 import monitor from './modules/monitor/index';
+import setting from './modules/setting/index';
+import tool from './modules/tool/zh_CN';
 
 export default {
   // 路由国际化
@@ -21,7 +23,7 @@ export default {
     switchRegisterPage: '立即注册',
     rule: {
       tenantId: {
-        required: '请输入您的租户编号'
+        required: '请输入您的企业编号'
       },
       username: {
         required: '请输入您的账号'
@@ -54,7 +56,7 @@ export default {
     switchLoginPage: '使用已有账户登录',
     rule: {
       tenantId: {
-        required: '请输入您的租户编号'
+        required: '请输入您的公司编号'
       },
       username: {
         required: '请输入您的账号',
@@ -84,13 +86,21 @@ export default {
     document: '项目文档',
     message: '消息',
     layoutSize: '布局大小',
-    selectTenant: '选择租户',
+    selectTenant: '选择公司',
     layoutSetting: '布局设置',
     personalCenter: '个人中心',
-    logout: '退出登录'
+    logout: '退出登录',
+    logoutConfirm: '确定注销并退出系统吗?',
+    logoutTitle: '提示',
+    confirm: '确定',
+    cancel: '取消'
   },
   // 系统管理模块(包含用户、角色等)
   ...system,
   // 监控管理模块
-  ...monitor
+  ...monitor,
+  // 设置管理模块
+  ...setting,
+  // 工具模块
+  ...tool
 };

+ 5 - 3
src/layout/components/Navbar.vue

@@ -107,7 +107,9 @@ import { TenantVO } from '@/api/types';
 import notice from './notice/index.vue';
 import router from '@/router';
 import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const appStore = useAppStore();
 const userStore = useUserStore();
 const settingsStore = useSettingsStore();
@@ -167,9 +169,9 @@ const toggleSideBar = () => {
 };
 
 const logout = async () => {
-  await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
+  await ElMessageBox.confirm(t('navbar.logoutConfirm'), t('navbar.logoutTitle'), {
+    confirmButtonText: t('navbar.confirm'),
+    cancelButtonText: t('navbar.cancel'),
     type: 'warning'
   } as ElMessageBoxOptions);
   userStore.logout().then(() => {

+ 15 - 2
src/layout/components/Sidebar/SidebarItem.vue

@@ -5,7 +5,7 @@
         <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
           <svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
           <template #title>
-            <span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
+            <span class="menu-title" :title="hasTitle(onlyOneChildTitle)">{{ onlyOneChildTitle }}</span>
           </template>
         </el-menu-item>
       </app-link>
@@ -14,7 +14,7 @@
     <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
       <template v-if="item.meta" #title>
         <svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
-        <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
+        <span class="menu-title" :title="hasTitle(itemTitle)">{{ itemTitle }}</span>
       </template>
 
       <sidebar-item
@@ -33,7 +33,9 @@
 import { isExternal } from '@/utils/validate';
 import AppLink from './Link.vue';
 import { getNormalPath } from '@/utils/ruoyi';
+import { parseI18nName } from '@/utils/i18n';
 import { RouteRecordRaw } from 'vue-router';
+import { useI18n } from 'vue-i18n';
 
 const props = defineProps({
   item: {
@@ -50,8 +52,19 @@ const props = defineProps({
   }
 });
 
+const { locale } = useI18n();
 const onlyOneChild = ref<any>({});
 
+// 使用计算属性缓存解析后的标题,添加 locale 依赖确保语言切换时更新
+const itemTitle = computed(() => {
+  const _ = locale.value;
+  return parseI18nName(props.item.meta?.title);
+});
+const onlyOneChildTitle = computed(() => {
+  const _ = locale.value;
+  return parseI18nName(onlyOneChild.value.meta?.title);
+});
+
 const hasOneShowingChild = (parent: RouteRecordRaw, children?: RouteRecordRaw[]) => {
   if (!children) {
     children = [];

+ 11 - 1
src/layout/components/TagsView/index.vue

@@ -13,7 +13,7 @@
         @contextmenu.prevent="openMenu(tag, $event)"
       >
         <svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon"/>
-        <span class="tags-view-item-title">{{ tag.title }}</span>
+        <span class="tags-view-item-title">{{ getTagTitle(tag) }}</span>
         <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
           <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
         </span>
@@ -37,6 +37,8 @@ import { useSettingsStore } from '@/store/modules/settings';
 import { usePermissionStore } from '@/store/modules/permission';
 import { useTagsViewStore } from '@/store/modules/tagsView';
 import { RouteRecordRaw, RouteLocationNormalized } from 'vue-router';
+import { parseI18nName } from '@/utils/i18n';
+import { useI18n } from 'vue-i18n';
 
 const visible = ref(false);
 const top = ref(0);
@@ -48,6 +50,7 @@ const scrollPaneRef = ref<InstanceType<typeof ScrollPane>>();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const route = useRoute();
 const router = useRouter();
+const { locale } = useI18n();
 
 const visitedViews = computed(() => useTagsViewStore().getVisitedViews());
 const routes = computed(() => usePermissionStore().getRoutes());
@@ -232,6 +235,13 @@ const handleScroll = () => {
   closeMenu();
 };
 
+// 解析标签标题 - 添加 locale.value 依赖以确保响应式更新
+const getTagTitle = (tag: RouteLocationNormalized): string => {
+  // 访问 locale.value 以建立响应式依赖
+  const _ = locale.value;
+  return parseI18nName(tag.title || tag.meta?.title);
+};
+
 onMounted(() => {
   initTags();
   addTags();

+ 2 - 2
src/router/index.ts

@@ -71,7 +71,7 @@ export const constantRoutes: RouteRecordRaw[] = [
         path: '/index',
         component: () => import('@/views/index.vue'),
         name: 'Index',
-        meta: { title: '首页', icon: 'dashboard', affix: true }
+        meta: { title: '{"zh_CN":"首页","en_US":"Dashboard"}', icon: 'dashboard', affix: true }
       }
     ]
   },
@@ -85,7 +85,7 @@ export const constantRoutes: RouteRecordRaw[] = [
         path: 'profile',
         component: () => import('@/views/system/user/profile/index.vue'),
         name: 'Profile',
-        meta: { title: '个人中心', icon: 'user' }
+        meta: { title: '{"zh_CN":"个人中心","en_US":"Personal Center"}', icon: 'user' }
       }
     ]
   }

+ 22 - 0
src/utils/i18n.ts

@@ -1,5 +1,6 @@
 // translate router.meta.title, be used in breadcrumb sidebar tagsview
 import i18n from '@/lang/index';
+import { getLanguage } from '@/lang/index';
 
 /**
  * 获取国际化路由,如果不存在则原生返回
@@ -14,3 +15,24 @@ export const translateRouteTitle = (title: string): string => {
   }
   return title;
 };
+
+/**
+ * 解析国际化JSON名称,根据当前语言环境返回对应的名称
+ * @param name JSON格式的名称字符串,如 {"zh_CN":"菜单","en_US":"menu"}
+ * @returns {string} 解析后的名称
+ */
+export const parseI18nName = (name: string | undefined): string => {
+  if (!name) {
+    return '';
+  }
+  try {
+    const nameObj = JSON.parse(name);
+    // 直接使用 i18n.global.locale.value 获取当前语言,确保响应式更新
+    const currentLang = i18n.global.locale.value;
+    // 根据当前语言返回对应的名称,如果没有则使用中文,最后使用原值
+    return nameObj[currentLang] || nameObj.zh_CN || nameObj.en_US || name;
+  } catch (e) {
+    // 如果解析失败,直接返回原值
+    return name;
+  }
+};

+ 102 - 14
src/views/setting/applet/index.vue

@@ -3,26 +3,40 @@
     <el-card v-loading="loading" shadow="never">
       <template #header>
         <div class="card-header">
-          <span class="text-lg font-semibold">小程序设置</span>
+          <span class="text-lg font-semibold">{{ t('applet.title') }}</span>
         </div>
       </template>
 
       <el-tabs v-if="appletData" v-model="activeTab" type="border-card">
-        <el-tab-pane label="用户协议" name="userAgreement">
-          <editor v-model="appletData.userAgreement" :min-height="400"/>
+        <el-tab-pane :label="t('applet.tab.userAgreement')" name="userAgreement">
+          <el-tabs v-model="userAgreementLang" type="card" class="mb-4">
+            <el-tab-pane :label="t('applet.lang.chinese')" name="zh_CN">
+              <editor v-model="userAgreementData.zh_CN" :min-height="400"/>
+            </el-tab-pane>
+            <el-tab-pane :label="t('applet.lang.english')" name="en_US">
+              <editor v-model="userAgreementData.en_US" :min-height="400"/>
+            </el-tab-pane>
+          </el-tabs>
           <div class="mt-4">
-            <el-button type="primary" :loading="saveLoading" @click="handleSave">保存</el-button>
+            <el-button type="primary" :loading="saveLoading" @click="handleSave">{{ t('applet.button.save') }}</el-button>
           </div>
         </el-tab-pane>
-        <el-tab-pane label="隐私协议" name="privacyAgreement">
-          <editor v-model="appletData.privacyAgreement" :min-height="400"/>
+        <el-tab-pane :label="t('applet.tab.privacyAgreement')" name="privacyAgreement">
+          <el-tabs v-model="privacyAgreementLang" type="card" class="mb-4">
+            <el-tab-pane :label="t('applet.lang.chinese')" name="zh_CN">
+              <editor v-model="privacyAgreementData.zh_CN" :min-height="400"/>
+            </el-tab-pane>
+            <el-tab-pane :label="t('applet.lang.english')" name="en_US">
+              <editor v-model="privacyAgreementData.en_US" :min-height="400"/>
+            </el-tab-pane>
+          </el-tabs>
           <div class="mt-4">
-            <el-button type="primary" :loading="saveLoading" @click="handleSave">保存</el-button>
+            <el-button type="primary" :loading="saveLoading" @click="handleSave">{{ t('applet.button.save') }}</el-button>
           </div>
         </el-tab-pane>
       </el-tabs>
 
-      <el-empty v-else description="暂无数据" />
+      <el-empty v-else :description="t('applet.message.noData')" />
     </el-card>
   </div>
 </template>
@@ -30,22 +44,86 @@
 <script setup name="Applet" lang="ts">
 import { getApplet, updateApplet } from '@/api/setting/applet';
 import { AppletVO } from '@/api/setting/applet/types';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const loading = ref(true);
 const saveLoading = ref(false);
 const activeTab = ref('userAgreement');
+const userAgreementLang = ref('zh_CN');
+const privacyAgreementLang = ref('zh_CN');
 const appletData = ref<AppletVO | null>(null);
 
+// 用户协议数据(中英文)
+const userAgreementData = ref({
+  zh_CN: '',
+  en_US: ''
+});
+
+// 隐私协议数据(中英文)
+const privacyAgreementData = ref({
+  zh_CN: '',
+  en_US: ''
+});
+
+/**
+ * 解析协议数据
+ * 支持三种格式:
+ * 1. BASE64编码的JSON字符串
+ * 2. 直接的JSON字符串
+ * 3. 普通字符串(兼容旧数据)
+ */
+const parseAgreementData = (data: string) => {
+  if (!data) {
+    return { zh_CN: '', en_US: '' };
+  }
+  
+  try {
+    // 尝试BASE64解码
+    const decoded = decodeURIComponent(escape(atob(data)));
+    const parsed = JSON.parse(decoded);
+    return {
+      zh_CN: parsed.zh_CN || '',
+      en_US: parsed.en_US || ''
+    };
+  } catch (e) {
+    // BASE64解码失败,尝试直接JSON解析
+    try {
+      const parsed = JSON.parse(data);
+      return {
+        zh_CN: parsed.zh_CN || '',
+        en_US: parsed.en_US || ''
+      };
+    } catch (e2) {
+      // JSON解析也失败,作为普通字符串处理(旧格式数据)
+      return {
+        zh_CN: data,
+        en_US: ''
+      };
+    }
+  }
+};
+
 /** 获取小程序设置数据 */
 const fetchData = async () => {
   loading.value = true;
   try {
     const res = await getApplet(1);
     appletData.value = res.data;
+    
+    // 解析用户协议
+    if (appletData.value?.userAgreement) {
+      userAgreementData.value = parseAgreementData(appletData.value.userAgreement);
+    }
+    
+    // 解析隐私协议
+    if (appletData.value?.privacyAgreement) {
+      privacyAgreementData.value = parseAgreementData(appletData.value.privacyAgreement);
+    }
   } catch (error) {
-    console.error('获取数据失败:', error);
+    console.error(t('applet.message.fetchFailed'), error);
   } finally {
     loading.value = false;
   }
@@ -57,18 +135,28 @@ const handleSave = async () => {
   
   saveLoading.value = true;
   try {
-    // 使用BASE64编码
+    // 将中英文协议整理成JSON格式,然后进行BASE64编码
+    const userAgreementJson = JSON.stringify({
+      zh_CN: userAgreementData.value.zh_CN || '',
+      en_US: userAgreementData.value.en_US || ''
+    });
+    
+    const privacyAgreementJson = JSON.stringify({
+      zh_CN: privacyAgreementData.value.zh_CN || '',
+      en_US: privacyAgreementData.value.en_US || ''
+    });
+    
     const encodedData = {
       ...appletData.value,
       id: 1, // 默认设置为1
-      userAgreement: btoa(unescape(encodeURIComponent(appletData.value.userAgreement || ''))),
-      privacyAgreement: btoa(unescape(encodeURIComponent(appletData.value.privacyAgreement || '')))
+      userAgreement: btoa(unescape(encodeURIComponent(userAgreementJson))),
+      privacyAgreement: btoa(unescape(encodeURIComponent(privacyAgreementJson)))
     };
     
     await updateApplet(encodedData);
-    proxy?.$modal.msgSuccess("保存成功");
+    proxy?.$modal.msgSuccess(t('applet.message.saveSuccess'));
   } catch (error) {
-    console.error('保存失败:', error);
+    console.error(t('applet.message.saveFailed'), error);
   } finally {
     saveLoading.value = false;
   }

+ 36 - 7
src/views/system/dict/data.vue

@@ -51,13 +51,13 @@
           <template #default="scope">
             <span
               v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)"
-              >{{ scope.row.dictLabel }}</span
+              >{{ parseI18nName(scope.row.dictLabel) }}</span
             >
             <el-tag
               v-else
               :type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
               :class="scope.row.cssClass"
-              >{{ scope.row.dictLabel }}</el-tag
+              >{{ parseI18nName(scope.row.dictLabel) }}</el-tag
             >
           </template>
         </el-table-column>
@@ -89,8 +89,11 @@
         <el-form-item label="字典类型">
           <el-input v-model="form.dictType" :disabled="true" />
         </el-form-item>
-        <el-form-item label="数据标签" prop="dictLabel">
-          <el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
+        <el-form-item label="中文标签" prop="dictLabelZh">
+          <el-input v-model="form.dictLabelZh" placeholder="请输入中文标签" />
+        </el-form-item>
+        <el-form-item label="英文标签" prop="dictLabelEn">
+          <el-input v-model="form.dictLabelEn" placeholder="请输入英文标签" />
         </el-form-item>
         <el-form-item label="数据键值" prop="dictValue">
           <el-input v-model="form.dictValue" placeholder="请输入数据键值" />
@@ -132,6 +135,7 @@ import { listData, getData, delData, addData, updateData } from '@/api/system/di
 import { DictTypeVO } from '@/api/system/dict/type/types';
 import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
 import { RouteLocationNormalized } from 'vue-router';
+import { parseI18nName } from '@/utils/i18n';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const route = useRoute();
@@ -167,6 +171,8 @@ const listClassOptions = ref<Array<{ value: string; label: string }>>([
 const initFormData: DictDataForm = {
   dictCode: undefined,
   dictLabel: '',
+  dictLabelZh: '',
+  dictLabelEn: '',
   dictValue: '',
   cssClass: '',
   listClass: 'primary',
@@ -183,7 +189,8 @@ const data = reactive<PageData<DictDataForm, DictDataQuery>>({
     dictLabel: ''
   },
   rules: {
-    dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
+    dictLabelZh: [{ required: true, message: '中文标签不能为空', trigger: 'blur' }],
+    dictLabelEn: [{ required: true, message: '英文标签不能为空', trigger: 'blur' }],
     dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
     dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
   }
@@ -267,6 +274,8 @@ const handleUpdate = async (row?: DictDataVO) => {
   const dictCode = row?.dictCode || ids.value[0];
   const res = await getData(dictCode);
   Object.assign(form.value, res.data);
+  // 解析国际化标签
+  parseDictLabelForEdit(res.data.dictLabel);
   dialog.visible = true;
   dialog.title = '修改字典数据';
 };
@@ -274,7 +283,13 @@ const handleUpdate = async (row?: DictDataVO) => {
 const submitForm = () => {
   dataFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      form.value.dictCode ? await updateData(form.value) : await addData(form.value);
+      // 合成国际化标签JSON字符串
+      const dictLabelJson = JSON.stringify({
+        zh_CN: form.value.dictLabelZh,
+        en_US: form.value.dictLabelEn
+      });
+      const submitData = { ...form.value, dictLabel: dictLabelJson };
+      form.value.dictCode ? await updateData(submitData) : await addData(submitData);
       useDictStore().removeDict(queryParams.value.dictType);
       proxy?.$modal.msgSuccess('操作成功');
       dialog.visible = false;
@@ -285,7 +300,8 @@ const submitForm = () => {
 /** 删除按钮操作 */
 const handleDelete = async (row?: DictDataVO) => {
   const dictCodes = row?.dictCode || ids.value;
-  await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
+  const labelText = row ? parseI18nName(row.dictLabel) : dictCodes;
+  await proxy?.$modal.confirm('是否确认删除字典标签为"' + labelText + '"的数据项?');
   await delData(dictCodes);
   await getList();
   proxy?.$modal.msgSuccess('删除成功');
@@ -302,6 +318,19 @@ const handleExport = () => {
   );
 };
 
+/** 解析国际化字典标签用于编辑 */
+const parseDictLabelForEdit = (dictLabel: string) => {
+  try {
+    const labelObj = JSON.parse(dictLabel);
+    form.value.dictLabelZh = labelObj.zh_CN || '';
+    form.value.dictLabelEn = labelObj.en_US || '';
+  } catch (e) {
+    // 如果解析失败,将原值设为中文标签
+    form.value.dictLabelZh = dictLabel;
+    form.value.dictLabelEn = '';
+  }
+};
+
 onMounted(() => {
   getTypes(route.params && (route.params.dictId as string));
   getTypeList();

+ 116 - 77
src/views/system/menu/index.vue

@@ -3,18 +3,18 @@
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="菜单名称" prop="menuName">
-              <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
+            <el-form-item :label="$t('menu.search.menuName')" prop="menuName">
+              <el-input v-model="queryParams.menuName" :placeholder="$t('menu.search.menuNamePlaceholder')" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="状态" prop="status">
-              <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
-                <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
+            <el-form-item :label="$t('menu.search.status')" prop="status">
+              <el-select v-model="queryParams.status" :placeholder="$t('menu.search.statusPlaceholder')" clearable>
+                <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value" />
               </el-select>
             </el-form-item>
             <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              <el-button type="primary" icon="Search" @click="handleQuery">{{ $t('menu.search.search') }}</el-button>
+              <el-button icon="Refresh" @click="resetQuery">{{ $t('menu.search.reset') }}</el-button>
             </el-form-item>
           </el-form>
         </el-card>
@@ -25,10 +25,10 @@
       <template #header>
         <el-row :gutter="10">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
+            <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">{{ $t('menu.button.add') }}</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">级联删除</el-button>
+            <el-button v-hasPermi="['system:menu:remove']" type="danger" plain icon="Delete" @click="handleCascadeDelete" :loading="deleteLoading">{{ $t('menu.button.cascadeDelete') }}</el-button>
           </el-col>
           <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
         </el-row>
@@ -46,34 +46,38 @@
         :load="getChildrenList"
         :expand-change="expandMenuHandle"
       >
-        <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
-        <el-table-column prop="icon" label="图标" align="center" width="100">
+        <el-table-column prop="menuName" :label="$t('menu.table.menuName')" :show-overflow-tooltip="true" width="160">
+          <template #default="scope">
+            <span>{{ parseI18nName(scope.row.menuName) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="icon" :label="$t('menu.table.icon')" align="center" width="100">
           <template #default="scope">
             <svg-icon :icon-class="scope.row.icon" />
           </template>
         </el-table-column>
-        <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
-        <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
-        <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
-        <el-table-column prop="status" label="状态" width="80">
+        <el-table-column prop="orderNum" :label="$t('menu.table.sort')" width="60"></el-table-column>
+        <el-table-column prop="perms" :label="$t('menu.table.perms')" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="component" :label="$t('menu.table.component')" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="status" :label="$t('menu.table.status')" width="80">
           <template #default="scope">
             <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
           </template>
         </el-table-column>
-        <el-table-column label="创建时间" align="center" prop="createTime">
+        <el-table-column :label="$t('menu.table.createTime')" align="center" prop="createTime">
           <template #default="scope">
             <span>{{ scope.row.createTime }}</span>
           </template>
         </el-table-column>
-        <el-table-column fixed="right" label="操作" width="180">
+        <el-table-column fixed="right" :label="$t('menu.table.operation')" width="180">
           <template #default="scope">
-            <el-tooltip content="修改" placement="top">
+            <el-tooltip :content="$t('menu.button.edit')" placement="top">
               <el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
             </el-tooltip>
-            <el-tooltip content="新增" placement="top">
+            <el-tooltip :content="$t('menu.button.add')" placement="top">
               <el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
             </el-tooltip>
-            <el-tooltip content="删除" placement="top">
+            <el-tooltip :content="$t('menu.button.delete')" placement="top">
               <el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
             </el-tooltip>
           </template>
@@ -81,43 +85,48 @@
       </el-table>
     </el-card>
 
-    <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px">
-      <el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px">
+    <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="900px">
+      <el-form ref="menuFormRef" :model="form" :rules="rules" label-width="150px">
         <el-row>
           <el-col :span="24">
-            <el-form-item label="上级菜单">
+            <el-form-item :label="$t('menu.form.parentMenu')">
               <el-tree-select
                 v-model="form.parentId"
                 :data="menuOptions"
                 :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
                 value-key="menuId"
-                placeholder="选择上级菜单"
+                :placeholder="$t('menu.form.parentMenuPlaceholder')"
                 check-strictly
               />
             </el-form-item>
           </el-col>
           <el-col :span="24">
-            <el-form-item label="菜单类型" prop="menuType">
+            <el-form-item :label="$t('menu.form.menuType')" prop="menuType">
               <el-radio-group v-model="form.menuType">
-                <el-radio value="M">目录</el-radio>
-                <el-radio value="C">菜单</el-radio>
-                <el-radio value="F">按钮</el-radio>
+                <el-radio value="M">{{ $t('menu.form.directory') }}</el-radio>
+                <el-radio value="C">{{ $t('menu.form.menu') }}</el-radio>
+                <el-radio value="F">{{ $t('menu.form.button') }}</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
           <el-col v-if="form.menuType !== 'F'" :span="24">
-            <el-form-item label="菜单图标" prop="icon">
+            <el-form-item :label="$t('menu.form.menuIcon')" prop="icon">
               <!-- 图标选择器 -->
               <icon-select v-model="form.icon" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="菜单名称" prop="menuName">
-              <el-input v-model="form.menuName" placeholder="请输入菜单名称" />
+            <el-form-item :label="$t('menu.form.zhName')" prop="menuNameZh">
+              <el-input v-model="form.menuNameZh" :placeholder="$t('menu.form.zhNamePlaceholder')" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item :label="$t('menu.form.enName')" prop="menuNameEn">
+              <el-input v-model="form.menuNameEn" :placeholder="$t('menu.form.enNamePlaceholder')" />
             </el-form-item>
           </el-col>
           <el-col :span="12">
-            <el-form-item label="显示排序" prop="orderNum">
+            <el-form-item :label="$t('menu.form.sort')" prop="orderNum">
               <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
             </el-form-item>
           </el-col>
@@ -125,16 +134,16 @@
             <el-form-item>
               <template #label>
                 <span>
-                  <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
+                  <el-tooltip :content="$t('menu.form.isFrameTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon> </el-tooltip
-                  >是否外链
+                  >{{ $t('menu.form.isFrame') }}
                 </span>
               </template>
               <el-radio-group v-model="form.isFrame">
-                <el-radio value="0"></el-radio>
-                <el-radio value="1"></el-radio>
+                <el-radio value="0">{{ $t('menu.form.yes') }}</el-radio>
+                <el-radio value="1">{{ $t('menu.form.no') }}</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -142,58 +151,58 @@
             <el-form-item prop="path">
               <template #label>
                 <span>
-                  <el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top">
+                  <el-tooltip :content="$t('menu.form.pathTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  路由地址
+                  {{ $t('menu.form.path') }}
                 </span>
               </template>
-              <el-input v-model="form.path" placeholder="请输入路由地址" />
+              <el-input v-model="form.path" :placeholder="$t('menu.form.pathPlaceholder')" />
             </el-form-item>
           </el-col>
           <el-col v-if="form.menuType === 'C'" :span="12">
             <el-form-item prop="component">
               <template #label>
                 <span>
-                  <el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
+                  <el-tooltip :content="$t('menu.form.componentTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  组件路径
+                  {{ $t('menu.form.component') }}
                 </span>
               </template>
-              <el-input v-model="form.component" placeholder="请输入组件路径" />
+              <el-input v-model="form.component" :placeholder="$t('menu.form.componentPlaceholder')" />
             </el-form-item>
           </el-col>
           <el-col v-if="form.menuType !== 'M'" :span="12">
             <el-form-item>
-              <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
+              <el-input v-model="form.perms" :placeholder="$t('menu.form.permsPlaceholder')" maxlength="100" />
               <template #label>
                 <span>
-                  <el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
+                  <el-tooltip :content="$t('menu.form.permsTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  权限字符
+                  {{ $t('menu.form.perms') }}
                 </span>
               </template>
             </el-form-item>
           </el-col>
           <el-col v-if="form.menuType === 'C'" :span="12">
             <el-form-item>
-              <el-input v-model="form.queryParam" placeholder="请输入路由参数" maxlength="255" />
+              <el-input v-model="form.queryParam" :placeholder="$t('menu.form.queryParamPlaceholder')" maxlength="255" />
               <template #label>
                 <span>
-                  <el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top">
+                  <el-tooltip :content="$t('menu.form.queryParamTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  路由参数
+                  {{ $t('menu.form.queryParam') }}
                 </span>
               </template>
             </el-form-item>
@@ -202,17 +211,17 @@
             <el-form-item>
               <template #label>
                 <span>
-                  <el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top">
+                  <el-tooltip :content="$t('menu.form.isCacheTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  是否缓存
+                  {{ $t('menu.form.isCache') }}
                 </span>
               </template>
               <el-radio-group v-model="form.isCache">
-                <el-radio value="0">缓存</el-radio>
-                <el-radio value="1">不缓存</el-radio>
+                <el-radio value="0">{{ $t('menu.form.cache') }}</el-radio>
+                <el-radio value="1">{{ $t('menu.form.noCache') }}</el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -220,16 +229,16 @@
             <el-form-item>
               <template #label>
                 <span>
-                  <el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top">
+                  <el-tooltip :content="$t('menu.form.visibleTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  显示状态
+                  {{ $t('menu.form.visible') }}
                 </span>
               </template>
               <el-radio-group v-model="form.visible">
-                <el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio>
+                <el-radio v-for="dict in sys_show_hide" :key="dict.value" :value="dict.value">{{ parseI18nName(dict.label) }} </el-radio>
               </el-radio-group>
             </el-form-item>
           </el-col>
@@ -237,17 +246,17 @@
             <el-form-item>
               <template #label>
                 <span>
-                  <el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top">
+                  <el-tooltip :content="$t('menu.form.statusTip')" placement="top">
                     <el-icon>
                       <question-filled />
                     </el-icon>
                   </el-tooltip>
-                  菜单状态
+                  {{ $t('menu.form.status') }}
                 </span>
               </template>
               <el-radio-group v-model="form.status">
                 <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">
-                  {{ dict.label }}
+                  {{ parseI18nName(dict.label) }}
                 </el-radio>
               </el-radio-group>
             </el-form-item>
@@ -256,13 +265,13 @@
       </el-form>
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitForm">确 定</el-button>
-          <el-button @click="cancel">取 消</el-button>
+          <el-button type="primary" @click="submitForm">{{ $t('menu.button.submit') }}</el-button>
+          <el-button @click="cancel">{{ $t('menu.button.cancel') }}</el-button>
         </div>
       </template>
     </el-dialog>
 
-    <el-dialog v-model="deleteDialog.visible" :title="deleteDialog.title" destroy-on-close append-to-bod width="750px">
+    <el-dialog v-model="deleteDialog.visible" :title="deleteDialog.title" destroy-on-close append-to-bod width="900px">
       <el-tree
         ref="menuTreeRef"
         class="tree-border"
@@ -270,14 +279,14 @@
         show-checkbox
         node-key="menuId"
         :check-strictly="false"
-        empty-text="加载中,请稍候"
+        :empty-text="$t('menu.message.loading')"
         :default-expanded-keys="[0]"
         :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
       />
       <template #footer>
         <div class="dialog-footer">
-          <el-button type="primary" @click="submitDeleteForm" :loading="deleteLoading">确 定</el-button>
-          <el-button @click="cancelCascade">取 消</el-button>
+          <el-button type="primary" @click="submitDeleteForm" :loading="deleteLoading">{{ $t('menu.button.submit') }}</el-button>
+          <el-button @click="cancelCascade">{{ $t('menu.button.cancel') }}</el-button>
         </div>
       </template>
     </el-dialog>
@@ -288,6 +297,7 @@
 import { addMenu, cascadeDelMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
 import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
 import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
+import { parseI18nName } from '@/utils/i18n';
 
 interface MenuOptionsType {
   menuId: number;
@@ -317,6 +327,8 @@ const initFormData = {
   menuId: undefined,
   parentId: 0,
   menuName: '',
+  menuNameZh: '',
+  menuNameEn: '',
   icon: '',
   menuType: MenuTypeEnum.M,
   orderNum: 1,
@@ -332,9 +344,10 @@ const data = reactive<PageData<MenuForm, MenuQuery>>({
     status: undefined
   },
   rules: {
-    menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
-    orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
-    path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
+    menuNameZh: [{ required: true, message: proxy?.$t('menu.rule.zhNameRequired'), trigger: 'blur' }],
+    menuNameEn: [{ required: true, message: proxy?.$t('menu.rule.enNameRequired'), trigger: 'blur' }],
+    orderNum: [{ required: true, message: proxy?.$t('menu.rule.sortRequired'), trigger: 'blur' }],
+    path: [{ required: true, message: proxy?.$t('menu.rule.pathRequired'), trigger: 'blur' }]
   }
 });
 
@@ -414,8 +427,13 @@ const getList = async () => {
 const getTreeselect = async () => {
   menuOptions.value = [];
   const response = await listMenu();
-  const menu: MenuOptionsType = { menuId: 0, menuName: '主类目', children: [] };
-  menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId');
+  // 解析每个菜单的国际化名称
+  const parsedData = response.data.map((item) => ({
+    ...item,
+    menuName: parseI18nName(item.menuName)
+  }));
+  const menu: MenuOptionsType = { menuId: 0, menuName: proxy?.$t('menu.form.mainMenu'), children: [] };
+  menu.children = proxy?.handleTree<MenuOptionsType>(parsedData, 'menuId');
   menuOptions.value.push(menu);
 };
 /** 取消按钮 */
@@ -444,7 +462,7 @@ const handleAdd = (row?: MenuVO) => {
   getTreeselect();
   row && row.menuId ? (form.value.parentId = row.menuId) : (form.value.parentId = 0);
   dialog.visible = true;
-  dialog.title = '添加菜单';
+  dialog.title = proxy?.$t('menu.dialog.add');
 };
 /** 修改按钮操作 */
 const handleUpdate = async (row: MenuVO) => {
@@ -453,16 +471,24 @@ const handleUpdate = async (row: MenuVO) => {
   if (row.menuId) {
     const { data } = await getMenu(row.menuId);
     form.value = data;
+    // 解析国际化名称
+    parseMenuNameForEdit(data.menuName);
   }
   dialog.visible = true;
-  dialog.title = '修改菜单';
+  dialog.title = proxy?.$t('menu.dialog.edit');
 };
 /** 提交按钮 */
 const submitForm = () => {
   menuFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
-      proxy?.$modal.msgSuccess('操作成功');
+      // 合成国际化名称JSON字符串
+      const menuNameJson = JSON.stringify({
+        zh_CN: form.value.menuNameZh,
+        en_US: form.value.menuNameEn
+      });
+      const submitData = { ...form.value, menuName: menuNameJson };
+      form.value.menuId ? await updateMenu(submitData) : await addMenu(submitData);
+      proxy?.$modal.msgSuccess(proxy?.$t('menu.message.operationSuccess'));
       dialog.visible = false;
       await getList();
     }
@@ -470,10 +496,10 @@ const submitForm = () => {
 };
 /** 删除按钮操作 */
 const handleDelete = async (row: MenuVO) => {
-  await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
+  await proxy?.$modal.confirm(proxy?.$t('menu.message.deleteConfirm', { name: parseI18nName(row.menuName) }));
   await delMenu(row.menuId);
   await getList();
-  proxy?.$modal.msgSuccess('删除成功');
+  proxy?.$modal.msgSuccess(proxy?.$t('menu.message.deleteSuccess'));
 };
 
 const deleteLoading = ref<boolean>(false);
@@ -481,7 +507,7 @@ const menuTreeRef = ref<ElTreeInstance>();
 
 const deleteDialog = reactive<DialogOption>({
   visible: false,
-  title: '级联删除菜单'
+  title: proxy?.$t('menu.dialog.cascadeDelete')
 });
 
 /** 级联删除按钮操作 */
@@ -512,6 +538,19 @@ const submitDeleteForm = async () => {
   deleteDialog.visible = false;
 };
 
+/** 解析国际化菜单名称用于编辑 */
+const parseMenuNameForEdit = (menuName: string) => {
+  try {
+    const nameObj = JSON.parse(menuName);
+    form.value.menuNameZh = nameObj.zh_CN || '';
+    form.value.menuNameEn = nameObj.en_US || '';
+  } catch (e) {
+    // 如果解析失败,将原值设为中文名称
+    form.value.menuNameZh = menuName;
+    form.value.menuNameEn = '';
+  }
+};
+
 onMounted(() => {
   getList();
 });

+ 19 - 2
src/views/system/role/index.vue

@@ -197,6 +197,7 @@ import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/m
 import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
 import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
 import { useI18n } from 'vue-i18n';
+import { parseI18nName } from '@/utils/i18n';
 
 const { t } = useI18n();
 const router = useRouter();
@@ -271,6 +272,22 @@ const dialog = reactive<DialogOption>({
   title: ''
 });
 
+/** 递归解析菜单树的label字段 */
+const parseMenuTree = (menus: MenuTreeOption[]): MenuTreeOption[] => {
+  return menus.map(menu => {
+    const parsedMenu = { ...menu };
+    // 解析label字段
+    if (parsedMenu.label) {
+      parsedMenu.label = parseI18nName(parsedMenu.label);
+    }
+    // 递归处理子节点
+    if (parsedMenu.children && parsedMenu.children.length > 0) {
+      parsedMenu.children = parseMenuTree(parsedMenu.children);
+    }
+    return parsedMenu;
+  });
+};
+
 /**
  * 查询角色列表
  */
@@ -343,7 +360,7 @@ const handleAuthUser = (row: RoleVO) => {
 /** 查询菜单树结构 */
 const getMenuTreeselect = async () => {
   const res = await menuTreeselect();
-  menuOptions.value = res.data;
+  menuOptions.value = parseMenuTree(res.data);
 };
 /** 所有部门节点数据 */
 const getDeptAllCheckedKeys = (): any => {
@@ -393,7 +410,7 @@ const handleUpdate = async (row?: RoleVO) => {
 /** 根据角色ID查询菜单树结构 */
 const getRoleMenuTreeselect = (roleId: string | number) => {
   return roleMenuTreeselect(roleId).then((res): RoleMenuTree => {
-    menuOptions.value = res.data.menus;
+    menuOptions.value = parseMenuTree(res.data.menus);
     return res.data;
   });
 };

+ 12 - 12
src/views/system/user/profile/index.vue

@@ -5,7 +5,7 @@
         <el-card class="box-card">
           <template #header>
             <div class="clearfix">
-              <span>个人信息</span>
+              <span>{{ $t('profile.card.personalInfo') }}</span>
             </div>
           </template>
           <div>
@@ -14,27 +14,27 @@
             </div>
             <ul class="list-group list-group-striped">
               <li class="list-group-item">
-                <svg-icon icon-class="user" />用户名称
+                <svg-icon icon-class="user" />{{ $t('profile.info.userName') }}
                 <div class="pull-right">{{ state.user.userName }}</div>
               </li>
               <li class="list-group-item">
-                <svg-icon icon-class="phone" />手机号码
+                <svg-icon icon-class="phone" />{{ $t('profile.info.phonenumber') }}
                 <div class="pull-right">{{ state.user.phonenumber }}</div>
               </li>
               <li class="list-group-item">
-                <svg-icon icon-class="email" />用户邮箱
+                <svg-icon icon-class="email" />{{ $t('profile.info.email') }}
                 <div class="pull-right">{{ state.user.email }}</div>
               </li>
               <li class="list-group-item">
-                <svg-icon icon-class="tree" />所属部门
+                <svg-icon icon-class="tree" />{{ $t('profile.info.dept') }}
                 <div v-if="state.user.deptName" class="pull-right">{{ state.user.deptName }} / {{ state.postGroup }}</div>
               </li>
               <li class="list-group-item">
-                <svg-icon icon-class="peoples" />所属角色
+                <svg-icon icon-class="peoples" />{{ $t('profile.info.role') }}
                 <div class="pull-right">{{ state.roleGroup }}</div>
               </li>
               <li class="list-group-item">
-                <svg-icon icon-class="date" />创建日期
+                <svg-icon icon-class="date" />{{ $t('profile.info.createTime') }}
                 <div class="pull-right">{{ state.user.createTime }}</div>
               </li>
             </ul>
@@ -45,20 +45,20 @@
         <el-card>
           <template #header>
             <div class="clearfix">
-              <span>基本资料</span>
+              <span>{{ $t('profile.card.basicInfo') }}</span>
             </div>
           </template>
           <el-tabs v-model="activeTab">
-            <el-tab-pane label="基本资料" name="userinfo">
+            <el-tab-pane :label="$t('profile.tab.userInfo')" name="userinfo">
               <userInfo :user="userForm" />
             </el-tab-pane>
-            <el-tab-pane label="修改密码" name="resetPwd">
+            <el-tab-pane :label="$t('profile.tab.resetPwd')" name="resetPwd">
               <resetPwd />
             </el-tab-pane>
-<!--            <el-tab-pane label="第三方应用" name="thirdParty">-->
+<!--            <el-tab-pane :label="$t('profile.tab.thirdParty')" name="thirdParty">-->
 <!--              <thirdParty :auths="state.auths" />-->
 <!--            </el-tab-pane>-->
-            <el-tab-pane label="在线设备" name="onlineDevice">
+            <el-tab-pane :label="$t('profile.tab.onlineDevice')" name="onlineDevice">
               <onlineDevice :devices="state.devices" />
             </el-tab-pane>
           </el-tabs>

+ 12 - 10
src/views/system/user/profile/onlineDevice.vue

@@ -1,23 +1,23 @@
 <template>
   <div>
     <el-table :data="devices" border style="width: 100%; height: 100%; font-size: 14px">
-      <el-table-column label="设备类型" align="center">
+      <el-table-column :label="$t('profile.onlineDevice.deviceType')" align="center">
         <template #default="scope">
           <dict-tag :options="sys_device_type" :value="scope.row.deviceType" />
         </template>
       </el-table-column>
-      <el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
-      <el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
-      <el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
-      <el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
-      <el-table-column label="登录时间" align="center" prop="loginTime" width="180">
+      <el-table-column :label="$t('profile.onlineDevice.ipaddr')" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
+      <el-table-column :label="$t('profile.onlineDevice.loginLocation')" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
+      <el-table-column :label="$t('profile.onlineDevice.os')" align="center" prop="os" :show-overflow-tooltip="true" />
+      <el-table-column :label="$t('profile.onlineDevice.browser')" align="center" prop="browser" :show-overflow-tooltip="true" />
+      <el-table-column :label="$t('profile.onlineDevice.loginTime')" align="center" prop="loginTime" width="180">
         <template #default="scope">
           <span>{{ proxy.parseTime(scope.row.loginTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <el-table-column :label="$t('profile.onlineDevice.operation')" align="center" class-name="small-padding fixed-width">
         <template #default="scope">
-          <el-tooltip content="删除" placement="top">
+          <el-tooltip :content="$t('profile.onlineDevice.delete')" placement="top">
             <el-button link type="primary" icon="Delete" @click="handldDelOnline(scope.row)"> </el-button>
           </el-tooltip>
         </template>
@@ -29,7 +29,9 @@
 <script setup name="Online" lang="ts">
 import { delOnline } from '@/api/monitor/online';
 import { propTypes } from '@/utils/propTypes';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
 
@@ -40,13 +42,13 @@ const devices = computed(() => props.devices);
 
 /** 删除按钮操作 */
 const handldDelOnline = (row: any) => {
-  ElMessageBox.confirm('删除设备后,在该设备登录需要重新进行验证')
+  ElMessageBox.confirm(t('profile.onlineDevice.deleteConfirm'))
     .then(() => {
       return delOnline(row.tokenId);
     })
     .then((res: any) => {
       if (res.code === 200) {
-        proxy?.$modal.msgSuccess('删除成功');
+        proxy?.$modal.msgSuccess(t('profile.onlineDevice.deleteSuccess'));
         proxy?.$tab.refreshPage();
       } else {
         proxy?.$modal.msgError(res.msg);

+ 18 - 16
src/views/system/user/profile/resetPwd.vue

@@ -1,17 +1,17 @@
 <template>
-  <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
-    <el-form-item label="旧密码" prop="oldPassword">
-      <el-input v-model="user.oldPassword" placeholder="请输入旧密码" type="password" show-password />
+  <el-form ref="pwdRef" :model="user" :rules="rules" label-width="160px">
+    <el-form-item :label="$t('profile.resetPwdForm.oldPassword')" prop="oldPassword">
+      <el-input v-model="user.oldPassword" :placeholder="$t('profile.resetPwdForm.oldPasswordPlaceholder')" type="password" show-password />
     </el-form-item>
-    <el-form-item label="新密码" prop="newPassword">
-      <el-input v-model="user.newPassword" placeholder="请输入新密码" type="password" show-password />
+    <el-form-item :label="$t('profile.resetPwdForm.newPassword')" prop="newPassword">
+      <el-input v-model="user.newPassword" :placeholder="$t('profile.resetPwdForm.newPasswordPlaceholder')" type="password" show-password />
     </el-form-item>
-    <el-form-item label="确认密码" prop="confirmPassword">
-      <el-input v-model="user.confirmPassword" placeholder="请确认新密码" type="password" show-password />
+    <el-form-item :label="$t('profile.resetPwdForm.confirmPassword')" prop="confirmPassword">
+      <el-input v-model="user.confirmPassword" :placeholder="$t('profile.resetPwdForm.confirmPasswordPlaceholder')" type="password" show-password />
     </el-form-item>
     <el-form-item>
-      <el-button type="primary" @click="submit">保存</el-button>
-      <el-button type="danger" @click="close">关闭</el-button>
+      <el-button type="primary" @click="submit">{{ $t('profile.resetPwdForm.save') }}</el-button>
+      <el-button type="danger" @click="close">{{ $t('profile.resetPwdForm.close') }}</el-button>
     </el-form-item>
   </el-form>
 </template>
@@ -19,7 +19,9 @@
 <script setup lang="ts">
 import { updateUserPwd } from '@/api/system/user';
 import type { ResetPwdForm } from '@/api/system/user/types';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const pwdRef = ref<ElFormInstance>();
 const user = ref<ResetPwdForm>({
@@ -30,25 +32,25 @@ const user = ref<ResetPwdForm>({
 
 const equalToPassword = (rule: any, value: string, callback: any) => {
   if (user.value.newPassword !== value) {
-    callback(new Error('两次输入的密码不一致'));
+    callback(new Error(t('profile.rule.passwordMismatch')));
   } else {
     callback();
   }
 };
 const rules = ref({
-  oldPassword: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
+  oldPassword: [{ required: true, message: t('profile.rule.oldPasswordRequired'), trigger: 'blur' }],
   newPassword: [
-    { required: true, message: '新密码不能为空', trigger: 'blur' },
+    { required: true, message: t('profile.rule.newPasswordRequired'), trigger: 'blur' },
     {
       min: 6,
       max: 20,
-      message: '长度在 6 到 20 个字符',
+      message: t('profile.rule.passwordLength'),
       trigger: 'blur'
     },
-    { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' }
+    { pattern: /^[^<>"'|\\]+$/, message: t('profile.rule.passwordPattern'), trigger: 'blur' }
   ],
   confirmPassword: [
-    { required: true, message: '确认密码不能为空', trigger: 'blur' },
+    { required: true, message: t('profile.rule.confirmPasswordRequired'), trigger: 'blur' },
     {
       required: true,
       validator: equalToPassword,
@@ -62,7 +64,7 @@ const submit = () => {
   pwdRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       await updateUserPwd(user.value.oldPassword, user.value.newPassword);
-      proxy?.$modal.msgSuccess('修改成功');
+      proxy?.$modal.msgSuccess(t('profile.message.updateSuccess'));
     }
   });
 };

+ 17 - 15
src/views/system/user/profile/userInfo.vue

@@ -1,23 +1,23 @@
 <template>
-  <el-form ref="userRef" :model="userForm" :rules="rules" label-width="80px">
-    <el-form-item label="用户昵称" prop="nickName">
+  <el-form ref="userRef" :model="userForm" :rules="rules" label-width="160px">
+    <el-form-item :label="$t('profile.userInfoForm.nickName')" prop="nickName">
       <el-input v-model="userForm.nickName" maxlength="30" />
     </el-form-item>
-    <el-form-item label="手机号码" prop="phonenumber">
+    <el-form-item :label="$t('profile.info.phonenumber')" prop="phonenumber">
       <el-input v-model="userForm.phonenumber" maxlength="11" />
     </el-form-item>
-    <el-form-item label="邮箱" prop="email">
+    <el-form-item :label="$t('profile.info.email')" prop="email">
       <el-input v-model="userForm.email" maxlength="50" />
     </el-form-item>
-    <el-form-item label="性别">
+    <el-form-item :label="$t('profile.userInfoForm.sex')">
       <el-radio-group v-model="userForm.sex">
-        <el-radio value="0"></el-radio>
-        <el-radio value="1"></el-radio>
+        <el-radio value="0">{{ $t('profile.userInfoForm.male') }}</el-radio>
+        <el-radio value="1">{{ $t('profile.userInfoForm.female') }}</el-radio>
       </el-radio-group>
     </el-form-item>
     <el-form-item>
-      <el-button type="primary" @click="submit">保存</el-button>
-      <el-button type="danger" @click="close">关闭</el-button>
+      <el-button type="primary" @click="submit">{{ $t('profile.userInfoForm.save') }}</el-button>
+      <el-button type="danger" @click="close">{{ $t('profile.userInfoForm.close') }}</el-button>
     </el-form-item>
   </el-form>
 </template>
@@ -25,7 +25,9 @@
 <script setup lang="ts">
 import { updateUserProfile } from '@/api/system/user';
 import { propTypes } from '@/utils/propTypes';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const props = defineProps({
   user: propTypes.any.isRequired
 });
@@ -33,22 +35,22 @@ const userForm = computed(() => props.user);
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const userRef = ref<ElFormInstance>();
 const rule: ElFormRules = {
-  nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
+  nickName: [{ required: true, message: t('profile.rule.nickNameRequired'), trigger: 'blur' }],
   email: [
-    { required: true, message: '邮箱地址不能为空', trigger: 'blur' },
+    { required: true, message: t('profile.rule.emailRequired'), trigger: 'blur' },
     {
       type: 'email',
-      message: '请输入正确的邮箱地址',
+      message: t('profile.rule.emailFormat'),
       trigger: ['blur', 'change']
     }
   ],
   phonenumber: [
     {
       required: true,
-      message: '手机号码不能为空',
+      message: t('profile.rule.phonenumberRequired'),
       trigger: 'blur'
     },
-    { pattern: /^1[3456789][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+    { pattern: /^1[3456789][0-9]\d{8}$/, message: t('profile.rule.phonenumberFormat'), trigger: 'blur' }
   ]
 };
 const rules = ref<ElFormRules>(rule);
@@ -58,7 +60,7 @@ const submit = () => {
   userRef.value?.validate(async (valid: boolean) => {
     if (valid) {
       await updateUserProfile(props.user);
-      proxy?.$modal.msgSuccess('修改成功');
+      proxy?.$modal.msgSuccess(t('profile.message.updateSuccess'));
     }
   });
 };

+ 19 - 16
src/views/tool/gen/basicInfoForm.vue

@@ -1,28 +1,28 @@
 <template>
-  <el-form ref="basicInfoForm" :model="infoForm" :rules="rules" label-width="150px">
+  <el-form ref="basicInfoForm" :model="infoForm" :rules="rules" label-width="180px">
     <el-row>
       <el-col :span="12">
-        <el-form-item label="表名称" prop="tableName">
-          <el-input v-model="infoForm.tableName" placeholder="请输入仓库名称" />
+        <el-form-item :label="$t('gen.edit.basic.tableName')" prop="tableName">
+          <el-input v-model="infoForm.tableName" :placeholder="$t('gen.edit.basic.tableNamePlaceholder')" />
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="表描述" prop="tableComment">
-          <el-input v-model="infoForm.tableComment" placeholder="请输入" />
+        <el-form-item :label="$t('gen.edit.basic.tableComment')" prop="tableComment">
+          <el-input v-model="infoForm.tableComment" :placeholder="$t('gen.edit.basic.tableCommentPlaceholder')" />
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="实体类名称" prop="className">
-          <el-input v-model="infoForm.className" placeholder="请输入" />
+        <el-form-item :label="$t('gen.edit.basic.className')" prop="className">
+          <el-input v-model="infoForm.className" :placeholder="$t('gen.edit.basic.classNamePlaceholder')" />
         </el-form-item>
       </el-col>
       <el-col :span="12">
-        <el-form-item label="作者" prop="functionAuthor">
-          <el-input v-model="infoForm.functionAuthor" placeholder="请输入" />
+        <el-form-item :label="$t('gen.edit.basic.functionAuthor')" prop="functionAuthor">
+          <el-input v-model="infoForm.functionAuthor" :placeholder="$t('gen.edit.basic.functionAuthorPlaceholder')" />
         </el-form-item>
       </el-col>
       <el-col :span="24">
-        <el-form-item label="备注" prop="remark">
+        <el-form-item :label="$t('gen.edit.basic.remark')" prop="remark">
           <el-input v-model="infoForm.remark" type="textarea" :rows="3"></el-input>
         </el-form-item>
       </el-col>
@@ -32,6 +32,9 @@
 
 <script setup lang="ts">
 import { propTypes } from '@/utils/propTypes';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
 
 const prop = defineProps({
   info: propTypes.any.def({})
@@ -40,10 +43,10 @@ const prop = defineProps({
 const infoForm = computed(() => prop.info);
 
 // 表单校验
-const rules = ref({
-  tableName: [{ required: true, message: '请输入表名称', trigger: 'blur' }],
-  tableComment: [{ required: true, message: '请输入表描述', trigger: 'blur' }],
-  className: [{ required: true, message: '请输入实体类名称', trigger: 'blur' }],
-  functionAuthor: [{ required: true, message: '请输入作者', trigger: 'blur' }]
-});
+const rules = computed(() => ({
+  tableName: [{ required: true, message: t('gen.edit.basic.tableNameRequired'), trigger: 'blur' }],
+  tableComment: [{ required: true, message: t('gen.edit.basic.tableCommentRequired'), trigger: 'blur' }],
+  className: [{ required: true, message: t('gen.edit.basic.classNameRequired'), trigger: 'blur' }],
+  functionAuthor: [{ required: true, message: t('gen.edit.basic.functionAuthorRequired'), trigger: 'blur' }]
+}));
 </script>

+ 32 - 30
src/views/tool/gen/editTable.vue

@@ -1,20 +1,20 @@
 <template>
   <el-card>
     <el-tabs v-model="activeName">
-      <el-tab-pane label="基本信息" name="basic">
+      <el-tab-pane :label="$t('gen.edit.tabs.basic')" name="basic">
         <basic-info-form ref="basicInfo" :info="info" />
       </el-tab-pane>
-      <el-tab-pane label="字段信息" name="columnInfo">
+      <el-tab-pane :label="$t('gen.edit.tabs.column')" name="columnInfo">
         <el-table ref="dragTable" border :data="columns" row-key="columnId" :max-height="tableHeight">
-          <el-table-column label="序号" type="index" min-width="5%" />
-          <el-table-column label="字段列名" prop="columnName" min-width="10%" :show-overflow-tooltip="true" />
-          <el-table-column label="字段描述" min-width="10%">
+          <el-table-column :label="$t('gen.edit.column.index')" type="index" min-width="5%" />
+          <el-table-column :label="$t('gen.edit.column.columnName')" prop="columnName" min-width="10%" :show-overflow-tooltip="true" />
+          <el-table-column :label="$t('gen.edit.column.columnComment')" min-width="10%">
             <template #default="scope">
               <el-input v-model="scope.row.columnComment"></el-input>
             </template>
           </el-table-column>
-          <el-table-column label="物理类型" prop="columnType" min-width="10%" :show-overflow-tooltip="true" />
-          <el-table-column label="Java类型" min-width="11%">
+          <el-table-column :label="$t('gen.edit.column.columnType')" prop="columnType" min-width="10%" :show-overflow-tooltip="true" />
+          <el-table-column :label="$t('gen.edit.column.javaType')" min-width="11%">
             <template #default="scope">
               <el-select v-model="scope.row.javaType">
                 <el-option label="Long" value="Long" />
@@ -27,33 +27,33 @@
               </el-select>
             </template>
           </el-table-column>
-          <el-table-column label="java属性" min-width="10%">
+          <el-table-column :label="$t('gen.edit.column.javaField')" min-width="10%">
             <template #default="scope">
               <el-input v-model="scope.row.javaField"></el-input>
             </template>
           </el-table-column>
 
-          <el-table-column label="插入" min-width="5%">
+          <el-table-column :label="$t('gen.edit.column.isInsert')" min-width="5%">
             <template #default="scope">
               <el-checkbox v-model="scope.row.isInsert" true-value="1" false-value="0"></el-checkbox>
             </template>
           </el-table-column>
-          <el-table-column label="编辑" min-width="5%">
+          <el-table-column :label="$t('gen.edit.column.isEdit')" min-width="5%">
             <template #default="scope">
               <el-checkbox v-model="scope.row.isEdit" true-value="1" false-value="0"></el-checkbox>
             </template>
           </el-table-column>
-          <el-table-column label="列表" min-width="5%">
+          <el-table-column :label="$t('gen.edit.column.isList')" min-width="5%">
             <template #default="scope">
               <el-checkbox v-model="scope.row.isList" true-value="1" false-value="0"></el-checkbox>
             </template>
           </el-table-column>
-          <el-table-column label="查询" min-width="5%">
+          <el-table-column :label="$t('gen.edit.column.isQuery')" min-width="5%">
             <template #default="scope">
               <el-checkbox v-model="scope.row.isQuery" true-value="1" false-value="0"></el-checkbox>
             </template>
           </el-table-column>
-          <el-table-column label="查询方式" min-width="10%">
+          <el-table-column :label="$t('gen.edit.column.queryType')" min-width="10%">
             <template #default="scope">
               <el-select v-model="scope.row.queryType">
                 <el-option label="=" value="EQ" />
@@ -67,29 +67,29 @@
               </el-select>
             </template>
           </el-table-column>
-          <el-table-column label="必填" min-width="5%">
+          <el-table-column :label="$t('gen.edit.column.isRequired')" min-width="6%">
             <template #default="scope">
               <el-checkbox v-model="scope.row.isRequired" true-value="1" false-value="0"></el-checkbox>
             </template>
           </el-table-column>
-          <el-table-column label="显示类型" min-width="12%">
+          <el-table-column :label="$t('gen.edit.column.htmlType')" min-width="12%">
             <template #default="scope">
               <el-select v-model="scope.row.htmlType">
-                <el-option label="文本框" value="input" />
-                <el-option label="文本域" value="textarea" />
-                <el-option label="下拉框" value="select" />
-                <el-option label="单选框" value="radio" />
-                <el-option label="复选框" value="checkbox" />
-                <el-option label="日期控件" value="datetime" />
-                <el-option label="图片上传" value="imageUpload" />
-                <el-option label="文件上传" value="fileUpload" />
-                <el-option label="富文本控件" value="editor" />
+                <el-option :label="$t('gen.edit.htmlType.input')" value="input" />
+                <el-option :label="$t('gen.edit.htmlType.textarea')" value="textarea" />
+                <el-option :label="$t('gen.edit.htmlType.select')" value="select" />
+                <el-option :label="$t('gen.edit.htmlType.radio')" value="radio" />
+                <el-option :label="$t('gen.edit.htmlType.checkbox')" value="checkbox" />
+                <el-option :label="$t('gen.edit.htmlType.datetime')" value="datetime" />
+                <el-option :label="$t('gen.edit.htmlType.imageUpload')" value="imageUpload" />
+                <el-option :label="$t('gen.edit.htmlType.fileUpload')" value="fileUpload" />
+                <el-option :label="$t('gen.edit.htmlType.editor')" value="editor" />
               </el-select>
             </template>
           </el-table-column>
-          <el-table-column label="字典类型" min-width="12%">
+          <el-table-column :label="$t('gen.edit.column.dictType')" min-width="12%">
             <template #default="scope">
-              <el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择" value-on-clear="">
+              <el-select v-model="scope.row.dictType" clearable filterable :placeholder="$t('gen.edit.column.dictTypePlaceholder')" value-on-clear="">
                 <el-option v-for="dict in dictOptions" :key="dict.dictType" :label="dict.dictName" :value="dict.dictType">
                   <span style="float: left">{{ dict.dictName }}</span>
                   <span style="float: right; color: #8492a6; font-size: 13px">{{ dict.dictType }}</span>
@@ -99,14 +99,14 @@
           </el-table-column>
         </el-table>
       </el-tab-pane>
-      <el-tab-pane label="生成信息" name="genInfo">
+      <el-tab-pane :label="$t('gen.edit.tabs.genInfo')" name="genInfo">
         <gen-info-form ref="genInfo" :info="info" :tables="tables" />
       </el-tab-pane>
     </el-tabs>
     <el-form label-width="100px">
       <div style="text-align: center; margin-left: -100px; margin-top: 10px">
-        <el-button type="primary" @click="submitForm()">提交</el-button>
-        <el-button @click="close()">返回</el-button>
+        <el-button type="primary" @click="submitForm()">{{ $t('gen.edit.button.submit') }}</el-button>
+        <el-button @click="close()">{{ $t('gen.edit.button.return') }}</el-button>
       </div>
     </el-form>
   </el-card>
@@ -120,7 +120,9 @@ import { DictTypeVO } from '@/api/system/dict/type/types';
 import BasicInfoForm from './basicInfoForm.vue';
 import GenInfoForm from './genInfoForm.vue';
 import { RouteLocationNormalized } from 'vue-router';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const route = useRoute();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -156,7 +158,7 @@ const submitForm = () => {
         close();
       }
     } else {
-      proxy?.$modal.msgError('表单校验未通过,请重新检查提交内容');
+      proxy?.$modal.msgError(t('gen.message.validationFailed'));
     }
   });
 };

+ 69 - 48
src/views/tool/gen/genInfoForm.vue

@@ -1,12 +1,12 @@
 <template>
-  <el-form ref="genInfoForm" :model="infoForm" :rules="rules" label-width="150px">
+  <el-form ref="genInfoForm" :model="infoForm" :rules="rules" label-width="180px">
     <el-row>
       <el-col :span="12">
         <el-form-item prop="tplCategory">
-          <template #label>生成模板</template>
+          <template #label>{{ $t('gen.edit.genInfo.tplCategory') }}</template>
           <el-select v-model="infoForm.tplCategory" @change="tplSelectChange">
-            <el-option label="单表(增删改查)" value="crud" />
-            <el-option label="树表(增删改查)" value="tree" />
+            <el-option :label="$t('gen.edit.genInfo.tplCategoryCrud')" value="crud" />
+            <el-option :label="$t('gen.edit.genInfo.tplCategoryTree')" value="tree" />
           </el-select>
         </el-form-item>
       </el-col>
@@ -14,8 +14,8 @@
       <el-col :span="12">
         <el-form-item prop="packageName">
           <template #label>
-            生成包路径
-            <el-tooltip content="生成在哪个java包下,例如 com.ruoyi.system" placement="top">
+            {{ $t('gen.edit.genInfo.packageName') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.packageNameTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
@@ -26,8 +26,8 @@
       <el-col :span="12">
         <el-form-item prop="moduleName">
           <template #label>
-            生成模块名
-            <el-tooltip content="可理解为子系统名,例如 system" placement="top">
+            {{ $t('gen.edit.genInfo.moduleName') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.moduleNameTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
@@ -38,8 +38,8 @@
       <el-col :span="12">
         <el-form-item prop="businessName">
           <template #label>
-            生成业务名
-            <el-tooltip content="可理解为功能英文名,例如 user" placement="top">
+            {{ $t('gen.edit.genInfo.businessName') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.businessNameTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
@@ -50,8 +50,8 @@
       <el-col :span="12">
         <el-form-item prop="functionName">
           <template #label>
-            生成功能名
-            <el-tooltip content="用作类描述,例如 用户" placement="top">
+            {{ $t('gen.edit.genInfo.functionName') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.functionNameTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
@@ -62,8 +62,8 @@
       <el-col :span="12">
         <el-form-item>
           <template #label>
-            上级菜单
-            <el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
+            {{ $t('gen.edit.genInfo.parentMenu') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.parentMenuTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
@@ -73,7 +73,7 @@
             :props="{ value: 'menuId', label: 'menuName', children: 'children' } as any"
             value-key="menuId"
             node-key="menuId"
-            placeholder="选择上级菜单"
+            :placeholder="$t('gen.edit.genInfo.parentMenuPlaceholder')"
             check-strictly
             filterable
             clearable
@@ -85,21 +85,21 @@
       <el-col :span="12">
         <el-form-item prop="genType">
           <template #label>
-            生成代码方式
-            <el-tooltip content="默认为zip压缩包下载,也可以自定义生成路径" placement="top">
+            {{ $t('gen.edit.genInfo.genType') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.genTypeTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
-          <el-radio v-model="infoForm.genType" value="0">zip压缩包</el-radio>
-          <el-radio v-model="infoForm.genType" value="1">自定义路径</el-radio>
+          <el-radio v-model="infoForm.genType" value="0">{{ $t('gen.edit.genInfo.genTypeZip') }}</el-radio>
+          <el-radio v-model="infoForm.genType" value="1">{{ $t('gen.edit.genInfo.genTypeCustom') }}</el-radio>
         </el-form-item>
       </el-col>
 
       <el-col v-if="infoForm.genType == '1'" :span="24">
         <el-form-item prop="genPath">
           <template #label>
-            自定义路径
-            <el-tooltip content="填写磁盘绝对路径,若不填写,则生成到当前Web项目下" placement="top">
+            {{ $t('gen.edit.genInfo.genPath') }}
+            <el-tooltip :content="$t('gen.edit.genInfo.genPathTip')" placement="top">
               <el-icon><question-filled /></el-icon>
             </el-tooltip>
           </template>
@@ -107,12 +107,12 @@
             <template #append>
               <el-dropdown>
                 <el-button type="primary">
-                  最近路径快速选择
+                  {{ $t('gen.edit.genInfo.recentPath') }}
                   <i class="el-icon-arrow-down el-icon--right"></i>
                 </el-button>
                 <template #dropdown>
                   <el-dropdown-menu>
-                    <el-dropdown-item @click="infoForm.genPath = '/'">恢复默认的生成基础路径</el-dropdown-item>
+                    <el-dropdown-item @click="infoForm.genPath = '/'">{{ $t('gen.edit.genInfo.restorePath') }}</el-dropdown-item>
                   </el-dropdown-menu>
                 </template>
               </el-dropdown>
@@ -123,17 +123,17 @@
     </el-row>
 
     <template v-if="info.tplCategory == 'tree'">
-      <h4 class="form-header">其他信息</h4>
+      <h4 class="form-header">{{ $t('gen.edit.genInfo.otherInfo') }}</h4>
       <el-row v-show="info.tplCategory == 'tree'">
         <el-col :span="12">
           <el-form-item>
             <template #label>
-              树编码字段
-              <el-tooltip content="树显示的编码字段名, 如:dept_id" placement="top">
+              {{ $t('gen.edit.genInfo.treeCode') }}
+              <el-tooltip :content="$t('gen.edit.genInfo.treeCodeTip')" placement="top">
                 <el-icon><question-filled /></el-icon>
               </el-tooltip>
             </template>
-            <el-select v-model="infoForm.treeCode" placeholder="请选择">
+            <el-select v-model="infoForm.treeCode" :placeholder="$t('gen.edit.genInfo.treeCodePlaceholder')">
               <el-option
                 v-for="(column, index) in info.columns"
                 :key="index"
@@ -146,12 +146,12 @@
         <el-col :span="12">
           <el-form-item>
             <template #label>
-              树父编码字段
-              <el-tooltip content="树显示的父编码字段名, 如:parent_Id" placement="top">
+              {{ $t('gen.edit.genInfo.treeParentCode') }}
+              <el-tooltip :content="$t('gen.edit.genInfo.treeParentCodeTip')" placement="top">
                 <el-icon><question-filled /></el-icon>
               </el-tooltip>
             </template>
-            <el-select v-model="infoForm.treeParentCode" placeholder="请选择">
+            <el-select v-model="infoForm.treeParentCode" :placeholder="$t('gen.edit.genInfo.treeCodePlaceholder')">
               <el-option
                 v-for="(column, index) in infoForm.columns"
                 :key="index"
@@ -164,12 +164,12 @@
         <el-col :span="12">
           <el-form-item>
             <template #label>
-              树名称字段
-              <el-tooltip content="树节点的显示名称字段名, 如:dept_name" placement="top">
+              {{ $t('gen.edit.genInfo.treeName') }}
+              <el-tooltip :content="$t('gen.edit.genInfo.treeNameTip')" placement="top">
                 <el-icon><question-filled /></el-icon>
               </el-tooltip>
             </template>
-            <el-select v-model="infoForm.treeName" placeholder="请选择">
+            <el-select v-model="infoForm.treeName" :placeholder="$t('gen.edit.genInfo.treeCodePlaceholder')">
               <el-option
                 v-for="(column, index) in info.columns"
                 :key="index"
@@ -183,17 +183,17 @@
     </template>
 
     <template v-if="info.tplCategory == 'sub'">
-      <h4 class="form-header">关联信息</h4>
+      <h4 class="form-header">{{ $t('gen.edit.genInfo.subInfo') }}</h4>
       <el-row>
         <el-col :span="12">
           <el-form-item>
             <template #label>
-              关联子表的表名
-              <el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
+              {{ $t('gen.edit.genInfo.subTableName') }}
+              <el-tooltip :content="$t('gen.edit.genInfo.subTableNameTip')" placement="top">
                 <el-icon><question-filled /></el-icon>
               </el-tooltip>
             </template>
-            <el-select v-model="infoForm.subTableName" placeholder="请选择" @change="subSelectChange">
+            <el-select v-model="infoForm.subTableName" :placeholder="$t('gen.edit.genInfo.subTableNamePlaceholder')" @change="subSelectChange">
               <el-option v-for="(t, index) in table" :key="index" :label="t.tableName + ':' + t.tableComment" :value="t.tableName"></el-option>
             </el-select>
           </el-form-item>
@@ -201,12 +201,12 @@
         <el-col :span="12">
           <el-form-item>
             <template #label>
-              子表关联的外键名
-              <el-tooltip content="子表关联的外键名, 如:user_id" placement="top">
+              {{ $t('gen.edit.genInfo.subTableFkName') }}
+              <el-tooltip :content="$t('gen.edit.genInfo.subTableFkNameTip')" placement="top">
                 <el-icon><question-filled /></el-icon>
               </el-tooltip>
             </template>
-            <el-select v-model="infoForm.subTableFkName" placeholder="请选择">
+            <el-select v-model="infoForm.subTableFkName" :placeholder="$t('gen.edit.genInfo.subTableNamePlaceholder')">
               <el-option
                 v-for="(column, index) in subColumns"
                 :key="index"
@@ -224,6 +224,10 @@
 <script setup lang="ts">
 import { listMenu } from '@/api/system/menu';
 import { propTypes } from '@/utils/propTypes';
+import { useI18n } from 'vue-i18n';
+import { parseI18nName } from '@/utils/i18n';
+
+const { t } = useI18n();
 
 interface MenuOptionsType {
   menuId: number | string;
@@ -245,13 +249,13 @@ const infoForm = computed(() => props.info);
 const table = computed(() => props.tables);
 
 // 表单校验
-const rules = ref({
-  tplCategory: [{ required: true, message: '请选择生成模板', trigger: 'blur' }],
-  packageName: [{ required: true, message: '请输入生成包路径', trigger: 'blur' }],
-  moduleName: [{ required: true, message: '请输入生成模块名', trigger: 'blur' }],
-  businessName: [{ required: true, message: '请输入生成业务名', trigger: 'blur' }],
-  functionName: [{ required: true, message: '请输入生成功能名', trigger: 'blur' }]
-});
+const rules = computed(() => ({
+  tplCategory: [{ required: true, message: t('gen.edit.genInfo.tplCategoryRequired'), trigger: 'blur' }],
+  packageName: [{ required: true, message: t('gen.edit.genInfo.packageNameRequired'), trigger: 'blur' }],
+  moduleName: [{ required: true, message: t('gen.edit.genInfo.moduleNameRequired'), trigger: 'blur' }],
+  businessName: [{ required: true, message: t('gen.edit.genInfo.businessNameRequired'), trigger: 'blur' }],
+  functionName: [{ required: true, message: t('gen.edit.genInfo.functionNameRequired'), trigger: 'blur' }]
+}));
 const subSelectChange = () => {
   infoForm.value.subTableFkName = '';
 };
@@ -271,13 +275,30 @@ const setSubTableColumns = (value: string) => {
   });
 };
 
+/** 解析菜单树的国际化名称 */
+const parseMenuTree = (menus: MenuOptionsType[]): MenuOptionsType[] => {
+  return menus.map((menu) => {
+    const parsedMenu = { ...menu };
+    // 解析menuName字段
+    if (parsedMenu.menuName) {
+      parsedMenu.menuName = parseI18nName(parsedMenu.menuName);
+    }
+    // 递归处理子节点
+    if (parsedMenu.children && parsedMenu.children.length > 0) {
+      parsedMenu.children = parseMenuTree(parsedMenu.children);
+    }
+    return parsedMenu;
+  });
+};
+
 /** 查询菜单下拉树结构 */
 const getMenuTreeselect = async () => {
   const res = await listMenu();
   const data = proxy?.handleTree<MenuOptionsType>(res.data, 'menuId');
 
   if (data) {
-    menuOptions.value = data;
+    // 解析菜单名称的国际化
+    menuOptions.value = parseMenuTree(data);
   }
 };
 

+ 19 - 17
src/views/tool/gen/importTable.vue

@@ -1,37 +1,37 @@
 <template>
   <!-- 导入表 -->
-  <el-dialog v-model="visible" title="导入表" width="1100px" top="5vh" append-to-body>
-    <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-      <el-form-item label="数据源" prop="dataName">
-        <el-select v-model="queryParams.dataName" filterable placeholder="请选择/输入数据源名称">
+  <el-dialog v-model="visible" :title="$t('gen.dialog.importTitle')" width="1100px" top="5vh" append-to-body>
+    <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+      <el-form-item :label="$t('gen.search.dataSource')" prop="dataName">
+        <el-select v-model="queryParams.dataName" filterable :placeholder="$t('gen.search.dataSourcePlaceholder')">
           <el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
         </el-select>
       </el-form-item>
-      <el-form-item label="表名称" prop="tableName">
-        <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" />
+      <el-form-item :label="$t('gen.search.tableName')" prop="tableName">
+        <el-input v-model="queryParams.tableName" :placeholder="$t('gen.search.tableNamePlaceholder')" clearable @keyup.enter="handleQuery" />
       </el-form-item>
-      <el-form-item label="表描述" prop="tableComment">
-        <el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable @keyup.enter="handleQuery" />
+      <el-form-item :label="$t('gen.search.tableComment')" prop="tableComment">
+        <el-input v-model="queryParams.tableComment" :placeholder="$t('gen.search.tableCommentPlaceholder')" clearable @keyup.enter="handleQuery" />
       </el-form-item>
       <el-form-item>
-        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+        <el-button type="primary" icon="Search" @click="handleQuery">{{ $t('gen.search.search') }}</el-button>
+        <el-button icon="Refresh" @click="resetQuery">{{ $t('gen.search.reset') }}</el-button>
       </el-form-item>
     </el-form>
     <el-row>
       <el-table ref="tableRef" border :data="dbTableList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55"></el-table-column>
-        <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
-        <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
-        <el-table-column prop="createTime" label="创建时间"></el-table-column>
-        <el-table-column prop="updateTime" label="更新时间"></el-table-column>
+        <el-table-column prop="tableName" :label="$t('gen.table.tableName')" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="tableComment" :label="$t('gen.table.tableComment')" :show-overflow-tooltip="true"></el-table-column>
+        <el-table-column prop="createTime" :label="$t('gen.table.createTime')"></el-table-column>
+        <el-table-column prop="updateTime" :label="$t('gen.table.updateTime')"></el-table-column>
       </el-table>
       <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
     </el-row>
     <template #footer>
       <div class="dialog-footer">
-        <el-button type="primary" @click="handleImportTable">确 定</el-button>
-        <el-button @click="visible = false">取 消</el-button>
+        <el-button type="primary" @click="handleImportTable">{{ $t('gen.button.confirm') }}</el-button>
+        <el-button @click="visible = false">{{ $t('gen.button.cancel') }}</el-button>
       </div>
     </template>
   </el-dialog>
@@ -40,7 +40,9 @@
 <script setup lang="ts">
 import { listDbTable, importTable, getDataNames } from '@/api/tool/gen';
 import { DbTableQuery, DbTableVO } from '@/api/tool/gen/types';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const total = ref(0);
 const visible = ref(false);
 const tables = ref<Array<string>>([]);
@@ -105,7 +107,7 @@ const resetQuery = () => {
 const handleImportTable = async () => {
   const tableNames = tables.value.join(',');
   if (tableNames == '') {
-    proxy?.$modal.msgError('请选择要导入的表');
+    proxy?.$modal.msgError(t('gen.message.selectTable'));
     return;
   }
   const res = await importTable({ tables: tableNames, dataName: queryParams.dataName });

+ 41 - 39
src/views/tool/gen/index.vue

@@ -3,32 +3,32 @@
     <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
       <div v-show="showSearch" class="mb-[10px]">
         <el-card shadow="hover">
-          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
-            <el-form-item label="数据源" prop="dataName">
-              <el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称">
-                <el-option key="" label="全部" value="" />
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+            <el-form-item :label="$t('gen.search.dataSource')" prop="dataName">
+              <el-select v-model="queryParams.dataName" filterable clearable :placeholder="$t('gen.search.dataSourcePlaceholder')">
+                <el-option key="" :label="$t('gen.search.all')" value="" />
                 <el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
               </el-select>
             </el-form-item>
-            <el-form-item label="表名称" prop="tableName">
-              <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" />
+            <el-form-item :label="$t('gen.search.tableName')" prop="tableName">
+              <el-input v-model="queryParams.tableName" :placeholder="$t('gen.search.tableNamePlaceholder')" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="表描述" prop="tableComment">
-              <el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable @keyup.enter="handleQuery" />
+            <el-form-item :label="$t('gen.search.tableComment')" prop="tableComment">
+              <el-input v-model="queryParams.tableComment" :placeholder="$t('gen.search.tableCommentPlaceholder')" clearable @keyup.enter="handleQuery" />
             </el-form-item>
-            <el-form-item label="创建时间" style="width: 308px">
+            <el-form-item :label="$t('gen.search.createTime')" style="width: 308px">
               <el-date-picker
                 v-model="dateRange"
                 value-format="YYYY-MM-DD"
                 type="daterange"
                 range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
+                :start-placeholder="$t('gen.search.startDate')"
+                :end-placeholder="$t('gen.search.endDate')"
               ></el-date-picker>
             </el-form-item>
             <el-form-item>
-              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
-              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+              <el-button type="primary" icon="Search" @click="handleQuery">{{ $t('gen.search.search') }}</el-button>
+              <el-button icon="Refresh" @click="resetQuery">{{ $t('gen.search.reset') }}</el-button>
             </el-form-item>
           </el-form>
         </el-card>
@@ -39,17 +39,17 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['tool:gen:code']" type="primary" plain icon="Download" @click="handleGenTable()">生成</el-button>
+            <el-button v-hasPermi="['tool:gen:code']" type="primary" plain icon="Download" @click="handleGenTable()">{{ $t('gen.button.generate') }}</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['tool:gen:import']" type="info" plain icon="Upload" @click="openImportTable">导入</el-button>
+            <el-button v-hasPermi="['tool:gen:import']" type="info" plain icon="Upload" @click="openImportTable">{{ $t('gen.button.import') }}</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button v-hasPermi="['tool:gen:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleEditTable()">修改</el-button>
+            <el-button v-hasPermi="['tool:gen:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleEditTable()">{{ $t('gen.button.edit') }}</el-button>
           </el-col>
           <el-col :span="1.5">
             <el-button v-hasPermi="['tool:gen:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
-              删除
+              {{ $t('gen.button.delete') }}
             </el-button>
           </el-col>
           <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
@@ -58,32 +58,32 @@
 
       <el-table v-loading="loading" border :data="tableList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" align="center" width="55"></el-table-column>
-        <el-table-column label="序号" type="index" width="50" align="center">
+        <el-table-column :label="$t('gen.table.index')" type="index" width="50" align="center">
           <template #default="scope">
             <span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="数据源" align="center" prop="dataName" :show-overflow-tooltip="true" />
-        <el-table-column label="表名称" align="center" prop="tableName" :show-overflow-tooltip="true" />
-        <el-table-column label="表描述" align="center" prop="tableComment" :show-overflow-tooltip="true" />
-        <el-table-column label="实体" align="center" prop="className" :show-overflow-tooltip="true" />
-        <el-table-column label="创建时间" align="center" prop="createTime" width="160" />
-        <el-table-column label="更新时间" align="center" prop="updateTime" width="160" />
-        <el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
+        <el-table-column :label="$t('gen.table.dataSource')" align="center" prop="dataName" :show-overflow-tooltip="true" />
+        <el-table-column :label="$t('gen.table.tableName')" align="center" prop="tableName" :show-overflow-tooltip="true" />
+        <el-table-column :label="$t('gen.table.tableComment')" align="center" prop="tableComment" :show-overflow-tooltip="true" />
+        <el-table-column :label="$t('gen.table.entity')" align="center" prop="className" :show-overflow-tooltip="true" />
+        <el-table-column :label="$t('gen.table.createTime')" align="center" prop="createTime" width="160" />
+        <el-table-column :label="$t('gen.table.updateTime')" align="center" prop="updateTime" width="160" />
+        <el-table-column :label="$t('gen.table.operation')" align="center" width="330" class-name="small-padding fixed-width">
           <template #default="scope">
-            <el-tooltip content="预览" placement="top">
+            <el-tooltip :content="$t('gen.tooltip.preview')" placement="top">
               <el-button v-hasPermi="['tool:gen:preview']" link type="primary" icon="View" @click="handlePreview(scope.row)"></el-button>
             </el-tooltip>
-            <el-tooltip content="编辑" placement="top">
+            <el-tooltip :content="$t('gen.tooltip.edit')" placement="top">
               <el-button v-hasPermi="['tool:gen:edit']" link type="primary" icon="Edit" @click="handleEditTable(scope.row)"></el-button>
             </el-tooltip>
-            <el-tooltip content="删除" placement="top">
+            <el-tooltip :content="$t('gen.tooltip.delete')" placement="top">
               <el-button v-hasPermi="['tool:gen:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
             </el-tooltip>
-            <el-tooltip content="同步" placement="top">
+            <el-tooltip :content="$t('gen.tooltip.sync')" placement="top">
               <el-button v-hasPermi="['tool:gen:edit']" link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)"></el-button>
             </el-tooltip>
-            <el-tooltip content="生成代码" placement="top">
+            <el-tooltip :content="$t('gen.tooltip.generateCode')" placement="top">
               <el-button v-hasPermi="['tool:gen:code']" link type="primary" icon="Download" @click="handleGenTable(scope.row)"></el-button>
             </el-tooltip>
           </template>
@@ -102,7 +102,7 @@
           :name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
         >
           <el-link v-copyText="value" v-copyText:callback="copyTextSuccess" :underline="false" icon="DocumentCopy" style="float: right">
-            &nbsp;复制
+            &nbsp;{{ $t('gen.dialog.copy') }}
           </el-link>
           <highlightjs :code="value" />
         </el-tab-pane>
@@ -117,7 +117,9 @@ import { delTable, genCode, getDataNames, listTable, previewTable, synchDb } fro
 import { TableQuery, TableVO } from '@/api/tool/gen/types';
 import router from '@/router';
 import ImportTable from './importTable.vue';
+import { useI18n } from 'vue-i18n';
 
+const { t } = useI18n();
 const route = useRoute();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
@@ -152,7 +154,7 @@ const preview = ref<{
 });
 const dialog = reactive<DialogOption>({
   visible: false,
-  title: '代码预览'
+  title: t('gen.dialog.previewTitle')
 });
 
 /** 查询多数据源名称 */
@@ -178,12 +180,12 @@ const handleQuery = () => {
 const handleGenTable = async (row?: TableVO) => {
   const tbIds = row?.tableId || ids.value;
   if (tbIds == '') {
-    proxy?.$modal.msgError('请选择要生成的数据');
+    proxy?.$modal.msgError(t('gen.message.selectData'));
     return;
   }
   if (row?.genType === '1') {
     await genCode(row.tableId);
-    proxy?.$modal.msgSuccess('成功生成到自定义路径:' + row.genPath);
+    proxy?.$modal.msgSuccess(t('gen.message.generateSuccess') + row.genPath);
   } else {
     proxy?.$download.zip('/tool/gen/batchGenCode?tableIdStr=' + tbIds, 'ruoyi.zip');
   }
@@ -191,9 +193,9 @@ const handleGenTable = async (row?: TableVO) => {
 /** 同步数据库操作 */
 const handleSynchDb = async (row: TableVO) => {
   const tableId = row.tableId;
-  await proxy?.$modal.confirm('确认要强制同步"' + row.tableName + '"表结构吗?');
+  await proxy?.$modal.confirm(t('gen.message.syncConfirm', { tableName: row.tableName }));
   await synchDb(tableId);
-  proxy?.$modal.msgSuccess('同步成功');
+  proxy?.$modal.msgSuccess(t('gen.message.syncSuccess'));
 };
 /** 打开导入表弹窗 */
 const openImportTable = () => {
@@ -214,7 +216,7 @@ const handlePreview = async (row: TableVO) => {
 };
 /** 复制代码成功 */
 const copyTextSuccess = () => {
-  proxy?.$modal.msgSuccess('复制成功');
+  proxy?.$modal.msgSuccess(t('gen.message.copySuccess'));
 };
 // 多选框选中数据
 const handleSelectionChange = (selection: TableVO[]) => {
@@ -230,10 +232,10 @@ const handleEditTable = (row?: TableVO) => {
 /** 删除按钮操作 */
 const handleDelete = async (row?: TableVO) => {
   const tableIds = row?.tableId || ids.value;
-  await proxy?.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?');
+  await proxy?.$modal.confirm(t('gen.message.deleteConfirm', { tableIds: tableIds }));
   await delTable(tableIds);
   await getList();
-  proxy?.$modal.msgSuccess('删除成功');
+  proxy?.$modal.msgSuccess(t('gen.message.deleteSuccess'));
 };
 
 onMounted(() => {