-
Notifications
You must be signed in to change notification settings - Fork 41
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-sensitive casing #880
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
## 0.10.2-wip | ||
|
||
- Add case mapping functionality. | ||
|
||
## 0.10.1 | ||
|
||
- Upgrade to new artifacts. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'intl4x.dart'; | ||
|
||
export 'src/case_mapping/case_mapping.dart'; | ||
export 'src/locale/locale.dart'; | ||
|
||
extension CaseMappingWithIntl4X on String { | ||
String toLocaleLowerCase(Locale locale) => | ||
Intl(locale: locale).caseMapping.toLowerCase(this); | ||
String toLocaleUpperCase(Locale locale) => | ||
Intl(locale: locale).caseMapping.toUpperCase(this); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import '../test_checker.dart'; | ||
import 'case_mapping_impl.dart'; | ||
|
||
/// A locale-sensitive case mapper for transforming strings. | ||
/// | ||
/// This class provides methods to convert strings to lowercase or uppercase | ||
/// based on the current locale. During testing, the input is returned | ||
/// unchanged. | ||
class CaseMapping { | ||
final CaseMappingImpl _caseMappingImpl; | ||
|
||
const CaseMapping(this._caseMappingImpl); | ||
|
||
String toLowerCase(String input) { | ||
if (isInTest) { | ||
return input; | ||
} else { | ||
return _caseMappingImpl.toLowerCase(input); | ||
} | ||
} | ||
|
||
String toUpperCase(String input) { | ||
if (isInTest) { | ||
return input; | ||
} else { | ||
return _caseMappingImpl.toUpperCase(input); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import '../bindings/lib.g.dart' as icu; | ||
import '../data.dart'; | ||
import '../data_4x.dart'; | ||
import '../locale/locale.dart'; | ||
import '../locale/locale_4x.dart'; | ||
import 'case_mapping_impl.dart'; | ||
|
||
CaseMappingImpl getCaseMapping4X( | ||
Locale locale, | ||
Data data, | ||
Null _, | ||
) => | ||
CaseMapping4X(locale, data); | ||
|
||
class CaseMapping4X extends CaseMappingImpl { | ||
final icu.CaseMapper _caseMapper; | ||
|
||
CaseMapping4X(super.locale, Data data) | ||
: _caseMapper = icu.CaseMapper(data.to4X()); | ||
|
||
@override | ||
String toLowerCase(String input) => | ||
_caseMapper.lowercase(input, locale.to4X()); | ||
|
||
@override | ||
String toUpperCase(String input) => | ||
_caseMapper.uppercase(input, locale.to4X()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'dart:js_interop'; | ||
|
||
import '../locale/locale.dart'; | ||
import '../options.dart'; | ||
import 'case_mapping_impl.dart'; | ||
|
||
CaseMappingImpl? getCaseMappingECMA( | ||
Locale locale, | ||
Null __, | ||
LocaleMatcher _, | ||
) => | ||
_CaseMappingECMA.tryToBuild(locale); | ||
|
||
extension on JSString { | ||
@JS('String.toLocaleUpperCase') | ||
external String toLocaleUpperCase(String locale); | ||
@JS('String.toLocaleLowerCase') | ||
external String toLocaleLowerCase(String locale); | ||
} | ||
|
||
class _CaseMappingECMA extends CaseMappingImpl { | ||
_CaseMappingECMA(super.locale); | ||
|
||
static CaseMappingImpl? tryToBuild( | ||
Locale locale, | ||
) => | ||
_CaseMappingECMA(locale); | ||
@override | ||
String toUpperCase(String input) => | ||
input.toJS.toLocaleUpperCase(locale.toLanguageTag()); | ||
|
||
@override | ||
String toLowerCase(String input) => | ||
input.toJS.toLocaleLowerCase(locale.toLanguageTag()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'package:meta/meta.dart' show ResourceIdentifier; | ||
|
||
import '../../ecma_policy.dart'; | ||
import '../data.dart'; | ||
import '../ecma/ecma_policy.dart'; | ||
import '../locale/locale.dart'; | ||
import '../options.dart'; | ||
import '../utils.dart'; | ||
import 'case_mapping_stub.dart' if (dart.library.js) 'case_mapping_ecma.dart'; | ||
import 'case_mapping_stub_4x.dart' if (dart.library.io) 'case_mapping_4x.dart'; | ||
|
||
abstract class CaseMappingImpl { | ||
final Locale locale; | ||
|
||
CaseMappingImpl(this.locale); | ||
|
||
String toLowerCase(String input); | ||
String toUpperCase(String input); | ||
|
||
@ResourceIdentifier('CaseMapping') | ||
static CaseMappingImpl build( | ||
Locale locales, | ||
Data data, | ||
LocaleMatcher localeMatcher, | ||
EcmaPolicy ecmaPolicy, | ||
) => | ||
buildFormatter( | ||
locales, | ||
data, | ||
null, | ||
localeMatcher, | ||
ecmaPolicy, | ||
getCaseMappingECMA, | ||
getCaseMapping4X, | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import '../locale/locale.dart'; | ||
import '../options.dart'; | ||
import 'case_mapping_impl.dart'; | ||
|
||
CaseMappingImpl? getCaseMappingECMA( | ||
Locale locale, | ||
Null _, | ||
LocaleMatcher __, | ||
) => | ||
throw UnimplementedError('Cannot use ECMA outside of web environments.'); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import '../data.dart'; | ||
import '../locale/locale.dart'; | ||
import 'case_mapping_impl.dart'; | ||
|
||
CaseMappingImpl getCaseMapping4X( | ||
Locale locale, | ||
Data data, | ||
Null _, | ||
) => | ||
throw UnimplementedError('Cannot use ICU4X in web environments.'); |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,25 @@ | ||||||||||
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | ||||||||||
// for details. All rights reserved. Use of this source code is governed by a | ||||||||||
// BSD-style license that can be found in the LICENSE file. | ||||||||||
|
||||||||||
import 'package:intl4x/case_mapping.dart'; | ||||||||||
import 'package:test/test.dart'; | ||||||||||
|
||||||||||
import 'utils.dart'; | ||||||||||
|
||||||||||
void main() { | ||||||||||
testWithFormatting('test name', () { | ||||||||||
const enUS = Locale(language: 'en', region: 'US'); | ||||||||||
expect('İstanbul'.toLocaleLowerCase(enUS), 'i̇stanbul'); | ||||||||||
expect('ALPHABET'.toLocaleLowerCase(enUS), 'alphabet'); | ||||||||||
|
||||||||||
expect('\u0130'.toLocaleLowerCase(const Locale(language: 'tr')), 'i'); | ||||||||||
expect('\u0130'.toLocaleLowerCase(enUS), isNot('i')); | ||||||||||
Comment on lines
+16
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I like the
Suggested change
|
||||||||||
|
||||||||||
final locales = ['tr', 'TR', 'tr-TR', 'tr-u-co-search', 'tr-x-turkish'] | ||||||||||
.map(Locale.parse); | ||||||||||
Comment on lines
+19
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ISTR there's one other case besides Turkish where locale-specific case-mapping is interesting — I think it was Lithuanian. It'd be nice to have a test case demonstrating that too. |
||||||||||
for (final locale in locales) { | ||||||||||
expect('\u0130'.toLocaleLowerCase(locale), 'i'); | ||||||||||
} | ||||||||||
}); | ||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's too many dots on the lowercase i. Does this pass?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the same result as I get from the corresponding browser API:
The result changes with a Turkish locale, to be what I think you're expecting:
This test should probably exercise the Turkish locale too:
so that the contrast is explicit. (It already does something very similar below on lines 16–17, but it's not obvious to the reader what that means for this "İstanbul" case.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah that makes sense