Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Locale.fromSubtags and support for scriptCode. #6518

Merged
merged 27 commits into from
Oct 29, 2018
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e042526
Add Locale.fromComponents.
hugovdm Oct 12, 2018
4013600
Change toString from underscores to dashes. Expand the unit tests.
hugovdm Oct 12, 2018
813998d
Rename 'fromComponents' to 'create'. Change variants from String to L…
hugovdm Oct 15, 2018
fe5e110
Use default for language parameter. Use hashCode/hashList.
hugovdm Oct 15, 2018
fb72be0
Have toString() stick with old (underscore) behaviour.
hugovdm Oct 15, 2018
c6a8b96
Demonstrate empty-list bug in assert code.
hugovdm Oct 15, 2018
af40b72
Fix empty-list assert bug.
hugovdm Oct 15, 2018
488cf31
Add ignores for lint issues. Unsure about 71340 though.
hugovdm Oct 15, 2018
829af35
Fix operator== via _listEquals.
hugovdm Oct 16, 2018
c749663
Remove length-checking asserts: we're anyway not checking characters …
hugovdm Oct 16, 2018
04c52e9
Documentation update.
hugovdm Oct 16, 2018
58bfdc6
Change reasoning for ignore:prefer_initializing_formals.
hugovdm Oct 16, 2018
bc4cc07
Try 'fromSubtags' as new constructor name.
hugovdm Oct 17, 2018
a8b2797
Documentation improvements based on Pull Request review.
hugovdm Oct 17, 2018
d6f06f5
Assert-fail for invalid-length subtags and drop bad subtags in produc…
hugovdm Oct 18, 2018
8948502
Revert "Assert-fail for invalid-length subtags and drop bad subtags i…
hugovdm Oct 19, 2018
20e8bd7
Re-fix Locale.toString() for variants=[].
hugovdm Oct 19, 2018
9911dfd
Tear out variants, in case we want to have one fewer pointer in the f…
hugovdm Oct 19, 2018
cb81b55
Make named parameters' names consistent with member names.
hugovdm Oct 19, 2018
5e0def8
Also remove _listEquals: no longer in use.
hugovdm Oct 22, 2018
9f6e3e0
Merge branch 'master' into fromComponents
hugovdm Oct 23, 2018
97ab4fe
Lint fix.
hugovdm Oct 23, 2018
5f625ae
Fix code review nits.
hugovdm Oct 24, 2018
3b87068
Lint fix for assert, and a couple more not-zero-length-string asserts.
hugovdm Oct 24, 2018
df46762
Code Review: two of three nits addressed...
hugovdm Oct 26, 2018
57b068e
Review fix: change 'should' to 'must' for subtag prescriptions.
hugovdm Oct 26, 2018
6f07e8d
Assert-check that countryCode is never ''.
hugovdm Oct 29, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 137 additions & 25 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ class WindowPadding {
}
}

/// An identifier used to select a user's language and formatting preferences,
/// consisting of a language and a country. This is a subset of locale
/// identifiers as defined by BCP 47.
/// An identifier used to select a user's language and formatting preferences.
/// This represents a [Unicode Language
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier).
///
/// Locales are canonicalized according to the "preferred value" entries in the
/// [IANA Language Subtag
Expand All @@ -145,12 +145,51 @@ class Locale {
/// The primary language subtag must not be null. The region subtag is
/// optional.
///
/// The values are _case sensitive_, and should match the case of the relevant
/// subtags in the [IANA Language Subtag
/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
/// Typically this means the primary language subtag should be lowercase and
/// the region subtag should be uppercase.
const Locale(this._languageCode, [ this._countryCode ]) : assert(_languageCode != null);
/// The subtag values are _case sensitive_ and should be one of the valid
/// subtags according to CLDR supplemental data:
/// [language](http://unicode.org/cldr/latest/common/validity/language.xml),
/// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The
/// primary language subtag should be two to three lowercase letters. The
/// region region subtag should be two uppercase letters or three digits. See
/// the [Unicode Language
/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier)
/// specification.
///
/// Validity is not checked by default, but some methods may throw away
/// invalid data.
///
/// See also: [Locale.create].
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
const Locale(
this._languageCode, [
this._countryCode,
]) : assert(_languageCode != null),
this.scriptCode = null,
this._variants = null;

/// Creates a new Locale object.
///
/// The keyword arguments specify the subtags of the Locale.
///
/// The subtag values are _case sensitive_ and should be valid subtags
/// according to CLDR supplemental data:
/// [language](http://unicode.org/cldr/latest/common/validity/language.xml),
/// [script](http://unicode.org/cldr/latest/common/validity/script.xml),
/// [region](http://unicode.org/cldr/latest/common/validity/region.xml) and
/// [variant](http://unicode.org/cldr/latest/common/validity/variant.xml). If
/// there is more than one variant, they should be in sorted order. This list
/// will be used as-is, and should never again be modified.
///
/// Validity is not checked by default, but some methods may throw away
/// invalid data.
const Locale.fromSubtags({
String language = 'und',
String script,
String region,
List<String> variants,
}) : _languageCode = language,
scriptCode = script, // ignore: prefer_initializing_formals, parameter name and member name are different.
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
_countryCode = region,
_variants = variants;

/// The primary language subtag for the locale.
///
Expand All @@ -166,10 +205,19 @@ class Locale {
/// Locale('he')` and `const Locale('iw')` are equal, and both have the
/// [languageCode] `he`, because `iw` is a deprecated language subtag that was
/// replaced by the subtag `he`.
String get languageCode => _canonicalizeLanguageCode(_languageCode);
///
/// This should be a valid Unicode Language subtag as listed in [Unicode CLDR
/// supplemental
/// data](http://unicode.org/cldr/latest/common/validity/language.xml).
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
///
/// See also:
///
/// * [Locale.fromSubtags], which describes the conventions for creating
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
/// [Locale] objects.
String get languageCode => _replaceDeprecatedLanguageSubtag(_languageCode);
final String _languageCode;

static String _canonicalizeLanguageCode(String languageCode) {
static String _replaceDeprecatedLanguageSubtag(String languageCode) {
// This switch statement is generated by //flutter/tools/gen_locale.dart
// Mappings generated for language subtag registry as of 2018-08-08.
switch (languageCode) {
Expand Down Expand Up @@ -255,9 +303,23 @@ class Locale {
}
}

/// The script subtag for the locale.
///
/// This may be null, indicating that there is no specified script subtag.
///
/// This should be a valid Unicode Language Identifier script subtag as listed
/// in [Unicode CLDR supplemental
/// data](http://unicode.org/cldr/latest/common/validity/script.xml).
///
/// See also:
///
/// * [Locale.fromSubtags], which describes the conventions for creating
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
/// [Locale] objects.
final String scriptCode;

/// The region subtag for the locale.
///
/// This can be null.
/// This may be null, indicating that there is no specified region subtag.
///
/// This is expected to be string registered in the [IANA Language Subtag
/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
Expand All @@ -269,10 +331,15 @@ class Locale {
/// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the
/// [countryCode] `DE`, because `DD` is a deprecated language subtag that was
/// replaced by the subtag `DE`.
String get countryCode => _canonicalizeRegionCode(_countryCode);
///
/// See also:
///
/// * [Locale.fromSubtags], which describes the conventions for creating
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
/// [Locale] objects.
String get countryCode => _replaceDeprecatedRegionSubtag(_countryCode);
final String _countryCode;

static String _canonicalizeRegionCode(String regionCode) {
static String _replaceDeprecatedRegionSubtag(String regionCode) {
// This switch statement is generated by //flutter/tools/gen_locale.dart
// Mappings generated for language subtag registry as of 2018-08-08.
switch (regionCode) {
Expand All @@ -286,6 +353,38 @@ class Locale {
}
}

/// The variants subtags for the locale.
///
/// Each subtag is expected to be a valid Unicode Language Identifier variant
/// subtag that is listed in Unicode CLDR supplemental data:
/// http://unicode.org/cldr/latest/common/validity/variants.xml. Please see
/// constructor documentation.
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
///
/// See also:
///
/// * [Locale.fromSubtags], which describes the conventions for creating
/// [Locale] objects.
Iterable<String> get variants {
if (_variants == null)
return const <String>[];
return _variants;
}

/// The variants subtags for the locale.
///
/// This may be null, indicating that there is no specified variants subtag.
///
/// This is expected to be a sorted list of valid Unicode Language Identifier
/// variant subtags that are listed in Unicode CLDR supplemental data:
/// http://unicode.org/cldr/latest/common/validity/variants.xml. Please see
/// constructor documentation.
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
///
/// See also:
///
/// * [Locale.fromSubtags], which describes the conventions for creating
/// [Locale] objects.
final List<String> _variants;

@override
bool operator ==(dynamic other) {
if (identical(this, other))
Expand All @@ -294,23 +393,36 @@ class Locale {
return false;
final Locale typedOther = other;
return languageCode == typedOther.languageCode
&& countryCode == typedOther.countryCode;
&& scriptCode == typedOther.scriptCode
&& countryCode == typedOther.countryCode
&& _listEquals<String>(_variants, typedOther._variants);
}

@override
int get hashCode {
int result = 373;
result = 37 * result + languageCode.hashCode;
if (_countryCode != null)
result = 37 * result + countryCode.hashCode;
return result;
bool _listEquals<T>(List<T> a, List<T> b) {
if (a == null)
return b == null;
if (b == null || a.length != b.length)
return false;
for (int index = 0; index < a.length; index += 1) {
if (a[index] != b[index])
return false;
}
return true;
}

@override
int get hashCode => hashValues(languageCode, scriptCode, countryCode, hashList(_variants));

@override
String toString() {
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
if (_countryCode == null)
return languageCode;
return '${languageCode}_$countryCode';
final StringBuffer out = StringBuffer(languageCode);
if (scriptCode != null)
out.write('_$scriptCode');
if (_countryCode != null)
out.write('_$countryCode');
if (_variants != null && _variants.isNotEmpty)
out.write('_${_variants.join("_")}');
return out.toString();
}
}

Expand Down
40 changes: 40 additions & 0 deletions testing/dart/locale_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,44 @@ void main() {
expect(const Locale('iw', 'DD').toString(), 'he_DE');
expect(const Locale('iw', 'DD'), const Locale('he', 'DE'));
});

test('Locale.fromSubtags', () {
expect(const Locale.fromSubtags().languageCode, 'und');
expect(const Locale.fromSubtags().scriptCode, null);
expect(const Locale.fromSubtags().countryCode, null);
expect(const Locale.fromSubtags().variants, orderedEquals([]));

expect(const Locale.fromSubtags(language: 'en').toString(), 'en');
expect(const Locale.fromSubtags(language: 'en').languageCode, 'en');
expect(const Locale.fromSubtags(script: 'Latn').toString(), 'und_Latn');
expect(const Locale.fromSubtags(script: 'Latn').scriptCode, 'Latn');
expect(const Locale.fromSubtags(region: 'US').toString(), 'und_US');
expect(const Locale.fromSubtags(region: 'US').countryCode, 'US');
hugovdm marked this conversation as resolved.
Show resolved Hide resolved
expect(const Locale.fromSubtags(variants: []).toString(), 'und');
expect(const Locale.fromSubtags(variants: []).variants,
orderedEquals([]));
expect(const Locale.fromSubtags(variants: ['fonipa', 'scouse']).variants,
orderedEquals(['fonipa', 'scouse']));

expect(Locale.fromSubtags(language: 'es', region: '419').toString(), 'es_419');
expect(Locale.fromSubtags(language: 'es', region: '419').languageCode, 'es');
expect(Locale.fromSubtags(language: 'es', region: '419').countryCode, '419');
expect(Locale.fromSubtags(script: 'Latn', variants: ['fonipa']).toString(), 'und_Latn_fonipa');
expect(Locale.fromSubtags(script: 'Latn', variants: ['fonipa']).scriptCode, 'Latn');
expect(Locale.fromSubtags(script: 'Latn', variants: ['fonipa']).variants,
orderedEquals(['fonipa']));

expect(Locale.fromSubtags(language: 'zh', script: 'Hans', region: 'CN').toString(), 'zh_Hans_CN');
});

test('Locale equality', () {
expect(Locale.fromSubtags(language: 'en', variants: ['fonipa']),
Locale.fromSubtags(language: 'en', variants: ['fonipa']));
expect(Locale.fromSubtags(language: 'en', variants: ['fonipa']).hashCode,
Locale.fromSubtags(language: 'en', variants: ['fonipa']).hashCode);
expect(Locale.fromSubtags(language: 'en'),
isNot(Locale.fromSubtags(language: 'en', script: 'Latn')));
expect(Locale.fromSubtags(language: 'en').hashCode,
isNot(Locale.fromSubtags(language: 'en', script: 'Latn').hashCode));
});
}