From 146296178c99ad3254f59e2a128b3e46eb34481c Mon Sep 17 00:00:00 2001 From: Tien Do Nam Date: Mon, 21 Oct 2024 02:03:32 +0200 Subject: [PATCH] fix: analyze command should ignore unused translations that are linked --- slang/lib/src/runner/analyze.dart | 47 +++++++++++++++++--- slang/test/unit/runner/analyze_test.dart | 55 ++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/slang/lib/src/runner/analyze.dart b/slang/lib/src/runner/analyze.dart index 8fc8fbd2..b1b2585f 100644 --- a/slang/lib/src/runner/analyze.dart +++ b/slang/lib/src/runner/analyze.dart @@ -70,7 +70,7 @@ void runAnalyzeTranslations({ result: missingTranslationsResult, ); - final unusedTranslationsResult = _getUnusedTranslations( + final unusedTranslationsResult = getUnusedTranslations( rawConfig: rawConfig, translations: translationModelList, full: full, @@ -121,6 +121,7 @@ Map> getMissingTranslations({ curr: currTranslations.root, resultMap: resultMap, handleOutdated: true, + ignorePaths: const {}, ); result[currTranslations.locale] = resultMap; } @@ -128,7 +129,7 @@ Map> getMissingTranslations({ return result; } -Map> _getUnusedTranslations({ +Map> getUnusedTranslations({ required RawConfig rawConfig, required List translations, required bool full, @@ -150,11 +151,16 @@ Map> _getUnusedTranslations({ } final resultMap = {}; + final linkedPaths = {}; + _getReferredPaths(localeData.root, linkedPaths); + + // { } = localeData - baseTranslations _getMissingTranslationsForOneLocaleRecursive( baseNode: localeData.root, curr: baseTranslations.root, resultMap: resultMap, handleOutdated: false, + ignorePaths: linkedPaths, ); result[localeData.locale] = resultMap; } @@ -169,17 +175,21 @@ void _getMissingTranslationsForOneLocaleRecursive({ required ObjectNode curr, required Map resultMap, required bool handleOutdated, + required Set ignorePaths, }) { for (final baseEntry in baseNode.entries.entries) { final baseChild = baseEntry.value; - if (baseChild.modifiers.containsKey(NodeModifiers.ignoreMissing)) { + if (baseChild.modifiers.containsKey(NodeModifiers.ignoreMissing) || + ignorePaths.contains(baseChild.path)) { continue; } final currChild = curr.entries[baseEntry.key]; final isOutdated = handleOutdated && currChild?.modifiers.containsKey(NodeModifiers.outdated) == true; - if (isOutdated || !_checkEquality(baseChild, currChild)) { + if (isOutdated || + currChild == null || + !_checkEquality(baseChild, currChild)) { if (baseChild is ContextNode && currChild is ContextNode) { // Only add missing enums for (final baseEnum in baseChild.entries.keys) { @@ -208,6 +218,7 @@ void _getMissingTranslationsForOneLocaleRecursive({ curr: currChild as ObjectNode, resultMap: resultMap, handleOutdated: handleOutdated, + ignorePaths: ignorePaths, ); } } @@ -287,7 +298,7 @@ void _addNodeRecursive({ /// Both nodes are considered the same /// when they have the same type and the same parameters. -bool _checkEquality(Node? a, Node? b) { +bool _checkEquality(Node a, Node b) { if (a.runtimeType != b.runtimeType) { return false; } @@ -402,6 +413,32 @@ I18nData _findBaseTranslations(RawConfig rawConfig, List i18nData) { return baseTranslations; } +/// Populates [paths] with all paths that are referred in +/// linked translations. +void _getReferredPaths(ObjectNode root, Set paths) { + for (final entry in root.entries.entries) { + final child = entry.value; + switch (child) { + case ObjectNode() when !child.isMap: + _getReferredPaths(child, paths); + break; + case PluralNode(): + for (final quantity in child.quantities.values) { + paths.addAll(quantity.links); + } + break; + case ContextNode(): + for (final context in child.entries.values) { + paths.addAll(context.links); + } + break; + case TextNode(): + paths.addAll(child.links); + break; + } + } +} + void _writeMap({ required String outDir, required String fileNamePrefix, diff --git a/slang/test/unit/runner/analyze_test.dart b/slang/test/unit/runner/analyze_test.dart index fe4f75af..b6715446 100644 --- a/slang/test/unit/runner/analyze_test.dart +++ b/slang/test/unit/runner/analyze_test.dart @@ -1,3 +1,7 @@ +import 'package:slang/src/builder/builder/translation_model_list_builder.dart'; +import 'package:slang/src/builder/model/i18n_locale.dart'; +import 'package:slang/src/builder/model/raw_config.dart'; +import 'package:slang/src/builder/model/translation_map.dart'; import 'package:slang/src/runner/analyze.dart'; import 'package:test/test.dart'; @@ -53,4 +57,55 @@ void main() { expect(result, 'ADEG'); }); }); + + group('getUnusedTranslations', () { + test('Should find unused but translations', () { + final result = _getUnusedTranslations({ + 'en': { + 'a': 'A', + }, + 'de': { + 'a': 'A', + 'b': 'B', + }, + }); + + expect(result[I18nLocale(language: 'de')], {'b': 'B'}); + }); + + test('Should ignore unused but linked translations', () { + final result = _getUnusedTranslations({ + 'en': { + 'a': 'A', + }, + 'de': { + 'a': 'A @:b', + 'b': 'B', + }, + }); + + expect(result[I18nLocale(language: 'de')], isEmpty); + }); + }); +} + +Map> _getUnusedTranslations( + Map> translations, +) { + final map = TranslationMap(); + for (final entry in translations.entries) { + map.addTranslations( + locale: I18nLocale(language: entry.key), + translations: entry.value, + ); + } + + return getUnusedTranslations( + rawConfig: RawConfig.defaultConfig, + translations: TranslationModelListBuilder.build( + RawConfig.defaultConfig, + map, + ), + full: false, + ); }