react 项目检查国际化配置脚本

说明

运行find:missing-i18n

日志会出事没有完成国际化的组件、行数和原文内容

修改package.json

json 复制代码
...
"find:missing-i18n": "ts-node --compiler-options '{\"module\":\"commonjs\"}' scripts/find-missing-i18n.ts",
...

find-missing-i18n.ts

在scripts文件夹里面增加find-missing-i18n.ts文件

ts 复制代码
import fs from 'fs';
import path from 'path';
import ts from 'typescript';

type Finding = {
  filePath: string;
  line: number;
  column: number;
  snippet: string;
};

const TARGET_COMPONENTS = new Set(['Text', 'ThemedText']);
const IGNORED_DIRS = new Set([
  'node_modules',
  '.git',
  '.expo',
  'android',
  'ios',
  'build',
  'builds',
  'dsyms',
]);

const ROOT_DIR = path.resolve(__dirname, '..');

function walk(dir: string, result: string[]): void {
  const entries = fs.readdirSync(dir, { withFileTypes: true });

  for (const entry of entries) {
    if (entry.name.startsWith('.DS_Store')) {
      continue;
    }

    const fullPath = path.join(dir, entry.name);

    if (entry.isDirectory()) {
      if (IGNORED_DIRS.has(entry.name)) {
        continue;
      }
      walk(fullPath, result);
    } else if (entry.isFile()) {
      if (fullPath.endsWith('.tsx') || fullPath.endsWith('.jsx')) {
        result.push(fullPath);
      }
    }
  }
}

function getTagName(tagName: ts.JsxTagNameExpression): string | null {
  if (ts.isIdentifier(tagName)) {
    return tagName.text;
  }
  return null;
}

function extractTextFromJsxElement(sourceFile: ts.SourceFile, element: ts.JsxElement): Finding[] {
  const tagName = getTagName(element.openingElement.tagName);

  if (!tagName || !TARGET_COMPONENTS.has(tagName)) {
    return [];
  }

  const findings: Finding[] = [];

  for (const child of element.children) {
    if (ts.isJsxText(child)) {
      const raw = child.getText();
      const trimmed = raw.replace(/\s+/g, ' ').trim();
      if (trimmed.length > 0) {
        const { line, character } = sourceFile.getLineAndCharacterOfPosition(child.getStart());
        findings.push({
          filePath: sourceFile.fileName,
          line: line + 1,
          column: character + 1,
          snippet: trimmed.slice(0, 80),
        });
      }
    } else if (ts.isJsxExpression(child) && child.expression) {
      const expr = child.expression;
      if (ts.isStringLiteralLike(expr)) {
        const text = expr.text.replace(/\s+/g, ' ').trim();
        if (text.length > 0) {
          const { line, character } = sourceFile.getLineAndCharacterOfPosition(expr.getStart());
          findings.push({
            filePath: sourceFile.fileName,
            line: line + 1,
            column: character + 1,
            snippet: text.slice(0, 80),
          });
        }
      } else if (ts.isNoSubstitutionTemplateLiteral(expr)) {
        const text = expr.text.replace(/\s+/g, ' ').trim();
        if (text.length > 0) {
          const { line, character } = sourceFile.getLineAndCharacterOfPosition(expr.getStart());
          findings.push({
            filePath: sourceFile.fileName,
            line: line + 1,
            column: character + 1,
            snippet: text.slice(0, 80),
          });
        }
      }
    }
  }

  return findings;
}

function analyzeFile(filePath: string): Finding[] {
  const fileContent = fs.readFileSync(filePath, 'utf8');
  const sourceFile = ts.createSourceFile(
    filePath,
    fileContent,
    ts.ScriptTarget.Latest,
    true,
    ts.ScriptKind.TSX
  );
  const findings: Finding[] = [];

  const visit = (node: ts.Node): void => {
    if (ts.isJsxElement(node)) {
      findings.push(...extractTextFromJsxElement(sourceFile, node));
    }
    ts.forEachChild(node, visit);
  };

  visit(sourceFile);

  return findings;
}

function main(): void {
  const filePaths: string[] = [];
  walk(ROOT_DIR, filePaths);

  const allFindings: Finding[] = [];
  for (const filePath of filePaths) {
    allFindings.push(...analyzeFile(filePath));
  }

  if (allFindings.length === 0) {
    console.log('No hardcoded Text or ThemedText content found.');
    return;
  }

  for (const finding of allFindings) {
    const relativePath = path.relative(ROOT_DIR, finding.filePath);
    console.log(`${relativePath}:${finding.line}:${finding.column} -> ${finding.snippet}`);
  }

  console.log(`\nTotal findings: ${allFindings.length}`);
}

main();
相关推荐
FreeTinker6 小时前
HTML本地存储技术解析:localStorage与sessionStorage的原理、差异与应用场景
前端·html
qq_452396236 小时前
第十二篇:《全链路监控与可观测性:前端错误追踪与性能分析》
前端
wangruofeng7 小时前
Phosphor Icons 官网源码拆解:URL 即存储、水波动画与 7362 Star 图标库的架构实践
前端·架构·github
BigTopOne7 小时前
WebRTC Native / Android 开发学习资源整理
前端
excel8 小时前
nuxt 3 升级 nuxt 4 报错之 hasInjectionContext
前端
何以解忧,唯有..8 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
软件聚导航9 小时前
「聚小软 AI 助手」技术升级:从数据库检索到 RAG 知识库问答
前端·数据库·mysql·微信小程序·小程序·ai编程·rag
恋猫de小郭10 小时前
看懂大模型架构术语,帮助你理解目前常见的大模型开源架构
前端·人工智能·ai编程
yma1610 小时前
通过提示词实现GitHub Pages 和 Nginx 双部署竟然这么简单?
前端