Skip to content

Commit

Permalink
Add ECMA PluralRules to package:intl4x (#720)
Browse files Browse the repository at this point in the history
* Add ECMA `PluralRules` to `package:intl4x`

* Fix conformance

* Switch conformance run to daily
  • Loading branch information
mosuem authored Sep 12, 2023
1 parent 31edac2 commit d3462c5
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
paths:
- '.github/workflows/conformance.yml'
- 'pkgs/intl4x/**'
schedule:
- cron: '0 0 * * *' # daily

jobs:
run_all:
Expand Down
3 changes: 2 additions & 1 deletion pkgs/intl4x/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 0.6.1-wip
## 0.7.0

- Add conformance testing workflow.
- Add ECMA `PluralRules`.

## 0.6.0

Expand Down
8 changes: 4 additions & 4 deletions pkgs/intl4x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ A lightweight modular library for internationalization (i18n) functionality.
We're actively iterating on the API for this package (please provide feedback
via our [issue tracker](https://github.com/dart-lang/i18n/issues)).

| | Number format | List format | Date format | Collation | Display names |
|---|:---:|:---:|:---:|:---:|:---:|
| **ECMA402 (web)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| **ICU4X (web/native)** | | | | | |
| | Number format | List format | Date format | Collation | Display names | Plural Rules |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| **ECMA402 (web)** | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| **ICU4X (web/native)** | | | | | | |

## Implementation and Goals

Expand Down
8 changes: 8 additions & 0 deletions pkgs/intl4x/lib/intl4x.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import 'src/list_format/list_format_impl.dart';
import 'src/list_format/list_format_options.dart';
import 'src/locale/locale.dart';
import 'src/number_format/number_format_impl.dart';
import 'src/plural_rules/plural_rules.dart';
import 'src/plural_rules/plural_rules_impl.dart';
import 'src/plural_rules/plural_rules_options.dart';

export 'src/locale/locale.dart';

Expand Down Expand Up @@ -74,6 +77,11 @@ class Intl {
DateTimeFormatImpl.build(locale, localeMatcher, ecmaPolicy),
);

PluralRules plural([PluralRulesOptions? options]) => PluralRules(
options ?? PluralRulesOptions(),
PluralRulesImpl.build(locale, localeMatcher, ecmaPolicy),
);

/// Construct an [Intl] instance providing the current [locale] and the
/// [ecmaPolicy] defining which locales should fall back to the browser
/// provided functions.
Expand Down
7 changes: 7 additions & 0 deletions pkgs/intl4x/lib/plural_rules.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// 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.

export 'src/options.dart';
export 'src/plural_rules/plural_rules.dart';
export 'src/plural_rules/plural_rules_options.dart';
34 changes: 34 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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 'plural_rules_impl.dart';
import 'plural_rules_options.dart';

class PluralRules {
final PluralRulesOptions _options;
final PluralRulesImpl _pluralRulesImpl;

const PluralRules(this._options, this._pluralRulesImpl);

/// Locale-dependant pluralization, for example in English:
///
/// select(2) == PluralCategory.other
PluralCategory select(num number) {
if (isInTest) {
return PluralCategory.other;
} else {
return _pluralRulesImpl.selectImpl(number, _options);
}
}
}

enum PluralCategory {
zero,
one,
two,
few,
many,
other;
}
19 changes: 19 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules_4x.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 'plural_rules.dart';
import 'plural_rules_impl.dart';
import 'plural_rules_options.dart';

PluralRulesImpl getPluralSelect4X(Locale locale) => PluralRules4X(locale);

class PluralRules4X extends PluralRulesImpl {
PluralRules4X(super.locale);

@override
PluralCategory selectImpl(num number, PluralRulesOptions options) {
throw UnimplementedError('Insert diplomat bindings here');
}
}
95 changes: 95 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules_ecma.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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:js/js.dart';
import 'package:js/js_util.dart';

import '../locale/locale.dart';
import '../options.dart';
import 'plural_rules.dart';
import 'plural_rules_impl.dart';
import 'plural_rules_options.dart';

PluralRulesImpl? getPluralSelectECMA(
Locale locale,
LocaleMatcher localeMatcher,
) =>
_PluralRulesECMA.tryToBuild(locale, localeMatcher);

@JS('Intl.PluralRules')
class PluralRulesJS {
external factory PluralRulesJS([List<String> locale, Object options]);
external String select(num number);
}

@JS('Intl.PluralRules.supportedLocalesOf')
external List<String> supportedLocalesOfJS(
List<String> listOfLocales, [
Object options,
]);

class _PluralRulesECMA extends PluralRulesImpl {
_PluralRulesECMA(super.locales);

static PluralRulesImpl? tryToBuild(
Locale locale,
LocaleMatcher localeMatcher,
) {
final supportedLocales = supportedLocalesOf(locale, localeMatcher);
return supportedLocales.isNotEmpty
? _PluralRulesECMA(supportedLocales.first)
: null;
}

static List<Locale> supportedLocalesOf(
Locale locale,
LocaleMatcher localeMatcher,
) {
final o = newObject<Object>();
setProperty(o, 'localeMatcher', localeMatcher.jsName);
return List.from(supportedLocalesOfJS([locale.toLanguageTag()], o))
.whereType<String>()
.map(Locale.parse)
.toList();
}

@override
PluralCategory selectImpl(num number, PluralRulesOptions options) {
final categoryString =
PluralRulesJS([locale.toLanguageTag()], options.toJsOptions())
.select(number);
return PluralCategory.values
.firstWhere((category) => category.name == categoryString);
}
}

extension on PluralRulesOptions {
Object toJsOptions() {
final o = newObject<Object>();
setProperty(o, 'localeMatcher', localeMatcher.jsName);
setProperty(o, 'type', type.name);
setProperty(o, 'roundingMode', roundingMode.name);
if (digits?.roundingPriority != null) {
setProperty(o, 'roundingPriority', digits?.roundingPriority!.name);
}
if (digits?.roundingIncrement != null) {
setProperty(o, 'roundingIncrement', digits?.roundingIncrement!);
}
setProperty(o, 'minimumIntegerDigits', minimumIntegerDigits);
if (digits?.fractionDigits.$1 != null) {
setProperty(o, 'minimumFractionDigits', digits?.fractionDigits.$1);
}
if (digits?.fractionDigits.$2 != null) {
setProperty(o, 'maximumFractionDigits', digits?.fractionDigits.$2);
}
if (digits?.significantDigits.$1 != null) {
setProperty(o, 'minimumSignificantDigits', digits?.significantDigits.$1);
}
if (digits?.significantDigits.$2 != null) {
setProperty(o, 'maximumSignificantDigits', digits?.significantDigits.$2);
}
setProperty(o, 'trailingZeroDisplay', trailingZeroDisplay.name);
return o;
}
}
34 changes: 34 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules_impl.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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 '../../ecma_policy.dart';
import '../ecma/ecma_policy.dart';
import '../locale/locale.dart';
import '../options.dart';
import '../utils.dart';
import 'plural_rules.dart';
import 'plural_rules_4x.dart';
import 'plural_rules_options.dart';
import 'plural_rules_stub.dart' if (dart.library.js) 'plural_rules_ecma.dart';

abstract class PluralRulesImpl {
final Locale locale;

PluralRulesImpl(this.locale);

PluralCategory selectImpl(num number, PluralRulesOptions options);

factory PluralRulesImpl.build(
Locale locales,
LocaleMatcher localeMatcher,
EcmaPolicy ecmaPolicy,
) =>
buildFormatter(
locales,
localeMatcher,
ecmaPolicy,
getPluralSelectECMA,
getPluralSelect4X,
);
}
55 changes: 55 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules_options.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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 '../../number_format.dart';

typedef ListStyle = Style;

class PluralRulesOptions {
final Type type;
final Digits? digits;
final RoundingMode roundingMode;
final int minimumIntegerDigits;
final TrailingZeroDisplay trailingZeroDisplay;

final LocaleMatcher localeMatcher;

PluralRulesOptions({
this.type = Type.cardinal,
Digits? digits,
this.roundingMode = RoundingMode.halfExpand,
this.minimumIntegerDigits = 1,
this.trailingZeroDisplay = TrailingZeroDisplay.auto,
this.localeMatcher = LocaleMatcher.bestfit,
}) : digits = NumberFormatOptions.getDigits(const DecimalStyle(), digits);

PluralRulesOptions copyWith({
Type? type,
Digits? digits,
RoundingMode? roundingMode,
int? minimumIntegerDigits,
TrailingZeroDisplay? trailingZeroDisplay,
LocaleMatcher? localeMatcher,
}) {
return PluralRulesOptions(
type: type ?? this.type,
digits: digits ?? this.digits,
roundingMode: roundingMode ?? this.roundingMode,
minimumIntegerDigits: minimumIntegerDigits ?? this.minimumIntegerDigits,
trailingZeroDisplay: trailingZeroDisplay ?? this.trailingZeroDisplay,
localeMatcher: localeMatcher ?? this.localeMatcher,
);
}
}

/// The number type to use.
enum Type {
/// For cardinal numbers (referring to the quantity of things): One, two,
/// three, four, five, etc.
cardinal,

/// For ordinal numbers (referring to the ordering or ranking of things):
/// "1st", "2nd", "3rd", etc.
ordinal;
}
13 changes: 13 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules_stub.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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 'plural_rules_impl.dart';

PluralRulesImpl? getPluralSelectECMA(
Locale locale,
LocaleMatcher localeMatcher,
) =>
throw UnimplementedError('Cannot use ECMA outside of web environments.');
2 changes: 1 addition & 1 deletion pkgs/intl4x/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: intl4x
description: >-
A lightweight modular library for internationalization (i18n) functionality.
version: 0.6.1-wip
version: 0.7.0
repository: https://github.com/dart-lang/i18n/tree/main/pkgs/intl4x
platforms: ## TODO: Add native platforms once ICU4X is integrated.
web:
Expand Down
50 changes: 50 additions & 0 deletions pkgs/intl4x/test/ecma/plural_rules_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// 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.

@TestOn('browser')
library;

import 'package:intl4x/intl4x.dart';
import 'package:intl4x/plural_rules.dart';
import 'package:test/test.dart';

import '../utils.dart';

void main() {
testWithFormatting('en-US simple', () {
final numberFormatOptions =
Intl(locale: const Locale(language: 'en', region: 'US'))
.plural(PluralRulesOptions());

expect(numberFormatOptions.select(0), PluralCategory.other);
expect(numberFormatOptions.select(1), PluralCategory.one);
expect(numberFormatOptions.select(2), PluralCategory.other);
expect(numberFormatOptions.select(3), PluralCategory.other);
});

testWithFormatting('ar-EG simple', () {
final numberFormatOptions =
Intl(locale: const Locale(language: 'ar', region: 'EG'))
.plural(PluralRulesOptions());

expect(numberFormatOptions.select(0), PluralCategory.zero);
expect(numberFormatOptions.select(1), PluralCategory.one);
expect(numberFormatOptions.select(2), PluralCategory.two);
expect(numberFormatOptions.select(6), PluralCategory.few);
expect(numberFormatOptions.select(18), PluralCategory.many);
});

testWithFormatting('en-US ordinal', () {
final numberFormatOptions =
Intl(locale: const Locale(language: 'en', region: 'US'))
.plural(PluralRulesOptions(type: Type.ordinal));

expect(numberFormatOptions.select(0), PluralCategory.other);
expect(numberFormatOptions.select(1), PluralCategory.one);
expect(numberFormatOptions.select(2), PluralCategory.two);
expect(numberFormatOptions.select(3), PluralCategory.few);
expect(numberFormatOptions.select(4), PluralCategory.other);
expect(numberFormatOptions.select(21), PluralCategory.one);
});
}
5 changes: 3 additions & 2 deletions pkgs/intl4x/test/tools/conformance_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"icu_version": "icu73",
"exec": "dart_web",
"test_type": [
"coll_shift_short",
"number_fmt"
"collation_short",
"number_fmt",
"likely_subtags"
],
"per_execution": 10000
}
Expand Down

0 comments on commit d3462c5

Please sign in to comment.