build static method
I18nData
build({
- required BuildConfig buildConfig,
- required I18nLocale locale,
- 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,
);
}