From 7ef482a2920d32412b5c4d028dd45fda30670beb Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Mon, 20 Feb 2023 10:31:02 +0100 Subject: [PATCH] REFACTOR: Fix pluralized strings in chat plugin (#20357) * FIX: Use pluralized string * REFACTOR: Fix misuse of pluralized string * REFACTOR: Fix misuse of pluralized string * DEV: Remove linting of `one` key in MessageFormat string, it doesn't work * REFACTOR: Fix misuse of pluralized string This also ensures that the URL works on subfolder and shows the site setting link only for admins instead of staff. The string is quite complicated, so the best option was to switch to MessageFormat. * REFACTOR: Fix misuse of pluralized string * FIX: Use pluralized string This also ensures that the URL works on subfolder and shows the site setting link only for admins instead of staff. * REFACTOR: Correctly pluralize reaction tooltips in chat This also ensures that maximum 5 usernames are shown and fixes the number of "others" which was off by 1 if the current user reacted on a message. * REFACTOR: Use translatable string as comma separator * DEV: Add comment to translation to clarify the meaning of `%{identifier}` * REFACTOR: Use translatable comma separator and use explicit interpolation keys * REFACTOR: Don't interpolate lowercase channel status * REFACTOR: Fix misuse of pluralized string * REFACTOR: Don't interpolate channel status * REFACTOR: Use %{count} interpolation key * REFACTOR: Fix misuse of pluralized string * REFACTOR: Correctly pluralize DM chat channel titles --- app/models/translation_override.rb | 3 +- config/locales/client.en.yml | 3 + config/locales/server.en.yml | 4 +- plugins/chat/app/models/chat_channel.rb | 4 - plugins/chat/app/models/chat_message.rb | 4 +- plugins/chat/app/models/direct_message.rb | 11 +- .../serializers/chat_message_serializer.rb | 2 +- .../components/chat-channel-status.js | 25 ++- .../discourse/components/chat-composer.js | 31 ++-- .../components/chat-mention-warnings.js | 114 +++++------- .../components/chat-message-reaction.js | 116 ++++++++---- .../discourse/components/chat-message.js | 68 +++---- .../components/chat-replying-indicator.js | 8 +- .../discourse/controllers/create-channel.js | 94 +++++++--- .../discourse/initializers/chat-sidebar.js | 4 +- .../discourse/models/chat-channel.js | 14 -- plugins/chat/config/locales/client.en.yml | 175 +++++++++++++----- plugins/chat/config/locales/server.en.yml | 34 ++-- plugins/chat/lib/chat_message_creator.rb | 5 +- plugins/chat/lib/chat_message_reactor.rb | 5 +- plugins/chat/lib/chat_message_updater.rb | 5 +- .../lib/direct_message_channel_creator.rb | 16 +- .../components/chat_message_creator_spec.rb | 32 +--- .../components/chat_message_updater_spec.rb | 29 +-- .../direct_message_channel_creator_spec.rb | 2 +- .../chat/spec/models/direct_message_spec.rb | 15 +- .../spec/requests/chat_controller_spec.rb | 6 +- .../incoming_chat_webhooks_controller_spec.rb | 2 +- .../chat/spec/system/closed_channel_spec.rb | 8 +- .../chat/spec/system/create_channel_spec.rb | 2 +- plugins/chat/spec/system/jit_messages_spec.rb | 6 +- plugins/chat/spec/system/read_only_spec.rb | 4 +- script/i18n_lint.rb | 7 - spec/models/translation_override_spec.rb | 4 + .../admin/email_templates_controller_spec.rb | 4 + .../admin/site_texts_controller_spec.rb | 1 + 36 files changed, 499 insertions(+), 368 deletions(-) diff --git a/app/models/translation_override.rb b/app/models/translation_override.rb index 32e45186d11..41334c3c24a 100644 --- a/app/models/translation_override.rb +++ b/app/models/translation_override.rb @@ -138,7 +138,8 @@ class TranslationOverride < ActiveRecord::Base :base, I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", - keys: invalid_keys.join(", "), + keys: invalid_keys.join(I18n.t("word_connector.comma")), + count: invalid_keys.size, ), ) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 2e3fdd52cd2..d855d9c87aa 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -161,6 +161,9 @@ en: email: "Send via email" url: "Copy and share URL" + word_connector: + comma: ", " + action_codes: public_topic: "Made this topic public %{when}" open_topic: "Converted this to a topic %{when}" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 3669228720b..120551cb510 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -641,7 +641,9 @@ en: translation_overrides: attributes: value: - invalid_interpolation_keys: 'The following interpolation key(s) are invalid: "%{keys}"' + invalid_interpolation_keys: + one: 'The following interpolation key is invalid: %{keys}' + other: 'The following interpolation keys are invalid: %{keys}' watched_word: attributes: word: diff --git a/plugins/chat/app/models/chat_channel.rb b/plugins/chat/app/models/chat_channel.rb index beb38e15a8f..35f427a2049 100644 --- a/plugins/chat/app/models/chat_channel.rb +++ b/plugins/chat/app/models/chat_channel.rb @@ -75,10 +75,6 @@ class ChatChannel < ActiveRecord::Base Chat::ChatChannelMembershipManager.new(self).unfollow(user) end - def status_name - I18n.t("chat.channel.statuses.#{self.status}") - end - def url "#{Discourse.base_url}/chat/c/#{self.slug || "-"}/#{self.id}" end diff --git a/plugins/chat/app/models/chat_message.rb b/plugins/chat/app/models/chat_message.rb index 677c14aa222..6b485036a31 100644 --- a/plugins/chat/app/models/chat_message.rb +++ b/plugins/chat/app/models/chat_message.rb @@ -52,7 +52,7 @@ class ChatMessage < ActiveRecord::Base :base, I18n.t( "chat.errors.minimum_length_not_met", - minimum: SiteSetting.chat_minimum_message_length, + count: SiteSetting.chat_minimum_message_length, ), ) end @@ -60,7 +60,7 @@ class ChatMessage < ActiveRecord::Base if message_too_long? self.errors.add( :base, - I18n.t("chat.errors.message_too_long", maximum: SiteSetting.chat_maximum_message_length), + I18n.t("chat.errors.message_too_long", count: SiteSetting.chat_maximum_message_length), ) end end diff --git a/plugins/chat/app/models/direct_message.rb b/plugins/chat/app/models/direct_message.rb index 40ad99c4723..58427608f15 100644 --- a/plugins/chat/app/models/direct_message.rb +++ b/plugins/chat/app/models/direct_message.rb @@ -25,7 +25,7 @@ class DirectMessage < ActiveRecord::Base # direct message to self if users.empty? - return I18n.t("chat.channel.dm_title.single_user", user: "@#{acting_user.username}") + return I18n.t("chat.channel.dm_title.single_user", username: "@#{acting_user.username}") end # all users deleted @@ -36,13 +36,16 @@ class DirectMessage < ActiveRecord::Base return( I18n.t( "chat.channel.dm_title.multi_user_truncated", - users: usernames_formatted[0..4].join(", "), - leftover: usernames_formatted.length - 5, + comma_separated_usernames: usernames_formatted[0..4].join(I18n.t("word_connector.comma")), + count: usernames_formatted.length - 5, ) ) end - I18n.t("chat.channel.dm_title.multi_user", users: usernames_formatted.join(", ")) + I18n.t( + "chat.channel.dm_title.multi_user", + comma_separated_usernames: usernames_formatted.join(I18n.t("word_connector.comma")), + ) end end diff --git a/plugins/chat/app/serializers/chat_message_serializer.rb b/plugins/chat/app/serializers/chat_message_serializer.rb index 66e96535402..9f6dc19a23e 100644 --- a/plugins/chat/app/serializers/chat_message_serializer.rb +++ b/plugins/chat/app/serializers/chat_message_serializer.rb @@ -40,7 +40,7 @@ class ChatMessageSerializer < ApplicationSerializer .reactions .group_by(&:emoji) .each do |emoji, reactions| - users = reactions[0..6].map(&:user).filter { |user| user.id != scope&.user&.id }[0..5] + users = reactions[0..5].map(&:user).filter { |user| user.id != scope&.user&.id }[0..4] next unless Emoji.exists?(emoji) diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-channel-status.js b/plugins/chat/assets/javascripts/discourse/components/chat-channel-status.js index f6048b21b9d..cfbd949cdac 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-channel-status.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-channel-status.js @@ -4,7 +4,6 @@ import Component from "@ember/component"; import { CHANNEL_STATUSES, channelStatusIcon, - channelStatusName, } from "discourse/plugins/chat/discourse/models/chat-channel"; export default Component.extend({ @@ -38,20 +37,28 @@ export default Component.extend({ }, _shortStatusMessage(channelStatus) { - return channelStatusName(channelStatus); + switch (channelStatus) { + case CHANNEL_STATUSES.archived: + return I18n.t("chat.channel_status.archived"); + case CHANNEL_STATUSES.closed: + return I18n.t("chat.channel_status.closed"); + case CHANNEL_STATUSES.open: + return I18n.t("chat.channel_status.open"); + case CHANNEL_STATUSES.readOnly: + return I18n.t("chat.channel_status.read_only"); + } }, _longStatusMessage(channelStatus) { switch (channelStatus) { - case CHANNEL_STATUSES.closed: - return I18n.t("chat.channel_status.closed_header"); - break; - case CHANNEL_STATUSES.readOnly: - return I18n.t("chat.channel_status.read_only_header"); - break; case CHANNEL_STATUSES.archived: return I18n.t("chat.channel_status.archived_header"); - break; + case CHANNEL_STATUSES.closed: + return I18n.t("chat.channel_status.closed_header"); + case CHANNEL_STATUSES.open: + return I18n.t("chat.channel_status.open_header"); + case CHANNEL_STATUSES.readOnly: + return I18n.t("chat.channel_status.read_only_header"); } }, }); diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js index 567df1590cc..315083f6fe2 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-composer.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-composer.js @@ -19,7 +19,6 @@ import { readOnly, reads } from "@ember/object/computed"; import { SKIP } from "discourse/lib/autocomplete"; import { Promise } from "rsvp"; import { translations } from "pretty-text/emoji/data"; -import { channelStatusName } from "discourse/plugins/chat/discourse/models/chat-channel"; import { setupHashtagAutocomplete } from "discourse/lib/hashtag-autocomplete"; import { chatComposerButtons, @@ -566,17 +565,21 @@ export default Component.extend(TextareaTextManipulation, { @discourseComputed("userSilenced", "chatChannel.{chatable.users.[],id}") placeholder(userSilenced, chatChannel) { if (!chatChannel.canModifyMessages(this.currentUser)) { - return I18n.t("chat.placeholder_new_message_disallowed", { - status: channelStatusName(chatChannel.status).toLowerCase(), - }); + return I18n.t( + `chat.placeholder_new_message_disallowed.${chatChannel.status}` + ); } if (chatChannel.isDraft) { - return I18n.t("chat.placeholder_start_conversation", { - usernames: chatChannel?.chatable?.users?.length - ? chatChannel.chatable.users.mapBy("username").join(", ") - : "...", - }); + if (chatChannel?.chatable?.users?.length) { + return I18n.t("chat.placeholder_start_conversation_users", { + usernames: chatChannel.chatable.users + .mapBy("username") + .join(I18n.t("word_connector.comma")), + }); + } else { + return I18n.t("chat.placeholder_start_conversation"); + } } if (userSilenced) { @@ -596,14 +599,14 @@ export default Component.extend(TextareaTextManipulation, { return I18n.t("chat.placeholder_self"); } - return I18n.t("chat.placeholder_others", { - messageRecipient: directMessageRecipients + return I18n.t("chat.placeholder_users", { + commaSeparatedNames: directMessageRecipients .map((u) => u.name || `@${u.username}`) - .join(", "), + .join(I18n.t("word_connector.comma")), }); } else { - return I18n.t("chat.placeholder_others", { - messageRecipient: `#${chatChannel.title}`, + return I18n.t("chat.placeholder_channel", { + channelName: `#${chatChannel.title}`, }); } }, diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-mention-warnings.js b/plugins/chat/assets/javascripts/discourse/components/chat-mention-warnings.js index 8ea0880fab2..24592fec121 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-mention-warnings.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-mention-warnings.js @@ -2,6 +2,7 @@ import Component from "@glimmer/component"; import I18n from "I18n"; import { htmlSafe } from "@ember/template"; import { inject as service } from "@ember/service"; +import getURL from "discourse-common/lib/get-url"; export default class ChatMentionWarnings extends Component { @service siteSettings; @@ -79,31 +80,22 @@ export default class ChatMentionWarnings extends Component { return; } - let notificationLimit = I18n.t( - "chat.mention_warning.groups.notification_limit" - ); - - if (this.currentUser.staff) { - notificationLimit = htmlSafe( - ` - ${notificationLimit} - ` + if (this.currentUser.admin) { + return htmlSafe( + I18n.t("chat.mention_warning.too_many_mentions_admin", { + count: this.siteSettings.max_mentions_per_chat_message, + siteSettingUrl: getURL( + "/admin/site_settings/category/plugins?filter=max_mentions_per_chat_message" + ), + }) + ); + } else { + return htmlSafe( + I18n.t("chat.mention_warning.too_many_mentions", { + count: this.siteSettings.max_mentions_per_chat_message, + }) ); } - - const settingLimit = I18n.t("chat.mention_warning.mentions_limit", { - count: this.siteSettings.max_mentions_per_chat_message, - }); - - return htmlSafe( - I18n.t("chat.mention_warning.too_many_mentions", { - notification_limit: notificationLimit, - limit: settingLimit, - }) - ); } get unreachableBody() { @@ -111,17 +103,21 @@ export default class ChatMentionWarnings extends Component { return; } - if (this.unreachableGroupMentionsCount <= 2) { - return I18n.t("chat.mention_warning.groups.unreachable", { - group: this.unreachableGroupMentions[0], - group_2: this.unreachableGroupMentions[1], - count: this.unreachableGroupMentionsCount, - }); - } else { - return I18n.t("chat.mention_warning.groups.unreachable_multiple", { - group: this.unreachableGroupMentions[0], - count: this.unreachableGroupMentionsCount - 1, //N others - }); + switch (this.unreachableGroupMentionsCount) { + case 1: + return I18n.t("chat.mention_warning.groups.unreachable_1", { + group: this.unreachableGroupMentions[0], + }); + case 2: + return I18n.t("chat.mention_warning.groups.unreachable_2", { + group1: this.unreachableGroupMentions[0], + group2: this.unreachableGroupMentions[1], + }); + default: + return I18n.t("chat.mention_warning.groups.unreachable_multiple", { + group: this.unreachableGroupMentions[0], + count: this.unreachableGroupMentionsCount - 1, + }); } } @@ -130,44 +126,18 @@ export default class ChatMentionWarnings extends Component { return; } - let notificationLimit = I18n.t( - "chat.mention_warning.groups.notification_limit" + return htmlSafe( + I18n.messageFormat("chat.mention_warning.groups.too_many_members_MF", { + groupCount: this.overMembersLimitMentionsCount, + isAdmin: this.currentUser.admin, + siteSettingUrl: getURL( + "/admin/site_settings/category/plugins?filter=max_users_notified_per_group_mention" + ), + notificationLimit: + this.siteSettings.max_users_notified_per_group_mention, + group1: this.overMembersLimitGroupMentions[0], + group2: this.overMembersLimitGroupMentions[1], + }) ); - - if (this.currentUser.staff) { - notificationLimit = htmlSafe( - ` - ${notificationLimit} - ` - ); - } - - const settingLimit = I18n.t("chat.mention_warning.groups.users_limit", { - count: this.siteSettings.max_users_notified_per_group_mention, - }); - - if (this.hasOverMembersLimitGroupMentions <= 2) { - return htmlSafe( - I18n.t("chat.mention_warning.groups.too_many_members", { - group: this.overMembersLimitGroupMentions[0], - group_2: this.overMembersLimitGroupMentions[1], - count: this.overMembersLimitMentionsCount, - notification_limit: notificationLimit, - limit: settingLimit, - }) - ); - } else { - return htmlSafe( - I18n.t("chat.mention_warning.groups.too_many_members_multiple", { - group: this.overMembersLimitGroupMentions[0], - count: this.overMembersLimitMentionsCount - 1, //N others - notification_limit: notificationLimit, - limit: settingLimit, - }) - ); - } } } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js b/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js index a15cdc06b73..748143d2ea3 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.js @@ -83,37 +83,91 @@ export default class ChatMessageReaction extends Component { @computed("reaction") get popoverContent() { - let usernames = this.reaction.users.mapBy("username").join(", "); - if (this.reaction.reacted) { - if (this.reaction.count === 1) { - return I18n.t("chat.reactions.only_you", { - emoji: this.reaction.emoji, - }); - } else if (this.reaction.count > 1 && this.reaction.count < 6) { - return I18n.t("chat.reactions.and_others", { - usernames, - emoji: this.reaction.emoji, - }); - } else if (this.reaction.count >= 6) { - return I18n.t("chat.reactions.you_others_and_more", { - usernames, - emoji: this.reaction.emoji, - more: this.reaction.count - 5, - }); - } - } else { - if (this.reaction.count > 0 && this.reaction.count < 6) { - return I18n.t("chat.reactions.only_others", { - usernames, - emoji: this.reaction.emoji, - }); - } else if (this.reaction.count >= 6) { - return I18n.t("chat.reactions.others_and_more", { - usernames, - emoji: this.reaction.emoji, - more: this.reaction.count - 5, - }); - } + return this.reaction.reacted + ? this._reactionTextWithSelf() + : this._reactionText(); + } + + _reactionTextWithSelf() { + const reactionCount = this.reaction.count; + + if (reactionCount === 0) { + return; } + + if (reactionCount === 1) { + return I18n.t("chat.reactions.only_you", { + emoji: this.reaction.emoji, + }); + } + + const maxUsernames = 4; + const usernames = this.reaction.users + .slice(0, maxUsernames) + .mapBy("username"); + + if (reactionCount === 2) { + return I18n.t("chat.reactions.you_and_single_user", { + emoji: this.reaction.emoji, + username: usernames.pop(), + }); + } + + // `-1` because the current user ("you") isn't included in `usernames` + const unnamedUserCount = reactionCount - usernames.length - 1; + + if (unnamedUserCount > 0) { + return I18n.t("chat.reactions.you_multiple_users_and_more", { + emoji: this.reaction.emoji, + commaSeparatedUsernames: this._joinUsernames(usernames), + count: unnamedUserCount, + }); + } + + return I18n.t("chat.reactions.you_and_multiple_users", { + emoji: this.reaction.emoji, + username: usernames.pop(), + commaSeparatedUsernames: this._joinUsernames(usernames), + }); + } + + _reactionText() { + const reactionCount = this.reaction.count; + + if (reactionCount === 0) { + return; + } + + const maxUsernames = 5; + const usernames = this.reaction.users + .slice(0, maxUsernames) + .mapBy("username"); + + if (reactionCount === 1) { + return I18n.t("chat.reactions.single_user", { + emoji: this.reaction.emoji, + username: usernames.pop(), + }); + } + + const unnamedUserCount = reactionCount - usernames.length; + + if (unnamedUserCount > 0) { + return I18n.t("chat.reactions.multiple_users_and_more", { + emoji: this.reaction.emoji, + commaSeparatedUsernames: this._joinUsernames(usernames), + count: unnamedUserCount, + }); + } + + return I18n.t("chat.reactions.multiple_users", { + emoji: this.reaction.emoji, + username: usernames.pop(), + commaSeparatedUsernames: this._joinUsernames(usernames), + }); + } + + _joinUsernames(usernames) { + return usernames.join(I18n.t("word_connector.comma")); } } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message.js b/plugins/chat/assets/javascripts/discourse/components/chat-message.js index 1931f97db37..a8865c36510 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message.js @@ -411,49 +411,53 @@ export default class ChatMessage extends Component { } get mentionedCannotSeeText() { - return I18n.t("chat.mention_warning.cannot_see", { - username: this.mentionWarning?.cannot_see?.[0]?.username, - count: this.mentionWarning?.cannot_see?.length, - others: this._othersTranslation( - this.mentionWarning?.cannot_see?.length - 1 - ), - }); + return this._findTranslatedWarning( + "chat.mention_warning.cannot_see", + "chat.mention_warning.cannot_see_multiple", + { + username: this.mentionWarning?.cannot_see?.[0]?.username, + count: this.mentionWarning?.cannot_see?.length, + } + ); } get mentionedWithoutMembershipText() { - return I18n.t("chat.mention_warning.without_membership", { - username: this.mentionWarning?.without_membership?.[0]?.username, - count: this.mentionWarning?.without_membership?.length, - others: this._othersTranslation( - this.mentionWarning?.without_membership?.length - 1 - ), - }); + return this._findTranslatedWarning( + "chat.mention_warning.without_membership", + "chat.mention_warning.without_membership_multiple", + { + username: this.mentionWarning?.without_membership?.[0]?.username, + count: this.mentionWarning?.without_membership?.length, + } + ); } get groupsWithDisabledMentions() { - return I18n.t("chat.mention_warning.group_mentions_disabled", { - group_name: this.mentionWarning?.group_mentions_disabled?.[0], - count: this.mentionWarning?.group_mentions_disabled?.length, - others: this._othersTranslation( - this.mentionWarning?.group_mentions_disabled?.length - 1 - ), - }); + return this._findTranslatedWarning( + "chat.mention_warning.group_mentions_disabled", + "chat.mention_warning.group_mentions_disabled_multiple", + { + group_name: this.mentionWarning?.group_mentions_disabled?.[0], + count: this.mentionWarning?.group_mentions_disabled?.length, + } + ); } get groupsWithTooManyMembers() { - return I18n.t("chat.mention_warning.too_many_members", { - group_name: this.mentionWarning.groups_with_too_many_members?.[0], - count: this.mentionWarning.groups_with_too_many_members?.length, - others: this._othersTranslation( - this.mentionWarning.groups_with_too_many_members?.length - 1 - ), - }); + return this._findTranslatedWarning( + "chat.mention_warning.too_many_members", + "chat.mention_warning.too_many_members_multiple", + { + group_name: this.mentionWarning.groups_with_too_many_members?.[0], + count: this.mentionWarning.groups_with_too_many_members?.length, + } + ); } - _othersTranslation(othersCount) { - return I18n.t("chat.mention_warning.warning_multiple", { - count: othersCount, - }); + _findTranslatedWarning(oneKey, multipleKey, args) { + const translationKey = args.count === 1 ? oneKey : multipleKey; + args.count--; + return I18n.t(translationKey, args); } @action diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-replying-indicator.js b/plugins/chat/assets/javascripts/discourse/components/chat-replying-indicator.js index 0e0f797e938..6c46c2e9101 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-replying-indicator.js +++ b/plugins/chat/assets/javascripts/discourse/components/chat-replying-indicator.js @@ -33,14 +33,18 @@ export default Component.extend({ if (usernames.length < 4) { const lastUsername = usernames.pop(); - const commaSeparatedUsernames = usernames.join(", "); + const commaSeparatedUsernames = usernames.join( + I18n.t("word_connector.comma") + ); return I18n.t("chat.replying_indicator.multiple_users", { commaSeparatedUsernames, lastUsername, }); } - const commaSeparatedUsernames = usernames.slice(0, 2).join(", "); + const commaSeparatedUsernames = usernames + .slice(0, 2) + .join(I18n.t("word_connector.comma")); return I18n.t("chat.replying_indicator.many_users", { commaSeparatedUsernames, count: usernames.length - 2, diff --git a/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js b/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js index 0b6328bd886..0f0079de005 100644 --- a/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js +++ b/plugins/chat/assets/javascripts/discourse/controllers/create-channel.js @@ -103,20 +103,40 @@ export default class CreateChannelController extends Controller.extend( _updateAutoJoinConfirmWarning(category, catPermissions) { const allowedGroups = catPermissions.allowed_groups; + let warning; if (catPermissions.private) { - const warningTranslationKey = - allowedGroups.length < 3 ? "warning_groups" : "warning_multiple_groups"; - - this.set( - "autoJoinWarning", - I18n.t(`chat.create_channel.auto_join_users.${warningTranslationKey}`, { - members_count: catPermissions.members_count, - group: escapeExpression(allowedGroups[0]), - group_2: escapeExpression(allowedGroups[1]), - count: allowedGroups.length, - }) - ); + switch (allowedGroups.length) { + case 1: + warning = I18n.t( + "chat.create_channel.auto_join_users.warning_1_group", + { + count: catPermissions.members_count, + group: escapeExpression(allowedGroups[0]), + } + ); + break; + case 2: + warning = I18n.t( + "chat.create_channel.auto_join_users.warning_2_groups", + { + count: catPermissions.members_count, + group1: escapeExpression(allowedGroups[0]), + group2: escapeExpression(allowedGroups[1]), + } + ); + break; + default: + warning = I18n.messageFormat( + "chat.create_channel.auto_join_users.warning_multiple_groups_MF", + { + groupCount: allowedGroups.length - 1, + userCount: catPermissions.members_count, + groupName: escapeExpression(allowedGroups[0]), + } + ); + break; + } } else { this.set( "autoJoinWarning", @@ -125,6 +145,8 @@ export default class CreateChannelController extends Controller.extend( }) ); } + + this.set("autoJoinWarning", warning); } _updatePermissionsHint(category) { @@ -136,20 +158,42 @@ export default class CreateChannelController extends Controller.extend( .then((catPermissions) => { this._updateAutoJoinConfirmWarning(category, catPermissions); const allowedGroups = catPermissions.allowed_groups; - const translationKey = - allowedGroups.length < 3 ? "hint_groups" : "hint_multiple_groups"; + const settingLink = `/c/${escapeExpression(fullSlug)}/edit/security`; + let hint; - this.set( - "categoryPermissionsHint", - htmlSafe( - I18n.t(`chat.create_channel.choose_category.${translationKey}`, { - link: `/c/${escapeExpression(fullSlug)}/edit/security`, - hint: escapeExpression(allowedGroups[0]), - hint_2: escapeExpression(allowedGroups[1]), - count: allowedGroups.length, - }) - ) - ); + switch (allowedGroups.length) { + case 1: + hint = I18n.t( + "chat.create_channel.choose_category.hint_1_group", + { + settingLink, + group: escapeExpression(allowedGroups[0]), + } + ); + break; + case 2: + hint = I18n.t( + "chat.create_channel.choose_category.hint_2_groups", + { + settingLink, + group1: escapeExpression(allowedGroups[0]), + group2: escapeExpression(allowedGroups[1]), + } + ); + break; + default: + hint = I18n.t( + "chat.create_channel.choose_category.hint_multiple_groups", + { + settingLink, + group: escapeExpression(allowedGroups[0]), + count: allowedGroups.length - 1, + } + ); + break; + } + + this.set("categoryPermissionsHint", htmlSafe(hint)); }); } else { this.set("categoryPermissionsHint", DEFAULT_HINT); diff --git a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js index 1cb408dbaba..56fff731a30 100644 --- a/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js +++ b/plugins/chat/assets/javascripts/discourse/initializers/chat-sidebar.js @@ -223,8 +223,8 @@ export default { } get title() { - return I18n.t("chat.placeholder_others", { - messageRecipient: this.channel.escapedTitle, + return I18n.t("chat.placeholder_channel", { + channelName: this.channel.escapedTitle, }); } diff --git a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js index c38b83d5650..c6e75e9edd3 100644 --- a/plugins/chat/assets/javascripts/discourse/models/chat-channel.js +++ b/plugins/chat/assets/javascripts/discourse/models/chat-channel.js @@ -1,5 +1,4 @@ import RestModel from "discourse/models/rest"; -import I18n from "I18n"; import User from "discourse/models/user"; import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership"; import { ajax } from "discourse/lib/ajax"; @@ -21,19 +20,6 @@ export const CHANNEL_STATUSES = { archived: "archived", }; -export function channelStatusName(channelStatus) { - switch (channelStatus) { - case CHANNEL_STATUSES.open: - return I18n.t("chat.channel_status.open"); - case CHANNEL_STATUSES.readOnly: - return I18n.t("chat.channel_status.read_only"); - case CHANNEL_STATUSES.closed: - return I18n.t("chat.channel_status.closed"); - case CHANNEL_STATUSES.archived: - return I18n.t("chat.channel_status.archived"); - } -} - export function channelStatusIcon(channelStatus) { if (channelStatus === CHANNEL_STATUSES.open) { return null; diff --git a/plugins/chat/config/locales/client.en.yml b/plugins/chat/config/locales/client.en.yml index 87a89e3dd3d..d3d47e6f853 100644 --- a/plugins/chat/config/locales/client.en.yml +++ b/plugins/chat/config/locales/client.en.yml @@ -106,46 +106,96 @@ en: new_messages: "new messages" mention_warning: dismiss: "dismiss" - cannot_see: - one: "%{username} cannot access this channel and was not notified." - other: "%{username} and %{others} cannot access this channel and were not notified." + cannot_see: "%{username} can't access this channel and was not notified." + cannot_see_multiple: + one: "%{username} and %{count} other user cannot access this channel and were not notified." + other: "%{username} and %{count} other users cannot access this channel and were not notified." invitations_sent: one: "Invitation sent" other: "Invitations sent" invite: "Invite to channel" - without_membership: - one: "%{username} has not joined this channel." - other: "%{username} and %{others} have not joined this channel." - group_mentions_disabled: - one: "%{group_name} doesn't allow mentions" - other: "%{group_name} and %{others} doesn't allow mentions" - too_many_members: - one: "%{group_name} has too many members. No one was notified" - other: "%{group_name} and %{others} have too many members. No one was notified" - warning_multiple: - one: "%{count} other" - other: "%{count} others" - + without_membership: "%{username} has not joined this channel." + without_membership_multiple: + one: "%{username} and %{count} other user have not joined this channel." + other: "%{username} and %{count} other users have not joined this channel." + group_mentions_disabled: "%{group_name} doesn't allow mentions." + group_mentions_disabled_multiple: + one: "%{group_name} and %{count} other group don't allow mentions." + other: "%{group_name} and %{count} other groups don't allow mentions." + too_many_members: "%{group_name} has too many members. No one was notified." + too_many_members_multiple: + one: "%{group_name} and %{count} other group have too many members. No one was notified." + other: "%{group_name} and %{count} other groups have too many members. No one was notified." groups: header: some: "Some users won't be notified" all: "Nobody will be notified" - unreachable: - one: "@%{group} doesn't allow mentions" - other: "@%{group} and @%{group_2} doesn't allow mentions" - unreachable_multiple: "@%{group} and %{count} others doesn't allow mentions" - too_many_members: - one: "Mentioning @%{group} exceeds the %{notification_limit} of %{limit}" - other: "Mentioning both @%{group} or @%{group_2} exceeds the %{notification_limit} of %{limit}" - too_many_members_multiple: "These %{count} groups exceed the %{notification_limit} of %{limit}" - users_limit: - one: "%{count} user" - other: "%{count} users" - notification_limit: "notification limit" - too_many_mentions: "This message exceeds the %{notification_limit} of %{limit}" - mentions_limit: - one: "%{count} mention" - other: "%{count} mentions" + unreachable_1: "@%{group} doesn't allow mentions." + unreachable_2: "@%{group1} and @%{group2} don't allow mentions." + unreachable_multiple: + one: "@%{group} and %{count} other group don't allow mentions." + other: "@%{group} and %{count} other groups don't allow mentions." + too_many_members_MF: | + { groupCount, plural, + =1 { + { isAdmin, select, + true { + { notificationLimit, plural, + one {Mentioning @{group1} exceeds the notification limit of # user.} + other {Mentioning @{group1} exceeds the notification limit of # users.} + } + } + false { + { notificationLimit, plural, + one {Mentioning @{group1} exceeds the notification limit of # user.} + other {Mentioning @{group1} exceeds the notification limit of # users.} + } + } + other {} + } + } + =2 { + { isAdmin, select, + true { + { notificationLimit, plural, + one {Mentioning @{group1} and @{group2} exceeds the notification limit of # user.} + other {Mentioning @{group1} and @{group2} exceeds the notification limit of # users.} + } + } + false { + { notificationLimit, plural, + one {Mentioning @{group1} and @{group2} exceeds the notification limit of # user.} + other {Mentioning @{group1} and @{group2} exceeds the notification limit of # users.} + } + } + other {} + } + } + other { + { isAdmin, select, + true { + { notificationLimit, plural, + one {Mentioning these {groupCount} groups exceeds the notification limit of # user.} + other {Mentioning these {groupCount} groups exceeds the notification limit of # users.} + } + } + false { + { notificationLimit, plural, + one {Mentioning these {groupCount} groups exceeds the notification limit of # user.} + other {Mentioning these {groupCount} groups exceeds the notification limit of # users.} + } + } + other {} + } + } + } + too_many_mentions: + one: "This message exceeds the notification limit of %{count} mention." + other: "This message exceeds the notification limit of %{count} mentions." + too_many_mentions_admin: + one: 'This message exceeds the notification limit of %{count} mention.' + other: 'This message exceeds the notification limit of %{count} mentions.' + aria_roles: header: "Chat header" composer: "Chat composer" @@ -163,10 +213,15 @@ en: close_full_page: "Close full-screen chat" open_message: "Open message in chat" placeholder_self: "Jot something down" - placeholder_others: "Chat with %{messageRecipient}" - placeholder_new_message_disallowed: "Channel is %{status}, you cannot send new messages right now." + placeholder_channel: "Chat with %{channelName}" + placeholder_users: "Chat with %{commaSeparatedNames}" + placeholder_new_message_disallowed: + archived: "Channel is archived, you cannot send new messages right now." + closed: "Channel is closed, you cannot send new messages right now." + read_only: "Channel is read only, you cannot send new messages right now." placeholder_silenced: "You cannot send messages at this time." - placeholder_start_conversation: Start a conversation with %{usernames} + placeholder_start_conversation: "Start a conversation with ..." + placeholder_start_conversation_users: "Start a conversation with %{commaSeparatedUsernames}" remove_upload: "Remove file" react: "React with emoji" reply: "Reply" @@ -276,18 +331,36 @@ en: create_channel: auto_join_users: public_category_warning: "%{category} is a public category. Automatically add all recently active users to this channel?" - warning_groups: - one: Automatically add %{members_count} users from %{group}? - other: Automatically add %{members_count} users from %{group} and %{group_2}? - warning_multiple_groups: Automatically add %{members_count} users from %{group_1} and %{count} others? + warning_1_group: + one: "Automatically add %{count} user from %{group}?" + other: "Automatically add %{count} users from %{group}?" + warning_2_groups: + one: "Automatically add %{count} user from %{group1} and %{group2}?" + other: "Automatically add %{count} users from %{group1} and %{group2}?" + warning_multiple_groups_MF: | + { groupCount, plural, + one { + { userCount, plural, + one {Automatically add {userCount} user from {groupName} and {groupCount} other group?} + other {Automatically add {userCount} users from {groupName} and {groupCount} other group?} + } + } + other { + { userCount, plural, + one {Automatically add {userCount} user from {groupName} and {groupCount} other groups?} + other {Automatically add {userCount} users from {groupName} and {groupCount} other groups?} + } + } + } choose_category: label: "Choose a category" none: "select one..." default_hint: Manage access by visiting %{category} security settings - hint_groups: - one: Users in %{hint} will have access to this channel per the security settings - other: Users in %{hint} and %{hint_2} will have access to this channel per the security settings - hint_multiple_groups: Users in %{hint_1} and %{count} other groups will have access to this channel per the security settings + hint_1_group: 'Users in %{group} will have access to this channel per the security settings' + hint_2_groups: 'Users in %{group1} and %{group2} will have access to this channel per the security settings' + hint_multiple_groups: + one: 'Users in %{group} and %{count} other group will have access to this channel per the security settings' + other: 'Users in %{group} and %{count} other groups will have access to this channel per the security settings' create: "Create channel" description: "Description (optional)" name: "Channel name" @@ -303,10 +376,16 @@ en: reactions: only_you: "You reacted with :%{emoji}:" - and_others: "You, %{usernames} reacted with :%{emoji}:" - only_others: "%{usernames} reacted with :%{emoji}:" - others_and_more: "%{usernames} and %{more} others reacted with :%{emoji}:" - you_others_and_more: "You, %{usernames} and %{more} others reacted with :%{emoji}:" + you_and_single_user: "You and %{username} reacted with :%{emoji}:" + you_and_multiple_users: "You, %{commaSeparatedUsernames} and %{username} reacted with :%{emoji}:" + you_multiple_users_and_more: + one: "You, %{commaSeparatedUsernames} and %{count} other reacted with :%{emoji}:" + other: "You, %{commaSeparatedUsernames} and %{count} others reacted with :%{emoji}:" + single_user: "%{username} reacted with :%{emoji}:" + multiple_users: "%{commaSeparatedUsernames} and %{username} reacted with :%{emoji}:" + multiple_users_and_more: + one: "%{commaSeparatedUsernames} and %{count} other reacted with :%{emoji}:" + other: "%{commaSeparatedUsernames} and %{count} others reacted with :%{emoji}:" composer: toggle_toolbar: "Toggle toolbar" @@ -465,11 +544,13 @@ en: direct: 'mentioned you in "%{channel}"' direct_html: '%{username} mentioned you in "%{channel}"' other_plain: 'mentioned %{identifier} in "%{channel}"' + # %{identifier} is either @here or @all other_html: '%{username} mentioned %{identifier} in "%{channel}"' direct_message_chat_mention: direct: "mentioned you in personal chat" direct_html: "%{username} mentioned you in personal chat" other_plain: "mentioned %{identifier} in personal chat" + # %{identifier} is either @here or @all other_html: "%{username} mentioned %{identifier} in personal chat" chat_message: "New chat message" chat_quoted: "%{username} quoted your chat message" diff --git a/plugins/chat/config/locales/server.en.yml b/plugins/chat/config/locales/server.en.yml index 89142083fbf..b6b312d6c53 100644 --- a/plugins/chat/config/locales/server.en.yml +++ b/plugins/chat/config/locales/server.en.yml @@ -49,16 +49,26 @@ en: deleted_chat_username: deleted errors: channel_exists_for_category: "A channel already exists for this category and name" - channel_new_message_disallowed: "The channel is %{status}, no new messages can be sent" - channel_modify_message_disallowed: "The channel is %{status}, no messages can be edited or deleted" + channel_new_message_disallowed: + archived: "The channel is archived, no new messages can be sent" + closed: "The channel is closed, no new messages can be sent" + read_only: "The channel is read only, no new messages can be sent" + channel_modify_message_disallowed: + archived: "The channel is archived, no messages can be edited or deleted" + closed: "The channel is closed, no messages can be edited or deleted" + read_only: "The channel is read only, no messages can be edited or deleted" user_cannot_send_message: "You cannot send messages at this time." rate_limit_exceeded: "Exceeded the limit of chat messages that can be sent within 30 seconds" auto_silence_from_flags: "Chat message flagged with score high enough to silence user." channel_cannot_be_archived: "The channel cannot be archived at this time, it must be either closed or open to archive." duplicate_message: "You posted an identical message too recently." delete_channel_failed: "Delete channel failed, please try again." - minimum_length_not_met: "Message is too short, must have a minimum of %{minimum} characters." - message_too_long: "Message is too long, messages must be a maximum of %{maximum} characters." + minimum_length_not_met: + one: "Message is too short, must have a minimum of %{count} character." + other: "Message is too short, must have a minimum of %{count} characters." + message_too_long: + one: "Message is too long, messages must be a maximum of %{count} characters." + other: "Message is too long, messages must be a maximum of %{count} characters." draft_too_long: "Draft is too long." max_reactions_limit_reached: "New reactions are not allowed on this message." message_move_invalid_channel: "The source and destination channel must be public channels." @@ -70,8 +80,9 @@ en: actor_disallowed_dms: "You have chosen to prevent users from sending you private and direct messages, so you cannot create new direct messages." actor_preventing_target_user_from_dm: "You have chosen to prevent %{username} from sending you private and direct messages, so you cannot create new direct messages to them." user_cannot_send_direct_messages: "Sorry, you cannot send direct messages." + over_chat_max_direct_message_users_allow_self: "You can only create a direct message with yourself." over_chat_max_direct_message_users: - one: "You can only create a direct message with yourself." + one: "You can't create a direct message with more than %{count} other user." other: "You can't create a direct message with more than %{count} other users." original_message_not_found: "The ancestor of the message you are replying cannot be found or has been deleted." reviewables: @@ -110,20 +121,17 @@ en: transcript_title: "Transcript of previous messages in %{channel_name}" transcript_body: "To give you more context, we included a transcript of the previous messages in this conversation (up to ten):\n\n%{transcript}" channel: - statuses: - read_only: "Read Only" - archived: "Archived" - closed: "Closed" - open: "Open" archive: first_post_raw: "This topic is an archive of the [%{channel_name}](%{channel_url}) chat channel." messages_moved: one: "@%{acting_username} moved a message to the [%{channel_name}](%{first_moved_message_url}) channel." other: "@%{acting_username} moved %{count} messages to the [%{channel_name}](%{first_moved_message_url}) channel." dm_title: - single_user: "%{user}" - multi_user: "%{users}" - multi_user_truncated: "%{users} and %{leftover} others" + single_user: "%{username}" + multi_user: "%{comma_separated_usernames}" + multi_user_truncated: + one: "%{comma_separated_usernames} and %{count} other" + other: "%{comma_separated_usernames} and %{count} others" category_channel: errors: diff --git a/plugins/chat/lib/chat_message_creator.rb b/plugins/chat/lib/chat_message_creator.rb index 08b5a711a08..6cd55f2fedb 100644 --- a/plugins/chat/lib/chat_message_creator.rb +++ b/plugins/chat/lib/chat_message_creator.rb @@ -82,10 +82,7 @@ class Chat::ChatMessageCreator raise StandardError.new(I18n.t("chat.errors.user_cannot_send_direct_messages")) else raise StandardError.new( - I18n.t( - "chat.errors.channel_new_message_disallowed", - status: @chat_channel.status_name, - ), + I18n.t("chat.errors.channel_new_message_disallowed.#{@chat_channel.status}"), ) end end diff --git a/plugins/chat/lib/chat_message_reactor.rb b/plugins/chat/lib/chat_message_reactor.rb index 9e7260f7979..8815fa135e3 100644 --- a/plugins/chat/lib/chat_message_reactor.rb +++ b/plugins/chat/lib/chat_message_reactor.rb @@ -53,10 +53,7 @@ class Chat::ChatMessageReactor raise Discourse::InvalidAccess.new( nil, nil, - custom_message: "chat.errors.channel_modify_message_disallowed", - custom_message_params: { - status: @chat_channel.status_name, - }, + custom_message: "chat.errors.channel_modify_message_disallowed.#{@chat_channel.status}", ) end diff --git a/plugins/chat/lib/chat_message_updater.rb b/plugins/chat/lib/chat_message_updater.rb index 79ecfed4e2c..43bb028c40d 100644 --- a/plugins/chat/lib/chat_message_updater.rb +++ b/plugins/chat/lib/chat_message_updater.rb @@ -51,10 +51,7 @@ class Chat::ChatMessageUpdater def validate_channel_status! return if @guardian.can_modify_channel_message?(@chat_channel) raise StandardError.new( - I18n.t( - "chat.errors.channel_modify_message_disallowed", - status: @chat_channel.status_name, - ), + I18n.t("chat.errors.channel_modify_message_disallowed.#{@chat_channel.status}"), ) end diff --git a/plugins/chat/lib/direct_message_channel_creator.rb b/plugins/chat/lib/direct_message_channel_creator.rb index 63342e3cfb7..505801d42fe 100644 --- a/plugins/chat/lib/direct_message_channel_creator.rb +++ b/plugins/chat/lib/direct_message_channel_creator.rb @@ -30,12 +30,16 @@ module Chat::DirectMessageChannelCreator target_users = target_users.reject { |user| user.id == acting_user.id } if !acting_user.staff? && target_users.size > SiteSetting.chat_max_direct_message_users - raise NotAllowed.new( - I18n.t( - "chat.errors.over_chat_max_direct_message_users", - count: SiteSetting.chat_max_direct_message_users + 1, # +1 for the acting_user - ), - ) + if SiteSetting.chat_max_direct_message_users == 0 + raise NotAllowed.new(I18n.t("chat.errors.over_chat_max_direct_message_users_allow_self")) + else + raise NotAllowed.new( + I18n.t( + "chat.errors.over_chat_max_direct_message_users", + count: SiteSetting.chat_max_direct_message_users + 1, # +1 for the acting_user + ), + ) + end end end diff --git a/plugins/chat/spec/components/chat_message_creator_spec.rb b/plugins/chat/spec/components/chat_message_creator_spec.rb index de4cf37173a..798149ece86 100644 --- a/plugins/chat/spec/components/chat_message_creator_spec.rb +++ b/plugins/chat/spec/components/chat_message_creator_spec.rb @@ -64,7 +64,7 @@ describe Chat::ChatMessageCreator do expect(creator.error.message).to match( I18n.t( "chat.errors.minimum_length_not_met", - { minimum: SiteSetting.chat_minimum_message_length }, + { count: SiteSetting.chat_minimum_message_length }, ), ) end @@ -79,10 +79,7 @@ describe Chat::ChatMessageCreator do ) expect(creator.failed?).to eq(true) expect(creator.error.message).to match( - I18n.t( - "chat.errors.message_too_long", - { maximum: SiteSetting.chat_maximum_message_length }, - ), + I18n.t("chat.errors.message_too_long", { count: SiteSetting.chat_maximum_message_length }), ) end @@ -866,10 +863,7 @@ describe Chat::ChatMessageCreator do creator = create_message(user1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t( - "chat.errors.channel_new_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_new_message_disallowed.closed"), ) end @@ -885,18 +879,12 @@ describe Chat::ChatMessageCreator do creator = create_message(user1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t( - "chat.errors.channel_new_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_new_message_disallowed.read_only"), ) creator = create_message(admin1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t( - "chat.errors.channel_new_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_new_message_disallowed.read_only"), ) end end @@ -908,18 +896,12 @@ describe Chat::ChatMessageCreator do creator = create_message(user1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t( - "chat.errors.channel_new_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_new_message_disallowed.archived"), ) creator = create_message(admin1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t( - "chat.errors.channel_new_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_new_message_disallowed.archived"), ) end end diff --git a/plugins/chat/spec/components/chat_message_updater_spec.rb b/plugins/chat/spec/components/chat_message_updater_spec.rb index 39c00726bc6..62de1bd9e3f 100644 --- a/plugins/chat/spec/components/chat_message_updater_spec.rb +++ b/plugins/chat/spec/components/chat_message_updater_spec.rb @@ -62,7 +62,7 @@ describe Chat::ChatMessageUpdater do expect(updater.error.message).to match( I18n.t( "chat.errors.minimum_length_not_met", - { minimum: SiteSetting.chat_minimum_message_length }, + { count: SiteSetting.chat_minimum_message_length }, ), ) expect(chat_message.reload.message).to eq(og_message) @@ -82,7 +82,7 @@ describe Chat::ChatMessageUpdater do ) expect(updater.failed?).to eq(true) expect(updater.error.message).to match( - I18n.t("chat.errors.message_too_long", { maximum: SiteSetting.chat_maximum_message_length }), + I18n.t("chat.errors.message_too_long", { count: SiteSetting.chat_maximum_message_length }), ) expect(chat_message.reload.message).to eq(og_message) end @@ -528,10 +528,7 @@ describe Chat::ChatMessageUpdater do updater = update_message(user1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t( - "chat.errors.channel_modify_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_modify_message_disallowed.closed"), ) end @@ -548,18 +545,12 @@ describe Chat::ChatMessageUpdater do updater = update_message(user1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t( - "chat.errors.channel_modify_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_modify_message_disallowed.read_only"), ) updater = update_message(admin1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t( - "chat.errors.channel_modify_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_modify_message_disallowed.read_only"), ) end end @@ -571,18 +562,12 @@ describe Chat::ChatMessageUpdater do updater = update_message(user1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t( - "chat.errors.channel_modify_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_modify_message_disallowed.archived"), ) updater = update_message(admin1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t( - "chat.errors.channel_modify_message_disallowed", - status: public_chat_channel.status_name, - ), + I18n.t("chat.errors.channel_modify_message_disallowed.archived"), ) end end diff --git a/plugins/chat/spec/lib/direct_message_channel_creator_spec.rb b/plugins/chat/spec/lib/direct_message_channel_creator_spec.rb index ff3863ca3a7..0ae71e0c462 100644 --- a/plugins/chat/spec/lib/direct_message_channel_creator_spec.rb +++ b/plugins/chat/spec/lib/direct_message_channel_creator_spec.rb @@ -212,7 +212,7 @@ describe Chat::DirectMessageChannelCreator do subject.create!(acting_user: user_1, target_users: [user_1, user_2]) }.to raise_error( Chat::DirectMessageChannelCreator::NotAllowed, - I18n.t("chat.errors.over_chat_max_direct_message_users", count: 1), + I18n.t("chat.errors.over_chat_max_direct_message_users_allow_self"), ) end end diff --git a/plugins/chat/spec/models/direct_message_spec.rb b/plugins/chat/spec/models/direct_message_spec.rb index 9e44e51cd5d..8e8443cf90d 100644 --- a/plugins/chat/spec/models/direct_message_spec.rb +++ b/plugins/chat/spec/models/direct_message_spec.rb @@ -20,7 +20,8 @@ describe DirectMessage do expect(direct_message.chat_channel_title_for_user(chat_channel, user1)).to eq( I18n.t( "chat.channel.dm_title.multi_user", - users: [user3, user2].map { |u| "@#{u.username}" }.join(", "), + comma_separated_usernames: + [user3, user2].map { |u| "@#{u.username}" }.join(I18n.t("word_connector.comma")), ), ) end @@ -36,8 +37,12 @@ describe DirectMessage do expect(direct_message.chat_channel_title_for_user(chat_channel, user1)).to eq( I18n.t( "chat.channel.dm_title.multi_user_truncated", - users: users[1..5].sort_by(&:username).map { |u| "@#{u.username}" }.join(", "), - leftover: 2, + comma_separated_usernames: + users[1..5] + .sort_by(&:username) + .map { |u| "@#{u.username}" } + .join(I18n.t("word_connector.comma")), + count: 2, ), ) end @@ -46,7 +51,7 @@ describe DirectMessage do direct_message = Fabricate(:direct_message, users: [user1, user2]) expect(direct_message.chat_channel_title_for_user(chat_channel, user1)).to eq( - I18n.t("chat.channel.dm_title.single_user", user: "@#{user2.username}"), + I18n.t("chat.channel.dm_title.single_user", username: "@#{user2.username}"), ) end @@ -54,7 +59,7 @@ describe DirectMessage do direct_message = Fabricate(:direct_message, users: [user1]) expect(direct_message.chat_channel_title_for_user(chat_channel, user1)).to eq( - I18n.t("chat.channel.dm_title.single_user", user: "@#{user1.username}"), + I18n.t("chat.channel.dm_title.single_user", username: "@#{user1.username}"), ) end diff --git a/plugins/chat/spec/requests/chat_controller_spec.rb b/plugins/chat/spec/requests/chat_controller_spec.rb index 9f1729837d0..d0fcbfd19ab 100644 --- a/plugins/chat/spec/requests/chat_controller_spec.rb +++ b/plugins/chat/spec/requests/chat_controller_spec.rb @@ -320,7 +320,7 @@ RSpec.describe Chat::ChatController do post "/chat/#{chat_channel.id}.json", params: { message: message } expect(response.status).to eq(422) expect(response.parsed_body["errors"]).to include( - I18n.t("chat.errors.channel_new_message_disallowed", status: chat_channel.status_name), + I18n.t("chat.errors.channel_new_message_disallowed.closed"), ) end @@ -336,7 +336,7 @@ RSpec.describe Chat::ChatController do post "/chat/#{chat_channel.id}.json", params: { message: message } expect(response.status).to eq(422) expect(response.parsed_body["errors"]).to include( - I18n.t("chat.errors.channel_new_message_disallowed", status: chat_channel.status_name), + I18n.t("chat.errors.channel_new_message_disallowed.read_only"), ) end @@ -903,7 +903,7 @@ RSpec.describe Chat::ChatController do }.not_to change { chat_message.reactions.where(user: user, emoji: emoji).count } expect(response.status).to eq(403) expect(response.parsed_body["errors"]).to include( - I18n.t("chat.errors.channel_modify_message_disallowed", status: chat_channel.status_name), + I18n.t("chat.errors.channel_modify_message_disallowed.#{chat_channel.status}"), ) end diff --git a/plugins/chat/spec/requests/incoming_chat_webhooks_controller_spec.rb b/plugins/chat/spec/requests/incoming_chat_webhooks_controller_spec.rb index a8a54334718..6c049a1f050 100644 --- a/plugins/chat/spec/requests/incoming_chat_webhooks_controller_spec.rb +++ b/plugins/chat/spec/requests/incoming_chat_webhooks_controller_spec.rb @@ -55,7 +55,7 @@ RSpec.describe Chat::IncomingChatWebhooksController do }.not_to change { ChatMessage.where(chat_channel: chat_channel).count } expect(response.status).to eq(422) expect(response.parsed_body["errors"]).to include( - I18n.t("chat.errors.channel_new_message_disallowed", status: chat_channel.status_name), + I18n.t("chat.errors.channel_new_message_disallowed.read_only"), ) end diff --git a/plugins/chat/spec/system/closed_channel_spec.rb b/plugins/chat/spec/system/closed_channel_spec.rb index 7237db225fe..5c19e7564c6 100644 --- a/plugins/chat/spec/system/closed_channel_spec.rb +++ b/plugins/chat/spec/system/closed_channel_spec.rb @@ -30,11 +30,7 @@ RSpec.describe "Closed channel", type: :system, js: true do chat.visit_channel(channel_1) expect(page).to have_field( - placeholder: - I18n.t( - "js.chat.placeholder_new_message_disallowed", - status: I18n.t("js.chat.channel_status.closed").downcase, - ), + placeholder: I18n.t("js.chat.placeholder_new_message_disallowed.closed"), disabled: true, ) end @@ -54,7 +50,7 @@ RSpec.describe "Closed channel", type: :system, js: true do chat.visit_channel(channel_1) expect(page).to have_no_field( - placeholder: I18n.t("js.chat.placeholder_new_message_disallowed"), + placeholder: I18n.t("js.chat.placeholder_new_message_disallowed.closed"), disabled: true, ) end diff --git a/plugins/chat/spec/system/create_channel_spec.rb b/plugins/chat/spec/system/create_channel_spec.rb index 294683dec1c..27a0de52dc5 100644 --- a/plugins/chat/spec/system/create_channel_spec.rb +++ b/plugins/chat/spec/system/create_channel_spec.rb @@ -69,7 +69,7 @@ RSpec.describe "Create channel", type: :system, js: true do end end - context "when category has a malicous group name" do + context "when category has a malicious group name" do fab!(:group_1) do group = Group.new(name: "") group.save(validate: false) diff --git a/plugins/chat/spec/system/jit_messages_spec.rb b/plugins/chat/spec/system/jit_messages_spec.rb index d5e0a2e7df4..0f0628b1ac9 100644 --- a/plugins/chat/spec/system/jit_messages_spec.rb +++ b/plugins/chat/spec/system/jit_messages_spec.rb @@ -22,7 +22,7 @@ RSpec.describe "JIT messages", type: :system, js: true do channel.send_message("hi @#{other_user.username}") expect(page).to have_content( - I18n.t("js.chat.mention_warning.without_membership.one", username: other_user.username), + I18n.t("js.chat.mention_warning.without_membership", username: other_user.username), wait: 5, ) end @@ -44,7 +44,7 @@ RSpec.describe "JIT messages", type: :system, js: true do channel.send_message("hi @#{other_user.username}") expect(page).to have_content( - I18n.t("js.chat.mention_warning.cannot_see.one", username: other_user.username), + I18n.t("js.chat.mention_warning.cannot_see", username: other_user.username), wait: 5, ) end @@ -61,7 +61,7 @@ RSpec.describe "JIT messages", type: :system, js: true do channel.send_message("hi @#{group_1.name}") expect(page).to have_content( - I18n.t("js.chat.mention_warning.group_mentions_disabled.one", group_name: group_1.name), + I18n.t("js.chat.mention_warning.group_mentions_disabled", group_name: group_1.name), wait: 5, ) end diff --git a/plugins/chat/spec/system/read_only_spec.rb b/plugins/chat/spec/system/read_only_spec.rb index 3610e55020e..ffadc0dce03 100644 --- a/plugins/chat/spec/system/read_only_spec.rb +++ b/plugins/chat/spec/system/read_only_spec.rb @@ -30,7 +30,7 @@ RSpec.describe "Read only", type: :system, js: true do chat.visit_channel(channel_1) expect(page).to have_field( - placeholder: I18n.t("js.chat.placeholder_new_message_disallowed", status: "read only"), + placeholder: I18n.t("js.chat.placeholder_new_message_disallowed.read_only"), disabled: true, ) end @@ -50,7 +50,7 @@ RSpec.describe "Read only", type: :system, js: true do chat.visit_channel(channel_1) expect(page).to have_field( - placeholder: I18n.t("js.chat.placeholder_new_message_disallowed", status: "read only"), + placeholder: I18n.t("js.chat.placeholder_new_message_disallowed.read_only"), disabled: true, ) end diff --git a/script/i18n_lint.rb b/script/i18n_lint.rb index f3f96abf87d..794b07b34ae 100755 --- a/script/i18n_lint.rb +++ b/script/i18n_lint.rb @@ -37,8 +37,6 @@ class LocaleFileValidator "Pluralized strings must have only the sub-keys 'one' and 'other'.\nThe following keys have missing or additional keys:", invalid_one_keys: "The following keys contain the number 1 instead of the interpolation key %{count}:", - invalid_message_format_one_key: - "The following keys use 'one {1 foo}' instead of the generic 'one {# foo}':", } PLURALIZATION_KEYS = %w[zero one two few many other] @@ -88,7 +86,6 @@ class LocaleFileValidator @errors[:invalid_relative_links] = [] @errors[:invalid_relative_image_sources] = [] @errors[:invalid_interpolation_key_format] = [] - @errors[:invalid_message_format_one_key] = [] each_translation(yaml) do |key, value| @errors[:invalid_relative_links] << key if value.match?(%r{href\s*=\s*["']/[^/]|\]\(/[^/]}i) @@ -98,10 +95,6 @@ class LocaleFileValidator if value.match?(/{{.+?}}/) && !key.end_with?("_MF") @errors[:invalid_interpolation_key_format] << key end - - if key.end_with?("_MF") && value.match?(/one {.*?1.*?}/) - @errors[:invalid_message_format_one_key] << key - end end end diff --git a/spec/models/translation_override_spec.rb b/spec/models/translation_override_spec.rb index 20f3200b430..d8c8a5c3971 100644 --- a/spec/models/translation_override_spec.rb +++ b/spec/models/translation_override_spec.rb @@ -27,6 +27,7 @@ RSpec.describe TranslationOverride do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "key, omg", + count: 2, ), ) end @@ -61,6 +62,7 @@ RSpec.describe TranslationOverride do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "something", + count: 1, ), ) end @@ -78,6 +80,7 @@ RSpec.describe TranslationOverride do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "topic_title_url_encoded", + count: 1, ), ) end @@ -132,6 +135,7 @@ RSpec.describe TranslationOverride do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "key3, key4", + count: 2, ), ) end diff --git a/spec/requests/admin/email_templates_controller_spec.rb b/spec/requests/admin/email_templates_controller_spec.rb index e9e15341bfb..4fb80b48daf 100644 --- a/spec/requests/admin/email_templates_controller_spec.rb +++ b/spec/requests/admin/email_templates_controller_spec.rb @@ -165,6 +165,7 @@ RSpec.describe Admin::EmailTemplatesController do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "email_wrongfix", + count: 1, ) }", ] @@ -183,6 +184,7 @@ RSpec.describe Admin::EmailTemplatesController do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "invalid", + count: 1, ) }", ] @@ -201,12 +203,14 @@ RSpec.describe Admin::EmailTemplatesController do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "invalid", + count: 1, ) }", "Body: #{ I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "invalid", + count: 1, ) }", ] diff --git a/spec/requests/admin/site_texts_controller_spec.rb b/spec/requests/admin/site_texts_controller_spec.rb index 0bdace38ede..14b3f2455c3 100644 --- a/spec/requests/admin/site_texts_controller_spec.rb +++ b/spec/requests/admin/site_texts_controller_spec.rb @@ -580,6 +580,7 @@ RSpec.describe Admin::SiteTextsController do I18n.t( "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", keys: "key, omg", + count: 2, ), ) end