Skip to content

Commit

Permalink
Add Locale.fromSubtags and support for scriptCode. (flutter#6518)
Browse files Browse the repository at this point in the history
* Add Locale.fromComponents.

* Change toString from underscores to dashes. Expand the unit tests.

* Rename 'fromComponents' to 'create'. Change variants from String to List<String>.

* Use default for language parameter. Use hashCode/hashList.

* Have toString() stick with old (underscore) behaviour.

* Demonstrate empty-list bug in assert code.

* Fix empty-list assert bug.

* Add ignores for lint issues. Unsure about 71340 though.

* Fix operator== via _listEquals.

* Remove length-checking asserts: we're anyway not checking characters in fields.

* Documentation update.

* Change reasoning for ignore:prefer_initializing_formals.

* Try 'fromSubtags' as new constructor name.

* Documentation improvements based on Pull Request review.

* Assert-fail for invalid-length subtags and drop bad subtags in production code.

* Revert "Assert-fail for invalid-length subtags and drop bad subtags in production code."

This reverts commit d6f06f5.

* Re-fix Locale.toString() for variants=[].

* Tear out variants, in case we want to have one fewer pointer in the future.

* Make named parameters' names consistent with member names.

* Also remove _listEquals: no longer in use.

* Lint fix.

* Fix code review nits.

* Lint fix for assert, and a couple more not-zero-length-string asserts.

* Code Review: two of three nits addressed...

* Review fix: change 'should' to 'must' for subtag prescriptions.

* Assert-check that countryCode is never ''.
  • Loading branch information
hugovdm authored and Hixie committed Oct 29, 2018
1 parent 58c8e30 commit 2b2fbf0
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 27 deletions.
120 changes: 95 additions & 25 deletions lib/ui/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ 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
/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier)
/// (i.e. without Locale extensions), except variants are not supported.
///
/// Locales are canonicalized according to the "preferred value" entries in the
/// [IANA Language Subtag
Expand All @@ -145,16 +147,58 @@ 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), assert(_languageCode != '');
/// The subtag values are _case sensitive_ and must 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 must be at least two and at most eight lowercase
/// letters, but not four letters. The region region subtag must 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:
///
/// * [new Locale.fromSubtags], which also allows a [scriptCode] to be
/// specified.
const Locale(
this._languageCode, [
this._countryCode,
]) : assert(_languageCode != null),
assert(_languageCode != ''),
scriptCode = null,
assert(_countryCode != '');

/// Creates a new Locale object.
///
/// The keyword arguments specify the subtags of the Locale.
///
/// The subtag values are _case sensitive_ and must 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) and
/// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for
/// each of languageCode, scriptCode and countryCode respectively.
///
/// Validity is not checked by default, but some methods may throw away
/// invalid data.
const Locale.fromSubtags({
String languageCode = 'und',
this.scriptCode,
String countryCode,
}) : assert(languageCode != null),
assert(languageCode != ''),
_languageCode = languageCode,
assert(scriptCode != ''),
assert(countryCode != ''),
_countryCode = countryCode;

/// The primary language subtag for the locale.
///
/// This must not be null.
/// This must not be null. It may be 'und', representing 'undefined'.
///
/// 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 @@ -166,10 +210,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 must be a valid Unicode Language subtag as listed in [Unicode CLDR
/// supplemental
/// data](http://unicode.org/cldr/latest/common/validity/language.xml).
///
/// See also:
///
/// * [new Locale.fromSubtags], which describes the conventions for creating
/// [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 +308,23 @@ class Locale {
}
}

/// The script subtag for the locale.
///
/// This may be null, indicating that there is no specified script subtag.
///
/// This must 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:
///
/// * [new Locale.fromSubtags], which describes the conventions for creating
/// [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 +336,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:
///
/// * [new Locale.fromSubtags], which describes the conventions for creating
/// [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 @@ -294,23 +366,21 @@ class Locale {
return false;
final Locale typedOther = other;
return languageCode == typedOther.languageCode
&& scriptCode == typedOther.scriptCode
&& countryCode == typedOther.countryCode;
}

@override
int get hashCode {
int result = 373;
result = 37 * result + languageCode.hashCode;
if (_countryCode != null)
result = 37 * result + countryCode.hashCode;
return result;
}
int get hashCode => hashValues(languageCode, scriptCode, countryCode);

@override
String toString() {
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');
return out.toString();
}
}

Expand Down
28 changes: 26 additions & 2 deletions testing/dart/locale_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,35 @@ void main() {
expect(const Locale('en').toString(), 'en');
expect(const Locale('en'), new Locale('en', $null));
expect(const Locale('en').hashCode, new Locale('en', $null).hashCode);
expect(const Locale('en'), isNot(new Locale('en', '')));
expect(const Locale('en').hashCode, isNot(new Locale('en', '').hashCode));
expect(const Locale('en', 'US').toString(), 'en_US');
expect(const Locale('iw').toString(), 'he');
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(languageCode: 'en').toString(), 'en');
expect(const Locale.fromSubtags(languageCode: 'en').languageCode, 'en');
expect(const Locale.fromSubtags(scriptCode: 'Latn').toString(), 'und_Latn');
expect(const Locale.fromSubtags(scriptCode: 'Latn').scriptCode, 'Latn');
expect(const Locale.fromSubtags(countryCode: 'US').toString(), 'und_US');
expect(const Locale.fromSubtags(countryCode: 'US').countryCode, 'US');

expect(Locale.fromSubtags(languageCode: 'es', countryCode: '419').toString(), 'es_419');
expect(Locale.fromSubtags(languageCode: 'es', countryCode: '419').languageCode, 'es');
expect(Locale.fromSubtags(languageCode: 'es', countryCode: '419').countryCode, '419');

expect(Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN').toString(), 'zh_Hans_CN');
});

test('Locale equality', () {
expect(Locale.fromSubtags(languageCode: 'en'),
isNot(Locale.fromSubtags(languageCode: 'en', scriptCode: 'Latn')));
expect(Locale.fromSubtags(languageCode: 'en').hashCode,
isNot(Locale.fromSubtags(languageCode: 'en', scriptCode: 'Latn').hashCode));
});
}

0 comments on commit 2b2fbf0

Please sign in to comment.