Skip to content
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

Merged
merged 17 commits into from
Oct 9, 2023
Merged

Localization sweep #306

merged 17 commits into from
Oct 9, 2023

Conversation

sirpengi
Copy link
Contributor

@sirpengi sirpengi commented Sep 20, 2023

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 the ZulipLocalizations object so they could perform the translation when needed.

@gnprice
Copy link
Member

gnprice commented Sep 21, 2023

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:

  • Let's in general squash the change that adds a given string to the ARB file together with the change that starts consuming it in our code. That provides context when reading either part of the change, from seeing the complementary part of it there in the same commit.

  • Many of these changes are nice and easy and boring, and it's fine to have those squashed together with each other. (Though I'd also be happy to have them split more finely.) But it looks like some are more substantive and interesting — in particular, the changes in lib/api/.

    It'd be good to have a separate commit for those lib/api/ changes, and probably one or more others for the other groups of "more complicated translations" that have the pattern you mention.

@sirpengi
Copy link
Contributor Author

@gnprice the PR has been split and bunched as requested, ready for review

Copy link
Member

@gnprice gnprice left a 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.

///
/// 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.
Copy link
Member

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'),
Copy link
Member

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.

Comment on lines 18 to 19
/// For [ZulipApiException] this may be supplied by the server as the `message`
/// property in the JSON response.
Copy link
Member

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.

Copy link
Contributor Author

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

Comment on lines 16 to 21
/// 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;
Copy link
Member

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.

Comment on lines 31 to 27
String toTranslatedString(ZulipLocalizations zulipLocalizations) {
return (translatableMessage != null)
? translatableMessage!(zulipLocalizations)
: super.toString();
}
Copy link
Member

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.

Copy link
Contributor Author

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)

Comment on lines 46 to 47
String get label;
String Function(ZulipLocalizations) get translateLabel;
Copy link
Member

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.

Comment on lines 82 to 52
"actionSheetShare": "Share",
"@actionSheetShare": {
"description": "Label for share button on action sheet."
Copy link
Member

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.

Comment on lines 58 to 64
"actionSheetCouldNotFetchMessageSource": "Could not fetch message source",
"@actionSheetCouldNotFetchMessageSource": {
"description": "Dialog message when the source of a message could not be fetched."
Copy link
Member

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.

Comment on lines 214 to 216
"dialogButtonOK": "OK",
"@dialogButtonOK": {
"description": "Button label in generic dialogs to confirm."
Copy link
Member

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.

Comment on lines 218 to 224
"dialogButtonCancel": "Cancel",
"@dialogButtonCancel": {
"description": "Button label in generic dialogs to cancel."
},
"dialogButtonContinue": "Continue",
"@dialogButtonContinue": {
"description": "Button label in generic dialogs to continue."
Copy link
Member

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".

@sirpengi sirpengi force-pushed the pr-localization-sweep branch 4 times, most recently from 3cbfccf to 629e84a Compare September 26, 2023 15:47
@sirpengi
Copy link
Contributor Author

@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.

Copy link
Member

@gnprice gnprice left a 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) .

Comment on lines 18 to 19
/// For [ZulipApiException] this is supplied by the server as the `message`
/// property in the JSON response, and is translated into the user's language.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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.

Comment on lines 22 to 26
ApiRequestException({required this.routeName, required this.message});

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray blank line

Comment on lines 18 to 20
"apiConnectionNetworkRequestFailed": "Network request failed",
"@apiConnectionNetworkRequestFailed": {
"description": "Message for error dialog when a network request fails."
Copy link
Member

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.

Comment on lines 340 to 343
"httpStatus": {
"type": "int",
"example": "500"
}
Copy link
Member

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:

Suggested change
"httpStatus": {
"type": "int",
"example": "500"
}
"httpStatus": {"type": "int", "example": "500"}

That'd make it a bit easier to scan through these.

"@composeBoxAttachFromCameraTooltip": {
"description": "Tooltip for compose box icon to attach an image from the camera to the message."
},
"composeBoxSelfDMHint": "Jot down something",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"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.

Comment on lines 294 to 296
"loginValidationPassword": "Please enter your password",
"@loginValidationPassword": {
"description": "Prompt for input for password field."
Copy link
Member

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.

Comment on lines 46 to 51
"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": {
Copy link
Member

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.

Copy link
Member

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?

Comment on lines 34 to 32
"permissionsNeededError": "Error",
"@permissionsNeededError": {
"description": "General title for access dialog when we encounter an unknown error."
Copy link
Member

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.

Copy link
Contributor Author

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.

Comment on lines 88 to 98
"num": {
"type": "int",
"example": "4"
},
"maxFileUploadSizeMib": {
"type": "int",
"example": "15"
},
"listMessage": {
"type": "String",
"example": "foo.txt\nbar.txt"
Copy link
Member

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

Comment on lines 568 to 586
class _AttachFileButton extends _AttachUploadsButton {
const _AttachFileButton({required super.contentController, required super.contentFocusNode});
class _AttachFileButton extends _AttachUploadsButton { const _AttachFileButton({required super.contentController, required super.contentFocusNode});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unintended 🙂

@sirpengi sirpengi force-pushed the pr-localization-sweep branch 3 times, most recently from f042fe4 to 4a87936 Compare September 29, 2023 21:51
@sirpengi
Copy link
Contributor Author

@gnprice ready for another go!

Copy link
Member

@gnprice gnprice left a 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.

Comment on lines 17 to 20
/// property in the JSON response, and is translated into the user's language.
/// property in the JSON response.
final String message;
Copy link
Member

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.

Comment on lines +52 to +53
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
supportedLocales: ZulipLocalizations.supportedLocales,
Copy link
Member

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.)

Copy link
Member

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 :)

Comment on lines 160 to 162
checkRequest((foo: 'bar'), it()
..message
.equals(zulipLocalizations.errorNetworkRequestFailed));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
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 {
Copy link
Member

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

Comment on lines 127 to 133
"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."
Copy link
Member

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".

},
"loginAddAnAccount": "Add an account",
"@loginAddAnAccount": {
"description": "Page title for screen to add a zulip account."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"description": "Page title for screen to add a zulip account."
"description": "Page title for screen to add a Zulip account."

Comment on lines 256 to 258
"loginAddAnAccountConfirm": "Continue",
"@loginAddAnAccountConfirm": {
"description": "Button label to submit form for adding an account."
Copy link
Member

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.

"@loginEmailLabel": {
"description": "Label for input when an email is required to login."
},
"loginErrorMissingEmail": "Please enter your email",
Copy link
Member

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.

"@loginHidePassword": {
"description": "Icon label for button to hide password in input form."
},
"loginEmailLabel": "Email",
Copy link
Member

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;

Copy link
Member

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.)

Copy link
Contributor Author

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.

@sirpengi sirpengi force-pushed the pr-localization-sweep branch 2 times, most recently from 9fd9e65 to da9daa0 Compare October 2, 2023 18:23
@sirpengi
Copy link
Contributor Author

sirpengi commented Oct 2, 2023

@gnprice Thanks for the comments as always. This is ready for review again!

Copy link
Member

@gnprice gnprice left a 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.

Comment on lines +52 to +53
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
supportedLocales: ZulipLocalizations.supportedLocales,
Copy link
Member

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 :)

Comment on lines 237 to 239
"loginAddAnAccount": "Add an account",
"@loginAddAnAccount": {
"description": "Page title for screen to add a Zulip account."
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump -- "page title" :-)

Comment on lines -113 to +119
errorMessage = 'That message does not seem to exist.';
errorMessage = zulipLocalizations.errorMessageDoesNotSeemToExist;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: These three commit messages should have a verb:

8b9ea0f action_sheet: Translations
159c854 dialog: Translations
d4b0cd2 lightbox: Translations

"Add translations", like in several of your other commits, would do nicely.

@sirpengi
Copy link
Contributor Author

sirpengi commented Oct 4, 2023

@gnprice ready again for review!

@gnprice
Copy link
Member

gnprice commented Oct 9, 2023

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.

@gnprice gnprice merged commit 7f464ae into zulip:main Oct 9, 2023
1 check passed
@sirpengi sirpengi deleted the pr-localization-sweep branch January 23, 2024 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants