Fix i18n issues reported on Crowdin (#11747)

* Pluralize `groups.errors.adding_too_many_users`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/248/en-ar#53882

* Pluralize `js.composer.error.title_too_short`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#41172

* Pluralize `js.composer.error.title_too_long`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#41174

* Pluralize `js.composer.error.post_length`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#41178

* Pluralize `js.topic.progress.jump_prompt_of`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#41958

* Use translations to join strings about posters
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/248/en-ar#49334
  It also makes some changes to the crawler view:
    * Removes `poster.moreCount` which is only available on the client for PMs
    * CSS class names are actually stored in `poster.extras` instead of `poster.extraClasses`

* Stop concatenating category stats
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#40740

* Pluralize `js.summary.description`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#40782

* Pluralize `js.summary.description_time_MF`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#40784

* Use translation to join list of tags
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#43372

* Pluralize `admin_js.admin.groups.manage.membership.automatic_membership_user_count`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#43720

* Pluralize `js.post.controls.delete_topic_confirm_modal`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#54804

* Stop concatenating `js.post.last_edited_on`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#42358

* Stop concatenating `js.post.wiki_last_edited_on`
  This fixes https://discourse.crowdin.com/translate/f3230e7607a36bb0a2f97fd90605a44e/246/en-ar#42356
  It also fixes a regression because `js.post.wiki_last_edited_on` wasn't used anymore since 2017.
This commit is contained in:
Gerhard Schlager 2021-02-02 10:50:04 +01:00 committed by GitHub
parent 9d2eaec88f
commit d055552994
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 111 additions and 80 deletions

View File

@ -239,7 +239,9 @@ export default Component.extend({
if (replyLength < 1) {
reason = I18n.t("composer.error.post_missing");
} else if (missingReplyCharacters > 0) {
reason = I18n.t("composer.error.post_length", { min: minimumPostLength });
reason = I18n.t("composer.error.post_length", {
count: minimumPostLength,
});
const tl = this.get("currentUser.trust_level");
if (tl === 0 || tl === 1) {
reason +=

View File

@ -44,11 +44,11 @@ export default Component.extend({
reason = I18n.t("composer.error.title_missing");
} else if (missingTitleChars > 0) {
reason = I18n.t("composer.error.title_too_short", {
min: minimumTitleLength,
count: minimumTitleLength,
});
} else if (titleLength > this.siteSettings.max_topic_title_length) {
reason = I18n.t("composer.error.title_too_long", {
max: this.siteSettings.max_topic_title_length,
count: this.siteSettings.max_topic_title_length,
});
}

View File

@ -71,8 +71,10 @@ export default Controller.extend({
return;
}
const joinedTags = tags.slice(0, displayN).join(", ");
let more = Math.max(0, tags.length - displayN);
const joinedTags = tags
.slice(0, displayN)
.join(I18n.t("tagging.tag_list_joiner"));
const more = Math.max(0, tags.length - displayN);
const tagsString =
more === 0

View File

@ -1,4 +1,5 @@
import { avatarImg, formatUsername } from "discourse/lib/utilities";
import I18n from "I18n";
import { get } from "@ember/object";
import { htmlSafe } from "@ember/template";
import { prioritizeNameInUx } from "discourse/lib/settings";
@ -59,7 +60,10 @@ function renderAvatar(user, options) {
// if a description has been provided
if (description && description.length > 0) {
// preprend the username before the description
title = displayName + " - " + description;
title = I18n.t("user.avatar.name_and_description", {
name: displayName,
description,
});
}
}
}

View File

@ -5,6 +5,7 @@ import PreloadStore from "discourse/lib/preload-store";
import Site from "discourse/models/site";
import Topic from "discourse/models/topic";
import { ajax } from "discourse/lib/ajax";
import { number } from "discourse/lib/formatter";
const CategoryList = ArrayProxy.extend({
init() {
@ -50,9 +51,14 @@ CategoryList.reopenClass({
case "week":
case "month":
const stat = c[`topics_${statPeriod}`];
const unit = I18n.t(statPeriod);
if (stat > 0) {
c.stat = `<span class="value">${stat}</span> / <span class="unit">${unit}</span>`;
const unit = I18n.t(`categories.topic_stat_unit.${statPeriod}`);
c.stat = I18n.t("categories.topic_stat", {
count: stat, // only used to correctly pluralize the string
number: `<span class="value">${number(stat)}</span>`,
unit: `<span class="unit">${unit}</span>`,
});
c.statTitle = I18n.t(
`categories.topic_stat_sentence_${statPeriod}`,
@ -61,13 +67,11 @@ CategoryList.reopenClass({
}
);
c[
"pick" + statPeriod[0].toUpperCase() + statPeriod.slice(1)
] = true;
c.pickAll = false;
break;
}
default:
c.stat = `<span class="value">${c.topics_all_time}</span>`;
c.stat = `<span class="value">${number(c.topics_all_time)}</span>`;
c.statTitle = I18n.t("categories.topic_sentence", {
count: c.topics_all_time,
});
@ -75,6 +79,13 @@ CategoryList.reopenClass({
break;
}
if (Site.currentProp("mobileView")) {
c.statTotal = I18n.t("categories.topic_stat_all_time", {
count: c.topics_all_time,
number: `<span class="value">${number(c.topics_all_time)}</span>`,
});
}
const record = Site.current().updateCategory(c);
record.setupGroupsAndPermissions();
categories.pushObject(record);

View File

@ -3,7 +3,7 @@
{{#if hasEdits}}
<a href {{action "showEditHistory"}}
class="has-edits {{historyClass}}"
title="{{i18n "post.last_edited_on"}} {{editedDate}}">
title={{i18n "post.last_edited_on" dateTime=editedDate}}>
{{d-icon "pencil-alt"}}
</a>
{{/if}}

View File

@ -40,28 +40,18 @@
</tbody>
</table>
<footer class="clearfix category-topics-count">
<figure title={{i18n "all_time_desc"}}>
<div class="category-stat">
<a href={{category.url}}>
{{number category.topics_all_time}}
<figcaption>{{i18n "all_time"}}</figcaption>
{{html-safe category.statTotal}}
</a>
</figure>
{{#if category.pickMonth}}
<figure title={{i18n "month_desc"}}>
</div>
{{#unless category.pickAll}}
<div class="category-stat">
<a href={{category.url}}>
{{number category.topics_month}}
<figcaption>/ {{i18n "month"}}</figcaption>
{{html-safe category.stat}}
</a>
</figure>
{{/if}}
{{#if category.pickWeek}}
<figure title={{i18n "week_desc"}}>
<a href={{category.url}}>
{{number category.topics_week}}
<figcaption>/ {{i18n "week"}}</figcaption>
</a>
</figure>
{{/if}}
</div>
{{/unless}}
</footer>
</div>
{{/unless}}

View File

@ -1,5 +1,5 @@
{{#d-modal-body}}
<p>{{i18n "post.controls.delete_topic_confirm_modal" minViews=siteSettings.min_topic_views_for_delete_confirm}}</p>
<p>{{i18n "post.controls.delete_topic_confirm_modal" count=siteSettings.min_topic_views_for_delete_confirm}}</p>
{{/d-modal-body}}
<div class="modal-footer">

View File

@ -43,12 +43,12 @@ export default createWidget("post-edits-indicator", {
className = `${className || ""} wiki`.trim();
if (attrs.version > 1) {
title = `${I18n.t("post.last_edited_on")} ${date}`;
title = I18n.t("post.wiki_last_edited_on", { dateTime: date });
} else {
title = I18n.t("post.wiki.about");
}
} else {
title = `${I18n.t("post.last_edited_on")} ${date}`;
title = I18n.t("post.last_edited_on", { dateTime: date });
}
return this.attach("flat-button", {

View File

@ -17,12 +17,12 @@ createWidget("toggle-summary-description", {
(attrs.topicPostsCount * MIN_POST_READ_TIME) / 60
)
);
return I18n.t("summary.description_time", {
return I18n.messageFormat("summary.description_time_MF", {
replyCount: attrs.topicReplyCount,
readingTime,
});
}
return I18n.t("summary.description", { replyCount: attrs.topicReplyCount });
return I18n.t("summary.description", { count: attrs.topicReplyCount });
},
html(attrs) {

View File

@ -358,15 +358,14 @@ tr.category-topic-link {
> footer {
border-top: 1px solid var(--primary-low);
padding: 7px 10px;
figure {
.category-stat {
float: left;
margin: 3px 7px 0 0;
font-weight: bold;
font-size: $font-down-1;
}
figcaption {
display: inline;
margin: 3px 1em 0 0;
font-weight: normal;
font-size: $font-down-1;
.value {
font-weight: bold;
}
}
.btn {
float: right;

View File

@ -339,7 +339,7 @@ class GroupsController < ApplicationController
end
if users.length > ADD_MEMBERS_LIMIT
return render_json_error(
I18n.t("groups.errors.adding_too_many_users", limit: ADD_MEMBERS_LIMIT)
I18n.t("groups.errors.adding_too_many_users", count: ADD_MEMBERS_LIMIT)
)
end
usernames_already_in_group = group.users.where(id: users.map(&:id)).pluck(:username)

View File

@ -15,8 +15,13 @@ class TopicPoster < OpenStruct
}
end
# TODO: Remove when old list is removed
def [](attr)
public_send(attr)
def name_and_description
if SiteSetting.prioritize_username_in_ux? || user.name.blank?
name = user.username
else
name = user.name
end
I18n.t("js.user.avatar.name_and_description", name: name, description: description)
end
end

View File

@ -9,7 +9,8 @@ class TopicPostersSummary
{
original_poster: I18n.t(:original_poster),
most_recent_poster: I18n.t(:most_recent_poster),
frequent_poster: I18n.t(:frequent_poster)
frequent_poster: I18n.t(:frequent_poster),
joiner: I18n.t(:poster_description_joiner)
}
end
@ -64,7 +65,7 @@ class TopicPostersSummary
end
def descriptions_for(user)
descriptions_by_id[user.id].join ', '
descriptions_by_id[user.id].join(@translations[:joiner])
end
def shuffle_last_poster_to_back_in(summary)

View File

@ -92,13 +92,9 @@
</td>
<td class='posters'>
<% t.posters.each do |poster| %>
<% if poster.moreCount %>
<a class="posters-more-count"><% poster.moreCount %></a>
<% else %>
<a href="<%= Discourse.base_url %>/u/<%= poster.user.username %>" class="<%= poster.extraClasses %>">
<img width="25" height="25" src="<%= poster.user.avatar_template.gsub('{size}', '25') %>" class="avatar" title='<%= h("#{poster.user.username} - #{poster.description}") %>' aria-label='<%= h("#{poster.user.username} - #{poster.description}") %>'>
</a>
<% end %>
<a href="<%= Discourse.base_url %>/u/<%= poster.user.username %>" class="<%= poster.extras %>">
<img width="25" height="25" src="<%= poster.user.avatar_template.gsub('{size}', '25') %>" class="avatar" title='<%= h(poster.name_and_description) %>' aria-label='<%= h(poster.name_and_description) %>'>
</a>
<% end %>
</td>
<td class="replies">

View File

@ -865,6 +865,15 @@ en:
topic_sentence:
one: "%{count} topic"
other: "%{count} topics"
topic_stat:
one: "%{number} / %{unit}"
other: "%{number} / %{unit}"
topic_stat_unit:
week: "week"
month: "month"
topic_stat_all_time:
one: "%{number} total"
other: "%{number} total"
topic_stat_sentence_week:
one: "%{count} new topic in the past week."
other: "%{count} new topics in the past week."
@ -1516,6 +1525,7 @@ en:
avatar:
title: "Profile Picture"
header_title: "profile, messages, bookmarks and preferences"
name_and_description: "%{name} - %{description}"
title:
title: "Title"
none: "(none)"
@ -1592,16 +1602,6 @@ en:
learn_more: "learn more..."
all_time: "total"
all_time_desc: "total topics created"
year: "year"
year_desc: "topics created in the last 365 days"
month: "month"
month_desc: "topics created in the last 30 days"
week: "week"
week_desc: "topics created in the last 7 days"
day: "day"
first_post: First post
mute: Mute
unmute: Unmute
@ -1626,8 +1626,11 @@ en:
summary:
enabled_description: "You're viewing a summary of this topic: the most interesting posts as determined by the community."
description: "There are <b>%{replyCount}</b> replies."
description_time: "There are <b>%{replyCount}</b> replies with an estimated read time of <b>%{readingTime} minutes</b>."
description:
one: "There is <b>%{count}</b> reply."
other: "There are <b>%{count}</b> replies."
# keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details
description_time_MF: "There {replyCount, plural, one {is <b>#</b> reply} other {are <b>#</b> replies}} with an estimated read time of <b>{readingTime, plural, one {# minute} other {# minutes}}</b>."
enable: "Summarize This Topic"
disable: "Show All Posts"
@ -1891,10 +1894,16 @@ en:
error:
title_missing: "Title is required"
title_too_short: "Title must be at least %{min} characters"
title_too_long: "Title can't be more than %{max} characters"
title_too_short:
one: "Title must be at least %{count} character"
other: "Title must be at least %{count} characters"
title_too_long:
one: "Title can't be more than %{count} character"
other: "Title can't be more than %{count} characters"
post_missing: "Post cant be empty"
post_length: "Post must be at least %{min} characters"
post_length:
one: "Post must be at least %{count} character"
other: "Post must be at least %{count} characters"
try_like: "Have you tried the %{heart} button?"
category_missing: "You must choose a category"
tags_missing:
@ -2460,7 +2469,9 @@ en:
go: "go"
jump_bottom: "jump to last post"
jump_prompt: "jump to..."
jump_prompt_of: "of %{count} posts"
jump_prompt_of:
one: "of %{count} post"
other: "of %{count} posts"
jump_prompt_long: "Jump to..."
jump_bottom_with_number: "jump to post %{post_number}"
jump_prompt_to_date: "to date"
@ -2749,8 +2760,8 @@ en:
edit_reason: "Reason: "
post_number: "post %{number}"
ignored: "Ignored content"
wiki_last_edited_on: "wiki last edited on"
last_edited_on: "post last edited on"
wiki_last_edited_on: "wiki last edited on %{dateTime}"
last_edited_on: "post last edited on %{dateTime}"
reply_as_new_topic: "Reply as linked Topic"
reply_as_new_private_message: "Reply as new message to the same recipients"
continue_discussion: "Continuing the discussion from %{postLink}:"
@ -2876,7 +2887,9 @@ en:
unlock_post_description: "allow the poster to edit this post"
delete_topic_disallowed_modal: "You don't have permission to delete this topic. If you really want it to be deleted, submit a flag for moderator attention together with reasoning."
delete_topic_disallowed: "you don't have permission to delete this topic"
delete_topic_confirm_modal: "This topic currently has over %{minViews} views and may be a popular search destination. Are you sure you want to delete this topic entirely, instead of editing it to improve it?"
delete_topic_confirm_modal:
one: "This topic currently has over %{count} view and may be a popular search destination. Are you sure you want to delete this topic entirely, instead of editing it to improve it?"
other: "This topic currently has over %{count} views and may be a popular search destination. Are you sure you want to delete this topic entirely, instead of editing it to improve it?"
delete_topic_confirm_modal_yes: "Yes, delete this topic"
delete_topic_confirm_modal_no: "No, keep this topic"
delete_topic_error: "An error occurred while deleting this topic"
@ -3506,6 +3519,8 @@ en:
one: "%{tags} and %{count} more"
other: "%{tags} and %{count} more"
delete_no_unused_tags: "There are no unused tags."
# Example: "tag1, tag2, tag3"
tag_list_joiner: ", "
delete_unused: "Delete Unused Tags"
delete_unused_description: "Delete all tags which are not attached to any topics or personal messages"
cancel_delete_unused: "Cancel"
@ -3749,7 +3764,9 @@ en:
effects: Effects
trust_levels_none: "None"
automatic_membership_email_domains: "Users who register with an email domain that exactly matches one in this list will be automatically added to this group:"
automatic_membership_user_count: "%{count} users have the new email domains and will be added to the group."
automatic_membership_user_count:
one: "%{count} user has the new email domains and will be added to the group."
other: "%{count} users have the new email domains and will be added to the group."
primary_group: "Automatically set as primary group"
name_placeholder: "Group name, no spaces, same as username rule"
primary: "Primary Group"

View File

@ -444,7 +444,9 @@ en:
email_already_used_in_category: "'%{email}' is already used by the category '%{category_name}'."
cant_allow_membership_requests: "You cannot allow membership requests for a group without any owners."
already_requested_membership: "You have already requested membership for this group."
adding_too_many_users: "Maximum %{limit} users can be added at once"
adding_too_many_users:
one: "Maximum %{count} user can be added at once"
other: "Maximum %{count} users can be added at once"
default_names:
everyone: "everyone"
admins: "admins"
@ -2361,6 +2363,8 @@ en:
most_posts: "Most Posts"
most_recent_poster: "Most Recent Poster"
frequent_poster: "Frequent Poster"
# Example: "Original Poster, Most Recent Poster"
poster_description_joiner: ", "
redirected_to_top_reasons:
new_user: "Welcome to our community! These are the most popular recent topics."

View File

@ -20,8 +20,8 @@ describe TopicPostersSummary do
summary.first.tap do |topic_poster|
expect(topic_poster.user).to eq topic_creator
expect(topic_poster.description).to eq(
"#{I18n.t(:original_poster)}, #{I18n.t(:most_recent_poster)}"
expect(topic_poster.description).to eq([
I18n.t(:original_poster), I18n.t(:most_recent_poster)].join(I18n.t(:poster_description_joiner))
)
end
end

View File

@ -1281,7 +1281,7 @@ describe GroupsController do
expect(response.parsed_body["errors"]).to include(I18n.t(
"groups.errors.adding_too_many_users",
limit: 1
count: 1
))
ensure
GroupsController.send(:remove_const, "ADD_MEMBERS_LIMIT")