mirror of
https://github.com/flutter/flutter
synced 2024-09-12 21:01:59 +00:00
Use FlutterError.reportError
instead of debugPrint
for l10n warning (#93076)
This commit is contained in:
parent
bd77118ecb
commit
eb00598bec
|
@ -248,6 +248,7 @@ abstract class _ErrorDiagnostic extends DiagnosticsProperty<List<Object>> {
|
|||
/// problem that was detected.
|
||||
/// * [ErrorHint], which provides specific, non-obvious advice that may be
|
||||
/// applicable.
|
||||
/// * [ErrorSpacer], which renders as a blank line.
|
||||
/// * [FlutterError], which is the most common place to use an
|
||||
/// [ErrorDescription].
|
||||
class ErrorDescription extends _ErrorDiagnostic {
|
||||
|
@ -323,6 +324,7 @@ class ErrorSummary extends _ErrorDiagnostic {
|
|||
/// * [ErrorDescription], which provides an explanation of the problem and its
|
||||
/// cause, any information that may help track down the problem, background
|
||||
/// information, etc.
|
||||
/// * [ErrorSpacer], which renders as a blank line.
|
||||
/// * [FlutterError], which is the most common place to use an [ErrorHint].
|
||||
class ErrorHint extends _ErrorDiagnostic {
|
||||
/// A lint enforces that this constructor can only be called with a string
|
||||
|
@ -516,14 +518,52 @@ class FlutterErrorDetails with Diagnosticable {
|
|||
/// This won't be called if [stack] is null.
|
||||
final IterableFilter<String>? stackFilter;
|
||||
|
||||
/// A callback which, when called with a [StringBuffer] will write to that buffer
|
||||
/// information that could help with debugging the problem.
|
||||
/// A callback which will provide information that could help with debugging
|
||||
/// the problem.
|
||||
///
|
||||
/// Information collector callbacks can be expensive, so the generated information
|
||||
/// should be cached, rather than the callback being called multiple times.
|
||||
/// Information collector callbacks can be expensive, so the generated
|
||||
/// information should be cached by the caller, rather than the callback being
|
||||
/// called multiple times.
|
||||
///
|
||||
/// The text written to the information argument may contain newlines but should
|
||||
/// not end with a newline.
|
||||
/// The callback is expected to return an iterable of [DiagnosticsNode] objects,
|
||||
/// typically implemented using `sync*` and `yield`.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
/// In this example, the information collector returns two pieces of information,
|
||||
/// one broadly-applicable statement regarding how the error happened, and one
|
||||
/// giving a specific piece of information that may be useful in some cases but
|
||||
/// may also be irrelevant most of the time (an argument to the method).
|
||||
///
|
||||
/// ```dart
|
||||
/// void climbElevator(int pid) {
|
||||
/// try {
|
||||
/// // ...
|
||||
/// } catch (error, stack) {
|
||||
/// FlutterError.reportError(FlutterErrorDetails(
|
||||
/// exception: error,
|
||||
/// stack: stack,
|
||||
/// informationCollector: () sync* {
|
||||
/// yield ErrorDescription('This happened while climbing the space elevator.');
|
||||
/// yield ErrorHint('The process ID is: $pid');
|
||||
/// },
|
||||
/// ));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// The following classes may be of particular use:
|
||||
///
|
||||
/// * [ErrorDescription], for information that is broadly applicable to the
|
||||
/// situation being described.
|
||||
/// * [ErrorHint], for specific information that may not always be applicable
|
||||
/// but can be helpful in certain situations.
|
||||
/// * [DiagnosticsStackTrace], for reporting stack traces.
|
||||
/// * [ErrorSpacer], for adding spaces (a blank line) between other items.
|
||||
///
|
||||
/// For objects that implement [Diagnosticable] one may consider providing
|
||||
/// additional information by yielding the output of the object's
|
||||
/// [Diagnosticable.toDiagnosticsNode] method.
|
||||
final InformationCollector? informationCollector;
|
||||
|
||||
/// Whether this error should be ignored by the default error reporting
|
||||
|
|
|
@ -111,11 +111,11 @@ typedef LocaleResolutionCallback = Locale? Function(Locale? locale, Iterable<Loc
|
|||
/// To summarize, the main matching priority is:
|
||||
///
|
||||
/// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode]
|
||||
/// 1. [Locale.languageCode] and [Locale.scriptCode] only
|
||||
/// 1. [Locale.languageCode] and [Locale.countryCode] only
|
||||
/// 1. [Locale.languageCode] only (with caveats, see above)
|
||||
/// 1. [Locale.countryCode] only when all [preferredLocales] fail to match
|
||||
/// 1. Returns the first element of [supportedLocales] as a fallback
|
||||
/// 2. [Locale.languageCode] and [Locale.scriptCode] only
|
||||
/// 3. [Locale.languageCode] and [Locale.countryCode] only
|
||||
/// 4. [Locale.languageCode] only (with caveats, see above)
|
||||
/// 5. [Locale.countryCode] only when all [preferredLocales] fail to match
|
||||
/// 6. Returns the first element of [supportedLocales] as a fallback
|
||||
///
|
||||
/// This algorithm does not take language distance (how similar languages are to each other)
|
||||
/// into account, and will not handle edge cases such as resolving `de` to `fr` rather than `zh`
|
||||
|
@ -885,7 +885,7 @@ class WidgetsApp extends StatefulWidget {
|
|||
/// `[const Locale('en', 'US')]`.
|
||||
///
|
||||
/// The order of the list matters. The default locale resolution algorithm,
|
||||
/// `basicLocaleListResolution`, attempts to match by the following priority:
|
||||
/// [basicLocaleListResolution], attempts to match by the following priority:
|
||||
///
|
||||
/// 1. [Locale.languageCode], [Locale.scriptCode], and [Locale.countryCode]
|
||||
/// 2. [Locale.languageCode] and [Locale.scriptCode] only
|
||||
|
@ -899,7 +899,7 @@ class WidgetsApp extends StatefulWidget {
|
|||
///
|
||||
/// The default locale resolution algorithm can be overridden by providing a
|
||||
/// value for [localeListResolutionCallback]. The provided
|
||||
/// `basicLocaleListResolution` is optimized for speed and does not implement
|
||||
/// [basicLocaleListResolution] is optimized for speed and does not implement
|
||||
/// a full algorithm (such as the one defined in
|
||||
/// [Unicode TR35](https://unicode.org/reports/tr35/#LanguageMatching)) that
|
||||
/// takes distances between languages into account.
|
||||
|
@ -1493,35 +1493,37 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
|||
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 (final 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.dev/tutorials/internationalization/ for more\n'
|
||||
"information about configuring an app's locale, supportedLocales,\n"
|
||||
'and localizationsDelegates parameters.',
|
||||
);
|
||||
message.writeln('\u2550' * 8);
|
||||
debugPrint(message.toString());
|
||||
FlutterError.reportError(FlutterErrorDetails(
|
||||
exception: "Warning: This application's locale, $appLocale, is not supported by all of its localization delegates.",
|
||||
library: 'widgets',
|
||||
informationCollector: () sync* {
|
||||
for (final Type unsupportedType in unsupportedTypes) {
|
||||
yield ErrorDescription(
|
||||
'• A $unsupportedType delegate that supports the $appLocale locale was not found.',
|
||||
);
|
||||
}
|
||||
yield ErrorSpacer();
|
||||
if (unsupportedTypes.length == 1 && unsupportedTypes.single.toString() == 'CupertinoLocalizations') {
|
||||
// We previously explicitly avoided checking for this class so it's not uncommon for applications
|
||||
// to have omitted importing the required delegate.
|
||||
yield ErrorHint(
|
||||
'If the application is built using GlobalMaterialLocalizations.delegate, consider using '
|
||||
'GlobalMaterialLocalizations.delegates (plural) instead, as that will automatically declare '
|
||||
'the appropriate Cupertino localizations.'
|
||||
);
|
||||
yield ErrorSpacer();
|
||||
}
|
||||
yield ErrorHint(
|
||||
'The declared supported locales for this app are: ${widget.supportedLocales.join(", ")}'
|
||||
);
|
||||
yield ErrorSpacer();
|
||||
yield ErrorDescription(
|
||||
'See https://flutter.dev/tutorials/internationalization/ for more '
|
||||
"information about configuring an app's locale, supportedLocales, "
|
||||
'and localizationsDelegates parameters.',
|
||||
);
|
||||
},
|
||||
));
|
||||
return true;
|
||||
}());
|
||||
return true;
|
||||
|
|
|
@ -461,6 +461,30 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
testWidgets("WidgetsApp reports an exception if the selected locale isn't supported", (WidgetTester tester) async {
|
||||
late final List<Locale>? localesArg;
|
||||
late final Iterable<Locale> supportedLocalesArg;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp( // This uses a MaterialApp because it introduces some actual localizations.
|
||||
localeListResolutionCallback: (List<Locale>? locales, Iterable<Locale> supportedLocales) {
|
||||
localesArg = locales;
|
||||
supportedLocalesArg = supportedLocales;
|
||||
return const Locale('C_UTF-8');
|
||||
},
|
||||
builder: (BuildContext context, Widget? child) => const Placeholder(),
|
||||
color: const Color(0xFF000000),
|
||||
),
|
||||
);
|
||||
if (!kIsWeb) {
|
||||
// On web, `flutter test` does not guarantee a particular locale, but
|
||||
// when using `flutter_tester`, we guarantee that it's en-US, zh-CN.
|
||||
// https://github.com/flutter/flutter/issues/93290
|
||||
expect(localesArg, const <Locale>[Locale('en', 'US'), Locale('zh', 'CN')]);
|
||||
}
|
||||
expect(supportedLocalesArg, const <Locale>[Locale('en', 'US')]);
|
||||
expect(tester.takeException(), "Warning: This application's locale, C_UTF-8, is not supported by all of its localization delegates.");
|
||||
});
|
||||
|
||||
testWidgets('WidgetsApp creates a MediaQuery if `useInheritedMediaQuery` is set to false', (WidgetTester tester) async {
|
||||
late BuildContext capturedContext;
|
||||
await tester.pumpWidget(
|
||||
|
|
|
@ -37,7 +37,7 @@ void main() {
|
|||
],
|
||||
localizationsDelegates: <LocalizationsDelegate<dynamic>>[
|
||||
_DummyLocalizationsDelegate(),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
...GlobalMaterialLocalizations.delegates,
|
||||
],
|
||||
home: PageView(),
|
||||
)
|
||||
|
@ -52,9 +52,7 @@ void main() {
|
|||
// Regression test for https://github.com/flutter/flutter/pull/16782
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('es', 'ES'),
|
||||
Locale('zh'),
|
||||
|
|
|
@ -35,9 +35,7 @@ void main() {
|
|||
await tester.pumpWidget(MaterialApp(
|
||||
supportedLocales: <Locale>[locale],
|
||||
locale: locale,
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: Builder(builder: (BuildContext context) {
|
||||
completer.complete(MaterialLocalizations.of(context).formatHour(timeOfDay));
|
||||
return Container();
|
||||
|
@ -82,9 +80,7 @@ void main() {
|
|||
await tester.pumpWidget(MaterialApp(
|
||||
supportedLocales: <Locale>[locale],
|
||||
locale: locale,
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: Builder(builder: (BuildContext context) {
|
||||
completer.complete(MaterialLocalizations.of(context).formatTimeOfDay(timeOfDay));
|
||||
return Container();
|
||||
|
@ -126,9 +122,7 @@ void main() {
|
|||
await tester.pumpWidget(MaterialApp(
|
||||
supportedLocales: <Locale>[locale],
|
||||
locale: locale,
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: Builder(builder: (BuildContext context) {
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
completer.complete(<DateType, String>{
|
||||
|
@ -184,12 +178,9 @@ void main() {
|
|||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
locale: const Locale('en', 'US'),
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
home: Builder(builder: (BuildContext context) {
|
||||
dateFormat = DateFormat('EEE, d MMM yyyy HH:mm:ss', 'en_US');
|
||||
|
||||
return Container();
|
||||
}),
|
||||
));
|
||||
|
|
|
@ -175,9 +175,10 @@ void main() {
|
|||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
delegates: <FooMaterialLocalizationsDelegate>[
|
||||
delegates: <LocalizationsDelegate<dynamic>>[
|
||||
const FooMaterialLocalizationsDelegate(supportedLanguage: 'fr', backButtonTooltip: 'FR'),
|
||||
const FooMaterialLocalizationsDelegate(supportedLanguage: 'de', backButtonTooltip: 'DE'),
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en'),
|
||||
|
@ -211,8 +212,9 @@ void main() {
|
|||
buildFrame(
|
||||
// Accept whatever locale we're given
|
||||
localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales) => locale,
|
||||
delegates: <FooMaterialLocalizationsDelegate>[
|
||||
delegates: <LocalizationsDelegate<dynamic>>[
|
||||
const FooMaterialLocalizationsDelegate(supportedLanguage: 'allLanguages'),
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
buildContent: (BuildContext context) {
|
||||
// Should always be 'foo', no matter what the locale is
|
||||
|
@ -240,8 +242,9 @@ void main() {
|
|||
|
||||
await tester.pumpWidget(
|
||||
buildFrame(
|
||||
delegates: <FooMaterialLocalizationsDelegate>[
|
||||
delegates: <LocalizationsDelegate<dynamic>>[
|
||||
const FooMaterialLocalizationsDelegate(),
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
// supportedLocales not specified, so all locales resolve to 'en'
|
||||
buildContent: (BuildContext context) {
|
||||
|
@ -297,6 +300,7 @@ void main() {
|
|||
// Yiddish was ji (ISO-639) is yi (ISO-639-1)
|
||||
await tester.binding.setLocale('ji', 'IL');
|
||||
await tester.pump();
|
||||
expect(tester.takeException(), "Warning: This application's locale, yi_IL, is not supported by all of its localization delegates.");
|
||||
expect(tester.widget<Text>(find.byKey(textKey)).data, 'yi_IL');
|
||||
|
||||
// Indonesian was in (ISO-639) is id (ISO-639-1)
|
||||
|
|
|
@ -22,9 +22,7 @@ void main() {
|
|||
return const Text('Next');
|
||||
},
|
||||
},
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en', 'US'),
|
||||
Locale('es', 'ES'),
|
||||
|
@ -108,9 +106,7 @@ void main() {
|
|||
return const Text('Next');
|
||||
},
|
||||
},
|
||||
localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
],
|
||||
localizationsDelegates: GlobalMaterialLocalizations.delegates,
|
||||
supportedLocales: const <Locale>[
|
||||
Locale('en', 'US'),
|
||||
Locale('es', 'ES'),
|
||||
|
|
Loading…
Reference in a new issue