Dart 脚本:一键整合,国际化语种翻译文件(.arb) 转 Excel

536 阅读2分钟

背景

部门开会让把国际化项目内支持的所有语种翻译整理成 Excel,于是我整理了这篇文章,通过脚本遍历工程目录,整合各模块内 arb文件,生成 Excel 表格。

引言

先简单介绍一下,flutter 框架本身支持国际化,添加依赖 flutter_localizationsflutter_intl,运行命令行 flutter intl generate

lib 文件夹内会生成两个文件夹 generated 和 l10n,l10n 内存放各国的文本翻译资源即 .arb 文件,每个 .arb文件 都是一个JSON结构。

image.png

如上示例,小编项目内 l10n 内存放的三种文本翻译资源,英文、中文简体、中文繁体。以 intl_zh_CN.arb 为例,数据结构是这样的:

image.png

代码实现

好啦,回归正题,本篇主旨是分享如何通过脚本,指定项目,从各个模块中提取所有的翻译文本,按语种分类,一一匹配,然后整合导出Excel文件。

直接上代码:(copy可直接使用)

import 'dart:io';
import 'dart:convert';
import 'package:path/path.dart' as path;
import 'package:excel/excel.dart';

void main() {
  const arbFileSuffix = 'arb'; // .arb 文件的后缀
  const intlDirFiled = 'l10n'; // 需要命中的 intl 文件夹

  final excel = Excel.createExcel();

  // 待遍历的根目录
  const rootDirPath = '/Users/rex/Downloads/project';
  final rootDir = Directory(rootDirPath);

  // 遍历目录,找到 intl 文件夹
  FileSystemEntity? traverForIntl(
    Directory directory,
  ) {
    final childrenDirs = directory.listSync();
    for (var childDir in childrenDirs) {
      if (childDir is Directory) {
        final dirName = path.basename(childDir.path);
        if (dirName == intlDirFiled) {
          return childDir;
        } else {
          final result = traverForIntl(childDir);
          if (result == null) {
            continue;
          } else {
            return result;
          }
        }
      }
    }
    return null;
  }

  rootDir.listSync().forEach((moduleDirs) {
    // 遍历模块目录,记录模块名称,用于 excel 的 sheet 名称
    final moduleName = path.basename(moduleDirs.path);
    if (moduleDirs is Directory) {
      final intlDir = traverForIntl(moduleDirs);
      if (intlDir != null && intlDir is Directory) {
        final arbFiles = intlDir
            .listSync()
            .where((element) => element.path.endsWith(arbFileSuffix));

        // 获取 arb 文件名称,例如 : intl_en_US、intl_zh_CN、用于 sheet 中 头部信息
        Map<String, Map<String, dynamic>> arbContentMap = {};
        for (var arbFile in arbFiles) {
          final arbName = path.basename(arbFile.path).replaceAll('.arb', '');
          final arbContent = (arbFile as File).readAsStringSync();
          final arbMap = jsonDecode(arbContent) as Map<String, dynamic>;
          arbContentMap.putIfAbsent(arbName, () => arbMap);
        }

        // 按 map 长度倒序排序
        final abc = arbContentMap.entries.toList();
        abc.sort((a, b) => b.value.length.compareTo(a.value.length));
        Map<String, Map<String, dynamic>> resortArbMaps = Map.fromEntries(abc);

        if (resortArbMaps.isNotEmpty) {
          Sheet sheetObject = excel[moduleName];
          // 每个头部信息 (标明语言类型)
          final header = [TextCellValue('arb_key')];
          header
              .addAll(resortArbMaps.keys.map((e) => TextCellValue(e)).toList());
          sheetObject.appendRow(header);

          List mapKeys = resortArbMaps.values.first.keys.toList();

          // 由于我们使用 appendRow 方法,所以遍历行数
          for (var index = 0; index < mapKeys.length; index++) {
            final itemKey = mapKeys[index];
            final cellValues = [TextCellValue(itemKey)];
            cellValues.addAll(
              resortArbMaps.values
                  .map((e) => TextCellValue(e[itemKey] ?? ''))
                  .toList(),
            );
            sheetObject.appendRow(cellValues);
          }
        }
      }
    }
  });

  final fileBytes = excel.save();
  File('/Users/rex/Downloads/arb翻译整理.xlsx')
    ..createSync(recursive: true)
    ..writeAsBytesSync(
      fileBytes!,
      mode: FileMode.write,
    );
  print('excel 文件生成成功');
}

PS:上述脚本需要添加三方库依赖

dependencies:
  flutter:
    sdk: flutter
  excel: ^4.0.0

如上示例中指定的目录为 /Users/rex/Downloads/project

image.png

指定项目路径内有5个模块,每个模块中包含3个语言资源,点击运行脚本,导出 Excel 表格结构如下:

image.png

Excel表格导出成功,5个模块对应5个Sheet,以语种为列,arbKey为索引。