build-flat-en.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. const fs = require("fs");
  2. const path = require("path");
  3. const SRC = path.join(__dirname, "../src");
  4. const OUT = path.join(__dirname, "../src/i18n/flat-en.js");
  5. const SKIP = /copy\.vue$|weatherInfo copy|flat-en\.js|messages\.js/i;
  6. const CHINESE_RE = /[\u4e00-\u9fff][\u4e00-\u9fff\d,。、?!:;""''()【】\-\s%%·]*/g;
  7. const messagesPath = path.join(__dirname, "../src/i18n/messages.js");
  8. const messages = new Function(`${fs.readFileSync(messagesPath, "utf8").replace("export default", "return ")}`)();
  9. const PHRASES = [
  10. ["长势报告", "Growth Report"],
  11. ["农情档案", "Crop Archives"],
  12. ["农事规划", "Farm Planning"],
  13. ["有味溯源", "Traceability"],
  14. ["荔枝长势报告", "Lychee Growth Report"],
  15. ["作物长势报告", "Crop Growth Report"],
  16. ["气象风险", "Weather Risks"],
  17. ["农事建议", "Farming Advice"],
  18. ["巡园重点", "Patrol Priorities"],
  19. ["未来7-15天气象风险", "7–15 Day Weather Risks"],
  20. ["未来七天天气", "7-Day Forecast"],
  21. ["农场列表", "Farm List"],
  22. ["示范农场", "Demo Farm"],
  23. ["专属数字农场,种好卖好", "Your Digital Farm — Grow Better, Sell Better"],
  24. ["点击解锁一键溯源增产", "Tap to Unlock Traceability & Yield Boost"],
  25. ["您的农场已绑定成功", "Farm Linked Successfully"],
  26. ["点击展开更多", "Show More"],
  27. ["点击收起", "Collapse"],
  28. ["暂无地址", "No Address"],
  29. ["干旱预警", "Drought Alert"],
  30. ["设为默认农场", "Set as Default"],
  31. ["新增农场", "Add Farm"],
  32. ["当前农场", "Current Farm"],
  33. ["农场信息维护", "Farm Profile"],
  34. ["查看全部农场列表,并可创建农场", "View All Farms and Create New Ones"],
  35. ["背景描述:", "Background: "],
  36. ["对策建议:", "Recommendations: "],
  37. ["点击解锁", "Unlock"],
  38. ["请求错误", "Bad Request"],
  39. ["未授权,请登录", "Unauthorized. Please log in"],
  40. ["没有权限,拒绝访问", "Forbidden"],
  41. ["请求地址出错", "Invalid URL"],
  42. ["请求超时", "Request Timeout"],
  43. ["服务器内部错误", "Internal Server Error"],
  44. ["服务未实现", "Not Implemented"],
  45. ["网关错误", "Bad Gateway"],
  46. ["服务不可用", "Service Unavailable"],
  47. ["网关超时", "Gateway Timeout"],
  48. ["HTTP版本不受支持", "HTTP Version Not Supported"],
  49. ["农事详情", "Farm Work Details"],
  50. ["农事凭证", "Farm Work Certificate"],
  51. ["执行区域", "Execution Area"],
  52. ["已认证", "Verified"],
  53. ["农事目的", "Purpose"],
  54. ["注意事项", "Notes"],
  55. ["药物处方", "Prescription"],
  56. ["执行方式", "Execution Method"],
  57. ["异常记录", "Exception Log"],
  58. ["转发给执行人员", "Forward to Executor"],
  59. ["确认上传", "Confirm Upload"],
  60. ["确认信息", "Confirm"],
  61. ["转发记录", "Forward Record"],
  62. ["物候不整齐?", "Uneven Phenology?"],
  63. ["发现异常,拍照记录", "Report Issue with Photo"],
  64. ["当前现状:", "Current Status:"],
  65. ["点击上传照片", "Upload Photo"],
  66. ["点击勾画新发生区域", "Draw New Area"],
  67. ["暂未到达进程", "Stage Not Reached"],
  68. ["邀请勾画", "Invite to Draw"],
  69. ["区域名称", "Area Name"],
  70. ["物候时间", "Phenology Period"],
  71. ["多选", "Multi-select"],
  72. ["调整pH值", "Adjust pH"],
  73. ["物候期时间设置", "Phenology Schedule"],
  74. ["起始时间", "Start Time"],
  75. ["请从上往下按照时间顺序填写日期", "Enter dates in chronological order from top to bottom"],
  76. ["确认设置", "Confirm"],
  77. ["上传中", "Uploading"],
  78. ["上传失败", "Upload Failed"],
  79. ["上传成功", "Upload Successful"],
  80. ["加载中", "Loading"],
  81. ["暂无数据", "No Data"],
  82. ["请稍候", "Please Wait"],
  83. ["操作成功", "Success"],
  84. ["操作失败", "Failed"],
  85. ["网络错误", "Network Error"],
  86. ["请求失败", "Request Failed"],
  87. ["登录", "Log In"],
  88. ["退出登录", "Log Out"],
  89. ["确定", "OK"],
  90. ["取消", "Cancel"],
  91. ["确认", "Confirm"],
  92. ["提交", "Submit"],
  93. ["保存", "Save"],
  94. ["删除", "Delete"],
  95. ["编辑", "Edit"],
  96. ["返回", "Back"],
  97. ["搜索", "Search"],
  98. ["搜索农场", "Search Farms"],
  99. ["选择地区", "Select Region"],
  100. ["选择品类", "Select Category"],
  101. ["请选择", "Please Select"],
  102. ["请输入", "Please Enter"],
  103. ["展开更多", "Expand"],
  104. ["收起", "Collapse"],
  105. ["详情", "Details"],
  106. ["查看", "View"],
  107. ["关闭", "Close"],
  108. ["下一步", "Next"],
  109. ["上一步", "Previous"],
  110. ["完成", "Done"],
  111. ["全部", "All"],
  112. ["更多", "More"],
  113. ["首页", "Home"],
  114. ["个人中心", "Profile"],
  115. ["农事服务", "Farm Services"],
  116. ["用户管理", "User Management"],
  117. ["历史风险报告", "Historical Risk Reports"],
  118. ["校准物候期", "Calibrate Phenology"],
  119. ["进程互动", "Progress Check"],
  120. ["长势互动", "Growth Check"],
  121. ["病虫互动", "Pest Check"],
  122. ["病虫风险", "Pest & Disease Risk"],
  123. ["阴雨寡照风险", "Rain & Low-Light Risk"],
  124. ["根外追肥", "Foliar Feeding"],
  125. ["虫害防治", "Pest Control"],
  126. ["种植档案管理", "Planting Records"],
  127. ["邀请关注", "Invite to Follow"],
  128. ];
  129. const WORDS = {
  130. 请: "Please ",
  131. 选择: "Select ",
  132. 输入: "Enter ",
  133. 暂无: "No ",
  134. 当前: "Current ",
  135. 未来: "Future ",
  136. 查看: "View ",
  137. 点击: "Tap ",
  138. 上传: "Upload ",
  139. 下载: "Download ",
  140. 搜索: "Search ",
  141. 筛选: "Filter ",
  142. 排序: "Sort ",
  143. 新增: "Add ",
  144. 编辑: "Edit ",
  145. 删除: "Delete ",
  146. 保存: "Save ",
  147. 提交: "Submit ",
  148. 确认: "Confirm ",
  149. 取消: "Cancel ",
  150. 返回: "Back ",
  151. 关闭: "Close ",
  152. 展开: "Expand ",
  153. 收起: "Collapse ",
  154. 成功: " Success",
  155. 失败: " Failed",
  156. 错误: " Error",
  157. 警告: " Warning",
  158. 提示: " Notice",
  159. 农场: "Farm",
  160. 果园: "Orchard",
  161. 地块: "Plot",
  162. 品种: "Variety",
  163. 作物: "Crop",
  164. 荔枝: "Lychee",
  165. 水稻: "Rice",
  166. 农事: "Farm Work",
  167. 天气: "Weather",
  168. 风险: " Risk",
  169. 报告: " Report",
  170. 档案: " Archives",
  171. 规划: " Planning",
  172. 溯源: " Traceability",
  173. 互动: " Interaction",
  174. 巡园: "Patrol ",
  175. 物候期: "Phenological Stage",
  176. 生育期: "Reproductive Stage",
  177. 膨果期: "Fruit Expansion Stage",
  178. 晴天: "Sunny",
  179. 周日: "Sun",
  180. 周一: "Mon",
  181. 周二: "Tue",
  182. 周三: "Wed",
  183. 周四: "Thu",
  184. 周五: "Fri",
  185. 周六: "Sat",
  186. };
  187. function pairZhEn(zhTree, enTree, out = {}) {
  188. Object.keys(zhTree || {}).forEach((k) => {
  189. if (zhTree[k] && typeof zhTree[k] === "object" && !Array.isArray(zhTree[k])) {
  190. pairZhEn(zhTree[k], (enTree || {})[k], out);
  191. } else if (typeof zhTree[k] === "string" && typeof (enTree || {})[k] === "string") {
  192. out[zhTree[k]] = enTree[k];
  193. }
  194. });
  195. return out;
  196. }
  197. function extractTemplateChinese(filePath) {
  198. const content = fs.readFileSync(filePath, "utf8");
  199. const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
  200. if (!templateMatch) return [];
  201. return (templateMatch[1].match(CHINESE_RE) || [])
  202. .map((s) => s.trim().replace(/\s+/g, " "))
  203. .filter((s) => s.length >= 2 && s.length <= 120 && /[\u4e00-\u9fff]/.test(s));
  204. }
  205. function walk(dir, files = []) {
  206. for (const name of fs.readdirSync(dir)) {
  207. const p = path.join(dir, name);
  208. if (fs.statSync(p).isDirectory()) {
  209. if (name !== "node_modules") walk(p, files);
  210. } else if (name.endsWith(".vue") && !SKIP.test(p)) files.push(p);
  211. }
  212. return files;
  213. }
  214. function autoTranslate(zh, cache) {
  215. if (cache[zh]) return cache[zh];
  216. for (const [key, val] of PHRASES) {
  217. if (zh === key || zh.includes(key)) {
  218. cache[zh] = zh.split(key).join(val);
  219. if (cache[zh] !== zh) return cache[zh];
  220. }
  221. }
  222. let result = zh;
  223. const keys = Object.keys(WORDS).sort((a, b) => b.length - a.length);
  224. keys.forEach((w) => {
  225. result = result.split(w).join(WORDS[w]);
  226. });
  227. result = result.replace(/\s+/g, " ").trim();
  228. if (/[\u4e00-\u9fff]/.test(result)) {
  229. result = `[${result}]`;
  230. }
  231. cache[zh] = result;
  232. return result;
  233. }
  234. const map = pairZhEn(messages.zh, messages.en);
  235. const cache = { ...map };
  236. for (const file of walk(SRC)) {
  237. extractTemplateChinese(file).forEach((s) => {
  238. if (!map[s]) map[s] = null;
  239. });
  240. }
  241. Object.keys(map).forEach((zh) => {
  242. if (!map[zh] || /[\u4e00-\u9fff]/.test(map[zh])) {
  243. map[zh] = autoTranslate(zh, cache);
  244. }
  245. });
  246. const esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
  247. const lines = ["export default {"];
  248. Object.keys(map)
  249. .sort()
  250. .forEach((zh) => {
  251. lines.push(` '${esc(zh)}': '${esc(map[zh])}',`);
  252. });
  253. lines.push("};");
  254. fs.writeFileSync(OUT, lines.join("\n"));
  255. console.log("Wrote", Object.keys(map).length, "entries");