最近在做国际化相关内容,要求在已经使用的项目中,查找出所有带汉字的变量或常量,然后进行国际化处理。人工查找费力不讨好,就想着能不能通过ast来查询子节点,找出所有需要的节点。
首先该dart脚本必须放于项目下,根据然后递归查找到该项目的根目录。这时候就出现一个问题,
项目中如果存在了临时文件(.g.dart)、测试的dart文件、或者第三方库的dart文件,则需要过滤。需要新建一个文件,将不要扫描的路径加入,加入后,遍历到路径后就不会进行查找。
///不进行扫描的路径
List<String> excludePath = [];
void main(List<String> arguments) {
Map<String, _LocationInfo> chineseSet = {};
///检索是否文件目录存在项目名,可以通过参数输入
final projectPath = arguments.isEmpty ? project : arguments.first;
Directory current = Directory.current;
var path = current.path;
///项目的目录
String targetPath;
while (path != "/" && !path.split("/").last.contains(projectPath)) {
current = current.parent;
path = current.path;
}
targetPath = path;
if (path.isEmpty) {
print('执行的dart文件必须属于项目中');
return;
}
excludePath = File("$targetPath/.pathignore")
.readAsLinesSync()
.map((e) => "$targetPath$e")
.toList();
findByDart(chineseSet, targetPath);
if (chineseSet.isEmpty) {
print("没有可读取中文,停止操作");
return;
}
writeToExcel(chineseSet, targetPath);
}
之后就是findByDart方法,该方法实现了从项目根目录,进行递归查找dart文件的逻辑,并过滤了.g.dart这种文件的过滤,一但查找到dart文件,将使用ast对该文件进行解析。
///递归查找所有路径下的dart文件
void findByDart(Map<String, _LocationInfo> chineseSet, String targetPath) {
Directory(targetPath).listSync().forEach((element) {
///如果是忽略目录的路径,则略过该目录,且不在扫描其子目录
if (excludePath.contains(element.path)) {
print('忽略扫描路径:${element.path}');
} else {
///如果是路径,则递归调用
if (FileSystemEntity.isDirectorySync(element.path)) {
findDartFile(chineseSet, element.path);
} else if (FileSystemEntity.isFileSync(element.path)) {
File file = File(element.path);
print('当前扫描文件路径:${element.path}');
///判定该文件是dart文件,进行解析
///忽略.g.dart文件
if (!file.uri.pathSegments.last.contains(".g.dart") &&
file.uri.pathSegments.last.contains(".dart")) {
ParseStringResult result = parseFile(
path: file.path, featureSet: FeatureSet.latestLanguageVersion());
result.unit
.accept(ChineseVisitor(chineseSet, element.path, result.unit));
}
}
}
});
}
然后就是ast节点的判断,判断节点类型是否是简单字符串类型或者是插值字符串类型,并判定是否包含中文的节点,如果判定成功,则过滤掉引号。并通过analysis库,将该节点在文件的行列进行输出,具体Ast的API可以查看analysis库。
class ChineseVisitor extends GeneralizingAstVisitor {
ChineseVisitor(this.chineseSet, this.filePath, this.unit);
RegExp chineseExp = RegExp(r"[\u4e00-\u9fa5]");
String filePath;
final CompilationUnit unit;
final Map<String, _LocationInfo> chineseSet;
@override
dynamic visitNode(AstNode node) {
///只解析字符串常量值与插入值,是否含中文
if (node.runtimeType.toString() == "SimpleStringLiteralImpl" ||
node.runtimeType.toString() == "InterpolationStringImpl") {
if (chineseExp.hasMatch(node.toSource())) {
///过滤引号
var sourceValue =
node.toSource().replaceAll("'", "").replaceAll('"', "").toString();
var location = unit.lineInfo.getLocation(node.offset);
chineseSet[sourceValue] =
_LocationInfo(filePath, location.lineNumber, location.columnNumber);
// print('${node.runtimeType}->$sourceValue');
}
}
return super.visitNode(node);
}
}
class _LocationInfo {
_LocationInfo(this.path, this.lineNumber, this.columnNumber);
///所属文件
String path;
///所在行
int lineNumber;
///所在列
int columnNumber;
}
之后将获取的信息输出到excel文件中,等待后续处理,具体实现参考excel库即可。
void writeToExcel(Map<String, _LocationInfo> chineseSet, String targetPath) {
var excel = Excel.createExcel();
excel.rename('Sheet1', 'translate');
var sheet = excel['translate'];
var cellA1 = sheet.cell(CellIndex.indexByString("A1"));
cellA1.value = "项目中的中文:";
var cellB1 = sheet.cell(CellIndex.indexByString("B1"));
cellB1.value = "所在文件位置:";
var cellC1 = sheet.cell(CellIndex.indexByString("C1"));
cellC1.value = "所在行:";
var cellD1 = sheet.cell(CellIndex.indexByString("D1"));
cellD1.value = "所在列:";
///A1作为标题
///从A2开始到AN为待翻译中文
for (int row = 2; row < chineseSet.length + 2; row++) {
sheet.cell(CellIndex.indexByString("A$row")).value =
chineseSet.keys.toList()[row - 2];
sheet.cell(CellIndex.indexByString("B$row")).value =
chineseSet.values.toList()[row - 2].path.replaceAll(targetPath, "");
sheet.cell(CellIndex.indexByString("C$row")).value =
chineseSet.values.toList()[row - 2].lineNumber;
sheet.cell(CellIndex.indexByString("D$row")).value =
chineseSet.values.toList()[row - 2].columnNumber;
}
var fileBytes = excel.save();
///文件保存在
if (fileBytes != null) {
var targetExcel = File("$targetPath/filter_chinese.xlsx");
if (targetExcel.existsSync()) {
targetExcel.deleteSync();
}
File("$targetPath/filter_chinese.xlsx")
..createSync(recursive: true)
..writeAsBytesSync(fileBytes);
print("成功写入到$targetPath/filter_chinese.xlsx");
}
}
该实现方式可以查找到所有包含中文的变量,缺点是有可能会遍历出一些使用中,被变量分割的情况,该情况被输出到excel时,需要根据输出的变量位置,实际考虑所用变量的语境。
输出到excel中的内容,自动化进行翻译等过程,本文不与操作,写个脚本即可。