Skip to content

Commit 2b2fbf0

Browse files
hugovdmHixie
authored andcommitted
Add Locale.fromSubtags and support for scriptCode. (#6518)
* 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 d6f06f5e7b3537d60000c47641580475ef16abbe. * 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 ''.
1 parent 58c8e30 commit 2b2fbf0

File tree

2 files changed

+121
-27
lines changed

2 files changed

+121
-27
lines changed

lib/ui/window.dart

Lines changed: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,11 @@ class WindowPadding {
116116
}
117117
}
118118

119-
/// An identifier used to select a user's language and formatting preferences,
120-
/// consisting of a language and a country. This is a subset of locale
121-
/// identifiers as defined by BCP 47.
119+
/// An identifier used to select a user's language and formatting preferences.
120+
///
121+
/// This represents a [Unicode Language
122+
/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier)
123+
/// (i.e. without Locale extensions), except variants are not supported.
122124
///
123125
/// Locales are canonicalized according to the "preferred value" entries in the
124126
/// [IANA Language Subtag
@@ -145,16 +147,58 @@ class Locale {
145147
/// The primary language subtag must not be null. The region subtag is
146148
/// optional.
147149
///
148-
/// The values are _case sensitive_, and should match the case of the relevant
149-
/// subtags in the [IANA Language Subtag
150-
/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry).
151-
/// Typically this means the primary language subtag should be lowercase and
152-
/// the region subtag should be uppercase.
153-
const Locale(this._languageCode, [ this._countryCode ]) : assert(_languageCode != null), assert(_languageCode != '');
150+
/// The subtag values are _case sensitive_ and must be one of the valid
151+
/// subtags according to CLDR supplemental data:
152+
/// [language](http://unicode.org/cldr/latest/common/validity/language.xml),
153+
/// [region](http://unicode.org/cldr/latest/common/validity/region.xml). The
154+
/// primary language subtag must be at least two and at most eight lowercase
155+
/// letters, but not four letters. The region region subtag must be two
156+
/// uppercase letters or three digits. See the [Unicode Language
157+
/// Identifier](https://www.unicode.org/reports/tr35/#Unicode_language_identifier)
158+
/// specification.
159+
///
160+
/// Validity is not checked by default, but some methods may throw away
161+
/// invalid data.
162+
///
163+
/// See also:
164+
///
165+
/// * [new Locale.fromSubtags], which also allows a [scriptCode] to be
166+
/// specified.
167+
const Locale(
168+
this._languageCode, [
169+
this._countryCode,
170+
]) : assert(_languageCode != null),
171+
assert(_languageCode != ''),
172+
scriptCode = null,
173+
assert(_countryCode != '');
174+
175+
/// Creates a new Locale object.
176+
///
177+
/// The keyword arguments specify the subtags of the Locale.
178+
///
179+
/// The subtag values are _case sensitive_ and must be valid subtags according
180+
/// to CLDR supplemental data:
181+
/// [language](http://unicode.org/cldr/latest/common/validity/language.xml),
182+
/// [script](http://unicode.org/cldr/latest/common/validity/script.xml) and
183+
/// [region](http://unicode.org/cldr/latest/common/validity/region.xml) for
184+
/// each of languageCode, scriptCode and countryCode respectively.
185+
///
186+
/// Validity is not checked by default, but some methods may throw away
187+
/// invalid data.
188+
const Locale.fromSubtags({
189+
String languageCode = 'und',
190+
this.scriptCode,
191+
String countryCode,
192+
}) : assert(languageCode != null),
193+
assert(languageCode != ''),
194+
_languageCode = languageCode,
195+
assert(scriptCode != ''),
196+
assert(countryCode != ''),
197+
_countryCode = countryCode;
154198

155199
/// The primary language subtag for the locale.
156200
///
157-
/// This must not be null.
201+
/// This must not be null. It may be 'und', representing 'undefined'.
158202
///
159203
/// This is expected to be string registered in the [IANA Language Subtag
160204
/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
@@ -166,10 +210,19 @@ class Locale {
166210
/// Locale('he')` and `const Locale('iw')` are equal, and both have the
167211
/// [languageCode] `he`, because `iw` is a deprecated language subtag that was
168212
/// replaced by the subtag `he`.
169-
String get languageCode => _canonicalizeLanguageCode(_languageCode);
213+
///
214+
/// This must be a valid Unicode Language subtag as listed in [Unicode CLDR
215+
/// supplemental
216+
/// data](http://unicode.org/cldr/latest/common/validity/language.xml).
217+
///
218+
/// See also:
219+
///
220+
/// * [new Locale.fromSubtags], which describes the conventions for creating
221+
/// [Locale] objects.
222+
String get languageCode => _replaceDeprecatedLanguageSubtag(_languageCode);
170223
final String _languageCode;
171224

172-
static String _canonicalizeLanguageCode(String languageCode) {
225+
static String _replaceDeprecatedLanguageSubtag(String languageCode) {
173226
// This switch statement is generated by //flutter/tools/gen_locale.dart
174227
// Mappings generated for language subtag registry as of 2018-08-08.
175228
switch (languageCode) {
@@ -255,9 +308,23 @@ class Locale {
255308
}
256309
}
257310

311+
/// The script subtag for the locale.
312+
///
313+
/// This may be null, indicating that there is no specified script subtag.
314+
///
315+
/// This must be a valid Unicode Language Identifier script subtag as listed
316+
/// in [Unicode CLDR supplemental
317+
/// data](http://unicode.org/cldr/latest/common/validity/script.xml).
318+
///
319+
/// See also:
320+
///
321+
/// * [new Locale.fromSubtags], which describes the conventions for creating
322+
/// [Locale] objects.
323+
final String scriptCode;
324+
258325
/// The region subtag for the locale.
259326
///
260-
/// This can be null.
327+
/// This may be null, indicating that there is no specified region subtag.
261328
///
262329
/// This is expected to be string registered in the [IANA Language Subtag
263330
/// Registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
@@ -269,10 +336,15 @@ class Locale {
269336
/// 'DE')` and `const Locale('de', 'DD')` are equal, and both have the
270337
/// [countryCode] `DE`, because `DD` is a deprecated language subtag that was
271338
/// replaced by the subtag `DE`.
272-
String get countryCode => _canonicalizeRegionCode(_countryCode);
339+
///
340+
/// See also:
341+
///
342+
/// * [new Locale.fromSubtags], which describes the conventions for creating
343+
/// [Locale] objects.
344+
String get countryCode => _replaceDeprecatedRegionSubtag(_countryCode);
273345
final String _countryCode;
274346

275-
static String _canonicalizeRegionCode(String regionCode) {
347+
static String _replaceDeprecatedRegionSubtag(String regionCode) {
276348
// This switch statement is generated by //flutter/tools/gen_locale.dart
277349
// Mappings generated for language subtag registry as of 2018-08-08.
278350
switch (regionCode) {
@@ -294,23 +366,21 @@ class Locale {
294366
return false;
295367
final Locale typedOther = other;
296368
return languageCode == typedOther.languageCode
369+
&& scriptCode == typedOther.scriptCode
297370
&& countryCode == typedOther.countryCode;
298371
}
299372

300373
@override
301-
int get hashCode {
302-
int result = 373;
303-
result = 37 * result + languageCode.hashCode;
304-
if (_countryCode != null)
305-
result = 37 * result + countryCode.hashCode;
306-
return result;
307-
}
374+
int get hashCode => hashValues(languageCode, scriptCode, countryCode);
308375

309376
@override
310377
String toString() {
311-
if (_countryCode == null)
312-
return languageCode;
313-
return '${languageCode}_$countryCode';
378+
final StringBuffer out = StringBuffer(languageCode);
379+
if (scriptCode != null)
380+
out.write('_$scriptCode');
381+
if (_countryCode != null)
382+
out.write('_$countryCode');
383+
return out.toString();
314384
}
315385
}
316386

testing/dart/locale_test.dart

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,35 @@ void main() {
1212
expect(const Locale('en').toString(), 'en');
1313
expect(const Locale('en'), new Locale('en', $null));
1414
expect(const Locale('en').hashCode, new Locale('en', $null).hashCode);
15-
expect(const Locale('en'), isNot(new Locale('en', '')));
16-
expect(const Locale('en').hashCode, isNot(new Locale('en', '').hashCode));
1715
expect(const Locale('en', 'US').toString(), 'en_US');
1816
expect(const Locale('iw').toString(), 'he');
1917
expect(const Locale('iw', 'DD').toString(), 'he_DE');
2018
expect(const Locale('iw', 'DD'), const Locale('he', 'DE'));
2119
});
20+
21+
test('Locale.fromSubtags', () {
22+
expect(const Locale.fromSubtags().languageCode, 'und');
23+
expect(const Locale.fromSubtags().scriptCode, null);
24+
expect(const Locale.fromSubtags().countryCode, null);
25+
26+
expect(const Locale.fromSubtags(languageCode: 'en').toString(), 'en');
27+
expect(const Locale.fromSubtags(languageCode: 'en').languageCode, 'en');
28+
expect(const Locale.fromSubtags(scriptCode: 'Latn').toString(), 'und_Latn');
29+
expect(const Locale.fromSubtags(scriptCode: 'Latn').scriptCode, 'Latn');
30+
expect(const Locale.fromSubtags(countryCode: 'US').toString(), 'und_US');
31+
expect(const Locale.fromSubtags(countryCode: 'US').countryCode, 'US');
32+
33+
expect(Locale.fromSubtags(languageCode: 'es', countryCode: '419').toString(), 'es_419');
34+
expect(Locale.fromSubtags(languageCode: 'es', countryCode: '419').languageCode, 'es');
35+
expect(Locale.fromSubtags(languageCode: 'es', countryCode: '419').countryCode, '419');
36+
37+
expect(Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'CN').toString(), 'zh_Hans_CN');
38+
});
39+
40+
test('Locale equality', () {
41+
expect(Locale.fromSubtags(languageCode: 'en'),
42+
isNot(Locale.fromSubtags(languageCode: 'en', scriptCode: 'Latn')));
43+
expect(Locale.fromSubtags(languageCode: 'en').hashCode,
44+
isNot(Locale.fromSubtags(languageCode: 'en', scriptCode: 'Latn').hashCode));
45+
});
2246
}

0 commit comments

Comments
 (0)