Merge pull request #3817 from gschlager/locale-pluralization
FIX: Pluralizations in English locale files
This commit is contained in:
commit
bb76c389fd
|
@ -14,7 +14,13 @@
|
|||
</ul>
|
||||
-->
|
||||
{{else}}
|
||||
<p class="grant-count">{{{i18n 'admin.badges.preview.grant_count' count=count}}}</p>
|
||||
<p class="grant-count">
|
||||
{{#if count}}
|
||||
{{{i18n 'admin.badges.preview.grant_count' count=count}}}
|
||||
{{else}}
|
||||
{{{i18n 'admin.badges.preview.no_grant_count'}}}
|
||||
{{/if}}
|
||||
</p>
|
||||
|
||||
{{#if count_warning}}
|
||||
<div class="count-warning">
|
||||
|
|
|
@ -248,7 +248,9 @@ const PostMenuComponent = Ember.Component.extend(StringBuffer, {
|
|||
if (likeCount > 0) {
|
||||
const likedPost = !!this.get('post.likeAction.acted');
|
||||
|
||||
const label = likedPost ? 'post.has_likes_title_you' : 'post.has_likes_title';
|
||||
const label = likedPost
|
||||
? likeCount === 1 ? 'post.has_likes_title_only_you' : 'post.has_likes_title_you'
|
||||
: 'post.has_likes_title';
|
||||
|
||||
return new Button('like-count', label, undefined, {
|
||||
className: 'like-count highlight-action',
|
||||
|
|
|
@ -41,7 +41,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
|||
|
||||
@computed("categoryLink", "pinnedInCategoryCount")
|
||||
alreadyPinnedMessage(categoryLink, count) {
|
||||
return I18n.t("topic.feature_topic.already_pinned", { categoryLink, count });
|
||||
const key = count === 0 ? "topic.feature_topic.not_pinned" : "topic.feature_topic.already_pinned";
|
||||
return I18n.t(key, { categoryLink, count });
|
||||
},
|
||||
|
||||
@computed("parsedPinnedInCategoryUntil")
|
||||
|
|
|
@ -12,12 +12,13 @@ const NavItem = Discourse.Model.extend({
|
|||
}
|
||||
|
||||
var extra = { count: count };
|
||||
var titleKey = count === 0 ? '.title' : '.title_with_count';
|
||||
|
||||
if (categoryName) {
|
||||
name = 'category';
|
||||
extra.categoryName = toTitleCase(categoryName);
|
||||
}
|
||||
return I18n.t("filters." + name.replace("/", ".") + ".title", extra);
|
||||
return I18n.t("filters." + name.replace("/", ".") + titleKey, extra);
|
||||
}.property('categoryName', 'name', 'count'),
|
||||
|
||||
categoryName: function() {
|
||||
|
|
|
@ -57,7 +57,7 @@ export default (filter, params) => {
|
|||
},
|
||||
|
||||
titleToken() {
|
||||
const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', { count: 0 }),
|
||||
const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title'),
|
||||
category = this.currentModel.category;
|
||||
|
||||
return I18n.t('filters.with_category', { filter: filterText, category: category.get('name') });
|
||||
|
|
|
@ -80,7 +80,7 @@ export default function(filter, extras) {
|
|||
titleToken() {
|
||||
if (filter === Discourse.Utilities.defaultHomepage()) { return; }
|
||||
|
||||
const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', {count: 0});
|
||||
const filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title');
|
||||
return I18n.t('filters.with_topics', {filter: filterText});
|
||||
},
|
||||
|
||||
|
|
|
@ -41,14 +41,22 @@
|
|||
{{/if}}
|
||||
|
||||
{{#menu-links}}
|
||||
<li>{{d-link route="discovery.latest" class="latest-topics-link" label="filters.latest.title.zero"}}</li>
|
||||
<li>{{d-link route="discovery.latest" class="latest-topics-link" label="filters.latest.title"}}</li>
|
||||
|
||||
{{#if currentUser}}
|
||||
<li>
|
||||
{{d-link route="discovery.new" class="new-topics-link" label="filters.new.title" count=newCount}}
|
||||
{{#if newCount}}
|
||||
{{d-link route="discovery.new" class="new-topics-link" label="filters.new.title_with_count" count=newCount}}
|
||||
{{else}}
|
||||
{{d-link route="discovery.new" class="new-topics-link" label="filters.new.title"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
<li>
|
||||
{{d-link route="discovery.unread" class="unread-topics-link" label="filters.unread.title" count=unreadCount}}
|
||||
{{#if unreadCount}}
|
||||
{{d-link route="discovery.unread" class="unread-topics-link" label="filters.unread.title_with_count" count=unreadCount}}
|
||||
{{else}}
|
||||
{{d-link route="discovery.unread" class="unread-topics-link" label="filters.unread.title"}}
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
<li>{{d-link route="discovery.top" class="top-topics-link" label="filters.top.title"}}</li>
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
{{#if model.pinned_globally}}
|
||||
<p>
|
||||
{{#conditional-loading-spinner size="small" condition=loading}}
|
||||
{{#if pinnedGloballyCount}}
|
||||
{{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}}
|
||||
{{else}}
|
||||
{{{i18n "topic.feature_topic.not_pinned_globally"}}}
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
</p>
|
||||
<p>{{i18n "topic.feature_topic.global_pin_note"}}</p>
|
||||
|
@ -48,7 +52,11 @@
|
|||
<div class="desc">
|
||||
<p>
|
||||
{{#conditional-loading-spinner size="small" condition=loading}}
|
||||
{{#if pinnedGloballyCount}}
|
||||
{{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}}
|
||||
{{else}}
|
||||
{{{i18n "topic.feature_topic.not_pinned_globally"}}}
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
</p>
|
||||
<p>
|
||||
|
@ -71,7 +79,11 @@
|
|||
<div class="desc">
|
||||
<p>
|
||||
{{#conditional-loading-spinner size="small" condition=loading}}
|
||||
{{{i18n "topic.feature_topic.already_banner" count=bannerCount}}}
|
||||
{{#if bannerCount}}
|
||||
{{{i18n "topic.feature_topic.banner_exists"}}}
|
||||
{{else}}
|
||||
{{{i18n "topic.feature_topic.no_banner_exists"}}}
|
||||
{{/if}}
|
||||
{{/conditional-loading-spinner}}
|
||||
</p>
|
||||
<p>
|
||||
|
|
|
@ -182,7 +182,11 @@
|
|||
{{preference-checkbox labelKey="user.email_always" checked=model.email_always}}
|
||||
|
||||
<div class='instructions'>
|
||||
{{#if siteSettings.email_time_window_mins}}
|
||||
{{i18n 'user.email.frequency' count=siteSettings.email_time_window_mins}}
|
||||
{{else}}
|
||||
{{i18n 'user.email.frequency_immediately'}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -538,8 +538,8 @@ en:
|
|||
ok: "We will email you to confirm"
|
||||
invalid: "Please enter a valid email address"
|
||||
authenticated: "Your email has been authenticated by {{provider}}"
|
||||
frequency_immediately: "We'll email you immediately if you haven't read the thing we're emailing you about."
|
||||
frequency:
|
||||
zero: "We'll email you immediately if you haven't read the thing we're emailing you about."
|
||||
one: "We'll only email you if we haven't seen you in the last minute."
|
||||
other: "We'll only email you if we haven't seen you in the last {{count}} minutes."
|
||||
|
||||
|
@ -1231,25 +1231,24 @@ en:
|
|||
unpin_until: "Remove this topic from the top of the {{categoryLink}} category or wait until <strong>%{until}</strong>."
|
||||
pin_note: "Users can unpin the topic individually for themselves."
|
||||
pin_validation: "A date is required to pin this topic."
|
||||
not_pinned: "There are no topics pinned in {{categoryLink}}."
|
||||
already_pinned:
|
||||
zero: "There are no topics pinned in {{categoryLink}}."
|
||||
one: "Topics currently pinned in {{categoryLink}}: <strong class='badge badge-notification unread'>1</strong>."
|
||||
other: "Topics currently pinned in {{categoryLink}}: <strong class='badge badge-notification unread'>{{count}}</strong>."
|
||||
one: "Topics currently pinned in {{categoryLink}}: <strong class='badge badge-notification unread'>1</strong>"
|
||||
other: "Topics currently pinned in {{categoryLink}}: <strong class='badge badge-notification unread'>{{count}}</strong>"
|
||||
pin_globally: "Make this topic appear at the top of all topic lists until"
|
||||
confirm_pin_globally: "You already have {{count}} globally pinned topics. Too many pinned topics may be a burden for new and anonymous users. Are you sure you want to pin another topic globally?"
|
||||
unpin_globally: "Remove this topic from the top of all topic lists."
|
||||
unpin_globally_until: "Remove this topic from the top of all topic lists or wait until <strong>%{until}</strong>."
|
||||
global_pin_note: "Users can unpin the topic individually for themselves."
|
||||
not_pinned_globally: "There are no topics pinned globally."
|
||||
already_pinned_globally:
|
||||
zero: "There are no topics pinned globally."
|
||||
one: "Topics currently pinned globally: <strong class='badge badge-notification unread'>1</strong>."
|
||||
other: "Topics currently pinned globally: <strong class='badge badge-notification unread'>{{count}}</strong>."
|
||||
one: "Topics currently pinned globally: <strong class='badge badge-notification unread'>1</strong>"
|
||||
other: "Topics currently pinned globally: <strong class='badge badge-notification unread'>{{count}}</strong>"
|
||||
make_banner: "Make this topic into a banner that appears at the top of all pages."
|
||||
remove_banner: "Remove the banner that appears at the top of all pages."
|
||||
banner_note: "Users can dismiss the banner by closing it. Only one topic can be bannered at any given time."
|
||||
already_banner:
|
||||
zero: "There is no banner topic."
|
||||
one: "There <strong class='badge badge-notification unread'>is</strong> currently a banner topic."
|
||||
no_banner_exists: "There is no banner topic."
|
||||
banner_exists: "There <strong class='badge badge-notification unread'>is</strong> currently a banner topic."
|
||||
|
||||
inviting: "Inviting..."
|
||||
automatically_add_to_groups_optional: "This invite also includes access to these groups: (optional, admin only)"
|
||||
|
@ -1370,8 +1369,8 @@ en:
|
|||
one: "1 person liked this post"
|
||||
other: "{{count}} people liked this post"
|
||||
|
||||
has_likes_title_only_you: "you liked this post"
|
||||
has_likes_title_you:
|
||||
zero: "you liked this post"
|
||||
one: "you and 1 other person liked this post"
|
||||
other: "you and {{count}} other people liked this post"
|
||||
|
||||
|
@ -1521,11 +1520,6 @@ en:
|
|||
one: "1 person voted for this post"
|
||||
other: "{{count}} people voted for this post"
|
||||
|
||||
edits:
|
||||
one: 1 edit
|
||||
other: "{{count}} edits"
|
||||
zero: no edits
|
||||
|
||||
delete:
|
||||
confirm:
|
||||
one: "Are you sure you want to delete that post?"
|
||||
|
@ -1606,8 +1600,6 @@ en:
|
|||
position_disabled_click: 'enable the "fixed category positions" setting.'
|
||||
parent: "Parent Category"
|
||||
notifications:
|
||||
title: ''
|
||||
reasons:
|
||||
watching:
|
||||
title: "Watching"
|
||||
description: "You will automatically watch all new topics in these categories. You will be notified of every new post in every topic, and a count of new replies will be shown."
|
||||
|
@ -1723,8 +1715,8 @@ en:
|
|||
with_topics: "%{filter} topics"
|
||||
with_category: "%{filter} %{category} topics"
|
||||
latest:
|
||||
title:
|
||||
zero: "Latest"
|
||||
title: "Latest"
|
||||
title_with_count:
|
||||
one: "Latest (1)"
|
||||
other: "Latest ({{count}})"
|
||||
help: "topics with recent posts"
|
||||
|
@ -1742,23 +1734,21 @@ en:
|
|||
title_in: "Category - {{categoryName}}"
|
||||
help: "all topics grouped by category"
|
||||
unread:
|
||||
title:
|
||||
zero: "Unread"
|
||||
title: "Unread"
|
||||
title_with_count:
|
||||
one: "Unread (1)"
|
||||
other: "Unread ({{count}})"
|
||||
help: "topics you are currently watching or tracking with unread posts"
|
||||
lower_title_with_count:
|
||||
zero: ""
|
||||
one: "1 unread"
|
||||
other: "{{count}} unread"
|
||||
new:
|
||||
lower_title_with_count:
|
||||
zero: ""
|
||||
one: "1 new"
|
||||
other: "{{count}} new"
|
||||
lower_title: "new"
|
||||
title:
|
||||
zero: "New"
|
||||
title: "New"
|
||||
title_with_count:
|
||||
one: "New (1)"
|
||||
other: "New ({{count}})"
|
||||
help: "topics created in the last few days"
|
||||
|
@ -1769,8 +1759,8 @@ en:
|
|||
title: "Bookmarks"
|
||||
help: "topics you have bookmarked"
|
||||
category:
|
||||
title:
|
||||
zero: "{{categoryName}}"
|
||||
title: "{{categoryName}}"
|
||||
title_with_count:
|
||||
one: "{{categoryName}} (1)"
|
||||
other: "{{categoryName}} ({{count}})"
|
||||
help: "latest topics in the {{categoryName}} category"
|
||||
|
@ -2572,8 +2562,8 @@ en:
|
|||
bad_count_warning:
|
||||
header: "WARNING!"
|
||||
text: "There are missing grant samples. This happens when the badge query returns user IDs or post IDs that do not exist. This may cause unexpected results later on - please double-check your query."
|
||||
no_grant_count: "No badges to be assigned."
|
||||
grant_count:
|
||||
zero: "No badges to be assigned."
|
||||
one: "<b>1</b> badge to be assigned."
|
||||
other: "<b>%{count}</b> badges to be assigned."
|
||||
sample: "Sample:"
|
||||
|
|
|
@ -140,26 +140,27 @@ en:
|
|||
one: "1 reply"
|
||||
other: "%{count} replies"
|
||||
|
||||
no_mentions_allowed: "Sorry, you can't mention other users."
|
||||
too_many_mentions:
|
||||
zero: "Sorry, you can't mention other users."
|
||||
one: "Sorry, you can only mention one other user in a post."
|
||||
other: "Sorry, you can only mention %{count} users in a post."
|
||||
no_mentions_allowed_newuser: "Sorry, new users can't mention other users."
|
||||
too_many_mentions_newuser:
|
||||
zero: "Sorry, new users can't mention other users."
|
||||
one: "Sorry, new users can only mention one other user in a post."
|
||||
other: "Sorry, new users can only mention %{count} users in a post."
|
||||
no_images_allowed: "Sorry, new users can't put images in posts."
|
||||
too_many_images:
|
||||
zero: "Sorry, new users can't put images in posts."
|
||||
one: "Sorry, new users can only put one image in a post."
|
||||
other: "Sorry, new users can only put %{count} images in a post."
|
||||
no_attachments_allowed: "Sorry, new users can't put attachments in posts."
|
||||
too_many_attachments:
|
||||
zero: "Sorry, new users can't put attachments in posts."
|
||||
one: "Sorry, new users can only put one attachment in a post."
|
||||
other: "Sorry, new users can only put %{count} attachments in a post."
|
||||
no_links_allowed: "Sorry, new users can't put links in posts."
|
||||
too_many_links:
|
||||
zero: "Sorry, new users can't put links in posts."
|
||||
one: "Sorry, new users can only put one link in a post."
|
||||
other: "Sorry, new users can only put %{count} links in a post."
|
||||
|
||||
spamming_host: "Sorry you cannot post a link to that host."
|
||||
user_is_suspended: "Suspended users are not allowed to post."
|
||||
topic_not_found: "Something has gone wrong. Perhaps this topic was closed or deleted while you were looking at it?"
|
||||
|
|
|
@ -51,9 +51,9 @@ class Validators::PostValidator < ActiveModel::Validator
|
|||
# Ensure maximum amount of mentions in a post
|
||||
def max_mention_validator(post)
|
||||
if acting_user_is_trusted?(post)
|
||||
add_error_if_count_exceeded(post, :too_many_mentions, post.raw_mentions.size, SiteSetting.max_mentions_per_post)
|
||||
add_error_if_count_exceeded(post, :no_mentions_allowed, :too_many_mentions, post.raw_mentions.size, SiteSetting.max_mentions_per_post)
|
||||
else
|
||||
add_error_if_count_exceeded(post, :too_many_mentions_newuser, post.raw_mentions.size, SiteSetting.newuser_max_mentions_per_post)
|
||||
add_error_if_count_exceeded(post, :no_mentions_allowed_newuser, :too_many_mentions_newuser, post.raw_mentions.size, SiteSetting.newuser_max_mentions_per_post)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,17 +65,17 @@ class Validators::PostValidator < ActiveModel::Validator
|
|||
|
||||
# Ensure new users can not put too many images in a post
|
||||
def max_images_validator(post)
|
||||
add_error_if_count_exceeded(post, :too_many_images, post.image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?(post)
|
||||
add_error_if_count_exceeded(post, :no_images_allowed, :too_many_images, post.image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?(post)
|
||||
end
|
||||
|
||||
# Ensure new users can not put too many attachments in a post
|
||||
def max_attachments_validator(post)
|
||||
add_error_if_count_exceeded(post, :too_many_attachments, post.attachment_count, SiteSetting.newuser_max_attachments) unless acting_user_is_trusted?(post)
|
||||
add_error_if_count_exceeded(post, :no_attachments_allowed, :too_many_attachments, post.attachment_count, SiteSetting.newuser_max_attachments) unless acting_user_is_trusted?(post)
|
||||
end
|
||||
|
||||
# Ensure new users can not put too many links in a post
|
||||
def max_links_validator(post)
|
||||
add_error_if_count_exceeded(post, :too_many_links, post.link_count, SiteSetting.newuser_max_links) unless acting_user_is_trusted?(post)
|
||||
add_error_if_count_exceeded(post, :no_links_allowed, :too_many_links, post.link_count, SiteSetting.newuser_max_links) unless acting_user_is_trusted?(post)
|
||||
end
|
||||
|
||||
# Stop us from posting the same thing too quickly
|
||||
|
@ -98,7 +98,13 @@ class Validators::PostValidator < ActiveModel::Validator
|
|||
post.acting_user.present? && post.acting_user.has_trust_level?(TrustLevel[1])
|
||||
end
|
||||
|
||||
def add_error_if_count_exceeded(post, key_for_translation, current_count, max_count)
|
||||
post.errors.add(:base, I18n.t(key_for_translation, count: max_count)) if current_count > max_count
|
||||
def add_error_if_count_exceeded(post, not_allowed_translation_key, limit_translation_key, current_count, max_count)
|
||||
if current_count > max_count
|
||||
if max_count == 0
|
||||
post.errors.add(:base, I18n.t(not_allowed_translation_key))
|
||||
else
|
||||
post.errors.add(:base, I18n.t(limit_translation_key, count: max_count))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,7 +58,7 @@ describe "i18n integrity checks" do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'keys in English locale files' do
|
||||
describe 'English locale file' do
|
||||
locale_files = ['config/locales', 'plugins/**/locales']
|
||||
.product(['server.en.yml', 'client.en.yml'])
|
||||
.collect { |dir, filename| Dir["#{Rails.root}/#{dir}/#{filename}"] }
|
||||
|
@ -85,12 +85,42 @@ describe "i18n integrity checks" do
|
|||
end
|
||||
end
|
||||
|
||||
module Pluralizations
|
||||
def self.load(path)
|
||||
whitelist = Regexp.union([/messages.restrict_dependent_destroy/])
|
||||
|
||||
yaml = YAML.load_file("#{Rails.root}/#{path}")
|
||||
pluralizations = find_pluralizations(yaml['en'])
|
||||
pluralizations.reject! { |key| key.match(whitelist) }
|
||||
pluralizations
|
||||
end
|
||||
|
||||
def self.find_pluralizations(hash, parent_key = '', pluralizations = Hash.new)
|
||||
hash.each do |key, value|
|
||||
if value.is_a? Hash
|
||||
current_key = parent_key.blank? ? key : "#{parent_key}.#{key}"
|
||||
find_pluralizations(value, current_key, pluralizations)
|
||||
elsif key == 'one' || key == 'other'
|
||||
pluralizations[parent_key] = hash
|
||||
end
|
||||
end
|
||||
|
||||
pluralizations
|
||||
end
|
||||
end
|
||||
|
||||
locale_files.each do |path|
|
||||
context path do
|
||||
it 'has no duplicate keys' do
|
||||
duplicates = DuplicateKeyFinder.new.find_duplicates("#{Rails.root}/#{path}")
|
||||
expect(duplicates).to be_empty
|
||||
end
|
||||
|
||||
Pluralizations.load(path).each do |key, values|
|
||||
it "key '#{key}' has valid pluralizations" do
|
||||
expect(values.keys).to contain_exactly('one', 'other')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue