Merge pull request #3817 from gschlager/locale-pluralization

FIX: Pluralizations in English locale files
This commit is contained in:
Robin Ward 2015-11-17 12:35:24 -05:00
commit bb76c389fd
13 changed files with 116 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,11 @@
{{#if model.pinned_globally}}
<p>
{{#conditional-loading-spinner size="small" condition=loading}}
{{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}}
{{#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}}
{{{i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount}}}
{{#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>

View File

@ -182,7 +182,11 @@
{{preference-checkbox labelKey="user.email_always" checked=model.email_always}}
<div class='instructions'>
{{i18n 'user.email.frequency' count=siteSettings.email_time_window_mins}}
{{#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>

View File

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

View File

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

View File

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

View File

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