diff --git a/dev/tools/localization/gen_l10n_types.dart b/dev/tools/localization/gen_l10n_types.dart index 2007e4b577..fbc052c8c4 100644 --- a/dev/tools/localization/gen_l10n_types.dart +++ b/dev/tools/localization/gen_l10n_types.dart @@ -5,6 +5,9 @@ import 'dart:convert'; import 'dart:io'; +import 'package:intl/locale.dart'; +import 'package:path/path.dart' as path; + import 'localizations_utils.dart'; // The set of date formats that can be automatically localized. @@ -374,19 +377,53 @@ class AppResourceBundle { } String localeString = resources['@@locale'] as String; - if (localeString == null) { - final RegExp filenameRE = RegExp(r'^[^_]*_(\w+)\.arb$'); - final RegExpMatch match = filenameRE.firstMatch(file.path); - localeString = match == null ? null : match[1]; + + // Look for the first instance of an ISO 639-1 language code, matching exactly. + final String fileName = path.basenameWithoutExtension(file.path); + + for (int index = 0; index < fileName.length; index += 1) { + // If an underscore was found, check if locale string follows. + if (fileName[index] == '_' && fileName[index + 1] != null) { + // If Locale.tryParse fails, it returns null. + final Locale parserResult = Locale.tryParse(fileName.substring(index + 1)); + // If the parserResult is not an actual locale identifier, end the loop. + if (parserResult != null && _iso639Languages.contains(parserResult.languageCode)) { + // The parsed result uses dashes ('-'), but we want underscores ('_'). + final String parserLocaleString = parserResult.toString().replaceAll('-', '_'); + + + if (localeString == null) { + // If @@locale was not defined, use the filename locale suffix. + localeString = parserLocaleString; + } else { + // If the localeString was defined in @@locale and in the filename, verify to + // see if the parsed locale matches, throw an error if it does not. This + // prevents developers from confusing issues when both @@locale and + // "_{locale}" is specified in the filename. + if (localeString != parserLocaleString) { + throw L10nException( + 'The locale specified in @@locale and the arb filename do not match. \n' + 'Please make sure that they match, since this prevents any confusion \n' + 'with which locale to use. Otherwise, specify the locale in either the \n' + 'filename of the @@locale key only.\n' + 'Current @@locale value: $localeString\n' + 'Current filename extension: $parserLocaleString' + ); + } + } + break; + } + } } + if (localeString == null) { throw L10nException( "The following .arb file's locale could not be determined: \n" '${file.path} \n' "Make sure that the locale is specified in the file's '@@locale' " 'property or as part of the filename (e.g. file_en.arb)' - ); - } + ); + } final Iterable ids = resources.keys.where((String key) => !key.startsWith('@')); return AppResourceBundle._(file, LocaleInfo.fromString(localeString), resources, ids); @@ -469,3 +506,191 @@ class AppResourceBundleCollection { return 'AppResourceBundleCollection(${_directory.path}, ${locales.length} locales)'; } } + +// A set containing all the ISO630-1 languages. This list was pulled from https://datahub.io/core/language-codes. +final Set _iso639Languages = { + 'aa', + 'ab', + 'ae', + 'af', + 'ak', + 'am', + 'an', + 'ar', + 'as', + 'av', + 'ay', + 'az', + 'ba', + 'be', + 'bg', + 'bh', + 'bi', + 'bm', + 'bn', + 'bo', + 'br', + 'bs', + 'ca', + 'ce', + 'ch', + 'co', + 'cr', + 'cs', + 'cu', + 'cv', + 'cy', + 'da', + 'de', + 'dv', + 'dz', + 'ee', + 'el', + 'en', + 'eo', + 'es', + 'et', + 'eu', + 'fa', + 'ff', + 'fi', + 'fj', + 'fo', + 'fr', + 'fy', + 'ga', + 'gd', + 'gl', + 'gn', + 'gu', + 'gv', + 'ha', + 'he', + 'hi', + 'ho', + 'hr', + 'ht', + 'hu', + 'hy', + 'hz', + 'ia', + 'id', + 'ie', + 'ig', + 'ii', + 'ik', + 'io', + 'is', + 'it', + 'iu', + 'ja', + 'jv', + 'ka', + 'kg', + 'ki', + 'kj', + 'kk', + 'kl', + 'km', + 'kn', + 'ko', + 'kr', + 'ks', + 'ku', + 'kv', + 'kw', + 'ky', + 'la', + 'lb', + 'lg', + 'li', + 'ln', + 'lo', + 'lt', + 'lu', + 'lv', + 'mg', + 'mh', + 'mi', + 'mk', + 'ml', + 'mn', + 'mr', + 'ms', + 'mt', + 'my', + 'na', + 'nb', + 'nd', + 'ne', + 'ng', + 'nl', + 'nn', + 'no', + 'nr', + 'nv', + 'ny', + 'oc', + 'oj', + 'om', + 'or', + 'os', + 'pa', + 'pi', + 'pl', + 'ps', + 'pt', + 'qu', + 'rm', + 'rn', + 'ro', + 'ru', + 'rw', + 'sa', + 'sc', + 'sd', + 'se', + 'sg', + 'si', + 'sk', + 'sl', + 'sm', + 'sn', + 'so', + 'sq', + 'sr', + 'ss', + 'st', + 'su', + 'sv', + 'sw', + 'ta', + 'te', + 'tg', + 'th', + 'ti', + 'tk', + 'tl', + 'tn', + 'to', + 'tr', + 'ts', + 'tt', + 'tw', + 'ty', + 'ug', + 'uk', + 'ur', + 'uz', + 've', + 'vi', + 'vo', + 'wa', + 'wo', + 'xh', + 'yi', + 'yo', + 'za', + 'zh', + 'zu', +}; diff --git a/dev/tools/test/localization/gen_l10n_test.dart b/dev/tools/test/localization/gen_l10n_test.dart index b055857b5c..5105e66372 100644 --- a/dev/tools/test/localization/gen_l10n_test.dart +++ b/dev/tools/test/localization/gen_l10n_test.dart @@ -308,6 +308,61 @@ void main() { expect(generator.header, '/// Sample header in a text file'); }); + test('sets templateArbFileName with more than one underscore correctly', () { + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile('app_localizations_en.arb') + .writeAsStringSync(singleMessageArbFileString); + l10nDirectory.childFile('app_localizations_es.arb') + .writeAsStringSync(singleEsMessageArbFileString); + LocalizationsGenerator generator; + try { + generator = LocalizationsGenerator(fs); + generator + ..initialize( + inputPathString: defaultL10nPathString, + templateArbFileName: 'app_localizations_en.arb', + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ) + ..loadResources() + ..writeOutputFiles(); + } on L10nException catch (e) { + fail('Generating output should not fail: \n${e.message}'); + } + + final Directory outputDirectory = fs.directory('lib').childDirectory('l10n'); + expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); + expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue); + expect(outputDirectory.childFile('output-localization-file_es.dart').existsSync(), isTrue); + }); + + test('filenames with invalid locales should not be recognized', () { + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile('app_localizations_en.arb') + .writeAsStringSync(singleMessageArbFileString); + l10nDirectory.childFile('app_localizations_en_CA_foo.arb') + .writeAsStringSync(singleMessageArbFileString); + LocalizationsGenerator generator; + try { + generator = LocalizationsGenerator(fs); + generator + ..initialize( + inputPathString: defaultL10nPathString, + templateArbFileName: 'app_localizations_en.arb', + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ) + ..loadResources(); + } on L10nException catch (e) { + expect(e.message, contains('The following .arb file\'s locale could not be determined')); + return; + } + + fail('Using app_en_CA_foo.arb should fail as it is not a valid locale.'); + }); + test('correctly creates an unimplemented messages file', () { fs.currentDirectory.childDirectory('lib').childDirectory('l10n') ..createSync(recursive: true) @@ -799,7 +854,7 @@ void main() { expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true); }); - test('correctly prioritizes @@locale property in arb file over filename', () { + test('correctly requires @@locale property in arb file to match the filename locale suffix', () { const String arbFileWithEnLocale = ''' { "@@locale": "en", @@ -837,15 +892,14 @@ void main() { ); generator.loadResources(); } on L10nException catch (e) { - fail('Setting language and locales should not fail: \n${e.message}'); + expect(e.message, contains('The locale specified in @@locale and the arb filename do not match.')); + return; } - // @@locale property should hold higher priority - expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true); - expect(generator.supportedLocales.contains(LocaleInfo.fromString('zh')), true); - // filename should not be used since @@locale is specified - expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), false); - expect(generator.supportedLocales.contains(LocaleInfo.fromString('am')), false); + fail( + 'An exception should occur if the @@locale and the filename extensions are ' + 'defined but not matching.' + ); }); test("throws when arb file's locale could not be determined", () {