diff --git a/packages/neon_framework/lib/l10n/en.arb b/packages/neon_framework/lib/l10n/en.arb index d699422f29b..76205df4db3 100644 --- a/packages/neon_framework/lib/l10n/en.arb +++ b/packages/neon_framework/lib/l10n/en.arb @@ -184,5 +184,54 @@ "accountOptionsAutomatic": "Automatic", "licenses": "Licenses", "sourceCode": "Source code", - "issueTracker": "Report a bug or request a feature" + "issueTracker": "Report a bug or request a feature", + "relativeTimeNow": "now", + "relativeTimePast": "{time} ago", + "@relativeTimePast": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "relativeTimeFuture": "in {time}", + "@relativeTimeFuture": { + "placeholders": { + "time": { + "type": "String" + } + } + }, + "relativeTimeMinutes": "{time} {time,plural, =1{minute}other{minutes}}", + "@relativeTimeMinutes": { + "placeholders": { + "time": { + "type": "int" + } + } + }, + "relativeTimeHours": "{time} {time,plural, =1{hour}other{hours}}", + "@relativeTimeHours": { + "placeholders": { + "time": { + "type": "int" + } + } + }, + "relativeTimeDays": "{time} {time,plural, =1{day}other{days}}", + "@relativeTimeDays": { + "placeholders": { + "time": { + "type": "int" + } + } + }, + "relativeTimeYears": "{time} {time,plural, =1{year}other{years}}", + "@relativeTimeYears": { + "placeholders": { + "time": { + "type": "int" + } + } + } } diff --git a/packages/neon_framework/lib/l10n/localizations.dart b/packages/neon_framework/lib/l10n/localizations.dart index 52e77fe39cc..32b6193b9cc 100644 --- a/packages/neon_framework/lib/l10n/localizations.dart +++ b/packages/neon_framework/lib/l10n/localizations.dart @@ -712,6 +712,48 @@ abstract class NeonLocalizations { /// In en, this message translates to: /// **'Report a bug or request a feature'** String get issueTracker; + + /// No description provided for @relativeTimeNow. + /// + /// In en, this message translates to: + /// **'now'** + String get relativeTimeNow; + + /// No description provided for @relativeTimePast. + /// + /// In en, this message translates to: + /// **'{time} ago'** + String relativeTimePast(String time); + + /// No description provided for @relativeTimeFuture. + /// + /// In en, this message translates to: + /// **'in {time}'** + String relativeTimeFuture(String time); + + /// No description provided for @relativeTimeMinutes. + /// + /// In en, this message translates to: + /// **'{time} {time,plural, =1{minute}other{minutes}}'** + String relativeTimeMinutes(int time); + + /// No description provided for @relativeTimeHours. + /// + /// In en, this message translates to: + /// **'{time} {time,plural, =1{hour}other{hours}}'** + String relativeTimeHours(int time); + + /// No description provided for @relativeTimeDays. + /// + /// In en, this message translates to: + /// **'{time} {time,plural, =1{day}other{days}}'** + String relativeTimeDays(int time); + + /// No description provided for @relativeTimeYears. + /// + /// In en, this message translates to: + /// **'{time} {time,plural, =1{year}other{years}}'** + String relativeTimeYears(int time); } class _NeonLocalizationsDelegate extends LocalizationsDelegate { diff --git a/packages/neon_framework/lib/l10n/localizations_en.dart b/packages/neon_framework/lib/l10n/localizations_en.dart index 98ac68edb93..cbbf6e04efa 100644 --- a/packages/neon_framework/lib/l10n/localizations_en.dart +++ b/packages/neon_framework/lib/l10n/localizations_en.dart @@ -358,4 +358,61 @@ class NeonLocalizationsEn extends NeonLocalizations { @override String get issueTracker => 'Report a bug or request a feature'; + + @override + String get relativeTimeNow => 'now'; + + @override + String relativeTimePast(String time) { + return '$time ago'; + } + + @override + String relativeTimeFuture(String time) { + return 'in $time'; + } + + @override + String relativeTimeMinutes(int time) { + String _temp0 = intl.Intl.pluralLogic( + time, + locale: localeName, + other: 'minutes', + one: 'minute', + ); + return '$time $_temp0'; + } + + @override + String relativeTimeHours(int time) { + String _temp0 = intl.Intl.pluralLogic( + time, + locale: localeName, + other: 'hours', + one: 'hour', + ); + return '$time $_temp0'; + } + + @override + String relativeTimeDays(int time) { + String _temp0 = intl.Intl.pluralLogic( + time, + locale: localeName, + other: 'days', + one: 'day', + ); + return '$time $_temp0'; + } + + @override + String relativeTimeYears(int time) { + String _temp0 = intl.Intl.pluralLogic( + time, + locale: localeName, + other: 'years', + one: 'year', + ); + return '$time $_temp0'; + } } diff --git a/packages/neon_framework/lib/src/utils/relative_time.dart b/packages/neon_framework/lib/src/utils/relative_time.dart index 13493386758..4dee4948df1 100644 --- a/packages/neon_framework/lib/src/utils/relative_time.dart +++ b/packages/neon_framework/lib/src/utils/relative_time.dart @@ -1,42 +1,45 @@ import 'package:meta/meta.dart'; +import 'package:neon_framework/l10n/localizations.dart'; /// Extension for formatting the difference between two [DateTime]s. @internal extension RelativeTimeFormatDateTime on DateTime { /// Format the relative time between this and [to]. /// - /// If unspecified [DateTime.now] will be used. - String formatRelative([DateTime? to]) => (to ?? DateTime.now()).difference(toLocal()).formatRelative(); + /// If [to] is unspecified [DateTime.now] will be used. + String formatRelative( + NeonLocalizations localizations, [ + DateTime? to, + ]) => + toLocal().difference(to ?? DateTime.now()).formatRelative(localizations); } /// Extension for formatting difference of a [Duration]. @internal extension RelativeTimeFormatDuration on Duration { /// Format the relative time. - String formatRelative() { - var diff = this; - final text = StringBuffer(); - - if (diff.isNegative) { - // Only add minus sign when not showing 'now' - if (diff.inMinutes <= -1) { - text.write('-'); - } - diff = Duration(microseconds: -diff.inMicroseconds); + /// + String formatRelative(NeonLocalizations localizations) { + final normalizedDuration = isNegative ? Duration(microseconds: -inMicroseconds) : this; + if (normalizedDuration.inMinutes < 1) { + return localizations.relativeTimeNow; } - if (diff.inMinutes < 1) { - text.write('now'); - } else if (diff.inHours < 1) { - text.write('${diff.inMinutes}m'); - } else if (diff.inDays < 1) { - text.write('${diff.inHours}h'); - } else if (diff.inDays < 365) { - text.write('${diff.inDays}d'); + String time; + if (normalizedDuration.inHours < 1) { + time = localizations.relativeTimeMinutes(normalizedDuration.inMinutes); + } else if (normalizedDuration.inDays < 1) { + time = localizations.relativeTimeHours(normalizedDuration.inHours); + } else if (normalizedDuration.inDays < 365) { + time = localizations.relativeTimeDays(normalizedDuration.inDays); } else { - text.write('${diff.inDays ~/ 365}y'); + time = localizations.relativeTimeYears(normalizedDuration.inDays ~/ 365); } - return text.toString(); + if (isNegative) { + return localizations.relativeTimePast(time); + } else { + return localizations.relativeTimeFuture(time); + } } } diff --git a/packages/neon_framework/lib/src/widgets/relative_time.dart b/packages/neon_framework/lib/src/widgets/relative_time.dart index 2a43f1a995d..0d47460d846 100644 --- a/packages/neon_framework/lib/src/widgets/relative_time.dart +++ b/packages/neon_framework/lib/src/widgets/relative_time.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; +import 'package:neon_framework/l10n/localizations.dart'; import 'package:neon_framework/src/utils/relative_time.dart'; /// Shows the time elapsed since a [DateTime] and periodically updates itself. @@ -46,7 +47,7 @@ class _RelativeTimeState extends State { @override Widget build(BuildContext context) => Text( - widget.date.formatRelative(), + widget.date.formatRelative(NeonLocalizations.of(context)), style: widget.style, ); } diff --git a/packages/neon_framework/test/relative_time_test.dart b/packages/neon_framework/test/relative_time_test.dart index 6d19fcbd415..3999c062158 100644 --- a/packages/neon_framework/test/relative_time_test.dart +++ b/packages/neon_framework/test/relative_time_test.dart @@ -1,45 +1,48 @@ import 'package:flutter_test/flutter_test.dart'; +import 'package:neon_framework/l10n/localizations_en.dart'; import 'package:neon_framework/src/utils/relative_time.dart'; void main() { + final localizations = NeonLocalizationsEn(); + final durations = { - const Duration(days: -730): '-2y', - const Duration(days: -729): '-1y', - const Duration(days: -365): '-1y', - const Duration(days: -364): '-364d', - const Duration(days: -1): '-1d', - const Duration(hours: -23): '-23h', - const Duration(hours: -1): '-1h', - const Duration(minutes: -59): '-59m', - const Duration(minutes: -1): '-1m', - const Duration(seconds: -59): 'now', - const Duration(seconds: -1): 'now', - const Duration(seconds: 1): 'now', + const Duration(days: 730): 'in 2 years', + const Duration(days: 729): 'in 1 year', + const Duration(days: 365): 'in 1 year', + const Duration(days: 364): 'in 364 days', + const Duration(days: 1): 'in 1 day', + const Duration(hours: 23): 'in 23 hours', + const Duration(hours: 1): 'in 1 hour', + const Duration(minutes: 59): 'in 59 minutes', + const Duration(minutes: 1): 'in 1 minute', const Duration(seconds: 59): 'now', - const Duration(minutes: 1): '1m', - const Duration(minutes: 59): '59m', - const Duration(hours: 1): '1h', - const Duration(hours: 23): '23h', - const Duration(days: 1): '1d', - const Duration(days: 364): '364d', - const Duration(days: 365): '1y', - const Duration(days: 729): '1y', - const Duration(days: 730): '2y', + const Duration(seconds: 1): 'now', + const Duration(seconds: -1): 'now', + const Duration(seconds: -59): 'now', + const Duration(minutes: -1): '1 minute ago', + const Duration(minutes: -59): '59 minutes ago', + const Duration(hours: -1): '1 hour ago', + const Duration(hours: -23): '23 hours ago', + const Duration(days: -1): '1 day ago', + const Duration(days: -364): '364 days ago', + const Duration(days: -365): '1 year ago', + const Duration(days: -729): '1 year ago', + const Duration(days: -730): '2 years ago', }; test('Duration', () { for (final entry in durations.entries) { - expect(entry.key.formatRelative(), entry.value); + expect(entry.key.formatRelative(localizations), entry.value); } }); test('DateTime', () { final base = DateTime.now(); - expect(base.formatRelative(), 'now'); + expect(base.formatRelative(localizations), 'now'); for (final entry in durations.entries) { - expect(base.formatRelative(base.add(entry.key)), entry.value); + expect(base.add(entry.key).formatRelative(localizations, base), entry.value); } }); }