wrap-vue-i18n.js 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. const fs = require("fs");
  2. const path = require("path");
  3. const ROOT = path.join(__dirname, "../src");
  4. const SKIP = /copy\.vue$|weatherInfo copy/i;
  5. const TEXT_IN_TAG = />([^<>{}\n]+)</g;
  6. const ATTR_RE = /(?<![:\w$])(text|title|label|name|placeholder|message|confirm-button-text|cancel-button-text)=["']([^"']*[\u4e00-\u9fff][^"']*)["']/g;
  7. function walk(dir, files = []) {
  8. for (const name of fs.readdirSync(dir)) {
  9. const p = path.join(dir, name);
  10. if (fs.statSync(p).isDirectory()) {
  11. if (!["node_modules"].includes(name)) walk(p, files);
  12. } else if (name.endsWith(".vue") && !SKIP.test(p)) files.push(p);
  13. }
  14. return files;
  15. }
  16. function hasChinese(s) {
  17. return /[\u4e00-\u9fff]/.test(s);
  18. }
  19. function escapeStr(s) {
  20. return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
  21. }
  22. function alreadyWrapped(text) {
  23. return text.includes("{{") || text.includes("$t(") || text.includes("t(");
  24. }
  25. function wrapTemplate(content) {
  26. let changed = false;
  27. let out = content.replace(TEXT_IN_TAG, (full, text) => {
  28. const trimmed = text.trim();
  29. if (!trimmed || !hasChinese(trimmed) || alreadyWrapped(trimmed)) return full;
  30. if (/^[\s\d.,:;+\-/%]+$/.test(trimmed)) return full;
  31. changed = true;
  32. return `>{{ $t('${escapeStr(trimmed)}') }}<`;
  33. });
  34. out = out.replace(ATTR_RE, (full, attr, val) => {
  35. changed = true;
  36. return `:${attr}="$t('${escapeStr(val)}')"`;
  37. });
  38. return { out, changed };
  39. }
  40. function processVueFile(content) {
  41. const open = content.indexOf("<template>");
  42. const close = content.lastIndexOf("</template>");
  43. if (open === -1 || close === -1 || close <= open) {
  44. return { content, changed: false };
  45. }
  46. const before = content.slice(0, open + "<template>".length);
  47. const templateBody = content.slice(open + "<template>".length, close);
  48. const after = content.slice(close);
  49. const { out, changed } = wrapTemplate(templateBody);
  50. if (!changed) return { content, changed: false };
  51. return { content: before + out + after, changed: true };
  52. }
  53. let total = 0;
  54. for (const file of walk(ROOT)) {
  55. const original = fs.readFileSync(file, "utf8");
  56. const { content, changed } = processVueFile(original);
  57. if (!changed) continue;
  58. if (content.length < original.length * 0.7) {
  59. console.warn("SKIP (suspicious shrink):", path.relative(ROOT, file));
  60. continue;
  61. }
  62. fs.writeFileSync(file, content);
  63. total++;
  64. console.log("Updated:", path.relative(ROOT, file));
  65. }
  66. console.log("Done. Updated", total, "files");