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

Sweep through UI strings to internationalize #1148

Merged
merged 14 commits into from
Feb 5, 2025
Merged

Conversation

PIG208
Copy link
Member

@PIG208 PIG208 commented Dec 12, 2024

@gnprice
Copy link
Member

gnprice commented Dec 17, 2024

Thanks!

This is a lot fewer new strings than I'd expected it to be. I guess we've been pretty good about i18n in new code for the past year or so.

I did part of a sweep through git grep '"' lib and git grep "'" lib and found a few more (amongst lots of results in comments and in non-user-facing or otherwise invariant strings):

lib/widgets/content.dart
1576:      TextSpan(text: "(unimplemented:", style: errorStyle),
1578:      TextSpan(text: ")", style: errorStyle),
1582:      TextSpan(text: "(unimplemented: text «", style: errorStyle),
1584:      TextSpan(text: "»)", style: errorStyle),
1588:      text: "(unimplemented: DOM node type ${htmlNode.nodeType})",

lib/widgets/lightbox.dart
391:        semanticsLabel: "Current position"),
421:        semanticsLabel: "Video duration"),

lib/widgets/message_list.dart
429:          return Text("DMs with ${names.join(", ")}"); // TODO show avatars
648:            child: Text("No earlier messages."))); // TODO use an icon
699:        tooltip: "Scroll to bottom",
1147:        .join(", "));

lib/licenses.dart
26:      return '$licenseFileText\n\nAUTHORS file follows:\n\n$authorsFileText';

lib/model/compose.dart
148:  return '${mention(sender!, silent: true)} ${inlineLink('said', url)}: ' // TODO(i18n) ?
149:    '*(loading message ${message.id})*\n'; // TODO(i18n) ?
172:    return '${mention(sender!, silent: true)} ${inlineLink('said', url)}:\n' // TODO(i18n) ?

(Stopping there to switch to another task — the remaining code for that sweep is in the "'" grep, starting from lib/model/content.dart.)

@PIG208
Copy link
Member Author

PIG208 commented Jan 8, 2025

Marking this for review as I think it would be a good checkpoint. I did a sweep with git grep "'" lib | grep -v // | grep -v 'import' and git grep '"' lib | grep -v // | grep -v 'import' (as well as git grep "'" lib | grep // and git grep '"' lib | grep //), and these seem to be the ones left:

lib/widgets/content.dart
1576:      TextSpan(text: "(unimplemented:", style: errorStyle),
1578:      TextSpan(text: ")", style: errorStyle),
1582:      TextSpan(text: "(unimplemented: text «", style: errorStyle),
1584:      TextSpan(text: "»)", style: errorStyle),
1588:      text: "(unimplemented: DOM node type ${htmlNode.nodeType})",

lib/licenses.dart
26:      return '$licenseFileText\n\nAUTHORS file follows:\n\n$authorsFileText';

lib/model/compose.dart
148:  return '${mention(sender!, silent: true)} ${inlineLink('said', url)}: ' // TODO(i18n) ?
149:    '*(loading message ${message.id})*\n'; // TODO(i18n) ?
172:    return '${mention(sender!, silent: true)} ${inlineLink('said', url)}:\n' // TODO(i18n) ?

As well as the .join onese (#1080) that are out-of-scope for #277.

The licenses one can probably be translated with GlobalLocalization; the compose.dart ones involve links; and the unimplemented text uses different styles. Leaving those as follow-ups to the PR.

For commits that are local to a file or a class, I use the more specific tag, otherwise i18n is used. None of them is nfc because there would be an appearance change once the new strings are translated.

@PIG208 PIG208 marked this pull request as ready for review January 8, 2025 16:27
@PIG208 PIG208 requested a review from chrisbobbe January 8, 2025 16:29
@PIG208 PIG208 added the maintainer review PR ready for review by Zulip maintainers label Jan 8, 2025
Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks! Small comments below.

I see some remaining TODO(i18n)s that look relevant for #277; would these be easy to do?

  • Some in lib/model/compose.dart
  • One in lib/notifications/display.dart
  • In poll.dart, TODO(i18n): Localize parenthesis characters—maybe we want to give this string to translators, rather than do it programmatically? This case seems similar to '#$streamName > $topic' which this PR would give to translators.

@@ -618,6 +618,14 @@
"@channelFeedButtonTooltip": {
"description": "Tooltip for button to navigate to a given channel's feed"
},
"notifChannelConversationLabel": "#{channel} > {topic}",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Huh, yeah I guess maybe this could look different in RTL languages, or languages that use a space character other than " "?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah. That will be up to the translator to handle then.

},
"videoCurrentPositionLabel": "Current position",
"@videoCurrentPositionLabel": {
"description": "The current playback position."
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we make this more descriptive of where in the app this string appears? Same with videoDurationLabel.

Copy link
Member Author

@PIG208 PIG208 Jan 15, 2025

Choose a reason for hiding this comment

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

Renaming this to lightboxVideoCurrentPositionLabel and updating the description to The current playback position of the video playing in the lightbox to emphasize that it appears in the lightbox.

},
"noEarlierMessages": "No earlier messages",
"@noEarlierMessages": {
"description": "Text to show at the start of a message list if there is no earlier messages."
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: "if there are no earlier messages"

Comment on lines 237 to 247
"errorOpenLinkTitle": "Unable to open link",
"@errorOpenLinkTitle": {
"description": "Error title when opening a link failed."
},
"errorOpenLinkDetails": "Link could not be opened: {url}",
"@errorOpenLinkDetails": {
"description": "Error details when opening a link failed.",
"placeholders": {
"url": {"type": "String", "example": "https://chat.example.com"}
}
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we have a naming pattern of fooTitle and fooMessage for a pair like this, where one string is for a dialog title and the other is for its message.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like we use fooDialogMessage and fooDialogTitle, but they are not so common (1 and 2 occurences, respectively). Updating the names to use these suffixes instead.

"description": "Display name for the user themself, to show after replying in an Android notification"
"selfUser": "You",
"@selfUser": {
"description": "Display name for the user themself, to show after replying in an Android notification or on an emoji reaction added by themself."
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we keep adding to the description in each new place we use this, it could get pretty long. What do you think about making separate entries for each place in the app that "You" appears? I don't know if that would be helpful for some languages or not.

If keeping as a single entry, maybe keep the description short by not listing any places the string appears, or perhaps saying "for example" and just listing one or two.

Copy link
Member Author

Choose a reason for hiding this comment

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

Having additional context for where each "You" might appear could be helpful, as I find "You" a bit obscure without context, so I'm adding another string.

@PIG208
Copy link
Member Author

PIG208 commented Jan 15, 2025

For the TODOs in lib/model/compose.dart, the reason for skipping those was given in #1148 (comment).

For the one in lib/notifications/display.dart, the string to be translated appears in the Android settings menu, outside of the Flutter app, so it seems to be tricky.

For TODO(i18n): Localize parenthesis characters, it makes sense to have a string for that, which should be straightforward.

@chrisbobbe
Copy link
Collaborator

For the TODOs in lib/model/compose.dart, the reason for skipping those was given in #1148 (comment).

Oops, I seem to have skipped right past that :) thanks for the bump. Looks like that reason is: "the compose.dart ones involve links".

Yeah, for the quote-and-reply string, we really want something like:

{user} <z>said</z>:

where we get handed the translation for "said" and do the Markdown link syntax around that. In zulip-mobile we have a few places like this:

          text={{
            text: 'Suggestion: <z-link>{suggestedServerUrl}</z-link>',
            values: {
              suggestedServerUrl: suggestion,
              'z-link': chunks => (
                <ZulipText
                  inheritFontSize
                  style={styles.suggestionTextLink}
                  onPress={() => handlePressSuggestion(suggestion)}
                >
                  {chunks}
                </ZulipText>
              ),
            },
          }}

but in a quick skim I don't see something like that with the APIs we're using.

For this one, perhaps also worth a mention in the followup issue that we might want to translate this into the org's language, not the user's language.

@chrisbobbe
Copy link
Collaborator

The other string to translate in lib/model/compose.dart is, I think:

(loading message {messageId})

That seems straightforward, and seems appropriate to translate into the user's language rather than the org's language, since it's meant as feedback to the user. Still fine to file as a followup issue :) but it'll be a tiny one.

@chrisbobbe
Copy link
Collaborator

For the one in lib/notifications/display.dart, the string to be translated appears in the Android settings menu, outside of the Flutter app, so it seems to be tricky.

Does GlobalLocalizations have any restrictions on where it can be used? If so, those would be good to document. Adding that documentation could be a followup issue if it's complicated or not yet understood, along with the issue of using it in this file.

If it's available to use there in lib/notifications/display.dart, I guess we can just go for it? 🤷‍♂️

@PIG208
Copy link
Member Author

PIG208 commented Jan 15, 2025

Filed #1285 for said and (unimplemented, and #1284 for notification channel name.

@PIG208 PIG208 force-pushed the dev-l10n branch 2 times, most recently from 90ff1a4 to 652e9a6 Compare January 16, 2025 00:25
@PIG208
Copy link
Member Author

PIG208 commented Jan 16, 2025

Thanks for the review! The PR has been updated. I also went through the existing i18n TODOs, and the outputs of git grep "'" lib | grep -vxFf <(git grep '//' lib) | grep -vxFf <(git grep 'import.*'"'" lib) and git grep '"' lib | grep -vxFf <(git grep '//' lib) again to double-check.

@PIG208 PIG208 linked an issue Jan 21, 2025 that may be closed by this pull request
@chrisbobbe
Copy link
Collaborator

LGTM, thanks! Marking for Greg's review.

@chrisbobbe chrisbobbe assigned gnprice and unassigned chrisbobbe Jan 25, 2025
@chrisbobbe chrisbobbe requested a review from gnprice January 25, 2025 00:30
@chrisbobbe chrisbobbe added integration review Added by maintainers when PR may be ready for integration and removed maintainer review PR ready for review by Zulip maintainers labels Jan 25, 2025
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 taking care of these! And thanks @chrisbobbe for the previous reviews.

Here's an initial review, on the first few commits:
32caefa display [nfc]: Mention issue for notification channel name's i18n TODO
2523d9c i18n [nfc]: Mention issue for translating styled strings
1494908 app: Translate the "about" page button on popup menu
16211c9 inbox: Make header titles localizable

Comment on lines 678 to 680
"noSubscriptions": "No channels found",
"@noSubscriptions": {
"description": "Text to display when there are no subscribed channels."
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
"noSubscriptions": "No channels found",
"@noSubscriptions": {
"description": "Text to display when there are no subscribed channels."
"noSubscriptions": "No channels found",
"@noSubscriptions": {
"description": "Text to display on subscribed-channels page when there are no subscribed channels."

@@ -312,7 +314,8 @@ class _AllDmsHeaderItem extends _HeaderItem {
required super.sectionContext,
});

@override String get title => 'Direct messages'; // TODO(i18n)
@override String title(ZulipLocalizations zulipLocalizations) =>
zulipLocalizations.recentDmConversationsPageTitle;
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 make this a separate string — I feel like it's not a given that the heading in this list will be the same as the title atop the other page.

Comment on lines -81 to -82
// Strings here left unlocalized as they likely will not
// exist in the settled design.
Copy link
Member

Choose a reason for hiding this comment

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

Huh, what was this about?

Copy link
Member Author

@PIG208 PIG208 Jan 31, 2025

Choose a reason for hiding this comment

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

This was from #397, which followed an old design. The comment is no longer accurate as we localize the strings in the PR. It doesn't seem useful to revise this comment, either.

Copy link
Member

Choose a reason for hiding this comment

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

Heh, and in fact the comment's prediction was wrong in the first place: the strings haven't changed at all, except for s/streams/channels/ in one of them.

Anyway, that explanation is good — let's put it in the commit message.

child: Text("No channels found",
child: Text(zulipLocalizations.noSubscriptions,
Copy link
Member

Choose a reason for hiding this comment

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

This feels like a mismatch between the name and the actual string. That risks being confusing when looking at the code and thinking about how it relates to the UI. Let's try subscriptionListNoChannels.

That name starts by scoping where this is found, and then gives the gist of the string's actual content.

@PIG208 PIG208 force-pushed the dev-l10n branch 2 times, most recently from c731c83 to 058a684 Compare January 31, 2025 19:13
@PIG208
Copy link
Member Author

PIG208 commented Jan 31, 2025

Updated the PR, thanks for the 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. A reply above, and here's from reading the next two commits:
d6d85e4 profile: Translate strings on _ProfileErrorPage
e1e35f5 msglist: Localize message list title for "DMs with yourself"

LMK when you've done the pass requested below, and I'll return to review the rest of the branch.

Text('Could not show user profile.'),
const Icon(Icons.error),
const SizedBox(width: 4),
Text(zulipLocalizations.errorShowUserProfile),
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 name this to match the other error strings we have — the name describes the failure, not (or not just) the attempted operation. Here's existing examples:

$ git grep -ohP '^  "error.*?"' assets/l10n/app_en.arb 
  "errorShowUserProfile"
  "errorWebAuthOperationalErrorTitle"
  "errorWebAuthOperationalError"
  "errorAccountLoggedInTitle"
  "errorAccountLoggedIn"
  "errorCouldNotFetchMessageSource"
  "errorCopyingFailed"
  "errorFailedToUploadFileTitle"
  "errorFilesTooLarge"
  "errorFilesTooLargeTitle"
  "errorLoginInvalidInputTitle"
  "errorLoginFailedTitle"
  "errorMessageNotSent"
  "errorLoginCouldNotConnect"
  "errorLoginCouldNotConnectTitle"
  "errorMessageDoesNotSeemToExist"
  "errorQuotationFailed"
  "errorServerMessage"
  "errorConnectingToServerShort"
  "errorConnectingToServerDetails"
  "errorHandlingEventTitle"
  "errorHandlingEventDetails"
  "errorOpenLinkDialogTitle"
  "errorOpenLinkDialogMessage"
  "errorMuteTopicFailed"
  "errorUnmuteTopicFailed"
  "errorFollowTopicFailed"
  "errorUnfollowTopicFailed"
  "errorSharingFailed"
  "errorStarMessageFailedTitle"
  "errorUnstarMessageFailedTitle"
  "errorBannerDeactivatedDmLabel"
  "errorBannerCannotPostInChannelLabel"
  "errorDialogContinue"
  "errorDialogTitle"
  "errorInvalidResponse"
  "errorNetworkRequestFailed"
  "errorMalformedResponse"
  "errorMalformedResponseWithCause"
  "errorRequestFailed"
  "errorVideoPlayerFailed"
  "errorMarkAsReadFailedTitle"
  "errorMarkAsUnreadFailedTitle"
  "errorNotificationOpenTitle"
  "errorNotificationOpenAccountMissing"
  "errorReactionAddingFailedTitle"
  "errorReactionRemovingFailedTitle"

A minimal fix would be errorShowingUserProfile (it's an error in showing a user profile), or errorCouldNotShowUserProfile.

Copy link
Member Author

Choose a reason for hiding this comment

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

Noting the use of -oh to show only the matches. That is helpful for finding naming examples.

Comment on lines 416 to 418
return const Text("DMs with yourself");
return Text(zulipLocalizations.messageListYouWithYourselfTitle);
Copy link
Member

Choose a reason for hiding this comment

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

Would you do a pass through the rest of the branch to find any other places where the string name doesn't align with its value? Similar to #1148 (comment) above, and to this one.

For this one, let's also make the name parallel the existing examples in the other cases of this switch — so dmsWithYourselfPageTitle.

Copy link
Member Author

@PIG208 PIG208 Jan 31, 2025

Choose a reason for hiding this comment

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

messageListDMsWithOthers -> dmsWithOthersPageTitle
errorOpenLinkDialogMessage -> errorCouldNotOpenLinkDialogMessage
errorOpenLinkDialogTitle -> errorCouldNotOpenLinkDialogTitle

I kept "reactedEmojiSelfUser" as-is:

  "reactedEmojiSelfUser": "You",
  "@reactedEmojiSelfUser": {
    "description": "Display name for the user themself, to show on an emoji reaction added by the user."
  },

to match notifSelfUser:

  "notifSelfUser": "You",
  "@notifSelfUser": {
    "description": "Display name for the user themself, to show after replying in an Android notification"
  },

composeBoxLoadingMessage is left as-is to match composeBoxUploadingFilename, but maybe we actually want to end both with a Placeholder suffix?

The remaining string names were unchanged.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks. Replied on specific items in separate threads:
#1148 (comment)
#1148 (comment)

Renamed the "composeBoxUnknownChannelName" string to generalize it.

Signed-off-by: Zixuan James Li <[email protected]>
@PIG208 PIG208 force-pushed the dev-l10n branch 2 times, most recently from bc450e8 to 5110001 Compare February 4, 2025 23:26
@PIG208
Copy link
Member Author

PIG208 commented Feb 4, 2025

Also adding a fixes comment now that we have the follow-up issues left out of scope for #277.

Filed #1285 for said and (unimplemented, and #1284 for notification channel name.

This descriptions were adapted from [VideoPlayerValue].

Signed-off-by: Zixuan James Li <[email protected]>
Ideally we should implement zulip#1080 for DMs title, but we might switch to
showing avatars as the TODO indicated before we work on lists' proper
translation.

Signed-off-by: Zixuan James Li <[email protected]>
and implement the necessary plumbing to access ZulipLocalizations

Signed-off-by: Zixuan James Li <[email protected]>
@gnprice
Copy link
Member

gnprice commented Feb 5, 2025

Thanks! These changes all look good.

Since this is marked now as fixing the whole issue, I went back through the same searches described at #1148 (comment) . Here's one hit that also appeared there:

lib/licenses.dart
26:      return '$licenseFileText\n\nAUTHORS file follows:\n\n$authorsFileText';

Would you fix that, and also look through the rest of the search results from that point on?

Meanwhile I'll merge this but leave out the "Fixes:" for now.

@gnprice gnprice merged commit 9517336 into zulip:main Feb 5, 2025
1 check failed
@PIG208
Copy link
Member Author

PIG208 commented Feb 5, 2025

I went through both queries before the previous push, and didn't find more occurrences since last time.

For the licenses one, we discussed here and decided to leave it as-is. We should probably add a comment before that line.

@PIG208 PIG208 deleted the dev-l10n branch February 5, 2025 00:25
PIG208 added a commit to PIG208/zulip-flutter that referenced this pull request Feb 5, 2025
The '#channel > topic' style strings are not supposed to be translated
into different languages as they are Zulip's language of expressing the
desintation, not something bound to the English language.

See also:
  zulip#1148 (comment)

Signed-off-by: Zixuan James Li <[email protected]>
PIG208 added a commit to PIG208/zulip-flutter that referenced this pull request Feb 5, 2025
The '#channel > topic' style strings are not supposed to be translated
into different languages as they are Zulip's language of expressing the
desintation, not something bound to the English language.

The string needs to be re-translated in other languages, as the
placeholder is now different.

See also:
  zulip#1148 (comment)

Signed-off-by: Zixuan James Li <[email protected]>
PIG208 added a commit to PIG208/zulip-flutter that referenced this pull request Feb 5, 2025
The '#channel > topic' style strings are not supposed to be translated
into different languages as they are Zulip's language of expressing the
desintation, not something bound to the English language.

The string needs to be re-translated in other languages, as the
placeholder is now different.

See also:
  zulip#1148 (comment)

Signed-off-by: Zixuan James Li <[email protected]>
@gnprice
Copy link
Member

gnprice commented Feb 5, 2025

Yep, that should get a comment :-). Thanks.

Cross-linking, here's what I found when I went and completed the grep sweep just now:
#277 (comment)

PIG208 added a commit to PIG208/zulip-flutter that referenced this pull request Feb 6, 2025
The '#channel > topic' style strings are not supposed to be translated
into different languages as they are Zulip's language of expressing the
desintation, not something bound to the English language.

The string needs to be re-translated in other languages, as the
placeholder is now different.

See also:
  zulip#1148 (comment)

Signed-off-by: Zixuan James Li <[email protected]>
PIG208 added a commit to PIG208/zulip-flutter that referenced this pull request Feb 7, 2025
The '#channel > topic' style strings are not supposed to be translated
into different languages as they are Zulip's language of expressing the
desintation, not something bound to the English language.

The string needs to be re-translated in other languages, as the
placeholder is now different.

See also:
  zulip#1148 (comment)

Signed-off-by: Zixuan James Li <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration review Added by maintainers when PR may be ready for integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants