Repository: flutter
Issue: Loading screen when loading asynchronous localization delegates
Labels: c: new feature,framework,a: internationalization,c: proposal,P3,team-framework,triaged-framework

<!-- Thank you for using Flutter!

     If you are looking for support, please check out our documentation
     or consider asking a question on Stack Overflow:
      * https://flutter.dev/
      * https://api.flutter.dev/
      * https://stackoverflow.com/questions/tagged/flutter?sort=frequent

     If you have found a bug or if our documentation doesn't have an answer
     to what you're looking for, then fill out the template below. Please read
     our guide to filing a bug first: https://flutter.dev/docs/resources/bug-reports
-->

## Use case

<!--
     Please tell us the problem you are running into that led to you wanting
     a new feature.

     Is your feature request related to a problem? Please give a clear and
     concise description of what the problem is.

     Describe the alternative solutions you've considered. Is there a package
     on pub.dev/flutter that already solves this?
-->

When having a localization delegate which loads asynchronously, the app renders a black screen as long as it is loading. Example:

```dart
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  runApp(const MainApp());
}

class DemoLocalization {
  String get welcomeText => 'Hello world';
}

class AsyncLocalizationDelegate
    extends LocalizationsDelegate<DemoLocalization> {
  const AsyncLocalizationDelegate();

  @override
  bool isSupported(Locale locale) => true;

  @override
  Future<DemoLocalization> load(Locale locale) async {
    await Future.delayed(const Duration(seconds: 10));
    return DemoLocalization();
  }

  @override
  bool shouldReload(covariant LocalizationsDelegate old) => false;
}

class MainApp extends StatelessWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Localizations(
      delegates: const [
        AsyncLocalizationDelegate(),
        GlobalWidgetsLocalizations.delegate,
      ],
      locale: const Locale('en', 'US'),
      child: Builder(
        builder: (context) {
          final localization =
              Localizations.of<DemoLocalization>(context, DemoLocalization);
          return Center(child: Text(localization!.welcomeText));
        },
      ),
    );
  }
}
```

In this case, the app will show a black screen for 10 seconds, after that it shows a text "Hello World". But it is currently not possible to render a custom loading screen easily.

We would like to have that option, because we are loading the locale bundles from a remote server. This might take a bit time depending on the user's internet connection. But in the moment, this causes the app to show a black screen on every startup.

## Proposal

<!--
     Briefly but precisely describe what you would like Flutter to be able to do.

     Consider attaching something showing what you are imagining:
      * images
      * videos
      * code samples

     Does this have to be provided by Flutter directly, or can it be provided
     by a package on pub.dev/flutter? If so, maybe consider implementing and
     publishing such a package rather than filing a bug.
-->

The `Localizations` widget, currently renders an empty container during loading:
```dart
@override
Widget build(BuildContext context) {
  if (_locale == null)
    return Container(); // here
  return Semantics(
    textDirection: _textDirection,
    child: _LocalizationsScope(
      key: _localizedResourcesScopeKey,
      locale: _locale!,
      localizationsState: this,
      typeToResources: _typeToResources,
      child: Directionality(
        textDirection: _textDirection,
        child: widget.child!,
      ),
    ),
  );
}
```
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/localizations.dart#L574

It would be sufficient for us, when there is a property, in which a widget can be passed for the loading, like that:
```dart
@override
Widget build(BuildContext context) {
  if (_locale == null)
    return widget.loadingBuilder(context); // here
  return Semantics(
    textDirection: _textDirection,
    child: _LocalizationsScope(
      key: _localizedResourcesScopeKey,
      locale: _locale!,
      localizationsState: this,
      typeToResources: _typeToResources,
      child: Directionality(
        textDirection: _textDirection,
        child: widget.child!,
      ),
    ),
  );
}
```

The only workaround I found currently is something like that:
```dart
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  runApp(const MainApp());
}

class DemoLocalization {
  String get welcomeText => 'Hello world';
}

class AsyncLocalizationDelegate
    extends LocalizationsDelegate<DemoLocalization> {
  bool _loaded = false;

  @override
  bool isSupported(Locale locale) => true;

  @override
  Future<DemoLocalization> load(Locale locale) {
    assert(_loaded, 'preload() must be invoked before load(locale)');
    return SynchronousFuture(DemoLocalization());
  }

  Future<void> preload() async {
    await Future.delayed(const Duration(seconds: 3));
    _loaded = true;
  }

  @override
  bool shouldReload(covariant LocalizationsDelegate old) => false;
}

class MainApp extends StatefulWidget {
  const MainApp({Key? key}) : super(key: key);

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  late bool loading;
  late AsyncLocalizationDelegate delegate;

  @override
  void initState() {
    super.initState();

    loading = true;
    delegate = AsyncLocalizationDelegate()
      ..preload().then((value) => setState(() => loading = false));
  }

  @override
  Widget build(BuildContext context) {
    if (loading) {
      return const Directionality(
          textDirection: TextDirection.ltr,
          child: Center(child: Text('Loading...'))
      );
    }

    return Localizations(
      delegates: [
        delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      locale: const Locale('en', 'US'),
      child: Builder(
        builder: (context) {
          final localization = Localizations.of<DemoLocalization>(
              context, DemoLocalization);
          return Center(child: Text(localization!.welcomeText));
        },
      ),
    );
  }
}
```