build static method

I18nData build({
  1. required BuildConfig buildConfig,
  2. required I18nLocale locale,
  3. required Map<String, dynamic> map,
})

Builds the i18n model for ONE locale

The map must be of type Map<String, dynamic> and all children may of type String, num, List

Implementation

static I18nData build({
  required BuildConfig buildConfig,
  required I18nLocale locale,
  required Map<String, dynamic> map,
}) {
  bool hasCardinal = false;
  bool hasOrdinal = false;

  // flat map for leaves (TextNode, PluralNode, ContextNode)
  final Map<String, LeafNode> leavesMap = {};

  // 1st round: Build nodes according to given map
  //
  // Linked Translations:
  // They will be tracked but not handled
  // Assumption: They are basic linked translations without parameters
  // Reason: Not all TextNodes are built, so final parameters are unknown
  final resultNodeTree = _parseMapNode(
    locale: locale,
    parentPath: '',
    curr: map,
    config: buildConfig,
    keyCase: buildConfig.keyCase,
    leavesMap: leavesMap,
    cardinalNotifier: () {
      hasCardinal = true;
    },
    ordinalNotifier: () {
      hasOrdinal = true;
    },
  );

  // 2nd round: Handle parameterized linked translations
  //
  // TextNodes with parameterized linked translations are rebuilt with correct parameters.
  leavesMap.entries
      .where((entry) => entry.value is TextNode)
      .forEach((entry) {
    final key = entry.key;
    final value = entry.value as TextNode;

    final linkParamMap = <String, Set<String>>{};
    final paramTypeMap = <String, String>{};
    value.links.forEach((link) {
      final paramSet = <String>{};
      final visitedLinks = <String>{};
      final pathQueue = Queue<String>();
      pathQueue.add(link);

      while (pathQueue.isNotEmpty) {
        final currLink = pathQueue.removeFirst();
        final linkedNode = leavesMap[currLink];
        if (linkedNode == null) {
          throw '"$key" is linked to "$currLink" but it is undefined (locale: ${locale.languageTag}).';
        }

        visitedLinks.add(currLink);

        if (linkedNode is TextNode) {
          paramSet.addAll(linkedNode.params);
          paramTypeMap.addAll(linkedNode.paramTypeMap);

          // lookup links
          linkedNode.links.forEach((child) {
            if (!visitedLinks.contains(child)) {
              pathQueue.add(child);
            }
          });
        } else if (linkedNode is PluralNode || linkedNode is ContextNode) {
          final Iterable<StringTextNode> textNodes = linkedNode is PluralNode
              ? linkedNode.quantities.values
              : (linkedNode as ContextNode).entries.values;
          final linkedParamSet = textNodes
              .map((e) => e.params)
              .expand((params) => params)
              .toSet();

          if (linkedNode is PluralNode) {
            linkedParamSet.add(linkedNode.paramName);
            paramTypeMap[linkedNode.paramName] = 'num';
          } else if (linkedNode is ContextNode) {
            linkedParamSet.add(linkedNode.paramName);
            paramTypeMap[linkedNode.paramName] = linkedNode.context.enumName;
          }

          paramSet.addAll(linkedParamSet);

          // lookup links of children
          textNodes.forEach((element) {
            element.links.forEach((child) {
              if (!visitedLinks.contains(child)) {
                pathQueue.add(child);
              }
            });
          });
        } else {
          throw '"$key" is linked to "$currLink" which is a ${linkedNode.runtimeType} (must be $TextNode or $ObjectNode).';
        }
      }

      linkParamMap[link] = paramSet;
    });

    if (linkParamMap.values.any((params) => params.isNotEmpty)) {
      // rebuild TextNode because its linked translations have parameters
      value.updateWithLinkParams(
        linkParamMap: linkParamMap,
        paramTypeMap: paramTypeMap,
      );
    }
  });

  // imaginary root node
  final root = ObjectNode(
    path: '',
    comment: null,
    entries: resultNodeTree,
    isMap: false,
  );

  // 3rd round: Add interfaces
  final List<Interface> resultInterfaces;
  if (buildConfig.interfaces.isEmpty) {
    resultInterfaces = [];
  } else {
    resultInterfaces = _applyInterfaceAndGenericsRecursive(
      curr: root,
      interfaceCollection: buildConfig.buildInterfaceCollection(),
    ).toList();
  }

  return I18nData(
    base: buildConfig.baseLocale == locale,
    locale: locale,
    root: root,
    interfaces: resultInterfaces,
    hasCardinal: hasCardinal,
    hasOrdinal: hasOrdinal,
  );
}