Warn about supported locales that lack localizations (#23850)

diff --git a/packages/flutter/lib/src/cupertino/localizations.dart b/packages/flutter/lib/src/cupertino/localizations.dart
index 05e4783..ce15d66 100644
--- a/packages/flutter/lib/src/cupertino/localizations.dart
+++ b/packages/flutter/lib/src/cupertino/localizations.dart
@@ -213,6 +213,9 @@
 
   @override
   bool shouldReload(_CupertinoLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'DefaultCupertinoLocalizations.delegate(en_US)';
 }
 
 /// US English strings for the cupertino widgets.
diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart
index 5927243..9fa3798 100644
--- a/packages/flutter/lib/src/material/material_localizations.dart
+++ b/packages/flutter/lib/src/material/material_localizations.dart
@@ -338,6 +338,9 @@
 
   @override
   bool shouldReload(_MaterialLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'DefaultMaterialLocalizations.delegate(en_US)';
 }
 
 /// US English strings for the material widgets.
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 3727486..ed6d941 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -5,6 +5,7 @@
 import 'dart:async';
 import 'dart:ui' as ui show window;
 
+import 'package:flutter/foundation.dart';
 import 'package:flutter/rendering.dart';
 
 import 'banner.dart';
@@ -769,6 +770,53 @@
 
   // BUILDER
 
+  bool _debugCheckLocalizations(Locale appLocale) {
+    assert(() {
+      final Set<Type> unsupportedTypes =
+        _localizationsDelegates.map<Type>((LocalizationsDelegate<dynamic> delegate) => delegate.type).toSet();
+      for (LocalizationsDelegate<dynamic> delegate in _localizationsDelegates) {
+        if (!unsupportedTypes.contains(delegate.type))
+          continue;
+        if (delegate.isSupported(appLocale))
+          unsupportedTypes.remove(delegate.type);
+      }
+      if (unsupportedTypes.isEmpty)
+        return true;
+
+      // Currently the Cupertino library only provides english localizations.
+      // Remove this when https://github.com/flutter/flutter/issues/23847
+      // is fixed.
+      if (listEquals(unsupportedTypes.map((Type type) => type.toString()).toList(), <String>['CupertinoLocalizations']))
+        return true;
+
+      final StringBuffer message = StringBuffer();
+      message.writeln('\u2550' * 8);
+      message.writeln(
+        'Warning: This application\'s locale, $appLocale, is not supported by all of its\n'
+        'localization delegates.'
+      );
+      for (Type unsupportedType in unsupportedTypes) {
+        // Currently the Cupertino library only provides english localizations.
+        // Remove this when https://github.com/flutter/flutter/issues/23847
+        // is fixed.
+        if (unsupportedType.toString() == 'CupertinoLocalizations')
+          continue;
+        message.writeln(
+          '> A $unsupportedType delegate that supports the $appLocale locale was not found.'
+        );
+      }
+      message.writeln(
+        'See https://flutter.io/tutorials/internationalization/ for more\n'
+        'information about configuring an app\'s locale, supportedLocales,\n'
+        'and localizationsDelegates parameters.'
+      );
+      message.writeln('\u2550' * 8);
+      debugPrint(message.toString());
+      return true;
+    }());
+    return true;
+  }
+
   @override
   Widget build(BuildContext context) {
     Widget navigator;
@@ -874,12 +922,16 @@
       );
     }
 
+    final Locale appLocale = widget.locale != null
+      ? _resolveLocale(widget.locale, widget.supportedLocales)
+      : _locale;
+
+    assert(_debugCheckLocalizations(appLocale));
+
     return MediaQuery(
       data: MediaQueryData.fromWindow(ui.window),
       child: Localizations(
-        locale: widget.locale != null
-          ? _resolveLocale(widget.locale, widget.supportedLocales)
-          : _locale,
+        locale: appLocale,
         delegates: _localizationsDelegates.toList(),
         child: title,
       ),
diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart
index d2c1f44..459a34c 100644
--- a/packages/flutter/lib/src/widgets/localizations.dart
+++ b/packages/flutter/lib/src/widgets/localizations.dart
@@ -181,6 +181,9 @@
 
   @override
   bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'DefaultWidgetsLocalizations.delegate(en_US)';
 }
 
 /// US English localizations for the widgets library.
diff --git a/packages/flutter_localizations/lib/src/material_localizations.dart b/packages/flutter_localizations/lib/src/material_localizations.dart
index 95151ce..f56b840 100644
--- a/packages/flutter_localizations/lib/src/material_localizations.dart
+++ b/packages/flutter_localizations/lib/src/material_localizations.dart
@@ -641,4 +641,7 @@
 
   @override
   bool shouldReload(_MaterialLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'GlobalMaterialLocalizations.delegate(${kSupportedLanguages.length} locales)';
 }
diff --git a/packages/flutter_localizations/lib/src/widgets_localizations.dart b/packages/flutter_localizations/lib/src/widgets_localizations.dart
index 45d7522..4150e19 100644
--- a/packages/flutter_localizations/lib/src/widgets_localizations.dart
+++ b/packages/flutter_localizations/lib/src/widgets_localizations.dart
@@ -74,4 +74,7 @@
 
   @override
   bool shouldReload(_WidgetsLocalizationsDelegate old) => false;
+
+  @override
+  String toString() => 'GlobalWidgetsLocalizations.delegate(all locales)';
 }