Update gen_l10n tool to require base locale; Stocks app refresh (#51602)

diff --git a/dev/benchmarks/test_apps/stocks/README.md b/dev/benchmarks/test_apps/stocks/README.md
index d8d0785..88b3dde 100644
--- a/dev/benchmarks/test_apps/stocks/README.md
+++ b/dev/benchmarks/test_apps/stocks/README.md
@@ -23,16 +23,10 @@
 ## Internationalization
 
 This app has been internationalized (just enough to show how it's
-done). It's an example of how one can do so with the
-[Dart intl package](https://pub.dev/packages/intl).
+done). It's an example of how one can do so with the gen_l10n tool.
 
-The [Flutter Internationalization Tutorial](https://flutter.dev/tutorials/internationalization/)
-covers Flutter app internationalization in general.
-
-See [regenerate.md](lib/i18n/regenerate.md) for an explanation
-of how the Dart internationalization tools, like
-`intl_translation:generate_from_arb`, were used to generate
-localizations for this app.
+See [regenerate.md](lib/i18n/regenerate.md) for an explanation for how
+the tool is used to generate localizations for this app.
 
 ## Icon
 
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/.dartignore b/dev/benchmarks/test_apps/stocks/lib/i18n/.dartignore
deleted file mode 100644
index e69de29..0000000
--- a/dev/benchmarks/test_apps/stocks/lib/i18n/.dartignore
+++ /dev/null
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_all.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_all.dart
deleted file mode 100644
index 867afc7..0000000
--- a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_all.dart
+++ /dev/null
@@ -1,67 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that looks up messages for specific locales by
-// delegating to the appropriate library.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:implementation_imports, file_names, unnecessary_new
-// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
-// ignore_for_file:argument_type_not_assignable, invalid_assignment
-// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
-// ignore_for_file:comment_references
-
-import 'dart:async';
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-import 'package:intl/src/intl_helpers.dart';
-
-import 'messages_en_US.dart' as messages_en_us;
-import 'messages_es_ES.dart' as messages_es_es;
-
-typedef Future<dynamic> LibraryLoader();
-Map<String, LibraryLoader> _deferredLibraries = {
-  'en_US': () => new Future.value(null),
-  'es_ES': () => new Future.value(null),
-};
-
-MessageLookupByLibrary _findExact(String localeName) {
-  switch (localeName) {
-    case 'en_US':
-      return messages_en_us.messages;
-    case 'es_ES':
-      return messages_es_es.messages;
-    default:
-      return null;
-  }
-}
-
-/// User programs should call this before using [localeName] for messages.
-Future<bool> initializeMessages(String localeName) async {
-  var availableLocale = Intl.verifiedLocale(
-    localeName,
-    (locale) => _deferredLibraries[locale] != null,
-    onFailure: (_) => null);
-  if (availableLocale == null) {
-    return new Future.value(false);
-  }
-  var lib = _deferredLibraries[availableLocale];
-  await (lib == null ? new Future.value(false) : lib());
-  initializeInternalMessageLookup(() => new CompositeMessageLookup());
-  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
-  return new Future.value(true);
-}
-
-bool _messagesExistFor(String locale) {
-  try {
-    return _findExact(locale) != null;
-  } catch (e) {
-    return false;
-  }
-}
-
-MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
-  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
-      onFailure: (_) => null);
-  if (actualLocale == null) return null;
-  return _findExact(actualLocale);
-}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_en_US.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_en_US.dart
deleted file mode 100644
index fedb57c..0000000
--- a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_en_US.dart
+++ /dev/null
@@ -1,28 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a en_US locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'en_US';
-
-  final messages = _notInlinedMessages(_notInlinedMessages);
-  static _notInlinedMessages(_) => <String, Function> {
-    "market" : MessageLookupByLibrary.simpleMessage("MARKET"),
-    "portfolio" : MessageLookupByLibrary.simpleMessage("PORTFOLIO"),
-    "title" : MessageLookupByLibrary.simpleMessage("Stocks")
-  };
-}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_es_ES.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_es_ES.dart
deleted file mode 100644
index b2c0fd3..0000000
--- a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_es_ES.dart
+++ /dev/null
@@ -1,28 +0,0 @@
-// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
-// This is a library that provides messages for a es_ES locale. All the
-// messages from the main program should be duplicated here with the same
-// function name.
-
-// Ignore issues from commonly used lints in this file.
-// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
-// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
-// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
-// ignore_for_file:unused_import, file_names
-
-import 'package:intl/intl.dart';
-import 'package:intl/message_lookup_by_library.dart';
-
-final messages = new MessageLookup();
-
-typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
-
-class MessageLookup extends MessageLookupByLibrary {
-  String get localeName => 'es_ES';
-
-  final messages = _notInlinedMessages(_notInlinedMessages);
-  static _notInlinedMessages(_) => <String, Function> {
-    "market" : MessageLookupByLibrary.simpleMessage("MERCADO"),
-    "portfolio" : MessageLookupByLibrary.simpleMessage("CARTERA"),
-    "title" : MessageLookupByLibrary.simpleMessage("Acciones")
-  };
-}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md b/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md
index e75b945..c4bbb1f 100644
--- a/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md
@@ -6,8 +6,9 @@
 
 Rebuilding everything requires two steps.
 
-1. Create or update the English and Spanish localizations, `stocks_en_US.arb`
-and `stocks_es_ES.arb`. See the [ARB specification](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification)
+1. Create or update the English and Spanish localizations,
+`stocks_en_US.arb`, `stocks_en.arb`, and `stocks_es.arb`. See the
+[ARB specification](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification)
 for more info.
 
 2. With `examples/stocks` as the current directory, generate a
@@ -20,7 +21,9 @@
     --output-class=StockStrings
 ```
 
-The `StockStrings` class uses the generated `initializeMessages()`function
-(`messages_all.dart`) to load the localized messages and `Intl.message()`
-to look them up. The generated class's API documentation explains how to add
-the new localizations delegate and supported locales to the Flutter application.
+The `StockStrings` class creates a delegate that performs message lookups
+based on the locale of the device. In this case, the stocks app supports
+`en`, `en_US`, and `es`. Thus, the `StockStringsEn` and `StockStringsEs`
+classes extends `StockStrings`. `StockStringsEnUs` extends
+`StockStringsEn`. This allows `StockStringsEnUs` to fall back on messages
+in `StockStringsEn`.
\ No newline at end of file
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart
index 8c12e5a..aee1bf6 100644
--- a/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart
@@ -1,10 +1,16 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
 import 'dart:async';
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
-import 'package:intl/intl.dart';
+// ignore: unused_import
+import 'package:intl/intl.dart' as intl;
 
-import 'messages_all.dart';
+// ignore_for_file: unnecessary_brace_in_string_interps
 
 /// Callers can lookup localized strings with an instance of StockStrings returned
 /// by `StockStrings.of(context)`.
@@ -58,16 +64,12 @@
 /// you wish to add from the pop-up menu in the Value field. This list should
 /// be consistent with the languages listed in the StockStrings.supportedLocales
 /// property.
-class StockStrings {
-  StockStrings(Locale locale) : _localeName = Intl.canonicalizedLocale(locale.toString());
+abstract class StockStrings {
+  StockStrings(String locale) : assert(locale != null), _localeName = intl.Intl.canonicalizedLocale(locale.toString());
 
+  // ignore: unused_field
   final String _localeName;
 
-  static Future<StockStrings> load(Locale locale) {
-    return initializeMessages(locale.toString())
-      .then<StockStrings>((_) => StockStrings(locale));
-  }
-
   static StockStrings of(BuildContext context) {
     return Localizations.of<StockStrings>(context, StockStrings);
   }
@@ -80,6 +82,10 @@
   /// Returns a list of localizations delegates containing this delegate along with
   /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
   /// and GlobalWidgetsLocalizations.delegate.
+  ///
+  /// Additional delegates can be added by appending to this list in
+  /// MaterialApp. This list does not have to be used at all if a custom list
+  /// of delegates is preferred or required.
   static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
     delegate,
     GlobalMaterialLocalizations.delegate,
@@ -89,44 +95,28 @@
 
   /// A list of this localizations delegate's supported locales.
   static const List<Locale> supportedLocales = <Locale>[
-    Locale('en', 'US'),
-    Locale('es', 'ES'),
+    Locale('en'),
+    Locale('en, US'),
+    Locale('es')
   ];
 
-  String get market {
-    return Intl.message(
-      r'MARKET',
-      locale: _localeName,
-      name: 'market',
-      desc: r'Label for the Market tab'
-    );
-  }
+  // Title for the Stocks application
+  String get title;
 
-  String get portfolio {
-    return Intl.message(
-      r'PORTFOLIO',
-      locale: _localeName,
-      name: 'portfolio',
-      desc: r'Label for the Portfolio tab'
-    );
-  }
+  // Label for the Market tab
+  String get market;
 
-  String get title {
-    return Intl.message(
-      r'Stocks',
-      locale: _localeName,
-      name: 'title',
-      desc: r'Title for the Stocks application'
-    );
-  }
-
+  // Label for the Portfolio tab
+  String get portfolio;
 }
 
 class _StockStringsDelegate extends LocalizationsDelegate<StockStrings> {
   const _StockStringsDelegate();
 
   @override
-  Future<StockStrings> load(Locale locale) => StockStrings.load(locale);
+  Future<StockStrings> load(Locale locale) {
+    return SynchronousFuture<StockStrings>(_lookupStockStrings(locale));
+  }
 
   @override
   bool isSupported(Locale locale) => <String>['en', 'es'].contains(locale.languageCode);
@@ -134,3 +124,59 @@
   @override
   bool shouldReload(_StockStringsDelegate old) => false;
 }
+
+/// The translations for English (`en`).
+class StockStringsEn extends StockStrings {
+  StockStringsEn([String locale = 'en']) : super(locale);
+
+  @override
+  String get title => 'Stocks';
+
+  @override
+  String get market => 'MARKET';
+
+  @override
+  String get portfolio => 'PORTFOLIO';
+}
+
+/// The translations for English, as used in the United States (`en_US`).
+class StockStringsEnUs extends StockStringsEn {
+  StockStringsEnUs([String locale = 'en_US']) : super(locale);
+
+  @override
+  String get title => 'Stocks';
+
+  @override
+  String get market => 'MARKET';
+
+  @override
+  String get portfolio => 'PORTFOLIO';
+}
+
+/// The translations for Spanish Castilian (`es`).
+class StockStringsEs extends StockStrings {
+  StockStringsEs([String locale = 'es']) : super(locale);
+
+  @override
+  String get title => 'Acciones';
+
+  @override
+  String get market => 'MERCADO';
+
+  @override
+  String get portfolio => 'CARTERA';
+}
+
+StockStrings _lookupStockStrings(Locale locale) {
+  switch(locale.languageCode) {
+    case 'en': {
+      switch (locale.countryCode) {
+        case 'US': return StockStringsEnUs();
+      }
+      return StockStringsEn();
+    }
+    case 'es': return StockStringsEs();
+  }
+  assert(false, 'StockStrings.delegate failed to load unsupported locale "$locale"');
+  return null;
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_en.arb b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_en.arb
new file mode 100644
index 0000000..90c141e
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_en.arb
@@ -0,0 +1,5 @@
+{
+  "title": "Stocks",
+  "market": "MARKET",
+  "portfolio": "PORTFOLIO"
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es_ES.arb b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es.arb
similarity index 100%
rename from dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es_ES.arb
rename to dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es.arb
diff --git a/dev/benchmarks/test_apps/stocks/test/locale_test.dart b/dev/benchmarks/test_apps/stocks/test/locale_test.dart
index a17e925..9b2daf5 100644
--- a/dev/benchmarks/test_apps/stocks/test/locale_test.dart
+++ b/dev/benchmarks/test_apps/stocks/test/locale_test.dart
@@ -11,18 +11,10 @@
 
   testWidgets('Changing locale', (WidgetTester tester) async {
     stocks.main();
-    await tester.idle(); // see https://github.com/flutter/flutter/issues/1865
     await tester.pump();
     // The initial test app's locale is "_", so we're seeing the fallback translation here.
     expect(find.text('MARKET'), findsOneWidget);
-    await tester.binding.setLocale('es', 'US');
-    await tester.idle();
-
-    // The Localizations widget has been built with the new locale. The
-    // new locale's strings are loaded asynchronously, so we're still
-    // displaying the previous locale's strings.
-    await tester.pump();
-    expect(find.text('MARKET'), findsOneWidget);
+    await tester.binding.setLocale('es', '');
 
     // The localized strings have finished loading and dependent
     // widgets have been updated.
diff --git a/dev/tools/localization/gen_l10n_templates.dart b/dev/tools/localization/gen_l10n_templates.dart
index 328be9a..ad34cad 100644
--- a/dev/tools/localization/gen_l10n_templates.dart
+++ b/dev/tools/localization/gen_l10n_templates.dart
@@ -8,6 +8,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
+// ignore: unused_import
 import 'package:intl/intl.dart' as intl;
 
 // ignore_for_file: unnecessary_brace_in_string_interps
@@ -67,6 +68,7 @@
 abstract class @(class) {
   @(class)(String locale) : assert(locale != null), _localeName = intl.Intl.canonicalizedLocale(locale.toString());
 
+  // ignore: unused_field
   final String _localeName;
 
   static @(class) of(BuildContext context) {
diff --git a/dev/tools/localization/gen_l10n_types.dart b/dev/tools/localization/gen_l10n_types.dart
index b760dfc..b2d06a8 100644
--- a/dev/tools/localization/gen_l10n_types.dart
+++ b/dev/tools/localization/gen_l10n_types.dart
@@ -423,6 +423,23 @@
         languageToLocales[bundle.locale.languageCode].add(bundle.locale);
       }
     }
+
+    languageToLocales.forEach((String language, List<LocaleInfo> listOfCorrespondingLocales) {
+      final List<String> localeStrings = listOfCorrespondingLocales.map((LocaleInfo locale) {
+        return locale.toString();
+      }).toList();
+      if (!localeStrings.contains(language)) {
+        throw L10nException(
+          'Arb file for a fallback, $language, does not exist, even though \n'
+          'the following locale(s) exist: $listOfCorrespondingLocales. \n'
+          'When locales specify a script code or country code, a \n'
+          'base locale (without the script code or country code) should \n'
+          'exist as the fallback. Please create a {fileName}_$language.arb \n'
+          'file.'
+        );
+      }
+    });
+
     return AppResourceBundleCollection._(directory, localeToBundle, languageToLocales);
   }
 
diff --git a/dev/tools/test/localization/gen_l10n_test.dart b/dev/tools/test/localization/gen_l10n_test.dart
index e090bc0..058c0dc 100644
--- a/dev/tools/test/localization/gen_l10n_test.dart
+++ b/dev/tools/test/localization/gen_l10n_test.dart
@@ -15,7 +15,7 @@
 import '../common.dart';
 
 final String defaultArbPathString = path.join('lib', 'l10n');
-const String defaultTemplateArbFileName = 'app_en_US.arb';
+const String defaultTemplateArbFileName = 'app_en.arb';
 const String defaultOutputFileString = 'output-localization-file';
 const String defaultClassNameString = 'AppLocalizations';
 const String singleMessageArbFileString = '''
@@ -251,7 +251,7 @@
         fail('Setting language and locales should not fail: \n$e');
       }
 
-      expect(generator.supportedLocales.contains(LocaleInfo.fromString('en_US')), true);
+      expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true);
       expect(generator.supportedLocales.contains(LocaleInfo.fromString('es')), true);
     });
 
@@ -263,7 +263,7 @@
         .writeAsStringSync(singleZhMessageArbFileString);
       l10nDirectory.childFile('app_es.arb')
         .writeAsStringSync(singleEsMessageArbFileString);
-      l10nDirectory.childFile('app_en_US.arb')
+      l10nDirectory.childFile('app_en.arb')
         .writeAsStringSync(singleMessageArbFileString);
 
       LocalizationsGenerator generator;
@@ -280,7 +280,7 @@
         fail('Setting language and locales should not fail: \n$e');
       }
 
-      expect(generator.supportedLocales.first, LocaleInfo.fromString('en_US'));
+      expect(generator.supportedLocales.first, LocaleInfo.fromString('en'));
       expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es'));
       expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('zh'));
     });
@@ -288,7 +288,7 @@
     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')
+      l10nDirectory.childFile('app_en.arb')
         .writeAsStringSync(singleMessageArbFileString);
       l10nDirectory.childFile('app_es.arb')
         .writeAsStringSync(singleEsMessageArbFileString);
@@ -313,7 +313,7 @@
 
       expect(generator.supportedLocales.first, LocaleInfo.fromString('zh'));
       expect(generator.supportedLocales.elementAt(1), LocaleInfo.fromString('es'));
-      expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('en_US'));
+      expect(generator.supportedLocales.elementAt(2), LocaleInfo.fromString('en'));
     });
 
     test(
@@ -322,14 +322,14 @@
       () {
         final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
           ..createSync(recursive: true);
-        l10nDirectory.childFile('app_en_US.arb')
+        l10nDirectory.childFile('app_en.arb')
           .writeAsStringSync(singleMessageArbFileString);
         l10nDirectory.childFile('app_es.arb')
           .writeAsStringSync(singleEsMessageArbFileString);
         l10nDirectory.childFile('app_zh.arb')
           .writeAsStringSync(singleZhMessageArbFileString);
 
-        const String preferredSupportedLocaleString = '[44, "en_US"]';
+        const String preferredSupportedLocaleString = '[44, "en"]';
         LocalizationsGenerator generator;
         try {
           generator = LocalizationsGenerator(fs);
@@ -363,7 +363,7 @@
       () {
         final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
           ..createSync(recursive: true);
-        l10nDirectory.childFile('app_en_US.arb')
+        l10nDirectory.childFile('app_en.arb')
           .writeAsStringSync(singleMessageArbFileString);
         l10nDirectory.childFile('app_es.arb')
           .writeAsStringSync(singleEsMessageArbFileString);
@@ -405,7 +405,7 @@
         .writeAsStringSync(singleZhMessageArbFileString);
       l10nDirectory.childFile('app_es.arb')
         .writeAsStringSync(singleEsMessageArbFileString);
-      l10nDirectory.childFile('app_en_US.arb')
+      l10nDirectory.childFile('app_en.arb')
         .writeAsStringSync(singleMessageArbFileString);
 
       LocalizationsGenerator generator;
@@ -423,11 +423,11 @@
       }
 
       if (Platform.isWindows) {
-        expect(generator.arbPathStrings.first, r'lib\l10n\app_en_US.arb');
+        expect(generator.arbPathStrings.first, r'lib\l10n\app_en.arb');
         expect(generator.arbPathStrings.elementAt(1), r'lib\l10n\app_es.arb');
         expect(generator.arbPathStrings.elementAt(2), r'lib\l10n\app_zh.arb');
       } else {
-        expect(generator.arbPathStrings.first, 'lib/l10n/app_en_US.arb');
+        expect(generator.arbPathStrings.first, 'lib/l10n/app_en.arb');
         expect(generator.arbPathStrings.elementAt(1), 'lib/l10n/app_es.arb');
         expect(generator.arbPathStrings.elementAt(2), 'lib/l10n/app_zh.arb');
       }
@@ -583,6 +583,32 @@
         'should fail'
       );
     });
+
+    test('throws when the base locale does not exist', () {
+      final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n')
+        ..createSync(recursive: true);
+      l10nDirectory.childFile('app_en_US.arb')
+        .writeAsStringSync(singleMessageArbFileString);
+
+      try {
+        final LocalizationsGenerator generator = LocalizationsGenerator(fs);
+        generator.initialize(
+          l10nDirectoryPath: defaultArbPathString,
+          templateArbFileName: 'app_en_US.arb',
+          outputFileString: defaultOutputFileString,
+          classNameString: defaultClassNameString,
+        );
+        generator.loadResources();
+      } on L10nException catch (e) {
+        expect(e.message, contains('Arb file for a fallback, en, does not exist'));
+        return;
+      }
+
+      fail(
+        'Since en_US.arb is specified, but en.arb is not, '
+        'the tool should throw an error.'
+      );
+    });
   });
 
   group('generateCode', () {