-
Notifications
You must be signed in to change notification settings - Fork 216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Localization sweep #306
Localization sweep #306
Conversation
Thanks! I was preoccupied today with the Sentry-induced crashes in zulip-mobile that we resolved with v27.213, so I didn't get to a detailed review of this. A couple of high-level comments which will make a good first pass:
|
891c717
to
baca2c9
Compare
@gnprice the PR has been split and bunched as requested, ready for review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! That split is very helpful.
Comments below. I read the lib/api/
commit fully, and skimmed through the rest. Several of the points below apply in several places across several commits; taken that way, I think they broadly cover all the feedback I'd have.
lib/api/exception.dart
Outdated
/// | ||
/// For [ZulipApiException] this may be supplied by the server as the `message` | ||
/// property in the JSON response. | ||
// TODO(i18n): no method yet to translate a server provided string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no TODO to be done here, happily; the server-provided string is already translated. See https://zulip.com/api/rest-error-handling .
// TODO(i18n) | ||
copyWithPopup(context: context, successContent: const Text('Text copied'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line is deleted in a commit that doesn't seem otherwise related to this code.
lib/api/exception.dart
Outdated
/// For [ZulipApiException] this may be supplied by the server as the `message` | ||
/// property in the JSON response. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the change from is
to may be
? Seems like it still is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, the may be
applied to the broader ApiRequestConnection
class, where in some cases the message
was supplied by the server, but that is the case specifically of ZulipApiException
lib/api/exception.dart
Outdated
/// Server supplied user-facing description of the error. | ||
/// | ||
/// For [ZulipApiException] this may be supplied by the server as the `message` | ||
/// property in the JSON response. | ||
// TODO(i18n): no method yet to translate a server provided string. | ||
final String? message; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the meaning of this is changing to be more specific, it should get a more specific name to match. serverMessage
would fit this description well.
lib/api/exception.dart
Outdated
String toTranslatedString(ZulipLocalizations zulipLocalizations) { | ||
return (translatableMessage != null) | ||
? translatableMessage!(zulipLocalizations) | ||
: super.toString(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of think that this should become the only public way to read the error message, so that the two fields that back it would become private. It seems like in this design, it'd be a bug if we ever take the plain message
and show that to the user, right? And that'd be a very easy bug to have.
In fact, asking the IDE to show references to the field, it seems like in this version of the PR we already introduce that bug — there's a reference to e.message
in lib/widgets/login.dart
:
final message = (e is ZulipApiException)
? zulipLocalizations.loginServerErrorMessage(e.toTranslatedString(zulipLocalizations))
: e.message;
showErrorDialog(context: context,
title: zulipLocalizations.loginServerLoginFailed,
message: message);
Then the name of this method can also become something simpler, like message
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this (and the previous comment) I've consolidated both into a message
attribute that takes the localizations object (even in the case where we won't run the string through l10n, as when it's supplied by the server)
lib/widgets/action_sheet.dart
Outdated
String get label; | ||
String Function(ZulipLocalizations) get translateLabel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separately, it doesn't seem like this benefits from being a getter, now that it's returning a function — it should just be a method, now taking an argument but still returning a String
.
assets/l10n/app_en.arb
Outdated
"actionSheetShare": "Share", | ||
"@actionSheetShare": { | ||
"description": "Label for share button on action sheet." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the "action sheet" prefix on these names. Perhaps a bit more explicitness to further scope what the context is about: actionSheetOptionShare
.
assets/l10n/app_en.arb
Outdated
"actionSheetCouldNotFetchMessageSource": "Could not fetch message source", | ||
"@actionSheetCouldNotFetchMessageSource": { | ||
"description": "Dialog message when the source of a message could not be fetched." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these error messages, I feel like the relevant context isn't so much "action sheet" as that it's the error message to appear in a dialog box.
As a test for making these naming choices, consider if we had some other place in the UI — somewhere other than an action sheet — where you could do something that needed to fetch the source of a message, and that fetching failed. Would we want the message to be different? I think we'd want it to be the same (and therefore save translators' effort re-translating it, and avoid showing users untranslated versions until they do).
So perhaps errorCouldNotFetchMessageSource
. Potentially could be like errorDialogCouldNotFetchMessageSource
, but I think the "dialog" specificity wouldn't be helpful either — if we show it in a toast/snackbar or something, we'll still want the same message.
assets/l10n/app_en.arb
Outdated
"dialogButtonOK": "OK", | ||
"@dialogButtonOK": { | ||
"description": "Button label in generic dialogs to confirm." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The actual use of this is somewhat more specific — it's in an error dialog, as the only available action, to acknowledge the error and dismiss the dialog.
I think that's a relevant distinction: it's fairly likely that if we had a dialog box with an actual choice, with options like perhaps "OK" / "Cancel", then in some languages the "OK" would be translated differently from the "OK" of the error dialog.
assets/l10n/app_en.arb
Outdated
"dialogButtonCancel": "Cancel", | ||
"@dialogButtonCancel": { | ||
"description": "Button label in generic dialogs to cancel." | ||
}, | ||
"dialogButtonContinue": "Continue", | ||
"@dialogButtonContinue": { | ||
"description": "Button label in generic dialogs to continue." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly these names and descriptions should be a bit more specific.
The function name is showSuggestedActionDialog
. So e.g. "to continue with a suggested action" / "to cancel a suggested action".
3cbfccf
to
629e84a
Compare
@gnprice ready for review again. In addition to your comments, I've consolidated most generic errors into their own prefix if it felt like they could be reused. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revision! Comments below.
Oh and a commit-message nit:
38498c4 msglist [nfc]: update i18n todo with specific issue
ea9c599 clipboard test [nfc]: Remove unnecessary TODO
3e20a17 core: Add translations for exceptions related to api requests
86753c1 action_sheet: translations
5edc7a8 dialog: translations
990548f lightbox: translations
d49c1a6 login: translations
629e84a compose: translations
Use sentence case in the summary lines, same as at #305 (comment) .
lib/api/exception.dart
Outdated
/// For [ZulipApiException] this is supplied by the server as the `message` | ||
/// property in the JSON response, and is translated into the user's language. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// For [ZulipApiException] this is supplied by the server as the `message` | |
/// property in the JSON response, and is translated into the user's language. | |
/// For [ZulipApiException] this is supplied by the server as the `message` | |
/// property in the JSON response. |
The "is translated" part is now covered by the type signature.
lib/api/exception.dart
Outdated
ApiRequestException({required this.routeName, required this.message}); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stray blank line
assets/l10n/app_en.arb
Outdated
"apiConnectionNetworkRequestFailed": "Network request failed", | ||
"@apiConnectionNetworkRequestFailed": { | ||
"description": "Message for error dialog when a network request fails." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's give this and the other two from the same commit the "error" prefix, like in some later commits — I think that works well. So like errorNetworkRequestFailed
.
assets/l10n/app_en.arb
Outdated
"httpStatus": { | ||
"type": "int", | ||
"example": "500" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Can we fit these placeholders on one line each? From browsing the examples so far, I think we can:
"httpStatus": { | |
"type": "int", | |
"example": "500" | |
} | |
"httpStatus": {"type": "int", "example": "500"} |
That'd make it a bit easier to scan through these.
assets/l10n/app_en.arb
Outdated
"@composeBoxAttachFromCameraTooltip": { | ||
"description": "Tooltip for compose box icon to attach an image from the camera to the message." | ||
}, | ||
"composeBoxSelfDMHint": "Jot down something", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"composeBoxSelfDMHint": "Jot down something", | |
"composeBoxSelfDmHint": "Jot down something", |
and similarly for the next few below.
See the definition of camel case linked from the Flutter style guide.
assets/l10n/app_en.arb
Outdated
"loginValidationPassword": "Please enter your password", | ||
"@loginValidationPassword": { | ||
"description": "Prompt for input for password field." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd describe this as stronger than a prompt — it's an error message.
In particular it gets returned from a validator
callback, which is documented (at [FormField.validator]) as:
/// An optional method that validates an input. Returns an error string to
/// display if the input is invalid, or null otherwise.
///
/// The returned value is exposed by the [FormFieldState.errorText] property.
/// The [TextFormField] uses this to override the [InputDecoration.errorText]
/// value.
So the returned value is "an error string", exposed by an errorText
property.
This specific error message is for when the user didn't enter a password in the password field. So could be loginErrorMissingPassword
.
assets/l10n/app_en.arb
Outdated
"permissionsNeededCameraAccessDenied": "To upload an image, please grant Zulip additional permissions in Settings.", | ||
"@permissionsNeededCameraAccessDenied": { | ||
"description": "Message for dialog when the user needs to grant permissions for camera access." | ||
}, | ||
"permissionsReadExternalStorageDenied": "To upload files, please grant Zulip additional permissions in Settings.", | ||
"@permissionsReadExternalStorageDenied": { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be consistent about whether "needed" is a part of their naming prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "needed" does feel kind of redundant with "denied". Perhaps permissionDeniedFooBar
?
assets/l10n/app_en.arb
Outdated
"permissionsNeededError": "Error", | ||
"@permissionsNeededError": { | ||
"description": "General title for access dialog when we encounter an unknown error." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to get used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There were some unreplaced strings this was meant to stand in for, so this is being used now.
assets/l10n/app_en.arb
Outdated
"num": { | ||
"type": "int", | ||
"example": "4" | ||
}, | ||
"maxFileUploadSizeMib": { | ||
"type": "int", | ||
"example": "15" | ||
}, | ||
"listMessage": { | ||
"type": "String", | ||
"example": "foo.txt\nbar.txt" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: make the example data consistent — if it's two filenames, then the number should be 2
lib/widgets/compose_box.dart
Outdated
class _AttachFileButton extends _AttachUploadsButton { | ||
const _AttachFileButton({required super.contentController, required super.contentFocusNode}); | ||
class _AttachFileButton extends _AttachUploadsButton { const _AttachFileButton({required super.contentController, required super.contentFocusNode}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems unintended 🙂
f042fe4
to
4a87936
Compare
@gnprice ready for another go! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revision! This is definitely converging — I think at this point I'm happy with all the structure of the code here.
Comments below. Several of them are places where I spotted a discrepancy between the existing hard-coded string and the new English version. It should be doable to really have zero of those, by rereading through the draft changes; I'd like to get to that point before merge, because that's otherwise a potential way for typos and other errors to sneak into the UI.
Related to that is ways of keeping down the complexity of reviewing these changes. One thing that's contributed to making these changes as readable as they are is how you've generally kept the strings grouped thematically, and ordered within each group either to match the corresponding code or alphabetically; that's been very helpful.
I think the biggest other area that would help there is splitting up the biggest sets of changes into smaller commits. Smaller commits tend to be easier to review in general (so long as they're coherent); and the nature of these changes makes them especially sensitive to commit size, for reasons I explain in the last comment below.
Tha last comment is about one place to split: the biggest commit, which appears to correspond to a squash of the two biggest commits from the previous revision.
And then I think in fact it would help to split those two biggest commits up somewhat further. For example, in compose_box
it could be validations; then the upload/attach flows; then other strings, including buttons and hint text.
lib/api/exception.dart
Outdated
/// property in the JSON response, and is translated into the user's language. | ||
/// property in the JSON response. | ||
final String message; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that this is remaining a String
, the old comment remains helpful.
localizationsDelegates: ZulipLocalizations.localizationsDelegates, | ||
supportedLocales: ZulipLocalizations.supportedLocales, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api: Add translations for exceptions related to api requests
Also hook up some other test suites that require localization
contexts for tests to pass given this change.
These test changes are no longer needed in this commit, right?
(I think they might each be needed in a later commit.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bump — I meant all three of the test changes mentioned in the quoted commit message :)
test/api/core_test.dart
Outdated
checkRequest((foo: 'bar'), it() | ||
..message | ||
.equals(zulipLocalizations.errorNetworkRequestFailed)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
checkRequest((foo: 'bar'), it() | |
..message | |
.equals(zulipLocalizations.errorNetworkRequestFailed)); | |
checkRequest((foo: 'bar'), it() | |
..message.equals(zulipLocalizations.errorNetworkRequestFailed)); |
That's how we usually write this; it's a bit more compact even here, and it's especially helpful when there are several such cascades ..
on the same Subject
.
@@ -0,0 +1,6 @@ | |||
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; | |||
|
|||
abstract final class GlobalLocalizations { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
model: Add GlobalLocalization class for static access to localizations
commit message should match code on name of class
assets/l10n/app_en.arb
Outdated
"errorMessageDoesNotSeemToExist": "That message does not seem to exist.", | ||
"@errorMessageDoesNotSeemToExist": { | ||
"description": "Error message when loading a message that does not exist." | ||
}, | ||
"errorQuotationFailed": "Quotation failed", | ||
"@errorQuotationFailed": { | ||
"description": "Error dialog message when quoting a message failed." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's pick a consistent term for these to use in descriptions — in particular in commit
491f21b action_sheet: Translations
we have "error message", "dialog message", and "error dialog message".
I think "error message" works well. Same reasoning as at #306 (comment) for not specifying "dialog".
assets/l10n/app_en.arb
Outdated
}, | ||
"loginAddAnAccount": "Add an account", | ||
"@loginAddAnAccount": { | ||
"description": "Page title for screen to add a zulip account." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"description": "Page title for screen to add a zulip account." | |
"description": "Page title for screen to add a Zulip account." |
assets/l10n/app_en.arb
Outdated
"loginAddAnAccountConfirm": "Continue", | ||
"@loginAddAnAccountConfirm": { | ||
"description": "Button label to submit form for adding an account." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name and description feel like they're going to puzzle some translators because they seem at variance with the word that makes up the English version.
In the UI where this is used, the reason "Continue" is appropriate is that this is really just one step of a form — it's the page where you enter what server you want to log into, and this button leads to the next page which gives you a choice of how to log in. (Well, for now, it leads straight to the email/password screen, but after #36 it'll offer more options.)
I think it'd make sense to make this more general — if we end up with several screens in different parts of the app that have similar "advance to next screen" flows, we'll want the button to get the same label on each one.
assets/l10n/app_en.arb
Outdated
"@loginEmailLabel": { | ||
"description": "Label for input when an email is required to login." | ||
}, | ||
"loginErrorMissingEmail": "Please enter your email", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This had a final period, so it should keep it (at least as part of a commit like this that's about restructuring things, not changing the visible UI).
Similarly for username and password.
assets/l10n/app_en.arb
Outdated
"@loginHidePassword": { | ||
"description": "Icon label for button to hide password in input form." | ||
}, | ||
"loginEmailLabel": "Email", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was "Email address".
@@ -48,12 +48,12 @@ enum TopicValidationError { | |||
mandatoryButEmpty, | |||
tooLong; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this file happen in a commit that says it's all about login:
login: Translations
---
assets/l10n/app_en.arb | 238 +++++++++++++++++++++++++++++++++++++++++++++--
lib/widgets/compose_box.dart | 115 +++++++++++++----------
lib/widgets/login.dart | 53 ++++++-----
3 files changed, 330 insertions(+), 76 deletions(-)
A previous revision had separate commits for this file and login.dart
, so they must have gotten squashed.
Both files' changes are pretty big — bigger than any of the others in this series, in fact, it looks like — so the combined size does make it noticeably harder to read and review. It'd be good to split them back up.
One fact about the nature of these changes which makes their readability especially sensitive to the size of the commit is that each small piece of the change naturally has two halves, located in widely separated places, which need to match up. So in order to review any given small piece of the change, the reader has to go and look up its other half, and view both of them at the same time (or at a minimum flip back and forth between them). The bigger the commit, the more there is to search through to find that other half; so the total review work is roughly quadratic in the size of the commit.
(If we did keep these squashed, though, we'd definitely want to edit the commit message to reflect its actual contents.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was two commits but got squashed (I believe unintentionally). I've broken it up into further conceptual pieces.
9fd9e65
to
da9daa0
Compare
@gnprice Thanks for the comments as always. This is ready for review again! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @sirpengi for the revision! This is very close now to merge — just a few comments on these changes, all minor. I appreciate the additional splitting-up of the commits; that was quite helpful for reading them.
There are also some small wording tweaks I have for the descriptions, which I can just do as a commit on top at merge time.
The one other comment I have is that this PR shouldn't be marked as closing #277, because there are still some hard-coded English strings left — see in particular git grep TODO.i18n
and git grep 'Text\(['\''"]' lib/
. But let's leave those out of scope for this PR. Perhaps the right approach is that after we merge this, I'll take a turn going through the remaining strings; that will also be good for spreading around our experience with the workflow.
localizationsDelegates: ZulipLocalizations.localizationsDelegates, | ||
supportedLocales: ZulipLocalizations.supportedLocales, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bump — I meant all three of the test changes mentioned in the quoted commit message :)
assets/l10n/app_en.arb
Outdated
"loginAddAnAccount": "Add an account", | ||
"@loginAddAnAccount": { | ||
"description": "Page title for screen to add a Zulip account." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the convention you've used elsewhere of having the name end in "page title" when it's the title of a page. Let's apply that consistently here, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bump -- "page title" :-)
errorMessage = 'That message does not seem to exist.'; | ||
errorMessage = zulipLocalizations.errorMessageDoesNotSeemToExist; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
da9daa0
to
c762e7b
Compare
@gnprice ready again for review! |
c762e7b
to
8f04e4b
Compare
… permission issues
Some grammatical fixes, some clarifications, some small corrections.
Thanks for the revison! Merging, with that added commit on top tweaking some of the descriptions. (And I guess the small last round of review that your last revision was in response to didn't make it into the GitHub thread's main timeline; but it's up at #306 (comment) .) I'll also go add myself as an assignee on the issue #277, and will plan to pick up the remaining part of it soon. |
8f04e4b
to
7f464ae
Compare
I've gone through the code and found any hard-coded strings or other
TODO(i18n)
markers and wired them up to the localization framework.For some more complicated translations that didn't have inherit access to a
BuildContext
(exception messages, or certain widget labels and tooltips) I opted for a pattern of attaching methods that take theZulipLocalizations
object so they could perform the translation when needed.