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

feat(intl): ignore script subtags when canonicalizing locale strings #948

Merged
merged 1 commit into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions pkgs/intl/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* Updated the Turkish Lira (TRY) currency symbol in `simpleCurrencySymbols`
from "TL" to "₺" (U+20BA). This ensures accuracy and alignment with the
official symbol introduced in 2012.
* Fix parsing of locale strings containing script codes in `verifiedLocale`.
For example, `zh-Hans-CN` would have been previously parsed as `zh`, but is
now parsed as `zh_CN`.

## 0.20.2
* Remove the dependency on `package:http`.
Expand Down
3 changes: 2 additions & 1 deletion pkgs/intl/lib/intl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ class Intl {
helpers.verifiedLocale(newLocale, localeExists, onFailure);

/// Return the short version of a locale name, e.g. 'en_US' => 'en'
static String shortLocale(String aLocale) => helpers.shortLocale(aLocale);
static String shortLocale(String aLocale) =>
helpers.languageOnlyLocale(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
Expand Down
44 changes: 37 additions & 7 deletions pkgs/intl/lib/src/intl_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ String? computeMessageName(String? name, String? text, String? meaning) {
return meaning == null ? text : '${text}_$meaning';
}

/// Returns an index of a separator between language and region.
/// Returns an index of a separator between language and other subtags.
///
/// Assumes that language length can be only 2 or 3.
int _separatorIndex(String locale) {
int _languageSeparatorIndex(String locale) {
if (locale.length < 3) {
return -1;
}
Expand All @@ -147,6 +147,19 @@ int _separatorIndex(String locale) {
return -1;
}

/// Returns an index of a separator between script and region.
///
/// Assumes that script contains exactly 4 characters.
int _scriptSeparatorIndex(String region) {
if (region.length < 5) {
return -1;
}
if (region[4] == '-' || region[4] == '_') {
return 4;
}
return -1;
}

String canonicalizedLocale(String? aLocale) {
// Locales of length < 5 are presumably two-letter forms, or else malformed.
// We return them unmodified and if correct they will be found.
Expand All @@ -159,7 +172,7 @@ String canonicalizedLocale(String? aLocale) {
if (aLocale == 'C') return 'en_ISO';
if (aLocale.length < 5) return aLocale;

var separatorIndex = _separatorIndex(aLocale);
var separatorIndex = _languageSeparatorIndex(aLocale);
if (separatorIndex == -1) {
return aLocale;
}
Expand All @@ -186,9 +199,10 @@ String? verifiedLocale(String? newLocale, bool Function(String) localeExists,
}
final fallbackOptions = [
canonicalizedLocale,
shortLocale,
languageRegionOnlyLocale,
languageOnlyLocale,
deprecatedLocale,
(locale) => deprecatedLocale(shortLocale(locale)),
(locale) => deprecatedLocale(languageOnlyLocale(locale)),
(locale) => deprecatedLocale(canonicalizedLocale(locale)),
(_) => 'fallback'
];
Expand Down Expand Up @@ -233,15 +247,15 @@ String deprecatedLocale(String aLocale) {
}

/// Return the short version of a locale name, e.g. 'en_US' => 'en'
String shortLocale(String aLocale) {
String languageOnlyLocale(String aLocale) {
// TODO(b/241094372): Remove this check.
if (aLocale == 'invalid') {
return 'in';
}
if (aLocale.length < 2) {
return aLocale;
}
var separatorIndex = _separatorIndex(aLocale);
var separatorIndex = _languageSeparatorIndex(aLocale);
if (separatorIndex == -1) {
if (aLocale.length < 4) {
// aLocale is already only a language code.
Expand All @@ -253,3 +267,19 @@ String shortLocale(String aLocale) {
}
return aLocale.substring(0, separatorIndex).toLowerCase();
}

String languageRegionOnlyLocale(String aLocale) {
if (aLocale.length < 10) return aLocale;

var separatorIndex = _languageSeparatorIndex(aLocale);
if (separatorIndex == -1) {
return aLocale;
}
var language = aLocale.substring(0, separatorIndex);
var subtags = aLocale.substring(separatorIndex + 1);
separatorIndex = _scriptSeparatorIndex(subtags);
var region = subtags.substring(separatorIndex + 1);
// If it's longer than three it's something odd, so don't touch it.
if (region.length <= 3) region = region.toUpperCase();
return '${language}_$region';
}
5 changes: 5 additions & 0 deletions pkgs/intl/test/intl_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ void main() {
expect(Intl.verifiedLocale('en-ZZ', NumberFormat.localeExists), 'en');
expect(Intl.verifiedLocale('es-999', NumberFormat.localeExists), 'es');
expect(Intl.verifiedLocale('gsw-CH', NumberFormat.localeExists), 'gsw');
expect(
Intl.verifiedLocale('zh-Hant-CN', NumberFormat.localeExists),
'zh_CN',
);

void checkAsNumberDefault(String locale, String expected) {
var oldDefault = Intl.defaultLocale;
Expand All @@ -74,6 +78,7 @@ void main() {
expect(Intl.verifiedLocale('en-ZZ', DateFormat.localeExists), 'en');
expect(Intl.verifiedLocale('es-999', DateFormat.localeExists), 'es');
expect(Intl.verifiedLocale('gsw-CH', DateFormat.localeExists), 'gsw');
expect(Intl.verifiedLocale('zh-Hant-CN', DateFormat.localeExists), 'zh_CN');
// TODO(b/241094372): Remove this check.
expect(Intl.verifiedLocale('invalid', DateFormat.localeExists), 'in');

Expand Down
11 changes: 11 additions & 0 deletions pkgs/intl/test/number_format_test_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,17 @@ void runTests(Map<String, num> allTestNumbers) {
});
}
});

test('Use script', () {
expect(
NumberFormat.currency(locale: 'zh_Hant_TW').format(12),
'TWD12.00',
);
expect(
NumberFormat.currency(locale: 'zh_Hant_CN').format(12),
'CNY12.00',
);
});
}

String stripExtras(String input) {
Expand Down