Browse Source

初步i18n

mxd 3 years ago
parent
commit
afef445010
67 changed files with 1153 additions and 497 deletions
  1. 3 4
      package.json
  2. 4 0
      src/assets/iconfont/iconfont.css
  3. BIN
      src/assets/iconfont/iconfont.ttf
  4. 5 1
      src/assets/index.css
  5. 1 1
      src/components/common/data/magic-table.css
  6. 5 4
      src/components/common/data/magic-table.js
  7. 3 2
      src/components/common/dialog/magic-alert.vue
  8. 4 3
      src/components/common/dialog/magic-confirm.vue
  9. 1 1
      src/components/common/dialog/magic-dialog.vue
  10. 2 1
      src/components/common/form/magic-file.vue
  11. 1 2
      src/components/common/form/magic-select.vue
  12. 2 1
      src/components/common/icon/magic-avatar.vue
  13. 2 1
      src/components/common/magic-empty.vue
  14. 2 1
      src/components/common/magic-loading.vue
  15. 2 1
      src/components/common/magic-monaco-editor.vue
  16. 20 19
      src/components/magic-editor.vue
  17. 2 1
      src/components/panel/api/magic-api-body.vue
  18. 10 9
      src/components/panel/api/magic-api-group.vue
  19. 9 8
      src/components/panel/api/magic-api-header.vue
  20. 12 11
      src/components/panel/api/magic-api-info.vue
  21. 4 3
      src/components/panel/api/magic-api-option.vue
  22. 9 8
      src/components/panel/api/magic-api-parameter.vue
  23. 8 7
      src/components/panel/api/magic-api-path.vue
  24. 16 10
      src/components/panel/api/magic-api-request-structure.vue
  25. 3 2
      src/components/panel/api/magic-api-response-body.vue
  26. 12 6
      src/components/panel/api/magic-api-response-structure.vue
  27. 4 4
      src/components/panel/api/magic-api-response.vue
  28. 3 2
      src/components/panel/common/magic-panel-common-toolbar.vue
  29. 15 14
      src/components/panel/datasource/magic-datasource-datasource.vue
  30. 24 22
      src/components/panel/footer/magic-backup.vue
  31. 6 5
      src/components/panel/footer/magic-debug.vue
  32. 4 3
      src/components/panel/footer/magic-event.vue
  33. 6 5
      src/components/panel/footer/magic-log.vue
  34. 8 8
      src/components/panel/footer/magic-online.vue
  35. 6 5
      src/components/panel/footer/magic-status-bar.vue
  36. 9 8
      src/components/panel/footer/magic-todo.vue
  37. 12 11
      src/components/panel/footer/magic-toolbar.vue
  38. 8 10
      src/components/panel/function/magic-function-parameter.vue
  39. 9 8
      src/components/panel/header/magic-export.vue
  40. 48 15
      src/components/panel/header/magic-header.vue
  41. 12 11
      src/components/panel/header/magic-push.vue
  42. 5 4
      src/components/panel/header/magic-search.vue
  43. 10 9
      src/components/panel/header/magic-upload.vue
  44. 0 8
      src/components/panel/magic-cron-info.vue
  45. 0 59
      src/components/panel/magic-function-info.vue
  46. 0 8
      src/components/panel/magic-websocket-info.vue
  47. 37 30
      src/components/panel/main/magic-data-resource.vue
  48. 5 4
      src/components/panel/main/magic-login.vue
  49. 11 14
      src/components/panel/main/magic-main.vue
  50. 2 1
      src/components/panel/main/magic-recent-opened.vue
  51. 76 64
      src/components/panel/main/magic-resource.vue
  52. 19 18
      src/components/panel/main/magic-script-editor.vue
  53. 8 7
      src/components/panel/task/magic-task-info.vue
  54. 1 2
      src/magic-editor.js
  55. 4 2
      src/scripts/bus.js
  56. 7 6
      src/scripts/constants.js
  57. 0 1
      src/scripts/constants/socket.js
  58. 14 0
      src/scripts/i18n.js
  59. 312 0
      src/scripts/i18n/en.js
  60. 314 0
      src/scripts/i18n/zh-cn.js
  61. 2 1
      src/scripts/service/magic-api.js
  62. 3 2
      src/scripts/service/magic-datasource.js
  63. 2 1
      src/scripts/service/magic-function.js
  64. 0 6
      src/scripts/service/magic-resource.js
  65. 2 1
      src/scripts/service/magic-task.js
  66. 0 8
      src/scripts/service/magic-websocket.js
  67. 3 3
      src/scripts/websocket.js

+ 3 - 4
package.json

@@ -9,11 +9,10 @@
     "axios": "^0.24.0",
     "monaco-editor": "0.29.1",
     "qs": "^6.10.1",
-    "vue": "^3.2.21"
+    "vue": "^3.2.26"
   },
   "devDependencies": {
-    "@vitejs/plugin-vue": "^1.9.4",
-    "vite": "^2.6.13",
-    "@vue/compiler-sfc": "^3.2.21"
+    "@vitejs/plugin-vue": "^2.0.1",
+    "vite": "^2.7.10"
   }
 }

+ 4 - 0
src/assets/iconfont/iconfont.css

@@ -11,6 +11,10 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.magic-icon-i18n:before {
+  content: "\e616";
+}
+
 .magic-icon-difference:before {
   content: "\e603";
 }

BIN
src/assets/iconfont/iconfont.ttf


+ 5 - 1
src/assets/index.css

@@ -158,7 +158,11 @@
   --scollbar-scrollbar-corner-background: rgba(194, 194, 194, 0.1);
   /* 滚动条颜色 */
 }
-.magic-editor,.magic-editor .monaco-editor, .magic-log pre{
+.magic-editor,
+.magic-editor .monaco-editor, 
+.magic-log pre, 
+.magic-input,
+.magic-dialog pre{
   font-family: 'JetBrainsMono', 'Consolas', 'Courier New', '微软雅黑';
 }
 .magic-editor * {

+ 1 - 1
src/components/common/data/magic-table.css

@@ -27,6 +27,6 @@
 .magic-table__border .magic-table-body .magic-table-row{
     border-bottom: 1px solid var(--table-border-color);
 }
-.magic-table__border .magic-table-body .magic-table-row .magic-table-column:not(:last-child){
+.magic-table__border .magic-table-column:not(:last-child){
     border-right: 1px solid var(--table-border-color);
 }

+ 5 - 4
src/components/common/data/magic-table.js

@@ -11,9 +11,10 @@ export default {
         border: {
             type: Boolean,
             default: false
-        }
+        },
+        align: String
     },
-    emits: ['clickRow', 'contextmenu'],
+    emits: ['clickRow', 'contextmenu', 'loadNext'],
     render() {
         const slots = this.$slots.default();
         const _getTableColumnStyle = (prop) => {
@@ -29,8 +30,8 @@ export default {
             } else if (prop.flex) {
                 styles.push(`flex: ${prop.flex}`)
             }
-            if (prop.align) {
-                styles.push(`text-align: ${prop.align}`)
+            if (prop.align || this.align) {
+                styles.push(`text-align: ${this.align || prop.align}`)
             }
             
             return styles.join(';')

+ 3 - 2
src/components/common/dialog/magic-alert.vue

@@ -8,14 +8,15 @@
 </template>
 <script setup>
 import { ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 defineProps({
     title: {
         type: String,
-        default: '提示'
+        default: $i('message.tips')
     },
     ok: {
         type: String,
-        default: '确定'
+        default: $i('message.ok')
     },
     message: {
         type: String,

+ 4 - 3
src/components/common/dialog/magic-confirm.vue

@@ -9,18 +9,19 @@
 </template>
 <script setup>
 import { ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 defineProps({
     title: {
         type: String,
-        default: '提示'
+        default: $i('message.tips')
     },
     ok: {
         type: String,
-        default: '确定'
+        default: $i('message.ok')
     },
     cancel: {
         type: String,
-        default: '取消'
+        default: $i('message.cancel')
     },
     message: {
         type: String,

+ 1 - 1
src/components/common/dialog/magic-dialog.vue

@@ -2,7 +2,7 @@
     <teleport to=".magic-editor" v-if="value">
         <div class="magic-dialog" tabindex="1" :class="(shade ? 'magic-dialog__shade' : '') + (className ? ' ' + className : '')" @mousemove="onMousemove" @mouseup="onMouseup" @keydown="onKeydown">
             <div class="magic-dialog-main" :style="{ position, top, left, width, height,'max-width': maxWidth }" ref="dialog">
-                <div ref="title" class="magic-dialog-header none-select" :class="{ moveable }" @mousedown="onMousedown">
+                <div class="magic-dialog-header none-select" :class="{ moveable }" @mousedown="onMousedown">
                     {{ title }}
                     <span v-if="showClose" @mousedown.stop="close">
                         <magic-icon icon="close"/>

+ 2 - 1
src/components/common/form/magic-file.vue

@@ -7,11 +7,12 @@
 </template>
 <script setup>
 import { ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const props = defineProps({
 	value: [Object, String],
 	placeholder: {
 		type: String,
-		default: '请选择文件'
+		default: $i('message.chooseFile')
 	},
 	accept: String,
 	multiple: {

+ 1 - 2
src/components/common/form/magic-select.vue

@@ -10,8 +10,7 @@
     </div>
 </template>
 <script setup>
-import { computed, getCurrentInstance, inject, nextTick, ref } from 'vue'
-import constants from '../../../scripts/constants'
+import { computed, inject, nextTick, ref } from 'vue'
 const props = defineProps({
 	value: [ Object, String, Number ],
 	options: Array,

+ 2 - 1
src/components/common/icon/magic-avatar.vue

@@ -4,6 +4,7 @@
 <script setup>
 import { computed } from 'vue'
 import { hash } from '../../../scripts/utils.js'
+import $i from '../../../scripts/i18n.js'
 const props = defineProps({ user: Object, text: String, size: {
     type: Number,
     default: 22
@@ -19,7 +20,7 @@ const style = computed(() => {
     }
 })
 const displayText = computed(() => props.text || props.user.username.substring(0, 1))
-const displayTitle = computed(() => props.user ? `用户名:${props.user.username}\nIP:${props.user.ip || 'unknown'}` : undefined)
+const displayTitle = computed(() => props.user ? `${$i('message.username')}:${props.user.username}\nIP:${props.user.ip || 'unknown'}` : undefined)
 </script>
 
 <style scoped>

+ 2 - 1
src/components/common/magic-empty.vue

@@ -6,10 +6,11 @@
     </div>
 </template>
 <script setup>
+import $i from '../../scripts/i18n.js'
 defineProps({
     text: {
         type: String,
-        default: '无数据'
+        default: $i('message.nodata')
     }
 })
 </script>

+ 2 - 1
src/components/common/magic-loading.vue

@@ -10,11 +10,12 @@
     <slot v-else />
 </template>
 <script setup>
+import $i from '../../scripts/i18n.js'
 const props = defineProps({
     loading: Boolean,
     loadingText: {
         type: String,
-        default: '加载中...'
+        default: $i('message.loading')
     },
     style: Object
 })

+ 2 - 1
src/components/common/magic-monaco-editor.vue

@@ -4,6 +4,7 @@
 <script>
 import * as monaco from 'monaco-editor'
 import constants from '../../scripts/constants.js'
+import $i from '../../scripts/i18n.js'
 import { CommandsRegistry } from 'monaco-editor/esm/vs/platform/commands/common/commands'
 import { KeybindingsRegistry } from 'monaco-editor/esm/vs/platform/keybinding/common/keybindingsRegistry.js'
 import { ContextKeyExpr } from 'monaco-editor/esm/vs/platform/contextkey/common/contextkey.js'
@@ -50,7 +51,7 @@ export default {
 		})
 		this.instance.addAction({
 			id: 'editor.action.triggerSuggest.extension',
-			label: '触发代码提示',
+			label: $i('editor.triggerSuggest'),
 			precondition: '!suggestWidgetVisible && !markersNavigationVisible && !parameterHintsVisible && !findWidgetVisible',
 			run: () => {
 				this.instance.trigger(null, 'editor.action.triggerSuggest', {})

+ 20 - 19
src/components/magic-editor.vue

@@ -22,7 +22,7 @@ import { defineTheme } from '../scripts/theme.js'
 import bus from '../scripts/bus.js'
 import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"
 import JsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"
-import { nextTick, onMounted, onUnmounted, provide, reactive, ref, watch } from 'vue'
+import { nextTick, onMounted, onUnmounted, provide, reactive, ref } from 'vue'
 import { initializeMagicScript } from '../scripts/editor/magic-script.js'
 import JavaClass from '../scripts/editor/java-class.js'
 import MagicWebSocket from '../scripts/websocket.js'
@@ -32,6 +32,8 @@ import { randomString, replaceURL } from '../scripts/utils.js'
 import store from '../scripts/store.js'
 import * as monaco from 'monaco-editor'
 import modal from './common/dialog/magic-modal.js'
+import $i from '../scripts/i18n.js'
+console.log($i('message.save'))
 initializeMagicScript()
 self.MonacoEnvironment = {
 	getWorker: function(moduleId, label) {
@@ -67,9 +69,9 @@ if (constants.BASE_URL.startsWith('http')) { // http开头
 	link = link + '/' + constants.BASE_URL
 }
 request.setBaseURL(constants.BASE_URL)
-bus.status('加载classes信息...')
-Promise.all([JavaClass.initClasses(), JavaClass.initImportClass()]).then(()=> bus.status('classes信息加载完毕')).catch((e)=>{
-	bus.status('classes信息加载失败')
+bus.status('message.loadClass')
+Promise.all([JavaClass.initClasses(), JavaClass.initImportClass()]).then(()=> bus.status('message.loadClassFinish')).catch((e)=>{
+	bus.status('message.loadClassError')
 }).finally(() => hideLoadingElement)
 const options = props.config.options || []
 provide('options', options)
@@ -90,19 +92,19 @@ const checkUpdate = () => {
 			if(response.status === 200){
 				response.json().then(json => {
 					if (constants.config.version !== json.value.replace('v', '')) {
-						bus.status(`版本检测完毕,最新版本为:${json.value},建议更新!!`)
+						bus.status('message.newVersionRelease', true, json.value)
 						if (json.value !== store.get(constants.IGNORE_VERSION)) {
 							bus.$emit(Message.NOTIFY, {
-								title: '更新提示',
+								title: $i('message.tips'),
 								icon: 'warning',
-								content: `检测到已有新版本${json.value},是否更新?`,
+								content: $i('message.versionUpdate', true, json.value),
 								buttons: [{
-									title: '更新日志',
+									title: $i('message.changelog'),
 									onClick: () => {
 										window.open('http://www.ssssssss.org/magic-api/changelog.html')
 									}
 								}, {
-									title: '不再提醒',
+									title: $i('message.ignore'),
 									onClick: () => {
 										store.set(constants.IGNORE_VERSION, json.value)
 									}
@@ -111,7 +113,7 @@ const checkUpdate = () => {
 							
 						} 
 					} else {
-						bus.status(`版本检测完毕,当前已是最新版`)
+						bus.status('message.versionLastest')
 					}
 				})
 			}
@@ -119,10 +121,10 @@ const checkUpdate = () => {
 }
 const autoLogin = () => {
 	constants.HEADER_MAGIC_TOKEN_VALUE = store.get(constants.STORE.token) || constants.HEADER_MAGIC_TOKEN_VALUE
-	bus.status('尝试自动登录')
+	bus.status('message.tryAutoLogin')
 	request.sendPost('/login').success(isLogin => {
 		if(isLogin){
-			bus.status('自动登录成功')
+			bus.status('message.autoLoginSuccess')
 			bus.$emit(Message.LOGINED)
 		} else {
 			showLogin.value = true
@@ -143,12 +145,11 @@ const init = () => {
 			constants.SERVER_URL = replaceURL(host + '/' + (res.data.prefix || ''))
 		}
 		if(constants.config.version && constants.config.version !== constants.MAGIC_API_VERSION_TEXT){
-			const msg = `检测到前后端版本不一致(前端:${constants.MAGIC_API_VERSION_TEXT} 后端:${constants.config.version}),请检查`
-			bus.status(msg, false)
+			bus.status('message.versionConflict', false, constants.MAGIC_API_VERSION_TEXT, constants.config.version)
 			bus.$emit(Message.NOTIFY, {
 				icon: 'error',
-				title: '版本检测',
-				content: msg
+				title: $i('message.versionCheck'),
+				content: $i('message.versionConflict', constants.MAGIC_API_VERSION_TEXT, constants.config.version)
 			})
 		}
 		hideLoadingElement()
@@ -159,8 +160,8 @@ const init = () => {
 	}).catch(e => {
 		console.error(e)
 		hideLoadingElement()
-		modal.alert('加载配置失败', '加载配置失败')
-		bus.status('加载配置失败', false)
+		modal.alert($i('message.tips'), $i('message.loadConfigError'))
+		bus.status('message.loadConfigError', false)
 	})
 }
 // 快捷键处理
@@ -203,7 +204,7 @@ bus.$on(Message.MESSAGE, (msgType, content) => {
 })
 bus.$event(Socket.OPEN, () => {
 	constants.CLIENT_ID = randomString(16)
-	bus.send(Socket.LOGIN, [constants.HEADER_MAGIC_TOKEN_VALUE, constants.CLIENT_ID].join(','))
+	nextTick(() => bus.send(Socket.LOGIN, [constants.HEADER_MAGIC_TOKEN_VALUE, constants.CLIENT_ID].join(',')))
 })
 bus.$event(Socket.LOGIN_RESPONSE, ([ret, user]) => {
 	if(ret === '1'){

+ 2 - 1
src/components/panel/api/magic-api-body.vue

@@ -7,12 +7,13 @@
 </template>
 <script setup>
 import { resolveDynamicComponent } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const props = defineProps({
     opened: Object
 })
 const navbars = [
     { title: 'Json', icon: 'json', component: resolveDynamicComponent('magic-api-request-body') },
-    { title: '视图', icon: 'structure', component: resolveDynamicComponent('magic-api-request-structure') }
+    { title: $i('message.view'), icon: 'structure', component: resolveDynamicComponent('magic-api-request-structure') }
 ]
 </script>
 <style scoped>

+ 10 - 9
src/components/panel/api/magic-api-group.vue

@@ -1,11 +1,11 @@
 <template>
 	<div class="magic-api-group">
 		<form>
-			<label>分组名称</label>
-			<magic-input v-model:value="info.name" placeholder="请输入接口名称" width="200px"/>
-			<label>分组路径</label>
-			<magic-input v-model:value="info.path" placeholder="请输入接口路径" width="auto" style="flex:1"/>
-            <magic-button value="保存" @onClick="onClick"/>
+			<label>{{ $i('resource.form.groupName') }}</label>
+			<magic-input v-model:value="info.name" :placeholder="$i('resource.form.placeholder.name', $i('api.name'))" width="250px"/>
+			<label>{{ $i('resource.form.groupPath') }}</label>
+			<magic-input v-model:value="info.path" :placeholder="$i('resource.form.placeholder.path', $i('api.name'))" width="auto" style="flex:1"/>
+            <magic-button :value="$i('message.save')" @onClick="onClick"/>
 		</form>
 	</div>
 	<magic-navbar direction="horizontal" ref="navbar" style="flex:1" :allow-close="false">
@@ -16,13 +16,14 @@
 </template>
 <script setup>
 import { resolveDynamicComponent, inject } from 'vue'
-import request from '../../../scripts/request'
+import request from '../../../scripts/request.js'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 const navbars = [{
-	title: '路径变量',
+	title: $i('api.navbars.path'),
 	component: resolveDynamicComponent('magic-api-path')
 },{
-	title: '分组选项',
+	title: $i('api.navbars.groupOption'),
 	component: resolveDynamicComponent('magic-api-option')
 }]
 const onClick = () => {
@@ -49,7 +50,7 @@ const onClick = () => {
 }
 .magic-api-group form label{
 	display: inline-block;
-	width: 75px;
+	width: 85px;
 	height: 22px;
 	line-height: 22px;
 	font-weight: 400;

+ 9 - 8
src/components/panel/api/magic-api-header.vue

@@ -1,8 +1,8 @@
 <template>
     <div class="magic-panel-api">
        <magic-panel-common-toolbar v-model:index="currentIndex" :value="info.headers"/>
-        <magic-table :data="info.headers" border @clickRow="index => currentIndex = index">
-            <magic-table-column title="必填" width="35" #default="{row}">
+        <magic-table :data="info.headers" border @clickRow="index => currentIndex = index" align="center">
+            <magic-table-column :title="$i('message.required')" width="65" #default="{row}">
                 <magic-checkbox v-model:value="row.required"/>
             </magic-table-column>
             <magic-table-column title="Key" #default="{ row }">
@@ -11,22 +11,22 @@
             <magic-table-column title="Value" #default="{ row }">
                 <magic-input v-model:value="row.value" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="参数类型" #default="{ row }" width="135">
+            <magic-table-column :title="$i('message.parameterType')" #default="{ row }" width="135">
                 <magic-select :options="$REQUEST_SIMPLE_TYPES" v-model:value="row.dataType" :default-select="$DEFAULT_REQUEST_SIMPLE_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="默认值" #default="{ row }">
+            <magic-table-column :title="$i('message.defaultValue')"  #default="{ row }">
                 <magic-input v-model:value="row.defaultValue" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="验证方式" #default="{ row }" width="100">
+            <magic-table-column :title="$i('api.validateType')" #default="{ row }" width="115">
                 <magic-select :options="$VALIDATE_TYPES" v-model:value="row.validateType" :default-select="$DEFAULT_VALIDATE_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="表达式或正则表达式" #default="{ row }">
+            <magic-table-column :title="$i('api.expression')" #default="{ row }" width="220">
                 <magic-input v-model:value="row.expression" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="验证说明" #default="{ row }">
+            <magic-table-column :title="$i('api.validate')" #default="{ row }" width="165">
                 <magic-input v-model:value="row.error" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="描述" #default="{ row }" flex="2">
+            <magic-table-column :title="$i('message.description')" #default="{ row }" flex="2">
                 <magic-input v-model:value="row.description" :border="false"/>
             </magic-table-column>
         </magic-table>
@@ -34,6 +34,7 @@
 </template>
 <script setup>
 import { inject, ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 const currentIndex = ref(-1)
 </script>

+ 12 - 11
src/components/panel/api/magic-api-info.vue

@@ -1,12 +1,12 @@
 <template>
 	<div class="magic-api-info">
 		<form>
-			<label>请求方法</label>
+			<label>{{$i('api.form.method')}}</label>
 			<magic-select width="100px" :options="$REQUEST_METHODS" :default-select="$DEFAULT_REQUEST_METHOD" v-model:value="info.method"/>
-			<label>接口名称</label>
-			<magic-input v-model:value="info.name" placeholder="请输入接口名称" width="200px"/>
-			<label>接口路径</label>
-			<magic-input v-model:value="info.path" placeholder="请输入接口路径" width="auto" style="flex:1"/>
+			<label>{{$i('api.form.name')}}</label>
+			<magic-input v-model:value="info.name" :placeholder="$i('api.form.placeholder.name')" width="200px"/>
+			<label>{{$i('api.form.path')}}</label>
+			<magic-input v-model:value="info.path" :placeholder="$i('api.form.placeholder.path')" width="auto" style="flex:1"/>
 		</form>
 	</div>
 	<magic-navbar direction="horizontal" ref="navbar" style="flex:1" :allow-close="false">
@@ -17,24 +17,25 @@
 </template>
 <script setup>
 import { resolveDynamicComponent, inject } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 const navbars = [{
-	title: '请求参数',
+	title: $i('api.navbars.parameter'),
 	component: resolveDynamicComponent('magic-api-parameter')
 },{
-	title: '请求Header',
+	title: $i('api.navbars.header'),
 	component: resolveDynamicComponent('magic-api-header')
 },{
-	title: '路径变量',
+	title: $i('api.navbars.path'),
 	component: resolveDynamicComponent('magic-api-path')
 },{
-	title: '请求Body',
+	title: $i('api.navbars.body'),
 	component: resolveDynamicComponent('magic-api-body')
 },{
-	title: '接口选项',
+	title: $i('api.navbars.option'),
 	component: resolveDynamicComponent('magic-api-option')
 },{
-	title: '接口描述',
+	title: $i('api.navbars.description'),
 	component: resolveDynamicComponent('magic-api-description')
 }]
 </script>

+ 4 - 3
src/components/panel/api/magic-api-option.vue

@@ -2,13 +2,13 @@
     <div class="magic-panel-api">
        <magic-panel-common-toolbar v-model:index="currentIndex" :value="info.options"/>
        <magic-table :data="info.options" border @clickRow="index => currentIndex = index">
-            <magic-table-column title="" #default="{ row }" width="20%">
+            <magic-table-column title="Key" #default="{ row }" width="20%">
                 <magic-select :options="options" v-model:value="row.name" :default-select="$DEFAULT_REQUEST_SIMPLE_TYPE" :border="false" inputable @select="v => onSelect(v, row)"/>
             </magic-table-column>
-            <magic-table-column title="" #default="{ row }" width="60%">
+            <magic-table-column title="Value" #default="{ row }" width="60%">
                 <magic-input v-model:value="row.value" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="描述" #default="{ row }" width="20%">
+            <magic-table-column :title="$i('message.description')" #default="{ row }" width="20%">
                 <magic-input v-model:value="row.description" :border="false"/>
             </magic-table-column>
         </magic-table>
@@ -16,6 +16,7 @@
 </template>
 <script setup>
 import { computed, inject, ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 const defaultOptions = inject('options') || []
 const options = computed(() => (defaultOptions).map(e => { return { text: e[0], value: e[0], description: e[1], defaultValue: e[2]} }))

+ 9 - 8
src/components/panel/api/magic-api-parameter.vue

@@ -1,8 +1,8 @@
 <template>
     <div class="magic-panel-api">
         <magic-panel-common-toolbar v-model:index="currentIndex" :value="info.parameters"/>
-        <magic-table :data="info.parameters" border @clickRow="index => currentIndex = index">
-            <magic-table-column title="必填" width="35" #default="{row}">
+        <magic-table :data="info.parameters" border @clickRow="index => currentIndex = index" align="center">
+            <magic-table-column :title="$i('message.required')" width="65" #default="{row}">
                 <magic-checkbox v-model:value="row.required"/>
             </magic-table-column>
             <magic-table-column title="Key" #default="{ row }">
@@ -13,22 +13,22 @@
                 <magic-file v-else-if="row.dataType === 'MultipartFiles' " v-model:value="row.value" :border="false" multiple/>
                 <magic-input v-else v-model:value="row.value" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="参数类型" #default="{ row }" width="135">
+            <magic-table-column :title="$i('message.parameterType')" #default="{ row }" width="135">
                 <magic-select :options="$REQUEST_PARAMETER_TYPES" v-model:value="row.dataType" :default-select="$DEFAULT_REQUEST_PARAMETER_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="默认值" #default="{ row }">
+            <magic-table-column :title="$i('message.defaultValue')"  #default="{ row }" >
                 <magic-input v-model:value="row.defaultValue" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="验证方式" #default="{ row }" width="100">
+            <magic-table-column :title="$i('api.validateType')" #default="{ row }" width="115">
                 <magic-select :options="$VALIDATE_TYPES" v-model:value="row.validateType" :default-select="$DEFAULT_VALIDATE_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="表达式或正则表达式" #default="{ row }">
+            <magic-table-column :title="$i('api.expression')" #default="{ row }" width="220">
                 <magic-input v-model:value="row.expression" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="验证说明" #default="{ row }">
+            <magic-table-column :title="$i('api.validate')" #default="{ row }" width="165">
                 <magic-input v-model:value="row.error" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="描述" #default="{ row }" flex="2">
+            <magic-table-column :title="$i('message.description')" #default="{ row }" flex="2">
                 <magic-input v-model:value="row.description" :border="false"/>
             </magic-table-column>
         </magic-table>
@@ -36,6 +36,7 @@
 </template>
 <script setup>
 import { inject, ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const currentIndex = ref(-1)
 const info = inject('info')
 </script>

+ 8 - 7
src/components/panel/api/magic-api-path.vue

@@ -1,33 +1,34 @@
 <template>
     <div class="magic-panel-api">
         <magic-panel-common-toolbar v-model:index="currentIndex" :value="info.paths"/>
-        <magic-table :data="info.paths" border @clickRow="index => currentIndex = index">
+        <magic-table :data="info.paths" border @clickRow="index => currentIndex = index" align="center">
             <magic-table-column title="Key" #default="{ row }">
                 <magic-input v-model:value="row.name" :border="false"/>
             </magic-table-column>
             <magic-table-column title="Value" #default="{ row }">
                 <magic-input v-model:value="row.value" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="参数类型" #default="{ row }" width="135">
+            <magic-table-column :title="$i('message.parameterType')" #default="{ row }" width="135">
                 <magic-select :options="$REQUEST_SIMPLE_TYPES" v-model:value="row.dataType" :default-select="$DEFAULT_REQUEST_SIMPLE_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="验证方式" #default="{ row }" width="100">
+            <magic-table-column :title="$i('api.validateType')" #default="{ row }" width="115">
                 <magic-select :options="$VALIDATE_TYPES" v-model:value="row.validateType" :default-select="$DEFAULT_VALIDATE_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="表达式或正则表达式" #default="{ row }">
+            <magic-table-column :title="$i('api.expression')" #default="{ row }" width="220">
                 <magic-input v-model:value="row.expression" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="验证说明" #default="{ row }">
+            <magic-table-column :title="$i('api.validate')" #default="{ row }" width="165">
                 <magic-input v-model:value="row.error" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="描述" #default="{ row }" flex="2">
+            <magic-table-column :title="$i('message.description')" #default="{ row }" flex="2">
                 <magic-input v-model:value="row.description" :border="false"/>
             </magic-table-column>
         </magic-table>
     </div>
 </template>
 <script setup>
-import { inject, ref, toRaw } from 'vue'
+import { inject, ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const currentIndex = ref(-1)
 const info = inject('info')
 </script>

+ 16 - 10
src/components/panel/api/magic-api-request-structure.vue

@@ -1,34 +1,34 @@
 <template>
     <magic-table :data="filteredTreeList" border>
-        <magic-table-column title="字段" #default="{ row }">
+        <magic-table-column :title="$i('api.field')" #default="{ row }">
             <span :style="{paddingLeft: `${(row.level + (row.folder ? 0 : 1)) * 16}px`}" @click="doExpand(row)">
                 <magic-icon v-if="row.folder" :icon="row.expand ? 'arrow-bottom' : 'arrow-right'" />
             </span>
             <span>{{ row.name || '-' }}</span>
         </magic-table-column>
-        <magic-table-column title="必填" #default="{ row }" width="35">
+        <magic-table-column :title="$i('message.required')" #default="{ row }" width="65">
             <magic-checkbox v-model:value="row.node.required"/>
         </magic-table-column>
-        <magic-table-column title="类型" #default="{ row }" width="80">
+        <magic-table-column :title="$i('message.type')" #default="{ row }" width="80">
             <magic-select :options="$BODY_DATA_TYPES" v-model:value="row.node.dataType" :border="false"/>
         </magic-table-column>
-        <magic-table-column title="" #default="{ row }" flex="2">
+        <magic-table-column title="Value" #default="{ row }" flex="2">
             <template v-if="row.folder"><p align="center" style="flex:1">-</p></template>
             <span v-else class="magic-data-type" :class="row.node.dataType?.toLowerCase()">{{ row.node.value }}</span>
         </magic-table-column>
-        <magic-table-column title="默认值" #default="{ row }">
+        <magic-table-column :title="$i('message.defaultValue')" #default="{ row }">
             <magic-input v-model:value="row.node.defaultValue" :border="false"/>
         </magic-table-column>
-        <magic-table-column title="验证方式" #default="{ row }" width="100">
+        <magic-table-column :title="$i('api.validateType')" #default="{ row }" width="115">
             <magic-select :options="$VALIDATE_TYPES" v-model:value="row.node.validateType" :default-select="$DEFAULT_VALIDATE_TYPE" :border="false"/>
         </magic-table-column>
-        <magic-table-column title="表达式或正则表达式" #default="{ row }">
+        <magic-table-column :title="$i('api.expression')" #default="{ row }">
             <magic-input v-model:value="row.node.expression" :border="false"/>
         </magic-table-column>
-        <magic-table-column title="验证说明" #default="{ row }">
+        <magic-table-column :title="$i('api.validate')" #default="{ row }">
             <magic-input v-model:value="row.node.error" :border="false"/>
         </magic-table-column>
-        <magic-table-column title="字段注释" #default="{ row }">
+        <magic-table-column :title="$i('message.description')" #default="{ row }">
             <magic-input v-if="row.name" v-model:value="row.node.description" :border="false"/>
             <template v-else><p align="center" style="flex:1">-</p></template>
         </magic-table-column>
@@ -36,13 +36,14 @@
 </template>
 <script setup>
 import { computed, inject} from 'vue'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 let treeList = []
 const appendNode = (node, level) =>{
     const item = {
         level,
         node,
-        name: level === 0 ? '根节点' : node.name,
+        name: level === 0 ? $i('message.root') : node.name,
         folder: node?.dataType === 'Object' || node?.dataType === 'Array',
         display: true
     }
@@ -83,6 +84,11 @@ const doExpand = (item) => {
 }
 .magic-table :deep(.magic-table-column){
     display: flex;
+    justify-content: center;
+    text-align: center;
+}
+.magic-table :deep(.magic-table-column:first-child){
+    justify-content: unset;
 }
 .magic-data-type{
     display: inline-block;

+ 3 - 2
src/components/panel/api/magic-api-response-body.vue

@@ -1,12 +1,13 @@
 <template>
     <magic-monaco-editor v-if="!opened.responseBlob" v-model:value="responseBody" language="json" :readonly="true"/>
     <iframe v-else-if="blobURL" :src="blobURL" @load="onIframeLoad" ref="iframe"/>
-    <magic-empty v-else text="无响应Body"/>
+    <magic-empty v-else :text="$i('message.empty', $i('message.responseBody'))"/>
 </template>
 <script setup>
 import { computed, inject, ref } from 'vue'
 import Beautifier from '../../../scripts/beautifier/javascript/beautifier'
-import { download } from '../../../scripts/utils'
+import { download } from '../../../scripts/utils.js'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 const opened = inject('opened')
 const iframe = ref(null)

+ 12 - 6
src/components/panel/api/magic-api-response-structure.vue

@@ -1,27 +1,28 @@
 <template>
     <magic-table v-if="filteredTreeList.length" :data="filteredTreeList" border>
-        <magic-table-column title="字段" #default="{ row }">
+        <magic-table-column :title="$i('api.field')" #default="{ row }">
             <span :style="{paddingLeft: `${(row.level + (row.folder ? 0 : 1)) * 16}px`}" @click="doExpand(row)">
                 <magic-icon v-if="row.folder" :icon="row.expand ? 'arrow-bottom' : 'arrow-right'" />
             </span>
             <span>{{ row.name || '-' }}</span>
         </magic-table-column>
-        <magic-table-column title="类型" #default="{ row }" width="80">
+        <magic-table-column :title="$i('message.type')" #default="{ row }" width="80">
             <magic-select :options="$BODY_DATA_TYPES" v-model:value="row.node.dataType" :border="false"/>
         </magic-table-column>
-        <magic-table-column title="" #default="{ row }" flex="3">
+        <magic-table-column title="Value" #default="{ row }" flex="3">
             <template v-if="row.folder"><p align="center" style="flex:1">-</p></template>
             <span v-else class="magic-data-type" :class="row.node.dataType?.toLowerCase()">{{ row.node.value }}</span>
         </magic-table-column>
-        <magic-table-column title="字段注释" #default="{ row }">
+        <magic-table-column :title="$i('message.description')" #default="{ row }">
             <magic-input v-if="row.name" v-model:value="row.node.description" :border="false"/>
             <template v-else><p align="center" style="flex:1">-</p></template>
         </magic-table-column>
     </magic-table>
-    <magic-empty v-else text="无响应结构"/>
+    <magic-empty v-else :text="$i('message.empty', $i('message.responseBody'))"/>
 </template>
 <script setup>
 import { computed, inject } from 'vue'
+import $i from '../../../scripts/i18n.js'
 
 const info = inject('info')
 const opened = inject('opened')
@@ -30,7 +31,7 @@ const appendNode = (node, level) =>{
     const item = {
         level,
         node,
-        name: level === 0 ? '根节点' : node.name,
+        name: level === 0 ? $i('message.root') : node.name,
         folder: node?.dataType === 'Object' || node?.dataType === 'Array',
         display: true
     }
@@ -74,6 +75,11 @@ const filteredTreeList = computed(() => {
 }
 .magic-table :deep(.magic-table-column){
     display: flex;
+    justify-content: center;
+    text-align: center;
+}
+.magic-table :deep(.magic-table-column:first-child){
+    justify-content: unset;
 }
 .magic-data-type{
     display: inline-block;

+ 4 - 4
src/components/panel/api/magic-api-response.vue

@@ -11,14 +11,14 @@
 </template>
 <script setup>
 import { resolveDynamicComponent } from "@vue/runtime-core"
-
+import $i from '../../../scripts/i18n.js'
 defineProps({
     opened: Object
 })
 const navbars = [
-    { title: 'Body', component: resolveDynamicComponent('magic-api-response-body')},
-    { title: '响应Header', component: resolveDynamicComponent('magic-api-response-header')},
-    { title: '响应结构', component: resolveDynamicComponent('magic-api-response-structure')}
+    { title: $i('message.responseBody'), component: resolveDynamicComponent('magic-api-response-body')},
+    { title: $i('message.responseHeader'), component: resolveDynamicComponent('magic-api-response-header')},
+    { title: $i('message.responseStructure'), component: resolveDynamicComponent('magic-api-response-structure')}
 ]
 </script>
 <style scoped>

+ 3 - 2
src/components/panel/common/magic-panel-common-toolbar.vue

@@ -2,20 +2,21 @@
     <magic-panel-toolbar :toolbars="toolbars"/>
 </template>
 <script setup>
+import $i from '../../../scripts/i18n.js'
 const props = defineProps({
     value: Array,
     index: Number
 })
 const emit = defineEmits(['update:index'])
 const toolbars = [{
-    title: '增加一行',
+    title: $i('message.addRow'),
     icon: 'plus',
     onClick(){
         props.value.push({})
         emit('update:index', props.value.length - 1)
     }
 },{
-    title: '删除一行',
+    title: $i('message.removeRow'),
     icon: 'minus',
     onClick(){
         if(props.value.length > 0){

+ 15 - 14
src/components/panel/datasource/magic-datasource-datasource.vue

@@ -1,43 +1,44 @@
 <template>
     <div class="magic-form-row">
-        <label>名称</label>
-        <magic-input v-model:value="info.name" placeholder="数据源名称,仅做展示使用"/>
+        <label>{{$i('message.name')}}</label>
+        <magic-input v-model:value="info.name" :placeholder="$i('datasource.form.placeholder.name')"/>
     </div>
     <div class="magic-form-row">
         <label>Key</label>
-        <magic-input v-model:value="info.key" placeholder="数据库key,后续代码中使用"/>
+        <magic-input v-model:value="info.key" :placeholder="$i('datasource.form.placeholder.key')"/>
     </div>
     <div class="magic-form-row">
         <label>URL</label>
-        <magic-input v-model:value="info.url" placeholder="请输入jdbcurl,如:jdbc:mysql://localhost"/>
+        <magic-input v-model:value="info.url" :placeholder="$i('datasource.form.placeholder.url')"/>
     </div>
     <div class="magic-form-row">
-        <label>用户名</label>
-        <magic-input v-model:value="info.username" placeholder="请输入数据库用户名"/>
+        <label>{{$i('message.username')}}</label>
+        <magic-input v-model:value="info.username" :placeholder="$i('datasource.form.placeholder.username')"/>
     </div>
     <div class="magic-form-row">
-        <label>密码</label>
-        <magic-input v-model:value="info.password" type="password" placeholder="请输入数据库密码"/>
+        <label>{{$i('message.password')}}</label>
+        <magic-input v-model:value="info.password" type="password" :placeholder="$i('datasource.form.placeholder.password')"/>
     </div>
     <div class="magic-form-row">
-        <label>驱动类</label>
-        <magic-select inputable v-model:value="info.driverClassName" width="100%" :options="$JDBC_DRIVERS.map(it => { return {text: it, value: it} })" placeholder="驱动类,可选,内部自动识别,也可以手动输入指定"/>
+        <label>{{$i('datasource.form.driver')}}</label>
+        <magic-select inputable v-model:value="info.driverClassName" width="100%" :options="$JDBC_DRIVERS.map(it => { return {text: it, value: it} })" :placeholder="$i('datasource.form.placeholder.driver')"/>
     </div>
     <div class="magic-form-row">
-        <label>类型</label>
-        <magic-select inputable v-model:value="info.type" width="100%" :options="$DATASOURCE_TYPES.map(it => { return {text: it, value: it} })" placeholder="数据源类型,可选,也可以手动输入指定"/>
+        <label>{{$i('datasource.form.type')}}</label>
+        <magic-select inputable v-model:value="info.type" width="100%" :options="$DATASOURCE_TYPES.map(it => { return {text: it, value: it} })" :placeholder="$i('datasource.form.placeholder.type')"/>
     </div>
     <div class="magic-form-row">
         <label>maxRows</label>
-        <magic-input v-model:value="info.maxRows" placeholder="最多返回条数,-1未不限制" :default-value="-1" type="number"/>
+        <magic-input v-model:value="info.maxRows" :placeholder="$i('datasource.form.placeholder.maxRows')" :default-value="-1" type="number"/>
     </div>
     <div class="magic-form-row">
-        <label>其它配置</label>
+        <label>{{$i('datasource.form.other')}}</label>
         <magic-monaco-editor language="json" v-model:value="properties" style="height:150px"/>
     </div>
 </template>
 <script setup>
 import { ref, watch } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const { info } = defineProps({
     info: Object
 })

+ 24 - 22
src/components/panel/footer/magic-backup.vue

@@ -4,25 +4,25 @@
 		<div>
 			<magic-loading :loading="loading"></magic-loading>
 			<magic-table v-if="backupData.length > 0" :data="backupData" :border="true" @contextmenu="onContextmenu" @loadNext="loadNext">
-				<magic-table-column title="记录时间" align="center" width="160" v-slot:default="{ row }">{{ formatDate(row.createDate) }}</magic-table-column>
-				<magic-table-column title="类型" align="center" width="90" v-slot:default="{ row }"><span>{{ displayTitle(row.type) }}</span></magic-table-column>
-				<magic-table-column title="操作人" align="center" width="100" v-slot:default="{ row }">{{ row.createBy || 'guest' }}</magic-table-column>
-				<magic-table-column title="名称" v-slot:default="{ row }">{{ row.name }}</magic-table-column>
+				<magic-table-column :title="$i('message.date')" align="center" width="160" v-slot:default="{ row }">{{ formatDate(row.createDate) }}</magic-table-column>
+				<magic-table-column :title="$i('message.type')" align="center" width="90" v-slot:default="{ row }"><span>{{ displayTitle(row.type) }}</span></magic-table-column>
+				<magic-table-column :title="$i('history.operator')" align="center" width="100" v-slot:default="{ row }">{{ row.createBy || 'guest' }}</magic-table-column>
+				<magic-table-column :title="$i('message.name')" v-slot:default="{ row }">{{ row.name }}</magic-table-column>
 			</magic-table>
-			<magic-empty v-else text="暂无历史记录"/>
+			<magic-empty v-else :text="$i('message.empty', $i('history.name'))"/>
 		</div>
 	</div>
-	<magic-dialog v-model:value="dialog" title="历史记录" width="80%" maxWidth="100%" top="60px" height="80%" className="magic-dialog-diff">
+	<magic-dialog v-model:value="dialog" :title="$i('history.name')" width="80%" maxWidth="100%" top="60px" height="80%" className="magic-dialog-diff">
 		<div class="magic-backup-diff-container">
 			<div class="magic-backup-diff-header">
 				<div>{{ currentTime }}</div>
-				<div>当前版本</div>
+				<div>{{ $i('backup.current') }}</div>
 			</div>
 			<magic-monaco-diff-editor v-if="!dialogLoading" v-model:value="diffValue" language="magicscript"/>
 			<magic-loading v-else :loading="dialogLoading" />
 		</div>
 		<magic-button-group align="right">
-			<magic-button value="恢复" type="active" @click="doRollback"/>
+			<magic-button :value="$i('backup.rollback')" type="active" @click="doRollback"/>
 		</magic-button-group>
 	</magic-dialog>
 </template>
@@ -32,21 +32,22 @@ import bus from '../../../scripts/bus.js'
 import constants from '../../../scripts/constants.js'
 import Message from '../../../scripts/constants/message.js'
 import request from '../../../scripts/request.js'
+import $i from '../../../scripts/i18n.js'
 import { formatDate } from '../../../scripts/utils.js'
 import modal from '../../common/dialog/magic-modal.js'
 const loading = ref(true)
 const toolbars = [{
 	icon: 'refresh',
-	title: '刷新',
+	title: $i('message.refresh'),
 	onClick() {
 		load()
 	}
 }, {
 	icon: 'copy',
-	title: '全量备份',
+	title: $i('backup.full'),
 	onClick() {
 		request.sendPost('/backup/full').success(() => {
-			bus.status('全量备份完毕')
+			bus.status('backup.backupSuccess')
 			load()
 		})
 	}
@@ -77,10 +78,10 @@ const load = (timestamp) => {
 const service = inject('service')
 const displayTitle = type => {
 	if(type.endsWith('-group')){
-		return service[type.replace('-group', '')].name + '分组'
+		return $i('message.group', service[type.replace('-group', '')].name)
 	}
 	if(type === 'full'){
-		return '全量备份'
+		return $i('backup.full')
 	}
 	return service[type]?.name || type
 }
@@ -104,7 +105,7 @@ const onContextmenu = (event, row) => {
 	if(row.id !== 'full' && !row.id.endsWith('-group')){
 		menus.push({
 			icon: 'difference',
-			label: '对比不同',
+			label: $i('backup.difference'),
 			onClick() {
 				currentTime.value = formatDate(row.createDate)
 				dialog.value = true
@@ -126,17 +127,17 @@ const onContextmenu = (event, row) => {
 	if(row.id === 'full' || !row.id.endsWith('-group')){
 		menus.push({
 			icon: 'rollback',
-			label: '还原修改',
+			label: $i('backup.rollback'),
 			onClick(){
 				const msg = `${row.name}(${formatDate(row.createDate)})`
-				modal.confirm('还原备份', '该模式是全量从备份文件中读取,并覆盖更新当前资源,是否继续?', () => {
+				modal.confirm($i('backup.rollback'), $i('backup.rollbackConfirm'), () => {
 					request.sendPost('/backup/rollback', { id: row.id, timestamp: row.createDate}).success(r => {
 						if(!r){
-							modal.alert(`恢复${msg}失败`)
-							bus.status(`恢复「${msg}」失败`, false)
+							modal.alert($i('backup.rollbackFailed', msg))
+							bus.status('backup.rollbackFailed', false, msg)
 						} else {
 							bus.report(`resource-rollback-full`)
-							bus.status(`恢复「${msg}」成功`)
+							bus.status(`backup.rollbackSuccess`, true, msg)
 							bus.$emit(Message.LOAD_RESOURCES)
 						}
 					})
@@ -153,11 +154,12 @@ const doRollback = () => {
 	const msg = `${currentSelected.value.name}(${formatDate(currentSelected.value.createDate)})`
 	request.sendPost('/backup/rollback', { id: currentSelected.value.id, timestamp: currentSelected.value.createDate}).success(r => {
 		if(!r){
-			modal.alert(`恢复${msg}失败`)
-			bus.status(`恢复「${msg}」失败`, false)
+			modal.alert($i('backup.rollbackFailed', msg))
+			bus.status('backup.rollbackFailed', false, msg)
 		} else {
 			bus.report(`resource-rollback`)
-			bus.status(`恢复「${msg}」成功`)
+			modal.alert($i('backup.rollbackSuccess', msg))
+			bus.status('backup.rollbackSuccess', true, msg)
 			bus.$emit(Message.LOAD_RESOURCES, currentSelected.value.type)
 		}
 	})

+ 6 - 5
src/components/panel/footer/magic-debug.vue

@@ -22,39 +22,40 @@
                     <span class="object-type" v-if="item.type && !item.isNull"> ({{ item.type }})</span>
                 </template>
             </magic-tree>
-            <magic-empty v-else text="暂无变量信息" />
+            <magic-empty v-else :text="$i('message.empty', $i('message.variable'))" />
         </div>
     </div>
 </template>
 <script setup>
 import { computed, inject, ref } from 'vue'
 import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 import Message from '../../../scripts/constants/message.js'
 import { processTree } from '../../../scripts/utils.js'
 const opened = inject('opened')
 const disabled = computed(() => !opened.value.variables)
 
 const toolbars = ref([{
-    title: '继续(F8)',
+    title: $i('editor.tooltip.resume') + '(F8)',
     icon: 'continue',
     disabled,
     onClick(){
         bus.$emit(Message.DEBUG_CONTINUE)
     }
 }, {
-    title: '单步(F6)',
+    title: $i('editor.tooltip.stepInto') + '(F6)',
     icon: 'step-over',
     disabled,
     onClick(){
         bus.$emit(Message.DEBUG_SETPINTO)
     }
 }, {
-    title: '展开',
+    title: $i('resource.header.expand'),
     icon: 'expand-all',
     disabled,
     onClick: () => processTree(opened.value.variables, it => it.opened = true)
 }, {
-    title: '折叠',
+    title: $i('resource.header.collapse'),
     icon: 'collapse-all',
     disabled,
     onClick: () => processTree(opened.value.variables, it => it.opened = false)

+ 4 - 3
src/components/panel/footer/magic-event.vue

@@ -3,10 +3,10 @@
         <magic-panel-toolbar :toolbars="toolbars"/>
         <div>
             <magic-table :data="data" border>
-                <magic-table-column title="时间" width="180" #default="{ row }">
+                <magic-table-column :title="$i('message.date')" width="180" #default="{ row }">
                     {{ row.timestamp }}
                 </magic-table-column>
-                <magic-table-column title="事件内容" #default="{ row }">
+                <magic-table-column :title="$i('event.message')" #default="{ row }">
                     <div v-html="row.content"></div>
                 </magic-table-column>
             </magic-table>
@@ -15,9 +15,10 @@
 </template>
 <script setup>
 import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 const data = bus.getStatusLog()
 const toolbars = [{
-    title: '清空',
+    title: $i('message.clear'),
     icon: 'clear',
     onClick: () => {
         bus.clearStatusLog()

+ 6 - 5
src/components/panel/footer/magic-log.vue

@@ -1,11 +1,11 @@
 <template>
 	<div class="magic-log-wrapper" @contextmenu.prevent="e=> onContextMenu(e)">
-		<magic-empty v-if="!logs ||logs.length === 0" text="暂无日志"/>
+		<magic-empty v-if="!logs ||logs.length === 0" :text="$i('message.empty', $i('message.log'))"/>
 		<div v-else class="magic-log" ref="element">
 			<div v-for="(item, key) in logs" :class="{ multiple: item.multiple, more: item.showMore }" :key="'run_log_' + key">
 				<pre v-html="item.html"></pre>
 				<span v-if="item.multiple" class="multiple" @click="item.showMore = !item.showMore">
-					{{ item.showMore ? '点击隐藏多行日志' : `有 ${item.lines} 行日志被隐藏 点击显示`}}
+					{{ item.showMore ? $i('log.hide') : $i('log.show', item.lines)}}
 				</span>
 			</div>
 		</div>
@@ -14,6 +14,7 @@
 <script setup>
 import { getCurrentInstance, nextTick, ref } from 'vue'
 import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 import Socket from '../../../scripts/constants/socket.js'
 const logs = ref([])
 const element = ref(null)
@@ -23,15 +24,15 @@ const onContextMenu = event => {
 		event,
 		menus: [{
 			icon: 'delete',
-			label: '清空日志',
+			label: $i('message.clear'),
 			onClick: ()=> logs.value.splice(0)
 		}, {
 			icon: 'expand-all',
-			label: '全部展开',
+			label: $i('resource.header.expand'),
 			onClick: ()=> logs.value.forEach(it => it.showMore = true)
 		}, {
 			icon: 'collapse-all',
-			label: '全部收缩',
+			label: $i('resource.header.collapse'),
 			onClick: ()=> logs.value.forEach(it => it.showMore = false)
 		}]
 	})

+ 8 - 8
src/components/panel/footer/magic-online.vue

@@ -1,13 +1,14 @@
 <template>
 	<div class="magic-online">
 		<magic-avatar-group :users="users" :max="9"/>
-		<span>当前在线:{{users.length}}</span>
+		<span>{{ $i('online.onlines', users.length) }}</span>
 	</div>
 </template>
 <script setup>
 import { inject, reactive } from 'vue'
 import bus from '../../../scripts/bus'
 import constants from '../../../scripts/constants.js'
+import $i from '../../../scripts/i18n.js'
 import Socket from '../../../scripts/constants/socket.js'
 import Message from '../../../scripts/constants/message.js'
 
@@ -37,29 +38,28 @@ bus.$event(Socket.LOGIN_RESPONSE, ([ret, user]) => {
 	users.splice(0, users.length)
 	if(ret === '1') {
 		addUser(user)
-		bus.send(Socket.GET_ONLINE)
 	}
 	
 })
 bus.$event(Socket.USER_LOGIN, ([user]) => {
 	if(constants.CLIENT_ID !== user.cid ){
 		bus.$emit(Message.NOTIFY, {
-			title: '用户上线',
-			content: `用户${user.username}已上线,IP:${user.ip}`,
+			title: $i('online.login'),
+			content: $i('online.loginTips', user.username, user.ip),
 			duration: 3000
 		})
-		bus.status(`用户${user.username}已上线,IP:${user.ip}`)
+		bus.status('online.loginTips', true, user.username, user.ip)
 	} 
 	addUser(user)
 })
 bus.$event(Socket.USER_LOGOUT, ([user]) => {
 	if(constants.CLIENT_ID !== user.cid ){
 		bus.$emit(Message.NOTIFY, {
-			title: '用户下线',
-			content: `用户${user.username}已下线,IP:${user.ip}`,
+			title: $i('online.lotout'),
+			content: $i('online.logoutTips', user.username, user.ip),
 			duration: 3000
 		})
-		bus.status(`用户${user.username}已下线,IP:${user.ip}`)
+		bus.status('online.logoutTips', true, user.username, user.ip)
 	}
 	const index = users.findIndex(it => it.cid === user.cid)
 	processFiles(user.cid, '0')

+ 6 - 5
src/components/panel/footer/magic-status-bar.vue

@@ -16,6 +16,7 @@ import Message from '../../../scripts/constants/message.js'
 import constants from '../../../scripts/constants.js'
 import request from '../../../scripts/request.js'
 import store from '../../../scripts/store.js'
+import $i from '../../../scripts/i18n.js'
 import modal from '../../common/dialog/magic-modal.js'
 const props = defineProps({
     config: Object
@@ -33,12 +34,12 @@ const icons = [{
     onClick: () => window.open('https://github.com/ssssssss-team/magic-api')
 }, {
     icon: 'qq',
-    title: '加入QQ群',
+    title: $i('message.joinGroup'),
     displayKey: 'qqGroup',
     onClick: () => window.open('https://qm.qq.com/cgi-bin/qm/qr?k=Q6dLmVS8cHwoaaP18A3tteK_o0244e6B&jump_from=webapi')
 }, {
     icon: 'help',
-    title: '帮助文档',
+    title: $i('message.document'),
     displayKey: 'document',
     onClick: () => window.open('https://ssssssss.org/magic-api')
 }]
@@ -47,20 +48,20 @@ const navs = computed(() => {
     user.value && user.value.id && user.value.username&& array.push({
         icon: 'logout',
         title: user.value.username,
-        onClick: () => modal.confirm('注销登录', `是否要注销登录「${user.value.username}」`, () => request.sendPost('/logout').success(() => {
+        onClick: () => modal.confirm($i('message.logout'), $i('message.logoutConfirm', user.value.username), () => request.sendPost('/logout').success(() => {
             user.value = null
             constants.HEADER_MAGIC_TOKEN_VALUE = 'unauthorization'
             constants.LOGINED = false
             store.remove(constants.STORE.token)
             bus.$emit(Message.LOGOUT)
-            bus.status('注销登录成功')
+            bus.status('message.logoutSuccess')
         }))
     })
     return array
 })
 const content = ref('')
 bus.$on(Message.LOGINED, () => {
-    bus.status('获取当前登录用户信息')
+    bus.status('message.getCurrentLoginUser')
     request.send('/user').success(value => user.value = value)
 })
 bus.$on(Message.STATUS, msg => content.value = msg)

+ 9 - 8
src/components/panel/footer/magic-todo.vue

@@ -18,31 +18,32 @@
 					<label class="todo">{{ item.text }}</label>
 				</template>
 			</magic-tree>
-			<magic-empty v-else text="暂无TODO事项"/>
+			<magic-empty v-else :text="$i('message.empty', 'TODO')"/>
 		</div>
 	</div>
 </template>
 <script setup>
 import { inject, onMounted, ref } from 'vue'
-import bus from '../../../scripts/bus'
-import constants from '../../../scripts/constants'
+import bus from '../../../scripts/bus.js'
+import constants from '../../../scripts/constants.js'
 import Message from '../../../scripts/constants/message.js'
-import request from '../../../scripts/request'
-import { processTree } from '../../../scripts/utils'
+import request from '../../../scripts/request.js'
+import $i from '../../../scripts/i18n.js'
+import { processTree } from '../../../scripts/utils.js'
 const loading = ref(true)
 const toolbars = [{
 	icon: 'refresh',
-	title: '刷新',
+	title: $i('message.refresh'),
 	onClick() {
 		load()
 	}
 }, {
 	icon: 'expand-all',
-	title: '展开',
+	title: $i('message.expand'),
 	onClick: () => processTree(tree.value, it => it.opened = true)
 }, {
 	icon: 'collapse-all',
-	title: '折叠',
+	title: $i('message.collapse'),
 	onClick: () => processTree(tree.value, it => it.opened = false)
 }]
 const tree = ref([])

+ 12 - 11
src/components/panel/footer/magic-toolbar.vue

@@ -6,7 +6,7 @@
                     <div class="magic-toolbar-header">
                         <label>{{ toolbar.title }}</label>
                         <div class="magic-toolbar-header-buttons">
-                            <magic-icon icon="minimize" size="14px" title="最小化" @click="navbar.select(-1)"/>
+                            <magic-icon icon="minimize" size="14px" :title="$i('message.hide')" @click="navbar.select(-1)"/>
                         </div>
                     </div>
                     <component :is="toolbar.component"/>
@@ -18,30 +18,31 @@
 <script setup>
 import { provide, reactive, ref, resolveDynamicComponent, shallowRef } from 'vue'
 import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 import Message from '../../../scripts/constants/message.js'
 const navbar = ref(null)
 const toolbars = reactive([
-    { type: 'api', title: '接口信息', icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-api-info')) },
+    { type: 'api', title: $i('api.title'), icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-api-info')) },
 
-    { id: 'response', type: 'api', title: '执行结果', icon: 'run', component: shallowRef(resolveDynamicComponent('magic-api-response')) },
+    { id: 'response', type: 'api', title: $i('toolbars.response'), icon: 'run', component: shallowRef(resolveDynamicComponent('magic-api-response')) },
 
-    { type: 'task', title: '定时任务信息', icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-task-info')) },
+    { type: 'task', title: $i('task.title'), icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-task-info')) },
 
-    { type: 'function', title: '函数信息', icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-function-info')) },
+    { type: 'function', title: $i('fn.title'), icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-function-info')) },
 
-    { type: 'group-api', title: '接口分组', icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-api-group')) },
+    { type: 'group-api', title: $i('message.group', $i('api.name')), icon: 'parameter', component: shallowRef(resolveDynamicComponent('magic-api-group')) },
 
-    { id: 'debug', type: ['api', 'task'], title: '调试信息', icon: 'debug-info', component: shallowRef(resolveDynamicComponent('magic-debug')) },
+    { id: 'debug', type: ['api', 'task'], title: $i('toolbars.debug'), icon: 'debug-info', component: shallowRef(resolveDynamicComponent('magic-debug')) },
 
-    { id: 'log', title: '运行日志', icon: 'log', component: shallowRef(resolveDynamicComponent('magic-log')) },
+    { id: 'log', title: $i('toolbars.log'), icon: 'log', component: shallowRef(resolveDynamicComponent('magic-log')) },
 
-    { type: 'api', title: '全局参数', icon: 'settings', component: shallowRef(resolveDynamicComponent('magic-global')) },
+    { type: 'api', title: $i('toolbars.global'), icon: 'settings', component: shallowRef(resolveDynamicComponent('magic-global')) },
 
     { id: 'todo', title: 'TODO', icon: 'todo', component: shallowRef(resolveDynamicComponent('magic-todo')) },
 
-    { id: 'history', title: '历史记录', icon: 'history', component: shallowRef(resolveDynamicComponent('magic-backup')) },
+    { id: 'history', title: $i('toolbars.history'), icon: 'history', component: shallowRef(resolveDynamicComponent('magic-backup')) },
 
-    { id: 'event', title: '事件', icon: 'event', component: shallowRef(resolveDynamicComponent('magic-event')), style: { float: 'right'} }
+    { id: 'event', title: $i('toolbars.event'), icon: 'event', component: shallowRef(resolveDynamicComponent('magic-event')), style: { float: 'right'} }
 ])
 const opened = ref({})
 const info = ref({})

+ 8 - 10
src/components/panel/function/magic-function-parameter.vue

@@ -1,24 +1,22 @@
 <template>
     <div class="magic-panel-function">
-        <magic-panel-common-toolbar v-model:index="currentIndex" :value="data"/>
-        <magic-table :data="data" border @clickRow="index => currentIndex = index">
-            <magic-table-column title="参数名" #default="{ row }" width="20%">
+        <magic-panel-common-toolbar v-model:index="currentIndex" :value="info.parameters"/>
+        <magic-table :data="info.parameters" border @clickRow="index => currentIndex = index">
+            <magic-table-column :title="$i('message.name')" #default="{ row }" width="20%">
                 <magic-input v-model:value="row.name" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="参数类型" #default="{ row }" width="20%">
+            <magic-table-column :title="$i('message.parameterType')" #default="{ row }" width="20%">
                 <magic-select :options="$FUNCTION_RETURN_TYPES" v-model:value="row.dataType" :default-select="$DEFAULT_FUNCTION_RETURN_TYPE" :border="false"/>
             </magic-table-column>
-            <magic-table-column title="描述" #default="{ row }" flex="1">
+            <magic-table-column :title="$i('message.description')" #default="{ row }" flex="1">
                 <magic-input v-model:value="row.description" :border="false"/>
             </magic-table-column>
         </magic-table>
     </div>
 </template>
 <script setup>
-import { ref, toRaw } from 'vue'
-const { info } = defineProps({
-    info: Object
-})
+import { inject, ref } from 'vue'
+import $i from '../../../scripts/i18n.js'
+const info = inject('info')
 const currentIndex = ref(-1)
-const data = ref(toRaw(info.parameters))
 </script>

+ 9 - 8
src/components/panel/header/magic-export.vue

@@ -1,12 +1,12 @@
 <template>
-	<magic-dialog title="导出" v-model:value="show" :shade="false" padding="0" width="400px" top="60px" overflow="hidden">
+	<magic-dialog :title="$i('message.export')" v-model:value="show" :shade="false" padding="0" width="480px" top="60px" overflow="hidden">
 		<magic-resource-choose ref="resource" v-model:value="selected"/>
         <magic-button-group align="right" style="margin:5px 0; margin-right:5px;">
-            <magic-button value="展开" @onClick="$refs.resource.expand(true)"></magic-button>
-            <magic-button value="收缩" @onClick="$refs.resource.expand(false)"></magic-button>
-            <magic-button value="全选" @onClick="$refs.resource.selectAll(true)"></magic-button>
-            <magic-button value="取消全选" @onClick="$refs.resource.selectAll(false)"></magic-button>
-            <magic-button type="active" value="导出" @onClick="doExport"></magic-button>
+            <magic-button :value="$i('message.expand')" @onClick="$refs.resource.expand(true)"></magic-button>
+            <magic-button :value="$i('message.collapse')" @onClick="$refs.resource.expand(false)"></magic-button>
+            <magic-button :value="$i('message.selectAll')" @onClick="$refs.resource.selectAll(true)"></magic-button>
+            <magic-button :value="$i('message.deselectAll')" @onClick="$refs.resource.selectAll(false)"></magic-button>
+            <magic-button type="active" :value="$i('message.export')" @onClick="doExport"></magic-button>
         </magic-button-group>
 	</magic-dialog>
 </template>
@@ -17,6 +17,7 @@ import modal from '../../common/dialog/magic-modal.js'
 import { ref } from 'vue'
 import request from '../../../scripts/request.js'
 import { download } from '../../../scripts/utils.js'
+import $i from '../../../scripts/i18n.js'
 const show = ref(false)
 const selected = ref([])
 bus.$on(Message.DO_DOWNLOAD, () => show.value = true)
@@ -31,12 +32,12 @@ const doExport = () => {
             responseType: 'blob'
         }).success(blob => {
             download(blob, 'magic-api.zip')
-            bus.status( `数据已导出完毕`)
+            bus.status('message.exported')
             show.value = false
             bus.report(`resource-export`)
         })
     }else{
-        modal.alert('请选择之后在进行导出!')
+        modal.alert($i('message.exportNoneSelect'))
     }
 }
 </script>

+ 48 - 15
src/components/panel/header/magic-header.vue

@@ -18,15 +18,33 @@
         <li v-for="theme in Object.keys(Themes)" :key="'theme_' + theme" @click="switchTheme(theme)">{{ theme }}</li>
       </ul>
     </div>
+     <div v-show="localeVisible" class="magic-locale-selector">
+      <ul>
+        <li v-for="item in locales" :key="'locale_' + item.id" @click="switchLocale(item)">{{ item.name }}</li>
+      </ul>
+    </div>
 </template>
 <script setup>
-import { computed, ref, watch } from 'vue'
+import { computed, reactive, ref, watch } from 'vue'
 import bus from '../../../scripts/bus.js'
 import { Themes } from '../../../scripts/theme.js'
 import constants from '../../../scripts/constants.js'
 import Message from '../../../scripts/constants/message.js'
 import * as monaco from 'monaco-editor'
 import store from '../../../scripts/store.js'
+import $i from '../../../scripts/i18n.js'
+import modal from '../../common/dialog/magic-modal.js'
+const localeScripts = import.meta.glob('../../../scripts/i18n/*.js')
+const locales = reactive([])
+for(let k in localeScripts){
+    localeScripts[k]().then(v => {
+        locales.push({
+            id: k.replace(/(.*?i18n\/)(.*)(\.js)/g, '$2'),
+            name: v.default.name
+        })
+    })
+   
+}
 const { themeStyle } = defineProps({
     title: {
         type: String,
@@ -36,6 +54,7 @@ const { themeStyle } = defineProps({
 })
 const version = constants.MAGIC_API_VERSION_TEXT
 const skinVisible = ref(false)
+const localeVisible = ref(false)
 const opened = ref({})
 const pageTitle = ref('')
 bus.$on(Message.OPEN_EMPTY, () => {
@@ -52,19 +71,20 @@ bus.$on(Message.OPEN, item => {
     })
 })
 const buttons = ref([
-    {name: '运行(Ctrl + Q)', icon: 'run', disabled: computed(() => opened.value.runnable !== true || opened.value.running === true), onClick: ()=> bus.$emit(Message.DO_TEST)},
-    {name: '保存(Ctrl + S)', icon: 'save', onClick: () => bus.$emit(Message.DO_SAVE, true)},
-    {name: '搜索(Ctrl + Shift + F)', icon: 'search', onClick: () => bus.$emit(Message.DO_SEARCH)},
-    {name: '上传', icon: 'upload', onClick: () => bus.$emit(Message.DO_UPLOAD)},
-    {name: '导出', icon: 'download', onClick: () => bus.$emit(Message.DO_DOWNLOAD)},
-    {name: '推送', icon: 'push', onClick: () => bus.$emit(Message.DO_PUSH)},
-    {name: '皮肤', icon: 'skin', onClick: () => skinVisible.value = !skinVisible.value},
-    {name: '重新加载所有数据', icon: 'refresh', onClick: () => bus.$emit(Message.RELOAD_RESOURCES)}
+    {name: `${$i('message.run')}(Ctrl + Q)`, icon: 'run', disabled: computed(() => opened.value.runnable !== true || opened.value.running === true), onClick: ()=> bus.$emit(Message.DO_TEST)},
+    {name: `${$i('message.save')}(Ctrl + S)`, icon: 'save', onClick: () => bus.$emit(Message.DO_SAVE, true)},
+    {name: `${$i('message.search')}(Ctrl + Shift + F)`, icon: 'search', onClick: () => bus.$emit(Message.DO_SEARCH)},
+    {name: $i('message.upload'), icon: 'upload', onClick: () => bus.$emit(Message.DO_UPLOAD)},
+    {name: $i('message.export'), icon: 'download', onClick: () => bus.$emit(Message.DO_DOWNLOAD)},
+    {name: $i('message.push'), icon: 'push', onClick: () => bus.$emit(Message.DO_PUSH)},
+    {name: $i('message.skin'), icon: 'skin', onClick: () => {skinVisible.value = !skinVisible.value; localeVisible.value = false}},
+    {name: $i('message.i18n'), icon: 'i18n', onClick: () => {localeVisible.value = !localeVisible.value; skinVisible.value = false}},
+    {name: $i('message.reload'), icon: 'refresh', onClick: () => bus.$emit(Message.RELOAD_RESOURCES)}
 ])
 const switchTheme = $theme => {
     constants.THEME = $theme
     bus.$emit(Message.SWITCH_THEME, $theme)
-    bus.status(`切换皮肤至「${$theme}」`)
+    bus.status('message.switchSkin',true, $theme)
     monaco.editor.setTheme($theme)
     Object.keys(themeStyle).forEach(key => themeStyle[key] = undefined)
     let theme = Themes[$theme]
@@ -76,6 +96,13 @@ const storeTheme = store.get(constants.STORE.theme)
 if(storeTheme !== constants.THEME && Themes[storeTheme]){
     switchTheme(storeTheme)
 }
+const switchLocale = ({ id, name }) => {
+    localeVisible.value = false
+    store.set('locale', id)
+    modal.confirm($i('message.tips'), $i('message.switchLocale', name), () => {
+        location.reload()
+    })
+}
 // switchTheme('dark')
 </script>
 <style scoped>
@@ -142,26 +169,32 @@ if(storeTheme !== constants.THEME && Themes[storeTheme]){
     white-space: nowrap;
 }
 
-.magic-skin-selector {
+.magic-skin-selector,.magic-locale-selector {
     position: absolute;
     top: 30px;
-    right: 30px;
+    right: 24px;
     z-index: 20;
     background-color: var(--main-background-color);
     border: 1px solid var(--main-border-color);
     border-top: none;
 }
-.magic-skin-selector ul li{
+.magic-skin-selector{
+    right: 48px;
+}
+.magic-skin-selector ul li,
+.magic-locale-selector ul li{
     height: 24px;
     line-height: 24px;
     text-align: center;
     cursor: pointer;
     padding: 2px 5px;
 }
-.magic-skin-selector ul li:not(:last-child) {
+.magic-skin-selector ul li:not(:last-child),
+.magic-locale-selector ul li:not(:last-child) {
     border-bottom: 1px solid var(--main-border-color);
 }
-.magic-skin-selector ul li:hover{
+.magic-skin-selector ul li:hover,
+.magic-locale-selector ul li:hover{
     background-color: var(--main-hover-background-color);
 }
 </style>

+ 12 - 11
src/components/panel/header/magic-push.vue

@@ -1,21 +1,21 @@
 <template>
-	<magic-dialog title="推送" v-model:value="show" :shade="false" padding="0" width="400px" top="60px" overflow="hidden">
+	<magic-dialog :title="$i('message.push')" v-model:value="show" :shade="false" padding="0" width="400px" top="60px" overflow="hidden">
 		<magic-resource-choose ref="resource" v-model:value="selected"/>
 		<div class="magic-push-form">
 			<div>
-				<label>远程地址:</label>
+				<label>{{ $i('message.remote') }}:</label>
 				<magic-input v-model:value="remote"/>
 			</div>
 			<div>
-				<label>秘钥:</label>
+				<label>{{ $i('message.secret') }}:</label>
 				<magic-input v-model:value="password" type="password"/>
 			</div>
 		</div>
 		<magic-button-group align="right" style="margin:5px 0; margin-right:5px;">
-			<magic-button value="全选" @onClick="$refs.resource.selectAll(true)"></magic-button>
-			<magic-button value="取消全选" @onClick="$refs.resource.selectAll(false)"></magic-button>
-			<magic-button type="active" value="增量推送" @onClick="doPush('increment')"></magic-button>
-			<magic-button value="全量推送" @onClick="doPush('full')"></magic-button>
+			<magic-button :value="$i('message.selectAll')" @onClick="$refs.resource.selectAll(true)"></magic-button>
+			<magic-button :value="$i('message.deselectAll')" @onClick="$refs.resource.selectAll(false)"></magic-button>
+			<magic-button :value="$i('push.increment')" @onClick="doPush('increment')" type="active"></magic-button>
+			<magic-button :value="$i('push.full')" @onClick="doPush('full')"></magic-button>
 		</magic-button-group>
 	</magic-dialog>
 </template>
@@ -23,6 +23,7 @@
 import bus from '../../../scripts/bus.js'
 import Message from '../../../scripts/constants/message.js'
 import modal from '../../common/dialog/magic-modal.js'
+import $i from '../../../scripts/i18n.js'
 import { ref } from 'vue'
 import request from '../../../scripts/request.js'
 const show = ref(false)
@@ -41,8 +42,8 @@ const _push = (mode)=> {
 		},
 		transformRequest: [],
 	}).success(() => {
-		const msg = mode === 'full' ? '全量推送' : '增量推送' 
-		bus.status(msg + '成功')
+		const msg = mode === 'full' ? $i('push.full') : $i('push.increment') 
+		bus.status('push.success', true, msg)
 		show.value = false
 		bus.$emit(Message.LOAD_RESOURCES)
 		bus.report(`resource-push`)
@@ -51,12 +52,12 @@ const _push = (mode)=> {
 const doPush = (mode) => {
 	if(selected.value.length){
 		if(mode === 'full'){
-			modal.confirm('远程推送', '全量模式推送时,以本地数据为准全量覆盖更新,是否继续?', () => _push(mode))
+			modal.confirm($i('message.push'), $i('message.pushWarning'), () => _push(mode))
 		} else {
 			_push(mode)
 		}
 	}else{
-		modal.alert('请选择之后在进行推送!')
+		modal.alert($i('message.pushNoneSelect'))
 	}
 }
 </script>

+ 5 - 4
src/components/panel/header/magic-search.vue

@@ -1,6 +1,6 @@
 <template>
-	<magic-dialog title="全局搜索" v-model:value="show" :shade="false" padding="0" width="700px" top="60px">
-		<magic-input v-model:value="keyword" placeholder="请输入关键字进行搜索" />
+	<magic-dialog :title="$i('message.search')" v-model:value="show" :shade="false" padding="0" width="700px" top="60px">
+		<magic-input v-model:value="keyword" :placeholder="$i('message.searchText')" />
 		<template v-if="results.length >0">
 			<div class="magic-search-result">
 				<div v-for="(item, key) in results" :key="key" class="magic-search-result-item" :class="{ selected: selectedItem === item}" @click="onClick(item)">
@@ -16,9 +16,10 @@
 </template>
 <script setup>
 import { computed, inject, ref, watch } from 'vue'
-import bus from '../../../scripts/bus'
+import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 import Message from '../../../scripts/constants/message.js'
-import request from '../../../scripts/request'
+import request from '../../../scripts/request.js'
 import { TokenizationRegistry } from 'monaco-editor/esm/vs/editor/common/modes.js'
 import { tokenizeToString } from 'monaco-editor/esm/vs/editor/common/modes/textToHtmlTokenizer.js'
 const keyword = ref('')

+ 10 - 9
src/components/panel/header/magic-upload.vue

@@ -1,17 +1,18 @@
 <template>
-	<magic-dialog title="上传" v-model:value="show">
+	<magic-dialog :title="$i('message.upload')" v-model:value="show">
 		<magic-file v-model:value="file" accept="application/x-zip-compressed"/>
 		<magic-button-group align="right" style="margin-top:5px">
-			<magic-button value="增量上传" type="active" @click="doUpload('increment')"/>
-			<magic-button value="全量上传" @click="doUpload('full')"/>
+			<magic-button :value="$i('upload.increment')" type="active" @click="doUpload('increment')"/>
+			<magic-button :value="$i('upload.full')" @click="doUpload('full')"/>
 		</magic-button-group>
 	</magic-dialog>
 </template>
 <script setup>
 import { ref } from 'vue'
-import bus from '../../../scripts/bus'
+import bus from '../../../scripts/bus.js'
 import Message from '../../../scripts/constants/message.js'
-import request from '../../../scripts/request'
+import request from '../../../scripts/request.js'
+import $i from '../../../scripts/i18n.js'
 import modal from '../../common/dialog/magic-modal.js'
 const show = ref(false)
 const file = ref(null)
@@ -27,20 +28,20 @@ const doUpload = mode => {
 					'Content-Type': 'multipart/form-data'
 				}
 			}).success(res => {
-				const msg = mode === 'full' ? '全量上传' : '增量上传' 
+				const msg = mode === 'full' ? $i('upload.full') : $i('upload.increment')
 				if(res){
-					bus.status(msg + '成功')
+					bus.status('upload.success', true, msg)
 					show.value = false
 					bus.$emit(Message.LOAD_RESOURCES)
 					bus.report(`upload-${mode}`)
 				} else {
-					bus.status(msg + '失败', false)
+					bus.status('upload.failed', false, msg)
 					bus.report(`upload-${mode}-error`)
 				}
 			})
 		}
 		if(mode === 'full'){
-			modal.confirm('上传', '全量模式上传时,以上传的数据为准进行覆盖更新操作,可能会删除其他接口<br>在非全量导出时,建议使用增量更新,是否继续?', _upload)
+			modal.confirm($i('message.upload'), $i('message.uploadWarning'), _upload)
 		} else {
 			_upload()
 		}

+ 0 - 8
src/components/panel/magic-cron-info.vue

@@ -1,8 +0,0 @@
-<template>
-    
-</template>
-<script setup>
-const props = defineProps({
-    info: Object
-})
-</script>

+ 0 - 59
src/components/panel/magic-function-info.vue

@@ -1,59 +0,0 @@
-<template>
-    <div class="magic-function-info">
-        <form>
-            <label>返回值</label>
-            <magic-select width="100px" :options="$FUNCTION_RETURN_TYPES" :default-select="$DEFAULT_FUNCTION_RETURN_TYPE" v-model:value="info.method"/>
-            <label>函数名称</label>
-            <magic-input v-model:value="info.name" placeholder="请输入函数名称" width="200px"/>
-            <label>函数路径</label>
-            <magic-input v-model:value="info.path" placeholder="请输入函数路径" width="auto" style="flex:1"/>
-        </form>
-    </div>
-    <magic-navbar direction="horizontal" ref="navbar" style="flex:1" :allow-close="false">
-        <magic-navbar-item v-for="(navbar, key) in navbars" :key="key" :title="navbar.title">
-            <component :is="navbar.component" :info="info"/>
-        </magic-navbar-item>
-    </magic-navbar>
-</template>
-<script setup>
-import { inject, resolveDynamicComponent } from 'vue'
-const info = inject('info')
-const navbars = [{
-    title: '函数参数',
-    component: resolveDynamicComponent('magic-function-parameter')
-},{
-    title: '函数描述',
-    component: resolveDynamicComponent('magic-api-description')
-}]
-</script>
-<style scoped>
-.magic-function-info{
-    display: flex;
-    flex-direction: column;
-}
-.magic-function-info form{
-    display: flex;
-    padding: 5px;
-}
-.magic-function-info form label{
-    display: inline-block;
-    width: 75px;
-    height: 22px;
-    line-height: 22px;
-    font-weight: 400;
-    text-align: right;
-    padding: 0 5px;
-}
-.magic-navbar{
-    flex-direction: column;
-    overflow: hidden;
-}
-.magic-navbar :deep(.magic-navbar-header){
-    border-bottom: 1px solid var(--main-border-color);
-}
-.magic-navbar :deep(.magic-navbar-body),
-.magic-navbar :deep(.magic-navbar-item){
-    width: 100%;
-    height: 100%;
-}
-</style>

+ 0 - 8
src/components/panel/magic-websocket-info.vue

@@ -1,8 +0,0 @@
-<template>
-    
-</template>
-<script setup>
-const props = defineProps({
-    info: Object
-})
-</script>

+ 37 - 30
src/components/panel/main/magic-data-resource.vue

@@ -8,26 +8,26 @@
 					</li>
 				</template>
 			</ul>
-			<magic-input v-model:value="keyword" placeholder="输入关键字搜索" width="auto"/>
+			<magic-input v-model:value="keyword" :placeholder="$i('message.searchText')" width="auto"/>
 			<magic-icon icon="search" size="14px"/>
 		</div>
-		<magic-empty v-if="datasources.length === 0" :text="`暂无${title}信息`"/>
+		<magic-empty v-if="datasources.length === 0" :text="$i('message.empty', title)"/>
 		<ul v-else>
 			<li v-for="(item, key) in datasources" :key="key" @contextmenu.prevent="e => onContextMenu(item, e)">
 				<magic-icon icon="datasource" />
-				<label>{{item.name || '主数据源'}}</label>
+				<label>{{item.name || $i('datasource.primary')}}</label>
 				<span>({{item.key || 'default'}})</span>
 				<magic-icon v-if="item.lock === '1'" icon="lock" />
 			</li>
 		</ul>
 	</div>
-	<magic-dialog v-model:value="dataResourceDialogVisible" :title="dataResourceDialogTitle" width="450px">
+	<magic-dialog v-model:value="dataResourceDialogVisible" :title="dataResourceDialogTitle" width="550px">
 		<magic-loading :loading="loading" style="min-height: 200px;">
 			<component :is="componentForm" :info="dataResourceObj"/>
 			<magic-button-group align="right" style="padding: 5px 0">
 				<magic-button :value="saveButtonText" type="active" @onClick="doSave()"/>
-				<magic-button value="测试连接" @onClick="doTest()"/>
-				<magic-button value="取消" @onClick="dataResourceDialogVisible = false"/>
+				<magic-button :value="$i('datasource.test')" @onClick="doTest()"/>
+				<magic-button :value="$i('message.cancel')" @onClick="dataResourceDialogVisible = false"/>
 			</magic-button-group>
 		</magic-loading>
 	</magic-dialog>
@@ -37,6 +37,7 @@ import { ref, resolveDynamicComponent, getCurrentInstance, computed, inject } fr
 import request from '../../../scripts/request.js'
 import bus from '../../../scripts/bus.js'
 import constants from '../../../scripts/constants.js'
+import $i from '../../../scripts/i18n.js'
 const props = defineProps({
 	/**
 	 * 类型,datasource、redis、mongo
@@ -62,11 +63,11 @@ const service = inject('service')[props.type]
 // 搜索条的按钮
 const buttons = [
 	{ 
-		name: `新建${props.title}`,
+		name: $i('message.createDataSource', props.title),
 		icon: 'plus', 
 		onClick:() => {
-			dataResourceDialogTitle.value = `创建${props.title}`
-			saveButtonText.value = '创建'
+			dataResourceDialogTitle.value = $i('message.createDataSource', props.title)
+			saveButtonText.value = $i('message.create')
 			dataResourceObj.value = {}
 			dataResourceDialogVisible.value = true
 			loading.value = false
@@ -77,7 +78,7 @@ const doTest = () => {
 	service.doTest(dataResourceObj.value)
 }
 const { proxy } = getCurrentInstance()
-const doSaveObj = (saveObj, msg) => {
+const doSaveObj = (saveObj, msgId, msg) => {
 	saveObj.groupId = `${props.type}:0`
 	request.sendJson(`/resource/file/${props.type}/save`, saveObj).success((id) => {
 		if(id) {
@@ -87,7 +88,7 @@ const doSaveObj = (saveObj, msg) => {
 				bus.report(`datasource-add`)
 			}
 			saveObj.id = id
-			bus.status(`${msg}成功`)
+			bus.status(msgId + 'Success',true, msg)
 			props.data[0].children = props.data[0].children || []
 			const target = props.data[0].children.find(it => it.id === saveObj.id)
 			if(target) {
@@ -97,14 +98,14 @@ const doSaveObj = (saveObj, msg) => {
 			}
 			dataResourceDialogVisible.value = false
 		}else{
-			bus.status(`${msg}失败`, false)
-			proxy.$alert(`${msg}失败`)
+			bus.status(msgId + 'Failed', false, msg)
+			proxy.$alert($i(msgId + 'Failed', msg))
 		}
 	})
 }
 const doSave = () => {
 	const saveObj = { ...dataResourceObj.value }
-	doSaveObj(saveObj, `保存${props.title}「${getFullPath(saveObj)}」`)
+	doSaveObj(saveObj, 'message.save',`${props.title}「${getFullPath(saveObj)}」`)
 }
 const getFullPath = item => `${item.name}(${item.key})`
 const deleteNode = item => {
@@ -115,28 +116,29 @@ const deleteNode = item => {
 }
 const onContextMenu = (item, event) => {
 	const menus = [{
-		label: `修改${props.title}`,
+		label: $i('message.updateTips', props.title),
 		icon: 'update',
 		divided: true,
 		onClick: () => {
 			loading.value = true
-			dataResourceDialogTitle.value = `修改${props.title}`
-			saveButtonText.value = '修改'
+			dataResourceDialogTitle.value = $i('message.updateTips', props.title)
+			saveButtonText.value = $i('message.update')
 			dataResourceDialogVisible.value = true
-			bus.status(`获取${props.title}「${getFullPath(item)}」详情`)
+			bus.status('message.getDetail', `${props.title}「${getFullPath(item)}」`)
 			request.sendGet(`/resource/file/${item.id}`).success(data => dataResourceObj.value = data).end(() => {
 				loading.value = false
 			})
 		}
 	},{
-		label: `删除${props.title}`,
+		label: $i('resource.contextmenu.delete'),
 		icon: 'delete',
 		onClick: () => {
-			proxy.$confirm(`删除${props.title}`, `是否要删除${props.title}「${getFullPath(item)}」`, () => {
+			const msg = `${props.title}「${getFullPath(item)}」`
+			proxy.$confirm($i('message.deleteTips', props.title), $i('message.deleteConfirm', msg), () => {
 				request.send('/resource/delete', { id: item.id }).success(ret => {
-					bus.status(`删除${props.title}「${getFullPath(item)}」${ret ? '成功': '失败'}`, ret)
+					bus.status(ret ? 'message.deleteSuccess': 'message.deleteFailed', ret, msg)
 					if(!ret) {
-						proxy.$alert(`删除${props.title}「${getFullPath(item)}」失败`)
+						proxy.$alert(ret ? 'message.deleteSuccess': 'message.deleteFailed', msg)
 					}else{
 						bus.report(`datasource-delete`)
 						deleteNode(item)
@@ -145,24 +147,24 @@ const onContextMenu = (item, event) => {
 			})
 		}
 	}, {
-		label: `复制${props.title}`,
+		label: $i('resource.contextmenu.copy', props.title),
 		icon: 'copy',
 		divided: true,
 		onClick: () => {
 			request.send(`/resource/file/${item.id}`).success(res => {
 				res.id = undefined
-				res.name = res.name + '(复制)'
+				res.name = res.name + `(${$i('message.copy')})`
 				res.key = res.key + '_copy'
-				doSaveObj(res, `复制${props.title}「${getFullPath(res)}」`)
+				doSaveObj(res, 'datasource.copy',`${props.title}「${getFullPath(res)}」`)
 			})
 		}
 	}]
 	if(item.lock === constants.LOCKED){
 		menus.push({
-			label: '解锁',
+			label: $i('resource.contextmenu.unlock'),
 			icon: 'unlock',
 			onClick: () => request.sendPost('/resource/unlock', { id: item.id}).success(ret => {
-				bus.status(`${props.title}「${getFullPath(item)}」解锁${ret ? '成功' : '失败'}`, ret)
+				bus.status(ret ? 'message.unlockSuccess': 'message.unlockFailed', ret, `${props.title}「${getFullPath(item)}」`)
 				if(ret) {
 					item.lock = constants.UNLOCK
 					bus.report(`resource-unlock`)
@@ -172,10 +174,10 @@ const onContextMenu = (item, event) => {
 		})
 	} else {
 		menus.push({
-			label: '锁定',
+			label: $i('resource.contextmenu.lock'),
 			icon: 'lock',
 			onClick: () => request.sendPost('/resource/lock', { id: item.id}).success(ret => {
-				bus.status(`${props.title}「${getFullPath(item)}」锁定${ret ? '成功' : '失败'}`, ret)
+				bus.status(ret ? 'message.lockSuccess': 'message.lockFailed', ret, `${props.title}「${getFullPath(item)}」`)
 				if(ret) {
 					item.lock = constants.LOCKED
 					bus.report(`resource-lock`)
@@ -264,9 +266,14 @@ const onContextMenu = (item, event) => {
 .magic-editor .magic-form-row label{
 	margin-right: 5px;
 	display: inline-block;
-	width: 60px;
+	width: 70px;
 	text-align: right;
 	height: 22px;
 	line-height: 22px;
 }
+.magic-editor .magic-form-row > input,
+.magic-editor .magic-form-row > .magic-select{
+	flex: 1;
+	width: auto;
+}
 </style>

+ 5 - 4
src/components/panel/main/magic-login.vue

@@ -1,21 +1,22 @@
 <template>
-    <magic-dialog title="登录" :showClose="false" v-model:value="value" >
+    <magic-dialog :title="$i('message.login')" :showClose="false" v-model:value="value" >
         <div class="magic-login">
-            <label>用户名:</label>
+            <label>{{ $i('message.username') }}:</label>
             <magic-input :onEnter="doLogin" v-model:value="username"/>
             <div style="height: 2px"/>
-            <label>密码:</label>
+            <label>{{ $i('message.password') }}:</label>
             <magic-input :onEnter="doLogin"  v-model:value="password" type="password"/>
             <div style="height: 2px"/>
         </div>
         <magic-button-group align="center">
-            <magic-button value="登录" @onClick="doLogin"/>
+            <magic-button :value="$i('message.login')" @onClick="doLogin"/>
         </magic-button-group>
     </magic-dialog>
 </template>
 <script setup>
 import { ref } from 'vue'
 import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 import constants from '../../../scripts/constants.js'
 import Message from '../../../scripts/constants/message.js'
 import request from '../../../scripts/request.js'

+ 11 - 14
src/components/panel/main/magic-main.vue

@@ -3,7 +3,7 @@
 		<!-- 左侧导航条 -->
 		<magic-navbar direction="vertical" :to="$refs.mrl" v-if="nextRender" ref="mnl" :spliter="true">
 			<magic-navbar-item v-for="(navbar, index) in leftNavbars" :key="index" v-bind="navbar">
-				<magic-resizer :max="600" :min="350" direction='x'>
+				<magic-resizer :max="600" :min="370" direction='x'>
 					<magic-loading :loading="loading">
 						<magic-resource v-bind="navbar" :data="resources[navbar.type]" @close="$refs.mnl.select(-1)" @onLoad="resourceOnLoad"/>
 					</magic-loading>
@@ -22,7 +22,7 @@
 		<!-- 右侧导航条 -->
 		<magic-navbar  :reverse="true" :default-select="-1" direction="vertical" :to="$refs.mrr" :spliter="true">
 			<magic-navbar-item v-for="(navbar, index) in rightNavbars" :key="index" :title="navbar.title" :icon="navbar.icon">
-				<magic-resizer :max="320" :min="220" direction='x' :reverse="true" v-if="nextRender">
+				<magic-resizer :max="320" :min="240" direction='x' :reverse="true" v-if="nextRender">
 					<magic-loading :loading="loading">
 						<magic-data-resource  :type="navbar.type" :title="navbar.name" :data="resources[navbar.type]" :component="navbar.component"/>
 					</magic-loading>
@@ -47,14 +47,13 @@ import request from '../../../scripts/request.js'
 import bus from '../../../scripts/bus.js'
 import MagicAPI from '../../../scripts/service/magic-api.js'
 import MagicFunction from '../../../scripts/service/magic-function.js'
-import MagicWebSocket from '../../../scripts/service/magic-websocket.js'
-import MagicResources from '../../../scripts/service/magic-resource.js'
 import MagicTask from '../../../scripts/service/magic-task.js'
 import MagicDatasource from '../../../scripts/service/magic-datasource.js'
 import Message from '../../../scripts/constants/message.js'
 import { replaceURL } from '../../../scripts/utils.js'
 import store from '../../../scripts/store.js'
 import constants from '../../../scripts/constants.js'
+import $i from '../../../scripts/i18n.js'
 const nextRender = ref(false)
 const resources = ref({})
 const loading = ref(true)
@@ -92,22 +91,20 @@ provide('findResource', id => {
 	}
 })
 const leftNavbars = [
-	{ type: 'api', title: '接口', icon: 'api'},
-	{ type: 'function', title: '函数', icon: 'function'},
-	{ type: 'task', title: '定时任务', icon: 'task'}
+	{ type: 'api', title: $i('api.name'), icon: 'api'},
+	{ type: 'function', title: $i('fn.name'), icon: 'function'},
+	{ type: 'task', title: $i('task.name'), icon: 'task'}
 ]
 const services = {
 	api: MagicAPI,
 	function: MagicFunction,
-	websocket: MagicWebSocket,
 	task: MagicTask,
-	resource: MagicResources,
 	datasource: MagicDatasource
 }
 provide('service', services)
 leftNavbars.map(it => it.type).forEach(key => resources.value[key] = [])
 const rightNavbars = [
-	{ type: 'datasource', title: 'Datasource', icon: 'datasource', name: '数据源'}
+	{ type: 'datasource', title: $i('datasource.title'), icon: 'datasource', name: $i('datasource.name')}
 ]
 rightNavbars.map(it => it.type).forEach(key => resources.value[key] = [])
 provide('resources', () => {
@@ -135,21 +132,21 @@ const processNode = item => {
 const loadAllResources = (type, callback) => {
 	loading.value = true
 	resources.value = {}
-	const name = type ? ((leftNavbars.find(it => it.type === type) || rightNavbars.find(it => it.type === type))?.title || '') : '全部'
-	bus.status(`获取${name}资源`)
+	const name = type ? ((leftNavbars.find(it => it.type === type) || rightNavbars.find(it => it.type === type))?.title || '') : $i('message.all')
+	bus.status(`message.getResource`, true, name)
 	request.send('/resource').success(data => {
 		[...leftNavbars, ...rightNavbars].filter(it => !type || it.type === type).forEach(it => {
 			resources.value[it.type] = data[it.type]?.children?.map(item => processNode(item)) || []
 		})
 		loading.value = false
-		bus.status(`获取${name}资源完毕`)
+		bus.status(`message.getResourceFinish`, true, name)
 		nextTick(() => callback())
 	})
 }
 bus.$on(Message.RELOAD_RESOURCES, () => {
 	loading.value = true
 	request.sendGet('/reload').success(() => {
-		bus.status('重新加载资源成功')
+		bus.status('message.reloadResourceSuccess')
 		loadAllResources(null, () => bus.$emit(Message.RELOAD_RESOURCES_FINISH))
 	}).end(() => loading.value = false)
 	

+ 2 - 1
src/components/panel/main/magic-recent-opened.vue

@@ -1,5 +1,5 @@
 <template>
-	<magic-dialog v-model:value="show" title="最近打开" padding="0" :shade="false">
+	<magic-dialog v-model:value="show" :title="$i('editor.tooltip.recent')" padding="0" :shade="false">
 		<ul class="magic-recent-opened">
 			<li v-for="(row, key) in data" :key="key" @click.stop="onClick(row.item.id)">
 				<magic-text-icon v-if="services[row.type] && services[row.type].getIcon" :icon="services[row.type].getIcon(row.item)"/>
@@ -15,6 +15,7 @@
 <script setup>
 import { computed, inject, reactive, ref, toRaw } from 'vue'
 import bus from '../../../scripts/bus.js'
+import $i from '../../../scripts/i18n.js'
 import constants from '../../../scripts/constants.js'
 import Message from '../../../scripts/constants/message.js'
 import store from '../../../scripts/store.js'

+ 76 - 64
src/components/panel/main/magic-resource.vue

@@ -2,7 +2,7 @@
 	<div class="magic-resource" @contextmenu.prevent="e => displayBlankContextMenu(e)">
 		<div class="magic-resource-header">
 			<magic-icon icon="search" size="14px"/>
-			<magic-input v-model:value="keyword" placeholder="输入关键字搜索" width="auto"/>
+			<magic-input v-model:value="keyword" :placeholder="$i('message.searchText')" width="auto"/>
 			<ul>
 				<template v-for="(button, index) in buttons" :key="index" >
 					<li v-if="!button.show || button.show()" :title="button.name || ''" @click="button.onClick&&button.onClick()" :class="{ separator: button.separator}">
@@ -11,7 +11,7 @@
 				</template>
 			</ul>
 		</div>
-		<magic-empty v-if="!data || data.length === 0" :text="`暂无${title}信息`"/>
+		<magic-empty v-if="!data || data.length === 0" :text="$i('message.empty', title)"/>
 		<!-- 树形菜单 -->
 		<magic-tree ref="treeObj" v-else :data="tree" @itemClick="onItemClick" @contextmenu="onContextMenu" :draggable="true" :sort="true" :descending="descending" :onMove="onMove" :filter="keyword" :filter-text="filterText" :selected="selectedItem">
 			<template v-slot:folder="{ item }">
@@ -29,24 +29,24 @@
 			</template>
 		</magic-tree>
 		<!-- 创建&修改对话框 -->
-		<magic-dialog :title="`${modeText}${title}分组`" v-model:value="showGroupDialog">
+		<magic-dialog :title="modeText" v-model:value="showGroupDialog" width="350px">
 			<!-- 表单项 -->
 			<ul class="magic-create-group">
-				<li><label>分组名称:</label><magic-input v-model:value="groupObj.name" :placeholder="`请输入${title}分组名称`"/></li>
-				<li v-if="requirePath"><label>分组路径:</label><magic-input v-model:value="groupObj.path" :placeholder="`请输入${title}分组路径`"/></li>
+				<li><label>{{ $i('resource.form.groupName') }}:</label><magic-input v-model:value="groupObj.name" :placeholder="$i('resource.form.placeholder.name', title)"/></li>
+				<li v-if="requirePath"><label>{{ $i('resource.form.groupPath') }}:</label><magic-input v-model:value="groupObj.path" :placeholder="$i('resource.form.placeholder.path', title)"/></li>
 			</ul>
 			<magic-button-group align="right" style="padding: 5px 0">
 				<magic-button :value="modeText" type="active" @onClick="saveGroup()"/>
-				<magic-button value="取消" @onClick="showGroupDialog = false"/>
+				<magic-button :value="$i('message.cancel')" @onClick="showGroupDialog = false"/>
 			</magic-button-group>
 		</magic-dialog>
 		<!-- 复制分组对话框 -->
-		<magic-dialog v-model:value="showCopyGroup" title="复制分组" :shade="false" padding="0" width="400px" overflow="hidden">
+		<magic-dialog v-model:value="showCopyGroup" :title="$i('resource.copyGroup')" :shade="false" padding="0" width="400px" overflow="hidden">
 			<magic-resource-choose ref="chooseGroup" v-model:value="chooseGroupItem" :file="false" :type="type" :single="true" />
 			<magic-button-group align="right" style="margin-right:5px;margin-bottom:5px;">
-				<magic-button value="展开" @onClick="$refs.chooseGroup.expand(true)"></magic-button>
-				<magic-button value="收缩" @onClick="$refs.chooseGroup.expand(false)"></magic-button>
-				<magic-button type="active" value="复制" @onClick="doCopyGroup"></magic-button>
+				<magic-button :value="$i('message.expand')" @onClick="$refs.chooseGroup.expand(true)"></magic-button>
+				<magic-button :value="$i('message.collapse')" @onClick="$refs.chooseGroup.expand(false)"></magic-button>
+				<magic-button type="active" :value="$i('message.copy')" @onClick="doCopyGroup"></magic-button>
 			</magic-button-group>
 		</magic-dialog>
 	</div>
@@ -56,6 +56,7 @@ import { ref, toRaw, computed, getCurrentInstance, reactive, inject, nextTick, o
 import bus from '../../../scripts/bus.js'
 import constants from '../../../scripts/constants.js'
 import request from '../../../scripts/request.js'
+import $i from '../../../scripts/i18n.js'
 import Message from '../../../scripts/constants/message.js'
 import { replaceURL, processTree, download, copyToClipboard } from '../../../scripts/utils.js'
 const props = defineProps({
@@ -83,7 +84,7 @@ const srcGroupId = ref('')
 // 选择分组对话框中的分组
 const chooseGroupItem = ref(null)
 const activateUserFiles = inject('activateUserFiles')
-const modeText = computed(() => mode.value ? '创建' : '修改')
+const modeText = computed(() => mode.value ? $i('resource.createGroup') : $i('resource.updateGroup'))
 // 排序
 const descending = ref(true)
 // 当前打开的
@@ -99,7 +100,7 @@ const emits = defineEmits(['close', 'onLoad'])
 // 搜索条的按钮
 const buttons = ref([
 	{ 
-		name: '新建分组',
+		name: $i('resource.createGroup'),
 		icon: 'group-add', 
 		onClick:() => {
 			groupObj.value = {
@@ -110,18 +111,18 @@ const buttons = ref([
 			showGroupDialog.value = true
 		} 
 	},
-	{ name: '全部展开', icon: 'expand-all', onClick: () => processTree(tree.value, it => it.opened = true) },
-	{ name: '全部折叠', icon: 'collapse-all', onClick: () => processTree(tree.value, it => it.opened = false) },
-	{ name: '按字母降序', icon: 'descending',  show: () => descending.value, onClick: () => descending.value = false },
-	{ name: '按字母升序', icon: 'ascending',  show: () => !descending.value, onClick: () => descending.value = true },
+	{ name: $i('resource.header.expand'), icon: 'expand-all', onClick: () => processTree(tree.value, it => it.opened = true) },
+	{ name: $i('resource.header.collapse'), icon: 'collapse-all', onClick: () => processTree(tree.value, it => it.opened = false) },
+	{ name: $i('resource.header.desc'), icon: 'descending',  show: () => descending.value, onClick: () => descending.value = false },
+	{ name: $i('resource.header.asc'), icon: 'ascending',  show: () => !descending.value, onClick: () => descending.value = true },
 	{ separator: true },
-	{ name: '定位当前文件', icon: 'position', onClick: () => {
+	{ name: $i('resource.header.position'), icon: 'position', onClick: () => {
 		if(treeObj.value && selectedItem.value){
 			bus.$emit(Message.SELECT_NAVBAR_BY_ITEM, selectedItem.value)
 			treeObj.value.scrollIntoView(selectedItem.value)
 		}
 	} },
-	{ name: '隐藏', icon: 'minimize', onClick:() => emits('close')},
+	{ name: $i('message.hide'), icon: 'minimize', onClick:() => emits('close')},
 	
 ])
 const deepFind = (itemOrId, array, nameStack, pathStack, folderStack) => {
@@ -197,28 +198,39 @@ const saveGroup = () => {
 	delete group.opened
 	delete group.folder
 	request.sendJson('/resource/folder/save', group).success(id => {
-		const msg = `保存${props.title}分组`
+		const msg = `resource.saveGroup`
+		const path = getFullPath(group)
 		if(id){
-			console.log(tree.value)
 			updateNode({...toRaw(groupObj.value), folder: true, id})
-			bus.status(`${msg}「${getFullPath(group)}」成功`)
+			bus.status('resource.saveGroupSuccess', true, props.title, path)
 			showGroupDialog.value = false
 			bus.report(`group-save`)
 		}else{
-			bus.status('${msg}失败', false)
-			proxy.$alert('${msg}失败', `保存${props.title}分组`)
+			bus.status('resource.saveGroupFailed', false, props.title, path)
+			proxy.$alert($i('resource.saveGroupFailed', props.title, path))
 		}
 		
 	})
 }
 const onMove = (src, target) => new Promise(reslove => request.send('/resource/move', { src: src.id, groupId: target.groupId || target.id }).success(r => {
-	const msg = `移动${src.folder ? `${props.title}分组`: ''}「${getFullPath(src)}」`
+	const msgId = src.folder ? 'resource.moveGroup' : 'resource.moveResource'
+	const path = getFullPath(src)
 	if(r){
+		if(src.folder){
+			bus.status(msgId + 'Success', true, props.title, path)
+		} else {
+			bus.status(msgId + 'Success', true, path)
+		}
 		src[src.folder ? 'parentId' : 'groupId'] = target.groupId || target.id
-		bus.status(`${msg}成功`)
+		
 	}else{
-		proxy.$alert(`${msg}失败`)
-	   bus.status(`${msg}失败`, false)
+		if(src.folder){
+			bus.status(msgId + 'Failed', false, props.title, path)
+			proxy.$alert($i(msgId + 'Failed', props.title, path))
+		} else {
+			bus.status(msgId + 'Failed', false, path)
+			proxy.$alert($i(msgId + 'Failed', path))
+		}
 	}
 	reslove(r)
 }))
@@ -271,12 +283,12 @@ const onContextMenu = (item, event) => {
 		const menus = []
 		if(item.folder) {
 			menus.push.apply(menus, [{
-				label: `新建${props.title}`,
+				label: $i('resource.contextmenu.newFile', props.title),
 				icon: 'plus',
 				onClick(){
 					const info = {
 						groupId: item.id,
-						name: '未定义名称',
+						name: $i('message.untitled'),
 						script: 'hello',
 						path: config.requirePath ? '' : undefined
 					}
@@ -284,7 +296,7 @@ const onContextMenu = (item, event) => {
 					onItemClick(info)
 				}
 			},{
-				label: `新建分组`,
+				label: $i('resource.createGroup'),
 				icon: 'group-add',
 				onClick(){
 					mode.value = true
@@ -295,7 +307,7 @@ const onContextMenu = (item, event) => {
 					showGroupDialog.value = true
 				}
 			},{
-				label: `修改分组`,
+				label: $i('resource.updateGroup'),
 				icon: 'update',
 				onClick(){
 					mode.value = false
@@ -305,25 +317,25 @@ const onContextMenu = (item, event) => {
 					showGroupDialog.value = true
 				}
 			},{
-				label: `复制分组`,
+				label: $i('resource.copyGroup'),
 				icon: 'copy',
 				onClick(){
 					srcGroupId.value = item.id
 					showCopyGroup.value = true
 				}
 			},{
-				label: `删除分组`,
+				label: $i('resource.contextmenu.deleteGroup'),
 				icon: 'delete',
 				onClick() {
-					proxy.$confirm(`删除分组`, `是否要删除分组「${getFullPath(item)}」`, ()=> {
+					proxy.$confirm($i('resource.contextmenu.deleteGroup'), $i('resource.deleteGroupConfirm', props.title, getFullPath(item)), ()=> {
 						if(item.id) {
 							request.send('/resource/delete', { id: item.id }).success(ret => {
 								if(!ret) {
-									proxy.$alert(`删除${props.title}分组「${getFullPath(item)}」失败`)
-									bus.status(`删除${props.title}分组「${getFullPath(item)}」失败`, false)
+									proxy.$alert('resource.deleteGroupFailed', props.title, getFullPath(item))
+									bus.status('resource.deleteGroupFailed', false, props.title, getFullPath(item))
 									bus.report(`group-delete`)
 								}else{
-									bus.status(`删除${props.title}分组「${getFullPath(item)}」成功`)
+									bus.status('resource.deleteGroupSuccess', true, props.title, getFullPath(item))
 									deleteNode(item)
 								}
 							})
@@ -333,28 +345,28 @@ const onContextMenu = (item, event) => {
 					})
 				}
 			},{
-				label: `导出`,
+				label: $i('resource.contextmenu.exportGroup'),
 				icon: 'download',
 				onClick() {
 					request.send(`/download?groupId=${item.id}`, null, { headers: { 'Content-Type': 'application/json' }, responseType: 'blob' }).success(blob => {
 						download(blob, `${item.name}.zip`)
-						bus.$emit('status', `分组「${item.name}」相关${props.title}已导出`)
+						bus.status('resource.groupExport', true, item.name, props.title)
 						bus.report(`group-export`)
 					})
 				}
 			}])
 			if(item.parentId !== '0'){
 				menus.push({
-					label: `移动到根节点`,
+					label: $i('resource.contextmenu.moveToRoot'),
 					icon: 'move',
 					onClick() {
-						proxy.$confirm(`移动分组`, `是否要将分组「${getFullPath(item)}」移动至根节点`, ()=> {
+						proxy.$confirm($i('resource.moveGroup'), $i('resource.moveRootGroupConfirm', getFullPath(item)), ()=> {
 							request.send('/resource/move', { src: item.id, groupId: '0' }).success(ret => {
 								if(!ret) {
-									proxy.$alert(`移动${props.title}分组「${getFullPath(item)}」至根节点失败`)
-									bus.status(`移动${props.title}分组「${getFullPath(item)}」至根节点失败`, false)
+									proxy.$alert($i('resource.moveRootFailed', props.title, getFullPath(item)))
+									bus.status('resource.moveRootFailed', false, props.title, getFullPath(item))
 								}else{
-									bus.status(`移动${props.title}分组「${getFullPath(item)}」至根节点成功`)
+									bus.status('resource.moveRootSuccess', true, props.title, getFullPath(item))
 									item.parentId = '0'
 									deleteNode(item)
 									updateNode(item)
@@ -367,13 +379,13 @@ const onContextMenu = (item, event) => {
 			}
 		} else {
 			menus.push.apply(menus, [{
-				label: `复制${props.title}`,
+				label: $i('resource.contextmenu.copy', props.title),
 				icon: 'copy',
 				divided: true,
 				onClick: () => {
 					request.send(`/resource/file/${item.id}`).success(res => {
 						res.id = `copy${parseInt(Math.random() * 10000000000)}`
-						res.name = res.name + '(复制)'
+						res.name = res.name + `(${$i('message.copy')})`
 						if(config.requirePath){
 							res.path = res.path + '_copy'
 						}
@@ -385,42 +397,42 @@ const onContextMenu = (item, event) => {
 			if(config.requirePath){
 				if(props.type === 'api'){
 					menus.push({
-						label: `复制路径`,
+						label: $i('resource.contextmenu.copyWithPath'),
 						icon: 'copy',
 						onClick: () => {
 							let path = getFullPath(item, true)
 							if(path){
 								path = replaceURL(constants.SERVER_URL + '/' + path)
 								if(copyToClipboard(path)){
-									bus.status(`${props.title}路径「${path}」复制成功`)
+									bus.status('resource.copyPathSuccess', true, props.title, path)
 								}else{
-									bus.status(`${props.title}路径「${path}」复制失败,请手动复制`)
+									bus.status('resource.copyPathFailed', false, props.title, path)
 								}
 							}
 						}
 					})
 				}
 				menus.push.apply(menus, [{
-					label: `复制相对路径`,
+					label: $i('resource.contextmenu.copyRelativePath'),
 					icon: 'copy',
 					divided: true,
 					onClick: () => {
 						const path = getFullPath(item, true)
 						if(path){
 							if(copyToClipboard(path)){
-								bus.status(`${props.title}相对路径「${path}」复制成功`)
+								bus.status('resource.copyRelativePathSuccess', true, props.title, path)
 							}else{
-								bus.status(`${props.title}相对路径「${path}」复制失败,请手动复制`)
+								bus.status('resource.copyRelativePathFailed', false, props.title, path)
 							}
 						}
 					}
 				}])
 				if(item.lock === constants.LOCKED){
 					menus.push({
-						label: '解锁',
+						label: $i('resource.contextmenu.unlock'),
 						icon: 'unlock',
 						onClick: () => request.sendPost('/resource/unlock', { id: item.id}).success(ret => {
-							bus.status(`${props.title}「${getFullPath(item)}」解锁${ret ? '成功' : '失败'}`, ret)
+							bus.status(ret ? 'message.unlockSuccess' : 'message.unlockFailed', ret, getFullPath(item))
 							if(ret) {
 								item.lock = constants.UNLOCK
 								bus.report(`resource-unlock`)
@@ -429,10 +441,10 @@ const onContextMenu = (item, event) => {
 					})
 				} else {
 					menus.push({
-						label: '锁定',
+						label: $i('resource.contextmenu.lock'),
 						icon: 'lock',
 						onClick: () => request.sendPost('/resource/lock', { id: item.id}).success(ret => {
-							bus.status(`${props.title}「${getFullPath(item)}」锁定${ret ? '成功' : '失败'}`, ret)
+							bus.status(ret ? 'message.lockSuccess' : 'message.lockFailed', ret, getFullPath(item))
 							if(ret) {
 								item.lock = constants.LOCKED
 								bus.report(`resource-lock`)
@@ -442,28 +454,28 @@ const onContextMenu = (item, event) => {
 				}
 			}
 			menus.push.apply(menus, [{
-				label: `刷新`,
+				label: $i('message.refresh'),
 				icon: 'refresh'
 			},{
-				label: `删除`,
+				label: $i('resource.contextmenu.delete'),
 				icon: 'delete',
 				onClick: () => {
-					const msg = `删除${props.title}「${getFullPath(item)}」`
-					proxy.$confirm(`删除${props.title}`, `是否要删除${props.title}「${getFullPath(item)}」`, ()=> {
+					const msg = `${props.title}「${getFullPath(item)}」`
+					proxy.$confirm($i('message.deleteTips', props.title), $i('message.deleteConfirm', msg), ()=> {
 						if(item.id && !item.id.startsWith('copy')){
 							request.send('/resource/delete', { id: item.id }).success(ret => {
 								if(!ret) {
-									bus.status(`${msg}失败`, false)
-									proxy.$alert(`${msg}失败`)
+									bus.status('message.deleteFailed', false, msg)
+									proxy.$alert($i('message.deleteFailed', msg))
 								}else{
-									bus.status(`${msg}成功`)
+									bus.status('message.deleteSuccess', true, msg)
 									deleteNode(item)
 									bus.$emit(Message.DELETE_FILE, item)
 									bus.report(`resource-delete`)
 								}
 							})
 						}else{
-							bus.status(`${msg}成功`)
+							bus.status('message.deleteSuccess', true, msg)
 							deleteNode(item)
 							bus.$emit(Message.DELETE_FILE, item)
 						}

+ 19 - 18
src/components/panel/main/magic-script-editor.vue

@@ -3,13 +3,13 @@
 		<div class="magic-empty-container" v-if="openedScripts.length === 0">
 			<div class="magic-hot-key">
 				<p>
-				保存<em>Ctrl + S</em><br/>
-				测试<em>Ctrl + Q</em><br/>
-				代码提示<em>Alt + /</em><br/>
-				恢复断点<em>F8</em><br/>
-				步进<em>F6</em><br/>
-				代码格式化<em>Ctrl + Alt + L</em><br/>
-				最近打开<em>Ctrl + E</em>
+				{{ $i('message.save') }}<em>Ctrl + S</em><br/>
+				{{ $i('message.run') }}<em>Ctrl + Q</em><br/>
+				{{ $i('editor.tooltip.complection')}}<em>Alt + /</em><br/>
+				{{ $i('editor.tooltip.resume')}}<em>F8</em><br/>
+				{{ $i('editor.tooltip.stepInto')}}<em>F6</em><br/>
+				{{ $i('editor.tooltip.format')}}<em>Ctrl + Alt + L</em><br/>
+				{{ $i('editor.tooltip.recent')}}<em>Ctrl + E</em>
 				</p>
 			</div>
 		</div>
@@ -39,6 +39,7 @@ import { convertVariables } from '../../../scripts/utils.js'
 import RequestParameter from '../../../scripts/editor/request-parameter.js'
 import Socket from '../../../scripts/constants/socket.js'
 import store from '../../../scripts/store.js'
+import $i from '../../../scripts/i18n.js'
 const { proxy } = getCurrentInstance()
 const openedScripts = reactive([])
 const selectTab = ref({})
@@ -101,8 +102,9 @@ const doSave = (flag) => {
 		const item = selectTab.value.processSave(opened.item)
 		Object.keys(item).forEach(key => opened.item[key] = item[key])
 		return request.sendJson(`/resource/file/${selectTab.value.type}/save?auto=${flag ? 0 : 1}`, item).success((id) => {
+			const msg = `${opened.title}「${opened.path()}」`
 			if(id) {
-				bus.status(`保存${opened.title}「${opened.path()}」成功`)
+				bus.status('message.saveSuccess', true, msg)
 				opened.tmpObject = JSON.parse(JSON.stringify(item))
 				if(opened.item.id !== id){
 					bus.report(`script-add`)
@@ -111,8 +113,8 @@ const doSave = (flag) => {
 				}
 				opened.item.id = id
 			}else{
-				bus.status(`保存${opened.title}「${opened.path()}」失败`, false)
-				proxy.$alert(`保存${opened.title}「${opened.path()}」失败`)
+				bus.status('message.saveFailed', false, msg)
+				proxy.$alert($i('message.saveFailed', msg))
 			}
 		})
 	}
@@ -131,30 +133,30 @@ const doContinue = step => {
 // tab 右键菜单事件
 const onContextMenu = (event, item, index) => {
 	const menus = [{
-		label: '关闭',
+		label: $i('editor.tab.close'),
 		divided: true,
 		onClick(){
 			onClose(item)
 		}
 	},{
-		label: '关闭其他',
+		label: $i('editor.tab.closeOther'),
 		divided: true,
 		onClick(){
 			[...openedScripts].forEach((it, i) => i != index && onClose(it))
 		}
 	},{
-		label: '关闭左侧',
+		label: $i('editor.tab.closeLeft'),
 		onClick(){
 			[...openedScripts].forEach((it, i) => i < index && onClose(it))
 		}
 	},{
-		label: '关闭右侧',
+		label: $i('editor.tab.closeRight'),
 		divided: true,
 		onClick(){
 			[...openedScripts].forEach((it, i) => i > index && onClose(it))
 		}
 	},{
-		label: '全部关闭',
+		label: $i('editor.tab.closeAll'),
 		onClick(){
 			[...openedScripts].forEach(it => onClose(it))
 		}
@@ -182,7 +184,7 @@ bus.$on(Message.DELETE_FILE, item => {
 // 重新加载资源完毕时
 bus.$on(Message.RELOAD_RESOURCES_FINISH, ()=> {
 	openedScripts.filter(opened => opened.item && opened.item.id).forEach(opened => request.sendGet(`/resource/file/${opened.item.id}`).success(data => {
-		bus.status(`获取${opened.title}「${opened.path()}」详情成功`)
+		bus.status('message.getDetailSuccess', true, `${opened.title}「${opened.path()}」`)
 		Object.keys(data).forEach(key => opened.item[key] = data[key])
 	}))
 })
@@ -205,7 +207,7 @@ bus.$on(Message.OPEN, opened => {
 		if(opened.item.id && !opened.item.script){
 			loading.value = true
 			request.sendGet(`/resource/file/${opened.item.id}`).success(data => {
-				bus.status(`获取${opened.title}「${opened.path()}」详情成功`)
+				bus.status('message.getDetail', true, `${opened.title}「${opened.path()}」`)
 				Object.keys(data).forEach(key => opened.item[key] = data[key])
 				opened.tmpObject = JSON.parse(JSON.stringify(opened.processSave(data)))
 				loading.value = false
@@ -240,7 +242,6 @@ bus.$on(Message.DO_TEST, () => {
 })
 // 进入断点
 bus.$event(Socket.BREAKPOINT, ([ scriptId, { range, variables } ]) => {
-	bus.status('进入断点..')
     // 如果切换或已关闭
     if(selectTab.value?.item?.id !== scriptId){
         const opened = openedScripts.find(it => it.item.id === scriptId)

+ 8 - 7
src/components/panel/task/magic-task-info.vue

@@ -1,22 +1,23 @@
 <template>
 	<div class="magic-task-info">
 		<form>
-			<label>是否启用</label>
+			<label>{{ $i('message.enable') }}</label>
 			<magic-checkbox v-model:value="info.enabled" />
 			<label>cron</label>
-			<magic-input v-model:value="info.cron" placeholder="请输入cron表达式" width="150px"/>
-			<label>任务名称</label>
-			<magic-input v-model:value="info.name" placeholder="请输入任务名称" width="200px"/>
-			<label>任务路径</label>
-			<magic-input v-model:value="info.path" placeholder="请输入任务路径" width="auto" style="flex:1"/>
+			<magic-input v-model:value="info.cron" :placeholder="$i('task.form.placeholder.cron')" width="250px"/>
+			<label>{{ $i('task.form.name') }}</label>
+			<magic-input v-model:value="info.name" :placeholder="$i('task.form.placeholder.name')" width="250px"/>
+			<label>{{ $i('task.form.path') }}</label>
+			<magic-input v-model:value="info.path" :placeholder="$i('task.form.placeholder.path')" width="auto" style="flex:1"/>
 		</form>
 		<div style="flex:1;padding-top:5px;">
-			<magic-textarea v-model:value="info.description" placeholder="任务描述"/>
+			<magic-textarea v-model:value="info.description" :placeholder="$i('task.form.placeholder.description')"/>
 		</div>
 	</div>
 </template>
 <script setup>
 import { inject } from 'vue'
+import $i from '../../../scripts/i18n.js'
 const info = inject('info')
 </script>
 <style scoped>

+ 1 - 2
src/magic-editor.js

@@ -1,4 +1,4 @@
-import { createApp, defineAsyncComponent, defineCustomElement } from 'vue'
+import { createApp, defineAsyncComponent } from 'vue'
 import MagicContextMenu from './components/common/magic-context-menu.vue'
 import { install as installModal } from './components/common/dialog/magic-modal.js'
 import constants from './scripts/constants.js'
@@ -7,7 +7,6 @@ Object.entries(import.meta.glob('./components/**')).forEach(([key, value]) => co
 const registerComponent = (app) => {
     let instance;
     Object.entries(components).forEach(([name, component]) => {
-        // customElements.define(name, defineCustomElement(component))
         app.component(name, component)
     })
     app.config.globalProperties.$contextmenu = options => {

+ 4 - 2
src/scripts/bus.js

@@ -1,6 +1,7 @@
 import { formatDate, paddingZero } from './utils.js'
 import { ref } from 'vue'
-import constants from './constants.js';
+import constants from './constants.js'
+import $i from './i18n.js'
 class Bus {
 	constructor(){
 		this.listeners = {}
@@ -48,8 +49,9 @@ class Bus {
 	send(msgType, content){
 		this.$emit('message', msgType, content)
 	}
-	status(content, flag){
+	status(content, flag, ...args){
 		const date = new Date()
+		content = $i(content, ...args) || content
 		if(flag === false){
 			content = `<font color="red">${content}</font>`
 		}

+ 7 - 6
src/scripts/constants.js

@@ -1,4 +1,5 @@
 import config from '../../package.json'
+import $i from './i18n.js'
 let MAGIC_API_VERSION_TEXT = config.version
 let MAGIC_API_VERSION = 'V' + MAGIC_API_VERSION_TEXT.replace(/\./g, '_')
 const constants = {
@@ -60,9 +61,9 @@ const constants = {
 		EDITOR_FONT_FAMILY: 'JetBrainsMono, Consolas, "Courier New",monospace, 微软雅黑',
 		EDITOR_FONT_SIZE: 14,
 		VALIDATE_TYPES: [
-			{ value: 'pass', text: '不验证'},
-			{ value: 'expression', text: '表达式验证'},
-			{ value: 'pattern', text: '正则验证'}
+			{ value: 'pass', text: $i('message.noValidate')},
+			{ value: 'expression', text: $i('message.validateExpression')},
+			{ value: 'pattern', text: $i('message.validatePattern')}
 		],
 		DEFAULT_VALIDATE_TYPE: 'pass',
 		REQUEST_PARAMETER_TYPES: [
@@ -113,9 +114,9 @@ const constants = {
 		],
 		DEFAULT_REQUEST_METHOD: 'GET',
 		FUNCTION_RETURN_TYPES: [
-			{value: 'java.lang.Number', text: '数值'},
-			{value: 'java.lang.String', text: '字符串'},
-			{value: 'java.util.Collection', text: '集合'},
+			{value: 'java.lang.Number', text: $i('fn.number')},
+			{value: 'java.lang.String', text: $i('fn.string')},
+			{value: 'java.util.Collection', text: $i('fn.collection')},
 			{value: 'java.util.Map', text: 'Map'},
 			{value: 'java.lang.Object', text: 'Object'}
 		],

+ 0 - 1
src/scripts/constants/socket.js

@@ -9,7 +9,6 @@ export default {
     EXCEPTION: 'exception',
     USER_LOGIN: 'user_login',
     USER_LOGOUT: 'user_logout',
-    GET_ONLINE: 'get_online',
     ONLINE_USERS: 'online_users',
     SET_FILE_ID: 'set_file_id',
     INTO_FILE_ID: 'into_file_id'

+ 14 - 0
src/scripts/i18n.js

@@ -0,0 +1,14 @@
+import store from './store.js'
+const locale = (await import(`./i18n/${store.get('locale') || 'zh-cn'}.js`)).default
+
+export default function(key, ...args){
+    try{
+        const msg = key.split('.').reduce((val, k) => val[k], locale);
+        if(msg && args.length > 0){
+            return msg.replace(/\{(\d+)\}/g, (match, index) => args[index])
+        }
+        return msg || key;
+    } catch(e){
+        return key
+    }
+}

+ 312 - 0
src/scripts/i18n/en.js

@@ -0,0 +1,312 @@
+export default {
+    name: 'English',
+    message: {
+        run: 'Run',
+        save: 'Save',
+        search: 'Search',
+        upload: 'Upload',
+        export: 'Export',
+        push: 'Push',
+        skin: 'Skin',
+        reload: 'reload all resources',
+        copy: 'Copy',
+        searchText: 'Enter keywords to search',
+        required: 'Required',
+        defaultValue: 'Default Value',
+        description: 'Description',
+        parameterType: 'Parameter Type',
+        view: 'View',
+        addRow: 'Add Row',
+        removeRow: 'Remove Row',
+        all: 'All',
+        clear: 'Clear',
+        empty: '{0} is empty.',
+        type: 'Type',
+        date: 'Date',
+        name: 'Name',
+        group: '{0} Group',
+        i18n: 'Language',
+        tips: 'Tips',
+        ok: 'OK',
+        refresh: 'Refresh',
+        loading: 'Loading...',
+        nodata: 'no data.',
+        cancel: 'Cancel',
+        update: 'Save',
+        create: 'Create',
+        username: 'Username',
+        password: 'Password',
+        createDataSource: 'Create {0}',
+        chooseFile: 'Please Choose File',
+        expand: 'Expand',
+        collapse: 'Collapse',
+        selectAll: 'Select All',
+        deselectAll: 'Deselect All',
+        hide: 'Hide',
+        login: 'Login',
+        ignore: 'Ignore',
+        document: 'Document',
+        joinGroup: 'Join QQ Group',
+        untitled: 'Untitled',
+        log: 'Log',
+        enable: 'Enable',
+        variable: 'Variable Info',
+
+        switchLocale: 'Switch Language To {0}, It work at after refreshing the page, Do you want to Refresh the page ?',
+        loadClass: 'Load Classes...',
+        loadClassError: 'Failed Load Classes',
+        switchSkin: 'Switch Skin To「{0}」',
+        loadClassFinish: 'Class Loaded',
+        tryAutoLogin: 'Try Auto Login',
+        autoLoginSuccess: 'Auto Login Success',
+        getCurrentLoginUser: 'Load Current Logined User',
+        getResource: 'Load {0} Resources',
+        getResourceFinish: '{0} Resources Loaded',
+        connectDebugServer: 'Debug Server Connecting...',
+        debugServerClose: 'Debug Server Disconnected',
+        connectDebugServerSuccess: 'Connect Debug Server Success',
+        reloadResourceSuccess: 'Resource Reload Success',
+
+        getDetail: 'Load {0} Detail',
+        getDetailSuccess: 'Load {0} Detail Success',
+
+        lockSuccess: 'Lock {0} Success',
+        lockFailed: 'Failed to Lock {0}',
+        unlockSuccess: 'UnLock {0} Success',
+        unlockFailed: 'Failed to UnLock {0}',
+        updateTips: 'Update {0}',
+        saveSuccess: 'Save {0} Success',
+        saveFailed: 'Failed to Save {0}',
+
+        newVersionRelease: 'New Version {0} available',
+        versionLastest: 'Current Version is Lastese',
+        versionUpdate: 'New Version {0} available, Upgrade?',
+        changelog: 'CHANGELOG',
+        versionConflict: 'Version does not matched frontend: {0}, backend: {1}, Please Check!',
+        versionCheck: 'Version Check',
+
+        logout: 'Logout',
+        logoutSuccess: 'Logout Success',
+        logoutConfirm: 'Are you sure Logout {0} ?',
+        deleteConfirm: 'Do you want Delete {0}',
+        deleteSuccess: 'Delete {0} Success',
+        deleteFailed: 'Failed to Delete {0}',
+        deleteTips: 'Delete {0}',
+
+        remote: 'Remote',
+        secret: 'Secret',
+        exported: 'The selected resource has been exported',
+        exportNoneSelect: 'Please select and then export',
+        pushNoneSelect: 'Please select and then push',
+
+        responseBody: 'Body',
+        responseHeader: 'Header',
+        responseStructure: 'Structure',
+        root: 'Root',
+        pushWarning: 'When the full mode is pushed, the local data shall prevail and the full coverage update will be carried out. Do you want to continue?',
+        uploadWarning: 'When uploading in full mode, the overwrite update operation is performed based on the uploaded data, and other interfaces may be deleted.<br>In the case of partial export, it is recommended to use incremental update. Do you want to continue?',
+
+        noValidate: 'No Validate',
+        validatePattern: 'Regex attern',
+        validateExpression: 'Expression'
+    },
+    resource: {
+        createGroup: 'Create Group',
+        updateGroup: 'Update Group',
+        copyGroup: 'Copy Group',
+        deleteGroupConfirm: 'Do you want Delete {0} Group「{1}」?',
+        deleteGroupSuccess: 'Delete {0} Group「{1}」Success',
+        deleteGroupFailed: 'Failed to Delete {0} Group 「{1}」',
+        groupExport: 'Group「{0}」\'s {1} Exported',
+        moveGroup: 'Move Group',
+        moveRootGroupConfirm: 'Do you want move Group 「{0}」into root?',
+        moveRootSuccess: 'Move {0} Group 「{1}」into root Success',
+        moveRootFailed: 'Failed to Move {0} 分组「{1}」 into root',
+        moveGroupSuccess: 'Move {0} Group 「{1}」 Success',
+        moveGroupFailed: 'Failed to Move {0} Group 「{1}」',
+        moveFileSuccess: 'Move {0} Success',
+        moveResourceFailed: 'Failed to Move {0}',
+        saveGroupSuccess: 'Save {0} Group {1}」 Success',
+        saveGroupFailed: 'Failed to Save {0} Group 「{1}」',
+        copyPathSuccess: 'Copy {0} Path {1}」 Success',
+        copyPathFailed: 'Failed to Copy {0} Path 「{1}」',
+        copyRelativePathSuccess: 'Copy {0} Relative Path 「{1}」 Success',
+        copyRelativePathFailed: 'Failed to Copy {0} Relative Path 「{1}」',
+        contextmenu: {
+            copy: 'Copy {0}',
+            copyWithPath: 'Copy Absolute Path',
+            copyRelativePath: 'Copy Relative Path',
+            lock: 'Lock',
+            unlock: 'UnLock',
+            delete: 'Delete',
+            newFile: 'New {0}',
+            deleteGroup: 'Delete Group',
+            exportGroup: 'Export Group',
+            moveToRoot: 'Move To Root'
+        },
+        header: {
+            expand: 'Expand All',
+            collapse: 'Collapse All',
+            asc: 'Ascending',
+            desc: 'Descending',
+            position: 'Select Opened File'
+        },
+        form: {
+            groupName: 'Group Name',
+            groupPath: 'Group Path',
+            placeholder: {
+                name: 'Please Enter {0} Group Name',
+                path: 'Please Enter {0} Group Path'
+            }
+        }
+    },
+    editor: {
+        tab: {
+            close: 'Close',
+            closeOther: 'Close Other Tabs',
+            closeLeft: 'Close Tabs to the Left',
+            closeRight: 'Close Tabs to the Right',
+            closeAll: 'Close All Tabs',
+        },
+        tooltip: {
+            complection: 'Trigger Suggest',
+            resume: 'Resume Breakpoint',
+            stepInto: 'Step Into',
+            format: 'Reformat Code',
+            recent: 'Recent Opened Files'
+        },
+        triggerSuggest: 'Trigger Suggest'
+    },
+    api: {
+        title: 'Api Info',
+        name: 'Api',
+        form: {
+            method: 'Method',
+            name: 'Name',
+            path: 'Path',
+            placeholder: {
+                name: 'Please Enter Api Name',
+                path: 'Please Enter Api Path'
+            }
+        },
+        navbars: {
+            parameter: 'Parameters',
+            header: 'Headers',
+            path: 'PathVariables',
+            body: 'Body',
+            option: 'Options',
+            description: 'Descriptions',
+            groupOption: 'Group Options'
+        },
+        validateType: 'Validate Type',
+        expression: 'Expression or Regex Pattern',
+        validate: 'Validate Description',
+        field: 'Field'
+    },
+    datasource: {
+        title: 'DataSource',
+        name: 'DataSource',
+        copySuccess: 'Copy {0} Success',
+        copyFailed: 'Failed to Copy {0}',
+        test: 'Test',
+        connected: 'Connected',
+        connectFailed: 'Failed to Connect, Reason:\r\n{0}',
+        primary: 'Primary',
+        form: {
+            placeholder: {
+                name: 'DataSource Name, Only Display Use',
+                key: 'DataSource Key, Required',
+                url: 'Please Enter jdbcURL,eg: jdbc:mysql://localhost/dbname',
+                username: 'Please Enter Database username, Optional',
+                password: 'Please Enter Database password, Optional',
+                driver: 'DriverClass, Optional',
+                type: 'Pool Type, Optional',
+                maxRows: 'Max Return Rows'
+            },
+            driver: 'Driver',
+            type: 'Type',
+            other: 'Others'
+        }
+    },
+    task: {
+        title: 'Task Info',
+        name: 'Task',
+        form: {
+            name: 'Task Name',
+            path: 'Task Path',
+            placeholder: {
+                cron: 'Please Enter Cron Expression',
+                name: 'Please Enter Task Name',
+                path: 'Please Enter Task Path',
+                description: 'Please Enter Task Description'
+            }
+        }
+    },
+    fn: {
+        title: 'Function Info',
+        name: 'Function',
+        number: 'Nunmber',
+        string: 'String',
+        collection: 'Collection',
+        returnValue: 'Return Types',
+        parameter: 'Function Parameters',
+        description: 'Function Description',
+        form: {
+            name: 'Name',
+            path: 'Path',
+            placeholder: {
+                name: 'Please Enter Function Name',
+                path: 'Please Enter Function Path'
+            }
+        }
+    },
+    toolbars: {
+        debug: 'Debug',
+        log: 'Run Log',
+        history: 'History',
+        event: 'Event',
+        global: 'Global Parameters',
+        response: 'Result'
+    },
+    event: {
+        message: 'Message'
+    },
+    history: {
+        name: 'History',
+        operator: 'Operators'
+    },
+    upload: {
+        full: 'Full Upload',
+        increment: 'Increment Upload',
+        success: '{0} Success',
+        failed: 'Failed to {0}'
+    },
+    push: {
+        full: 'Full Push',
+        increment: 'Increment Push',
+        success: '{0} Success',
+        failed: 'Failed to {0}'
+    },
+    backup:{
+        full: 'Full Backup',
+        backupSuccess: 'Full Backup Success',
+        rollback: 'Rollback',
+        current: 'Current',
+        difference: 'Difference',
+        rollbackSuccess: 'Rollback {0} Success',
+        rollbackFailed: 'Failed to Rollback {0}',
+        rollbackConfirm: 'this mode is read from backup and overview current resources, Do you want to continue?'
+    },
+    online: {
+        login: 'User Login',
+        loginTips: 'User {0} Login, IP: {1}',
+        logout: 'User Logout',
+        logoutTips: 'User {0} Logout, IP: {1}',
+        onlines: 'Online: {0}'
+    },
+    log: {
+        hide: 'Click to hide multiline log',
+        show: '{0} lines of log are hidden, Click to show'
+    }
+
+}

+ 314 - 0
src/scripts/i18n/zh-cn.js

@@ -0,0 +1,314 @@
+export default {
+    name: '简体中文',
+    message: {
+        run: '运行',
+        save: '保存',
+        search: '搜索',
+        upload: '上传',
+        export: '导出',
+        push: '推送',
+        skin: '皮肤',
+        reload: '重新加载所有数据',
+        copy: '复制',
+        searchText: '输入关键字搜索',
+        required: '必填',
+        defaultValue: '默认值',
+        description: '描述',
+        parameterType: '参数类型',
+        view: '视图',
+        addRow: '增加一行',
+        removeRow: '删除一行',
+        all: '全部',
+        clear: '清空',
+        empty: '暂无{0}',
+        type: '类型',
+        date: '时间',
+        name: '名称',
+        group: '{0}分组',
+        i18n: '语言',
+        tips: '提示',
+        ok: '确定',
+        refresh: '刷新',
+        loading: '加载中',
+        nodata: '无数据',
+        cancel: '取消',
+        update: '修改',
+        create: '创建',
+        username: '用户名',
+        password: '密码',
+        createDataSource: '创建{0}',
+        chooseFile: '请选择文件',
+        expand: '展开',
+        collapse: '收缩',
+        selectAll: '全选',
+        deselectAll: '取消全选',
+        hide: '隐藏',
+        login: '登录',
+        ignore: '不再提醒',
+        document: '帮助文档',
+        joinGroup: '加入QQ群',
+        untitled: '未定义名称',
+        log: '日志',
+        enabled: '启用',
+        variable: '变量信息',
+
+        switchLocale: '已切换至{0},刷新页面后生效,是否刷新?',
+        loadClass: '加载classes信息...',
+        loadClassError: '加载classes信息失败',
+        switchSkin: '切换皮肤至「{0}」',
+        loadClassFinish: 'classes信息加载完毕',
+        tryAutoLogin: '尝试自动登录',
+        autoLoginSuccess: '自动登录成功',
+        getCurrentLoginUser: '获取当前登录用户信息',
+        getResource: '获取{0}资源',
+        getResourceFinish: '获取{0}资源完毕',
+        connectDebugServer: '连接调试服务器...',
+        debugServerClose: '调试服务器已断开',
+        connectDebugServerSuccess: '连接调试服务器成功',
+        reloadResourceSuccess: '重新加载资源成功',
+
+        getDetail: '获取{0}',
+        getDetailSuccess: '获取{0}详情成功',
+
+        lockSuccess: '成功锁定{0}',
+        lockFailed: '锁定{0}失败',
+        unlockSuccess: '成功解锁{0}',
+        unlockFailed: '解锁{0}失败',
+        updateTips: '修改{0}',
+        saveSuccess: '保存{0}成功',
+        saveFailed: '保存{0}失败',
+
+        newVersionRelease: '版本检测完毕,最新版本为:{0},建议更新!!',
+        versionLastest: '版本检测完毕,当前已是最新版',
+        versionUpdate: '检测到已有新版本{0},是否更新?',
+        changelog: '更新日志',
+        versionConflict: '检测到前后端版本不一致(前端:${0} 后端:{1}),请检查',
+        versionCheck: '版本检测',
+        loadConfigError: '加载配置失败',
+        logout: '注销登录',
+        logoutSuccess: '注销登录成功',
+        logoutConfirm: '是否要注销登录「{0}」',
+        deleteConfirm: '是否要删除{0}',
+        deleteSuccess: '删除{0}成功',
+        deleteFailed: '删除{0}失败',
+        deleteTips: '删除{0}',
+
+        remote: '远程地址',
+        secret: '秘钥',
+        exported: '数据已导出完毕',
+        exportNoneSelect: '请选择之再在进行导出!',
+        pushNoneSelect: '请选择之后再进行推送!',
+
+        responseBody: 'Body',
+        responseHeader: '响应Header',
+        responseStructure: '响应结构',
+        root: '根节点',
+        pushWarning: '全量模式推送时,以本地数据为准全量覆盖更新,是否继续?',
+        uploadWarning: '全量模式上传时,以上传的数据为准进行覆盖更新操作,可能会删除其他接口<br>在非全量导出时,建议使用增量更新,是否继续?',
+
+        noValidate: '不验证',
+        validatePattern: '正则验证',
+        validateExpression: '表达式验证',
+
+    },
+    resource: {
+        createGroup: '创建分组',
+        updateGroup: '修改分组',
+        copyGroup: '复制分组',
+        deleteGroupConfirm: '是否要删除{0}分组「{1}」?',
+        deleteGroupSuccess: '删除{0}分组「{1}」成功',
+        deleteGroupFailed: '删除{0}分组「{1}」失败',
+        groupExport: '分组「{0}」相关{1}已导出',
+        moveGroup: '移动分组',
+        moveRootGroupConfirm: '是否要将分组「{0}」移动至根节点',
+        moveRootSuccess: '移动{0}分组「{1}」至根节点成功',
+        moveRootFailed: '移动{0}分组「{1}」至根节点失败',
+        moveGroupSuccess: '移动{0}分组「{1}」成功',
+        moveGroupFailed: '移动{0}分组「{1}」失败',
+        moveFileSuccess: '移动资源「{0}」成功',
+        moveResourceFailed: '移动资源「{0}」失败',
+        saveGroupSuccess: '保存{0}分组「{1}」成功',
+        saveGroupFailed: '保存{0}分组「{1}」失败',
+        copyPathSuccess: '{0}路径「{1}」复制成功',
+        copyPathFailed: '{0}路径「{1}」复制失败,请手动复制',
+        copyRelativePathSuccess: '{0}相对路径「{1}」复制成功',
+        copyRelativePathFailed: '{0}相对路径「{1}」复制失败,请手动复制',
+        contextmenu: {
+            copy: '复制{0}',
+            copyWithPath: '复制路径',
+            copyRelativePath: '复制相对路径',
+            lock: '锁定',
+            unlock: '解锁',
+            delete: '删除',
+            newFile: '新建{0}',
+            deleteGroup: '删除分组',
+            exportGroup: '导出分组',
+            moveToRoot: '移动至根节点'
+        },
+        header: {
+            expand: '全部展开',
+            collapse: '全部折叠',
+            asc: '按字母升序',
+            desc: '按字母降序',
+            position: '定位当前文件'
+        },
+        form: {
+            groupName: '分组名称',
+            groupPath: '分组路径',
+            placeholder: {
+                name: '请输入{0}分组名称',
+                path: '请输入{0}分组路径'
+            }
+        }
+    },
+    editor: {
+        tab: {
+            close: '关闭',
+            closeOther: '关闭其它',
+            closeLeft: '关闭左侧',
+            closeRight: '关闭右侧',
+            closeAll: '全部关闭',
+        },
+        tooltip: {
+            complection: '代码提示',
+            resume: '恢复断点',
+            stepInto: '步进',
+            format: '代码格式化',
+            recent: '最近打开'
+        },
+        triggerSuggest: '触发代码提示'
+    },
+    api: {
+        title: '接口信息',
+        name: '接口',
+        form: {
+            method: '请求方法',
+            name: '接口名称',
+            path: '接口路径',
+            placeholder: {
+                name: '请输入接口名称',
+                path: '请输入接口路径'
+            }
+        },
+        navbars: {
+            parameter: '请求参数',
+            header: '请求Header',
+            path: '路径变量',
+            body: '请求Body',
+            option: '接口选项',
+            description: '接口描述',
+            groupOption: '分组选项'
+        },
+        validateType: '验证方式',
+        expression: '表达式或正则表达式',
+        validate: '验证说明',
+        field: '字段'
+    },
+    datasource: {
+        title: 'DataSource',
+        name: '数据源',
+        copySuccess: '复制{0}成功',
+        copyFailed: '复制{0}失败',
+        test: '测试连接',
+        connected: '连接成功',
+        connectFailed: '连接失败,错误原因:\r\n{0}',
+        primary: '主数据源',
+        form: {
+            placeholder: {
+                name: '数据源名称,仅做显示使用',
+                key: '数据源Key,后续代码中使用',
+                url: '请输入jdbcURL,如:jdbc:mysql://localhost/dbname',
+                username: '请输入数据库用户名',
+                password: '请输入数据库密码',
+                driver: '驱动类,可选,内部自动识别,也可以手动输入指定',
+                type: '连接池类型,可选,也可以手动输入指定',
+                maxRows: '最多返回条数,-1为不限制'
+            },
+            driver: '驱动类',
+            type: '类型',
+            other: '其它配置'
+        }
+    },
+    task: {
+        title: '定时任务信息',
+        name: '定时任务',
+        form: {
+            name: '任务名称',
+            path: '任务路径',
+            placeholder: {
+                cron: '请输入Cron表达式',
+                name: '请输入任务名称',
+                path: '请输入任务路径',
+                description: '请输入任务描述'
+            }
+        }
+    },
+    fn: {
+        title: '函数信息',
+        name: '函数',
+        number: '数值',
+        string: '字符串',
+        collection: '集合',
+        fnName: '函数名称',
+        returnValue: '返回值',
+        parameter: '函数参数',
+        description: '函数描述',
+        forn: {
+            name: '函数名称',
+            path: '函数路径',
+            placeholder: {
+                name: '请输入函数名称',
+                path: '请输入函数路径'
+            }
+        }
+
+    },
+    toolbars: {
+        debug: '调试信息',
+        log: '运行日志',
+        history: '历史记录',
+        event: '事件',
+        global: '全局参数',
+        response: '执行结果'
+    },
+    event: {
+        message: '事件内容'
+    },
+    history: {
+        name: '历史记录',
+        operator: '操作人'
+    },
+    upload: {
+        full: '全量上传',
+        increment: '增量上传',
+        success: '{0}成功',
+        failed: '{0}失败'
+    },
+    push: {
+        full: '全量推送',
+        increment: '增量推送',
+        success: '{0} Success',
+        failed: 'Failed to {0}'
+    },
+    backup:{
+        full: '全量备份',
+        backupSuccess: '全量备份完毕',
+        rollback: '还原',
+        current: '当前版本',
+        difference: '对比不同',
+        rollbackSuccess: '恢复{0}成功',
+        rollbackFailed: '恢复{0}失败',
+        rollbackConfirm: '该模式是全量从备份文件中读取,并覆盖更新当前资源,是否继续?'
+    },
+    online: {
+        login: '用户上线',
+        loginTips: '用户「{0}」已上线,IP:{1}',
+        logout: '用户下线',
+        logoutTips: '用户「{0}」已下线,IP:{1}',
+        onlines: '当前在线:{0}人'
+    },
+    log: {
+        hide: '点击隐藏多行日志',
+        show: '有 {0} 行日志被隐藏,点击显示'
+    },
+}

+ 2 - 1
src/scripts/service/magic-api.js

@@ -2,6 +2,7 @@ import constants from '../constants.js'
 import Message from '../constants/message.js'
 import request from '../request.js'
 import bus from '../bus.js'
+import $i from '../i18n.js'
 import modal from '../../components/common/dialog/magic-modal.js'
 import { getSizeUnit } from '../utils.js'
 import { parseJson } from '../parsing/parser.js'
@@ -126,7 +127,7 @@ export default {
 	getIcon: item => item.method || 'GET',
 	runnable: true,
 	requirePath: true,
-	name: '接口',
+	name: $i('api.name'),
 	merge: item => {
 		item.method = item.method || constants.DEFAULT_REQUEST_METHOD
 		item.parameters = item.parameters || []

+ 3 - 2
src/scripts/service/magic-datasource.js

@@ -1,4 +1,5 @@
 import request from '../request.js'
+import $i from '../i18n.js'
 import modal from '../../components/common/dialog/magic-modal.js'
 import JavaClass from '../editor/java-class.js'
 let findResources
@@ -16,9 +17,9 @@ export default {
 	doTest: info => {
 		request.sendJson('/datasource/jdbc/test', info).success(res => {
 			if(res === 'ok'){
-				modal.alert('连接成功', '测试连接')
+				modal.alert($i('datasource.connected'), $i('datasource.test'))
 			}else {
-				modal.alert('连接失败,错误原因:\r\n' + res, '测试连接')
+				modal.alert($i('datasource.connectFailed', res), $i('datasource.test'))
 			}
 		})
 	}

+ 2 - 1
src/scripts/service/magic-function.js

@@ -1,6 +1,7 @@
+import $i from '../i18n.js'
 export default {
 	getIcon: item => 'function',
-	name: '函数',
+	name: $i('fn.name'),
 	runnable: false,
 	requirePath: true,
 	merge: item => {

+ 0 - 6
src/scripts/service/magic-resource.js

@@ -1,6 +0,0 @@
-export default {
-	getIcon: item => 'task',
-	runnable: false,
-	requirePath: false,
-	merge: item => item
-}

+ 2 - 1
src/scripts/service/magic-task.js

@@ -1,11 +1,12 @@
 import bus from '../bus.js'
 import constants from '../constants.js'
+import $i from '../i18n.js'
 import Message from '../constants/message.js'
 import request from '../request.js'
 
 export default {
 	getIcon: item => 'task',
-	name: '定时任务',
+	name: $i('task.name'),
 	doTest: (opened) => {
 		opened.running = true
 		const info = opened.item

+ 0 - 8
src/scripts/service/magic-websocket.js

@@ -1,8 +0,0 @@
-export default {
-	getIcon: item => 'websocket',
-	runnable: false,
-	requirePath: true,
-	merge: item => {
-		return item
-	}
-}

+ 3 - 3
src/scripts/websocket.js

@@ -5,14 +5,14 @@ function MagicWebSocket(url) {
 	this.socket = new ReconnectingWebSocket(url);
 	this.socket.onmessage = this.messageReceived;
 	this.socket.onconnecting = () => {
-        bus.status('连接调试服务器...')
+        bus.status('message.connectDebugServer')
     }
 	this.socket.onopen = () => {
-        bus.status('连接调试服务器成功')
+        bus.status('message.connectDebugServerSuccess')
 		bus.$emit('ws_open')
 	}
 	this.socket.onclose = () => {
-        bus.status('调试服务器已断开')
+        bus.status('message.debugServerClose')
 		bus.$emit('ws_close')
 	}
 }