Skip to content

Commit

Permalink
Add ICU4X number formatting to package:intl4x (#791)
Browse files Browse the repository at this point in the history
* Add ICU4X number formatting to package:intl4x

* Add checks to readme

* Working on matching options

* Fix testing

* Add newlines
  • Loading branch information
mosuem authored Feb 6, 2024
1 parent e8e4c18 commit 3c7f76b
Show file tree
Hide file tree
Showing 13 changed files with 328 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@
# Specific files
pkgs/intl/test/number_format_compact_google3_icu_test.dart
pkgs/intl/update_from_cldr_data.sh

.vscode
4 changes: 4 additions & 0 deletions pkgs/intl4x/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.2-wip

- Add ICU4X support for number formatting.

## 0.8.1

- Add ICU4X support for collation.
Expand Down
2 changes: 1 addition & 1 deletion pkgs/intl4x/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ via our [issue tracker](https://github.com/dart-lang/i18n/issues)).
| | 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)** | | | | | | |
| **ICU4X (web/native)** | :heavy_check_mark: | | | :heavy_check_mark: | | |

## Implementation and Goals

Expand Down
3 changes: 3 additions & 0 deletions pkgs/intl4x/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ analyzer:
exclude:
- "submodules/*"
- "lib/src/bindings/*"

enable-experiment:
- native-assets
2 changes: 1 addition & 1 deletion pkgs/intl4x/lib/src/collation/collation_stub_4x.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ import 'collation_impl.dart';
/// Stub for the conditional import
CollationImpl getCollator4X(
Locale locale, Data data, CollationOptions options) =>
throw UnimplementedError('Cannot use ECMA outside of web environments.');
throw UnimplementedError('Cannot use ICU4X in web environments.');
113 changes: 111 additions & 2 deletions pkgs/intl4x/lib/src/number_format/number_format_4x.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
// 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:math';

import '../bindings/lib.g.dart' as icu;
import '../data.dart';
import '../data_4x.dart';
import '../locale/locale.dart';
import '../locale/locale_4x.dart';
import 'number_format_impl.dart';
import 'number_format_options.dart';

Expand All @@ -12,10 +17,114 @@ NumberFormatImpl getNumberFormatter4X(
NumberFormat4X(locale, data, options);

class NumberFormat4X extends NumberFormatImpl {
NumberFormat4X(super.locale, Data data, super.options);
final icu.FixedDecimalFormatter _formatter;
NumberFormat4X(super.locale, Data data, super.options)
: _formatter = icu.FixedDecimalFormatter.withGroupingStrategy(
data.to4X(),
locale.to4X(),
options.groupingStrategy4X(),
);

@override
String formatImpl(Object number) {
throw UnimplementedError('Insert diplomat bindings here');
final fixedDecimal = _toFixedDecimal(number);
final format = _formatter.format(fixedDecimal);
return format;
}

icu.FixedDecimal _toFixedDecimal(Object number) {
final icu.FixedDecimal fixedDecimal;
fixedDecimal = switch (number) {
final int i => icu.FixedDecimal.fromInt(i),
final double d => icu.FixedDecimal.fromDoubleWithDoublePrecision(d),
final String s => icu.FixedDecimal.fromString(s),
Object() => icu.FixedDecimal.fromString(number.toString()),
};
return _constructDouble(fixedDecimal);
}

icu.FixedDecimal _constructDouble(icu.FixedDecimal fixedDecimal) {
fixedDecimal.padStart(options.minimumIntegerDigits);
final minFractionDigits = options.digits?.fractionDigits.$1;
final maxFractionDigits = options.digits?.fractionDigits.$2;
final minSignificantDigits = options.digits?.significantDigits.$1;
final maxSignificantDigits = options.digits?.significantDigits.$2;

final overhead = fixedDecimal.length - (maxSignificantDigits ?? 0);
final maxSignificantPosition = fixedDecimal.magnitudeStart + overhead;
final maxFractionPosition =
max(fixedDecimal.magnitudeStart, -(maxFractionDigits ?? 0));

final roundingPriority = options.digits?.roundingPriority;
final bool? useSignificant;
if (maxFractionDigits != null &&
maxSignificantDigits != null &&
roundingPriority != null) {
useSignificant = switch (roundingPriority) {
RoundingPriority.auto => true,
RoundingPriority.morePrecision =>
maxSignificantPosition < maxFractionPosition,
RoundingPriority.lessPrecision =>
maxSignificantPosition > maxFractionPosition,
};
} else {
useSignificant = null;
}

if (minFractionDigits != null) {
fixedDecimal.padEnd(-minFractionDigits);
}
if (maxFractionDigits != null && !(useSignificant ?? false)) {
final int position;
if (minFractionDigits != null) {
position = min(maxFractionPosition, -minFractionDigits);
} else {
position = maxFractionPosition;
}
_roundDecimal(fixedDecimal, position);
}
if (minSignificantDigits != null &&
fixedDecimal.length < minSignificantDigits) {
final missingZeroes = minSignificantDigits - fixedDecimal.length;
fixedDecimal.padEnd(fixedDecimal.magnitudeStart - missingZeroes);
}
if (maxSignificantDigits != null &&
fixedDecimal.length > maxSignificantDigits &&
(useSignificant ?? true)) {
_roundDecimal(fixedDecimal, maxSignificantPosition);
}
return fixedDecimal;
}

void _roundDecimal(
icu.FixedDecimal fixedDecimal, int maxSignificantPosition) {
final roundingFunction = switch (options.roundingMode) {
RoundingMode.ceil => fixedDecimal.ceil,
RoundingMode.floor => fixedDecimal.floor,
RoundingMode.expand => fixedDecimal.expand,
RoundingMode.trunc => fixedDecimal.trunc,
RoundingMode.halfCeil => fixedDecimal.halfCeil,
RoundingMode.halfFloor => fixedDecimal.halfFloor,
RoundingMode.halfExpand => fixedDecimal.halfExpand,
RoundingMode.halfTrunc => fixedDecimal.halfTrunc,
RoundingMode.halfEven => fixedDecimal.halfEven,
};
roundingFunction(maxSignificantPosition);
}
}

extension on NumberFormatOptions {
icu.FixedDecimalGroupingStrategy groupingStrategy4X() =>
switch (useGrouping) {
Grouping.always => icu.FixedDecimalGroupingStrategy.always,
Grouping.auto => icu.FixedDecimalGroupingStrategy.auto,
Grouping.never => icu.FixedDecimalGroupingStrategy.never,
Grouping.min2 => icu.FixedDecimalGroupingStrategy.min2,
};
}

extension on icu.FixedDecimal {
int get length => fractionLength + integerLength;
int get fractionLength => max(0, -magnitudeStart);
int get integerLength => max(0, magnitudeEnd + 1);
}
3 changes: 2 additions & 1 deletion pkgs/intl4x/lib/src/number_format/number_format_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import '../ecma/ecma_policy.dart';
import '../locale/locale.dart';
import '../options.dart';
import '../utils.dart';
import 'number_format_4x.dart';
import 'number_format_options.dart';
import 'number_format_stub.dart' if (dart.library.js) 'number_format_ecma.dart';
import 'number_format_stub_4x.dart'
if (dart.library.io) 'number_format_4x.dart';

/// This is an intermediate to defer to the actual implementations of
/// Number formatting.
Expand Down
3 changes: 2 additions & 1 deletion pkgs/intl4x/lib/src/number_format/number_format_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ final class Digits {
significantDigits = (null, null),
roundingPriority = null,
assert(roundingIncrement == null ||
((minimum != null || maximum != null) || minimum == maximum));
((minimum != null || maximum != null) || minimum == maximum)),
assert((minimum == null || maximum == null) || minimum < maximum);

const Digits.withSignificantDigits({
int? minimum = 1,
Expand Down
12 changes: 12 additions & 0 deletions pkgs/intl4x/lib/src/number_format/number_format_stub_4x.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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 'number_format_impl.dart';
import 'number_format_options.dart';

NumberFormatImpl getNumberFormatter4X(
Locale locale, Data data, NumberFormatOptions options) =>
throw UnimplementedError('Cannot use ICU4X in web environments.');
2 changes: 1 addition & 1 deletion pkgs/intl4x/lib/src/plural_rules/plural_rules_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ 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';
import 'plural_rules_stub_4x.dart' if (dart.library.io) 'plural_rules_4x.dart';

abstract class PluralRulesImpl {
final Locale locale;
Expand Down
15 changes: 15 additions & 0 deletions pkgs/intl4x/lib/src/plural_rules/plural_rules_stub_4x.dart
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 '../../plural_rules.dart';
import '../data.dart';
import '../locale/locale.dart';
import 'plural_rules_impl.dart';

PluralRulesImpl getPluralSelect4X(
Locale locale,
Data data,
PluralRulesOptions options,
) =>
throw UnimplementedError('Cannot use ICU4X in web environments.');
3 changes: 2 additions & 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.8.1
version: 0.8.2-wip
repository: https://github.com/dart-lang/i18n/tree/main/pkgs/intl4x
platforms: ## TODO: Add native platforms once ICU4X is integrated.
web:
Expand All @@ -18,6 +18,7 @@ dev_dependencies:
args: ^2.4.2
build_runner: ^2.1.4
build_web_compilers: ^3.2.1
collection: ^1.18.0
dart_flutter_team_lints: ^1.0.0
lints: ^2.0.0
native_assets_cli: ^0.3.2
Expand Down
Loading

0 comments on commit 3c7f76b

Please sign in to comment.