Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
a7af0b31
Unverified
Commit
a7af0b31
authored
May 11, 2021
by
Shi-Hao Hong
Committed by
GitHub
May 11, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Expose basicLocaleListResolution in widget library (#81898)
parent
df158290
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
258 additions
and
128 deletions
+258
-128
app.dart
packages/flutter/lib/src/widgets/app.dart
+131
-128
app_test.dart
packages/flutter/test/widgets/app_test.dart
+127
-0
No files found.
packages/flutter/lib/src/widgets/app.dart
View file @
a7af0b31
...
...
@@ -79,6 +79,134 @@ typedef LocaleListResolutionCallback = Locale? Function(List<Locale>? locales, I
/// {@endtemplate}
typedef
LocaleResolutionCallback
=
Locale
?
Function
(
Locale
?
locale
,
Iterable
<
Locale
>
supportedLocales
);
/// The default locale resolution algorithm.
///
/// Custom resolution algorithms can be provided through
/// [WidgetsApp.localeListResolutionCallback] or
/// [WidgetsApp.localeResolutionCallback].
///
/// When no custom locale resolution algorithms are provided or if both fail
/// to resolve, Flutter will default to calling this algorithm.
///
/// This algorithm prioritizes speed at the cost of slightly less appropriate
/// resolutions for edge cases.
///
/// This algorithm will resolve to the earliest preferred locale that
/// matches the most fields, prioritizing in the order of perfect match,
/// languageCode+countryCode, languageCode+scriptCode, languageCode-only.
///
/// In the case where a locale is matched by languageCode-only and is not the
/// default (first) locale, the next preferred locale with a
/// perfect match can supersede the languageCode-only match if it exists.
///
/// When a preferredLocale matches more than one supported locale, it will
/// resolve to the first matching locale listed in the supportedLocales.
///
/// When all preferred locales have been exhausted without a match, the first
/// countryCode only match will be returned.
///
/// When no match at all is found, the first (default) locale in
/// [supportedLocales] will be returned.
///
/// 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
///
/// 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`
/// when `de` is not supported and `zh` is listed before `fr` (German is closer to French
/// than Chinese).
Locale
basicLocaleListResolution
(
List
<
Locale
>?
preferredLocales
,
Iterable
<
Locale
>
supportedLocales
)
{
// preferredLocales can be null when called before the platform has had a chance to
// initialize the locales. Platforms without locale passing support will provide an empty list.
// We default to the first supported locale in these cases.
if
(
preferredLocales
==
null
||
preferredLocales
.
isEmpty
)
{
return
supportedLocales
.
first
;
}
// Hash the supported locales because apps can support many locales and would
// be expensive to search through them many times.
final
Map
<
String
,
Locale
>
allSupportedLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
,
Locale
>
languageAndCountryLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
,
Locale
>
languageAndScriptLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
,
Locale
>
languageLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
?,
Locale
>
countryLocales
=
HashMap
<
String
?,
Locale
>();
for
(
final
Locale
locale
in
supportedLocales
)
{
allSupportedLocales
[
'
${locale.languageCode}
_
${locale.scriptCode}
_
${locale.countryCode}
'
]
??=
locale
;
languageAndScriptLocales
[
'
${locale.languageCode}
_
${locale.scriptCode}
'
]
??=
locale
;
languageAndCountryLocales
[
'
${locale.languageCode}
_
${locale.countryCode}
'
]
??=
locale
;
languageLocales
[
locale
.
languageCode
]
??=
locale
;
countryLocales
[
locale
.
countryCode
]
??=
locale
;
}
// Since languageCode-only matches are possibly low quality, we don't return
// it instantly when we find such a match. We check to see if the next
// preferred locale in the list has a high accuracy match, and only return
// the languageCode-only match when a higher accuracy match in the next
// preferred locale cannot be found.
Locale
?
matchesLanguageCode
;
Locale
?
matchesCountryCode
;
// Loop over user's preferred locales
for
(
int
localeIndex
=
0
;
localeIndex
<
preferredLocales
.
length
;
localeIndex
+=
1
)
{
final
Locale
userLocale
=
preferredLocales
[
localeIndex
];
// Look for perfect match.
if
(
allSupportedLocales
.
containsKey
(
'
${userLocale.languageCode}
_
${userLocale.scriptCode}
_
${userLocale.countryCode}
'
))
{
return
userLocale
;
}
// Look for language+script match.
if
(
userLocale
.
scriptCode
!=
null
)
{
final
Locale
?
match
=
languageAndScriptLocales
[
'
${userLocale.languageCode}
_
${userLocale.scriptCode}
'
];
if
(
match
!=
null
)
{
return
match
;
}
}
// Look for language+country match.
if
(
userLocale
.
countryCode
!=
null
)
{
final
Locale
?
match
=
languageAndCountryLocales
[
'
${userLocale.languageCode}
_
${userLocale.countryCode}
'
];
if
(
match
!=
null
)
{
return
match
;
}
}
// If there was a languageCode-only match in the previous iteration's higher
// ranked preferred locale, we return it if the current userLocale does not
// have a better match.
if
(
matchesLanguageCode
!=
null
)
{
return
matchesLanguageCode
;
}
// Look and store language-only match.
Locale
?
match
=
languageLocales
[
userLocale
.
languageCode
];
if
(
match
!=
null
)
{
matchesLanguageCode
=
match
;
// Since first (default) locale is usually highly preferred, we will allow
// a languageCode-only match to be instantly matched. If the next preferred
// languageCode is the same, we defer hastily returning until the next iteration
// since at worst it is the same and at best an improved match.
if
(
localeIndex
==
0
&&
!(
localeIndex
+
1
<
preferredLocales
.
length
&&
preferredLocales
[
localeIndex
+
1
].
languageCode
==
userLocale
.
languageCode
))
{
return
matchesLanguageCode
;
}
}
// countryCode-only match. When all else except default supported locale fails,
// attempt to match by country only, as a user is likely to be familiar with a
// language from their listed country.
if
(
matchesCountryCode
==
null
&&
userLocale
.
countryCode
!=
null
)
{
match
=
countryLocales
[
userLocale
.
countryCode
];
if
(
match
!=
null
)
{
matchesCountryCode
=
match
;
}
}
}
// When there is no languageCode-only match. Fallback to matching countryCode only. Country
// fallback only applies on iOS. When there is no countryCode-only match, we return first
// supported locale.
final
Locale
resolvedLocale
=
matchesLanguageCode
??
matchesCountryCode
??
supportedLocales
.
first
;
return
resolvedLocale
;
}
/// The signature of [WidgetsApp.onGenerateTitle].
///
/// Used to generate a value for the app's [Title.title], which the device uses
...
...
@@ -721,6 +849,7 @@ class WidgetsApp extends StatefulWidget {
///
/// * [MaterialApp.localeListResolutionCallback], which sets the callback of the
/// [WidgetsApp] it creates.
/// * [basicLocaleListResolution], the default locale resolution algorithm.
final
LocaleListResolutionCallback
?
localeListResolutionCallback
;
/// {@macro flutter.widgets.widgetsApp.localeListResolutionCallback}
...
...
@@ -736,6 +865,7 @@ class WidgetsApp extends StatefulWidget {
///
/// * [MaterialApp.localeResolutionCallback], which sets the callback of the
/// [WidgetsApp] it creates.
/// * [basicLocaleListResolution], the default locale resolution algorithm.
final
LocaleResolutionCallback
?
localeResolutionCallback
;
/// {@template flutter.widgets.widgetsApp.supportedLocales}
...
...
@@ -804,6 +934,7 @@ class WidgetsApp extends StatefulWidget {
/// when the device's locale changes.
/// * [localizationsDelegates], which collectively define all of the localized
/// resources used by this app.
/// * [basicLocaleListResolution], the default locale resolution algorithm.
final
Iterable
<
Locale
>
supportedLocales
;
/// Turns on a performance overlay.
...
...
@@ -1311,134 +1442,6 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
return
basicLocaleListResolution
(
preferredLocales
,
supportedLocales
);
}
/// The default locale resolution algorithm.
///
/// Custom resolution algorithms can be provided through
/// [WidgetsApp.localeListResolutionCallback] or
/// [WidgetsApp.localeResolutionCallback].
///
/// When no custom locale resolution algorithms are provided or if both fail
/// to resolve, Flutter will default to calling this algorithm.
///
/// This algorithm prioritizes speed at the cost of slightly less appropriate
/// resolutions for edge cases.
///
/// This algorithm will resolve to the earliest preferred locale that
/// matches the most fields, prioritizing in the order of perfect match,
/// languageCode+countryCode, languageCode+scriptCode, languageCode-only.
///
/// In the case where a locale is matched by languageCode-only and is not the
/// default (first) locale, the next preferred locale with a
/// perfect match can supersede the languageCode-only match if it exists.
///
/// When a preferredLocale matches more than one supported locale, it will
/// resolve to the first matching locale listed in the supportedLocales.
///
/// When all preferred locales have been exhausted without a match, the first
/// countryCode only match will be returned.
///
/// When no match at all is found, the first (default) locale in
/// [supportedLocales] will be returned.
///
/// 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
///
/// 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`
/// when `de` is not supported and `zh` is listed before `fr` (German is closer to French
/// than Chinese).
static
Locale
basicLocaleListResolution
(
List
<
Locale
>?
preferredLocales
,
Iterable
<
Locale
>
supportedLocales
)
{
// preferredLocales can be null when called before the platform has had a chance to
// initialize the locales. Platforms without locale passing support will provide an empty list.
// We default to the first supported locale in these cases.
if
(
preferredLocales
==
null
||
preferredLocales
.
isEmpty
)
{
return
supportedLocales
.
first
;
}
// Hash the supported locales because apps can support many locales and would
// be expensive to search through them many times.
final
Map
<
String
,
Locale
>
allSupportedLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
,
Locale
>
languageAndCountryLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
,
Locale
>
languageAndScriptLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
,
Locale
>
languageLocales
=
HashMap
<
String
,
Locale
>();
final
Map
<
String
?,
Locale
>
countryLocales
=
HashMap
<
String
?,
Locale
>();
for
(
final
Locale
locale
in
supportedLocales
)
{
allSupportedLocales
[
'
${locale.languageCode}
_
${locale.scriptCode}
_
${locale.countryCode}
'
]
??=
locale
;
languageAndScriptLocales
[
'
${locale.languageCode}
_
${locale.scriptCode}
'
]
??=
locale
;
languageAndCountryLocales
[
'
${locale.languageCode}
_
${locale.countryCode}
'
]
??=
locale
;
languageLocales
[
locale
.
languageCode
]
??=
locale
;
countryLocales
[
locale
.
countryCode
]
??=
locale
;
}
// Since languageCode-only matches are possibly low quality, we don't return
// it instantly when we find such a match. We check to see if the next
// preferred locale in the list has a high accuracy match, and only return
// the languageCode-only match when a higher accuracy match in the next
// preferred locale cannot be found.
Locale
?
matchesLanguageCode
;
Locale
?
matchesCountryCode
;
// Loop over user's preferred locales
for
(
int
localeIndex
=
0
;
localeIndex
<
preferredLocales
.
length
;
localeIndex
+=
1
)
{
final
Locale
userLocale
=
preferredLocales
[
localeIndex
];
// Look for perfect match.
if
(
allSupportedLocales
.
containsKey
(
'
${userLocale.languageCode}
_
${userLocale.scriptCode}
_
${userLocale.countryCode}
'
))
{
return
userLocale
;
}
// Look for language+script match.
if
(
userLocale
.
scriptCode
!=
null
)
{
final
Locale
?
match
=
languageAndScriptLocales
[
'
${userLocale.languageCode}
_
${userLocale.scriptCode}
'
];
if
(
match
!=
null
)
{
return
match
;
}
}
// Look for language+country match.
if
(
userLocale
.
countryCode
!=
null
)
{
final
Locale
?
match
=
languageAndCountryLocales
[
'
${userLocale.languageCode}
_
${userLocale.countryCode}
'
];
if
(
match
!=
null
)
{
return
match
;
}
}
// If there was a languageCode-only match in the previous iteration's higher
// ranked preferred locale, we return it if the current userLocale does not
// have a better match.
if
(
matchesLanguageCode
!=
null
)
{
return
matchesLanguageCode
;
}
// Look and store language-only match.
Locale
?
match
=
languageLocales
[
userLocale
.
languageCode
];
if
(
match
!=
null
)
{
matchesLanguageCode
=
match
;
// Since first (default) locale is usually highly preferred, we will allow
// a languageCode-only match to be instantly matched. If the next preferred
// languageCode is the same, we defer hastily returning until the next iteration
// since at worst it is the same and at best an improved match.
if
(
localeIndex
==
0
&&
!(
localeIndex
+
1
<
preferredLocales
.
length
&&
preferredLocales
[
localeIndex
+
1
].
languageCode
==
userLocale
.
languageCode
))
{
return
matchesLanguageCode
;
}
}
// countryCode-only match. When all else except default supported locale fails,
// attempt to match by country only, as a user is likely to be familiar with a
// language from their listed country.
if
(
matchesCountryCode
==
null
&&
userLocale
.
countryCode
!=
null
)
{
match
=
countryLocales
[
userLocale
.
countryCode
];
if
(
match
!=
null
)
{
matchesCountryCode
=
match
;
}
}
}
// When there is no languageCode-only match. Fallback to matching countryCode only. Country
// fallback only applies on iOS. When there is no countryCode-only match, we return first
// supported locale.
final
Locale
resolvedLocale
=
matchesLanguageCode
??
matchesCountryCode
??
supportedLocales
.
first
;
return
resolvedLocale
;
}
@override
void
didChangeLocales
(
List
<
Locale
>?
locales
)
{
final
Locale
newLocale
=
_resolveLocales
(
locales
,
widget
.
supportedLocales
);
...
...
packages/flutter/test/widgets/app_test.dart
View file @
a7af0b31
...
...
@@ -328,6 +328,133 @@ void main() {
);
expect
(
ScrollConfiguration
.
of
(
capturedContext
).
runtimeType
,
ScrollBehavior
);
});
test
(
'basicLocaleListResolution'
,
()
{
// Matches exactly for language code.
expect
(
basicLocaleListResolution
(
<
Locale
>[
const
Locale
(
'zh'
),
const
Locale
(
'un'
),
const
Locale
(
'en'
),
],
<
Locale
>[
const
Locale
(
'en'
),
],
),
const
Locale
(
'en'
),
);
// Matches exactly for language code and country code.
expect
(
basicLocaleListResolution
(
<
Locale
>[
const
Locale
(
'en'
),
const
Locale
(
'en'
,
'US'
),
],
<
Locale
>[
const
Locale
(
'en'
,
'US'
),
],
),
const
Locale
(
'en'
,
'US'
),
);
// Matches language+script over language+country
expect
(
basicLocaleListResolution
(
<
Locale
>[
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
scriptCode:
'Hant'
,
countryCode:
'HK'
,
),
],
<
Locale
>[
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
countryCode:
'HK'
,
),
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
scriptCode:
'Hant'
,
),
],
),
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
scriptCode:
'Hant'
,
),
);
// Matches exactly for language code, script code and country code.
expect
(
basicLocaleListResolution
(
<
Locale
>[
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
),
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
scriptCode:
'Hant'
,
countryCode:
'TW'
,
),
],
<
Locale
>[
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
scriptCode:
'Hant'
,
countryCode:
'TW'
,
),
],
),
const
Locale
.
fromSubtags
(
languageCode:
'zh'
,
scriptCode:
'Hant'
,
countryCode:
'TW'
,
),
);
// Selects for country code if the language code is not found in the
// preferred locales list.
expect
(
basicLocaleListResolution
(
<
Locale
>[
const
Locale
.
fromSubtags
(
languageCode:
'en'
,
),
const
Locale
.
fromSubtags
(
languageCode:
'ar'
,
countryCode:
'tn'
,
),
],
<
Locale
>[
const
Locale
.
fromSubtags
(
languageCode:
'fr'
,
countryCode:
'tn'
,
),
],
),
const
Locale
.
fromSubtags
(
languageCode:
'fr'
,
countryCode:
'tn'
,
),
);
// Selects first (default) locale when no match at all is found.
expect
(
basicLocaleListResolution
(
<
Locale
>[
const
Locale
(
'tn'
),
],
<
Locale
>[
const
Locale
(
'zh'
),
const
Locale
(
'un'
),
const
Locale
(
'en'
),
],
),
const
Locale
(
'zh'
),
);
});
}
typedef
SimpleRouterDelegateBuilder
=
Widget
Function
(
BuildContext
,
RouteInformation
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment