diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 1060027553..ee7e96c35f 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -56,6 +56,10 @@ "@profileButtonSendDirectMessage": { "description": "Label for button in profile screen to navigate to DMs with the shown user." }, + "errorCouldNotShowUserProfile": "Could not show user profile.", + "@errorCouldNotShowUserProfile": { + "description": "Message that appears on the user profile page when the profile cannot be shown." + }, "permissionsNeededTitle": "Permissions needed", "@permissionsNeededTitle": { "description": "Title for dialog asking the user to grant additional permissions." @@ -230,6 +234,17 @@ "event": {"type": "String", "example": "UpdateMessageEvent(id: 123, messageIds: [2345, 3456], newTopic: 'dinner')"} } }, + "errorCouldNotOpenLinkTitle": "Unable to open link", + "@errorCouldNotOpenLinkTitle": { + "description": "Error title when opening a link failed." + }, + "errorCouldNotOpenLink": "Link could not be opened: {url}", + "@errorCouldNotOpenLink": { + "description": "Error message when opening a link failed.", + "placeholders": { + "url": {"type": "String", "example": "https://chat.example.com"} + } + }, "errorMuteTopicFailed": "Failed to mute topic", "@errorMuteTopicFailed": { "description": "Error message when muting a topic failed." @@ -321,8 +336,8 @@ "@composeBoxSendTooltip": { "description": "Tooltip for send button in compose box." }, - "composeBoxUnknownChannelName": "(unknown channel)", - "@composeBoxUnknownChannelName": { + "unknownChannelName": "(unknown channel)", + "@unknownChannelName": { "description": "Replacement name for channel when it cannot be found in the store." }, "composeBoxTopicHintText": "Topic", @@ -336,10 +351,21 @@ "filename": {"type": "String", "example": "file.txt"} } }, + "composeBoxLoadingMessage": "(loading message {messageId})", + "@composeBoxLoadingMessage": { + "description": "Placeholder in compose box showing the quoted message is currently loading.", + "placeholders": { + "messageId": {"type": "int", "example": "1234"} + } + }, "unknownUserName": "(unknown user)", "@unknownUserName": { "description": "Name placeholder to use for a user when we don't know their name." }, + "dmsWithYourselfPageTitle": "DMs with yourself", + "@dmsWithYourselfPageTitle": { + "description": "Message list page title for a DM group that only includes yourself." + }, "messageListGroupYouAndOthers": "You and {others}", "@messageListGroupYouAndOthers": { "description": "Message list recipient header for a DM group with others.", @@ -347,6 +373,13 @@ "others": {"type": "String", "example": "Alice, Bob"} } }, + "dmsWithOthersPageTitle": "DMs with {others}", + "@dmsWithOthersPageTitle": { + "description": "Message list page title for a DM group with others.", + "placeholders": { + "others": {"type": "String", "example": "Alice, Bob"} + } + }, "messageListGroupYouWithYourself": "You with yourself", "@messageListGroupYouWithYourself": { "description": "Message list recipient header for a DM group that only includes yourself." @@ -395,6 +428,14 @@ "@lightboxCopyLinkTooltip": { "description": "Tooltip in lightbox for the copy link action." }, + "lightboxVideoCurrentPosition": "Current position", + "@lightboxVideoCurrentPosition": { + "description": "The current playback position of the video playing in the lightbox." + }, + "lightboxVideoDuration": "Video duration", + "@lightboxVideoDuration": { + "description": "The total duration of the video playing in the lightbox." + }, "loginPageTitle": "Log in", "@loginPageTitle": { "description": "Title for login page." @@ -586,6 +627,10 @@ "@recentDmConversationsPageTitle": { "description": "Title for the page with a list of DM conversations." }, + "recentDmConversationsSectionHeader": "Direct messages", + "@recentDmConversationsSectionHeader": { + "description": "Heading for direct messages section on the 'Inbox' message view." + }, "combinedFeedPageTitle": "Combined feed", "@combinedFeedPageTitle": { "description": "Page title for the 'Combined feed' message view." @@ -618,10 +663,26 @@ "numOthers": {"type": "int", "example": "4"} } }, + "pinnedSubscriptionsLabel": "Pinned", + "@pinnedSubscriptionsLabel": { + "description": "Label for the list of pinned subscribed channels." + }, + "unpinnedSubscriptionsLabel": "Unpinned", + "@unpinnedSubscriptionsLabel": { + "description": "Label for the list of unpinned subscribed channels." + }, + "subscriptionListNoChannels": "No channels found", + "@subscriptionListNoChannels": { + "description": "Text to display on subscribed-channels page when there are no subscribed channels." + }, "notifSelfUser": "You", "@notifSelfUser": { "description": "Display name for the user themself, to show after replying in an Android notification" }, + "reactedEmojiSelfUser": "You", + "@reactedEmojiSelfUser": { + "description": "Display name for the user themself, to show on an emoji reaction added by the user." + }, "onePersonTyping": "{typist} is typing…", "@onePersonTyping": { "description": "Text to display when there is one user typing.", @@ -685,6 +746,13 @@ "@messageIsMovedLabel": { "description": "Label for a moved message. (Use ALL CAPS for cased alphabets: Latin, Greek, Cyrillic, etc.)" }, + "pollVoterNames": "({voterNames})", + "@pollVoterNames": { + "description": "The list of people who voted for a poll option, wrapped in parentheses.", + "placeholders": { + "voterNames": {"type": "String", "example": "Alice, Bob, Chad"} + } + }, "pollWidgetQuestionMissing": "No question.", "@pollWidgetQuestionMissing": { "description": "Text to display for a poll when the question is missing" @@ -716,5 +784,13 @@ "emojiPickerSearchEmoji": "Search emoji", "@emojiPickerSearchEmoji": { "description": "Hint text for the emoji picker search text field." + }, + "noEarlierMessages": "No earlier messages", + "@noEarlierMessages": { + "description": "Text to show at the start of a message list if there are no earlier messages." + }, + "scrollToBottomTooltip": "Scroll to bottom", + "@scrollToBottomTooltip": { + "description": "Tooltip for button to scroll to bottom." } } diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index 501eb577bf..b6fbb70769 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -189,6 +189,12 @@ abstract class ZulipLocalizations { /// **'Send direct message'** String get profileButtonSendDirectMessage; + /// Message that appears on the user profile page when the profile cannot be shown. + /// + /// In en, this message translates to: + /// **'Could not show user profile.'** + String get errorCouldNotShowUserProfile; + /// Title for dialog asking the user to grant additional permissions. /// /// In en, this message translates to: @@ -405,6 +411,18 @@ abstract class ZulipLocalizations { /// **'Error handling a Zulip event from {serverUrl}; will retry.\n\nError: {error}\n\nEvent: {event}'** String errorHandlingEventDetails(String serverUrl, String error, String event); + /// Error title when opening a link failed. + /// + /// In en, this message translates to: + /// **'Unable to open link'** + String get errorCouldNotOpenLinkTitle; + + /// Error message when opening a link failed. + /// + /// In en, this message translates to: + /// **'Link could not be opened: {url}'** + String errorCouldNotOpenLink(String url); + /// Error message when muting a topic failed. /// /// In en, this message translates to: @@ -535,7 +553,7 @@ abstract class ZulipLocalizations { /// /// In en, this message translates to: /// **'(unknown channel)'** - String get composeBoxUnknownChannelName; + String get unknownChannelName; /// Hint text for topic input widget in compose box. /// @@ -549,18 +567,36 @@ abstract class ZulipLocalizations { /// **'Uploading {filename}…'** String composeBoxUploadingFilename(String filename); + /// Placeholder in compose box showing the quoted message is currently loading. + /// + /// In en, this message translates to: + /// **'(loading message {messageId})'** + String composeBoxLoadingMessage(int messageId); + /// Name placeholder to use for a user when we don't know their name. /// /// In en, this message translates to: /// **'(unknown user)'** String get unknownUserName; + /// Message list page title for a DM group that only includes yourself. + /// + /// In en, this message translates to: + /// **'DMs with yourself'** + String get dmsWithYourselfPageTitle; + /// Message list recipient header for a DM group with others. /// /// In en, this message translates to: /// **'You and {others}'** String messageListGroupYouAndOthers(String others); + /// Message list page title for a DM group with others. + /// + /// In en, this message translates to: + /// **'DMs with {others}'** + String dmsWithOthersPageTitle(String others); + /// Message list recipient header for a DM group that only includes yourself. /// /// In en, this message translates to: @@ -633,6 +669,18 @@ abstract class ZulipLocalizations { /// **'Copy link'** String get lightboxCopyLinkTooltip; + /// The current playback position of the video playing in the lightbox. + /// + /// In en, this message translates to: + /// **'Current position'** + String get lightboxVideoCurrentPosition; + + /// The total duration of the video playing in the lightbox. + /// + /// In en, this message translates to: + /// **'Video duration'** + String get lightboxVideoDuration; + /// Title for login page. /// /// In en, this message translates to: @@ -891,6 +939,12 @@ abstract class ZulipLocalizations { /// **'Direct messages'** String get recentDmConversationsPageTitle; + /// Heading for direct messages section on the 'Inbox' message view. + /// + /// In en, this message translates to: + /// **'Direct messages'** + String get recentDmConversationsSectionHeader; + /// Page title for the 'Combined feed' message view. /// /// In en, this message translates to: @@ -933,12 +987,36 @@ abstract class ZulipLocalizations { /// **'{senderFullName} to you and {numOthers, plural, =1{1 other} other{{numOthers} others}}'** String notifGroupDmConversationLabel(String senderFullName, int numOthers); + /// Label for the list of pinned subscribed channels. + /// + /// In en, this message translates to: + /// **'Pinned'** + String get pinnedSubscriptionsLabel; + + /// Label for the list of unpinned subscribed channels. + /// + /// In en, this message translates to: + /// **'Unpinned'** + String get unpinnedSubscriptionsLabel; + + /// Text to display on subscribed-channels page when there are no subscribed channels. + /// + /// In en, this message translates to: + /// **'No channels found'** + String get subscriptionListNoChannels; + /// Display name for the user themself, to show after replying in an Android notification /// /// In en, this message translates to: /// **'You'** String get notifSelfUser; + /// Display name for the user themself, to show on an emoji reaction added by the user. + /// + /// In en, this message translates to: + /// **'You'** + String get reactedEmojiSelfUser; + /// Text to display when there is one user typing. /// /// In en, this message translates to: @@ -1023,6 +1101,12 @@ abstract class ZulipLocalizations { /// **'MOVED'** String get messageIsMovedLabel; + /// The list of people who voted for a poll option, wrapped in parentheses. + /// + /// In en, this message translates to: + /// **'({voterNames})'** + String pollVoterNames(String voterNames); + /// Text to display for a poll when the question is missing /// /// In en, this message translates to: @@ -1070,6 +1154,18 @@ abstract class ZulipLocalizations { /// In en, this message translates to: /// **'Search emoji'** String get emojiPickerSearchEmoji; + + /// Text to show at the start of a message list if there are no earlier messages. + /// + /// In en, this message translates to: + /// **'No earlier messages'** + String get noEarlierMessages; + + /// Tooltip for button to scroll to bottom. + /// + /// In en, this message translates to: + /// **'Scroll to bottom'** + String get scrollToBottomTooltip; } class _ZulipLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index 721b20ac02..025b4b1444 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'Send direct message'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Permissions needed'; @@ -188,6 +191,14 @@ class ZulipLocalizationsAr extends ZulipLocalizations { return 'Error handling a Zulip event from $serverUrl; will retry.\n\nError: $error\n\nEvent: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Failed to mute topic'; @@ -256,7 +267,7 @@ class ZulipLocalizationsAr extends ZulipLocalizations { String get composeBoxSendTooltip => 'Send'; @override - String get composeBoxUnknownChannelName => '(unknown channel)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Topic'; @@ -266,14 +277,27 @@ class ZulipLocalizationsAr extends ZulipLocalizations { return 'Uploading $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(unknown user)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'You and $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'You with yourself'; @@ -310,6 +334,12 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Copy link'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Log in'; @@ -463,6 +493,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Direct messages'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Combined feed'; @@ -492,9 +525,21 @@ class ZulipLocalizationsAr extends ZulipLocalizations { return '$senderFullName to you and $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'You'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist is typing…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get messageIsMovedLabel => 'MOVED'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'No question.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index 6936cfe736..9467d33428 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'Send direct message'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Permissions needed'; @@ -188,6 +191,14 @@ class ZulipLocalizationsEn extends ZulipLocalizations { return 'Error handling a Zulip event from $serverUrl; will retry.\n\nError: $error\n\nEvent: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Failed to mute topic'; @@ -256,7 +267,7 @@ class ZulipLocalizationsEn extends ZulipLocalizations { String get composeBoxSendTooltip => 'Send'; @override - String get composeBoxUnknownChannelName => '(unknown channel)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Topic'; @@ -266,14 +277,27 @@ class ZulipLocalizationsEn extends ZulipLocalizations { return 'Uploading $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(unknown user)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'You and $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'You with yourself'; @@ -310,6 +334,12 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Copy link'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Log in'; @@ -463,6 +493,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Direct messages'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Combined feed'; @@ -492,9 +525,21 @@ class ZulipLocalizationsEn extends ZulipLocalizations { return '$senderFullName to you and $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'You'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist is typing…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get messageIsMovedLabel => 'MOVED'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'No question.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index c431471645..f363ee0043 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'ダイレクトメッセージを送信'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Permissions needed'; @@ -188,6 +191,14 @@ class ZulipLocalizationsJa extends ZulipLocalizations { return 'Error handling a Zulip event from $serverUrl; will retry.\n\nError: $error\n\nEvent: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Failed to mute topic'; @@ -256,7 +267,7 @@ class ZulipLocalizationsJa extends ZulipLocalizations { String get composeBoxSendTooltip => 'Send'; @override - String get composeBoxUnknownChannelName => '(unknown channel)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Topic'; @@ -266,14 +277,27 @@ class ZulipLocalizationsJa extends ZulipLocalizations { return 'Uploading $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(unknown user)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'You and $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'You with yourself'; @@ -310,6 +334,12 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Copy link'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Log in'; @@ -463,6 +493,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Direct messages'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Combined feed'; @@ -492,9 +525,21 @@ class ZulipLocalizationsJa extends ZulipLocalizations { return '$senderFullName to you and $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'You'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist is typing…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get messageIsMovedLabel => 'MOVED'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'No question.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index fc530fccaa..35b3e86fe5 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'Send direct message'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Permissions needed'; @@ -188,6 +191,14 @@ class ZulipLocalizationsNb extends ZulipLocalizations { return 'Error handling a Zulip event from $serverUrl; will retry.\n\nError: $error\n\nEvent: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Failed to mute topic'; @@ -256,7 +267,7 @@ class ZulipLocalizationsNb extends ZulipLocalizations { String get composeBoxSendTooltip => 'Send'; @override - String get composeBoxUnknownChannelName => '(unknown channel)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Topic'; @@ -266,14 +277,27 @@ class ZulipLocalizationsNb extends ZulipLocalizations { return 'Uploading $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(unknown user)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'You and $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'You with yourself'; @@ -310,6 +334,12 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Copy link'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Log in'; @@ -463,6 +493,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Direct messages'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Combined feed'; @@ -492,9 +525,21 @@ class ZulipLocalizationsNb extends ZulipLocalizations { return '$senderFullName to you and $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'You'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist is typing…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get messageIsMovedLabel => 'MOVED'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'No question.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Search emoji'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index f817d400a8..0594722d31 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'Wyślij wiadomość bezpośrednią'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Wymagane uprawnienia'; @@ -188,6 +191,14 @@ class ZulipLocalizationsPl extends ZulipLocalizations { return 'Błąd zdarzenia Zulip z $serverUrl; ponawiam.\n\nBłąd: $error\n\nZdarzenie: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Wyciszenie bez powodzenia'; @@ -256,7 +267,7 @@ class ZulipLocalizationsPl extends ZulipLocalizations { String get composeBoxSendTooltip => 'Wyślij'; @override - String get composeBoxUnknownChannelName => '(nieznany kanał)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Wątek'; @@ -266,14 +277,27 @@ class ZulipLocalizationsPl extends ZulipLocalizations { return 'Przekazywanie $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(nieznany użytkownik)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'Ty i $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'Ty ze sobą'; @@ -310,6 +334,12 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Skopiuj odnośnik'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Zaloguj'; @@ -463,6 +493,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Wiadomości bezpośrednie'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Mieszany widok'; @@ -492,9 +525,21 @@ class ZulipLocalizationsPl extends ZulipLocalizations { return '$senderFullName do ciebie i $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'Ty'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist coś pisze…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get messageIsMovedLabel => 'PRZENIESIONO'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'Brak pytania.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Szukaj emoji'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index f6d8f1e41c..879559fed4 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'Отправить личное сообщение'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Требуются разрешения'; @@ -188,6 +191,14 @@ class ZulipLocalizationsRu extends ZulipLocalizations { return 'Ошибка обработки события Zulip от $serverUrl; повторим попытку.\n\nОшибка: $error\n\nСобытие: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Не удалось отключить тему'; @@ -256,7 +267,7 @@ class ZulipLocalizationsRu extends ZulipLocalizations { String get composeBoxSendTooltip => 'Отправить'; @override - String get composeBoxUnknownChannelName => '(неизвестный канал)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Тема'; @@ -266,14 +277,27 @@ class ZulipLocalizationsRu extends ZulipLocalizations { return 'Загрузка $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(неизвестный пользователь)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'Вы и $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'Вы с собой'; @@ -310,6 +334,12 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Скопировать ссылку'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Вход в систему'; @@ -463,6 +493,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Личные сообщения'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Объединенная лента'; @@ -492,9 +525,21 @@ class ZulipLocalizationsRu extends ZulipLocalizations { return '$senderFullName вам и еще $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'Вы'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist набирает сообщение…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get messageIsMovedLabel => 'ПЕРЕМЕЩЕНО'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'Нет вопроса.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Поиск эмодзи'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index d6e04126d3..af87dfd949 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -52,6 +52,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get profileButtonSendDirectMessage => 'Poslať priamu správu'; + @override + String get errorCouldNotShowUserProfile => 'Could not show user profile.'; + @override String get permissionsNeededTitle => 'Permissions needed'; @@ -188,6 +191,14 @@ class ZulipLocalizationsSk extends ZulipLocalizations { return 'Chyba obsluhy Zulip udalosti na serveri $serverUrl; skúsim znovu.\n\nChyba: $error\n\nUdalosť: $event'; } + @override + String get errorCouldNotOpenLinkTitle => 'Unable to open link'; + + @override + String errorCouldNotOpenLink(String url) { + return 'Link could not be opened: $url'; + } + @override String get errorMuteTopicFailed => 'Nepodarilo sa ztíšiť tému'; @@ -256,7 +267,7 @@ class ZulipLocalizationsSk extends ZulipLocalizations { String get composeBoxSendTooltip => 'Send'; @override - String get composeBoxUnknownChannelName => '(unknown channel)'; + String get unknownChannelName => '(unknown channel)'; @override String get composeBoxTopicHintText => 'Topic'; @@ -266,14 +277,27 @@ class ZulipLocalizationsSk extends ZulipLocalizations { return 'Uploading $filename…'; } + @override + String composeBoxLoadingMessage(int messageId) { + return '(loading message $messageId)'; + } + @override String get unknownUserName => '(unknown user)'; + @override + String get dmsWithYourselfPageTitle => 'DMs with yourself'; + @override String messageListGroupYouAndOthers(String others) { return 'You and $others'; } + @override + String dmsWithOthersPageTitle(String others) { + return 'DMs with $others'; + } + @override String get messageListGroupYouWithYourself => 'You with yourself'; @@ -310,6 +334,12 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get lightboxCopyLinkTooltip => 'Skopírovať odkaz'; + @override + String get lightboxVideoCurrentPosition => 'Current position'; + + @override + String get lightboxVideoDuration => 'Video duration'; + @override String get loginPageTitle => 'Prihlásiť sa'; @@ -463,6 +493,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get recentDmConversationsPageTitle => 'Priama správa'; + @override + String get recentDmConversationsSectionHeader => 'Direct messages'; + @override String get combinedFeedPageTitle => 'Zlúčený kanál'; @@ -492,9 +525,21 @@ class ZulipLocalizationsSk extends ZulipLocalizations { return '$senderFullName to you and $_temp0'; } + @override + String get pinnedSubscriptionsLabel => 'Pinned'; + + @override + String get unpinnedSubscriptionsLabel => 'Unpinned'; + + @override + String get subscriptionListNoChannels => 'No channels found'; + @override String get notifSelfUser => 'Ty'; + @override + String get reactedEmojiSelfUser => 'You'; + @override String onePersonTyping(String typist) { return '$typist píše…'; @@ -541,6 +586,11 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get messageIsMovedLabel => 'PRESUNUTÉ'; + @override + String pollVoterNames(String voterNames) { + return '($voterNames)'; + } + @override String get pollWidgetQuestionMissing => 'Bez otázky.'; @@ -564,4 +614,10 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get emojiPickerSearchEmoji => 'Hľadať emotikon'; + + @override + String get noEarlierMessages => 'No earlier messages'; + + @override + String get scrollToBottomTooltip => 'Scroll to bottom'; } diff --git a/lib/model/compose.dart b/lib/model/compose.dart index 13b9d59cf5..54c7f6ce00 100644 --- a/lib/model/compose.dart +++ b/lib/model/compose.dart @@ -1,6 +1,7 @@ import 'dart:math'; import '../api/model/model.dart'; +import '../generated/l10n/zulip_localizations.dart'; import 'internal_link.dart'; import 'narrow.dart'; import 'store.dart'; @@ -182,7 +183,9 @@ String inlineLink(String visibleText, Uri? destination) { } /// What we show while fetching the target message's raw Markdown. -String quoteAndReplyPlaceholder(PerAccountStore store, { +String quoteAndReplyPlaceholder( + ZulipLocalizations zulipLocalizations, + PerAccountStore store, { required Message message, }) { final sender = store.users[message.senderId]; @@ -191,8 +194,8 @@ String quoteAndReplyPlaceholder(PerAccountStore store, { SendableNarrow.ofMessage(message, selfUserId: store.selfUserId), nearMessageId: message.id); // See note in [quoteAndReply] about asking `mention` to omit the | part. - return '${userMention(sender!, silent: true)} ${inlineLink('said', url)}: ' // TODO(i18n) ? - '*(loading message ${message.id})*\n'; // TODO(i18n) ? + return '${userMention(sender!, silent: true)} ${inlineLink('said', url)}: ' // TODO(#1285) + '*${zulipLocalizations.composeBoxLoadingMessage(message.id)}*\n'; } /// Quote-and-reply syntax. @@ -215,6 +218,6 @@ String quoteAndReply(PerAccountStore store, { // Could ask `mention` to omit the | part unless the mention is ambiguous… // but that would mean a linear scan through all users, and the extra noise // won't much matter with the already probably-long message link in there too. - return '${userMention(sender!, silent: true)} ${inlineLink('said', url)}:\n' // TODO(i18n) ? + return '${userMention(sender!, silent: true)} ${inlineLink('said', url)}:\n' // TODO(#1285) '${wrapWithBacktickFence(content: rawContent, infoString: 'quote')}'; } diff --git a/lib/notifications/display.dart b/lib/notifications/display.dart index 8319ee65b1..b897530c21 100644 --- a/lib/notifications/display.dart +++ b/lib/notifications/display.dart @@ -204,7 +204,7 @@ class NotificationChannelManager { await _androidHost.createNotificationChannel(NotificationChannel( id: kChannelId, - name: 'Messages', // TODO(i18n) + name: 'Messages', // TODO(#1284) importance: NotificationImportance.high, lightsEnabled: true, soundUrl: defaultSoundUrl, @@ -263,7 +263,7 @@ class NotificationDisplayManager { FcmMessageChannelRecipient(:var streamName?, :var topic) => '#$streamName > ${topic.displayName}', FcmMessageChannelRecipient(:var topic) => - '#(unknown channel) > ${topic.displayName}', // TODO get stream name from data + '#${zulipLocalizations.unknownChannelName} > ${topic.displayName}', // TODO get stream name from data FcmMessageDmRecipient(:var allRecipientIds) when allRecipientIds.length > 2 => zulipLocalizations.notifGroupDmConversationLabel( data.senderFullName, allRecipientIds.length - 2), // TODO use others' names, from data diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index 6a90b61e64..3eff182bca 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -665,7 +665,9 @@ class QuoteAndReplyButton extends MessageActionSheetMenuItemButton { // This inserts a "[Quoting…]" placeholder into the content input, // giving the user a form of progress feedback. final tag = composeBoxController.content - .registerQuoteAndReplyStart(PerAccountStoreWidget.of(pageContext), + .registerQuoteAndReplyStart( + zulipLocalizations, + PerAccountStoreWidget.of(pageContext), message: message, ); diff --git a/lib/widgets/app.dart b/lib/widgets/app.dart index 75fec7bc8b..9525dffdfe 100644 --- a/lib/widgets/app.dart +++ b/lib/widgets/app.dart @@ -323,6 +323,7 @@ class ChooseAccountPageOverflowButton extends StatelessWidget { @override Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); final materialLocalizations = MaterialLocalizations.of(context); return MenuAnchor( menuChildren: [ @@ -330,7 +331,7 @@ class ChooseAccountPageOverflowButton extends StatelessWidget { onPressed: () { Navigator.push(context, AboutZulipPage.buildRoute(context)); }, - child: const Text('About Zulip')), // TODO(i18n) + child: Text(zulipLocalizations.aboutPageTitle)), ], builder: (BuildContext context, MenuController controller, Widget? child) { return IconButton( diff --git a/lib/widgets/compose_box.dart b/lib/widgets/compose_box.dart index 3058425bf1..9ee87754b8 100644 --- a/lib/widgets/compose_box.dart +++ b/lib/widgets/compose_box.dart @@ -266,10 +266,15 @@ class ComposeContentController extends ComposeController /// /// Returns an int "tag" that should be passed to registerQuoteAndReplyEnd on /// success or failure - int registerQuoteAndReplyStart(PerAccountStore store, {required Message message}) { + int registerQuoteAndReplyStart( + ZulipLocalizations zulipLocalizations, + PerAccountStore store, { + required Message message, + }) { final tag = _nextQuoteAndReplyTag; _nextQuoteAndReplyTag += 1; - final placeholder = quoteAndReplyPlaceholder(store, message: message); + final placeholder = quoteAndReplyPlaceholder( + zulipLocalizations, store, message: message); _quoteAndReplies[tag] = (messageId: message.id, placeholder: placeholder); notifyListeners(); // _quoteAndReplies change could affect validationErrors insertPadded(placeholder); @@ -574,7 +579,7 @@ class _StreamContentInputState extends State<_StreamContentInput> { final store = PerAccountStoreWidget.of(context); final zulipLocalizations = ZulipLocalizations.of(context); final streamName = store.streams[widget.narrow.streamId]?.name - ?? zulipLocalizations.composeBoxUnknownChannelName; + ?? zulipLocalizations.unknownChannelName; return _ContentInput( narrow: widget.narrow, destination: TopicNarrow(widget.narrow.streamId, TopicName(_topicTextNormalized)), @@ -636,7 +641,7 @@ class _FixedDestinationContentInput extends StatelessWidget { case TopicNarrow(:final streamId, :final topic): final store = PerAccountStoreWidget.of(context); final streamName = store.streams[streamId]?.name - ?? zulipLocalizations.composeBoxUnknownChannelName; + ?? zulipLocalizations.unknownChannelName; return zulipLocalizations.composeBoxChannelContentHint( streamName, topic.displayName); diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index f3b369e876..02c4f05ca3 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -1319,10 +1319,11 @@ class MessageTableCell extends StatelessWidget { void _launchUrl(BuildContext context, String urlString) async { DialogStatus showError(BuildContext context, String? message) { + final zulipLocalizations = ZulipLocalizations.of(context); return showErrorDialog(context: context, - title: 'Unable to open link', + title: zulipLocalizations.errorCouldNotOpenLinkTitle, message: [ - 'Link could not be opened: $urlString', + zulipLocalizations.errorCouldNotOpenLink(urlString), if (message != null) message, ].join("\n\n")); } @@ -1570,6 +1571,7 @@ InlineSpan _errorUnimplemented(UnimplementedNode node, {required BuildContext co // because release mode isn't yet about general users but developer demos, // and we want to keep the demos honest. // TODO(#194) think through UX for general release + // TODO(#1285) translate this final htmlNode = node.htmlNode; if (htmlNode is dom.Element) { return TextSpan(children: [ diff --git a/lib/widgets/emoji_reaction.dart b/lib/widgets/emoji_reaction.dart index 3d69e3d0d1..de3e2baa26 100644 --- a/lib/widgets/emoji_reaction.dart +++ b/lib/widgets/emoji_reaction.dart @@ -147,6 +147,7 @@ class ReactionChip extends StatelessWidget { @override Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); final reactionType = reactionWithVotes.reactionType; final emojiCode = reactionWithVotes.emojiCode; @@ -160,8 +161,8 @@ class ReactionChip extends StatelessWidget { // // 'Chris、Greg、Alya、Shu' ? userIds.map((id) { return id == store.selfUserId - ? 'You' - : store.users[id]?.fullName ?? '(unknown user)'; // TODO(i18n) + ? zulipLocalizations.reactedEmojiSelfUser + : store.users[id]?.fullName ?? zulipLocalizations.unknownUserName; }).join(', ') : userIds.length.toString(); diff --git a/lib/widgets/inbox.dart b/lib/widgets/inbox.dart index 598b99beac..6dbe31ce04 100644 --- a/lib/widgets/inbox.dart +++ b/lib/widgets/inbox.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../api/model/model.dart'; +import '../generated/l10n/zulip_localizations.dart'; import '../model/narrow.dart'; import '../model/recent_dm_conversations.dart'; import '../model/unreads.dart'; @@ -237,7 +238,7 @@ abstract class _HeaderItem extends StatelessWidget { required this.sectionContext, }); - String get title; + String title(ZulipLocalizations zulipLocalizations); IconData get icon; Color collapsedIconColor(BuildContext context); Color uncollapsedIconColor(BuildContext context); @@ -257,6 +258,7 @@ abstract class _HeaderItem extends StatelessWidget { @override Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); final designVariables = DesignVariables.of(context); return Material( color: collapsed @@ -291,7 +293,7 @@ abstract class _HeaderItem extends StatelessWidget { ).merge(weightVariableTextStyle(context, wght: 600)), maxLines: 1, overflow: TextOverflow.ellipsis, - title))), + title(zulipLocalizations)))), const SizedBox(width: 12), if (hasMention) const _IconMarker(icon: ZulipIcons.at_sign), Padding(padding: const EdgeInsetsDirectional.only(end: 16), @@ -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.recentDmConversationsSectionHeader; @override IconData get icon => ZulipIcons.user; // TODO(design) check if this is the right variable for these @@ -381,16 +384,20 @@ class _DmItem extends StatelessWidget { final store = PerAccountStoreWidget.of(context); final selfUser = store.users[store.selfUserId]!; + final zulipLocalizations = ZulipLocalizations.of(context); final designVariables = DesignVariables.of(context); final title = switch (narrow.otherRecipientIds) { // TODO dedupe with [RecentDmConversationsItem] [] => selfUser.fullName, - [var otherUserId] => store.users[otherUserId]?.fullName ?? '(unknown user)', + [var otherUserId] => + store.users[otherUserId]?.fullName ?? zulipLocalizations.unknownUserName, // TODO(i18n): List formatting, like you can do in JavaScript: // new Intl.ListFormat('ja').format(['Chris', 'Greg', 'Alya', 'Shu']) // // 'Chris、Greg、Alya、Shu' - _ => narrow.otherRecipientIds.map((id) => store.users[id]?.fullName ?? '(unknown user)').join(', '), + _ => narrow.otherRecipientIds.map( + (id) => store.users[id]?.fullName ?? zulipLocalizations.unknownUserName + ).join(', '), }; return Material( @@ -436,7 +443,8 @@ class _StreamHeaderItem extends _HeaderItem { required super.sectionContext, }); - @override String get title => subscription.name; + @override String title(ZulipLocalizations zulipLocalizations) => + subscription.name; @override IconData get icon => iconDataForStream(subscription); @override Color collapsedIconColor(context) => colorSwatchFor(context, subscription).iconOnPlainBackground; diff --git a/lib/widgets/lightbox.dart b/lib/widgets/lightbox.dart index 4635e496f4..1406b5fdbd 100644 --- a/lib/widgets/lightbox.dart +++ b/lib/widgets/lightbox.dart @@ -385,13 +385,14 @@ class _VideoPositionSliderControlState extends State<_VideoPositionSliderControl @override Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); final currentPosition = _isSliderDragging ? _sliderValue : widget.controller.value.position; return Row(children: [ VideoDurationLabel(currentPosition, - semanticsLabel: "Current position"), + semanticsLabel: zulipLocalizations.lightboxVideoCurrentPosition), Expanded( child: Slider( value: currentPosition.inMilliseconds.toDouble(), @@ -421,7 +422,7 @@ class _VideoPositionSliderControlState extends State<_VideoPositionSliderControl ), ), VideoDurationLabel(widget.controller.value.duration, - semanticsLabel: "Video duration"), + semanticsLabel: zulipLocalizations.lightboxVideoDuration), ]); } } diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index bd85c03c05..0d1a936dff 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -335,6 +335,7 @@ class MessageListAppBarTitle extends StatelessWidget { Widget _buildStreamRow(BuildContext context, { ZulipStream? stream, }) { + final zulipLocalizations = ZulipLocalizations.of(context); // A null [Icon.icon] makes a blank space. final icon = stream != null ? iconDataForStream(stream) : null; return Row( @@ -346,7 +347,8 @@ class MessageListAppBarTitle extends StatelessWidget { children: [ Icon(size: 16, icon), const SizedBox(width: 4), - Flexible(child: Text(stream?.name ?? '(unknown channel)')), + Flexible(child: Text( + stream?.name ?? zulipLocalizations.unknownChannelName)), ]); } @@ -413,10 +415,13 @@ class MessageListAppBarTitle extends StatelessWidget { case DmNarrow(:var otherRecipientIds): final store = PerAccountStoreWidget.of(context); if (otherRecipientIds.isEmpty) { - return const Text("DMs with yourself"); + return Text(zulipLocalizations.dmsWithYourselfPageTitle); } else { - final names = otherRecipientIds.map((id) => store.users[id]?.fullName ?? '(unknown user)'); - return Text("DMs with ${names.join(", ")}"); // TODO show avatars + final names = otherRecipientIds.map( + (id) => store.users[id]?.fullName ?? zulipLocalizations.unknownUserName); + // TODO show avatars + return Text( + zulipLocalizations.dmsWithOthersPageTitle(names.join(', '))); } } } @@ -568,6 +573,7 @@ class _MessageListState extends State with PerAccountStoreAwareStat Widget _buildListView(BuildContext context) { final length = model!.items.length; const centerSliverKey = ValueKey('center sliver'); + final zulipLocalizations = ZulipLocalizations.of(context); Widget sliver = SliverStickyHeaderList( headerPlacement: HeaderPlacement.scrollingStart, @@ -604,7 +610,7 @@ class _MessageListState extends State with PerAccountStoreAwareStat if (i == 2) return TypingStatusWidget(narrow: widget.narrow); final data = model!.items[length - 1 - (i - 3)]; - return _buildItem(data, i); + return _buildItem(zulipLocalizations, data, i); })); if (!ComposeBox.hasComposeBox(widget.narrow)) { @@ -640,13 +646,13 @@ class _MessageListState extends State with PerAccountStoreAwareStat ]); } - Widget _buildItem(MessageListItem data, int i) { + Widget _buildItem(ZulipLocalizations zulipLocalizations, MessageListItem data, int i) { switch (data) { case MessageListHistoryStartItem(): - return const Center( + return Center( child: Padding( - padding: EdgeInsets.symmetric(vertical: 16.0), - child: Text("No earlier messages."))); // TODO use an icon + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Text(zulipLocalizations.noEarlierMessages))); // TODO use an icon case MessageListLoadingItem(): return const Center( child: Padding( @@ -690,6 +696,7 @@ class ScrollToBottomButton extends StatelessWidget { @override Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); return ValueListenableBuilder( valueListenable: visibleValue, builder: (BuildContext context, bool value, Widget? child) { @@ -697,7 +704,7 @@ class ScrollToBottomButton extends StatelessWidget { }, // TODO: fix hardcoded values for size and style here child: IconButton( - tooltip: "Scroll to bottom", + tooltip: zulipLocalizations.scrollToBottomTooltip, icon: const Icon(Icons.expand_circle_down_rounded), iconSize: 40, // Web has the same color in light and dark mode. @@ -1030,6 +1037,7 @@ class StreamMessageRecipientHeader extends StatelessWidget { // https://github.com/zulip/zulip-mobile/issues/5511 final store = PerAccountStoreWidget.of(context); final designVariables = DesignVariables.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); final topic = message.topic; @@ -1054,7 +1062,7 @@ class StreamMessageRecipientHeader extends StatelessWidget { final stream = store.streams[message.streamId]; final streamName = stream?.name ?? message.displayRecipient - ?? '(unknown channel)'; // TODO(log) + ?? zulipLocalizations.unknownChannelName; // TODO(log) streamWidget = GestureDetector( onTap: () => Navigator.push(context, diff --git a/lib/widgets/poll.dart b/lib/widgets/poll.dart index 07123fe196..dc340d08b8 100644 --- a/lib/widgets/poll.dart +++ b/lib/widgets/poll.dart @@ -126,8 +126,8 @@ class _PollWidgetState extends State { children: [ Text(option.text, style: textStyleBold.copyWith(fontSize: 16)), if (option.voters.isNotEmpty) - // TODO(i18n): Localize parenthesis characters. - Text('($voterNames)', style: textStyleVoterNames), + Text(zulipLocalizations.pollVoterNames(voterNames), + style: textStyleVoterNames), ]))), ]); } diff --git a/lib/widgets/profile.dart b/lib/widgets/profile.dart index 57dd76a0ab..f38daf792d 100644 --- a/lib/widgets/profile.dart +++ b/lib/widgets/profile.dart @@ -121,17 +121,18 @@ class _ProfileErrorPage extends StatelessWidget { @override Widget build(BuildContext context) { + final zulipLocalizations = ZulipLocalizations.of(context); return Scaffold( - appBar: ZulipAppBar(title: const Text('Error')), - body: const SingleChildScrollView( + appBar: ZulipAppBar(title: Text(zulipLocalizations.errorDialogTitle)), + body: SingleChildScrollView( child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 32), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 32), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.error), - SizedBox(width: 4), - Text('Could not show user profile.'), + const Icon(Icons.error), + const SizedBox(width: 4), + Text(zulipLocalizations.errorCouldNotShowUserProfile), ])))); } } @@ -290,8 +291,9 @@ class _UserWidget extends StatelessWidget { @override Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); final user = store.users[userId]; - final fullName = user?.fullName ?? '(unknown user)'; + final fullName = user?.fullName ?? zulipLocalizations.unknownUserName; return InkWell( onTap: () => Navigator.push(context, ProfilePage.buildRoute(context: context, diff --git a/lib/widgets/recent_dm_conversations.dart b/lib/widgets/recent_dm_conversations.dart index c9d3131591..ddc32861a3 100644 --- a/lib/widgets/recent_dm_conversations.dart +++ b/lib/widgets/recent_dm_conversations.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../generated/l10n/zulip_localizations.dart'; import '../model/narrow.dart'; import '../model/recent_dm_conversations.dart'; import '../model/unreads.dart'; @@ -81,6 +82,7 @@ class RecentDmConversationsItem extends StatelessWidget { final store = PerAccountStoreWidget.of(context); final selfUser = store.users[store.selfUserId]!; + final zulipLocalizations = ZulipLocalizations.of(context); final designVariables = DesignVariables.of(context); final String title; @@ -94,13 +96,15 @@ class RecentDmConversationsItem extends StatelessWidget { // (should we offer a "spam folder" style summary screen of recent // 1:1 DM conversations from muted users?) final otherUser = store.users[otherUserId]; - title = otherUser?.fullName ?? '(unknown user)'; + title = otherUser?.fullName ?? zulipLocalizations.unknownUserName; avatar = AvatarImage(userId: otherUserId, size: _avatarSize); default: // TODO(i18n): List formatting, like you can do in JavaScript: // new Intl.ListFormat('ja').format(['Chris', 'Greg', 'Alya']) // // 'Chris、Greg、Alya' - title = narrow.otherRecipientIds.map((id) => store.users[id]?.fullName ?? '(unknown user)').join(', '); + title = narrow.otherRecipientIds.map( + (id) => store.users[id]?.fullName ?? zulipLocalizations.unknownUserName + ).join(', '); avatar = ColoredBox(color: designVariables.groupDmConversationIconBg, child: Center( child: Icon(color: designVariables.groupDmConversationIcon, diff --git a/lib/widgets/subscription_list.dart b/lib/widgets/subscription_list.dart index 07f9b84c4a..a51e722b51 100644 --- a/lib/widgets/subscription_list.dart +++ b/lib/widgets/subscription_list.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../api/model/model.dart'; +import '../generated/l10n/zulip_localizations.dart'; import '../model/narrow.dart'; import '../model/unreads.dart'; import 'icons.dart'; @@ -77,10 +78,8 @@ class _SubscriptionListPageBodyState extends State wit // TODO: Implement collapsible topics - // TODO(i18n): localize strings on page - // Strings here left unlocalized as they likely will not - // exist in the settled design. final store = PerAccountStoreWidget.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); final List pinned = []; final List unpinned = []; @@ -102,11 +101,11 @@ class _SubscriptionListPageBodyState extends State wit if (pinned.isEmpty && unpinned.isEmpty) const _NoSubscriptionsItem(), if (pinned.isNotEmpty) ...[ - const _SubscriptionListHeader(label: "Pinned"), + _SubscriptionListHeader(label: zulipLocalizations.pinnedSubscriptionsLabel), _SubscriptionList(unreadsModel: unreadsModel, subscriptions: pinned), ], if (unpinned.isNotEmpty) ...[ - const _SubscriptionListHeader(label: "Unpinned"), + _SubscriptionListHeader(label: zulipLocalizations.unpinnedSubscriptionsLabel), _SubscriptionList(unreadsModel: unreadsModel, subscriptions: unpinned), ], @@ -124,11 +123,12 @@ class _NoSubscriptionsItem extends StatelessWidget { @override Widget build(BuildContext context) { final designVariables = DesignVariables.of(context); + final zulipLocalizations = ZulipLocalizations.of(context); return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(10), - child: Text("No channels found", + child: Text(zulipLocalizations.subscriptionListNoChannels, textAlign: TextAlign.center, style: TextStyle( color: designVariables.subscriptionListHeaderText, diff --git a/test/model/compose_test.dart b/test/model/compose_test.dart index ceda0d4cd6..73fa9452d7 100644 --- a/test/model/compose_test.dart +++ b/test/model/compose_test.dart @@ -1,6 +1,7 @@ import 'package:checks/checks.dart'; import 'package:test/scaffolding.dart'; import 'package:zulip/model/compose.dart'; +import 'package:zulip/model/localizations.dart'; import 'package:zulip/model/store.dart'; import '../example_data.dart' as eg; @@ -288,7 +289,8 @@ hello await store.addStream(stream); await store.addUser(sender); - check(quoteAndReplyPlaceholder(store, message: message)).equals(''' + check(quoteAndReplyPlaceholder( + GlobalLocalizations.zulipLocalizations, store, message: message)).equals(''' @_**Full Name|123** [said](${eg.selfAccount.realmUrl}#narrow/stream/1-test-here/topic/some.20topic/near/${message.id}): *(loading message ${message.id})* '''); diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index e6b48384b8..2bb07b4946 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -641,7 +641,8 @@ void main() { }) { check(contentController).value.equals((ComposeContentController() ..value = valueBefore - ..insertPadded(quoteAndReplyPlaceholder(store, message: message)) + ..insertPadded(quoteAndReplyPlaceholder( + GlobalLocalizations.zulipLocalizations, store, message: message)) ).value); check(contentController).validationErrors.contains(ContentValidationError.quoteAndReplyInProgress); }