Skip to content

Commit 891c717

Browse files
committed
intl: replace all hard-coded strings to be wired into localization framework
Fixes: #277
1 parent 65e61f5 commit 891c717

14 files changed

+215
-105
lines changed

lib/api/core.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22
import 'dart:io';
33

4+
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
45
import 'package:http/http.dart' as http;
56

67
import '../log.dart';
@@ -84,15 +85,25 @@ class ApiConnection {
8485
try {
8586
response = await _client.send(request);
8687
} catch (e) {
87-
final String message;
88+
final String? message;
89+
final String Function(ZulipLocalizations)? translatableMessage;
8890
if (e is http.ClientException) {
8991
message = e.message;
92+
translatableMessage = null;
9093
} else if (e is TlsException) {
9194
message = e.message;
95+
translatableMessage = null;
9296
} else {
93-
message = 'Network request failed';
97+
message = null;
98+
translatableMessage = (ZulipLocalizations zulipLocalizations) {
99+
return zulipLocalizations.apiConnectionNetworkRequestFailed;
100+
};
94101
}
95-
throw NetworkException(routeName: routeName, cause: e, message: message);
102+
throw NetworkException(
103+
routeName: routeName,
104+
cause: e,
105+
message: message,
106+
translatableMessage: translatableMessage);
96107
}
97108

98109
final int httpStatus = response.statusCode;
@@ -173,6 +184,7 @@ ApiRequestException _makeApiException(String routeName, int httpStatus, Map<Stri
173184
// TODO(server): `code` should always be present. Get the "Invalid API key" case fixed.
174185
code: json.remove('code') ?? 'BAD_REQUEST',
175186
message: json.remove('msg'),
187+
translatableMessage: null,
176188
data: json,
177189
);
178190
}

lib/api/exception.dart

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11

2+
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
3+
24
/// Some kind of error from a Zulip API network request.
35
sealed class ApiRequestException implements Exception {
46
/// The name of the Zulip API route for the request.
@@ -11,13 +13,26 @@ sealed class ApiRequestException implements Exception {
1113
/// and its route name would be "getMessages".
1214
final String routeName;
1315

14-
/// A user-facing description of the error.
16+
/// Server supplied user-facing description of the error.
17+
///
18+
/// For [ZulipApiException] this may be supplied by the server as the `message`
19+
/// property in the JSON response.
20+
// TODO(i18n): no method yet to translate a server provided string.
21+
final String? message;
22+
23+
/// App supplied user-facing description of the error.
1524
///
16-
/// For [ZulipApiException] this is supplied by the server as the `message`
17-
/// property in the JSON response, and is translated into the user's language.
18-
final String message;
25+
/// This description will be translated according to the
26+
/// current set locale.
27+
final String Function(ZulipLocalizations)? translatableMessage;
28+
29+
ApiRequestException({required this.routeName, required this.message, required this.translatableMessage});
1930

20-
ApiRequestException({required this.routeName, required this.message});
31+
String toTranslatedString(ZulipLocalizations zulipLocalizations) {
32+
return (translatableMessage != null)
33+
? translatableMessage!(zulipLocalizations)
34+
: super.toString();
35+
}
2136
}
2237

2338
/// An error returned through the Zulip server API.
@@ -46,6 +61,7 @@ class ZulipApiException extends ApiRequestException {
4661
required this.httpStatus,
4762
required this.data,
4863
required super.message,
64+
required super.translatableMessage,
4965
}) : assert(400 <= httpStatus && httpStatus <= 499);
5066
}
5167

@@ -58,7 +74,7 @@ class NetworkException extends ApiRequestException {
5874
/// but empirically it can be [TlsException] and possibly others.
5975
final Object cause;
6076

61-
NetworkException({required super.routeName, required super.message, required this.cause});
77+
NetworkException({required super.routeName, required super.message, required super.translatableMessage, required this.cause});
6278
}
6379

6480
/// Some kind of server-side error in handling the request.
@@ -79,6 +95,7 @@ abstract class ServerException extends ApiRequestException {
7995
required this.httpStatus,
8096
required this.data,
8197
required super.message,
98+
required super.translatableMessage,
8299
});
83100
}
84101

@@ -89,7 +106,11 @@ class Server5xxException extends ServerException {
89106
required super.httpStatus,
90107
required super.data,
91108
}) : assert(500 <= httpStatus && httpStatus <= 599),
92-
super(message: 'Network request failed: HTTP status $httpStatus'); // TODO(i18n)
109+
super(
110+
message: null,
111+
translatableMessage: (ZulipLocalizations zulipLocalizations) {
112+
return zulipLocalizations.serverExceptionRequestFailed(httpStatus);
113+
});
93114
}
94115

95116
/// An error where the server's response doesn't match the Zulip API.
@@ -110,5 +131,9 @@ class MalformedServerResponseException extends ServerException {
110131
required super.routeName,
111132
required super.httpStatus,
112133
required super.data,
113-
}) : super(message: 'Server gave malformed response; HTTP status $httpStatus'); // TODO(i18n)
134+
}) : super(
135+
message: null,
136+
translatableMessage: (ZulipLocalizations zulipLocalizations) {
137+
return zulipLocalizations.serverExceptionMalformedResponse(httpStatus);
138+
});
114139
}

lib/widgets/action_sheet.dart

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter/services.dart';
3+
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
34
import 'package:share_plus/share_plus.dart';
45

56
import '../api/exception.dart';
@@ -43,18 +44,19 @@ abstract class MessageActionSheetMenuItemButton extends StatelessWidget {
4344
}) : assert(messageListContext.findAncestorWidgetOfExactType<MessageListPage>() != null);
4445

4546
IconData get icon;
46-
String get label;
47+
String Function(ZulipLocalizations) get translateLabel;
4748
void Function(BuildContext) get onPressed;
4849

4950
final Message message;
5051
final BuildContext messageListContext;
5152

5253
@override
5354
Widget build(BuildContext context) {
55+
final zulipLocalizations = ZulipLocalizations.of(context);
5456
return MenuItemButton(
5557
leadingIcon: Icon(icon),
5658
onPressed: () => onPressed(context),
57-
child: Text(label));
59+
child: Text(translateLabel(zulipLocalizations)));
5860
}
5961
}
6062

@@ -67,7 +69,9 @@ class ShareButton extends MessageActionSheetMenuItemButton {
6769

6870
@override get icon => Icons.adaptive.share;
6971

70-
@override get label => 'Share';
72+
@override get translateLabel => (ZulipLocalizations zulipLocalizations) {
73+
return zulipLocalizations.actionSheetShare;
74+
};
7175

7276
@override get onPressed => (BuildContext context) async {
7377
// Close the message action sheet; we're about to show the share
@@ -104,22 +108,23 @@ Future<String?> fetchRawContentWithFeedback({
104108
// - If request(s) take(s) a long time, show snackbar with cancel
105109
// button, like "Still working on quote-and-reply…".
106110
// On final failure or success, auto-dismiss the snackbar.
111+
final zulipLocalizations = ZulipLocalizations.of(context);
107112
try {
108113
fetchedMessage = await getMessageCompat(PerAccountStoreWidget.of(context).connection,
109114
messageId: messageId,
110115
applyMarkdown: false,
111116
);
112117
if (fetchedMessage == null) {
113-
errorMessage = 'That message does not seem to exist.';
118+
errorMessage = zulipLocalizations.actionSheetMessageDoesNotSeemToExist;
114119
}
115120
} catch (e) {
116121
switch (e) {
117122
case ZulipApiException():
118-
errorMessage = e.message;
123+
errorMessage = e.toTranslatedString(zulipLocalizations);
119124
// TODO specific messages for common errors, like network errors
120125
// (support with reusable code)
121126
default:
122-
errorMessage = 'Could not fetch message source.';
127+
errorMessage = zulipLocalizations.actionSheetCouldNotFetchMessageSource;
123128
}
124129
}
125130

@@ -146,12 +151,15 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
146151

147152
@override get icon => Icons.format_quote_outlined;
148153

149-
@override get label => 'Quote and reply';
154+
@override get translateLabel => (ZulipLocalizations zulipLocalizations) {
155+
return zulipLocalizations.actionSheetQuoteAndReply;
156+
};
150157

151158
@override get onPressed => (BuildContext bottomSheetContext) async {
152159
// Close the message action sheet. We'll show the request progress
153160
// in the compose-box content input with a "[Quoting…]" placeholder.
154161
Navigator.of(bottomSheetContext).pop();
162+
final zulipLocalizations = ZulipLocalizations.of(messageListContext);
155163

156164
// This will be null only if the compose box disappeared after the
157165
// message action sheet opened, and before "Quote and reply" was pressed.
@@ -174,7 +182,7 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton {
174182
final rawContent = await fetchRawContentWithFeedback(
175183
context: messageListContext,
176184
messageId: message.id,
177-
errorDialogTitle: 'Quotation failed',
185+
errorDialogTitle: zulipLocalizations.actionSheetQuotationFailed,
178186
);
179187

180188
if (!messageListContext.mounted) return;
@@ -203,26 +211,29 @@ class CopyButton extends MessageActionSheetMenuItemButton {
203211

204212
@override get icon => Icons.copy;
205213

206-
@override get label => 'Copy message text';
214+
@override get translateLabel => (ZulipLocalizations zulipLocalizations) {
215+
return zulipLocalizations.actionSheetCopyMessageText;
216+
};
207217

208218
@override get onPressed => (BuildContext context) async {
209219
// Close the message action sheet. We won't be showing request progress,
210220
// but hopefully it won't take long at all, and
211221
// fetchRawContentWithFeedback has a TODO for giving feedback if it does.
212222
Navigator.of(context).pop();
223+
final zulipLocalizations = ZulipLocalizations.of(messageListContext);
213224

214225
final rawContent = await fetchRawContentWithFeedback(
215226
context: messageListContext,
216227
messageId: message.id,
217-
errorDialogTitle: 'Copying failed',
228+
errorDialogTitle: zulipLocalizations.actionSheetCopyingFailed,
218229
);
219230

220231
if (rawContent == null) return;
221232

222233
if (!messageListContext.mounted) return;
223234

224-
// TODO(i18n)
225-
copyWithPopup(context: context, successContent: const Text('Message copied'),
235+
copyWithPopup(context: context,
236+
successContent: Text(zulipLocalizations.actionSheetMessageCopied),
226237
data: ClipboardData(text: rawContent));
227238
};
228239
}

0 commit comments

Comments
 (0)