bill недель назад: 3
Родитель
Сommit
39bd19c30e
71 измененных файлов с 1030 добавлено и 5461 удалено
  1. 135 0
      package-lock.json
  2. 3 0
      package.json
  3. 0 4008
      pnpm-lock.yaml
  4. 53 0
      scripts/fetch-langs.mjs
  5. 3 2
      src/core/components/arrow/arrow.vue
  6. 2 1
      src/core/components/arrow/index.ts
  7. 2 1
      src/core/components/circle/index.ts
  8. 3 2
      src/core/components/group/group.vue
  9. 2 1
      src/core/components/group/index.ts
  10. 2 1
      src/core/components/icon/icon.ts
  11. 2 1
      src/core/components/image/index.ts
  12. 2 1
      src/core/components/line-chunk/index.ts
  13. 2 1
      src/core/components/line-icon/icon.vue
  14. 2 1
      src/core/components/line-icon/index.ts
  15. 5 4
      src/core/components/line/attach-server.ts
  16. 2 1
      src/core/components/line/index.ts
  17. 3 2
      src/core/components/line/single-line.vue
  18. 2 1
      src/core/components/polygon/index.ts
  19. 2 1
      src/core/components/polygon/polygon.vue
  20. 2 1
      src/core/components/rectangle/index.ts
  21. 2 1
      src/core/components/sequent-line/index.ts
  22. 2 1
      src/core/components/sequent-line/line.vue
  23. 18 10
      src/core/components/serial/index.ts
  24. 2 1
      src/core/components/serial/serial.vue
  25. 1 1
      src/core/components/share/edit-point.vue
  26. 2 1
      src/core/components/table/index.ts
  27. 5 4
      src/core/components/table/table.vue
  28. 3 2
      src/core/components/text/index.ts
  29. 2 1
      src/core/components/triangle/index.ts
  30. 3 2
      src/core/hook/use-alignment.ts
  31. 8 7
      src/core/hook/use-component.ts
  32. 28 20
      src/core/hook/use-group.ts
  33. 1 1
      src/core/html-mount/propertys/components/select.vue
  34. 1 1
      src/core/html-mount/propertys/components/text.vue
  35. 3 2
      src/core/html-mount/propertys/describes.ts
  36. 2 2
      src/core/html-mount/propertys/mount-describes.vue
  37. 34 32
      src/core/html-mount/propertys/sys-describes.json
  38. 2 1
      src/example/components/container/container.vue
  39. 9 8
      src/example/components/header/actions.ts
  40. 1 1
      src/example/components/show-vr.vue
  41. 13 12
      src/example/components/slide/actions.ts
  42. 7 2
      src/example/components/slide/slide-icons.vue
  43. 107 119
      src/example/constant.ts
  44. 10 9
      src/example/dialog/ai/ai.vue
  45. 3 2
      src/example/dialog/ai/index.ts
  46. 3 2
      src/example/dialog/basemap/gd-index.ts
  47. 20 6
      src/example/dialog/basemap/gd-map/selectAMapImage.vue
  48. 20 12
      src/example/dialog/basemap/index.ts
  49. 16 12
      src/example/dialog/basemap/leaflet/index.vue
  50. 2 2
      src/example/dialog/dialog.vue
  51. 4 4
      src/example/dialog/expose/expose-format.vue
  52. 3 2
      src/example/dialog/expose/index.ts
  53. 3 2
      src/example/dialog/vr/index.ts
  54. 2 2
      src/example/dialog/vr/vr.vue
  55. 2 1
      src/example/fuse/enter-mix.ts
  56. 18 17
      src/example/fuse/enter-shared.ts
  57. 2 1
      src/example/fuse/enter.ts
  58. 8 2
      src/example/fuse/router.ts
  59. 5 4
      src/example/fuse/views/error/index.vue
  60. 6 5
      src/example/fuse/views/overview/header.vue
  61. 3 2
      src/example/fuse/views/overview/index.vue
  62. 3 2
      src/example/fuse/views/overview/slide.vue
  63. 4 4
      src/example/fuse/views/tabulation/gen-tab.ts
  64. 4 3
      src/example/fuse/views/tabulation/header.vue
  65. 9 9
      src/example/fuse/views/tabulation/index.vue
  66. 1 1
      src/example/fuse/views/tabulation/slide-icons.vue
  67. 3 2
      src/example/fuse/views/tabulation/slide.vue
  68. 4 3
      src/example/platform/platform-resource.ts
  69. 1 1
      src/lang/index.ts
  70. 386 1
      src/lang/locales/zh.json
  71. 0 1086
      test.json

+ 135 - 0
package-lock.json

@@ -44,8 +44,10 @@
       },
       "devDependencies": {
         "@vitejs/plugin-vue": "^5.1.4",
+        "node-fetch": "^3.3.2",
         "sass-embedded": "^1.80.4",
         "typescript": "~5.6.2",
+        "unzipper": "^0.12.3",
         "vite": "^5.4.9",
         "vue-tsc": "^2.1.6"
       }
@@ -2327,6 +2329,15 @@
       "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
       "license": "MIT"
     },
+    "node_modules/data-uri-to-buffer": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+      "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+      "dev": true,
+      "engines": {
+        "node": ">= 12"
+      }
+    },
     "node_modules/data-view-buffer": {
       "version": "1.0.2",
       "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
@@ -2586,6 +2597,15 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/duplexer2": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+      "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+      "dev": true,
+      "dependencies": {
+        "readable-stream": "^2.0.2"
+      }
+    },
     "node_modules/dxf-writer": {
       "version": "1.18.4",
       "resolved": "https://registry.npmmirror.com/dxf-writer/-/dxf-writer-1.18.4.tgz",
@@ -2967,6 +2987,29 @@
         "reusify": "^1.0.4"
       }
     },
+    "node_modules/fetch-blob": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+      "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "paypal",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "dependencies": {
+        "node-domexception": "^1.0.0",
+        "web-streams-polyfill": "^3.0.3"
+      },
+      "engines": {
+        "node": "^12.20 || >= 14.13"
+      }
+    },
     "node_modules/fflate": {
       "version": "0.8.2",
       "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz",
@@ -3023,6 +3066,18 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/formdata-polyfill": {
+      "version": "4.0.10",
+      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+      "dev": true,
+      "dependencies": {
+        "fetch-blob": "^3.1.2"
+      },
+      "engines": {
+        "node": ">=12.20.0"
+      }
+    },
     "node_modules/fragment-cache": {
       "version": "0.2.1",
       "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -4504,6 +4559,44 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "deprecated": "Use your platform's native DOMException instead",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+      "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+      "dev": true,
+      "dependencies": {
+        "data-uri-to-buffer": "^4.0.0",
+        "fetch-blob": "^3.1.4",
+        "formdata-polyfill": "^4.0.10"
+      },
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/node-fetch"
+      }
+    },
     "node_modules/node-html-parser": {
       "version": "5.4.2",
       "resolved": "https://registry.npmmirror.com/node-html-parser/-/node-html-parser-5.4.2.tgz",
@@ -4514,6 +4607,12 @@
         "he": "1.2.0"
       }
     },
+    "node_modules/node-int64": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+      "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+      "dev": true
+    },
     "node_modules/normalize-wheel-es": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
@@ -6787,6 +6886,33 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/unzipper": {
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz",
+      "integrity": "sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==",
+      "dev": true,
+      "dependencies": {
+        "bluebird": "~3.7.2",
+        "duplexer2": "~0.1.4",
+        "fs-extra": "^11.2.0",
+        "graceful-fs": "^4.2.2",
+        "node-int64": "^0.4.0"
+      }
+    },
+    "node_modules/unzipper/node_modules/fs-extra": {
+      "version": "11.3.3",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
+      "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=14.14"
+      }
+    },
     "node_modules/urix": {
       "version": "0.1.0",
       "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz",
@@ -7082,6 +7208,15 @@
         "typescript": ">=5.0.0"
       }
     },
+    "node_modules/web-streams-polyfill": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+      "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
     "node_modules/which-boxed-primitive": {
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",

+ 3 - 0
package.json

@@ -4,6 +4,7 @@
   "version": "1.3.0",
   "type": "module",
   "scripts": {
+    "translate": "node ./scripts/fetch-langs.mjs",
     "dev:exe": "vite --mode=exedev",
     "build:exe": "vite build --mode=exe",
     "dev:fuse": "vite --mode=firedev",
@@ -53,8 +54,10 @@
   },
   "devDependencies": {
     "@vitejs/plugin-vue": "^5.1.4",
+    "node-fetch": "^3.3.2",
     "sass-embedded": "^1.80.4",
     "typescript": "~5.6.2",
+    "unzipper": "^0.12.3",
     "vite": "^5.4.9",
     "vue-tsc": "^2.1.6"
   }

Разница между файлами не показана из-за своего большого размера
+ 0 - 4008
pnpm-lock.yaml


+ 53 - 0
scripts/fetch-langs.mjs

@@ -0,0 +1,53 @@
+import fetch from 'node-fetch'
+import fs from 'fs'
+import path from 'path'
+import unzipper from 'unzipper'
+
+import { pipeline } from 'node:stream';
+import { promisify } from 'node:util'
+import { createReadStream, createWriteStream } from "node:fs";
+
+const streamPipeline = promisify(pipeline);
+
+
+/**
+ * 下载并解压离线包
+ * @param {string} url 下载地址
+ * @param {string} destDir 解压目标目录
+ */
+async function downloadAndExtract(url, destDir) {
+    const zipPath = path.join(destDir, 'temp.zip')
+
+
+    console.log(`开始下载语言包: ${url}`)
+
+    const res = await fetch(url, {
+        method: 'GET',
+        headers: {
+            //'X-API-Key':'tgpak_gm2f6ntnor2gy4ztnfuw65twoj2wu2tdovzwwztqoe4ts5q'
+        },
+    })
+
+    if (!res.ok) {
+        throw new Error(`下载语言包失败: ${res.status} ${res.statusText}`)
+    }
+
+    // zip文件保存到本地目录
+    await streamPipeline(res.body, createWriteStream(zipPath));
+
+    console.log('语言包下载完成,开始解压...')
+
+    // 解压到指定目录
+    await streamPipeline(createReadStream(zipPath), unzipper.Extract({ path: destDir }));
+
+    console.log('语言包解压完成!')
+
+    // 删除临时压缩包
+    fs.unlinkSync(zipPath)
+}
+
+// 示例调用
+const url = 'http://192.168.0.211:9012/v2/projects/export?ak=tgpak_gq3f6ylngrwdo4rqnfxhmnlegy2wy3dhm5stm5jqmy2g2yq'
+const dest = './src/lang/locales'
+
+await downloadAndExtract(url, dest)

+ 3 - 2
src/core/components/arrow/arrow.vue

@@ -38,6 +38,7 @@ import { flatPositions } from "@/utils/shared.ts";
 import { themeColor } from "@/constant";
 import { watch } from "vue";
 import { useInstallStrokeWidthDescribe } from "@/core/hook/use-describe.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: ArrowData }>();
 const emit = defineEmits<{
@@ -98,8 +99,8 @@ const { shape, tData, operateMenus, describes, data } = useComponentStatus<
 watch(
   describes,
   (describes) => {
-    describes.fill.label = "颜色";
-    describes.strokeWidth.label = "粗细";
+    describes.fill.label = ui18n.t("sys.color");
+    describes.strokeWidth.label = ui18n.t("sys.strokeWidth");
   },
   { immediate: true }
 );

+ 2 - 1
src/core/components/arrow/index.ts

@@ -5,6 +5,7 @@ import { BaseItem, generateSnapInfos, getBaseItem } from "../util.ts";
 import { getMouseColors } from "@/utils/colors.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Transform } from "konva/lib/Util";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./arrow.vue";
 export { default as TempComponent } from "./temp-arrow.vue";
@@ -14,7 +15,7 @@ export enum PointerPosition {
   end = "end",
   all = "all",
 }
-export const shapeName = "箭头";
+export const shapeName = ui18n.t('shape.arrow.name');
 export const defaultStyle = {
   fixed: true,
   fill: '#000000',

+ 2 - 1
src/core/components/circle/index.ts

@@ -10,11 +10,12 @@ import { lineCenter, Pos } from "@/utils/math.ts";
 import { Transform } from "konva/lib/Util";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { MathUtils } from "three";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./circle.vue";
 export { default as TempComponent } from "./temp-circle.vue";
 
-export const shapeName = "圆形";
+export const shapeName = ui18n.t('shape.circle.name');
 export const defaultStyle = {
   dash: [30, 0],
   stroke: "#000000",

+ 3 - 2
src/core/components/group/group.vue

@@ -48,6 +48,7 @@ import { themeColor } from "@/constant";
 import { debounce } from "@/utils/shared.ts";
 import { useComponentsDescribes } from "@/core/hook/use-group.ts";
 import { components, ShapeType } from "../index.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: GroupData }>();
 const emit = defineEmits<{
@@ -82,7 +83,7 @@ const setPropertyDatas = computed(() => {
 });
 
 const similars = {
-  形状: ["rectangle", "circle", "triangle", "polygon"],
+  [ui18n.t("sys.shape")]: ["rectangle", "circle", "triangle", "polygon"],
 } as Record<string, ShapeType[]>;
 
 const shapesStatus = useMouseShapesStatus();
@@ -120,7 +121,7 @@ const propertyName = computed(() => {
       return target[0];
     }
   }
-  return [...names].join("、") || "多实现";
+  return [...names].join("、") || ui18n.t("sys.similars");
 });
 
 const changePropertyHandler = () => {

+ 2 - 1
src/core/components/group/index.ts

@@ -9,11 +9,12 @@ import {
 } from "../index.ts";
 import { Transform } from "konva/lib/Util";
 import { components } from "../index";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./group.vue";
 export { default as TempComponent } from "./temp-group.vue";
 
-export const shapeName = "分组";
+export const shapeName = ui18n.t('shape.group.name');
 export const defaultStyle = {
   stroke: "#cccccc",
   strokeWidth: 2,

+ 2 - 1
src/core/components/icon/icon.ts

@@ -10,8 +10,9 @@ import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Size } from "@/utils/math.ts";
 import { getSvgContent, parseSvgContent } from "@/utils/resource.ts";
 import { Color } from "three";
+import { ui18n } from "@/lang/index.ts";
 
-export const shapeName = "图例";
+export const shapeName = ui18n.t('shape.icon.name');
 export const defaultStyle = {
   coverFill: "#000000",
   coverOpcatiy: 0,

+ 2 - 1
src/core/components/image/index.ts

@@ -10,11 +10,12 @@ import { imageInfo } from "@/utils/resource.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Size } from "@/utils/math.ts";
 import { themeColor } from "@/constant";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./image.vue";
 export { default as TempComponent } from "./temp-image.vue";
 
-export const shapeName = "图片";
+export const shapeName = ui18n.t('shape.image.name');
 export const defaultStyle = {
   strokeWidth: 0,
   stroke: themeColor,

+ 2 - 1
src/core/components/line-chunk/index.ts

@@ -9,8 +9,9 @@ import { EntityShape } from "@/deconstruction";
 import { Group } from "konva/lib/Group";
 import mitt from "mitt";
 import { watch } from "vue";
+import { ui18n } from "@/lang";
 
-export const shapeName = "线段";
+export const shapeName = ui18n.t('shape.line.name');
 export const defaultStyle = {
   strokeWidth: 3,
   stroke: "#000000",

+ 2 - 1
src/core/components/line-icon/icon.vue

@@ -49,6 +49,7 @@ import {
 } from "@/utils/math.ts";
 import { copy } from "@/utils/shared.ts";
 import { useInstallStrokeWidthDescribe } from "@/core/hook/use-describe.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: LineIconData }>();
 const emit = defineEmits<{
@@ -240,7 +241,7 @@ if (props.data.type === "align-bottom" || props.data.type === "align-bottom-fix"
     //   },
     // },
     {
-      label: "翻转",
+      label: ui18n.t('shape.lineIcon.rever'),
       handler: () => {
         emit("updateShape", {
           ...data.value,

+ 2 - 1
src/core/components/line-icon/index.ts

@@ -18,13 +18,14 @@ import {
 import { Vector2 } from "three";
 import { LineData } from "../line/index.ts";
 import { DrawStore } from "@/core/store/index.ts";
+import { ui18n } from "@/lang/index.ts";
 
 export { defaultStyle, addMode, TempComponent, Component };
 export { getMouseStyle, getPredefine } from "../icon/index.ts";
 
 export const fixedStrokeOptions:number[] = [];
 
-export const shapeName = "线段图例";
+export const shapeName = ui18n.t('shape.lineIcon.name');
 export type LineIconData = Omit<IconData, "mat" | "width"> & {
   startLen: number;
   endLen: number;

+ 5 - 4
src/core/components/line/attach-server.ts

@@ -51,6 +51,7 @@ import {
 import { useDrawIngData } from "@/core/hook/use-draw";
 import { useComponentDescribes } from "@/core/hook/use-component";
 import { useInstallStrokeWidthDescribe } from "@/core/hook/use-describe";
+import { ui18n } from "@/lang";
 
 export type NLineDataCtx = {
   del: {
@@ -604,7 +605,7 @@ export const useLineDescribes = (
     watch(
       d,
       (d) => {
-        d.strokeWidth.label = "厚度";
+        d.strokeWidth.label = ui18n.t('shape.wall.strokeWidth');
         d.strokeWidth.props = {
           ...d.strokeWidth.props,
           proportion: true,
@@ -613,16 +614,16 @@ export const useLineDescribes = (
       { immediate: true }
     );
   } else {
-    useInstallStrokeWidthDescribe(d, line, fixedStrokeOptions, undefined, undefined, '线宽');
+    useInstallStrokeWidthDescribe(d, line, fixedStrokeOptions, undefined, undefined, ui18n.t('sys.line.strokeWidth'));
   }
   
   watch(
     d,
     (d) => {
-      d.stroke.label = "颜色";
+      d.stroke.label = ui18n.t('sys.color');
       d.length = {
         type: "inputNum",
-        label: "长度",
+        label: ui18n.t('sys.length'),
         "layout-type": "row",
         props: {
           proportion: true,

+ 2 - 1
src/core/components/line/index.ts

@@ -16,6 +16,7 @@ import { getInitCtx, NLineDataCtx, normalLineData } from "./attach-server.ts";
 import * as wallRenderer from "./renderer/wall";
 import { Group } from "konva/lib/Group";
 import { generateUseDraw } from "./use-draw.ts";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./line.vue";
 export { default as TempComponent } from "./temp-line.vue";
@@ -23,7 +24,7 @@ export { generateUseDraw } from "./use-draw.ts";
 
 export const useDraw = generateUseDraw("line");
 
-export const shapeName = "墙";
+export const shapeName = ui18n.t('shape.wall.name');
 export const defaultStyle = {
   stroke: "#000000",
   strokeWidth: 20,

+ 3 - 2
src/core/components/line/single-line.vue

@@ -112,6 +112,7 @@ import { useHistory } from "@/core/hook/use-history.ts";
 import { useTransformIngShapes } from "@/core/hook/use-global-vars.ts";
 import { components } from "../index.ts";
 import { fixedStrokeOptions } from "../line-chunk/index.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{
   line: LineDataLine;
@@ -181,8 +182,8 @@ const { drawProps, enter: enterDrawLinePoint } = useDrawLinePoint(
 const drawData = computed(() => drawProps.value?.data);
 const drawGetShapeAttrib = renderer.value!.genGetShapeAttrib(drawData);
 const menus = [
-  { label: "加点", handler: enterDrawLinePoint },
-  { label: "删除", handler: delHandler },
+  { label: ui18n.t("shape.line.addPoint"), handler: enterDrawLinePoint },
+  { label: ui18n.t("sys.del"), handler: delHandler },
 ];
 
 const mStatus = useMouseShapesStatus();

+ 2 - 1
src/core/components/polygon/index.ts

@@ -4,11 +4,12 @@ import { getMouseColors } from "@/utils/colors.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Transform } from "konva/lib/Util";
 import { inRevise } from "@/utils/shared.ts";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./polygon.vue";
 export { default as TempComponent } from "./temp-polygon.vue";
 
-export const shapeName = "多边形";
+export const shapeName = ui18n.t('shape.polygon.name') ;
 export const fixedStrokeOptions:number[] = [];
 export const defaultStyle = {
   stroke: "#000000",

+ 2 - 1
src/core/components/polygon/polygon.vue

@@ -34,6 +34,7 @@ import { useInteractiveDrawShapeAPI } from "@/core/hook/use-draw.ts";
 import { useStore } from "@/core/store/index.ts";
 import { Pos } from "@/utils/math.ts";
 import { useInstallStrokeWidthDescribe } from "@/core/hook/use-describe.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: PolygonData }>();
 const emit = defineEmits<{
@@ -82,7 +83,7 @@ const addPoint = ({ ndx, val }: { ndx: number; val: Pos }) => {
 const draw = useInteractiveDrawShapeAPI();
 const store = useStore();
 operateMenus.push({
-  label: "钢笔编辑",
+  label: ui18n.t("shape.polygon.penEdit"),
   handler() {
     draw.enterDrawShape("polygon", {
       ...props.data,

+ 2 - 1
src/core/components/rectangle/index.ts

@@ -4,11 +4,12 @@ import { onlyId } from "@/utils/shared.ts";
 import { BaseItem, generateSnapInfos, getBaseItem } from "../util.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Transform } from "konva/lib/Util";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./rectangle.vue";
 export { default as TempComponent } from "./temp-rectangle.vue";
 
-export const shapeName = "矩形";
+export const shapeName = ui18n.t('shape.rect.name');
 export const defaultStyle = {
   dash: [30, 0],
   fixed: true,

+ 2 - 1
src/core/components/sequent-line/index.ts

@@ -4,11 +4,12 @@ import { getMouseColors } from "@/utils/colors.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Transform } from "konva/lib/Util";
 import { inRevise } from "@/utils/shared.ts";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./line.vue";
 export { default as TempComponent } from "./temp-line.vue";
 
-export const shapeName = "连续线段";
+export const shapeName = ui18n.t('shape.polygon1.name');
 export const defaultStyle = {
   stroke: '#000000',
   strokeWidth: 20,

+ 2 - 1
src/core/components/sequent-line/line.vue

@@ -27,6 +27,7 @@ import TempLine from "./temp-line.vue";
 import { useInteractiveDrawShapeAPI } from "@/core/hook/use-draw.ts";
 import { useStore } from "@/core/store/index.ts";
 import { Pos } from "@/utils/math.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: SLineData }>();
 const emit = defineEmits<{
@@ -79,7 +80,7 @@ const addPoint = ({ ndx, val }: { ndx: number; val: Pos }) => {
 const draw = useInteractiveDrawShapeAPI();
 const store = useStore();
 operateMenus.push({
-  label: "钢笔编辑",
+  label: ui18n.t("shape.polygon.penEdit"),
   handler() {
     draw.enterDrawShape("sequentLine", {
       ...props.data,

+ 18 - 10
src/core/components/serial/index.ts

@@ -17,6 +17,7 @@ import {
 } from "@/core/hook/use-viewer.ts";
 import { useHistory } from "@/core/hook/use-history.ts";
 import { DrawExpose } from "@/core/hook/use-expose.ts";
+import { ui18n } from "@/lang/index.ts";
 
 export {
   getMouseStyle,
@@ -40,7 +41,7 @@ export const defaultTableStyle = {
   repColCount: 1,
   tableStrokeWidth: 1,
 };
-export const shapeName = "序号";
+export const shapeName = ui18n.t('shape.serial.name');
 export const addMode = "dot";
 export const defaultStyle = {
   ...circleDefaultStyle,
@@ -161,7 +162,7 @@ export const delItemRaw = (
 };
 
 export const joinKey = "serial-table";
-export const tableTitle = `图示`;
+export const tableTitle = ui18n.t('shape.serial.tableTitle');
 export const delItem = (store: DrawStore, item: SerialData) => {
   const table = store
     .getTypeItems("table")
@@ -212,7 +213,7 @@ const getTempContents = () => {
     tempContents.push(
       {
         key: "preset-col",
-        content: "序号",
+        content: ui18n.t('shape.serial.head1'),
         readonly: true,
         notdel: true,
         width: defaultTableStyle.nameColWidth,
@@ -222,7 +223,7 @@ const getTempContents = () => {
       },
       {
         key: "preset-col",
-        content: "描述",
+        content: ui18n.t('shape.serial.head2'),
         readonly: true,
         notdel: true,
         width: defaultTableStyle.valueColWidth,
@@ -302,13 +303,13 @@ export const syncTable = (table: TableData, items: SerialData[]) => {
       }
     }
   });
-  
+
   if (~presetIndex) {
     table.content = table.content.slice(0, presetIndex + 1);
-    table.height = table.content.reduce((t, item) => item[0].height + t, 0)
+    table.height = table.content.reduce((t, item) => item[0].height + t, 0);
   } else {
     table.content = [];
-    table.height = 0
+    table.height = 0;
   }
 
   const cols = table.content.flatMap((row) => {
@@ -327,17 +328,18 @@ export const syncTable = (table: TableData, items: SerialData[]) => {
     }
     let rowNdx = Math.floor(i / colCount);
     let colNdx = (i % colCount) * 2;
-    const val = item.desc || oldData[item.id] || ""
+    const val = item.desc || oldData[item.id] || "";
     if (colNdx) {
       table.content[rowNdx][colNdx].content = item.content!;
       table.content[rowNdx][colNdx + 1].content = val;
-      table.content[rowNdx][colNdx + 1].joinId = item.id
+      table.content[rowNdx][colNdx + 1].joinId = item.id;
     } else {
       table.height += tempRow[0].height;
       let cols = [
         {
           ...tempRow[0],
           content: item.content!,
+          readonly: false,
           hidden: false,
           key: "serial-name",
         },
@@ -352,7 +354,13 @@ export const syncTable = (table: TableData, items: SerialData[]) => {
       ];
       for (let i = 1; i < colCount; i++) {
         cols.push(
-          { ...tempRow[2], content: "", hidden: false, key: "serial-name" },
+          {
+            ...tempRow[2],
+            readonly: false,
+            content: "",
+            hidden: false,
+            key: "serial-name",
+          },
           {
             ...tempRow[3],
             content: "",

+ 2 - 1
src/core/components/serial/serial.vue

@@ -31,6 +31,7 @@ import { cloneRepShape, useCustomTransformer } from "@/core/hook/use-transformer
 import { Ellipse } from "konva/lib/shapes/Ellipse";
 import { Pos } from "@/utils/math.ts";
 import { useInstallStrokeWidthDescribe } from "@/core/hook/use-describe.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: SerialData }>();
 const emit = defineEmits<{
@@ -101,7 +102,7 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus<
 
 describes.value.content = {
   type: "inputNum",
-  label: "数值",
+  label: ui18n.t("shape.serial.diff"),
   "layout-type": "row",
   get value() {
     return Number(data.value.content || 1);

+ 1 - 1
src/core/components/share/edit-point.vue

@@ -5,7 +5,7 @@
   />
   <Operate
     :target="circle"
-    :menus="[{ label: '删除', handler: () => emit('delete') }]"
+    :menus="[{ label: $t('sys.del'), handler: () => emit('delete') }]"
     v-if="!notDelete"
   />
 </template>

+ 2 - 1
src/core/components/table/index.ts

@@ -11,11 +11,12 @@ import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { numEq, Size } from "@/utils/math.ts";
 import { copy } from "@/utils/shared.ts";
 import { MathUtils } from "three";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./table.vue";
 export { default as TempComponent } from "./temp-table.vue";
 
-export const shapeName = "表格";
+export const shapeName = ui18n.t('shape.table.name');
 export const defaultStyle = {
   stroke: '#000000',
   strokeWidth: 1,

+ 5 - 4
src/core/components/table/table.vue

@@ -54,6 +54,7 @@ import {
 import { useCursor, usePointerPos } from "@/core/hook/use-global-vars.ts";
 import { Pos } from "@/utils/math.ts";
 import { useInstallStrokeWidthDescribe } from "@/core/hook/use-describe.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ data: TableData }>();
 const emit = defineEmits<{
@@ -298,7 +299,7 @@ const menuShowHandler = () => {
   addMenu = [];
   if (!data.value.notaddRow) {
     addMenu.push({
-      label: "插入行",
+      label: ui18n.t("shape.table.addRow"),
       handler: () => {
         const temprow = data.value.content[config.rowNdx];
         data.value.content.splice(
@@ -316,7 +317,7 @@ const menuShowHandler = () => {
   const canDelRaw = data.value.content[config.rowNdx].every((item) => !item.notdel);
   if (canDelRaw) {
     addMenu.push({
-      label: "删除行",
+      label: ui18n.t("shape.table.delRow"),
       handler: () => {
         const temprow = data.value.content[config.rowNdx];
         data.value.content.splice(config.rowNdx, 1);
@@ -334,7 +335,7 @@ const menuShowHandler = () => {
 
   if (!data.value.notaddCol) {
     addMenu.push({
-      label: "插入列",
+      label: ui18n.t("shape.table.addCol"),
       handler: () => {
         const tempCol = data.value.content[0][config.colNdx];
         for (let i = 0; i < data.value.content.length; i++) {
@@ -352,7 +353,7 @@ const menuShowHandler = () => {
   const canDelCol = data.value.content.every((items) => !items[config.colNdx].notdel);
   if (canDelCol) {
     addMenu.push({
-      label: "删除列",
+      label: ui18n.t("shape.table.delCol"),
       handler: () => {
         const tempCol = data.value.content[0][config.colNdx];
         for (let i = 0; i < data.value.content.length; i++) {

+ 3 - 2
src/core/components/text/index.ts

@@ -12,6 +12,7 @@ import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { zeroEq } from "@/utils/math.ts";
 import { MathUtils } from "three";
 import { getSupportedFont } from "@/utils/shared.ts";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./text.vue";
 export { default as TempComponent } from "./temp-text.vue";
@@ -24,7 +25,7 @@ const supportedFont = getSupportedFont([
   'sans-serif'
 ]);
 
-export const shapeName = "文本";
+export const shapeName =  ui18n.t('shape.text.name');
 export const defaultStyle = {
   fill: '#000000',
   // strokeWidth: 0,
@@ -84,7 +85,7 @@ export const interactiveToData: InteractiveTo<"text"> = ({
       ...defaultStyle,
       ...getBaseItem(),
       ...preset,
-      content: preset.content || "文本",
+      content: preset.content || ui18n.t('shape.text.name'),
     } as unknown as TextData;
     return interactiveFixData({ ...args, info, data: item });
   }

+ 2 - 1
src/core/components/triangle/index.ts

@@ -3,11 +3,12 @@ import { BaseItem, generateSnapInfos, getBaseItem } from "../util.ts";
 import { getMouseColors } from "@/utils/colors.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Transform } from "konva/lib/Util";
+import { ui18n } from "@/lang/index.ts";
 
 export { default as Component } from "./triangle.vue";
 export { default as TempComponent } from "./temp-triangle.vue";
 
-export const shapeName = "三角形";
+export const shapeName = ui18n.t('shape.triangle.name');
 export const defaultStyle = {
   stroke: '#000000',
   strokeWidth: 5,

+ 3 - 2
src/core/hook/use-alignment.ts

@@ -19,6 +19,7 @@ import { shapeTreeContain } from "@/utils/shape";
 import { useViewerInvertTransform, useViewerTransform } from "./use-viewer";
 import { Mode } from "@/constant/mode";
 import { useMouseShapesStatus } from "./use-mouse-status";
+import { ui18n } from "@/lang";
 
 export const useJoinShapes = (
   shapes: (Ref<DC<EntityShape> | undefined> | undefined)[]
@@ -210,10 +211,10 @@ export const useAlignmentShape = (shape: Ref<DC<EntityShape> | undefined>) => {
     quitHooks.push(() => window.removeEventListener("keydown", keydownHandler));
 
     for (const [join] of joins) {
-      info("请在当前图例选择位置(按esc退出对齐)");
+      info(ui18n.t('alignment.start.tip'));
       try {
         const map = await join((ndx) => {
-          ndx === 0 && info("请在画板中与图例对应的位置(按esc退出对齐)");
+          ndx === 0 && info(ui18n.t('alignment.end.tip'));
           $shape.opacity(0.3);
         });
         alignMaps.push(map);

+ 8 - 7
src/core/hook/use-component.ts

@@ -37,6 +37,7 @@ import { useAlignmentShape } from "./use-alignment";
 import { useViewerTransform } from "./use-viewer";
 import { usePause } from "./use-pause";
 import { useGlobalDescribes } from "./use-group";
+import { ui18n } from "@/lang";
 
 type Emit<T> = EmitFn<{
   updateShape: (value: T) => void;
@@ -62,14 +63,14 @@ export const useComponentMenus = <T extends DrawItem>(
   const currentZIndex = useCurrentZIndex();
   operateMenus.push(
     reactive({
-      label: computed(() => (data.value.lock ? "解锁" : "锁定")) as any,
+      label: computed(() => (data.value.lock ? ui18n.t('sys.unlock') : ui18n.t('sys.lock'))) as any,
       handler() {
         data.value.lock = !data.value.lock;
         emit("updateShape", { ...data.value });
       },
     }),
     reactive({
-      label: "隐藏",
+      label: ui18n.t('sys.hide'),
       hide: true,
       handler() {
         data.value.hide = true;
@@ -97,7 +98,7 @@ export const useComponentMenus = <T extends DrawItem>(
   if (alignment) {
     const [alignmentShape] = useAlignmentShape(shape);
     operateMenus.push({
-      label: "对齐",
+      label: ui18n.t('sys.alignment'),
       async handler() {
         const mat = await alignmentShape();
         alignment(data.value, mat);
@@ -114,7 +115,7 @@ export const useComponentMenus = <T extends DrawItem>(
     const status = useMouseShapesStatus();
     const stage = useStage();
     operateMenus.push({
-      label: `复制`,
+      label: ui18n.t('sys.copy'),
       async handler() {
         const transform = getCopyTransform();
         const copyData = copyHandler(
@@ -136,14 +137,14 @@ export const useComponentMenus = <T extends DrawItem>(
   }
   operateMenus.push(
     {
-      label: `置顶`,
+      label: ui18n.t('sys.ztop'),
       handler() {
         data.value.zIndex = currentZIndex.max + 1;
         emit("updateShape", { ...data.value });
       },
     },
     {
-      label: `置底`,
+      label: ui18n.t('sys.zbot'),
       handler() {
         data.value.zIndex = currentZIndex.min - 1;
         emit("updateShape", { ...data.value });
@@ -152,7 +153,7 @@ export const useComponentMenus = <T extends DrawItem>(
   );
   if (!data.value.disableDelete) {
     operateMenus.push({
-      label: `删除`,
+      label: ui18n.t('sys.del'),
       handler() {
         emit("delShape");
       },

+ 28 - 20
src/core/hook/use-group.ts

@@ -1,9 +1,10 @@
-import { computed, reactive, Ref,  } from "vue";
+import { computed, reactive, Ref } from "vue";
 import { PropertyDescribes } from "../html-mount/propertys";
 import { installGlobalVar } from "./use-global-vars";
 import { inRevise, mergeFuns } from "@/utils/shared";
 import { useStore } from "../store";
 import { ShapeType } from "../components";
+import { ui18n } from "@/lang";
 
 export const useGlobalDescribes = installGlobalVar(() => {
   const shapesDescribes: Record<string, PropertyDescribes> = reactive({});
@@ -12,21 +13,21 @@ export const useGlobalDescribes = installGlobalVar(() => {
   return {
     set(item: { id: string }, descs: PropertyDescribes) {
       if (item.id in shapesSetCount) {
-        shapesSetCount[item.id]++
+        shapesSetCount[item.id]++;
       } else {
-        shapesSetCount[item.id] = 1
+        shapesSetCount[item.id] = 1;
       }
       shapesDescribes[item.id] = descs;
       data[item.id] = item;
     },
     del(id: string) {
       if (id in shapesSetCount) {
-        shapesSetCount[id]--
+        shapesSetCount[id]--;
       }
       if (shapesSetCount[id] === 0) {
         delete shapesDescribes[id];
-        delete shapesSetCount[id]
-        delete data[id]
+        delete shapesSetCount[id];
+        delete data[id];
       }
     },
     get(id: string) {
@@ -39,27 +40,33 @@ export const useGlobalDescribes = installGlobalVar(() => {
 
 export const useComponentsDescribes = (ids: Ref<string[]>) => {
   const gdesc = useGlobalDescribes();
-  const store = useStore()
+  const store = useStore();
   const excludeKeys = ["length", "name"];
   const groups = computed(() => {
-    return ids.value.map((id) => gdesc.get(id)).filter((item) => !!item).map(item => ({...item, type: store.getType(item.data.id)}));
+    return ids.value
+      .map((id) => gdesc.get(id))
+      .filter((item) => !!item)
+      .map((item) => ({ ...item, type: store.getType(item.data.id) }));
   });
   const similars = [
-    ['rectangle', 'circle', 'triangle', 'polygon']
-  ] as ShapeType[][]
+    ["rectangle", "circle", "triangle", "polygon"],
+  ] as ShapeType[][];
 
   const shareDescribes = computed(() => {
     if (groups.value.length === 0) {
       return {};
     }
-    const types = [...new Set(groups.value.map(item => item.type))] as ShapeType[]
+    const types = [
+      ...new Set(groups.value.map((item) => item.type)),
+    ] as ShapeType[];
     if (types.length > 1) {
-      if (!similars.some(similar => types.every(t => similar.includes(t)))) {
-        return
+      if (
+        !similars.some((similar) => types.every((t) => similar.includes(t)))
+      ) {
+        return;
       }
     }
 
-
     const shareDescribes: Record<
       string,
       { desc: PropertyDescribes[string][]; data: { id: string }[] }
@@ -93,10 +100,10 @@ export const useComponentsDescribes = (ids: Ref<string[]>) => {
     return shareDescribes;
   });
 
-  const stopWatchs: (() => void)[] = []
+  const stopWatchs: (() => void)[] = [];
   const mergeDesc = computed(() => {
-    mergeFuns(stopWatchs)()
-    stopWatchs.length = 0
+    mergeFuns(stopWatchs)();
+    stopWatchs.length = 0;
     const mergeDesc: Record<
       string,
       PropertyDescribes[string] & { joins: { id: string }[] }
@@ -117,13 +124,14 @@ export const useComponentsDescribes = (ids: Ref<string[]>) => {
         }
       }
       if (i !== descs.length) {
-        mergeDesc[key].label = mergeDesc[key].label + '(多值)'
-        value = undefined
+        mergeDesc[key].label =
+          mergeDesc[key].label + `(${ui18n.t("sys.sys.more")})`;
+        value = undefined;
       }
 
       Object.defineProperty(mergeDesc[key], "value", {
         get() {
-          return value
+          return value;
         },
         set(val) {
           for (const desc of descs) {

+ 1 - 1
src/core/html-mount/propertys/components/select.vue

@@ -2,7 +2,7 @@
   <el-select
     :model-value="value"
     @update:model-value="(value) => $emit('update:value', value)"
-    placeholder="选择"
+    :placeholder="$t('sys.placeholder.select')"
     @change="$emit('change')"
     style="width: 98px"
   >

+ 1 - 1
src/core/html-mount/propertys/components/text.vue

@@ -3,7 +3,7 @@
     :modelValue="value"
     style="width: 140px"
     @update:model-value="(value: string) => $emit('update:value', value)"
-    placeholder="请输入"
+    :placeholder="$t('sys.placeholder.input')"
     @change="$emit('change')"
   />
 </template>

+ 3 - 2
src/core/html-mount/propertys/describes.ts

@@ -1,10 +1,11 @@
 import { Ref } from "vue";
-import sysDescribes from "./sys-describes.json";
+import sysDescribes from "./sys-describes";
+import { ui18n } from "@/lang";
 
 export const getFixedStrokeWidthProperty = (self: Ref<{ strokeWidth?: number }>) => {
   const property = {
     type: "select",
-    label: "边框线宽",
+    label: ui18n.t('sys.strokeWidth2'),
     props: {
       options: [
         {

+ 2 - 2
src/core/html-mount/propertys/mount-describes.vue

@@ -3,7 +3,7 @@
     <transition name="mount-fade">
       <div class="mount-layout" v-if="show">
         <div v-if="name" class="title">
-          <h4>设置{{ name }}</h4>
+          <h4>{{ $t("sys.setting", { name }) }}</h4>
           <icon name="close" size="20px" class="operate" @click="emit('close')" />
         </div>
         <div :size="8" class="mount-controller">
@@ -40,7 +40,7 @@
         </div>
         <div class="mount-bottom" v-if="calDelete">
           <el-button type="danger" plain @click="emit('delete')" class="del-btn">
-            删除
+            {{ $t("sys.del") }}
           </el-button>
         </div>
       </div>

+ 34 - 32
src/core/html-mount/propertys/sys-describes.json

@@ -1,49 +1,51 @@
-{
+import { ui18n } from "@/lang";
+
+export default {
   "stroke": {
-    "type": "color",
-    "label": "边框色",
+    "type":  "color",
+    "label": ui18n.t('describes.stroke'),
     "layout-type": "column"
   },
   "name": {
     "type": "text",
-    "label": "名称",
+    "label": ui18n.t('describes.name'),
     "layout-type": "row"
   },
   "coverStroke": {
     "type": "color",
-    "label": "背景边框颜色",
+    "label": ui18n.t('describes.coverStroke'),
     "layout-type": "column"
   },
   "fill": {
     "type": "color",
-    "label": "填充色",
+    "label": ui18n.t('describes.fill'),
     "layout-type": "column"
   },
   "fontColor": {
     "type": "color",
-    "label": "文字颜色",
+    "label": ui18n.t('describes.fontColor'),
     "layout-type": "column"
   },
   "coverFill": {
     "type": "color",
-    "label": "背景颜色",
+    "label": ui18n.t('describes.coverFill'),
     "layout-type": "column"
   },
   "ref": {
     "type": "check",
-    "label": "参考物",
+    "label": ui18n.t('describes.ref'),
     "default": false,
     "layout-type": "row"
   },
   "strokeScaleEnabled": {
     "type": "check",
-    "label": "边框粗细随缩放",
+    "label": ui18n.t('describes.strokeScaleEnabled'),
     "default": false,
     "layout-type": "row"
   },
   "strokeWidth": {
     "type": "num",
-    "label": "边框粗细",
+    "label": ui18n.t('describes.strokeWidth'),
     "default": 1,
     "props": {
       "min": 1,
@@ -54,7 +56,7 @@
   },
   "rotate": {
     "type": "num",
-    "label": "旋转角度",
+    "label": ui18n.t('describes.rotate'),
     "default": 0,
     "props": {
       "min": -180,
@@ -64,7 +66,7 @@
   },
   "coverStrokeWidth": {
     "type": "num",
-    "label": "背景边框粗细",
+    "label": ui18n.t('describes.coverStrokeWidth'),
     "default": 0,
     "props": {
       "min": 0.5,
@@ -74,7 +76,7 @@
   },
   "opacity": {
     "type": "num",
-    "label": "不透明度",
+    "label": ui18n.t('describes.opacity'),
     "props": {
       "min": 0,
       "max": 1,
@@ -85,7 +87,7 @@
   },
   "coverOpcatiy": {
     "type": "num",
-    "label": "背景不透明度",
+    "label": ui18n.t('describes.coverOpcatiy'),
     "props": {
       "min": 0,
       "max": 1,
@@ -96,7 +98,7 @@
   },
   "zIndex": {
     "type": "num",
-    "label": "层叠层级",
+    "label": ui18n.t('describes.zIndex'),
     "props": {
       "min": -1000,
       "max": 1000,
@@ -107,7 +109,7 @@
   },
   "pointerLength": {
     "type": "num",
-    "label": "箭头大小",
+    "label": ui18n.t('describes.pointerLength'),
     "props": {
       "min": 0.5,
       "max": 10
@@ -116,19 +118,19 @@
   },
   "align": {
     "type": "select",
-    "label": "对齐方式",
+    "label": ui18n.t('describes.align.name'),
     "props": {
       "options": [
         {
-          "label": "居中对齐",
+          "label": ui18n.t('describes.align.center'),
           "value": "center"
         },
         {
-          "label": "左对齐",
+          "label": ui18n.t('describes.align.left'),
           "value": "left"
         },
         {
-          "label": "右对齐",
+          "label": ui18n.t('describes.align.right'),
           "value": "right"
         }
       ]
@@ -137,7 +139,7 @@
   },
   "fontSize": {
     "type": "num",
-    "label": "文字大小",
+    "label": ui18n.t('describes.fontSize'),
     "props": {
       "min": 1,
       "step": 1,
@@ -147,23 +149,23 @@
   },
   "fontStyle": {
     "type": "select",
-    "label": "字体样式",
+    "label": ui18n.t('describes.fontStyle.name'),
     "props": {
       "options": [
         {
-          "label": "默认",
+          "label": ui18n.t('describes.fontStyle.normal'),
           "value": "normal"
         },
         {
-          "label": "斜体",
+          "label": ui18n.t('describes.fontStyle.italic'),
           "value": "italic"
         },
         {
-          "label": "粗体",
+          "label": ui18n.t('describes.fontStyle.bold'),
           "value": "bold"
         },
         {
-          "label": "粗斜体",
+          "label": ui18n.t('describes.fontStyle.italicBold'),
           "value": "italic bold"
         }
       ]
@@ -172,7 +174,7 @@
   },
   "dash": {
     "type": "proportion",
-    "label": "虚线比例",
+    "label": ui18n.t('describes.dash'),
     "props": {
       "scale": 30
     },
@@ -184,19 +186,19 @@
   },
   "pointerPosition": {
     "type": "select",
-    "label": "箭头方向",
+    "label": ui18n.t('describes.pointerPosition.name'),
     "props": {
       "options": [
         {
-          "label": "起点",
+          "label": ui18n.t('describes.pointerPosition.start'),
           "value": "start"
         },
         {
-          "label": "终点",
+          "label": ui18n.t('describes.pointerPosition.end'),
           "value": "end"
         },
         {
-          "label": "全部",
+          "label": ui18n.t('describes.pointerPosition.all'),
           "value": "all"
         }
       ]

+ 2 - 1
src/example/components/container/container.vue

@@ -31,6 +31,7 @@ import { listener } from "@/utils/event.ts";
 import { ElMessage, ElButton } from "element-plus";
 import { mergeFuns, startAnimation } from "@/utils/shared";
 import { installDraw } from "./use-draw";
+import { ui18n } from "@/lang";
 
 const props = defineProps<{
   full: boolean;
@@ -48,7 +49,7 @@ watch(
     const hideMsg =
       props.full &&
       ElMessage.warning({
-        message: "按ESC键可退出全屏模式",
+        message: ui18n.t('sys.full.exit'),
         duration: 3000,
       });
 

+ 9 - 8
src/example/components/header/actions.ts

@@ -4,6 +4,7 @@ import { animation } from "@/core/hook/use-animation";
 import saveAs from "@/utils/file-serve";
 import { ElMessage } from "element-plus";
 import { Mode } from "@/constant/mode";
+import { ui18n } from "@/lang";
 
 export type Action = {
   handler?: (draw: Draw) => void;
@@ -47,13 +48,13 @@ export const getHeaderActions = (draw: Draw) => {
   return {
     undo: reactive({
       handler: () => draw.history.undo(),
-      text: "撤销",
+      text: ui18n.t('sys.actions.undo'),
       icon: "undo",
       disabled: computed(() => !draw.history.hasUndo.value),
     }),
     redo: reactive({
       handler: () => draw.history.redo(),
-      text: "恢复",
+      text: ui18n.t('sys.actions.redo'),
       icon: "redo",
       disabled: computed(() => !draw.history.hasRedo.value),
     }),
@@ -65,17 +66,17 @@ export const getHeaderActions = (draw: Draw) => {
         draw.store.clear();
       },
       disabled: computed(() => draw.drawing),
-      text: "清空",
+      text: ui18n.t('sys.actions.clear'),
       icon: "clear",
     }),
     rotateView: reactive({
       handler: () => rotateView(draw),
-      text: "旋转画布",
+      text: ui18n.t('sys.actions.rotate'),
       icon: "rotate",
     }),
     initViewport: reactive({
       handler: () => draw.initViewport(),
-      text: "适应视图",
+      text: ui18n.t('sys.actions.a_adapt'),
       icon: "a_adapt",
     }),
     toggleShow: reactive({
@@ -90,14 +91,14 @@ export const getHeaderActions = (draw: Draw) => {
           });
         });
       },
-      text: computed(() => (floorCoversHide.value ? "显示底图" : "隐藏底图")),
+      text: computed(() => (floorCoversHide.value ? ui18n.t('sys.actions.floor.show') : ui18n.t('sys.actions.floor.hide'))),
       icon: computed(() => "visible___" + (floorCoversHide.value ? "n" : "s")),
       disabled: computed(() => !floorCovers.value.length)
     }),
     expose: reactive({
       disabled: computed(() => draw.drawing),
       handler: () => {},
-      text: "导出",
+      text: ui18n.t('sys.actions.download.name'),
       icon: "download",
       children: [
         {
@@ -136,7 +137,7 @@ export const getHeaderActions = (draw: Draw) => {
               pop();
               draw.config.back = oldBack;
               draw.config.showGrid = oldShowGrid;
-              ElMessage.success("导出成功");
+              ElMessage.success(ui18n.t('sys.actions.download.success'));
             });
           },
           text: "PNG",

+ 1 - 1
src/example/components/show-vr.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="vr" :style="{ width: width + 'px', height: height + 'px' }" ref="vrRef">
     <div class="header">
-      <span>VR全景</span>
+      <span>{{ $t("vr.name") }}</span>
       <Icon class="close" name="close" @click="$emit('close')" />
     </div>
     <div class="content" :style="{ pointerEvents: downPos ? 'none' : 'all' }">

+ 13 - 12
src/example/components/slide/actions.ts

@@ -21,6 +21,7 @@ import { getBaseItem } from "@/core/components/util";
 import { loading } from "@/example/loadding";
 import { overviewMMToPixel } from "@/example/constant";
 import { Transform } from "konva/lib/Util";
+import { ui18n } from "@/lang";
 
 export type PresetAdd<T extends ShapeType = ShapeType> = {
   type: T;
@@ -38,7 +39,7 @@ const genDrawItem = <T extends ShapeType>(
 
 export const draw: MenuItem = {
   icon: "line_d",
-  name: "绘制",
+  name: ui18n.t('sys.actions.draw.name'),
   value: uuid(),
   defSelect: true,
   children: [
@@ -54,7 +55,7 @@ export const draw: MenuItem = {
 
 export const text: MenuItem = {
   icon: "text",
-  ...genDrawItem("text", { content: "文本" }),
+  ...genDrawItem("text", { content: ui18n.t('sys.actions.draw.text')}),
   single: true,
 };
 
@@ -71,13 +72,13 @@ export const serial: MenuItem = {
 
 export const imp: MenuItem = {
   icon: "import",
-  name: "导入",
+  name: ui18n.t('sys.actions.import.name'),
   value: uuid(),
   children: [
     {
       value: uuid(),
       icon: "scene_i",
-      name: "场景",
+      name: ui18n.t('sys.actions.import.scene'),
       handler: async (draw: Draw) => {
         const aiData = await selectAI();
         console.log(aiData);
@@ -87,7 +88,7 @@ export const imp: MenuItem = {
     {
       value: uuid(),
       icon: "map",
-      name: "地图",
+      name: ui18n.t('sys.actions.import.map'),
       handler: async (draw: Draw) => {
         const tileGroups = (await loading(
           window.platform.getTileGroups()
@@ -100,7 +101,7 @@ export const imp: MenuItem = {
         const realSize = result.info.size;
         const scale = 1000 * overviewMMToPixel;
 
-        ElMessage.warning("请在画图面板中选择放置位置,鼠标右键取消");
+        ElMessage.warning(ui18n.t('sys.actions.import.tip'));
         draw.enterDrawShape(
           "image",
           {
@@ -120,7 +121,7 @@ export const imp: MenuItem = {
     {
       value: uuid(),
       icon: "local_i",
-      name: "本地",
+      name: ui18n.t('sys.actions.import.local.name'),
       handler: async (draw: Draw) => {
         let files: File[];
         try {
@@ -132,13 +133,13 @@ export const imp: MenuItem = {
           return;
         }
         if (files[0].size >= 100 * 1024 * 1024) {
-          ElMessage.error("图片大小不超过100MB");
+          ElMessage.error(ui18n.t('sys.actions.import.image.maxTip'));
           return;
         }
 
         const url = await window.platform.uploadResourse(files[0]);
         const image = await getImage(window.platform.getResource(url));
-        ElMessage.warning("请在画图面板中选择放置位置,鼠标右键取消");
+        ElMessage.warning(ui18n.t('sys.actions.import.local.tip'));
         draw.enterDrawShape(
           "image",
           {
@@ -201,7 +202,7 @@ const setPaper = (draw: Draw, p: number[], scale: number) => {
 
 export const paper = {
   icon: "drawing",
-  name: "纸张",
+  name: ui18n.t('sys.actions.paper.ma,e'),
   type: "sub-menu-horizontal",
   value: uuid(),
   children: [
@@ -216,7 +217,7 @@ export const paper = {
       value: uuid(),
       icon: "A4_h",
       key: "a4",
-      name: "A4横版",
+      name: ui18n.t('sys.actions.paper.A4_h'),
       handler: (draw: Draw) =>
         setPaper(draw, paperConfigs.a4.size, paperConfigs.a4.scale),
     },
@@ -231,7 +232,7 @@ export const paper = {
       value: uuid(),
       icon: "A3_h",
       key: "a3",
-      name: "A3横版",
+      name: ui18n.t('sys.actions.paper.A3_h'),
       handler: (draw: Draw) =>
         setPaper(draw, paperConfigs.a3.size, paperConfigs.a3.scale),
     },

+ 7 - 2
src/example/components/slide/slide-icons.vue

@@ -1,6 +1,11 @@
 <template>
   <div class="icon-layout">
-    <ElInput v-model="keyword" placeholder="搜索图例" class="search" style="height: 34px">
+    <ElInput
+      v-model="keyword"
+      :placeholder="$t('icons.search')"
+      class="search"
+      style="height: 34px"
+    >
       <template #prefix>
         <icon name="Search" size="16px" />
       </template>
@@ -29,7 +34,7 @@
           </div>
         </div>
       </ElCollapseItem>
-      <el-empty description="暂无匹配结果" v-else />
+      <el-empty :description="$t('icons.undata')" v-else />
     </ElCollapse>
   </div>
 </template>

+ 107 - 119
src/example/constant.ts

@@ -1,4 +1,5 @@
 import { LineIconData } from "@/core/components/line-icon";
+import { ui18n } from "@/lang";
 
 export type IconItem = {
   wall?: boolean;
@@ -22,211 +23,199 @@ export type IconGroup = {
   }[];
 };
 
-const traceIcons = [
-  { icon: "zhiwen_o", name: "手印" },
-  { icon: "zuozuji_o", name: "脚印" },
-  { icon: "youzuji_o", name: "脚印" },
-  { icon: "xieyin_o", name: "鞋印" },
-  { icon: "chelunhenji_o", name: "车轮印" },
-  { icon: "dantou_o", name: "弹头" },
-  { icon: "danke_o", name: "弹壳" },
-  { icon: "shouqiang_o", name: "手枪" },
-  { icon: "buqiang_o", name: "步枪" },
+// 辅助函数:快速获取项的国际化名称
+const getIT = (icon: string) => ui18n.t(`icons.items.${icon}`);
+// 辅助函数:快速获取组的国际化名称
+const getGT = (key: string) => ui18n.t(`icons.groups.${key}`);
+
+const traceIcons: IconItem[] = [
+  { icon: "zhiwen_o", name: getIT("zhiwen_o") },
+  { icon: "zuozuji_o", name: getIT("zuozuji_o") },
+  { icon: "youzuji_o", name: getIT("youzuji_o") },
+  { icon: "xieyin_o", name: getIT("xieyin_o") },
+  { icon: "chelunhenji_o", name: getIT("chelunhenji_o") },
+  { icon: "dantou_o", name: getIT("dantou_o") },
+  { icon: "danke_o", name: getIT("danke_o") },
+  { icon: "shouqiang_o", name: getIT("shouqiang_o") },
+  { icon: "buqiang_o", name: getIT("buqiang_o") },
   {
     icon: "xuepo_o",
-    name: "血泊",
+    name: getIT("xuepo_o"),
     color: "#DD2C2C",
     parse: { fill: "#DD2C2C", stroke: undefined },
   },
   {
     icon: "xueji_o",
-    name: "血迹",
+    name: getIT("xueji_o"),
     color: "#DD2C2C",
     parse: { fill: "#DD2C2C", stroke: undefined },
   },
-  { parse: { key: "trace" }, icon: "shitiz_o", name: "尸体正面" },
-  { parse: { key: "trace" }, icon: "shitib_o", name: "尸体背面" },
-  { parse: { key: "trace" }, icon: "shitifuhao_o", name: "尸体" },
+  { parse: { key: "trace" }, icon: "shitiz_o", name: getIT("shitiz_o") },
+  { parse: { key: "trace" }, icon: "shitib_o", name: getIT("shitib_o") },
+  { parse: { key: "trace" }, icon: "shitifuhao_o", name: getIT("shitifuhao_o") },
 
-  { parse: { key: "trace" }, icon: "handprint", name: "手印痕迹" },
-  { parse: { key: "trace" }, icon: "footprint", name: "足迹痕迹" },
+  { parse: { key: "trace" }, icon: "handprint", name: getIT("handprint") },
+  { parse: { key: "trace" }, icon: "footprint", name: getIT("footprint") },
   {
     parse: { key: "trace" },
     icon: "wrenchAndScrewdriver",
-    name: "工具痕迹",
+    name: getIT("wrenchAndScrewdriver"),
   },
-  { parse: { key: "trace" }, icon: "video", name: "视听物证" },
-  { parse: { key: "trace" }, icon: "virus", name: "生物物证" },
-  { parse: { key: "trace" }, icon: "cartridge", name: "枪弹痕迹" },
-  { parse: { key: "trace" }, icon: "poison", name: "毒化物证" },
+  { parse: { key: "trace" }, icon: "video", name: getIT("video") },
+  { parse: { key: "trace" }, icon: "virus", name: getIT("virus") },
+  { parse: { key: "trace" }, icon: "cartridge", name: getIT("cartridge") },
+  { parse: { key: "trace" }, icon: "poison", name: getIT("poison") },
   {
     parse: { key: "trace" },
     icon: "physics_and_chemistry",
-    name: "理化物证",
+    name: getIT("physics_and_chemistry"),
   },
-  { parse: { key: "trace" }, icon: "folder_close", name: "文捡物证" },
-  { parse: { key: "trace" }, icon: "check", name: "特殊物证" },
+  { parse: { key: "trace" }, icon: "folder_close", name: getIT("folder_close") },
+  { parse: { key: "trace" }, icon: "check", name: getIT("check") },
   {
     parse: { key: "trace" },
     icon: "case_other",
-    name: "其他物证",
+    name: getIT("case_other"),
   },
-  { parse: { key: "trace" }, icon: "electronic", name: "电子物证" },
-  { parse: { key: "trace" }, icon: "cube", name: "提取物品" },
-  { parse: { key: "trace" }, icon: "corpse", name: "尸体" },
+  { parse: { key: "trace" }, icon: "electronic", name: getIT("electronic") },
+  { parse: { key: "trace" }, icon: "cube", name: getIT("cube") },
+  { parse: { key: "trace" }, icon: "corpse", name: getIT("corpse") },
 ];
+
 export const iconGroups: IconGroup[] = [
   {
-    name: "户型",
+    name: getGT("huxing"),
     children: [
       {
-        name: "门",
+        name: getGT("men"),
         children: [
           {
             wall: true,
             icon: "men_l",
-            name: "开门",
+            name: getIT("men_l"),
             parse: { type: "align-bottom" },
           },
-          // {
-          //   wall: true,
-          //   icon: "men",
-          //   name: "右开门",
-          //   parse: { type: "align-bottom" },
-          // },
           {
             wall: true,
             icon: "shuangkaimen",
-            name: "双开门",
+            name: getIT("shuangkaimen"),
             parse: { type: "align-bottom" },
           },
           {
             wall: true,
             icon: "yimen",
-            name: "移门",
+            name: getIT("yimen"),
             parse: { type: "full" },
           },
-          { wall: true, icon: "yakou", name: "哑口", parse: { type: "full" } },
+          { wall: true, icon: "yakou", name: getIT("yakou"), parse: { type: "full" } },
         ],
       },
       {
-        name: "窗",
+        name: getGT("chuang"),
         children: [
-          { wall: true, icon: "chuang", name: "窗", parse: { type: "full" } },
+          { wall: true, icon: "chuang", name: getIT("chuang"), parse: { type: "full" } },
           {
             wall: true,
             icon: "piaochuang",
-            name: "飘窗",
+            name: getIT("piaochuang"),
             parse: { type: "align-bottom-fix", width: 180, height: 70 },
           },
           {
             wall: true,
             icon: "luodichuang",
-            name: "落地窗",
+            name: getIT("luodichuang"),
             parse: { type: "full" },
           },
         ],
       },
       {
-        name: "构件",
+        name: getGT("goujian"),
         children: [
-          { icon: "zhuzi", name: "柱子" },
-          { icon: "yandao", name: "烟道" },
-          { icon: "loudao", name: "楼道" },
+          { icon: "zhuzi", name: getIT("zhuzi") },
+          { icon: "yandao", name: getIT("yandao") },
+          { icon: "loudao", name: getIT("loudao") },
         ],
       },
     ],
   },
   {
-    name: "家具",
+    name: getGT("jiaju"),
     children: [
       {
-        name: "客餐厅",
+        name: getGT("kecan"),
         children: [
-          { icon: "TV", name: "电视柜" },
-          { icon: "HangingTV", name: "电视-壁挂" },
-          { icon: "Cabinet", name: "柜子" },
-
-
-          { icon: "CombinationSofa", name: "组合沙发" },
-          { icon: "ThreeSofa", name: "三人沙发" },
-          { icon: "SingleSofa", name: "单人沙发" },
-
-          { icon: "SingleSofaR", name: "组合沙发" },
-          { icon: "CurvedSofa", name: "弧形沙发2.2m" },
-          { icon: "CornerSofa", name: "转角沙发2.7m" },
-          { icon: "TwoSofa", name: "双人沙发1.8m" },
-
-
-          { icon: "TeaTable", name: "茶几" },
-          { icon: "Carpet", name: "地毯" },
-          { icon: "Plant", name: "植物" },
-
-          { icon: "TeaTableR", name: "茶几-圆形" },
-          { icon: "TeaTableO", name: "茶几-椭圆" },
-          { icon: "SideTableR", name: "边几-圆形" },
-          { icon: "SideTableS", name: "边几-方形" },
-
-
-          { icon: "DiningTableC", name: "餐桌椅" }, 
-          { icon: "Chair", name: "椅子" },
-
-          { icon: "DiningTableRC", name: "餐桌" },
-          { icon: "DiningTable", name: "餐桌" },
-          { icon: "DiningTableR", name: "餐桌-圆" },
-          { icon: "Stool", name: "条凳" },
-
+          { icon: "TV", name: getIT("TV") },
+          { icon: "HangingTV", name: getIT("HangingTV") },
+          { icon: "Cabinet", name: getIT("Cabinet") },
+          { icon: "CombinationSofa", name: getIT("CombinationSofa") },
+          { icon: "ThreeSofa", name: getIT("ThreeSofa") },
+          { icon: "SingleSofa", name: getIT("SingleSofa") },
+          { icon: "SingleSofaR", name: getIT("SingleSofaR") },
+          { icon: "CurvedSofa", name: getIT("CurvedSofa") },
+          { icon: "CornerSofa", name: getIT("CornerSofa") },
+          { icon: "TwoSofa", name: getIT("TwoSofa") },
+          { icon: "TeaTable", name: getIT("TeaTable") },
+          { icon: "Carpet", name: getIT("Carpet") },
+          { icon: "Plant", name: getIT("Plant") },
+          { icon: "TeaTableR", name: getIT("TeaTableR") },
+          { icon: "TeaTableO", name: getIT("TeaTableO") },
+          { icon: "SideTableR", name: getIT("SideTableR") },
+          { icon: "SideTableS", name: getIT("SideTableS") },
+          { icon: "DiningTableC", name: getIT("DiningTableC") }, 
+          { icon: "Chair", name: getIT("Chair") },
+          { icon: "DiningTableRC", name: getIT("DiningTableRC") },
+          { icon: "DiningTable", name: getIT("DiningTable") },
+          { icon: "DiningTableR", name: getIT("DiningTableR") },
+          { icon: "Stool", name: getIT("Stool") },
         ],
       },
       {
-        name: "卧室",
+        name: getGT("woshi"),
         children: [
-          { icon: "DoubleBed", name: "双人床" },
-          { icon: "SingleBed", name: "单人床" },
-          { icon: "Wardrobe", name: "衣柜" },
-          { icon: "DoubleBedB", name: "双人床1.8m " },
-
-          { icon: "Dresser", name: "梳妆台" },
-          { icon: "BedsideCupboard", name: "床头柜" },
-          { icon: "Pillow", name: "抱枕" },
+          { icon: "DoubleBed", name: getIT("DoubleBed") },
+          { icon: "SingleBed", name: getIT("SingleBed") },
+          { icon: "Wardrobe", name: getIT("Wardrobe") },
+          { icon: "DoubleBedB", name: getIT("DoubleBedB") },
+          { icon: "Dresser", name: getIT("Dresser") },
+          { icon: "BedsideCupboard", name: getIT("BedsideCupboard") },
+          { icon: "Pillow", name: getIT("Pillow") },
         ],
       },
       {
-        name: "厨卫",
+        name: getGT("chuwei"),
         children: [
-          { icon: "GasStove", name: "燃气灶" },
-          { icon: "Cupboard", name: "橱柜" },
-          { icon: "Bathtub", name: "浴缸" },
-          { icon: "Closestool", name: "马桶" },
-          { icon: "Washstand", name: "洗漱台" },
-
-          { icon: "CupboardU", name: "通用橱柜" },
-          { icon: "WaterChannel", name: "水槽" },
-          { icon: "WaterChannelD", name: "双水槽" },
-          { icon: "FridgeS", name: "冰箱" },
-          { icon: "FridgeD", name: "冰箱-双门" },
-          { icon: "CabinetB", name: "浴室柜" },
-          { icon: "SquattingPan", name: "蹲便器" },
-          { icon: "Shower", name: "花洒" },
+          { icon: "GasStove", name: getIT("GasStove") },
+          { icon: "Cupboard", name: getIT("Cupboard") },
+          { icon: "Bathtub", name: getIT("Bathtub") },
+          { icon: "Closestool", name: getIT("Closestool") },
+          { icon: "Washstand", name: getIT("Washstand") },
+          { icon: "CupboardU", name: getIT("CupboardU") },
+          { icon: "WaterChannel", name: getIT("WaterChannel") },
+          { icon: "WaterChannelD", name: getIT("WaterChannelD") },
+          { icon: "FridgeS", name: getIT("FridgeS") },
+          { icon: "FridgeD", name: getIT("FridgeD") },
+          { icon: "CabinetB", name: getIT("CabinetB") },
+          { icon: "SquattingPan", name: getIT("SquattingPan") },
+          { icon: "Shower", name: getIT("Shower") },
         ],
       },
       {
-        name: "其他",
+        name: getGT("qita"),
         children: [
-          { icon: "Desk", name: "书桌" },
-          { icon: "BalconyChair", name: "阳台椅" },
-          { icon: "MopPool", name: "拖把池" },
-          { icon: "WashingMachine", name: "洗衣机" },
-          { icon: "Elevator", name: "电梯" },
-          { icon: "WaterFountain", name: "饮水机" },
-          { icon: "AirConditioner", name: "空调-圆形" },
-          { icon: "Tablelamp", name: "台灯" },
+          { icon: "Desk", name: getIT("Desk") },
+          { icon: "BalconyChair", name: getIT("BalconyChair") },
+          { icon: "MopPool", name: getIT("MopPool") },
+          { icon: "WashingMachine", name: getIT("WashingMachine") },
+          { icon: "Elevator", name: getIT("Elevator") },
+          { icon: "WaterFountain", name: getIT("WaterFountain") },
+          { icon: "AirConditioner", name: getIT("AirConditioner") },
+          { icon: "Tablelamp", name: getIT("Tablelamp") },
         ],
       },
     ],
   },
   {
-    name: "痕迹物证",
+    name: getGT("henji"),
     children: [
       {
         name: "",
@@ -235,7 +224,6 @@ export const iconGroups: IconGroup[] = [
     ],
   },
 ];
-
 export const getIconItem = (icon: string) => {
   for (const group of iconGroups) {
     for (const itemGroup of group.children) {

+ 10 - 9
src/example/dialog/ai/ai.vue

@@ -1,8 +1,8 @@
 <template>
   <div style="padding: 20px 20px 40px">
-    <VR v-model:value="scene" class="vr-layout" label="请选择场景" />
+    <VR v-model:value="scene" class="vr-layout" :label="$t('sys.vr.dialog.title')" />
     <div v-if="floors?.length" class="tagging-layout">
-      <p class="title" style="margin-bottom: 10px">楼层</p>
+      <p class="title" style="margin-bottom: 10px">{{ $t("sys.vr.dialog.floor") }}</p>
 
       <el-select v-model="syncFloor" style="width: 100%">
         <el-option
@@ -17,7 +17,7 @@
       </el-radio-group> -->
     </div>
     <div v-if="scene" class="tagging-layout">
-      <p class="title">图例</p>
+      <p class="title">{{ $t("sys.vr.dialog.icon") }}</p>
       <ElCheckboxGroup v-model="syncTags" style="width: 200px">
         <ElCheckbox
           :label="item.label"
@@ -36,16 +36,17 @@ import { ElCheckboxGroup, ElCheckbox, ElMessage, ElSelect, ElOption } from "elem
 import { Scene, SCENE_TYPE } from "../../platform/platform-resource";
 import { getFloors } from "../../platform/resource-swkk";
 import { SelectSceneData } from ".";
+import { ui18n } from "@/lang";
 
 const scene = ref<Scene>();
 const options = {
   [SCENE_TYPE.mesh]: [
-    { value: "signage", label: "指示牌" },
-    { value: "traces", label: "痕迹物证" },
+    { value: "signage", label: ui18n.t("sys.vr.dialog.signage") },
+    { value: "traces", label: ui18n.t("sys.vr.dialog.traces") },
     // { value: "hot", label: "多媒体标签" },
   ],
-  [SCENE_TYPE.cloud]: [{ value: "hot", label: "热点" }],
-  [SCENE_TYPE.fuse]: [{ value: "hot", label: "标签" }],
+  [SCENE_TYPE.cloud]: [{ value: "hot", label: ui18n.t("sys.vr.dialog.hot") }],
+  [SCENE_TYPE.fuse]: [{ value: "hot", label: ui18n.t("sys.vr.dialog.hot") }],
 };
 const syncTags = ref<string[]>([]);
 const syncFloor = ref<string>();
@@ -76,11 +77,11 @@ defineExpose({
   disabled: computed(() => !scene.value),
   submit: async (): Promise<SelectSceneData> => {
     if (!scene.value) {
-      ElMessage.error("请选择同步场景");
+      ElMessage.error(ui18n.t("sys.vr.dialog.selectScene"));
       throw "请选择同步场景";
     }
     if (!syncFloor.value && floors.value.length) {
-      ElMessage.error("请选择楼层");
+      ElMessage.error(ui18n.t("sys.vr.dialog.selectFloor"));
       throw "请选择楼层";
     }
     return {

+ 3 - 2
src/example/dialog/ai/index.ts

@@ -1,6 +1,7 @@
 import { markRaw, reactive } from "vue";
 import AI from "./ai.vue";
 import { ResourceArgs } from "@/example/platform/platform-resource";
+import { ui18n } from "@/lang";
 
 export type SelectSceneData = Omit<ResourceArgs, 'scale'>
 type Props = {
@@ -14,14 +15,14 @@ type Props = {
 };
 
 export const props = reactive({
-  title: "全景VR",
+  title: ui18n.t('vr.name'),
   width: "500px",
 }) as Props;
 
 export const selectAI = () =>
   new Promise<SelectSceneData>((resolve, reject) => {
     props.content = markRaw(AI);
-    props.title = "选择场景";
+    props.title = ui18n.t('sys.vr.dialog.title');
     props.visiable = true;
     props.submit = (info) => {
       resolve(info);

+ 3 - 2
src/example/dialog/basemap/gd-index.ts

@@ -1,6 +1,7 @@
 import { markRaw, reactive } from "vue";
 import selectAMapImage from "./gd-map/selectAMapImage.vue";
 import { Size } from "@/utils/math";
+import { ui18n } from "@/lang";
 
 export type BasemapInfo = { blob: Blob; info?: MarkInfo, size: Size };
 
@@ -19,7 +20,7 @@ type Props = {
   cancel: () => void;
 };
 export const props = reactive({
-  title: "底图设置",
+  title: ui18n.t('background.setting.name'),
   width: "1252px",
 }) as Props;
 
@@ -28,7 +29,7 @@ export type MarkInfo = { lat: number; lng: number; text: string; zoom?: number }
 export const getAMapInfo = (args?: Props['args']) =>
   new Promise<BasemapInfo>((resolve, reject) => {
     props.content = markRaw(selectAMapImage);
-    props.title = "选择高德地图底图";
+    props.title = ui18n.t('background.setting.map');
     props.args = args
     props.visiable = true;
     props.submit = (info: BasemapInfo) => {

+ 20 - 6
src/example/dialog/basemap/gd-map/selectAMapImage.vue

@@ -2,7 +2,7 @@
   <div class="search-layout">
     <el-input
       v-model="keyword"
-      placeholder="输入名称或经纬度(如113.281272,23.117661)搜索"
+      :placeholder="$t('background.setting.input.place')"
       style="width: 400px"
       @focus="showSearch = true"
       clearable
@@ -28,9 +28,18 @@
   </div>
 
   <div class="def-map-info" v-if="mapInfo">
-    <p><span>纬度</span>{{ mapInfo.lat }}</p>
-    <p><span>经度</span>{{ mapInfo.lng }}</p>
-    <p><span>缩放级别</span>{{ mapInfo.zoom }}</p>
+    <p>
+      <span>{{ $t("background.setting.lat") }}</span
+      >{{ mapInfo.lat }}
+    </p>
+    <p>
+      <span>{{ $t("background.setting.lng") }}</span
+      >{{ mapInfo.lng }}
+    </p>
+    <p>
+      <span>{{ $t("background.setting.zoom") }}</span
+      >{{ mapInfo.zoom }}
+    </p>
   </div>
 </template>
 
@@ -41,6 +50,7 @@ import { ref, watch } from "vue";
 import { analysisGPS, debounce } from "@/utils/shared";
 import { Size } from "@/utils/math";
 import { BasemapInfo, MarkInfo } from "../gd-index";
+import { ui18n } from "@/lang";
 
 const props = withDefaults(
   defineProps<{
@@ -90,7 +100,7 @@ const initMap = async () => {
     onlyMarker({
       lat: e.lnglat.lat,
       lng: e.lnglat.lng,
-      text: "点击位置",
+      text: ui18n.t("background.setting.click"),
     });
   });
 };
@@ -155,7 +165,11 @@ const init = async () => {
       const gps = analysisGPS(keyword.value);
       if (gps) {
         isKeySearch.value = false;
-        onlyMarker({ lat: gps.y, lng: gps.x, text: "直接输入经纬度" });
+        onlyMarker({
+          lat: gps.y,
+          lng: gps.x,
+          text: ui18n.t("background.setting.inputLatLng"),
+        });
         console.log(gps);
       } else {
         isKeySearch.value = true;

+ 20 - 12
src/example/dialog/basemap/index.ts

@@ -3,20 +3,28 @@ import selectMapImage from "./leaflet/index.vue";
 import { Size } from "@/utils/math";
 import { Tile } from "./leaflet/useLeaflet";
 import { LatLng } from "leaflet";
+import { ui18n } from "@/lang";
 
-export type SearchResultItem = { name: string, address: string, latlng: LatLng }
-export type SearchAPI = (keyword: string, mapId: number) => Promise<SearchResultItem[]>
+export type SearchResultItem = {
+  name: string;
+  address: string;
+  latlng: LatLng;
+};
+export type SearchAPI = (
+  keyword: string,
+  mapId: number,
+) => Promise<SearchResultItem[]>;
 export type BasemapInfo = { blob: Blob; size: Size };
 export type TileGroup = {
-  id: number
+  id: number;
   tiles: Tile[];
   name: string;
 };
 export type SelectMapImageProps = {
-  tileGroups: TileGroup[]
+  tileGroups: TileGroup[];
   activeGroupIndex?: number;
-  search: SearchAPI
-}
+  search: SearchAPI;
+};
 
 type Props = {
   title: string;
@@ -24,27 +32,27 @@ type Props = {
   visiable: boolean;
   height?: string;
   content: any;
-  args?: SelectMapImageProps,
+  args?: SelectMapImageProps;
   submit: (info: BasemapInfo) => void;
   cancel: () => void;
 };
 export const props = reactive({
-  title: "选择地图位置",
+  title: ui18n.t("background.setting.select"),
   width: "1200px",
 }) as Props;
 
 export const getMapInfo = (args: SelectMapImageProps) =>
   new Promise<BasemapInfo>((resolve, reject) => {
     props.content = markRaw(selectMapImage);
-    props.title = "选择地图位置";
-    props.args = args
+    props.title = ui18n.t("background.setting.select");
+    props.args = args;
     props.visiable = true;
     props.submit = (info: BasemapInfo) => {
       resolve(info);
-      props.visiable = false
+      props.visiable = false;
     };
     props.cancel = () => {
       reject("cancel");
-      props.visiable = false
+      props.visiable = false;
     };
   });

+ 16 - 12
src/example/dialog/basemap/leaflet/index.vue

@@ -4,7 +4,9 @@
       <el-input
         v-model="keyword"
         :placeholder="
-          searchType === 'latlng' ? '如39.909187,116.397463' : `请输入${searchName}搜索`
+          searchType === 'latlng'
+            ? $t('background.setting.input.place1')
+            : $t('background.setting.input.place2', { searchName })
         "
         class="input-with-select"
         @keydown.enter="searchHandler"
@@ -26,16 +28,13 @@
         :class="{ success: mark }"
       >
         <template v-if="mark">
-          <h3>经纬度定位成功</h3>
-          <p>纬度:{{ mark.lat }}</p>
-          <p>经度:{{ mark.lng }}</p>
+          <h3>{{$t('background.setting.latlng.success')}}</h3>
+          <p>{{ $t("background.setting.lat") }}:{{ mark.lat }}</p>
+          <p>{{ $t("background.setting.lng") }}:{{ mark.lng }}</p>
         </template>
         <template v-else>
-          <h3>经纬度格式错误</h3>
-          <p>请输入正确的经纬度格式</p>
-          <p>纬度,经度 (例如23.11766,113.28122)</p>
-          <p>纬度范围:-90到90</p>
-          <p>经度范围:-180到180</p>
+          <h3>{{$t('background.setting.latlng.errname')}}</h3>
+          <div class="err-tip" v-html="$t('background.setting.latlng.errtip')"></div>
         </template>
       </div>
 
@@ -51,7 +50,7 @@
             {{ option.address }}
           </div>
         </template>
-        <p v-else style="text-align: center">暂无数据</p>
+        <p v-else style="text-align: center">{{ $t('icons.undata') }}</p>
       </div>
     </div>
 
@@ -78,6 +77,7 @@ import { BasemapInfo, SearchResultItem, SelectMapImageProps } from "../index";
 import html2canvas from "html2canvas";
 import { ElInput, ElSelect, ElOption } from "element-plus";
 import { asyncTimeout } from "@/utils/shared";
+import { ui18n } from "@/lang";
 
 const props = defineProps<SelectMapImageProps>();
 
@@ -97,8 +97,8 @@ const tiles = computed(() => props.tileGroups[groupIndex.value].tiles);
 watchEffect(() => setTileLayers(tiles.value));
 
 const searchTypes = [
-  { label: "地址", value: "name" },
-  { label: "经纬度", value: "latlng" },
+  { label: ui18n.t('background.map.name'), value:  "name" },
+  { label: ui18n.t('background.map.latlng'), value: "latlng" },
 ];
 const searchType = ref(searchTypes[1].value as "latlng" | "name");
 const searchName = computed(
@@ -245,6 +245,7 @@ defineExpose({ submit });
   justify-content: center;
   padding: 16px;
 
+  .err-tip,
   p {
     font-size: 14px;
     color: #a7a7a7;
@@ -252,6 +253,9 @@ defineExpose({ submit });
     min-width: 144px;
     text-align: left;
   }
+  .err-tip {
+    white-space: pre-wrap;
+  }
 
   h3 {
     color: #f56c6c;

+ 2 - 2
src/example/dialog/dialog.vue

@@ -16,13 +16,13 @@
     </div>
     <template #footer>
       <div class="dialog-footer">
-        <el-button @click="props.cancel">取消</el-button>
+        <el-button @click="props.cancel">{{ $t("sys.cancel") }}</el-button>
         <el-button
           type="primary"
           @click="submits[i]()"
           :disabled="contentRefs[i]?.disabled"
         >
-          确定
+          {{ $t("sys.enter") }}
         </el-button>
       </div>
     </template>

+ 4 - 4
src/example/dialog/expose/expose-format.vue

@@ -6,16 +6,16 @@
       style="max-width: 600px"
       label-position="left"
     >
-      <el-form-item label="格式:">
+      <el-form-item :label="`${$t('sys.actions.download.format')}:`">
         <el-radio-group v-model="data.format">
           <el-radio value="JPEG">JPG</el-radio>
           <el-radio value="PDF">PDF</el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="颜色:">
+      <el-form-item :label="`${$t('sys.actions.download.color')}:`">
         <el-radio-group v-model="data.color">
-          <el-radio value="grayscale">黑白</el-radio>
-          <el-radio value="raw">彩色</el-radio>
+          <el-radio value="grayscale">{{$t('sys.actions.download.grayscale')}}</el-radio>
+          <el-radio value="raw">{{$t('sys.actions.download.raw')}}</el-radio>
         </el-radio-group>
       </el-form-item>
     </el-form>

+ 3 - 2
src/example/dialog/expose/index.ts

@@ -1,5 +1,6 @@
 import { markRaw, reactive } from "vue";
 import ExposeFormat from "./expose-format.vue";
+import { ui18n } from "@/lang";
 
 type Props = {
   title: string;
@@ -15,14 +16,14 @@ type Props = {
 export type ExposeFormatData = {format: string, color: 'raw' | 'grayscale' }
 
 export const props = reactive({
-  title: "导出设置",
+  title: ui18n.t('sys.action.download.setting'),
   width: "500px",
 }) as Props;
 
 export const selectExposeFormat = (args?: ExposeFormatData) =>
   new Promise<ExposeFormatData>((resolve, reject) => {
     props.content = markRaw(ExposeFormat);
-    props.title = "导出设置";
+    props.title = ui18n.t('sys.action.download.setting');
     props.visiable = true;
     props.args = {ef: args}
     props.submit = (info) => {

+ 3 - 2
src/example/dialog/vr/index.ts

@@ -1,6 +1,7 @@
 import { markRaw, reactive } from "vue";
 import VR from "./vr.vue";
 import { Scene } from "../../platform/platform-resource";
+import { ui18n } from "@/lang";
 
 type Props = {
   title: string;
@@ -12,14 +13,14 @@ type Props = {
   cancel: () => void;
 };
 export const props = reactive({
-  title: "全景VR",
+  title: ui18n.t('vr.name'),
   width: "500px",
 }) as Props;
 
 export const selectScene = () =>
   new Promise<Scene>((resolve, reject) => {
     props.content = markRaw(VR);
-    props.title = "全景VR";
+    props.title = ui18n.t('vr.name');
     props.visiable = true;
     props.submit = (info) => {
       resolve(info);

+ 2 - 2
src/example/dialog/vr/vr.vue

@@ -1,7 +1,7 @@
 <template>
   <div style="padding: 20px 20px 40px">
     <p class="title">
-      {{ label ? label : "选择场景" }}
+      {{ label ? label : $t('sys.vr.dialog.title') }}
     </p>
     <el-select
       :model-value="value?.id"
@@ -9,7 +9,7 @@
       filterable
       remote
       reserve-keyword
-      placeholder="请输入"
+      :placeholder="$t('sys.placeholder.input')"
       clearable
       :remote-method="searchSenects"
       remote-show-suffix

+ 2 - 1
src/example/fuse/enter-mix.ts

@@ -2,6 +2,7 @@ import { params, token } from "../env";
 import { post, login } from "./enter-shared";
 import './enter-case'
 import { ElMessage } from "element-plus";
+import { ui18n } from "@/lang";
 
 let isLoging = false;
 window.platform.login = (isBack = true) => {
@@ -30,7 +31,7 @@ window.platform.login = (isBack = true) => {
 
 setTimeout(() => {
   if (!params.value.caseId || !token) {
-    ElMessage.error("当前项目号不存在!");
+    ElMessage.error(ui18n.t('sys.unproject'));
   } else {
     window.platform.getSceneList("");
   }

+ 18 - 17
src/example/fuse/enter-shared.ts

@@ -14,6 +14,7 @@ import { tableTitleKey } from '../constant'
 import { getPaperConfig, paperConfigs } from "../components/slide/actions";
 import { getBaseItem } from "@/core/components/util";
 import { getRealPixel } from "./views/tabulation/gen-tab";
+import { ui18n } from "@/lang";
 
 
 export const SCENE_TYPE = {
@@ -114,12 +115,12 @@ const after = async (fet: Promise<Response>) => {
     if (response.status === 404) {
       res = {
         code: 404,
-        message: "网络异常",
+        message: ui18n.t('res.code.404'),
       };
     } else {
       res = {
         code: 500,
-        message: "服务异常",
+        message: ui18n.t('res.code.500'),
       };
     }
   } else {
@@ -128,7 +129,7 @@ const after = async (fet: Promise<Response>) => {
     } catch {
       res = {
         code: 500,
-        message: "服务异常",
+        message: ui18n.t('res.code.500'),
       };
     }
   }
@@ -151,7 +152,7 @@ const after = async (fet: Promise<Response>) => {
   }
   // 特殊code 不跳转
   if ([8035].includes(res.code)) {
-    throw "存储路径不可用,请前往设置/文件管理修改原始数据最新路径后重试。";
+    throw ui18n.t('res.code.8035');
   }
 
   if (res.code !== 0) {
@@ -421,11 +422,11 @@ export let getTableTemp = async () => {
 
   if (!table!) {
     table = {
-      案发日期: "",
-      案发地点: "",
-      绘图单位: "",
-      绘图人: "",
-      绘图时间: "",
+      [ui18n.t('tableTemp.th1')]: "",
+      [ui18n.t('tableTemp.th2')]: "",
+      [ui18n.t('tableTemp.th3')]: "",
+      [ui18n.t('tableTemp.th4')]: "",
+      [ui18n.t('tableTemp.th5')]: "",
     };
 
     if (window.platform.num) {
@@ -433,22 +434,22 @@ export let getTableTemp = async () => {
         num: window.platform.num,
       });
       if (item) {
-        table.案发日期 = item.crimeTimeBegin;
-        table.案发地点 = item.caseLocation;
-        table.绘图单位 = item.orgName;
-        table.绘图人 = item.investigatorName;
-        table.绘图时间 = formatDate(new Date(), "yyyy.MM.dd hh:mm");
+        table[ui18n.t('tableTemp.th1')] = item.crimeTimeBegin;
+        table[ui18n.t('tableTemp.th2')] = item.caseLocation;
+        table[ui18n.t('tableTemp.th3')] = item.orgName;
+        table[ui18n.t('tableTemp.th4')] = item.investigatorName;
+        table[ui18n.t('tableTemp.th5')] = formatDate(new Date(), "yyyy.MM.dd hh:mm");
         if (!title) {
-          title = `“${item.crimeTimeBegin}”${item.caseLocation}${item.caseTypeName}现场平面示意图`;
+          title = ui18n.t('tableTemp.title', item);
         }
         return (tempCache = { table, title });
       }
     }
   }
   if (!title) {
-    title = "默认标题";
+    title = ui18n.t('tableTemp.title1');
   }
-  return { table, title, tableTitle: '绘图说明' };
+  return { table, title, tableTitle: ui18n.t('tableTemp.title2') };
 };
 
 export const getTileGroups = async () => {

+ 2 - 1
src/example/fuse/enter.ts

@@ -3,6 +3,7 @@ import { params, preventReload } from "../env";
 import * as platform from "./enter-shared";
 import { asyncTimeout } from "@/utils/shared";
 import { encodePwd } from "@/utils/encode";
+import { ui18n } from "@/lang";
 
 window.platform = { ...platform };
 
@@ -95,7 +96,7 @@ if (!!params.value.sceneDraw) {
 setTimeout(() => {
   if (!window.platform.preventLogin) {
     if (!platform.getHeaders().token) {
-      ElMessage.error("当前用户未登录");
+      ElMessage.error(ui18n.t('sys.untoken'));
       window.platform.login();
     } else {
       platform.getSceneList("").next();

+ 8 - 2
src/example/fuse/router.ts

@@ -2,16 +2,22 @@ import { createRouter, createWebHashHistory } from "vue-router";
 
 import Overview from "./views/overview/index.vue";
 import Tabulation from "./views/tabulation/index.vue";
+import { ui18n } from "@/lang";
 
 export const history = createWebHashHistory();
 export const routes = [
-  { path: "/overview", name: "overview", label: "绘图", component: Overview },
+  {
+    path: "/overview",
+    name: "overview",
+    label: ui18n.t("overview.name"),
+    component: Overview,
+  },
 ];
 // if (!window.platform.sceneDraw) {
 routes.push({
   path: "/tabulation",
   name: "tabulation",
-  label: "制表",
+  label: ui18n.t("tabulation.name"),
   component: Tabulation,
 });
 // }

+ 5 - 4
src/example/fuse/views/error/index.vue

@@ -14,27 +14,28 @@ import empty from "./pic_empty.png";
 import e404 from "./pic_over.png";
 import err from "./image.png";
 import authority from "./pic_over(1).png";
+import { ui18n } from "@/lang";
 
 const errMap = [
   {
     code: [500],
     img: err,
-    msg: "服务器异常",
+    msg: ui18n.t('res.code.500'),
   },
   {
     code: [404],
     img: e404,
-    msg: "网络异常",
+    msg: ui18n.t('res.code.404'),
   },
   {
     code: [8032],
     img: empty,
-    msg: "绘图不存在",
+    msg: ui18n.t('res.code.8032'),
   },
   {
     code: [6012, 4010],
     img: authority,
-    msg: "无权操作",
+    msg: ui18n.t('res.code.6012'),
   },
 ];
 

+ 6 - 5
src/example/fuse/views/overview/header.vue

@@ -2,7 +2,7 @@
   <Header :action-groups="actions" :title="title" no-back :draw="draw">
     <template #saves>
       <el-button type="primary" @click="saveHandler" :disabled="draw.drawing">
-        保存
+        {{$t('sys.save')}}
       </el-button>
     </template>
     <template #nav>
@@ -34,6 +34,7 @@ import { listener } from "@/utils/event.ts";
 import { asyncTimeout, mergeFuns, repeatedlyOnly } from "@/utils/shared.ts";
 import saveAs from "@/utils/file-serve.ts";
 import { setViewToTableCover } from "./actions.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ title: string }>();
 const draw = useDraw();
@@ -64,7 +65,7 @@ const actions = [
         const scene = await selectScene();
         emit("selectVR", scene);
       },
-      text: "VR辅助",
+      text: ui18n.t('vr.fz'),
       icon: "VR",
     },
   ],
@@ -91,10 +92,10 @@ const actions = [
               return blob;
             });
             if (!blob) {
-              ElMessage.error("导出失败");
+              ElMessage.error(ui18n.t('sys.actions.download.error'));
             } else {
               await saveAs(blob, `${props.title}.${format}`);
-              ElMessage.success("导出成功");
+              ElMessage.success(ui18n.t('sys.actions.download.success'));
             }
           } else {
             await item.handler(props.title);
@@ -187,7 +188,7 @@ const saveHandler = repeatedlyOnly(async () => {
   let listUrl = null;
   let kankanUrl = null;
   if (!listBlob || !kkBlob) {
-    ElMessage.error("截图保存失败");
+    ElMessage.error(ui18n.t('sys.screenshot.error'));
   } else {
     [listUrl, kankanUrl] = await Promise.all([
       // window.platform.uploadResourse(new File([tabBlob], `tabulation-cover.png`)),

+ 3 - 2
src/example/fuse/views/overview/index.vue

@@ -9,7 +9,7 @@
     <template #header>
       <Header
         @selectVR="(scene) => (vrScene = scene)"
-        title="绘图"
+        :title="$t('overview.name')"
         @save-after="() => mergeFuns(saveAfterHandlers)()"
         :ref="(r) => (header = r)"
       />
@@ -51,6 +51,7 @@ import { getFloors } from "@/example/platform/resource-swkk";
 import { mergeFuns } from "@/utils/shared";
 import { drawPlatformResource } from "@/example/platform/platform-draw";
 import { overviewBorderMMToPixel, overviewMMToPixel } from "@/example/constant";
+import { ui18n } from "@/lang";
 
 const uploadResourse = window.platform.uploadResourse;
 const full = ref(false);
@@ -132,7 +133,7 @@ watch(draw, (draw, _, onCleanup) => {
     });
   }
 });
-const title = computed(() => overviewData.value?.title || "平面图");
+const title = computed(() => overviewData.value?.title || ui18n.t('sys.defTitle'));
 watchEffect(() => {
   document.title = title.value;
 });

+ 3 - 2
src/example/fuse/views/overview/slide.vue

@@ -16,10 +16,11 @@ import { Draw, useDraw } from "../../../components/container/use-draw.ts";
 import { h, reactive } from "vue";
 import SlideIcons from "../../../components/slide/slide-icons.vue";
 import { iconGroups } from "../../../constant";
+import { ui18n } from "@/lang/index.ts";
 
 const legend = {
   icon: "legend",
-  name: "图例",
+  name: ui18n.t('shape.icon.name'),
   value: uuid(),
   mount: (props: any) => h(SlideIcons, { ...props, groups: iconGroups }),
 };
@@ -35,7 +36,7 @@ const oSerial = {
 };
 const mark = {
   icon: "info",
-  name: "注释",
+  name: ui18n.t('mark.name'),
   value: uuid(),
   defSelect: true,
   children: [text, oSerial],

+ 4 - 4
src/example/fuse/views/tabulation/gen-tab.ts

@@ -32,6 +32,7 @@ import {
   defaultStyle as serialDefaultStyle,
 } from "@/core/components/serial";
 import type { TabCover } from "../../store";
+import { ui18n } from "@/lang";
 
 export const getRealPixel = (real: number, paperKey: PaperKey) => {
   const realPixelScale = paperConfigs[paperKey].scale;
@@ -168,7 +169,7 @@ export const genTabulationData = async (
       width,
       height,
       mat: [1, 0, 0, 1, 0, 0],
-      itemName: "比例",
+      itemName: ui18n.t('cover.itemName'),
     };
     const pos = getFixPosition(
       {
@@ -240,14 +241,13 @@ export const genTabulationData = async (
       ...getBaseItem(),
       ...style,
       disableDelete: true,
-      itemName: "指南针",
+      itemName: ui18n.t('compass.name'),
       coverOpcatiy: 0,
       strokeScaleEnabled: false,
       key: tableCompassKey,
       mat: mat.m,
-      name: "指南针",
+      name: ui18n.t('compass.name'),
     };
-    console.error("指南针", data);
 
     const pos = getFixPosition(
       {

+ 4 - 3
src/example/fuse/views/tabulation/header.vue

@@ -8,7 +8,7 @@
   >
     <template #saves>
       <el-button type="primary" @click="saveHandler" :disabled="draw.drawing">
-        保存
+        {{$t('sys.save')}}
       </el-button>
     </template>
     <template #nav>
@@ -41,6 +41,7 @@ import { listener } from "@/utils/event.ts";
 import { router, routes } from "../../router.ts";
 import { initViewport, paperConfigs } from "@/example/components/slide/actions.ts";
 import { asyncTimeout } from "@/utils/shared.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const props = defineProps<{ title: string }>();
 const emit = defineEmits<{ (e: "screenshot", val: boolean): void }>();
@@ -137,9 +138,9 @@ const actions = [
           URL.revokeObjectURL(url);
         }
         await saveAs(fileBlob, `${filename}.${ext}`);
-        ElMessage.success("导出成功");
+        ElMessage.success(ui18n.t('sys.actions.download.success'));
       },
-      text: "导出",
+      text: ui18n.t('sys.actions.download.name'),
       icon: "download",
     },
   ],

+ 9 - 9
src/example/fuse/views/tabulation/index.vue

@@ -6,7 +6,7 @@
     :ref="(d: any) => initDraw(d?.draw)"
   >
     <template #header>
-      <Header v-if="draw" title="图纸" @screenshot="(val) => (needScreenshot = val)" />
+      <Header v-if="draw" :title="$t('tabulation.name1')" @screenshot="(val) => (needScreenshot = val)" />
     </template>
     <template #slide>
       <Slide v-if="draw" @update-map-image="setMapHandler" />
@@ -80,6 +80,7 @@ import { router } from "../../router";
 import { params } from "@/example/env";
 import { tableSerialTableKey } from "@/example/constant";
 import { addTable, getCurrentNdxRaw, syncTable } from "@/core/components/serial";
+import { ui18n } from "@/lang";
 
 const uploadResourse = window.platform.uploadResourse;
 const full = ref(false);
@@ -165,7 +166,7 @@ const setMapHandler = async (config: { url: string; size: Size }) => {
     if (!draw.value || !serialTable.value) return;
     const un = draw.value.menusFilter.setShapeMenusFilter(
       serialTable.value.id,
-      (menu) => [{ label: "更新数据", handler: updateSerialTable }, ...menu]
+      (menu) => [{ label: ui18n.t('sys.update'), handler: updateSerialTable }, ...menu]
     );
     onCleanup(un);
   });
@@ -277,7 +278,7 @@ const setCover = (paperKey: PaperKey, draw: Draw) => {
       width: 0,
       height: 0,
       mat: [1, 0, 0, 1, 0, 0],
-      itemName: "比例",
+      itemName: ui18n.t('cover.itemName'),
     });
     const stopWatch = watch(
       originConfig,
@@ -360,7 +361,6 @@ const setCover = (paperKey: PaperKey, draw: Draw) => {
       (cover.widthRaw! / cover.width) *
       paperConfigs[paperKey].scale *
       cover.proportion!.scale;
-    console.log("兼容旧cover");
     cover.userData = {
       showScale: true,
       scale: round(scale, 1),
@@ -396,7 +396,7 @@ const setCover = (paperKey: PaperKey, draw: Draw) => {
       ...menus,
       scale: {
         type: "fixProportion",
-        label: "缩放比例",
+        label: ui18n.t('cover.fixProportion'),
         "layout-type": "row",
         get value() {
           return scale.value;
@@ -413,7 +413,7 @@ const setCover = (paperKey: PaperKey, draw: Draw) => {
       },
       showScale: {
         type: "check",
-        label: "显示比例",
+        label: ui18n.t('cover.showProportion'),
         "layout-type": "row",
         get value() {
           return coverSetting.value?.showScale;
@@ -527,7 +527,7 @@ const initDraw = async (_draw: Draw) => {
   quitMerges.concat(
     Object.keys(components).map((type) =>
       _draw.menusFilter.setMenusFilter(type as ShapeType, (items) => {
-        return items.filter((item) => item.label !== "隐藏");
+        return items.filter((item) => item.label !== ui18n.t('sys.hide'));
       })
     )
   );
@@ -553,7 +553,7 @@ watch(compass, (compass, _, onCleanup) => {
       // ...des,
       rotate: {
         type: "num",
-        label: "旋转角度",
+        label: ui18n.t('describes.rotate'),
         default: 0,
         props: {
           min: 0,
@@ -601,7 +601,7 @@ watchEffect((onCleanup) => {
   );
 });
 
-const title = computed(() => tabulationData.value?.title || "图纸");
+const title = computed(() => tabulationData.value?.title || ui18n.t('tabulation.name1'));
 watchEffect(() => {
   document.title = title.value;
 });

+ 1 - 1
src/example/fuse/views/tabulation/slide-icons.vue

@@ -22,7 +22,7 @@
         </div>
       </div>
     </ElCollapseItem>
-    <el-empty description="暂无数据" v-else />
+    <el-empty :description="$t('icons.undata')" v-else />
   </ElCollapse>
 </template>
 

+ 3 - 2
src/example/fuse/views/tabulation/slide.vue

@@ -24,6 +24,7 @@ import { v4 as uuid } from "uuid";
 import { Size } from "@/utils/math.ts";
 import { loading } from "@/example/loadding.ts";
 import { TileGroup } from "@/example/dialog/basemap/index.ts";
+import { ui18n } from "@/lang/index.ts";
 
 const emit = defineEmits<{
   (e: "updateMapImage", v: { url: string; size: Size }): void;
@@ -42,14 +43,14 @@ const paper = reactive({
 const drawMenu = copy(drawMenuRaw);
 drawMenu.children!.shift();
 drawMenu.children!.shift();
-drawMenu.name = "标注";
+drawMenu.name = ui18n.t('tagging.name');
 
 let tileGroups: TileGroup[];
 loading(window.platform.getTileGroups()).then((data: any) => (tileGroups = data));
 
 const mark = {
   icon: "info",
-  name: "注释",
+  name: ui18n.t('mark.name'),
   value: uuid(),
   defSelect: true,
   children: [

+ 4 - 3
src/example/platform/platform-resource.ts

@@ -3,6 +3,7 @@ import { getResource as getCloudResource } from "./resource-cloud";
 import { getResource as getSWKKResource } from "./resource-swkk";
 import { getResource as getFuseResource } from "./resource-fuse";
 import { LineIconData } from "@/core/components/line-icon";
+import { ui18n } from "@/lang";
 
 export enum SCENE_TYPE {
   fuse = "fuse",
@@ -74,9 +75,9 @@ export type ResourceArgs = {
 };
 
 export const SceneTypeNames = {
-  [SCENE_TYPE.fuse]: "融合场景",
-  [SCENE_TYPE.mesh]: "Mesh场景",
-  [SCENE_TYPE.cloud]: "点云场景",
+  [SCENE_TYPE.fuse]: ui18n.t('scene.fuse'),
+  [SCENE_TYPE.mesh]: ui18n.t('scene.mesh'),
+  [SCENE_TYPE.cloud]: ui18n.t('scene.cloud'),
 };
 
 export const getSceneApi = async (type: string | undefined, url: string) => {

+ 1 - 1
src/lang/index.ts

@@ -33,7 +33,7 @@ i18n.global.setLocaleMessage(langNameEum.zh, zh);
 
 i18n.global.change = (lang) => {
   params.value.lang = lang
-  // location.reload()
+  location.reload()
 }
 export const ui18n = i18n.global
 export const setupI18n = (app: App) => {

+ 386 - 1
src/lang/locales/zh.json

@@ -1 +1,386 @@
-{}
+{
+  "alignment": {
+    "end": {
+      "tip": "请在画板中与图例对应的位置(按esc退出对齐)"
+    },
+    "start": {
+      "tip": "请在当前图例选择位置(按esc退出对齐)"
+    }
+  },
+  "background": {
+    "map": {
+      "latlng": "经纬度",
+      "name": "地址"
+    },
+    "setting": {
+      "click": "点击位置",
+      "input": {
+        "place": "输入名称或经纬度(如113.281272,23.117661)搜索",
+        "place1": "如39.909187,116.397463",
+        "place2": "请输入{searchName}搜索"
+      },
+      "inputLatLng": "直接输入经纬度",
+      "lat": "纬度",
+      "latlng": {
+        "errname": "经纬度格式错误",
+        "errtip": "请输入正确的经纬度格式\n纬度,经度 (例如23.11766,113.28122)\n纬度范围:-90到90\n经度范围:-180到180",
+        "success": "经纬度定位成功"
+      },
+      "lng": "经度",
+      "map": "选择高德地图底图",
+      "name": "底图设置",
+      "select": "选择地图位置",
+      "zoom": "缩放级别"
+    }
+  },
+  "compass": {
+    "name": "指南针"
+  },
+  "cover": {
+    "fixProportion": "缩放比例",
+    "itemName": "比例",
+    "showProportion": "显示比例"
+  },
+  "describes": {
+    "align": {
+      "center": "居中对齐",
+      "left": "左对齐",
+      "name": "对齐方式",
+      "right": "右对齐"
+    },
+    "coverFill": "背景颜色",
+    "coverOpcatiy": "背景不透明度",
+    "coverStroke": "背景边框颜色",
+    "coverStrokeWidth": "背景边框粗细",
+    "dash": "虚线比例",
+    "fill": "填充色",
+    "fontColor": "文字颜色",
+    "fontSize": "文字大小",
+    "fontStyle": {
+      "bold": "粗体",
+      "italic": "斜体",
+      "italicBold": "粗斜体",
+      "name": "字体样式",
+      "normal": "默认"
+    },
+    "name": "名称",
+    "opacity": "不透明度",
+    "pointerLength": "箭头大小",
+    "pointerPosition": {
+      "all": "全部",
+      "end": "终点",
+      "name": "箭头方向",
+      "start": "起点"
+    },
+    "ref": "参考物",
+    "rotate": "旋转角度",
+    "stroke": "边框色",
+    "strokeScaleEnabled": "边框粗细随缩放",
+    "strokeWidth": "边框粗细",
+    "zIndex": "层叠层级"
+  },
+  "icons": {
+    "groups": {
+      "chuang": "窗",
+      "chuwei": "厨卫",
+      "goujian": "构件",
+      "henji": "痕迹物证",
+      "huxing": "户型",
+      "jiaju": "家具",
+      "kecan": "客餐厅",
+      "men": "门",
+      "qita": "其他",
+      "woshi": "卧室"
+    },
+    "items": {
+      "AirConditioner": "空调-圆形",
+      "BalconyChair": "阳台椅",
+      "Bathtub": "浴缸",
+      "BedsideCupboard": "床头柜",
+      "Cabinet": "柜子",
+      "CabinetB": "浴室柜",
+      "Carpet": "地毯",
+      "Chair": "椅子",
+      "Closestool": "马桶",
+      "CombinationSofa": "组合沙发",
+      "CornerSofa": "转角沙发2.7m",
+      "Cupboard": "橱柜",
+      "CupboardU": "通用橱柜",
+      "CurvedSofa": "弧形沙发2.2m",
+      "Desk": "书桌",
+      "DiningTable": "餐桌",
+      "DiningTableC": "餐桌椅",
+      "DiningTableR": "餐桌-圆",
+      "DiningTableRC": "餐桌",
+      "DoubleBed": "双人床",
+      "DoubleBedB": "双人床1.8m",
+      "Dresser": "梳妆台",
+      "Elevator": "电梯",
+      "FridgeD": "冰箱-双门",
+      "FridgeS": "冰箱",
+      "GasStove": "燃气灶",
+      "HangingTV": "电视-壁挂",
+      "MopPool": "拖把池",
+      "Pillow": "抱枕",
+      "Plant": "植物",
+      "Shower": "花洒",
+      "SideTableR": "边几-圆形",
+      "SideTableS": "边几-方形",
+      "SingleBed": "单人床",
+      "SingleSofa": "单人沙发",
+      "SingleSofaR": "组合沙发",
+      "SquattingPan": "蹲便器",
+      "Stool": "条凳",
+      "TV": "电视柜",
+      "Tablelamp": "台灯",
+      "TeaTable": "茶几",
+      "TeaTableO": "茶几-椭圆",
+      "TeaTableR": "茶几-圆形",
+      "ThreeSofa": "三人沙发",
+      "TwoSofa": "双人沙发1.8m",
+      "Wardrobe": "衣柜",
+      "WashingMachine": "洗衣机",
+      "Washstand": "洗漱台",
+      "WaterChannel": "水槽",
+      "WaterChannelD": "双水槽",
+      "WaterFountain": "饮水机",
+      "buqiang_o": "步枪",
+      "cartridge": "枪弹痕迹",
+      "case_other": "其他物证",
+      "check": "特殊物证",
+      "chelunhenji_o": "车轮印",
+      "chuang": "窗",
+      "corpse": "尸体",
+      "cube": "提取物品",
+      "danke_o": "弹壳",
+      "dantou_o": "弹头",
+      "electronic": "电子物证",
+      "folder_close": "文捡物证",
+      "footprint": "足迹痕迹",
+      "handprint": "手印痕迹",
+      "loudao": "楼道",
+      "luodichuang": "落地窗",
+      "men_l": "开门",
+      "physics_and_chemistry": "理化物证",
+      "piaochuang": "飘窗",
+      "poison": "毒化物证",
+      "shitib_o": "尸体背面",
+      "shitifuhao_o": "尸体",
+      "shitiz_o": "尸体正面",
+      "shouqiang_o": "手枪",
+      "shuangkaimen": "双开门",
+      "video": "视听物证",
+      "virus": "生物物证",
+      "wrenchAndScrewdriver": "工具痕迹",
+      "xieyin_o": "鞋印",
+      "xueji_o": "血迹",
+      "xuepo_o": "血泊",
+      "yakou": "哑口",
+      "yandao": "烟道",
+      "yimen": "移门",
+      "youzuji_o": "脚印",
+      "zhiwen_o": "手印",
+      "zhuzi": "柱子",
+      "zuozuji_o": "脚印"
+    },
+    "search": "搜索图例",
+    "undata": "暂无匹配结果"
+  },
+  "mark": {
+    "name": "注释"
+  },
+  "overview": {
+    "name": "绘图"
+  },
+  "res": {
+    "code": {
+      "404": "网络异常",
+      "500": "服务异常",
+      "6012": "无权操作",
+      "8032": "绘图不存在",
+      "8035": "存储路径不可用,请前往设置/文件管理修改原始数据最新路径后重试。"
+    }
+  },
+  "scene": {
+    "cloud": "点云场景",
+    "fuse": "融合场景",
+    "mesh": "Mesh场景"
+  },
+  "shape": {
+    "arrow": {
+      "name": "箭头"
+    },
+    "circle": {
+      "name": "圆形"
+    },
+    "group": {
+      "name": "分组"
+    },
+    "icon": {
+      "name": "图例"
+    },
+    "image": {
+      "name": "图片"
+    },
+    "line": {
+      "addPoint": "加点",
+      "name": "线段"
+    },
+    "lineIcon": {
+      "name": "线段图例",
+      "rever": "翻转"
+    },
+    "polygon": {
+      "name": "多边形",
+      "penEdit": "钢笔编辑"
+    },
+    "polygon1": {
+      "name": "连续线段"
+    },
+    "rect": {
+      "name": "矩形"
+    },
+    "serial": {
+      "diff": "数值",
+      "head1": "序号",
+      "head2": "描述",
+      "name": "序号",
+      "tableTitle": "图示"
+    },
+    "table": {
+      "addCol": "插入列",
+      "addRow": "插入行",
+      "delCol": "删除列",
+      "delRow": "删除行",
+      "name": "表格"
+    },
+    "text": {
+      "name": "文本"
+    },
+    "triangle": {
+      "name": "三角形"
+    },
+    "wall": {
+      "name": "墙",
+      "strokeWidth": "厚度"
+    }
+  },
+  "sys": {
+    "action": {
+      "download": {
+        "setting": "导出设置"
+      }
+    },
+    "actions": {
+      "a_adapt": "适应视图",
+      "clear": "清空",
+      "download": {
+        "color": "颜色",
+        "error": "导出失败",
+        "format": "格式",
+        "grayscale": "黑白",
+        "name": "导出",
+        "raw": "彩色",
+        "success": "导出成功"
+      },
+      "draw": {
+        "name": "绘制",
+        "text": "文本"
+      },
+      "floor": {
+        "hide": "隐藏底图",
+        "show": "显示底图"
+      },
+      "import": {
+        "image": {
+          "maxTip": "图片大小不超过100MB"
+        },
+        "local": {
+          "name": "本地",
+          "tip": "请在画图面板中选择放置位置,鼠标右键取消"
+        },
+        "map": "地图",
+        "name": "导入",
+        "scene": "场景",
+        "tip": "请在画图面板中选择放置位置,鼠标右键取消"
+      },
+      "paper": {
+        "A3_h": "A3横版",
+        "A4_h": "A4横版",
+        "name": "纸张"
+      },
+      "redo": "恢复",
+      "rotate": "旋转画布",
+      "undo": "撤销"
+    },
+    "alignment": "对齐",
+    "cancel": "取消",
+    "color": "颜色",
+    "copy": "复制",
+    "defTitle": "平面图",
+    "del": "删除",
+    "enter": "确定",
+    "full": {
+      "exit": "按ESC键可退出全屏模式"
+    },
+    "hide": "隐藏",
+    "length": "长度",
+    "line": {
+      "strokeWidth": "线宽"
+    },
+    "lock": "锁定",
+    "more": "多值",
+    "placeholder": {
+      "input": "请输入",
+      "select": "选择"
+    },
+    "save": "保存",
+    "screenshot": {
+      "error": "截图保存失败"
+    },
+    "setting": "设置{name}",
+    "shape": "形状",
+    "similars": "多实现",
+    "strokeWidth": "粗细",
+    "strokeWidth2": "边框线宽",
+    "unlock": "解锁",
+    "unproject": "当前项目号不存在!",
+    "untoken": "当前用户未登录",
+    "update": "更新数据",
+    "vr": {
+      "dialog": {
+        "floor": "楼层",
+        "hot": "热点",
+        "icon": "图例",
+        "selectFloor": "请选择楼层",
+        "selectScene": "请选择同步场景",
+        "signage": "指示牌",
+        "title": "请选择场景",
+        "traces": "痕迹物证"
+      }
+    },
+    "zbot": "置底",
+    "ztop": "置顶"
+  },
+  "tableTemp": {
+    "th1": "案发日期",
+    "th2": "案发地点",
+    "th3": "绘图单位",
+    "th4": "绘图人",
+    "th5": "绘图时间",
+    "title": "”{crimeTimeBegin}”{caseLocation}{caseTypeName}现场平面示意图",
+    "title1": "默认标题",
+    "title2": "绘图说明"
+  },
+  "tabulation": {
+    "name": "制表",
+    "name1": "图纸"
+  },
+  "tagging": {
+    "name": "标注"
+  },
+  "vr": {
+    "fz": "VR辅助",
+    "name": "VR全景"
+  }
+}

Разница между файлами не показана из-за своего большого размера
+ 0 - 1086
test.json