Skip to content

Commit

Permalink
feat(l10n): add a replaceL10nPlaceholderWithWidget helper
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-anders committed Jul 25, 2024
1 parent 5da8cf4 commit b01a34c
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 7 deletions.
48 changes: 48 additions & 0 deletions lib/src/utils/l10n.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:ui' as ui;

import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lichess_mobile/l10n/l10n.dart';
Expand Down Expand Up @@ -52,3 +53,50 @@ class _LocaleObserver extends WidgetsBindingObserver {
_didChangeLocales(locales);
}
}

/// Takes a localization function (from lib/l10n/l10n.dart) with a single placeholder
/// and replaces the placeholder with the given widget, surrounded by Text widgets if necessary.
///
/// For example:
///
/// ```dart
/// String translationFun(String name) {
/// return 'Hello, ${name}!';
/// }
///
/// // returns [Text('Hello, '), MyFancyWidget('Magnus'), Text('!')]
/// replaceL10nPlaceholderWithWidget(translationFun, MyFancyWidget('Magnus'));
/// ```
///
/// @param l10nWithReplacement The localization function corresponding to a localized string with a placeholder.
/// @param widget The widget to replace the placeholder with.
/// @param testStyle The style to apply to the Text widgets.
///
/// If the placeholder is not present in the localized string,
/// will return a single Text widget with the localized string.
/// If the placeholder is at the beginning or end of the localized string,
/// will return a list with a single Text widget and the widget.
/// Otherwise, will return a list with three Text widgets:
/// the first with the text before the placeholder, the second with the widget,
/// and the third with the text after the placeholder.
IList<Widget> replaceL10nPlaceholderWithWidget<T extends Widget>(
String Function(String) l10nWithReplacement,
T widget, {
TextStyle? textStyle,
}) {
final localizedStringWithPlaceholder = l10nWithReplacement('%s');

final parts = localizedStringWithPlaceholder.split('%s');

// Localized string did not actually contain the placeholder
if (parts[0] == localizedStringWithPlaceholder) {
return [Text(localizedStringWithPlaceholder)].lock;
}

return [
if (parts[0].isNotEmpty) Text(parts[0], style: textStyle),
if (parts[0] != localizedStringWithPlaceholder) widget,
if (parts.length > 1 && parts[1].isNotEmpty)
Text(parts[1], style: textStyle),
].lock;
}
15 changes: 8 additions & 7 deletions lib/src/view/home/home_tab_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:lichess_mobile/src/navigation.dart';
import 'package:lichess_mobile/src/styles/styles.dart';
import 'package:lichess_mobile/src/utils/chessground_compat.dart';
import 'package:lichess_mobile/src/utils/connectivity.dart';
import 'package:lichess_mobile/src/utils/l10n.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
import 'package:lichess_mobile/src/utils/navigation.dart';
import 'package:lichess_mobile/src/utils/screen.dart';
Expand Down Expand Up @@ -507,8 +508,6 @@ class _HelloWidget extends ConsumerWidget {

final user = accountUser ?? session?.user;

final greetingParts = context.l10n.mobileGreeting.split('%s');

return Padding(
padding:
Styles.horizontalBodyPadding.add(Styles.sectionBottomPadding).add(
Expand All @@ -530,11 +529,13 @@ class _HelloWidget extends ConsumerWidget {
color: context.lichessColors.brag,
),
const SizedBox(width: 5.0),
if (user != null && greetingParts.length == 2) ...[
Text(greetingParts[0], style: style),
UserFullNameWidget(user: user, style: style),
Text(greetingParts[1], style: style),
] else
if (user != null)
...replaceL10nPlaceholderWithWidget(
context.l10n.mobileGreeting,
UserFullNameWidget(user: user),
textStyle: style,
)
else
Text(context.l10n.mobileGreetingWithoutName, style: style),
],
),
Expand Down
61 changes: 61 additions & 0 deletions test/utils/l10n_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:lichess_mobile/src/utils/l10n.dart';

void main() {
group('replaceL10nPlaceholderWithWidget', () {
const widget = Text('I am a widget');
test('placeholder in the middle', () {
final widgets = replaceL10nPlaceholderWithWidget(
(_) => 'foo %s bar',
widget,
);
expect(widgets.length, 3);
expect((widgets[0] as Text).data, 'foo ');
expect(widgets[1], widget);
expect((widgets[2] as Text).data, ' bar');
});

test('no placeholder', () {
final widgets = replaceL10nPlaceholderWithWidget(
(_) => 'foo bar',
widget,
);
expect(widgets.length, 1);
expect((widgets.first as Text).data, 'foo bar');
});

test('placeholder at the beginning', () {
final widgets = replaceL10nPlaceholderWithWidget(
(_) => '%s foo bar',
widget,
);
expect(widgets.length, 2);
expect(widgets[0], widget);
expect((widgets[1] as Text).data, ' foo bar');
});

test('placeholder at the end', () {
final widgets = replaceL10nPlaceholderWithWidget(
(_) => 'foo bar %s',
widget,
);
expect(widgets.length, 2);
expect((widgets[0] as Text).data, 'foo bar ');
expect(widgets[1], widget);
});

test('passes textStyle', () {
final widgets = replaceL10nPlaceholderWithWidget(
(_) => 'foo %s bar',
widget,
textStyle: const TextStyle(fontWeight: FontWeight.bold),
);
expect(widgets.length, 3);
expect((widgets[0] as Text).style?.fontWeight, FontWeight.bold);
expect(widgets[1], widget);
expect((widgets[2] as Text).style?.fontWeight, FontWeight.bold);
});
});
}

0 comments on commit b01a34c

Please sign in to comment.