From 173322dc78c794fd2b63e447bdadc99678b8f8a0 Mon Sep 17 00:00:00 2001 From: Shi-Hao Hong Date: Mon, 30 Dec 2019 11:22:01 -0800 Subject: [PATCH] Support preferred locales for gen_l10n (#47845) * Add preferred-supported-locales option to gen_l10n.dart executable --- dev/tools/localization/bin/gen_l10n.dart | 11 ++ dev/tools/localization/gen_l10n.dart | 55 ++++++++- .../test/localization/gen_l10n_test.dart | 112 ++++++++++++++++++ 3 files changed, 175 insertions(+), 3 deletions(-) diff --git a/dev/tools/localization/bin/gen_l10n.dart b/dev/tools/localization/bin/gen_l10n.dart index 31fb572e181..16b558764b1 100644 --- a/dev/tools/localization/bin/gen_l10n.dart +++ b/dev/tools/localization/bin/gen_l10n.dart @@ -46,6 +46,15 @@ Future main(List arguments) async { help: 'The Dart class name to use for the output localization and ' 'localizations delegate classes.', ); + parser.addOption( + 'preferred-supported-locales', + help: 'The list of preferred supported locales for the application. ' + 'By default, the tool will generate the supported locales list in ' + 'alphabetical order. Use this flag if you would like to default to ' + 'a different locale. \n\n' + 'For example, pass in [\'en_US\'] if you would like your app to ' + 'default to American English if a device supports it.', + ); final argslib.ArgResults results = parser.parse(arguments); if (results['help'] == true) { @@ -57,6 +66,7 @@ Future main(List arguments) async { final String outputFileString = results['output-localization-file'] as String; final String templateArbFileName = results['template-arb-file'] as String; final String classNameString = results['output-class'] as String; + final String preferredSupportedLocaleString = results['preferred-supported-locales'] as String; const local.LocalFileSystem fs = local.LocalFileSystem(); final LocalizationsGenerator localizationsGenerator = LocalizationsGenerator(fs); @@ -67,6 +77,7 @@ Future main(List arguments) async { templateArbFileName: templateArbFileName, outputFileString: outputFileString, classNameString: classNameString, + preferredSupportedLocaleString: preferredSupportedLocaleString, ) ..parseArbFiles() ..generateClassMethods() diff --git a/dev/tools/localization/gen_l10n.dart b/dev/tools/localization/gen_l10n.dart index cd3067cc9da..5281c44c632 100644 --- a/dev/tools/localization/gen_l10n.dart +++ b/dev/tools/localization/gen_l10n.dart @@ -534,8 +534,21 @@ class LocalizationsGenerator { /// The class name is specified with the [initialize] method. String get className => _className; String _className; - /// Sets the [className] for the localizations and localizations delegate - /// classes. + + /// The list of preferred supported locales. + /// + /// By default, the list of supported locales in the localizations class + /// will be sorted in alphabetical order. However, this option + /// allows for a set of preferred locales to appear at the top of the + /// list. + /// + /// The order of locales in this list will also be the order of locale + /// priority. For example, if a device supports 'en' and 'es' and + /// ['es', 'en'] is passed in, the 'es' locale will take priority over 'en'. + /// + /// The list of preferred locales is specified with the [initialize] method. + List get preferredSupportedLocales => _preferredSupportedLocales; + List _preferredSupportedLocales; /// The list of all arb path strings in [l10nDirectory]. final List arbPathStrings = []; @@ -564,10 +577,12 @@ class LocalizationsGenerator { String templateArbFileName, String outputFileString, String classNameString, + String preferredSupportedLocaleString, }) { setL10nDirectory(l10nDirectoryPath); setTemplateArbFile(templateArbFileName); setOutputFile(outputFileString); + setPreferredSupportedLocales(preferredSupportedLocaleString); className = classNameString; } @@ -616,6 +631,8 @@ class LocalizationsGenerator { outputFile = _fs.file(path.join(l10nDirectory.path, outputFileString)); } + /// Sets the [className] for the localizations and localizations delegate + /// classes. @visibleForTesting set className(String classNameString) { if (classNameString == null) @@ -627,6 +644,21 @@ class LocalizationsGenerator { _className = classNameString; } + /// Sets [preferredSupportedLocales] so that this particular list of locales + /// will take priority over the other locales. + @visibleForTesting + void setPreferredSupportedLocales(String inputLocales) { + if (inputLocales != null) { + final List preferredLocalesStringList = json.decode(inputLocales) as List; + _preferredSupportedLocales = preferredLocalesStringList.map((dynamic localeString) { + if (localeString.runtimeType != String) { + throw L10nException('Incorrect runtime type for $localeString'); + } + return LocaleInfo.fromString(localeString.toString()); + }).toList(); + } + } + /// Scans [l10nDirectory] for arb files and parses them for language and locale /// information. void parseArbFiles() { @@ -668,10 +700,27 @@ class LocalizationsGenerator { arbPathStrings.sort(); localeInfoList.sort(); - supportedLocales.addAll(localeInfoList); supportedLanguageCodes.addAll(localeInfoList.map((LocaleInfo localeInfo) { return '\'${localeInfo.languageCode}\''; })); + + if (preferredSupportedLocales != null) { + for (LocaleInfo preferredLocale in preferredSupportedLocales) { + if (!localeInfoList.contains(preferredLocale)) { + throw L10nException( + 'The preferred supported locale, \'$preferredLocale\', cannot be ' + 'added. Please make sure that there is a corresponding arb file ' + 'with translations for the locale, or remove the locale from the ' + 'preferred supported locale list if there is no intent to support ' + 'it.' + ); + } + + localeInfoList.removeWhere((LocaleInfo localeInfo) => localeInfo == preferredLocale); + } + localeInfoList.insertAll(0, preferredSupportedLocales); + } + supportedLocales.addAll(localeInfoList); } /// Generates the methods for the localizations class. diff --git a/dev/tools/test/localization/gen_l10n_test.dart b/dev/tools/test/localization/gen_l10n_test.dart index 232b2cb0562..5ff906505b6 100644 --- a/dev/tools/test/localization/gen_l10n_test.dart +++ b/dev/tools/test/localization/gen_l10n_test.dart @@ -294,6 +294,118 @@ void main() { expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('zh')); }); + test('adds preferred locales to the top of supportedLocales and supportedLanguageCodes', () { + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile('app_en_US.arb') + .writeAsStringSync(singleMessageArbFileString); + l10nDirectory.childFile('app_es.arb') + .writeAsStringSync(singleEsMessageArbFileString); + l10nDirectory.childFile('app_zh.arb') + .writeAsStringSync(singleZhMessageArbFileString); + + const String preferredSupportedLocaleString = '["zh", "es"]'; + LocalizationsGenerator generator; + try { + generator = LocalizationsGenerator(fs); + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + preferredSupportedLocaleString: preferredSupportedLocaleString, + ); + generator.parseArbFiles(); + } on L10nException catch (e) { + fail('Setting language and locales should not fail: \n$e'); + } + + expect(generator.supportedLocales.first, LocaleInfo.fromString('zh')); + expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es')); + expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('en_US')); + }); + + test( + 'throws an error attempting to add preferred locales ' + 'with incorrect runtime type', + () { + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile('app_en_US.arb') + .writeAsStringSync(singleMessageArbFileString); + l10nDirectory.childFile('app_es.arb') + .writeAsStringSync(singleEsMessageArbFileString); + l10nDirectory.childFile('app_zh.arb') + .writeAsStringSync(singleZhMessageArbFileString); + + const String preferredSupportedLocaleString = '[44, "en_US"]'; + LocalizationsGenerator generator; + try { + generator = LocalizationsGenerator(fs); + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + preferredSupportedLocaleString: preferredSupportedLocaleString, + ); + generator.parseArbFiles(); + } on L10nException catch (e) { + expect( + e.message, + contains('Incorrect runtime type'), + ); + return; + } + + fail( + 'Should fail since an incorrect runtime type was used ' + 'in the preferredSupportedLocales list.' + ); + }, + ); + + test( + 'throws an error attempting to add preferred locales ' + 'when there is no corresponding arb file for that ' + 'locale', + () { + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile('app_en_US.arb') + .writeAsStringSync(singleMessageArbFileString); + l10nDirectory.childFile('app_es.arb') + .writeAsStringSync(singleEsMessageArbFileString); + l10nDirectory.childFile('app_zh.arb') + .writeAsStringSync(singleZhMessageArbFileString); + + const String preferredSupportedLocaleString = '["am", "es"]'; + LocalizationsGenerator generator; + try { + generator = LocalizationsGenerator(fs); + generator.initialize( + l10nDirectoryPath: defaultArbPathString, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + preferredSupportedLocaleString: preferredSupportedLocaleString, + ); + generator.parseArbFiles(); + } on L10nException catch (e) { + expect( + e.message, + contains("The preferred supported locale, 'am', cannot be added."), + ); + return; + } + + fail( + 'Should fail since an unsupported locale was added ' + 'to the preferredSupportedLocales list.' + ); + }, + ); + test('correctly sorts arbPathString alphabetically', () { final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true);