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
This commit is contained in:
Gerhard Schlager 2023-02-20 10:31:02 +01:00 committed by GitHub
parent d71a82786a
commit 7ef482a292
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 499 additions and 368 deletions

View File

@ -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,
),
)

View File

@ -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}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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");
}
},
});

View File

@ -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}`,
});
}
},

View File

@ -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(
`<a
target="_blank"
href="/admin/site_settings/category/plugins?filter=max_mentions_per_chat_message"
>
${notificationLimit}
</a>`
);
}
const settingLimit = I18n.t("chat.mention_warning.mentions_limit", {
count: this.siteSettings.max_mentions_per_chat_message,
});
if (this.currentUser.admin) {
return htmlSafe(
I18n.t("chat.mention_warning.too_many_mentions", {
notification_limit: notificationLimit,
limit: settingLimit,
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,
})
);
}
}
get unreachableBody() {
@ -111,16 +103,20 @@ export default class ChatMentionWarnings extends Component {
return;
}
if (this.unreachableGroupMentionsCount <= 2) {
return I18n.t("chat.mention_warning.groups.unreachable", {
switch (this.unreachableGroupMentionsCount) {
case 1:
return I18n.t("chat.mention_warning.groups.unreachable_1", {
group: this.unreachableGroupMentions[0],
group_2: this.unreachableGroupMentions[1],
count: this.unreachableGroupMentionsCount,
});
} else {
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, //N others
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"
);
if (this.currentUser.staff) {
notificationLimit = htmlSafe(
`<a
target="_blank"
href="/admin/site_settings/category/plugins?filter=max_users_notified_per_group_mention"
>
${notificationLimit}
</a>`
);
}
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,
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],
})
);
}
}
}

View File

@ -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 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,
});
} else if (this.reaction.count > 1 && this.reaction.count < 6) {
return I18n.t("chat.reactions.and_others", {
usernames,
}
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,
});
} 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,
username: usernames.pop(),
});
}
} else {
if (this.reaction.count > 0 && this.reaction.count < 6) {
return I18n.t("chat.reactions.only_others", {
usernames,
// `-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,
});
} else if (this.reaction.count >= 6) {
return I18n.t("chat.reactions.others_and_more", {
usernames,
emoji: this.reaction.emoji,
more: this.reaction.count - 5,
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"));
}
}

View File

@ -411,49 +411,53 @@ export default class ChatMessage extends Component {
}
get mentionedCannotSeeText() {
return I18n.t("chat.mention_warning.cannot_see", {
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,
others: this._othersTranslation(
this.mentionWarning?.cannot_see?.length - 1
),
});
}
);
}
get mentionedWithoutMembershipText() {
return I18n.t("chat.mention_warning.without_membership", {
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,
others: this._othersTranslation(
this.mentionWarning?.without_membership?.length - 1
),
});
}
);
}
get groupsWithDisabledMentions() {
return I18n.t("chat.mention_warning.group_mentions_disabled", {
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,
others: this._othersTranslation(
this.mentionWarning?.group_mentions_disabled?.length - 1
),
});
}
);
}
get groupsWithTooManyMembers() {
return I18n.t("chat.mention_warning.too_many_members", {
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,
others: this._othersTranslation(
this.mentionWarning.groups_with_too_many_members?.length - 1
),
});
}
);
}
_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

View File

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

View File

@ -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,
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]),
group_2: escapeExpression(allowedGroups[1]),
count: allowedGroups.length,
})
}
);
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);

View File

@ -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,
});
}

View File

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

View File

@ -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 <a href="{siteSettingUrl}" target="_blank">notification limit</a> of # user.}
other {Mentioning @{group1} exceeds the <a href="{siteSettingUrl}" target="_blank">notification limit</a> 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 <a href="{siteSettingUrl}" target="_blank">notification limit</a> of # user.}
other {Mentioning @{group1} and @{group2} exceeds the <a href="{siteSettingUrl}" target="_blank">notification limit</a> 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 <a href="{siteSettingUrl}" target="_blank">notification limit</a> of # user.}
other {Mentioning these {groupCount} groups exceeds the <a href="{siteSettingUrl}" target="_blank">notification limit</a> 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 <a href="%{siteSettingUrl}" target="_blank">notification limit</a> of %{count} mention.'
other: 'This message exceeds the <a href="%{siteSettingUrl}" target="_blank">notification limit</a> 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 <a href=%{link} target="_blank">%{category} security settings</a>
hint_groups:
one: Users in %{hint} will have access to this channel per the <a href=%{link} target="_blank">security settings</a>
other: Users in %{hint} and %{hint_2} will have access to this channel per the <a href=%{link} target="_blank">security settings</a>
hint_multiple_groups: Users in %{hint_1} and %{count} other groups will have access to this channel per the <a href=%{link} target="_blank">security settings</a>
hint_1_group: 'Users in %{group} will have access to this channel per the <a href="%{settingLink}" target="_blank">security settings</a>'
hint_2_groups: 'Users in %{group1} and %{group2} will have access to this channel per the <a href="%{settingLink}" target="_blank">security settings</a>'
hint_multiple_groups:
one: 'Users in %{group} and %{count} other group will have access to this channel per the <a href="%{settingLink}" target="_blank">security settings</a>'
other: 'Users in %{group} and %{count} other groups will have access to this channel per the <a href="%{settingLink}" target="_blank">security settings</a>'
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: '<span>%{username}</span> <span>mentioned you in "%{channel}"</span>'
other_plain: 'mentioned %{identifier} in "%{channel}"'
# %{identifier} is either @here or @all
other_html: '<span>%{username}</span> <span>mentioned %{identifier} in "%{channel}"</span>'
direct_message_chat_mention:
direct: "mentioned you in personal chat"
direct_html: "<span>%{username}</span> <span>mentioned you in personal chat</span>"
other_plain: "mentioned %{identifier} in personal chat"
# %{identifier} is either @here or @all
other_html: "<span>%{username}</span> <span>mentioned %{identifier} in personal chat</span>"
chat_message: "New chat message"
chat_quoted: "%{username} quoted your chat message"

View File

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

View File

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

View File

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

View File

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

View File

@ -30,6 +30,9 @@ 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
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",
@ -38,6 +41,7 @@ module Chat::DirectMessageChannelCreator
)
end
end
end
def self.update_memberships(acting_user, target_users, chat_channel_id)
sql_params = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: "<script>e</script>")
group.save(validate: false)

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
)
}",
"<b>Body</b>: #{
I18n.t(
"activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys",
keys: "invalid",
count: 1,
)
}",
]

View File

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