Migrate more `package:intl` to null safety: migrate most of `src/intl`, leaving `number_formatter` and `compact_number_formatter`.
PiperOrigin-RevId: 328329336
diff --git a/lib/intl.dart b/lib/intl.dart
index cb83005..880dbcc 100644
--- a/lib/intl.dart
+++ b/lib/intl.dart
@@ -202,43 +202,12 @@
/// Note that null is interpreted as meaning the default locale, so if
/// [newLocale] is null the default locale will be returned.
static String verifiedLocale(
- String newLocale, bool Function(String) localeExists,
- {String Function(String) onFailure = _throwLocaleError}) {
- // TODO(alanknight): Previously we kept a single verified locale on the Intl
- // object, but with different verification for different uses, that's more
- // difficult. As a result, we call this more often. Consider keeping
- // verified locales for each purpose if it turns out to be a performance
- // issue.
- if (newLocale == null) {
- return verifiedLocale(getCurrentLocale(), localeExists,
- onFailure: onFailure);
- }
- if (localeExists(newLocale)) {
- return newLocale;
- }
- for (var each in [
- canonicalizedLocale(newLocale),
- shortLocale(newLocale),
- 'fallback'
- ]) {
- if (localeExists(each)) {
- return each;
- }
- }
- return onFailure(newLocale);
- }
-
- /// The default action if a locale isn't found in verifiedLocale. Throw
- /// an exception indicating the locale isn't correct.
- static String _throwLocaleError(String localeName) {
- throw ArgumentError('Invalid locale "$localeName"');
- }
+ String newLocale, bool Function(String) localeExists,
+ {String Function(String) onFailure}) =>
+ helpers.verifiedLocale(newLocale, localeExists, onFailure);
/// Return the short version of a locale name, e.g. 'en_US' => 'en'
- static String shortLocale(String aLocale) {
- if (aLocale.length < 2) return aLocale;
- return aLocale.substring(0, 2).toLowerCase();
- }
+ static String shortLocale(String aLocale) => helpers.shortLocale(aLocale);
/// Return the name [aLocale] turned into xx_YY where it might possibly be
/// in the wrong case or with a hyphen instead of an underscore. If
diff --git a/lib/src/date_format_internal.dart b/lib/src/date_format_internal.dart
index 08c9129..63df2d0 100644
--- a/lib/src/date_format_internal.dart
+++ b/lib/src/date_format_internal.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
/// This contains internal implementation details of the date formatting code
/// which are exposed as public functions because they must be called by other
@@ -43,10 +42,10 @@
UninitializedLocaleData('initializeDateFormatting(<locale>)', en_USSymbols);
/// Cache the last used symbols to reduce repeated lookups.
-DateSymbols cachedDateSymbols;
+DateSymbols? cachedDateSymbols;
/// Which locale was last used for symbol lookup.
-String lastDateSymbolLocale;
+String? lastDateSymbolLocale;
/// This holds the patterns used for date/time formatting, indexed
/// by locale. Note that it will be set differently during initialization,
diff --git a/lib/src/intl/bidi.dart b/lib/src/intl/bidi.dart
index ba028c4..07d18a2 100644
--- a/lib/src/intl/bidi.dart
+++ b/lib/src/intl/bidi.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
/// Bidi stands for Bi-directional text. According to
/// http://en.wikipedia.org/wiki/Bi-directional_text: Bi-directional text is
@@ -113,8 +112,8 @@
r'($|-|_)',
caseSensitive: false);
- static String _lastLocaleCheckedForRtl;
- static bool _lastRtlCheck;
+ static String? _lastLocaleCheckedForRtl;
+ static bool? _lastRtlCheck;
/// Check if a BCP 47 / III [languageString] indicates an RTL language.
///
@@ -137,13 +136,13 @@
/// http://www.iana.org/assignments/language-subtag-registry, as well as
/// Sindhi (sd) and Uyghur (ug). The presence of other subtags of the
/// language code, e.g. regions like EG (Egypt), is ignored.
- static bool isRtlLanguage([String languageString]) {
+ static bool isRtlLanguage([String? languageString]) {
var language = languageString ?? global_state.getCurrentLocale();
if (_lastLocaleCheckedForRtl != language) {
_lastLocaleCheckedForRtl = language;
_lastRtlCheck = _rtlLocaleRegex.hasMatch(language);
}
- return _lastRtlCheck;
+ return _lastRtlCheck!;
}
/// Enforce the [html] snippet in RTL directionality regardless of overall
@@ -185,7 +184,7 @@
if (html.startsWith('<')) {
var buffer = StringBuffer();
var startIndex = 0;
- Match match = RegExp('<\\w+').firstMatch(html);
+ var match = RegExp('<\\w+').firstMatch(html);
if (match != null) {
buffer
..write(html.substring(startIndex, match.end))
@@ -202,7 +201,7 @@
/// problem of messy bracket display that frequently happens in RTL layout.
/// If [isRtlContext] is true, then we explicitly want to wrap in a span of
/// RTL directionality, regardless of the estimated directionality.
- static String guardBracketInHtml(String str, [bool isRtlContext]) {
+ static String guardBracketInHtml(String str, [bool? isRtlContext]) {
var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext;
var matchingBrackets =
RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?(>)+)');
@@ -216,7 +215,7 @@
/// as good as guardBracketInHtml. If [isRtlContext] is true, then we
/// explicitly want to wrap in a span of RTL directionality, regardless of the
/// estimated directionality.
- static String guardBracketInText(String str, [bool isRtlContext]) {
+ static String guardBracketInText(String str, [bool? isRtlContext]) {
var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext;
var mark = useRtl ? RLM : LRM;
return _guardBracketHelper(
@@ -230,7 +229,7 @@
/// would return 'firehydrant!'. // TODO(efortuna): Get rid of this once this
/// is implemented in Dart. // See Issue 2979.
static String _guardBracketHelper(String str, RegExp regexp,
- [String before, String after]) {
+ [String? before, String? after]) {
var buffer = StringBuffer();
var startIndex = 0;
for (var match in regexp.allMatches(str)) {
diff --git a/lib/src/intl/bidi_formatter.dart b/lib/src/intl/bidi_formatter.dart
index f84cd14..2e7331a 100644
--- a/lib/src/intl/bidi_formatter.dart
+++ b/lib/src/intl/bidi_formatter.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
import 'dart:convert';
@@ -71,13 +70,13 @@
/// should always use a `span` tag, even when the input directionality is
/// neutral or matches the context, so that the DOM structure of the output
/// does not depend on the combination of directionalities.
- BidiFormatter.LTR([alwaysSpan = false])
+ BidiFormatter.LTR([bool alwaysSpan = false])
: contextDirection = TextDirection.LTR,
_alwaysSpan = alwaysSpan;
- BidiFormatter.RTL([alwaysSpan = false])
+ BidiFormatter.RTL([bool alwaysSpan = false])
: contextDirection = TextDirection.RTL,
_alwaysSpan = alwaysSpan;
- BidiFormatter.UNKNOWN([alwaysSpan = false])
+ BidiFormatter.UNKNOWN([bool alwaysSpan = false])
: contextDirection = TextDirection.UNKNOWN,
_alwaysSpan = alwaysSpan;
@@ -100,7 +99,7 @@
/// a trailing unicode BiDi mark matching the context directionality is
/// appended (LRM or RLM). If [isHtml] is false, we HTML-escape the [text].
String wrapWithSpan(String text,
- {bool isHtml = false, bool resetDir = true, TextDirection direction}) {
+ {bool isHtml = false, bool resetDir = true, TextDirection? direction}) {
direction ??= estimateDirection(text, isHtml: isHtml);
String result;
if (!isHtml) text = const HtmlEscape().convert(text);
@@ -135,7 +134,7 @@
/// [isHtml]. [isHtml] is used to designate if the text contains HTML (escaped
/// or unescaped).
String wrapWithUnicode(String text,
- {bool isHtml = false, bool resetDir = true, TextDirection direction}) {
+ {bool isHtml = false, bool resetDir = true, TextDirection? direction}) {
direction ??= estimateDirection(text, isHtml: isHtml);
var result = text;
if (contextDirection.isDirectionChange(direction)) {
diff --git a/lib/src/intl/date_builder.dart b/lib/src/intl/date_builder.dart
index 7281fb3..f0eaddd 100644
--- a/lib/src/intl/date_builder.dart
+++ b/lib/src/intl/date_builder.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
import 'package:clock/clock.dart';
@@ -35,7 +34,7 @@
///
/// Kept as a field to cache the result and to reduce the possibility of error
/// after we've verified.
- DateTime _date;
+ DateTime? _date;
/// The number of times we've retried, for error reporting.
int _retried = 0;
@@ -146,7 +145,7 @@
}
void _verify(int value, int min, int max, String desc, String originalInput,
- [DateTime parsed]) {
+ [DateTime? parsed]) {
if (value < min || value > max) {
var parsedDescription = parsed == null ? '' : ' Date parsed as $parsed.';
var errorDescription =
@@ -182,7 +181,7 @@
DateTime asDate({int retries = 3}) {
// TODO(alanknight): Validate the date, especially for things which
// can crash the VM, e.g. large month values.
- if (_date != null) return _date;
+ if (_date != null) return _date!;
DateTime preliminaryResult;
final hasCentury = !_hasAmbiguousCentury || year < 0 || year >= 100;
@@ -227,7 +226,7 @@
} else {
_date = _correctForErrors(preliminaryResult, retries);
}
- return _date;
+ return _date!;
}
/// Given a local DateTime, check for errors and try to compensate for them if
diff --git a/lib/src/intl/date_format.dart b/lib/src/intl/date_format.dart
index 03d868c..9251ee2 100644
--- a/lib/src/intl/date_format.dart
+++ b/lib/src/intl/date_format.dart
@@ -1,11 +1,10 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
import 'package:intl/date_symbols.dart';
-import 'package:intl/intl.dart';
import 'package:intl/src/date_format_internal.dart';
+import 'package:intl/src/intl_helpers.dart' as helpers;
import 'constants.dart' as constants;
import 'date_builder.dart';
@@ -266,13 +265,13 @@
///
/// If [locale] does not exist in our set of supported locales then an
/// [ArgumentError] is thrown.
- DateFormat([String newPattern, String locale]) {
+ DateFormat([String? newPattern, String? locale])
+ : _locale = helpers.verifiedLocale(locale, localeExists, null) {
// TODO(alanknight): It should be possible to specify multiple skeletons eg
// date, time, timezone all separately. Adding many or named parameters to
// the constructor seems awkward, especially with the possibility of
// confusion with the locale. A 'fluent' interface with cascading on an
// instance might work better? A list of patterns is also possible.
- _locale = Intl.verifiedLocale(locale, localeExists);
addPattern(newPattern);
}
@@ -362,8 +361,7 @@
}
DateTime _parseLoose(String inputString, bool utc) {
- var dateFields =
- DateBuilder(locale ?? Intl.defaultLocale, dateTimeConstructor);
+ var dateFields = DateBuilder(locale, dateTimeConstructor);
if (utc) dateFields.utc = true;
var stream = IntlStream(inputString);
for (var field in _formatFields) {
@@ -391,8 +389,7 @@
DateTime _parse(String inputString, {bool utc = false, bool strict = false}) {
// TODO(alanknight): The Closure code refers to special parsing of numeric
// values with no delimiters, which we currently don't do. Should we?
- var dateFields =
- DateBuilder(locale ?? Intl.defaultLocale, dateTimeConstructor);
+ var dateFields = DateBuilder(locale, dateTimeConstructor);
if (utc) dateFields.utc = true;
dateFields.dateOnly = dateOnly;
var stream = IntlStream(inputString);
@@ -411,7 +408,7 @@
///
/// For example, 'yyyy-MM-dd' would be true, but 'dd hh:mm' would be false.
bool get dateOnly => _dateOnly ??= _checkDateOnly;
- bool _dateOnly;
+ bool? _dateOnly;
bool get _checkDateOnly => _formatFields.every((each) => each.forDate);
/// Given user input, attempt to parse the [inputString] into the anticipated
@@ -641,20 +638,20 @@
/// The full template string. This may have been specified directly, or
/// it may have been derived from a skeleton and the locale information
/// on how to interpret that skeleton.
- String _pattern;
+ String? _pattern;
/// We parse the format string into individual [_DateFormatField] objects
/// that are used to do the actual formatting and parsing. Do not use
/// this variable directly, use the getter [_formatFields].
- List<_DateFormatField> _formatFieldsPrivate;
+ List<_DateFormatField>? _formatFieldsPrivate;
/// Getter for [_formatFieldsPrivate] that lazily initializes it.
List<_DateFormatField> get _formatFields {
if (_formatFieldsPrivate == null) {
if (_pattern == null) _useDefaultPattern();
- _formatFieldsPrivate = parsePattern(_pattern);
+ _formatFieldsPrivate = parsePattern(_pattern!);
}
- return _formatFieldsPrivate;
+ return _formatFieldsPrivate!;
}
/// We are being asked to do formatting without having set any pattern.
@@ -694,7 +691,7 @@
/// known skeletons. If it's found there, then use the corresponding pattern
/// for this locale. If it's not, then treat [inputPattern] as an explicit
/// pattern.
- DateFormat addPattern(String inputPattern, [String separator = ' ']) {
+ DateFormat addPattern(String? inputPattern, [String separator = ' ']) {
// TODO(alanknight): This is an expensive operation. Caching recently used
// formats, or possibly introducing an entire 'locale' object that would
// cache patterns for that locale could be a good optimization.
@@ -710,7 +707,7 @@
}
/// Return the pattern that we use to format dates.
- String get pattern => _pattern;
+ String? get pattern => _pattern;
/// Return the skeletons for our current locale.
Map<dynamic, dynamic> get _availableSkeletons => dateTimePatterns[locale];
@@ -719,14 +716,15 @@
///
/// This can be useful to find lists like the names of weekdays or months in a
/// locale, but the structure of this data may change, and it's generally
- /// better to go through the [format] and [parse] APIs. If the locale isn't
- /// present, or is uninitialized, returns null.
+ /// better to go through the [format] and [parse] APIs.
+ ///
+ /// If the locale isn't present, or is uninitialized, throws.
DateSymbols get dateSymbols {
if (_locale != lastDateSymbolLocale) {
lastDateSymbolLocale = _locale;
cachedDateSymbols = dateTimeSymbols[_locale];
}
- return cachedDateSymbols;
+ return cachedDateSymbols!;
}
static final Map<String, bool> _useNativeDigitsByDefault = {};
@@ -753,14 +751,14 @@
_useNativeDigitsByDefault[locale] = value;
}
- bool _useNativeDigits;
+ bool? _useNativeDigits;
/// Should we use native digits for printing DateTime, or ASCII.
///
/// The default for this can be set using [useNativeDigitsByDefaultFor].
bool get useNativeDigits => _useNativeDigits == null
? _useNativeDigits = shouldUseNativeDigitsByDefaultFor(locale)
- : _useNativeDigits;
+ : _useNativeDigits!;
/// Should we use native digits for printing DateTime, or ASCII.
set useNativeDigits(bool native) {
@@ -778,28 +776,28 @@
/// locale.
static final Map<String, RegExp> _digitMatchers = {};
- RegExp _digitMatcher;
+ RegExp? _digitMatcher;
/// A regular expression which matches against digits for this locale.
RegExp get digitMatcher {
- if (_digitMatcher != null) return _digitMatcher;
+ if (_digitMatcher != null) return _digitMatcher!;
_digitMatcher = _digitMatchers.putIfAbsent(localeZero, _initDigitMatcher);
- return _digitMatcher;
+ return _digitMatcher!;
}
- int _localeZeroCodeUnit;
+ int? _localeZeroCodeUnit;
/// For performance, keep the code unit of the zero digit available.
int get localeZeroCodeUnit => _localeZeroCodeUnit == null
? _localeZeroCodeUnit = localeZero.codeUnitAt(0)
- : _localeZeroCodeUnit;
+ : _localeZeroCodeUnit!;
- String _localeZero;
+ String? _localeZero;
/// For performance, keep the zero digit available.
String get localeZero => _localeZero == null
? _localeZero = useNativeDigits ? dateSymbols.ZERODIGIT ?? '0' : '0'
- : _localeZero;
+ : _localeZero!;
// Does this use non-ASCII digits, e.g. Eastern Arabic.
bool get usesNativeDigits =>
@@ -812,7 +810,7 @@
/// locale digits.
String _localizeDigits(String numberString) {
if (usesAsciiDigits) return numberString;
- var newDigits = List<int>(numberString.length);
+ var newDigits = List<int>.filled(numberString.length, 0);
var oldDigits = numberString.codeUnits;
for (var i = 0; i < numberString.length; i++) {
newDigits[i] =
@@ -848,7 +846,6 @@
/// Parse the template pattern and return a list of field objects.
List<_DateFormatField> parsePattern(String pattern) {
- if (pattern == null) return null;
return _parsePatternHelper(pattern).reversed.toList();
}
@@ -866,12 +863,12 @@
}
/// Find elements in a string that are patterns for specific fields.
- _DateFormatField _match(String pattern) {
+ _DateFormatField? _match(String pattern) {
for (var i = 0; i < _matchers.length; i++) {
var regex = _matchers[i];
var match = regex.firstMatch(pattern);
if (match != null) {
- return _fieldConstructors[i](match.group(0), this);
+ return _fieldConstructors[i](match.group(0)!, this);
}
}
return null;
diff --git a/lib/src/intl/date_format_field.dart b/lib/src/intl/date_format_field.dart
index 0bb0159..b6aa50e 100644
--- a/lib/src/intl/date_format_field.dart
+++ b/lib/src/intl/date_format_field.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
part of 'date_format.dart';
@@ -17,11 +16,10 @@
DateFormat parent;
/// Trimmed version of [pattern].
- String _trimmedPattern;
+ final String _trimmedPattern;
- _DateFormatField(this.pattern, this.parent) {
- _trimmedPattern = pattern.trim();
- }
+ _DateFormatField(this.pattern, this.parent)
+ : _trimmedPattern = pattern.trim();
/// Does this field potentially represent part of a Date, i.e. is not
/// time-specific.
@@ -84,7 +82,7 @@
}
/// Throw a format exception with an error message indicating the position.
- void throwFormatException(IntlStream stream) {
+ Never throwFormatException(IntlStream stream) {
throw FormatException('Trying to read $this from ${stream.contents} '
'at position ${stream.index}');
}
@@ -94,7 +92,8 @@
/// change according to the date's data. As such, the implementation
/// is extremely simple.
class _DateFormatLiteralField extends _DateFormatField {
- _DateFormatLiteralField(pattern, parent) : super(pattern, parent);
+ _DateFormatLiteralField(String pattern, DateFormat parent)
+ : super(pattern, parent);
void parse(IntlStream input, DateBuilder dateFields) {
parseLiteral(input);
@@ -107,14 +106,13 @@
/// Represents a literal field with quoted characters in it. This is
/// only slightly more complex than a _DateFormatLiteralField.
class _DateFormatQuotedField extends _DateFormatField {
- String _fullPattern;
+ final String _fullPattern;
String fullPattern() => _fullPattern;
- _DateFormatQuotedField(pattern, parent)
- : super(_patchQuotes(pattern), parent) {
- _fullPattern = pattern;
- }
+ _DateFormatQuotedField(String pattern, DateFormat parent)
+ : _fullPattern = pattern,
+ super(_patchQuotes(pattern), parent);
void parse(IntlStream input, DateBuilder dateFields) {
parseLiteral(input);
@@ -258,7 +256,7 @@
_LoosePatternField(pattern, parent).parse(input, dateFields);
}
- bool _forDate;
+ bool? _forDate;
/// Is this field involved in computing the date portion, as opposed to the
/// time.
diff --git a/lib/src/intl/intl_stream.dart b/lib/src/intl/intl_stream.dart
index 3149a53..888ed9d 100644
--- a/lib/src/intl/intl_stream.dart
+++ b/lib/src/intl/intl_stream.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
import 'dart:math';
@@ -60,7 +59,7 @@
/// Find the index of the first element for which [f] returns true.
/// Advances the stream to that position.
- int findIndex(bool Function(dynamic) f) {
+ int? findIndex(bool Function(dynamic) f) {
while (!atEnd()) {
if (f(next())) return index - 1;
}
@@ -83,14 +82,14 @@
/// For non-ascii digits, the optional arguments are a regular expression
/// [digitMatcher] to find the next integer, and the codeUnit of the local
/// zero [zeroDigit].
- int nextInteger({RegExp digitMatcher, int zeroDigit}) {
+ int? nextInteger({RegExp? digitMatcher, int? zeroDigit}) {
var string = (digitMatcher ?? regexp.asciiDigitMatcher).stringMatch(rest());
if (string == null || string.isEmpty) return null;
read(string.length);
if (zeroDigit != null && zeroDigit != constants.asciiZeroCodeUnit) {
// Trying to optimize this, as it might get called a lot.
var oldDigits = string.codeUnits;
- var newDigits = List<int>(string.length);
+ var newDigits = List<int>.filled(string.length, 0);
for (var i = 0; i < string.length; i++) {
newDigits[i] = oldDigits[i] - zeroDigit + constants.asciiZeroCodeUnit;
}
diff --git a/lib/src/intl/text_direction.dart b/lib/src/intl/text_direction.dart
index 7bcb7e4..e9b5649 100644
--- a/lib/src/intl/text_direction.dart
+++ b/lib/src/intl/text_direction.dart
@@ -1,7 +1,6 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// @dart=2.9
/// Represents directionality of text.
///
diff --git a/lib/src/intl_helpers.dart b/lib/src/intl_helpers.dart
index 46c57eb..6b3d992 100644
--- a/lib/src/intl_helpers.dart
+++ b/lib/src/intl_helpers.dart
@@ -10,6 +10,7 @@
import 'dart:async';
import 'global_state.dart' as global_state;
+import 'intl_helpers.dart' as helpers;
/// Type for the callback action when a message translation is not found.
typedef MessageIfAbsent = String Function(
@@ -145,3 +146,41 @@
if (region.length <= 3) region = region.toUpperCase();
return '${aLocale[0]}${aLocale[1]}_$region';
}
+
+String verifiedLocale(String? newLocale, bool Function(String) localeExists,
+ String Function(String)? onFailure) {
+// TODO(alanknight): Previously we kept a single verified locale on the Intl
+// object, but with different verification for different uses, that's more
+// difficult. As a result, we call this more often. Consider keeping
+// verified locales for each purpose if it turns out to be a performance
+// issue.
+ if (newLocale == null) {
+ return verifiedLocale(
+ global_state.getCurrentLocale(), localeExists, onFailure);
+ }
+ if (localeExists(newLocale)) {
+ return newLocale;
+ }
+ for (var each in [
+ helpers.canonicalizedLocale(newLocale),
+ helpers.shortLocale(newLocale),
+ 'fallback'
+ ]) {
+ if (localeExists(each)) {
+ return each;
+ }
+ }
+ return (onFailure ?? _throwLocaleError)(newLocale);
+}
+
+/// The default action if a locale isn't found in verifiedLocale. Throw
+/// an exception indicating the locale isn't correct.
+String _throwLocaleError(String localeName) {
+ throw ArgumentError('Invalid locale "$localeName"');
+}
+
+/// Return the short version of a locale name, e.g. 'en_US' => 'en'
+String shortLocale(String aLocale) {
+ if (aLocale.length < 2) return aLocale;
+ return aLocale.substring(0, 2).toLowerCase();
+}