diff --git a/README.md b/README.md index d945cc7..16cb693 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ flappy_translator: expose_get_string: false expose_loca_strings: false expose_locale_maps: false + generate_comments: false + comment_languages: [] ``` | Setting | Default | Description | @@ -65,6 +67,8 @@ flappy_translator: | expose_get_string | false | The default value for whether a getString method should be exposed. | | expose_loca_strings | false | The default value for whether a locaStrings getter should be exposed. | | expose_locale_maps | false | The default value for whether a localeMaps getter should be exposed. | +| generate_comments | false | Whether documentation comments should be used to display translations. | +| comment_languages | [] | Languages that are displayed in the comments. Empty -> All languages are used. | ### Run package diff --git a/bin/flappy_translator.dart b/bin/flappy_translator.dart index 0a4d98c..8f9c43a 100644 --- a/bin/flappy_translator.dart +++ b/bin/flappy_translator.dart @@ -29,6 +29,10 @@ void main() { exposeGetString: settings[_YamlArguments.exposeGetString], exposeLocaStrings: settings[_YamlArguments.exposeLocaStrings], exposeLocaleMaps: settings[_YamlArguments.exposeLocaleMaps], + generateComments: settings[_YamlArguments.generateComments], + commentLanguages: (settings[_YamlArguments.commentLanguages] as YamlList?) + ?.map((node) => node.toString()) + .toList(), ); } @@ -52,6 +56,8 @@ class _YamlArguments { static const exposeGetString = 'expose_get_string'; static const exposeLocaStrings = 'expose_loca_strings'; static const exposeLocaleMaps = 'expose_locale_maps'; + static const generateComments = 'generate_comments'; + static const commentLanguages = 'comment_languages'; } /// Returns configuration settings for flappy_translator from pubspec.yaml diff --git a/example/pubspec.lock b/example/pubspec.lock index 53b1010..e023d48 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -42,7 +42,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -158,13 +158,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.17.0" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" package_config: dependency: transitive description: @@ -239,7 +246,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" watcher: dependency: transitive description: @@ -262,5 +269,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.14.0 <3.0.0" flutter: ">=2.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e3c69fd..823684f 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -31,6 +31,9 @@ flappy_translator: expose_get_string: false expose_loca_strings: false expose_locale_maps: false + generate_comments: false + comment_languages: [] + flutter: uses-material-design: true diff --git a/lib/src/configs/default_settings.dart b/lib/src/configs/default_settings.dart index 1afaa9e..7401036 100644 --- a/lib/src/configs/default_settings.dart +++ b/lib/src/configs/default_settings.dart @@ -34,4 +34,10 @@ class DefaultSettings { /// The default value for whether a localeMaps getter should be exposed static const exposeLocaleMaps = false; + + /// The default value for whether comments containing the translation should be included in the generated code + static const generateComments = false; + + /// The default value for which languages to use in the generated comments. Defaults to all languages. + static const commentLanguages = []; } diff --git a/lib/src/extensions/list_extensions.dart b/lib/src/extensions/list_extensions.dart new file mode 100644 index 0000000..55281a3 --- /dev/null +++ b/lib/src/extensions/list_extensions.dart @@ -0,0 +1,17 @@ +extension ListExtensions on List { + /// Maps a list of elements to a list of elements of another type. + /// + /// The [predicate] filters elements that are passed to the [transform] function. + /// This does not change the order or index of the elements. + /// The [transform] function is called for each element in the list with its respective index and maps it to a new element. + List mapIndexedWhere(T Function(int index, E element) transform, + [bool Function(int index, E element)? predicate]) { + final result = []; + for (var i = 0; i < length; i++) { + if (predicate != null ? predicate(i, this[i]) : true) { + result.add(transform(i, this[i])); + } + } + return result; + } +} diff --git a/lib/src/services/code_generation/code_generator.dart b/lib/src/services/code_generation/code_generator.dart index 0657e67..ae1043a 100644 --- a/lib/src/services/code_generation/code_generator.dart +++ b/lib/src/services/code_generation/code_generator.dart @@ -3,6 +3,7 @@ import 'package:dart_style/dart_style.dart'; import '../../configs/constants.dart' as constants; import '../../configs/default_settings.dart'; import '../../utils/flappy_logger.dart'; +import '../../extensions/list_extensions.dart'; import 'template.dart'; /// A service which generates I18n class and delegate using string concatenation @@ -11,6 +12,7 @@ class CodeGenerator { final String _quoteString; final bool replaceNoBreakSpaces; final _parametersRegex = RegExp(r'(\%[[0-9a-zA-Z]+]*\$(d|s))'); + final List _commentLanguages = []; late String _template; late List> _maps; @@ -44,6 +46,16 @@ class CodeGenerator { _fields = ''; } + void enableCommentGeneration([List commentLanguages = const []]) { + if (commentLanguages.isEmpty) { + _commentLanguages.addAll(_supportedLanguages); + } else { + // make sure to not use languages that are not supported + _commentLanguages.addAll(_supportedLanguages + .where((supportedLang) => commentLanguages.contains(supportedLang))); + } + } + void setSupportedLanguages(List supportedLanguages) { _supportedLanguages = supportedLanguages; @@ -71,7 +83,10 @@ class CodeGenerator { } void addField(String key, String defaultWord, List words) { - var result = ''; + var result = _supportedLanguages + .mapIndexedWhere((i, lang) => '/// * $lang: ${words[i]} \n', + (_, lang) => _commentLanguages.contains(lang)) + .join(''); final getTextString = '_getText($_quoteString$key$_quoteString)'; final hasParameters = _parametersRegex.hasMatch(defaultWord); if (hasParameters) { @@ -83,7 +98,7 @@ class CodeGenerator { 'required $parameterType ${_getParameterNameFromPlaceholder(match.group(0)!)}, '; } - result = + result += (!dependOnContext ? 'static ' : '') + 'String $key({$parameters}) =>'; result += getTextString; @@ -97,7 +112,7 @@ class CodeGenerator { result += ';\n\n'; } else { - result = (!dependOnContext ? 'static ' : '') + + result += (!dependOnContext ? 'static ' : '') + 'String get $key => $getTextString;\n\n'; } diff --git a/lib/src/services/flappy_translator.dart b/lib/src/services/flappy_translator.dart index 986a4a2..e781fd7 100644 --- a/lib/src/services/flappy_translator.dart +++ b/lib/src/services/flappy_translator.dart @@ -23,6 +23,8 @@ class FlappyTranslator { bool? exposeGetString, bool? exposeLocaStrings, bool? exposeLocaleMaps, + bool? generateComments, + List? commentLanguages, }) { final file = File(inputFilePath); Validator.validateFile(file); @@ -42,6 +44,8 @@ class FlappyTranslator { exposeGetString ??= DefaultSettings.exposeGetString; exposeLocaStrings ??= DefaultSettings.exposeLocaStrings; exposeLocaleMaps ??= DefaultSettings.exposeLocaleMaps; + generateComments ??= DefaultSettings.generateComments; + commentLanguages ??= DefaultSettings.commentLanguages; final codeGenerator = CodeGenerator( className: className, @@ -64,6 +68,9 @@ class FlappyTranslator { final supportedLanguages = parser.supportedLanguages; Validator.validateSupportedLanguages(supportedLanguages); codeGenerator.setSupportedLanguages(supportedLanguages); + if (generateComments) { + codeGenerator.enableCommentGeneration(commentLanguages); + } FlappyLogger.logProgress('Locales $supportedLanguages determined.'); final localizationsTable = parser.localizationsTable; diff --git a/test/extensions/list_extensions_test.dart b/test/extensions/list_extensions_test.dart new file mode 100644 index 0000000..35b3dad --- /dev/null +++ b/test/extensions/list_extensions_test.dart @@ -0,0 +1,26 @@ +import 'package:flappy_translator/src/extensions/list_extensions.dart'; +import 'package:test/test.dart'; + +void main() { + test('mapIndexedWhere', () { + expect( + ['a', 'b', 'c'].mapIndexedWhere((i, e) => e).toList(), ['a', 'b', 'c']); + expect(['a', 'b', 'c'].mapIndexedWhere((i, e) => i).toList(), [0, 1, 2]); + // with filter + expect( + ['aasd', 'bas', 'cas', 'a', 'asdas'] + .mapIndexedWhere((i, e) => e.length) + .toList(), + [4, 3, 3, 1, 5]); + expect( + ['aasd', 'bas', 'cas', 'a', 'asdas'] + .mapIndexedWhere((i, e) => e, (i, e) => e.length != 3) + .toList(), + ['aasd', 'a', 'asdas']); + expect( + ['123456', '123', '123', '12345678', '123456'] + .mapIndexedWhere((i, e) => e[i], (i, e) => e.length != 3) + .toList(), + ['1', '4', '5']); + }); +} diff --git a/test/services/code_generation/code_generator_test.dart b/test/services/code_generation/code_generator_test.dart index 522a04c..b0bed53 100644 --- a/test/services/code_generation/code_generator_test.dart +++ b/test/services/code_generation/code_generator_test.dart @@ -456,6 +456,187 @@ class I18nDelegate extends LocalizationsDelegate { @override bool shouldReload(I18nDelegate old) => false; } +''', + ); + }); + + test( + 'When comment generation is true, documentation style comments with translations are added', + () { + final codeGenerator = CodeGenerator(); + codeGenerator.setSupportedLanguages(['en', 'de', 'es']); + codeGenerator.enableCommentGeneration(); + codeGenerator.addField( + 'test', 'Hello', ['Hello', 'Hello in german', 'Hello in spanish']); + codeGenerator.finalize(); + + expect( + codeGenerator.formattedString, + ''' +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: public_member_api_docs, prefer_single_quotes, avoid_escaping_inner_quotes, prefer_const_constructors, sort_constructors_first, always_specify_types + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +/// A class generated by flappy_translator package containing localized strings +class I18n { + /// * en: Hello + /// * de: Hello in german + /// * es: Hello in spanish + String get test => _getText("test"); + + static late Map _localizedValues; + + static const _enValues = { + "test": "Hello", + }; + + static const _deValues = { + "test": "Hello in german", + }; + + static const _esValues = { + "test": "Hello in spanish", + }; + + static const _allValues = { + "en": _enValues, + "de": _deValues, + "es": _esValues, + }; + + I18n(Locale locale) : _locale = locale { + _localizedValues = {}; + } + + final Locale _locale; + + static I18n of(BuildContext context) => + Localizations.of(context, I18n)!; + + String _getText(String key) => _localizedValues[key] ?? '** \$key not found'; + + Locale get currentLocale => _locale; + + String get currentLanguage => _locale.languageCode; + + static Future load(Locale locale) async { + final translations = I18n(locale); + _localizedValues = _allValues[locale.toString()]!; + return translations; + } +} + +class I18nDelegate extends LocalizationsDelegate { + const I18nDelegate(); + + static final Set supportedLocals = { + Locale('en'), + Locale('de'), + Locale('es'), + }; + + @override + bool isSupported(Locale locale) => supportedLocals.contains(locale); + + @override + Future load(Locale locale) => I18n.load(locale); + + @override + bool shouldReload(I18nDelegate old) => false; +} +''', + ); + }); + + test( + 'When when comment languages are supplied, only the selected languages are added to comments', + () { + final codeGenerator = CodeGenerator(); + codeGenerator.setSupportedLanguages(['en', 'de', 'es']); + codeGenerator.enableCommentGeneration(['de', 'es']); + codeGenerator.addField( + 'test', 'Hello', ['Hello', 'Hello in german', 'Hello in spanish']); + codeGenerator.finalize(); + + expect( + codeGenerator.formattedString, + ''' +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: public_member_api_docs, prefer_single_quotes, avoid_escaping_inner_quotes, prefer_const_constructors, sort_constructors_first, always_specify_types + +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +/// A class generated by flappy_translator package containing localized strings +class I18n { + /// * de: Hello in german + /// * es: Hello in spanish + String get test => _getText("test"); + + static late Map _localizedValues; + + static const _enValues = { + "test": "Hello", + }; + + static const _deValues = { + "test": "Hello in german", + }; + + static const _esValues = { + "test": "Hello in spanish", + }; + + static const _allValues = { + "en": _enValues, + "de": _deValues, + "es": _esValues, + }; + + I18n(Locale locale) : _locale = locale { + _localizedValues = {}; + } + + final Locale _locale; + + static I18n of(BuildContext context) => + Localizations.of(context, I18n)!; + + String _getText(String key) => _localizedValues[key] ?? '** \$key not found'; + + Locale get currentLocale => _locale; + + String get currentLanguage => _locale.languageCode; + + static Future load(Locale locale) async { + final translations = I18n(locale); + _localizedValues = _allValues[locale.toString()]!; + return translations; + } +} + +class I18nDelegate extends LocalizationsDelegate { + const I18nDelegate(); + + static final Set supportedLocals = { + Locale('en'), + Locale('de'), + Locale('es'), + }; + + @override + bool isSupported(Locale locale) => supportedLocals.contains(locale); + + @override + Future load(Locale locale) => I18n.load(locale); + + @override + bool shouldReload(I18nDelegate old) => false; +} ''', ); });