+
+ {{/if}}
+ {{/if}}
+ {{/if}}
+
+{{/d-modal-body}}
+
+
diff --git a/app/assets/javascripts/discourse/templates/modal/split-topic.hbs b/app/assets/javascripts/discourse/templates/modal/split-topic.hbs
deleted file mode 100644
index 4022af325a..0000000000
--- a/app/assets/javascripts/discourse/templates/modal/split-topic.hbs
+++ /dev/null
@@ -1,21 +0,0 @@
-{{#d-modal-body id="move-selected" title="topic.split_topic.title"}}
- {{{i18n 'topic.split_topic.instructions' count=selectedPostsCount}}}
-
-
-{{/d-modal-body}}
-
-
diff --git a/app/assets/javascripts/discourse/templates/selected-posts.hbs b/app/assets/javascripts/discourse/templates/selected-posts.hbs
index 781b228276..452fc8b187 100644
--- a/app/assets/javascripts/discourse/templates/selected-posts.hbs
+++ b/app/assets/javascripts/discourse/templates/selected-posts.hbs
@@ -12,12 +12,8 @@
{{d-button action="deleteSelected" icon="trash-o" label="topic.multi_select.delete" class="btn-danger"}}
{{/if}}
-{{#if canSplitTopic}}
- {{d-button action="splitTopic" icon="sign-out" label="topic.split_topic.action"}}
-{{/if}}
-
{{#if canMergeTopic}}
- {{d-button action="mergeTopic" icon="sign-out" label="topic.merge_topic.action"}}
+ {{d-button action=(route-action "moveToTopic") icon="sign-out" label="topic.move_to.action" class="move-to-topic"}}
{{/if}}
{{#if canChangeOwner}}
diff --git a/app/assets/javascripts/discourse/widgets/header-topic-info.js.es6 b/app/assets/javascripts/discourse/widgets/header-topic-info.js.es6
index def37089aa..40237b9c6c 100644
--- a/app/assets/javascripts/discourse/widgets/header-topic-info.js.es6
+++ b/app/assets/javascripts/discourse/widgets/header-topic-info.js.es6
@@ -5,6 +5,54 @@ import DiscourseURL from "discourse/lib/url";
import RawHtml from "discourse/widgets/raw-html";
import renderTags from "discourse/lib/render-tags";
import { topicFeaturedLinkNode } from "discourse/lib/render-topic-featured-link";
+import { avatarImg } from "discourse/widgets/post";
+
+createWidget("topic-header-participant", {
+ tagName: "span",
+
+ buildClasses(attrs) {
+ return `trigger-${attrs.type}-card`;
+ },
+
+ html(attrs) {
+ const { user, group } = attrs;
+ let content, url;
+
+ if (attrs.type === "user") {
+ content = avatarImg("tiny", {
+ template: user.avatar_template,
+ username: user.username
+ });
+ url = user.get("path");
+ } else {
+ content = [iconNode("users")];
+ url = Discourse.getURL(`/groups/${group.name}`);
+ content.push(h("span", group.name));
+ }
+
+ return h(
+ "a.icon",
+ {
+ attributes: {
+ href: url,
+ "data-auto-route": true,
+ title: attrs.username
+ }
+ },
+ content
+ );
+ },
+
+ click(e) {
+ const $target = $(e.target);
+ this.appEvents.trigger(
+ `topic-header:trigger-${this.attrs.type}-card`,
+ this.attrs.username,
+ $target
+ );
+ e.preventDefault();
+ }
+});
export default createWidget("header-topic-info", {
tagName: "div.extra-info-wrapper",
@@ -73,6 +121,58 @@ export default createWidget("header-topic-info", {
extra.push(new RawHtml({ html: tags }));
}
+ if (showPM) {
+ const maxHeaderParticipants = extra.length > 0 ? 5 : 10;
+ const participants = [];
+ const topicDetails = topic.get("details");
+ const totalParticipants =
+ topicDetails.allowed_users.length +
+ topicDetails.allowed_groups.length;
+
+ topicDetails.allowed_users.some(user => {
+ if (participants.length >= maxHeaderParticipants) {
+ return true;
+ }
+
+ participants.push(
+ this.attach("topic-header-participant", {
+ type: "user",
+ user,
+ username: user.username
+ })
+ );
+ });
+
+ topicDetails.allowed_groups.some(group => {
+ if (participants.length >= maxHeaderParticipants) {
+ return true;
+ }
+
+ participants.push(
+ this.attach("topic-header-participant", {
+ type: "group",
+ group,
+ username: group.name
+ })
+ );
+ });
+
+ if (totalParticipants > maxHeaderParticipants) {
+ const remaining = totalParticipants - maxHeaderParticipants;
+ participants.push(
+ this.attach("link", {
+ className: "more-participants",
+ action: "jumpToTopPost",
+ href,
+ attributes: { "data-topic-id": topic.get("id") },
+ contents: () => `+${remaining}`
+ })
+ );
+ }
+
+ extra.push(h("div.topic-header-participants", participants));
+ }
+
extra = extra.concat(applyDecorators(this, "after-tags", attrs, state));
if (this.siteSettings.topic_featured_link_enabled) {
diff --git a/app/assets/javascripts/discourse/widgets/post-menu.js.es6 b/app/assets/javascripts/discourse/widgets/post-menu.js.es6
index c796a5e1fb..a0f00d1a73 100644
--- a/app/assets/javascripts/discourse/widgets/post-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/post-menu.js.es6
@@ -267,7 +267,7 @@ registerButton("delete", attrs => {
return {
id: "delete_topic",
action: "deletePost",
- title: "topic.actions.delete",
+ title: "post.controls.delete_topic",
icon: "trash-o",
className: "delete"
};
diff --git a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
index b029650af9..b73cdf52d3 100644
--- a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
@@ -211,15 +211,13 @@ export default createWidget("topic-admin-menu", {
});
}
- if (this.currentUser.get("staff")) {
- buttons.push({
- className: "topic-admin-reset-bump-date",
- buttonClass: "btn-default",
- action: "resetBumpDate",
- icon: "anchor",
- label: "actions.reset_bump_date"
- });
- }
+ buttons.push({
+ className: "topic-admin-reset-bump-date",
+ buttonClass: "btn-default",
+ action: "resetBumpDate",
+ icon: "anchor",
+ label: "actions.reset_bump_date"
+ });
if (!isPrivateMessage) {
buttons.push({
diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6
index 4229ab3418..2270d8a612 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/bbcode-block.js.es6
@@ -344,6 +344,11 @@ export function setup(helper) {
helper.registerPlugin(md => {
const ruler = md.block.bbcode.ruler;
+ ruler.push("excerpt", {
+ tag: "excerpt",
+ wrap: "div.excerpt"
+ });
+
ruler.push("code", {
tag: "code",
replace: function(state, tagInfo, content) {
diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/category-hashtag.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/category-hashtag.js.es6
index e37f9066f9..5e997dfab6 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/category-hashtag.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/category-hashtag.js.es6
@@ -44,7 +44,7 @@ function addHashtag(buffer, matches, state) {
export function setup(helper) {
helper.registerPlugin(md => {
const rule = {
- matcher: /#([\w-:]{1,101})/,
+ matcher: /#([\u00C0-\u1FFF\u2C00-\uD7FF\w-:]{1,101})/,
onMatch: addHashtag
};
diff --git a/app/assets/javascripts/select-kit/components/composer-actions.js.es6 b/app/assets/javascripts/select-kit/components/composer-actions.js.es6
index 9beff4acab..dc63706985 100644
--- a/app/assets/javascripts/select-kit/components/composer-actions.js.es6
+++ b/app/assets/javascripts/select-kit/components/composer-actions.js.es6
@@ -193,8 +193,11 @@ export default DropdownSelectBoxComponent.extend({
}
const currentUser = Discourse.User.current();
+ const showToggleTopicBump =
+ currentUser &&
+ (currentUser.get("staff") || currentUser.trust_level === 4);
- if (action === REPLY && currentUser && currentUser.get("staff")) {
+ if (action === REPLY && showToggleTopicBump) {
items.push({
name: I18n.t("composer.composer_actions.toggle_topic_bump.label"),
description: I18n.t("composer.composer_actions.toggle_topic_bump.desc"),
@@ -298,6 +301,7 @@ export default DropdownSelectBoxComponent.extend({
options.action = action;
options.categoryId = this.get("composerModel.categoryId");
options.topicTitle = this.get("composerModel.title");
+ options.skipDraftCheck = true;
this._openComposer(options);
},
diff --git a/app/assets/javascripts/select-kit/components/period-chooser.js.es6 b/app/assets/javascripts/select-kit/components/period-chooser.js.es6
index 4aad9fd22b..88896a4070 100644
--- a/app/assets/javascripts/select-kit/components/period-chooser.js.es6
+++ b/app/assets/javascripts/select-kit/components/period-chooser.js.es6
@@ -5,7 +5,7 @@ export default DropdownSelectBoxComponent.extend({
classNames: ["period-chooser"],
rowComponent: "period-chooser/period-chooser-row",
headerComponent: "period-chooser/period-chooser-header",
- content: Ember.computed.alias("site.periods"),
+ content: Ember.computed.oneWay("site.periods"),
value: Ember.computed.alias("period"),
isHidden: Ember.computed.alias("showPeriods"),
diff --git a/app/assets/javascripts/select-kit/components/select-kit.js.es6 b/app/assets/javascripts/select-kit/components/select-kit.js.es6
index 36d1a4ca1a..b907e875b7 100644
--- a/app/assets/javascripts/select-kit/components/select-kit.js.es6
+++ b/app/assets/javascripts/select-kit/components/select-kit.js.es6
@@ -25,10 +25,6 @@ export default Ember.Component.extend(
"isExpanded",
"isDisabled",
"isHidden",
- "isAbove",
- "isBelow",
- "isLeftAligned",
- "isRightAligned",
"hasSelection",
"hasReachedMaximum",
"hasReachedMinimum"
diff --git a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6 b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6
index d490fa0abe..ccbdfc8a7c 100644
--- a/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6
+++ b/app/assets/javascripts/select-kit/mixins/dom-helpers.js.es6
@@ -63,10 +63,9 @@ export default Ember.Mixin.create({
return this.$(this.filterInputSelector);
},
- @on("didRender")
_adjustPosition() {
- this._applyFixedPosition();
this._applyDirection();
+ this._applyFixedPosition();
this._positionWrapper();
},
@@ -124,15 +123,27 @@ export default Ember.Mixin.create({
});
this.focusFilterOrHeader();
this.autoHighlight();
- this._boundaryActionHandler("onExpand", this);
+
+ Ember.run.next(() => {
+ this._boundaryActionHandler("onExpand", this);
+ Ember.run.schedule("afterRender", () => {
+ if (!this.isDestroying && !this.isDestroyed) {
+ this._adjustPosition();
+ }
+ });
+ });
},
collapse() {
this.set("isExpanded", false);
Ember.run.next(() => {
- Ember.run.schedule("afterRender", () => this._removeFixedPosition());
this._boundaryActionHandler("onCollapse", this);
+ Ember.run.schedule("afterRender", () => {
+ if (!this.isDestroying && !this.isDestroyed) {
+ this._removeFixedPosition();
+ }
+ });
});
},
@@ -181,38 +192,61 @@ export default Ember.Mixin.create({
: windowWidth;
const bodyWidth = this._computedStyle(this.$body()[0], "width");
- let marginToEdge;
+ let spaceToLeftEdge;
if (this.$scrollableParent().length) {
- marginToEdge =
+ spaceToLeftEdge =
this.$().offset().left - this.$scrollableParent().offset().left;
} else {
- marginToEdge = this.get("element").getBoundingClientRect().left;
+ spaceToLeftEdge = this.get("element").getBoundingClientRect().left;
}
- const enoughMarginToOppositeEdge =
- parentWidth - marginToEdge - bodyWidth + this.get("horizontalOffset") >
- 0;
- if (enoughMarginToOppositeEdge) {
- this.setProperties({ isLeftAligned: true, isRightAligned: false });
- options.left = this.get("horizontalOffset");
- options.right = "unset";
+ let isLeftAligned = true;
+ const spaceToRightEdge = parentWidth - spaceToLeftEdge;
+ const elementWidth = this.get("element").getBoundingClientRect().width;
+ if (spaceToRightEdge > spaceToLeftEdge + elementWidth) {
+ isLeftAligned = false;
+ }
+
+ if (isLeftAligned) {
+ this.$()
+ .addClass("is-left-aligned")
+ .removeClass("is-right-aligned");
+
+ if (this._isRTL()) {
+ options.right = this.get("horizontalOffset");
+ } else {
+ options.left =
+ -bodyWidth + elementWidth - this.get("horizontalOffset");
+ }
} else {
- this.setProperties({ isLeftAligned: false, isRightAligned: true });
- options.left = "unset";
- options.right = this.get("horizontalOffset");
+ this.$()
+ .addClass("is-right-aligned")
+ .removeClass("is-left-aligned");
+
+ if (this._isRTL()) {
+ options.right =
+ -bodyWidth + elementWidth - this.get("horizontalOffset");
+ } else {
+ options.left = this.get("horizontalOffset");
+ }
}
}
const fullHeight =
this.get("verticalOffset") + bodyHeight + componentHeight;
- const hasBelowSpace = $(window).height() - offsetBottom - fullHeight > 0;
- const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight > 0;
+ const hasBelowSpace = $(window).height() - offsetBottom - fullHeight >= -1;
+ const hasAboveSpace = offsetTop - fullHeight - discourseHeaderHeight >= -1;
const headerHeight = this._computedStyle(this.$header()[0], "height");
+
if (hasBelowSpace || (!hasBelowSpace && !hasAboveSpace)) {
- this.setProperties({ isBelow: true, isAbove: false });
+ this.$()
+ .addClass("is-below")
+ .removeClass("is-above");
options.top = headerHeight + this.get("verticalOffset");
} else {
- this.setProperties({ isBelow: false, isAbove: true });
+ this.$()
+ .addClass("is-above")
+ .removeClass("is-below");
options.bottom = headerHeight + this.get("verticalOffset");
}
diff --git a/app/assets/javascripts/wizard/lib/preview.js.es6 b/app/assets/javascripts/wizard/lib/preview.js.es6
index 887a72ba18..59732d6868 100644
--- a/app/assets/javascripts/wizard/lib/preview.js.es6
+++ b/app/assets/javascripts/wizard/lib/preview.js.es6
@@ -151,19 +151,32 @@ export function createPreviewComponent(width, height, obj) {
);
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 45, 55);
- const headerFontSize = headerHeight / 44;
-
- ctx.font = `${headerFontSize}em FontAwesome`;
- ctx.fillText(
- "\uf0c9",
- width - avatarSize * 2 - headerMargin * 0.5,
- avatarSize
+ const pathScale = headerHeight / 1200;
+ // search icon SVG path
+ const searchIcon = new Path2D(
+ "M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"
);
- ctx.fillText(
- "\uf002",
+ // hamburger icon
+ const hamburgerIcon = new Path2D(
+ "M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
+ );
+ ctx.save(); // Save the previous state for translation and scale
+ ctx.translate(
width - avatarSize * 3 - headerMargin * 0.5,
- avatarSize
+ avatarSize / 2
);
+ // need to scale paths otherwise they're too large
+ ctx.scale(pathScale, pathScale);
+ ctx.fill(searchIcon);
+ ctx.restore();
+ ctx.save();
+ ctx.translate(
+ width - avatarSize * 2 - headerMargin * 0.5,
+ avatarSize / 2
+ );
+ ctx.scale(pathScale, pathScale);
+ ctx.fill(hamburgerIcon);
+ ctx.restore();
},
drawPills(colors, headerHeight, opts) {
@@ -176,37 +189,41 @@ export function createPreviewComponent(width, height, obj) {
const headerMargin = headerHeight * 0.2;
ctx.beginPath();
- ctx.fillStyle = darkLightDiff(
- colors.primary,
- colors.secondary,
- 90,
- -65
- );
+ ctx.strokeStyle = colors.primary;
+ ctx.lineWidth = 0.5;
ctx.rect(
headerMargin,
headerHeight + headerMargin,
categoriesSize,
badgeHeight
);
- ctx.fill();
+ ctx.stroke();
const fontSize = Math.round(badgeHeight * 0.5);
+
ctx.font = `${fontSize}px 'Arial'`;
ctx.fillStyle = colors.primary;
ctx.fillText(
"all categories",
headerMargin * 1.5,
- headerHeight + headerMargin * 1.42 + fontSize
+ headerHeight + headerMargin * 1.4 + fontSize
);
- ctx.font = "0.9em 'FontAwesome'";
- ctx.fillStyle = colors.primary;
- ctx.fillText(
- "\uf0da",
- categoriesSize - headerMargin / 4,
- headerHeight + headerMargin * 1.6 + fontSize
+ const pathScale = badgeHeight / 1000;
+ // caret icon
+ const caretIcon = new Path2D(
+ "M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
);
+ ctx.save();
+ ctx.translate(
+ categoriesSize - headerMargin / 4,
+ headerHeight + headerMargin + badgeHeight / 4
+ );
+ ctx.scale(pathScale, pathScale);
+ ctx.fill(caretIcon);
+ ctx.restore();
+
const text = opts.categories ? "Categories" : "Latest";
const activeWidth = categoriesSize * (opts.categories ? 0.8 : 0.55);
diff --git a/app/assets/stylesheets/common/admin/admin_base.scss b/app/assets/stylesheets/common/admin/admin_base.scss
index 6662105f5f..ead6a3ede0 100644
--- a/app/assets/stylesheets/common/admin/admin_base.scss
+++ b/app/assets/stylesheets/common/admin/admin_base.scss
@@ -470,7 +470,7 @@ $mobile-breakpoint: 700px;
nav {
display: inline-flex;
position: relative;
- flex: 1;
+ flex: 1 0 0;
height: auto;
overflow: hidden;
padding: 0;
@@ -892,7 +892,7 @@ table#user-badges {
.value-input {
box-sizing: border-box;
- flex: 1;
+ flex: 1 0 0;
border-color: $primary-low;
cursor: pointer;
margin: 0;
@@ -919,7 +919,7 @@ table#user-badges {
margin-left: -0.25em;
margin-top: -0.125em;
.new-value-input {
- flex: 1;
+ flex: 1 0 0;
}
.value-input,
.new-value-input {
diff --git a/app/assets/stylesheets/common/admin/admin_report.scss b/app/assets/stylesheets/common/admin/admin_report.scss
index 031363bad0..395d7590ae 100644
--- a/app/assets/stylesheets/common/admin/admin_report.scss
+++ b/app/assets/stylesheets/common/admin/admin_report.scss
@@ -132,7 +132,7 @@
.mode {
display: inline-flex;
- flex: 1;
+ flex: 1 0 0;
.mode-btn.is-current {
color: $tertiary;
@@ -184,3 +184,9 @@
margin-right: auto;
}
}
+
+.admin-report.storage-stats {
+ .main {
+ flex: 1 0 auto;
+ }
+}
diff --git a/app/assets/stylesheets/common/admin/admin_report_counters.scss b/app/assets/stylesheets/common/admin/admin_report_counters.scss
index a2e1c7c9bc..476b65bbab 100644
--- a/app/assets/stylesheets/common/admin/admin_report_counters.scss
+++ b/app/assets/stylesheets/common/admin/admin_report_counters.scss
@@ -1,7 +1,7 @@
.admin-report {
.admin-report-counters {
display: grid;
- flex: 1;
+ flex: 1 0 0;
grid-template-columns: 33% repeat(auto-fit, minmax(20px, 1fr));
grid-template-rows: repeat(auto-fit, minmax(32px, 1fr));
align-items: center;
diff --git a/app/assets/stylesheets/common/admin/admin_reports.scss b/app/assets/stylesheets/common/admin/admin_reports.scss
index 137bf814e5..7481cadb99 100644
--- a/app/assets/stylesheets/common/admin/admin_reports.scss
+++ b/app/assets/stylesheets/common/admin/admin_reports.scss
@@ -13,20 +13,6 @@
height: 400px;
}
- .reports-list {
- list-style: none;
- margin: 0;
-
- .report {
- padding-bottom: 0.5em;
- margin-bottom: 0.5em;
-
- .report-description {
- margin: 0;
- }
- }
- }
-
.report-container {
display: flex;
@@ -41,7 +27,7 @@
.filters {
display: flex;
- flex: 1;
+ flex: 1 0 0;
align-items: center;
flex-direction: column;
margin-left: 2em;
diff --git a/app/assets/stylesheets/common/admin/dashboard_next.scss b/app/assets/stylesheets/common/admin/dashboard_next.scss
index 1ba8f9a6c8..eb4e6bc172 100644
--- a/app/assets/stylesheets/common/admin/dashboard_next.scss
+++ b/app/assets/stylesheets/common/admin/dashboard_next.scss
@@ -43,6 +43,10 @@
@include active-navigation-item;
}
+ &.dashboard-next-reports .navigation-item.reports {
+ @include active-navigation-item;
+ }
+
&.general .navigation-item.general {
@include active-navigation-item;
}
@@ -191,7 +195,7 @@
display: flex;
border: 1px solid $primary-low;
- .durability,
+ .storage-stats,
.last-dashboard-update {
flex: 1 1 50%;
box-sizing: border-box;
@@ -199,7 +203,7 @@
padding: 0 1em;
}
- .durability {
+ .storage-stats {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@@ -213,15 +217,11 @@
.uploads p:last-of-type {
margin-bottom: 0;
}
-
- .durability-title {
- text-transform: capitalize;
- }
}
@media screen and (max-width: 400px) {
flex-wrap: wrap;
- .durability,
+ .storage-stats,
.last-dashboard-update {
flex: 1 1 100%;
text-align: left;
@@ -286,7 +286,7 @@
.counters-list {
display: flex;
- flex: 1;
+ flex: 1 0 0;
flex-direction: column;
.counters-header {
@@ -530,3 +530,33 @@
grid-row-gap: 1em;
}
}
+
+.dashboard-next-reports {
+ .reports-list {
+ display: flex;
+ flex-wrap: wrap;
+ list-style-type: none;
+ margin: 0 -1.5%;
+ }
+ .report {
+ margin: 1.5%;
+ border: 1px solid $primary-low;
+ flex: 1 1 28%;
+ transition: box-shadow 0.25s;
+ min-width: 225px;
+ max-width: 550px;
+ a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+ padding: 1em;
+ .report-description {
+ color: $primary-high;
+ }
+ }
+ &:hover {
+ box-shadow: shadow("card");
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/base/_topic-list.scss b/app/assets/stylesheets/common/base/_topic-list.scss
index 8bf1386fd2..d276ea033c 100644
--- a/app/assets/stylesheets/common/base/_topic-list.scss
+++ b/app/assets/stylesheets/common/base/_topic-list.scss
@@ -133,22 +133,23 @@
.link-bottom-line {
font-size: $font-down-1;
- a.badge-wrapper.box {
+ display: flex;
+ align-items: center;
+ a.badge-wrapper.box,
+ a.discourse-tag.box {
padding-top: 0;
padding-bottom: 0;
}
+ .discourse-tag.simple:after,
+ .discourse-tag.box {
+ margin-right: 0.25em;
+ }
}
.topic-featured-link {
padding-left: 5px;
}
- span.badge-category {
- .category-name {
- max-width: 150px;
- }
- }
-
.topic-excerpt {
font-size: $font-down-1;
margin-top: 5px;
diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss
index 238ad5e709..c11f336e62 100644
--- a/app/assets/stylesheets/common/base/compose.scss
+++ b/app/assets/stylesheets/common/base/compose.scss
@@ -216,7 +216,7 @@
}
.category-chooser {
display: flex;
- flex: 1;
+ flex: 1 0 auto;
width: auto;
}
}
diff --git a/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss b/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss
index 24edccbaa9..9901a4e35c 100644
--- a/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss
+++ b/app/assets/stylesheets/common/base/edit-topic-status-update-modal.scss
@@ -60,6 +60,6 @@
// mobile styles
.mobile-view .edit-topic-timer-modal {
.select-kit.combo-box {
- flex: 1;
+ flex: 1 0 0;
}
}
diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss
index 710027de16..df07721f59 100644
--- a/app/assets/stylesheets/common/base/emoji.scss
+++ b/app/assets/stylesheets/common/base/emoji.scss
@@ -26,7 +26,7 @@ sup img.emoji {
.emoji-picker .categories-column {
display: flex;
flex-direction: column;
- flex: 1;
+ flex: 1 0 0;
align-items: center;
justify-content: space-between;
border-right: 1px solid dark-light-diff($primary, $secondary, 90%, -60%);
@@ -61,7 +61,7 @@ sup img.emoji {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
padding: 0;
- flex: 1;
+ flex: 1 0 0;
flex-direction: column;
}
@@ -223,7 +223,7 @@ sup img.emoji {
.emoji-picker .filter input {
height: 24px;
margin: 0;
- flex: 1;
+ flex: 1 0 0;
border: none;
box-shadow: none;
padding-right: 24px;
@@ -247,7 +247,7 @@ sup img.emoji {
align-items: center;
justify-content: flex-start;
padding: 4px;
- flex: 1;
+ flex: 1 0 0;
}
.emoji-picker .filter .clear-filter {
diff --git a/app/assets/stylesheets/common/base/header.scss b/app/assets/stylesheets/common/base/header.scss
index 1a7708a1c6..081c35c25c 100644
--- a/app/assets/stylesheets/common/base/header.scss
+++ b/app/assets/stylesheets/common/base/header.scss
@@ -9,7 +9,6 @@
box-shadow: shadow("header");
> .wrap {
- box-sizing: border-box;
width: 100%;
height: 100%;
.contents {
@@ -298,3 +297,39 @@
min-width: 0;
}
}
+
+.topic-header-participants {
+ &:not(:first-child) {
+ margin-left: 4px;
+ }
+
+ > span {
+ margin: 0 2px;
+ display: inline-block;
+ height: 20px;
+ }
+
+ .trigger-group-card {
+ height: 16px;
+ margin: 0 4px;
+ padding: 1px 4px;
+ border: 1px solid $primary-low;
+ border-radius: 0.25em;
+ align-items: center;
+
+ a {
+ color: $primary-high;
+
+ .d-icon {
+ margin-right: 4px;
+ }
+ }
+ }
+
+ .more-participants {
+ display: inline-block;
+ color: $header_primary-high;
+ line-height: 20px;
+ padding: 0 4px;
+ }
+}
diff --git a/app/assets/stylesheets/common/base/lightbox.scss b/app/assets/stylesheets/common/base/lightbox.scss
index 7fc53af519..1c1e46ade0 100644
--- a/app/assets/stylesheets/common/base/lightbox.scss
+++ b/app/assets/stylesheets/common/base/lightbox.scss
@@ -1,6 +1,7 @@
.lightbox-wrapper .lightbox {
position: relative;
display: inline-block;
+ overflow: hidden;
background: $primary-low;
&:hover .meta {
opacity: 0.9;
@@ -9,10 +10,13 @@
}
.d-lazyload-hidden {
- opacity: 0;
box-sizing: border-box;
}
+.onebox img.d-lazyload-hidden {
+ border: 1px solid $primary-low;
+}
+
.cooked img.d-lazyload {
transition: opacity 0.4s 0.75s ease;
}
diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss
index e9017d2112..2713ec0dce 100644
--- a/app/assets/stylesheets/common/base/modal.scss
+++ b/app/assets/stylesheets/common/base/modal.scss
@@ -567,7 +567,7 @@
.left,
.right {
- flex: 1;
+ flex: 1 0 0;
}
.text {
diff --git a/app/assets/stylesheets/common/base/search.scss b/app/assets/stylesheets/common/base/search.scss
index 18bfe3ceb0..717851c4dd 100644
--- a/app/assets/stylesheets/common/base/search.scss
+++ b/app/assets/stylesheets/common/base/search.scss
@@ -10,7 +10,7 @@
margin-bottom: 1em;
.search-query {
- flex: 1;
+ flex: 1 0 0;
margin: 0 0.5em 0 0;
}
diff --git a/app/assets/stylesheets/common/base/tagging.scss b/app/assets/stylesheets/common/base/tagging.scss
index e85e138a28..23d38678f1 100644
--- a/app/assets/stylesheets/common/base/tagging.scss
+++ b/app/assets/stylesheets/common/base/tagging.scss
@@ -124,14 +124,9 @@ $tag-color: $primary-medium;
}
.topic-list-item .discourse-tags {
- display: inline-block;
+ display: inline-flex;
font-weight: normal;
font-size: $font-down-1;
-
- .discourse-tag.box {
- position: relative;
- top: 2px;
- }
}
.categories-list .topic-list-latest .discourse-tags {
@@ -153,15 +148,16 @@ $tag-color: $primary-medium;
}
.discourse-tag.bullet {
- margin-right: 0.25em;
+ margin-right: 0.5em;
+ display: inline-flex;
+ align-items: center;
&:before {
background: $primary-low-mid;
margin-right: 5px;
position: relative;
- width: 8px;
- height: 8px;
+ width: 9px;
+ height: 9px;
display: inline-block;
- vertical-align: middle;
content: "";
}
}
diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss
index 9ddaf96644..bb27732d2d 100644
--- a/app/assets/stylesheets/common/base/topic-post.scss
+++ b/app/assets/stylesheets/common/base/topic-post.scss
@@ -396,7 +396,7 @@ aside.quote {
.remove-invited {
display: flex;
- flex: 1;
+ flex: 1 0 0;
align-items: center;
justify-content: center;
box-sizing: border-box;
@@ -419,7 +419,8 @@ aside.quote {
.avatar-flair-preview,
.user-card-avatar,
.topic-map .poster,
-.user-profile-avatar {
+.user-profile-avatar,
+.user-image {
.avatar-flair {
display: flex;
align-items: center;
@@ -433,7 +434,8 @@ aside.quote {
}
.topic-avatar .avatar-flair,
.avatar-flair-preview .avatar-flair,
-.collapsed-info .user-profile-avatar .avatar-flair {
+.collapsed-info .user-profile-avatar .avatar-flair,
+.user-image .avatar-flair {
background-size: 20px 20px;
width: 20px;
height: 20px;
diff --git a/app/assets/stylesheets/common/base/user-badges.scss b/app/assets/stylesheets/common/base/user-badges.scss
index 332189afb9..db433abf70 100644
--- a/app/assets/stylesheets/common/base/user-badges.scss
+++ b/app/assets/stylesheets/common/base/user-badges.scss
@@ -4,14 +4,12 @@
color: $primary;
border: 1px solid $primary-low;
line-height: $line-height-large;
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
background-color: $secondary;
margin: 0 0 3px;
-
- .fa {
- padding-right: 3px;
- font-size: 1.4em;
- vertical-align: bottom;
+ .d-icon {
+ margin-right: 0.25em;
}
img {
diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss
index fef2b60148..a7ac6f0b95 100644
--- a/app/assets/stylesheets/common/base/user.scss
+++ b/app/assets/stylesheets/common/base/user.scss
@@ -156,7 +156,7 @@
list-style-type: none;
}
- a {
+ .btn {
margin-bottom: 10px;
line-height: $line-height-medium;
}
diff --git a/app/assets/stylesheets/common/components/badges.scss b/app/assets/stylesheets/common/components/badges.scss
index cb1cbe4ee3..aaae8aba0e 100644
--- a/app/assets/stylesheets/common/components/badges.scss
+++ b/app/assets/stylesheets/common/components/badges.scss
@@ -51,6 +51,7 @@
}
.badge-category-parent-bg,
.badge-category-bg {
+ flex: 0 0 auto;
width: 9px;
height: 9px;
margin-right: 5px;
diff --git a/app/assets/stylesheets/common/components/svg.scss b/app/assets/stylesheets/common/components/svg.scss
index d4b88f1b2f..b7955f9d1b 100644
--- a/app/assets/stylesheets/common/components/svg.scss
+++ b/app/assets/stylesheets/common/components/svg.scss
@@ -11,6 +11,11 @@
overflow: visible;
}
+// Fixes Edge bug with SVG elements not triggering click event
+svg > use {
+ pointer-events: none;
+}
+
// Stacked Icons
// Usage:
//
diff --git a/app/assets/stylesheets/common/components/user-info.scss b/app/assets/stylesheets/common/components/user-info.scss
index a71e7ef1fc..fee047c488 100644
--- a/app/assets/stylesheets/common/components/user-info.scss
+++ b/app/assets/stylesheets/common/components/user-info.scss
@@ -45,18 +45,6 @@
}
}
- .avatar-flair {
- background-repeat: no-repeat;
- background-position: center;
- position: absolute;
- bottom: -2px;
- right: -8px;
- background-size: 18px 18px;
- border-radius: 12px;
- width: 24px;
- height: 24px;
- }
-
&.small {
width: 333px;
@media screen and (max-width: $small-width) {
diff --git a/app/assets/stylesheets/common/foundation/variables.scss b/app/assets/stylesheets/common/foundation/variables.scss
index dd8edcb288..114811ef58 100644
--- a/app/assets/stylesheets/common/foundation/variables.scss
+++ b/app/assets/stylesheets/common/foundation/variables.scss
@@ -230,7 +230,7 @@ $highlight-medium: dark-light-diff($highlight, $secondary, 50%, -55%);
$highlight-high: dark-light-diff($highlight, $secondary, -50%, -10%);
//danger
-$danger-low: dark-light-diff($danger, $secondary, 85%, -85%);
+$danger-low: dark-light-diff($danger, $secondary, 85%, -64%);
$danger-medium: dark-light-diff($danger, $secondary, 30%, -35%);
//success
diff --git a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss
index 39db4c20eb..b83f8334a9 100644
--- a/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss
+++ b/app/assets/stylesheets/common/select-kit/mini-tag-chooser.scss
@@ -21,6 +21,8 @@
}
.select-kit-header {
+ height: 30px;
+
.selected-name {
margin: 0;
border: 0;
@@ -28,6 +30,7 @@
outline: none;
box-shadow: none;
cursor: pointer;
+ max-width: 250px;
}
}
diff --git a/app/assets/stylesheets/common/select-kit/multi-select.scss b/app/assets/stylesheets/common/select-kit/multi-select.scss
index 0de88ae66b..e959a6a3b2 100644
--- a/app/assets/stylesheets/common/select-kit/multi-select.scss
+++ b/app/assets/stylesheets/common/select-kit/multi-select.scss
@@ -17,7 +17,7 @@
.select-kit-filter {
border: 0;
- flex: 1;
+ flex: 1 0 0;
margin: 1px;
}
@@ -89,7 +89,7 @@
min-width: 50px;
padding: 0;
outline: none;
- flex: 1;
+ flex: 1 0 0;
.filter-input,
.filter-input:focus {
@@ -112,7 +112,7 @@
.selected-color {
.selected-color-wrapper {
display: flex;
- flex: 1;
+ flex: 1 0 0;
flex-direction: column;
}
@@ -137,7 +137,7 @@
padding: 2px 4px;
line-height: $line-height-medium;
display: flex;
- flex: 1;
+ flex: 1 0 0;
align-items: center;
}
}
diff --git a/app/assets/stylesheets/common/select-kit/period-chooser.scss b/app/assets/stylesheets/common/select-kit/period-chooser.scss
index 3b2db54c93..4cfb90b2c7 100644
--- a/app/assets/stylesheets/common/select-kit/period-chooser.scss
+++ b/app/assets/stylesheets/common/select-kit/period-chooser.scss
@@ -57,7 +57,7 @@
display: flex;
.period-title {
- flex: 1;
+ flex: 1 0 0;
}
.date-section {
diff --git a/app/assets/stylesheets/common/select-kit/select-kit.scss b/app/assets/stylesheets/common/select-kit/select-kit.scss
index f70e9c9da0..cd5c7576d2 100644
--- a/app/assets/stylesheets/common/select-kit/select-kit.scss
+++ b/app/assets/stylesheets/common/select-kit/select-kit.scss
@@ -24,6 +24,8 @@
z-index: z("dropdown");
.select-kit-body {
+ -webkit-animation: fadein 0.25s;
+ animation: fadein 0.25s;
display: flex;
flex-direction: column;
left: 0;
@@ -212,7 +214,7 @@
background: none;
margin: 0;
padding: 0;
- flex: 1;
+ flex: 1 0 0;
outline: none;
border: 0;
border-radius: 0;
diff --git a/app/assets/stylesheets/desktop/category-list.scss b/app/assets/stylesheets/desktop/category-list.scss
index fbccd13f44..f00b409bb0 100644
--- a/app/assets/stylesheets/desktop/category-list.scss
+++ b/app/assets/stylesheets/desktop/category-list.scss
@@ -109,7 +109,7 @@
flex-flow: row wrap;
div.column {
- flex: 1;
+ flex: 1 0 0;
flex-direction: row;
min-width: 300px;
}
diff --git a/app/assets/stylesheets/desktop/latest-topic-list.scss b/app/assets/stylesheets/desktop/latest-topic-list.scss
index cac4055b44..9bfb716aaf 100644
--- a/app/assets/stylesheets/desktop/latest-topic-list.scss
+++ b/app/assets/stylesheets/desktop/latest-topic-list.scss
@@ -41,7 +41,7 @@
}
}
.topic-stats {
- flex: 1;
+ flex: 1 0 0;
text-align: right;
color: dark-light-choose($primary-medium, $secondary-high);
}
diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss
index 4fe10c2b89..895292e943 100644
--- a/app/assets/stylesheets/desktop/modal.scss
+++ b/app/assets/stylesheets/desktop/modal.scss
@@ -113,19 +113,28 @@
}
}
-.split-modal {
+.move-to-modal {
.modal-body {
position: relative;
height: 350px;
}
#move-selected {
+ width: 475px;
+
p {
margin-top: 0;
}
- input[type="radio"] {
- margin-right: 10px;
+ .radios {
+ margin-bottom: 10px;
+ display: flex;
+ flex-direction: row;
+
+ .radio-label {
+ display: inline-block;
+ padding-right: 15px;
+ }
}
button {
@@ -142,9 +151,19 @@
width: 95%;
margin-top: 20px;
#split-topic-name,
- #choose-topic-title {
+ #choose-topic-title,
+ #choose-message-title {
width: 100%;
}
+
+ .participant-selector {
+ width: 100%;
+ }
+
+ div.ac-wrap {
+ width: 100%;
+ margin-bottom: 9px;
+ }
}
}
}
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss
index 7cfbad661c..3bbfa6a384 100644
--- a/app/assets/stylesheets/desktop/topic-post.scss
+++ b/app/assets/stylesheets/desktop/topic-post.scss
@@ -517,9 +517,6 @@ video {
overflow: hidden;
text-overflow: ellipsis;
}
- .topic-header-extra {
- margin: 0 0 0 5px;
- }
}
/* default docked header CSS for all topics, including those without categories */
diff --git a/app/assets/stylesheets/desktop/user-card.scss b/app/assets/stylesheets/desktop/user-card.scss
index 54893e04e1..f94ae71a90 100644
--- a/app/assets/stylesheets/desktop/user-card.scss
+++ b/app/assets/stylesheets/desktop/user-card.scss
@@ -279,12 +279,14 @@ $user_card_background: $secondary;
}
.badge-section {
- float: left;
+ display: flex;
+ align-items: flex-start;
width: 500px;
padding-bottom: 10px;
margin-top: 5px;
.user-badge {
+ margin-right: 0.5em;
background: $primary-very-low;
border: 1px solid $primary-low;
color: $user_card_primary;
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index f7b80208ce..6883200e5e 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -195,7 +195,7 @@
float: right;
text-align: right;
- a {
+ .btn {
min-width: 140px;
}
diff --git a/app/assets/stylesheets/mobile/admin_reports.scss b/app/assets/stylesheets/mobile/admin_reports.scss
index 0179cd4161..0c00969802 100644
--- a/app/assets/stylesheets/mobile/admin_reports.scss
+++ b/app/assets/stylesheets/mobile/admin_reports.scss
@@ -16,7 +16,7 @@
.report-alert {
margin: 0;
order: 1;
- flex: 1;
+ flex: 1 0 0;
padding: 1em;
}
}
diff --git a/app/assets/stylesheets/mobile/compose.scss b/app/assets/stylesheets/mobile/compose.scss
index da399d8693..2de704e4aa 100644
--- a/app/assets/stylesheets/mobile/compose.scss
+++ b/app/assets/stylesheets/mobile/compose.scss
@@ -150,7 +150,7 @@
.with-tags {
.category-input {
flex-basis: auto;
- flex: 1;
+ flex: 1 0 0;
margin: 0;
width: 45%;
margin-bottom: 5px;
@@ -158,7 +158,7 @@
.mini-tag-chooser {
width: 50%;
- flex: 1;
+ flex: 1 0 0;
margin-left: 5px;
margin-bottom: 5px;
z-index: z("base");
diff --git a/app/assets/stylesheets/mobile/header.scss b/app/assets/stylesheets/mobile/header.scss
index e7efb8ce8d..df9b599074 100644
--- a/app/assets/stylesheets/mobile/header.scss
+++ b/app/assets/stylesheets/mobile/header.scss
@@ -18,6 +18,7 @@
width: 100%;
}
}
+
#site-logo {
max-height: 2.4em;
max-width: 100%;
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index f14f66d562..51a8afc17e 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -1,9 +1,7 @@
-require 'disk_space'
class Admin::DashboardController < Admin::AdminController
def index
dashboard_data = AdminDashboardData.fetch_cached_stats || Jobs::DashboardStats.new.execute({})
dashboard_data.merge!(version_check: DiscourseUpdates.check_version.as_json) if SiteSetting.version_checks?
- dashboard_data[:disk_space] = DiskSpace.cached_stats
render json: dashboard_data
end
diff --git a/app/controllers/admin/dashboard_next_controller.rb b/app/controllers/admin/dashboard_next_controller.rb
index 41360bd7e5..72e3a0cecd 100644
--- a/app/controllers/admin/dashboard_next_controller.rb
+++ b/app/controllers/admin/dashboard_next_controller.rb
@@ -1,5 +1,3 @@
-require 'disk_space'
-
class Admin::DashboardNextController < Admin::AdminController
def index
data = AdminDashboardNextIndexData.fetch_cached_stats
@@ -13,27 +11,9 @@ class Admin::DashboardNextController < Admin::AdminController
def moderation; end
def security; end
+ def reports; end
def general
- data = AdminDashboardNextGeneralData.fetch_cached_stats
-
- if SiteSetting.enable_backups
- data[:last_backup_taken_at] = last_backup_taken_at
- data[:disk_space] = DiskSpace.cached_stats
- end
-
- render json: data
- end
-
- private
-
- def last_backup_taken_at
- store = BackupRestore::BackupStore.create
-
- begin
- store.latest_file&.last_modified
- rescue BackupRestore::BackupStore::StorageError
- nil
- end
+ render json: AdminDashboardNextGeneralData.fetch_cached_stats
end
end
diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb
index a3a5499a1c..04b015b3c0 100644
--- a/app/controllers/admin/email_controller.rb
+++ b/app/controllers/admin/email_controller.rb
@@ -13,8 +13,6 @@ class Admin::EmailController < Admin::AdminController
Jobs::TestEmail.new.execute(to_address: params[:email_address])
if SiteSetting.disable_emails == "yes"
render json: { sent_test_email_message: I18n.t("admin.email.sent_test_disabled") }
- elsif SiteSetting.disable_emails == "non-staff" && !User.find_by_email(params[:email_address])&.staff?
- render json: { sent_test_email_message: I18n.t("admin.email.sent_test_disabled_for_non_staff") }
else
render json: { sent_test_email_message: I18n.t("admin.email.sent_test") }
end
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb
index 6907bb8bbb..1ec347f25c 100644
--- a/app/controllers/admin/reports_controller.rb
+++ b/app/controllers/admin/reports_controller.rb
@@ -6,7 +6,7 @@ class Admin::ReportsController < Admin::AdminController
ApplicationRequest.req_types.keys
.select { |r| r =~ /^page_view_/ && r !~ /mobile/ }
.map { |r| r + "_reqs" } +
- Report.singleton_methods.grep(/^report_(?!about)/)
+ Report.singleton_methods.grep(/^report_(?!about|storage_stats)/)
reports = reports_methods.map do |name|
type = name.to_s.gsub('report_', '')
diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb
index 3e39de47a8..ab0347e928 100644
--- a/app/controllers/admin/themes_controller.rb
+++ b/app/controllers/admin/themes_controller.rb
@@ -96,8 +96,15 @@ class Admin::ThemesController < Admin::AdminController
end
def index
- @themes = Theme.order(:name).includes(:theme_fields, :remote_theme)
- @color_schemes = ColorScheme.all.to_a
+ @themes = Theme.order(:name).includes(:child_themes,
+ :remote_theme,
+ :theme_settings,
+ :settings_field,
+ :user,
+ :color_scheme,
+ theme_fields: :upload
+ )
+ @color_schemes = ColorScheme.all.includes(:theme, color_scheme_colors: :color_scheme).to_a
light = ColorScheme.new(name: I18n.t("color_schemes.light"))
@color_schemes.unshift(light)
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index ae0ed9de28..14627a1087 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -26,7 +26,8 @@ class Admin::UsersController < Admin::AdminController
:revoke_api_key,
:anonymize,
:reset_bounce_score,
- :disable_second_factor]
+ :disable_second_factor,
+ :delete_posts_batch]
def index
users = ::AdminUserIndexQuery.new(params).find_users
@@ -46,8 +47,7 @@ class Admin::UsersController < Admin::AdminController
end
def delete_posts_batch
- user = User.find_by(id: params[:user_id])
- deleted_posts = user.delete_posts_in_batches(guardian)
+ deleted_posts = @user.delete_posts_in_batches(guardian)
# staff action logs will have an entry for each post
render json: { posts_deleted: deleted_posts.length }
@@ -563,6 +563,7 @@ class Admin::UsersController < Admin::AdminController
def fetch_user
@user = User.find_by(id: params[:user_id])
+ raise Discourse::NotFound unless @user
end
def refresh_browser(user)
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index e4e0ccf761..6820ed87de 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -513,7 +513,7 @@ class PostsController < ApplicationController
guardian.ensure_can_rebake!
post = find_post_from_params
- post.rebake!(invalidate_oneboxes: true)
+ post.rebake!(invalidate_oneboxes: true, invalidate_broken_images: true)
render body: nil
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index ad74f59574..b1fc8b0f5a 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -9,6 +9,9 @@ class SearchController < ApplicationController
end
def show
+ @search_term = params[:q]
+ raise Discourse::InvalidParameters.new(:q) if @search_term.present? && @search_term.length < SiteSetting.min_search_term_length
+
search_args = {
type_filter: 'topic',
guardian: guardian,
@@ -29,7 +32,6 @@ class SearchController < ApplicationController
search_args[:ip_address] = request.remote_ip
search_args[:user_id] = current_user.id if current_user.present?
- @search_term = params[:q]
search = Search.new(@search_term, search_args)
result = search.execute
diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb
index 195ad63831..8712276560 100644
--- a/app/controllers/session_controller.rb
+++ b/app/controllers/session_controller.rb
@@ -1,5 +1,6 @@
require_dependency 'rate_limiter'
require_dependency 'single_sign_on'
+require_dependency 'single_sign_on_provider'
require_dependency 'url_helper'
class SessionController < ApplicationController
@@ -46,7 +47,7 @@ class SessionController < ApplicationController
payload ||= request.query_string
if SiteSetting.enable_sso_provider
- sso = SingleSignOn.parse(payload)
+ sso = SingleSignOnProvider.parse(payload)
if sso.return_sso_url.blank?
render plain: "return_sso_url is blank, it must be provided", status: 400
@@ -115,7 +116,7 @@ class SessionController < ApplicationController
sso = DiscourseSingleSignOn.parse(request.query_string)
rescue DiscourseSingleSignOn::ParseError => e
if SiteSetting.verbose_sso_logging
- Rails.logger.warn("Verbose SSO log: Signature parse error\n\n#{e.message}\n\n#{sso.diagnostics}")
+ Rails.logger.warn("Verbose SSO log: Signature parse error\n\n#{e.message}\n\n#{sso&.diagnostics}")
end
# Do NOT pass the error text to the client, it would give them the correct signature
diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb
index 43b4a851ab..7037e1f257 100644
--- a/app/controllers/static_controller.rb
+++ b/app/controllers/static_controller.rb
@@ -12,7 +12,7 @@ class StaticController < ApplicationController
def show
return redirect_to(path '/') if current_user && (params[:id] == 'login' || params[:id] == 'signup')
- if SiteSetting.login_required? && current_user.nil? && ['faq', 'guidelines', 'rules'].include?(params[:id])
+ if SiteSetting.login_required? && current_user.nil? && ['faq', 'guidelines'].include?(params[:id])
return redirect_to path('/login')
end
@@ -31,7 +31,7 @@ class StaticController < ApplicationController
end
# The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting.
- @page = 'faq' if @page == 'guidelines' || @page == 'rules'
+ @page = 'faq' if @page == 'guidelines'
# Don't allow paths like ".." or "/" or anything hacky like that
@page.gsub!(/[^a-z0-9\_\-]/, '')
@@ -103,12 +103,17 @@ class StaticController < ApplicationController
FAVICON ||= -"favicon"
- # We need to be able to draw our favicon on a canvas
- # and pull it off the canvas into a data uri
- # This can work by ensuring people set all the right CORS
- # settings in the CDN asset, BUT its annoying and error prone
- # instead we cache the favicon in redis and serve it out real quick with
- # a huge expiry, we also cache these assets in nginx so it bypassed if needed
+ # We need to be able to draw our favicon on a canvas, this happens when you enable the feature
+ # that draws the notification count on top of favicon (per user default off)
+ #
+ # With s3 the original upload is going to be stored at s3, we don't have a local copy of the favicon.
+ # To allow canvas to work with s3 we are going to need to add special CORS headers and use
+ # a special crossorigin hint on the original, this is not easily workable.
+ #
+ # Forcing all consumers to set magic CORS headers on a CDN is also not workable for us.
+ #
+ # So we cache the favicon in redis and serve it out real quick with
+ # a huge expiry, we also cache these assets in nginx so it is bypassed if needed
def favicon
is_asset_path
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index e022dd70ea..33b0ebecc0 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -607,13 +607,26 @@ class TopicsController < ApplicationController
end
def merge_topic
- params.require(:destination_topic_id)
+ topic_id = params.require(:topic_id)
+ destination_topic_id = params.require(:destination_topic_id)
+ params.permit(:participants)
+ params.permit(:archetype)
- topic = Topic.find_by(id: params[:topic_id])
+ raise Discourse::InvalidAccess if params[:archetype] == "private_message" && !guardian.is_staff?
+
+ topic = Topic.find_by(id: topic_id)
guardian.ensure_can_move_posts!(topic)
- dest_topic = topic.move_posts(current_user, topic.posts.pluck(:id), destination_topic_id: params[:destination_topic_id].to_i)
- render_topic_changes(dest_topic)
+ args = {}
+ args[:destination_topic_id] = destination_topic_id.to_i
+
+ if params[:archetype].present?
+ args[:archetype] = params[:archetype]
+ args[:participants] = params[:participants] if params[:participants].present? && params[:archetype] == "private_message"
+ end
+
+ destination_topic = topic.move_posts(current_user, topic.posts.pluck(:id), args)
+ render_topic_changes(destination_topic)
end
def move_posts
@@ -621,6 +634,10 @@ class TopicsController < ApplicationController
topic_id = params.require(:topic_id)
params.permit(:category_id)
params.permit(:tags)
+ params.permit(:participants)
+ params.permit(:archetype)
+
+ raise Discourse::InvalidAccess if params[:archetype] == "private_message" && !guardian.is_staff?
topic = Topic.with_deleted.find_by(id: topic_id)
guardian.ensure_can_move_posts!(topic)
@@ -630,8 +647,8 @@ class TopicsController < ApplicationController
return render_json_error("When moving posts to a new topic, the first post must be a regular post.")
end
- dest_topic = move_posts_to_destination(topic)
- render_topic_changes(dest_topic)
+ destination_topic = move_posts_to_destination(topic)
+ render_topic_changes(destination_topic)
rescue ActiveRecord::RecordInvalid => ex
render_json_error(ex)
end
@@ -893,9 +910,15 @@ class TopicsController < ApplicationController
args = {}
args[:title] = params[:title] if params[:title].present?
args[:destination_topic_id] = params[:destination_topic_id].to_i if params[:destination_topic_id].present?
- args[:category_id] = params[:category_id].to_i if params[:category_id].present?
args[:tags] = params[:tags] if params[:tags].present?
+ if params[:archetype].present?
+ args[:archetype] = params[:archetype]
+ args[:participants] = params[:participants] if params[:participants].present? && params[:archetype] == "private_message"
+ else
+ args[:category_id] = params[:category_id].to_i if params[:category_id].present?
+ end
+
topic.move_posts(current_user, post_ids_including_replies, args)
end
diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb
index b3852a6a77..cb67ee7b42 100644
--- a/app/controllers/user_badges_controller.rb
+++ b/app/controllers/user_badges_controller.rb
@@ -58,7 +58,7 @@ class UserBadgesController < ApplicationController
if params[:reason].present?
unless is_badge_reason_valid? params[:reason]
- return render json: { failed: I18n.t('invalid_grant_badge_reason_link') }, status: 400
+ return render json: failed_json.merge(message: I18n.t('invalid_grant_badge_reason_link')), status: 400
end
path = begin
diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb
index 176dab9cb9..e3aed77955 100644
--- a/app/helpers/user_notifications_helper.rb
+++ b/app/helpers/user_notifications_helper.rb
@@ -23,9 +23,7 @@ module UserNotificationsHelper
logo_url = SiteSetting.site_logo_url if logo_url.blank? || logo_url =~ /\.svg$/i
return nil if logo_url.blank? || logo_url =~ /\.svg$/i
- uri = URI.parse(UrlHelper.absolute(upload_cdn_path(logo_url)))
- uri.scheme = SiteSetting.scheme if uri.scheme.blank?
- uri.to_s
+ full_cdn_url(logo_url)
end
def html_site_link(color)
diff --git a/app/jobs/regular/update_disk_space.rb b/app/jobs/regular/update_disk_space.rb
deleted file mode 100644
index 5ebd3782ad..0000000000
--- a/app/jobs/regular/update_disk_space.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'disk_space'
-
-module Jobs
- class UpdateDiskSpace < Jobs::Base
- sidekiq_options retry: false
-
- def execute(args)
- Discourse.cache.write(DiskSpace::DISK_SPACE_STATS_CACHE_KEY, DiskSpace.stats.to_json)
- Discourse.cache.write(DiskSpace::DISK_SPACE_STATS_UPDATED_CACHE_KEY, Time.now.to_i)
- end
- end
-end
diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb
index 2d35370225..af664694dc 100644
--- a/app/mailers/user_notifications.rb
+++ b/app/mailers/user_notifications.rb
@@ -35,7 +35,7 @@ class UserNotifications < ActionMailer::Base
def suspicious_login(user, opts = {})
ipinfo = DiscourseIpInfo.get(opts[:client_ip])
- location = [ipinfo[:city], ipinfo[:region], ipinfo[:country]].reject(&:blank?).join(", ")
+ location = ipinfo[:location]
browser = BrowserDetection.browser(opts[:user_agent])
device = BrowserDetection.device(opts[:user_agent])
os = BrowserDetection.os(opts[:user_agent])
diff --git a/app/models/category.rb b/app/models/category.rb
index af32b64859..d7a4c0fa6d 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -572,11 +572,9 @@ class Category < ActiveRecord::Base
def create_category_permalink
old_slug = saved_changes.transform_values(&:first)["slug"]
- if self.parent_category
- url = "c/#{self.parent_category.slug}/#{old_slug}"
- else
- url = "c/#{old_slug}"
- end
+ url = +"#{Discourse.base_uri}/c"
+ url << "/#{parent_category.slug}" if parent_category_id
+ url << "/#{old_slug}"
if Permalink.where(url: url).exists?
Permalink.where(url: url).update_all(category_id: id)
diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb
index c9cdbd94ef..5e5e8e4191 100644
--- a/app/models/optimized_image.rb
+++ b/app/models/optimized_image.rb
@@ -68,7 +68,7 @@ class OptimizedImage < ActiveRecord::Base
Rails.logger.error("Could not find file in the store located at url: #{upload.url}")
else
# create a temp file with the same extension as the original
- extension = ".#{upload.extension}"
+ extension = ".#{opts[:format] || upload.extension}"
if extension.length == 1
return nil
@@ -96,6 +96,7 @@ class OptimizedImage < ActiveRecord::Base
url: "",
filesize: File.size(temp_path)
)
+
# store the optimized image and update its url
File.open(temp_path) do |file|
url = Discourse.store.store_optimized_image(file, thumbnail)
@@ -173,7 +174,20 @@ class OptimizedImage < ActiveRecord::Base
IM_DECODERS ||= /\A(jpe?g|png|tiff?|bmp|ico|gif)\z/i
def self.prepend_decoder!(path, ext_path = nil, opts = nil)
- extension = File.extname((opts && opts[:filename]) || ext_path || path)[1..-1]
+ opts ||= {}
+
+ # This logic is a little messy but the result of using mocks for most
+ # of the image tests. The idea here is you shouldn't trust the "original"
+ # path of a file to figure out its extension. However, in certain cases
+ # such as generating the loading upload thumbnail, we force the format,
+ # and this allows us to use the forced format in that case.
+ extension = nil
+ if (opts[:format] && path != ext_path)
+ extension = File.extname(path)[1..-1]
+ else
+ extension = File.extname(opts[:filename] || ext_path || path)[1..-1]
+ end
+
raise Discourse::InvalidAccess if !extension || !extension.match?(IM_DECODERS)
"#{extension}:#{path}"
end
@@ -189,10 +203,14 @@ class OptimizedImage < ActiveRecord::Base
from = prepend_decoder!(from, to, opts)
to = prepend_decoder!(to, to, opts)
+ instructions = ['convert', "#{from}[0]"]
+
+ if opts[:colors]
+ instructions << "-colors" << opts[:colors].to_s
+ end
+
# NOTE: ORDER is important!
- %W{
- convert
- #{from}[0]
+ instructions.concat(%W{
-auto-orient
-gravity center
-background transparent
@@ -204,7 +222,7 @@ class OptimizedImage < ActiveRecord::Base
-quality 98
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
#{to}
- }
+ })
end
def self.resize_instructions_animated(from, to, dimensions, opts = {})
@@ -212,7 +230,7 @@ class OptimizedImage < ActiveRecord::Base
%W{
gifsicle
- --colors=256
+ --colors=#{opts[:colors] || 256}
--resize-fit #{dimensions}
--optimize=3
--output #{to}
@@ -248,7 +266,7 @@ class OptimizedImage < ActiveRecord::Base
%W{
gifsicle
--crop 0,0+#{dimensions}
- --colors=256
+ --colors=#{opts[:colors] || 256}
--optimize=3
--output #{to}
#{from}
@@ -300,9 +318,13 @@ class OptimizedImage < ActiveRecord::Base
convert_with(instructions, to, opts)
end
+ MAX_PNGQUANT_SIZE = 500_000
+
def self.convert_with(instructions, to, opts = {})
Discourse::Utils.execute_command(*instructions)
- FileHelper.optimize_image!(to)
+
+ allow_pngquant = to.downcase.ends_with?(".png") && File.size(to) < MAX_PNGQUANT_SIZE
+ FileHelper.optimize_image!(to, allow_pngquant: allow_pngquant)
true
rescue => e
if opts[:raise_on_error]
diff --git a/app/models/post.rb b/app/models/post.rb
index 42434c7375..7279a0ff27 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -55,13 +55,13 @@ class Post < ActiveRecord::Base
has_many :user_actions, foreign_key: :target_post_id
- validates_with ::Validators::PostValidator
+ validates_with ::Validators::PostValidator, unless: :skip_validation
after_save :index_search
after_save :create_user_action
# We can pass several creating options to a post via attributes
- attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes, :cooking_options, :skip_unique_check
+ attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes, :cooking_options, :skip_unique_check, :skip_validation
LARGE_IMAGES ||= "large_images".freeze
BROKEN_IMAGES ||= "broken_images".freeze
@@ -530,7 +530,7 @@ class Post < ActiveRecord::Base
if attempts > 3
post.update_columns(baked_version: BAKED_VERSION)
- Discourse.warn_exception(e, message: "Can not rebake post# #{p.id} after 3 attempts, giving up")
+ Discourse.warn_exception(e, message: "Can not rebake post# #{post.id} after 3 attempts, giving up")
else
post.custom_fields["rebake_attempts"] = attempts + 1
post.save_custom_fields
@@ -549,6 +549,11 @@ class Post < ActiveRecord::Base
update_columns(cooked: new_cooked, baked_at: Time.new, baked_version: BAKED_VERSION)
+ if opts.fetch(:invalidate_broken_images, false)
+ custom_fields.delete(BROKEN_IMAGES)
+ save_custom_fields
+ end
+
# Extracts urls from the body
TopicLink.extract_from(self)
QuotedPost.extract_from(self)
diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb
index 359a08e6ed..47b8f3c133 100644
--- a/app/models/post_mover.rb
+++ b/app/models/post_mover.rb
@@ -5,18 +5,24 @@ class PostMover
@move_types ||= Enum.new(:new_topic, :existing_topic)
end
- def initialize(original_topic, user, post_ids)
+ def initialize(original_topic, user, post_ids, move_to_pm: false)
@original_topic = original_topic
@user = user
@post_ids = post_ids
+ @move_to_pm = move_to_pm
end
- def to_topic(id)
+ def to_topic(id, participants: nil)
@move_type = PostMover.move_types[:existing_topic]
+ topic = Topic.find_by_id(id)
+ raise Discourse::InvalidParameters unless topic.archetype == @original_topic.archetype
+
Topic.transaction do
- move_posts_to Topic.find_by_id(id)
+ move_posts_to topic
end
+ add_allowed_users(participants) if participants.present? && @move_to_pm
+ topic
end
def to_new_topic(title, category_id = nil, tags = nil)
@@ -24,13 +30,15 @@ class PostMover
post = Post.find_by(id: post_ids.first)
raise Discourse::InvalidParameters unless post
+ archetype = @move_to_pm ? Archetype.private_message : Archetype.default
Topic.transaction do
new_topic = Topic.create!(
user: post.user,
title: title,
category_id: category_id,
- created_at: post.created_at
+ created_at: post.created_at,
+ archetype: archetype
)
DiscourseTagging.tag_topic_by_names(new_topic, Guardian.new(user), tags)
move_posts_to new_topic
@@ -79,7 +87,11 @@ class PostMover
posts.each do |post|
post.is_first_post? ? create_first_post(post) : move(post)
+ if @move_to_pm
+ destination_topic.topic_allowed_users.build(user_id: post.user_id) unless destination_topic.topic_allowed_users.where(user_id: post.user_id).exists?
+ end
end
+ destination_topic.save! if @move_to_pm
PostReply.where("reply_id IN (:post_ids) OR post_id IN (:post_ids)", post_ids: post_ids).each do |post_reply|
if post_reply.post && post_reply.reply && post_reply.reply.topic_id != post_reply.post.topic_id
@@ -189,6 +201,7 @@ class PostMover
I18n.t(
"move_posts.#{move_type_str}_moderator_post",
count: posts.length,
+ entity: @move_to_pm ? "message" : "topic",
topic_link: posts.first.is_first_post? ?
"[#{destination_topic.title}](#{destination_topic.relative_url})" :
"[#{destination_topic.title}](#{posts.first.url})"
@@ -234,4 +247,14 @@ class PostMover
notifications_reason_id: TopicUser.notification_reasons[:created_topic]
)
end
+
+ def add_allowed_users(usernames)
+ return unless usernames.present?
+
+ names = usernames.split(',').flatten
+ User.where(username: names).find_each do |user|
+ destination_topic.topic_allowed_users.build(user_id: user.id) unless destination_topic.topic_allowed_users.where(user_id: user.id).exists?
+ end
+ destination_topic.save!
+ end
end
diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb
index eff45a652b..5dbb3a9b15 100644
--- a/app/models/remote_theme.rb
+++ b/app/models/remote_theme.rb
@@ -39,7 +39,7 @@ class RemoteTheme < ActiveRecord::Base
end
def self.import_theme(url, user = Discourse.system_user, private_key: nil, branch: nil)
- importer = ThemeStore::GitImporter.new(url, private_key: private_key, branch: branch)
+ importer = ThemeStore::GitImporter.new(url.strip, private_key: private_key, branch: branch)
importer.import!
theme_info = JSON.parse(importer["about.json"])
diff --git a/app/models/report.rb b/app/models/report.rb
index 19b0b70922..82f23caec4 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -49,8 +49,10 @@ class Report
].compact.map(&:to_s).join(':')
end
- def self.clear_cache
- Discourse.cache.keys("reports:*").each do |key|
+ def self.clear_cache(type = nil)
+ pattern = type ? "reports:#{type}:*" : "reports:*"
+
+ Discourse.cache.keys(pattern).each do |key|
Discourse.cache.redis.del(key)
end
end
@@ -76,9 +78,9 @@ class Report
{
type: type,
- title: I18n.t("reports.#{type}.title"),
- xaxis: I18n.t("reports.#{type}.xaxis"),
- yaxis: I18n.t("reports.#{type}.yaxis"),
+ title: I18n.t("reports.#{type}.title", default: nil),
+ xaxis: I18n.t("reports.#{type}.xaxis", default: nil),
+ yaxis: I18n.t("reports.#{type}.yaxis", default: nil),
description: description.presence ? description : nil,
data: data,
start_date: start_date&.iso8601,
@@ -244,6 +246,8 @@ class Report
def self.report_signups(report)
report.group_filtering = true
+ report.icon = 'user-plus'
+
if report.group_id
basic_report_about report, User.real, :count_by_signup_date, report.start_date, report.end_date, report.group_id
add_counts report, User.real, 'users.created_at'
@@ -721,7 +725,7 @@ class Report
title: I18n.t("reports.trending_search.labels.term")
},
{
- property: :unique_searches,
+ property: :searches,
type: :number,
title: I18n.t("reports.trending_search.labels.searches")
},
@@ -742,17 +746,10 @@ class Report
)
trends.each do |trend|
- ctr =
- if trend.click_through == 0 || trend.searches == 0
- 0
- else
- trend.click_through.to_f / trend.searches.to_f
- end
-
report.data << {
term: trend.term,
- unique_searches: trend.unique_searches,
- ctr: (ctr * 100).ceil(1)
+ searches: trend.searches,
+ ctr: trend.ctr
}
end
end
@@ -946,7 +943,12 @@ class Report
report.labels = [
{
- property: :action_type,
+ type: :post,
+ properties: {
+ topic_id: :topic_id,
+ number: :post_number,
+ truncated_raw: :post_type
+ },
title: I18n.t("reports.flags_status.labels.flag")
},
{
@@ -985,7 +987,7 @@ class Report
report.data = []
- flag_types = PostActionType.flag_types_without_custom
+ flag_types = PostActionType.flag_types
sql = <<~SQL
WITH period_actions AS (
@@ -1002,13 +1004,16 @@ class Report
user_id,
COALESCE(disagreed_at, agreed_at, deferred_at) AS responded_at
FROM post_actions
- WHERE post_action_type_id IN (#{PostActionType.flag_types_without_custom.values.join(',')})
- AND created_at >= '#{report.start_date}'
- AND created_at <= '#{report.end_date}'
+ WHERE post_action_type_id IN (#{flag_types.values.join(',')})
+ AND created_at >= '#{report.start_date}'
+ AND created_at <= '#{report.end_date}'
+ ORDER BY created_at DESC
),
poster_data AS (
SELECT pa.id,
p.user_id AS poster_id,
+ p.topic_id as topic_id,
+ p.post_number as post_number,
u.username_lower AS poster_username,
u.uploaded_avatar_id AS poster_avatar_id
FROM period_actions pa
@@ -1042,6 +1047,8 @@ class Report
pd.poster_username,
pd.poster_id,
pd.poster_avatar_id,
+ pd.post_number,
+ pd.topic_id,
fd.flagger_username,
fd.flagger_id,
fd.flagger_avatar_id,
@@ -1065,7 +1072,10 @@ class Report
DB.query(sql).each do |row|
data = {}
- data[:action_type] = flag_types.key(row.post_action_type_id).to_s
+
+ data[:post_type] = flag_types.key(row.post_action_type_id).to_s
+ data[:post_number] = row.post_number
+ data[:topic_id] = row.topic_id
if row.staff_id
data[:staff_username] = row.staff_username
@@ -1407,6 +1417,94 @@ class Report
end
end
+ def self.report_storage_stats(report)
+ backup_stats = begin
+ BackupRestore::BackupStore.create.stats
+ rescue BackupRestore::BackupStore::StorageError
+ nil
+ end
+
+ report.data = {
+ backups: backup_stats,
+ uploads: {
+ used_bytes: DiskSpace.uploads_used_bytes,
+ free_bytes: DiskSpace.uploads_free_bytes
+ }
+ }
+ end
+
+ def self.report_top_uploads(report)
+ report.modes = [:table]
+
+ report.labels = [
+ {
+ type: :link,
+ properties: [
+ :file_url,
+ :file_name,
+ ],
+ title: I18n.t("reports.top_uploads.labels.filename")
+ },
+ {
+ type: :user,
+ properties: {
+ username: :author_username,
+ id: :author_id,
+ avatar: :author_avatar_template,
+ },
+ title: I18n.t("reports.top_uploads.labels.author")
+ },
+ {
+ type: :text,
+ property: :extension,
+ title: I18n.t("reports.top_uploads.labels.extension")
+ },
+ {
+ type: :bytes,
+ property: :filesize,
+ title: I18n.t("reports.top_uploads.labels.filesize")
+ },
+ ]
+
+ report.data = []
+
+ sql = <<~SQL
+ SELECT
+ u.id as user_id,
+ u.username,
+ u.uploaded_avatar_id,
+ up.filesize,
+ up.original_filename,
+ up.extension,
+ up.url
+ FROM uploads up
+ JOIN users u
+ ON u.id = up.user_id
+ WHERE up.created_at >= '#{report.start_date}' AND up.created_at <= '#{report.end_date}'
+ ORDER BY up.filesize DESC
+ LIMIT #{report.limit || 250}
+ SQL
+
+ DB.query(sql).each do |row|
+ data = {}
+ data[:author_id] = row.user_id
+ data[:author_username] = row.username
+ data[:author_avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id)
+ data[:filesize] = row.filesize
+ data[:extension] = row.extension
+ data[:file_url] = Discourse.store.cdn_url(row.url)
+ data[:file_name] = row.original_filename.truncate(25)
+
+ report.data << data
+ end
+ end
+
+ DiscourseEvent.on(:site_setting_saved) do |site_setting|
+ if ["backup_location", "s3_backup_bucket"].include?(site_setting.name.to_s)
+ clear_cache(:storage_stats)
+ end
+ end
+
private
def hex_to_rgbs(hex_color)
diff --git a/app/models/search_log.rb b/app/models/search_log.rb
index 9fa4f92db6..d046daee06 100644
--- a/app/models/search_log.rb
+++ b/app/models/search_log.rb
@@ -3,6 +3,14 @@ require_dependency 'enum'
class SearchLog < ActiveRecord::Base
validates_presence_of :term
+ attr_reader :ctr
+
+ def ctr
+ return 0 if click_through == 0 || searches == 0
+
+ ((click_through.to_f / searches.to_f) * 100).ceil(1)
+ end
+
def self.search_types
@search_types ||= Enum.new(
header: 1,
@@ -117,8 +125,7 @@ class SearchLog < ActiveRecord::Base
SUM(CASE
WHEN search_result_id IS NOT NULL THEN 1
ELSE 0
- END) AS click_through,
- COUNT(DISTINCT ip_address) AS unique_searches
+ END) AS click_through
SQL
result = SearchLog.select(select_sql)
@@ -132,9 +139,9 @@ class SearchLog < ActiveRecord::Base
result = result.where('search_type = ?', search_types[search_type])
end
- result = result.group('lower(term)')
- .order('unique_searches DESC, click_through ASC, term ASC')
- .limit(limit).to_a
+ result.group('lower(term)')
+ .order('searches DESC, click_through DESC, term ASC')
+ .limit(limit)
end
def self.start_of(period)
diff --git a/app/models/site.rb b/app/models/site.rb
index 8577329d67..cdc34c2ef8 100644
--- a/app/models/site.rb
+++ b/app/models/site.rb
@@ -125,10 +125,12 @@ class Site
json
end
+ SITE_JSON_CHANNEL = '/site_json'
+
def self.clear_anon_cache!
# publishing forces the sequence up
# the cache is validated based on the sequence
- MessageBus.publish('/site_json', '')
+ MessageBus.publish(SITE_JSON_CHANNEL, '')
end
end
diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
index 687f000f0a..ee20d17616 100644
--- a/app/models/site_setting.rb
+++ b/app/models/site_setting.rb
@@ -1,7 +1,9 @@
require 'site_setting_extension'
+require_dependency 'global_path'
require_dependency 'site_settings/yaml_loader'
class SiteSetting < ActiveRecord::Base
+ extend GlobalPath
extend SiteSettingExtension
validates_presence_of :name
@@ -151,7 +153,7 @@ class SiteSetting < ActiveRecord::Base
# cf. http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
if SiteSetting.s3_endpoint == "https://s3.amazonaws.com"
- if SiteSetting.Upload.s3_region == 'cn-north-1' || SiteSetting.Upload.s3_region == 'cn-northwest-1'
+ if SiteSetting.Upload.s3_region.start_with?("cn-")
"//#{bucket}.s3.#{SiteSetting.Upload.s3_region}.amazonaws.com.cn"
else
"//#{bucket}.s3.dualstack.#{SiteSetting.Upload.s3_region}.amazonaws.com"
@@ -176,35 +178,43 @@ class SiteSetting < ActiveRecord::Base
}.each { |client_setting| client_settings << client_setting }
def self.site_logo_url
- self.logo&.url || self.logo_url(warn: false)
+ upload = self.logo
+ upload ? full_cdn_url(upload.url) : self.logo_url(warn: false)
end
def self.site_logo_small_url
- self.logo_small&.url || self.logo_small_url(warn: false)
+ upload = self.logo_small
+ upload ? full_cdn_url(upload.url) : self.logo_small_url(warn: false)
end
def self.site_digest_logo_url
- self.digest_logo&.url || self.digest_logo_url(warn: false)
+ upload = self.digest_logo
+ upload ? full_cdn_url(upload.url) : self.digest_logo_url(warn: false)
end
def self.site_mobile_logo_url
- self.mobile_logo&.url || self.mobile_logo_url(warn: false)
+ upload = self.mobile_logo
+ upload ? full_cdn_url(upload.url) : self.mobile_logo_url(warn: false)
end
def self.site_large_icon_url
- self.large_icon&.url || self.large_icon_url(warn: false)
+ upload = self.large_icon
+ upload ? full_cdn_url(upload.url) : self.large_icon_url(warn: false)
end
def self.site_favicon_url
- self.favicon&.url || self.favicon_url(warn: false)
+ upload = self.favicon
+ upload ? full_cdn_url(upload.url) : self.favicon_url(warn: false)
end
def self.site_apple_touch_icon_url
- self.apple_touch_icon&.url || self.apple_touch_icon_url(warn: false)
+ upload = self.apple_touch_icon
+ upload ? full_cdn_url(upload.url) : self.apple_touch_icon_url(warn: false)
end
def self.opengraph_image_url
- self.opengraph_image&.url || self.default_opengraph_image_url(warn: false)
+ upload = self.opengraph_image
+ upload ? full_cdn_url(upload.url) : self.default_opengraph_image_url(warn: false)
end
def self.site_twitter_summary_large_image_url
diff --git a/app/models/theme.rb b/app/models/theme.rb
index f3b90ff6f0..29073467bf 100644
--- a/app/models/theme.rb
+++ b/app/models/theme.rb
@@ -16,10 +16,12 @@ class Theme < ActiveRecord::Base
has_many :theme_fields, dependent: :destroy
has_many :theme_settings, dependent: :destroy
has_many :child_theme_relation, class_name: 'ChildTheme', foreign_key: 'parent_theme_id', dependent: :destroy
- has_many :child_themes, through: :child_theme_relation, source: :child_theme
+ has_many :child_themes, -> { order(:name) }, through: :child_theme_relation, source: :child_theme
has_many :color_schemes
belongs_to :remote_theme
+ has_one :settings_field, -> { where(target_id: Theme.targets[:settings], name: "yaml") }, class_name: 'ThemeField'
+
validate :component_validations
scope :user_selectable, ->() {
@@ -346,7 +348,7 @@ class Theme < ActiveRecord::Base
end
def settings
- field = theme_fields.where(target_id: Theme.targets[:settings], name: "yaml").first
+ field = settings_field
return [] unless field && field.error.nil?
settings = []
diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb
index 7b235f281e..3a393c22e0 100644
--- a/app/models/theme_field.rb
+++ b/app/models/theme_field.rb
@@ -171,10 +171,7 @@ COMPILED
errors << e.message
end
- self.error = errors.join("\n").presence unless self.destroyed?
- if will_save_change_to_error?
- update_columns(error: self.error)
- end
+ self.error = errors.join("\n").presence
end
def self.guess_type(name)
@@ -237,6 +234,8 @@ COMPILED
end
before_save do
+ validate_yaml!
+
if will_save_change_to_value? && !will_save_change_to_value_baked?
self.value_baked = nil
end
@@ -245,7 +244,6 @@ COMPILED
after_commit do
ensure_baked!
ensure_scss_compiles!
- validate_yaml!
theme.clear_cached_settings!
Stylesheet::Manager.clear_theme_cache! if self.name.include?("scss")
diff --git a/app/models/theme_setting.rb b/app/models/theme_setting.rb
index 3616cb8a71..bb58c0e167 100644
--- a/app/models/theme_setting.rb
+++ b/app/models/theme_setting.rb
@@ -9,6 +9,7 @@ class ThemeSetting < ActiveRecord::Base
theme.clear_cached_settings!
theme.remove_from_cache!
theme.theme_fields.update_all(value_baked: nil)
+ theme.theme_settings.reload
SvgSprite.expire_cache if self.name.to_s.include?("_icon")
CSP::Extension.clear_theme_extensions_cache! if name.to_s == CSP::Extension::THEME_SETTING
end
diff --git a/app/models/topic.rb b/app/models/topic.rb
index d9ad81b9e5..b1baf0ecf0 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -896,10 +896,10 @@ class Topic < ActiveRecord::Base
end
def move_posts(moved_by, post_ids, opts)
- post_mover = PostMover.new(self, moved_by, post_ids)
+ post_mover = PostMover.new(self, moved_by, post_ids, move_to_pm: opts[:archetype].present? && opts[:archetype] == "private_message")
if opts[:destination_topic_id]
- topic = post_mover.to_topic(opts[:destination_topic_id])
+ topic = post_mover.to_topic(opts[:destination_topic_id], participants: opts[:participants])
DiscourseEvent.trigger(:topic_merged,
post_mover.original_topic,
@@ -1357,7 +1357,7 @@ class Topic < ActiveRecord::Base
user_deleted: false,
hidden: false,
post_type: Post.types[:regular]
- ).last
+ ).last || first_post
update!(bumped_at: post.created_at)
end
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 350537952c..a90eb4c14e 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -46,13 +46,10 @@ class Upload < ActiveRecord::Base
thumbnail(width, height).present?
end
- def create_thumbnail!(width, height, crop = false)
+ def create_thumbnail!(width, height, opts = nil)
return unless SiteSetting.create_thumbnails?
-
- opts = {
- allow_animation: SiteSetting.allow_animated_thumbnails,
- crop: crop
- }
+ opts ||= {}
+ opts[:allow_animation] = SiteSetting.allow_animated_thumbnails
if get_optimized_image(width, height, opts)
save(validate: false)
@@ -68,7 +65,8 @@ class Upload < ActiveRecord::Base
opts = opts.merge(raise_on_error: true)
begin
OptimizedImage.create_for(self, width, height, opts)
- rescue
+ rescue => ex
+ Rails.logger.info ex if Rails.env.development?
opts = opts.merge(raise_on_error: false)
if fix_image_extension
OptimizedImage.create_for(self, width, height, opts)
@@ -128,8 +126,19 @@ class Upload < ActiveRecord::Base
end
begin
- self.width, self.height = size = FastImage.new(path, raise_on_failure: true).size
- self.thumbnail_width, self.thumbnail_height = ImageSizer.resize(*size)
+ w, h = FastImage.new(path, raise_on_failure: true).size
+
+ self.width = w || 0
+ self.height = h || 0
+
+ self.thumbnail_width, self.thumbnail_height = ImageSizer.resize(w, h)
+
+ self.update_columns(
+ width: width,
+ height: height,
+ thumbnail_width: thumbnail_width,
+ thumbnail_height: thumbnail_height
+ )
rescue => e
Discourse.warn_exception(e, message: "Error getting image dimensions")
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8ce55b3e06..1f47eaacb0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -122,6 +122,7 @@ class User < ActiveRecord::Base
after_save :badge_grant
after_save :expire_old_email_tokens
after_save :index_search
+ after_save :check_site_contact_username
after_commit :trigger_user_created_event, on: :create
after_commit :trigger_user_destroyed_event, on: :destroy
@@ -352,6 +353,10 @@ class User < ActiveRecord::Base
.maximum("groups.grant_trust_level")
end
+ def visible_groups
+ groups.visible_groups(self)
+ end
+
def enqueue_welcome_message(message_type)
return unless SiteSetting.send_welcome_message?
Jobs.enqueue(:send_system_message, user_id: id, message_type: message_type)
@@ -1369,6 +1374,13 @@ class User < ActiveRecord::Base
true
end
+ def check_site_contact_username
+ if (saved_change_to_admin? || saved_change_to_moderator?) &&
+ self.username == SiteSetting.site_contact_username && !staff?
+ SiteSetting.set_and_log(:site_contact_username, SiteSetting.defaults[:site_contact_username])
+ end
+ end
+
def self.ensure_consistency!
DB.exec <<~SQL
UPDATE users
diff --git a/app/models/user_summary.rb b/app/models/user_summary.rb
index cea0d05b58..00cf5164cd 100644
--- a/app/models/user_summary.rb
+++ b/app/models/user_summary.rb
@@ -192,10 +192,12 @@ protected
lookup = AvatarLookup.new(user_ids)
user_ids.map do |user_id|
+ lookup_hash = lookup[user_id]
+
UserWithCount.new(
- lookup[user_id].attributes.merge(count: user_hash[user_id])
- )
- end.sort_by { |u| -u[:count] }
+ lookup_hash.attributes.merge(count: user_hash[user_id])
+ ) if lookup_hash.present?
+ end.compact.sort_by { |u| -u[:count] }
end
end
diff --git a/app/serializers/admin_user_list_serializer.rb b/app/serializers/admin_user_list_serializer.rb
index 47e5fea86d..f318cac53a 100644
--- a/app/serializers/admin_user_list_serializer.rb
+++ b/app/serializers/admin_user_list_serializer.rb
@@ -38,8 +38,8 @@ class AdminUserListSerializer < BasicUserSerializer
def include_email?
# staff members can always see their email
- (scope.is_staff? && object.id == scope.user.id) || scope.can_see_emails? ||
- (scope.is_staff? && object.staged?)
+ (scope.is_staff? && (object.id == scope.user.id || object.staged?)) ||
+ (scope.is_admin? && scope.can_see_emails?)
end
alias_method :include_secondary_emails?, :include_email?
diff --git a/app/serializers/concerns/user_auth_tokens_mixin.rb b/app/serializers/concerns/user_auth_tokens_mixin.rb
index c0aa2fcb52..65a7cb3f5d 100644
--- a/app/serializers/concerns/user_auth_tokens_mixin.rb
+++ b/app/serializers/concerns/user_auth_tokens_mixin.rb
@@ -21,11 +21,7 @@ module UserAuthTokensMixin
def location
ipinfo = DiscourseIpInfo.get(client_ip, locale: I18n.locale)
-
- location = [ipinfo[:city], ipinfo[:region], ipinfo[:country]].reject { |x| x.blank? }.join(", ")
- return I18n.t('staff_action_logs.unknown') if location.blank?
-
- location
+ ipinfo[:location].presence || I18n.t('staff_action_logs.unknown')
end
def browser
diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb
index 27eb3500a9..1f65e474a3 100644
--- a/app/serializers/current_user_serializer.rb
+++ b/app/serializers/current_user_serializer.rb
@@ -38,14 +38,16 @@ class CurrentUserSerializer < BasicUserSerializer
:previous_visit_at,
:seen_notification_id,
:primary_group_id,
- :primary_group_name,
:can_create_topic,
:link_posting_access,
:external_id,
:top_category_ids,
- :hide_profile_and_presence
+ :hide_profile_and_presence,
+ :groups
- has_many :groups, embed: :object, serializer: BasicGroupSerializer
+ def groups
+ object.visible_groups.pluck(:id, :name).map { |id, name| { id: id, name: name.downcase } }
+ end
def link_posting_access
scope.link_posting_access
@@ -210,14 +212,6 @@ class CurrentUserSerializer < BasicUserSerializer
object.primary_group_id.present?
end
- def primary_group_name
- object.primary_group.name.downcase
- end
-
- def include_primary_group_name?
- object.primary_group&.name.present?
- end
-
def external_id
object&.single_sign_on_record&.external_id
end
diff --git a/app/serializers/search_logs_serializer.rb b/app/serializers/search_logs_serializer.rb
index 3eb6d6a293..9191a2e434 100644
--- a/app/serializers/search_logs_serializer.rb
+++ b/app/serializers/search_logs_serializer.rb
@@ -1,6 +1,5 @@
class SearchLogsSerializer < ApplicationSerializer
attributes :term,
:searches,
- :click_through,
- :unique_searches
+ :ctr
end
diff --git a/app/serializers/theme_serializer.rb b/app/serializers/theme_serializer.rb
index 41dfe0120d..eb744802dd 100644
--- a/app/serializers/theme_serializer.rb
+++ b/app/serializers/theme_serializer.rb
@@ -75,7 +75,7 @@ class ThemeSerializer < ChildThemeSerializer
end
def child_themes
- object.child_themes.order(:name)
+ object.child_themes
end
def settings
diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb
index a702efde7b..1ee5a839d7 100644
--- a/app/services/post_alerter.rb
+++ b/app/services/post_alerter.rb
@@ -271,6 +271,7 @@ class PostAlerter
Notification.types[:replied],
Notification.types[:quoted],
Notification.types[:posted],
+ Notification.types[:private_message],
]
def create_notification(user, type, post, opts = {})
@@ -341,13 +342,8 @@ class PostAlerter
collapsed = true
end
- if type == Notification.types[:private_message]
- destroy_notifications(user, type, post.topic)
- collapsed = true
- end
-
original_post = post
- original_username = opts[:display_username] || post.username # xxxxx need something here too
+ original_username = opts[:display_username].presence || post.username
if collapsed
post = first_unread_post(user, post.topic) || post
diff --git a/bin/docker/boot_dev b/bin/docker/boot_dev
index 65f92a3dfc..125e959eb4 100755
--- a/bin/docker/boot_dev
+++ b/bin/docker/boot_dev
@@ -59,7 +59,12 @@ echo "Using data in: ${DATA_DIR}"
mkdir -p "${DATA_DIR}"
-docker run -d -p 1080:1080 -p 3000:3000 -v "$DATA_DIR:/shared/postgres_data:delegated" -v "$SOURCE_DIR:/src:delegated" $ENV_ARGS --hostname=discourse --name=discourse_dev --restart=always discourse/discourse_dev:release /sbin/boot
+# 1080 mailcatcher
+# 3000 puma... if you must (but unicorn is preferred)
+# 9292 unicorn
+# 9405 prometheus exporter
+
+docker run -d -p 9405:9405 -p 1080:1080 -p 3000:3000 -p 9292:9292 -v "$DATA_DIR:/shared/postgres_data:delegated" -v "$SOURCE_DIR:/src:delegated" $ENV_ARGS --hostname=discourse --name=discourse_dev --restart=always discourse/discourse_dev:release /sbin/boot
if [ "${initialize}" = "initialize" ]; then
echo "Installing gems..."
diff --git a/bin/docker/bundle b/bin/docker/bundle
index 1d1005c5a0..73d39aba5f 100755
--- a/bin/docker/bundle
+++ b/bin/docker/bundle
@@ -1,5 +1,5 @@
#!/bin/bash
PARAMS="$@"
-CMD="cd /src && RAILS_ENV=${RAILS_ENV:=development} bundle $PARAMS"
+CMD="cd /src && USER=discourse RAILS_ENV=${RAILS_ENV:=development} bundle $PARAMS"
docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/mailcatcher b/bin/docker/mailcatcher
index f1f258d70f..385dcbb727 100755
--- a/bin/docker/mailcatcher
+++ b/bin/docker/mailcatcher
@@ -1,4 +1,4 @@
#!/bin/bash
-CMD="mailcatcher --http-ip 0.0.0.0 -f"
+CMD="USER=discourse mailcatcher --http-ip 0.0.0.0 -f"
docker exec -it discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/migrate b/bin/docker/migrate
index 4cdc94f88b..be9e9ebd92 100755
--- a/bin/docker/migrate
+++ b/bin/docker/migrate
@@ -1,6 +1,6 @@
#!/bin/bash
-CMD="cd /src && RAILS_ENV=development rake db:migrate"
+CMD="cd /src && USER=discourse RAILS_ENV=development rake db:migrate"
docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
-CMD="cd /src && RAILS_ENV=test rake db:migrate"
+CMD="cd /src && USER=discourse RAILS_ENV=test rake db:migrate"
docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/psql b/bin/docker/psql
index c9542d02de..c25c840b79 100755
--- a/bin/docker/psql
+++ b/bin/docker/psql
@@ -1,5 +1,5 @@
#!/bin/bash
PARAMS="$@"
-CMD="psql $PARAMS"
+CMD="USER=discourse psql $PARAMS"
docker exec -it -u postgres discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/rails b/bin/docker/rails
index 1c35b78212..4836d47eeb 100755
--- a/bin/docker/rails
+++ b/bin/docker/rails
@@ -6,5 +6,5 @@ then
PARAMS="$PARAMS -b 0.0.0.0"
fi
-CMD="cd /src && RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 LD_PRELOAD=/usr/lib/libjemalloc.so RACK_HANDLER=puma RAILS_ENV=${RAILS_ENV:=development} rails $PARAMS"
+CMD="cd /src && USER=discourse RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 LD_PRELOAD=/usr/lib/libjemalloc.so RACK_HANDLER=puma RAILS_ENV=${RAILS_ENV:=development} rails $PARAMS"
docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/rake b/bin/docker/rake
index 146a824bb2..b1b73b7c95 100755
--- a/bin/docker/rake
+++ b/bin/docker/rake
@@ -1,5 +1,5 @@
#!/bin/bash
PARAMS="$@"
-CMD="cd /src && RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 LD_PRELOAD=/usr/lib/libjemalloc.so RAILS_ENV=${RAILS_ENV:=development} bundle exec rake $PARAMS"
+CMD="cd /src && USER=discourse RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 LD_PRELOAD=/usr/lib/libjemalloc.so RAILS_ENV=${RAILS_ENV:=development} bundle exec rake $PARAMS"
docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/sidekiq b/bin/docker/sidekiq
index 8c90d5e98d..cfb36311aa 100755
--- a/bin/docker/sidekiq
+++ b/bin/docker/sidekiq
@@ -1,5 +1,5 @@
#!/bin/bash
PARAMS="$@"
-CMD="cd /src && RAILS_ENV=${RAILS_ENV:=development} bundle exec sidekiq -q critical -q low -q default"
+CMD="cd /src && USER=discourse RAILS_ENV=${RAILS_ENV:=development} bundle exec sidekiq -q critical -q low -q default"
docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
diff --git a/bin/docker/unicorn b/bin/docker/unicorn
new file mode 100755
index 0000000000..ba615c0954
--- /dev/null
+++ b/bin/docker/unicorn
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+PARAMS="$@"
+if [[ $# = 1 ]] && [[ "$1" =~ "s" ]];
+then
+ PARAMS="$PARAMS -b 0.0.0.0"
+fi
+
+CMD="cd /src && USER=discourse RUBY_GLOBAL_METHOD_CACHE_SIZE=131072 LD_PRELOAD=/usr/lib/libjemalloc.so RAILS_ENV=${RAILS_ENV:=development} bin/unicorn $PARAMS"
+docker exec -it -u discourse:discourse discourse_dev /bin/bash -c "$CMD"
diff --git a/config/initializers/099-anon-cache.rb b/config/initializers/099-anon-cache.rb
index 1b8165511e..9c47f36712 100644
--- a/config/initializers/099-anon-cache.rb
+++ b/config/initializers/099-anon-cache.rb
@@ -1,4 +1,4 @@
-require_dependency "middleware/anonymous_cache"
+require "middleware/anonymous_cache"
enabled =
if Rails.configuration.respond_to?(:enable_anon_caching)
diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml
index e43403dad8..0237414737 100644
--- a/config/locales/client.ar.yml
+++ b/config/locales/client.ar.yml
@@ -336,7 +336,6 @@ ar:
choose_topic:
none_found: "لم نجد اي موضوعات."
title:
- search: "أبحث عن موضوع بالعنوان أو الـ URL أو الـ Id:"
placeholder: "اكتب عنوان الموضوع هنا"
queue:
topic: "الموضوع:"
@@ -504,7 +503,6 @@ ar:
description: "سنرسل لك إشعارا عن كل منشور جديد في كل رسالة، وسترى عداد للردود الجديدة."
watching_first_post:
title: "مراقبة اول منشور"
- description: "سنرسل لك إشعارا لاول منشور فقط في كل موضوع في هذة المجموعة."
tracking:
title: "مُتابع"
description: "سنرسل لك إشعارا إن أشار أحد إلى @اسمك أو ردّ عليك، وستري عداد للردود الجديدة."
@@ -1745,7 +1743,6 @@ ar:
split_topic:
title: "نقل الى موضوع جديد"
action: "نقل الى موضوع جديد"
- topic_name: "اسم الموضوع الجديد"
error: "حدث عطل أثناء نقل المنشورات إلى موضوع جديد."
instructions:
zero: "أنت على وشك انشاء موضوع جديد, ولم تقم باختيار أي منشور لتعبئته."
@@ -2125,7 +2122,6 @@ ar:
description: "ستراقب آليا كل الموضوعات بهذا القسم. ستصلك إشعارات لكل منشور أو موضوع جديد، وسيظهر أيضا عدّاد الردود الجديدة."
watching_first_post:
title: "يُراقب فيها أول مشاركة"
- description: "سنرسل لك إشعارا بأول منشور فقط في كل موضوع جديد بهذة الاقسام."
tracking:
title: "مُتابعة"
description: "ستتابع آليا كل موضوعات هذا القسم. ستصلك إشعارات إن أشار أحدهم إلى @اسمك أو رد عليك، وسيظهر عدّاد الردود الجديدة."
@@ -2611,9 +2607,6 @@ ar:
private_messages_short: "الرسائل"
private_messages_title: "الرسائل"
mobile_title: "متنقل"
- space_free: "{{size}} فارغ"
- uploads: "عمليات الرفع"
- backups: "النسخ الاحتياطية"
traffic_short: "المرور"
traffic: "طلبات تطبيقات الويب"
page_views: "زيارات الصفحة"
diff --git a/config/locales/client.bg.yml b/config/locales/client.bg.yml
index f6d23f250a..e2bd18ab23 100644
--- a/config/locales/client.bg.yml
+++ b/config/locales/client.bg.yml
@@ -250,7 +250,6 @@ bg:
choose_topic:
none_found: "Няма нови теми."
title:
- search: "Търсене на тема по име, url адрес или id номер:"
placeholder: "Въведете заглавието на темата тук"
queue:
topic: "Тема:"
@@ -387,7 +386,6 @@ bg:
description: "Вие бъдете уведомени за всеки нов пост във всяко съобщение, както и за броя на новите съобщения."
watching_first_post:
title: "Следейки за първа публикация"
- description: "Вие ще бъдете уведомявани само за първата публикация във всяка нова тема в тази група."
tracking:
title: "Проследяване"
description: "Ще бъдете уведомени, ако някой ви спомене чрез @name или ви отговори, както и на броя на новите ви съобщения ще бъдат показани."
@@ -1344,7 +1342,6 @@ bg:
split_topic:
title: "Премести в нова тема"
action: "премести в нова тема"
- topic_name: "Име на новата тема"
error: "Възникна грешка при преместването на публикацията в нова тема."
instructions:
one: "На път сте да създадете нова тема и да я запълните с публикацията която избрахте."
@@ -1828,7 +1825,6 @@ bg:
description: "Вие автоматично ще следите всички теми с този таг. Ще бъдете информиран за всички нови публикации и теми, и броят на непрочетените и новите публикации ще бъде показан до съответната тема. "
watching_first_post:
title: "Наблюдаване на първа публикация"
- description: "Ще бъдете уведомени за първата публикация във всяка нова тема с този етикет."
tracking:
title: "Следене"
description: "Автоматично ще следите всички теми с този етикет. Броя на непрочетените и новите публикации ще се появява до темата."
@@ -1888,9 +1884,6 @@ bg:
private_messages_short: "Съобщения"
private_messages_title: "Съобщения"
mobile_title: "Мобилен"
- space_free: "{{size}} свободни"
- uploads: "качвания"
- backups: "бекъпи"
traffic_short: "Трафик"
traffic: "Приложение уеб заявки"
show_traffic_report: "Покажи детайлен репорт на трафика"
@@ -2291,9 +2284,6 @@ bg:
title: "Търсене в логовете"
term: "Термин"
searches: "Търсения"
- click_through: "Посещения"
- unique: "Уникални"
- unique_title: "уникални потребители извършващи търсенето"
types:
full_page: "Пълна страница"
logster:
diff --git a/config/locales/client.bs_BA.yml b/config/locales/client.bs_BA.yml
index 6adbdff30a..073af3c6f6 100644
--- a/config/locales/client.bs_BA.yml
+++ b/config/locales/client.bs_BA.yml
@@ -302,7 +302,6 @@ bs_BA:
choose_topic:
none_found: "Nema tema."
title:
- search: "Traži temu prema naslovu, url-u ili id-u:"
placeholder: "ovdje unesite naslov teme"
queue:
topic: "Tema:"
@@ -480,7 +479,6 @@ bs_BA:
description: "Dobit ćete obavijest za svaki naredni post u svakoj narednoj poruci, i broj novih odgovora će biti prikazan."
watching_first_post:
title: "Pratiti prve objave"
- description: "Bit će te obavješteni samo o prvim objavama u svakoj novoj temi ove grupe."
tracking:
title: "Praćenje"
description: "Bićete obaviješteni ukoliko neko spomene vaše @ime ili nešto što je naslovljeno za vas, i broj novih odgovora će biti prikazan."
@@ -1766,7 +1764,6 @@ bs_BA:
split_topic:
title: "Move to New Topic"
action: "move to new topic"
- topic_name: "Ime Nove Teme"
error: "There was an error moving posts to the new topic."
instructions:
one: "Kreirati će te novu temu i popuniti je sa objavom koju ste označili."
@@ -2192,9 +2189,6 @@ bs_BA:
private_messages_short: "PP"
private_messages_title: "Privatne Poruke"
mobile_title: "Mobiteli"
- space_free: "{{size}} slobodno"
- uploads: "Učitavanja"
- backups: "backupovi"
show_traffic_report: "Pokaži detaljan izvještaj prometa"
reports:
today: "Today"
diff --git a/config/locales/client.ca.yml b/config/locales/client.ca.yml
index 557c2ca08d..383e9d8924 100644
--- a/config/locales/client.ca.yml
+++ b/config/locales/client.ca.yml
@@ -259,7 +259,6 @@ ca:
choose_topic:
none_found: "No s'ha trobat temes."
title:
- search: "Cercar un tema per nom, url o id:"
placeholder: "escriu el títol del tema aquí"
queue:
topic: "Tema:"
@@ -365,7 +364,6 @@ ca:
description: "Se't notificarà cada nova publicació a tot missatge i es mostrarà un recompte de respostes"
watching_first_post:
title: "Mirant la primera publicació"
- description: "Només se't notificarà de la primera publicació per cada nou tema a aquest grup."
tracking:
title: "Rastreig"
description: "Se't notificarà si algú menciona el teu @nom o et respòn, i se't mostrarà un recompte de noves respostes."
@@ -1390,7 +1388,6 @@ ca:
split_topic:
title: "Mou a un nou tema"
action: "mou a un nou tema"
- topic_name: "Nom del nou tema"
error: "Hi ha hagut una errada en moure publicacions a aquest nou tema."
instructions:
one: "Estàs a punt de crear un nou tema i difondre'l amb la publicació que has triat."
@@ -1654,7 +1651,6 @@ ca:
description: "Veuràs automàticament tots els temes dins aquestes categories. Et notificarem cada publicació nova a cada tema i et mostrarem un recompte de noves respostes."
watching_first_post:
title: "Mirant la primera publicació"
- description: "Només et notificarem sobre la primera publicació a cada nou tema dins aquestes categories."
tracking:
title: "Seguint"
description: "Automàticament observaràs tots els nous temes en aquestes categories. Se't notificarà si algú menciona el teu @nom o et respòn, i se't mostrarà un recompte de noves respostes."
@@ -2047,9 +2043,6 @@ ca:
private_messages_short: "Mtgs"
private_messages_title: "Missatges"
mobile_title: "Mòbil"
- space_free: "{{size}} gratis"
- uploads: "actualitzacions"
- backups: "còpies de seguretat"
traffic_short: "Trànsit"
traffic: "Peticions d'aplicació web"
page_views: "Vistes de pàgina"
diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml
index f4f40e7e68..ef8d318511 100644
--- a/config/locales/client.cs.yml
+++ b/config/locales/client.cs.yml
@@ -341,7 +341,6 @@ cs:
choose_topic:
none_found: "Žádná témata nenalezena."
title:
- search: "Hledat téma podle názvu, URL nebo ID:"
placeholder: "sem napište název tématu"
queue:
topic: "Téma:"
@@ -525,7 +524,6 @@ cs:
description: "Budete informováni o každém novém příspěvku v každné zprávě a také se vám zobrazí počet nepřečtených příspěvků."
watching_first_post:
title: "Hlídané první příspěvky"
- description: "Budete informováni o prvním novém příspěvku v každém novém tématu vytvořeném v této skupině."
tracking:
title: "Sledování"
description: "Budete informováni pokud někdo zmíní vaše @jméno nebo odpoví na váš příspěvek a zobrazí se vám počet nových odpovědí."
@@ -1929,7 +1927,6 @@ cs:
split_topic:
title: "Rozdělit téma"
action: "do nového tématu"
- topic_name: "Název nového tématu:"
error: "Bohužel nastala chyba při rozdělování tématu."
instructions:
one: "Chystáte se vytvořit nové téma a naplnit ho příspěvkem, který jste označili."
@@ -2327,7 +2324,6 @@ cs:
description: "Budete automaticky sledovat všechna témata v těchto kategoriích. Budete upozorněni na každý nový příspěvek v každém tématu a zobrazí se počet nových odpovědí."
watching_first_post:
title: "Hlídání prvního příspěvku"
- description: "Budete informováni o prvním novém příspěvku v každém novém tématu vytvořeném v těchto kategoriích."
tracking:
title: "Sledování"
description: "Budete automaticky sledovat všechna nová témata v těchto kategoriích. Budete upozorněni, pokud někdo zmíní vaše @jméno nebo vám odpoví, a zobrazí se počet nových odpovědí."
@@ -2712,7 +2708,6 @@ cs:
description: "Budete automaticky sledovat všechna nová témata s těmito štítky. Na všechny nové příspěvky a témata budete upozorněni. Počet nových příspěvků se zobrazí vedle tématu."
watching_first_post:
title: "Hlídané první příspěvky"
- description: "Budete upozorněni na první příspěvek v každém novém tématu s tímto tagem."
tracking:
title: "Sledování"
description: "Budete automaticky sledovat všechna témata s tímto tagem. Počet nepřečtených a nových příspěvků bude vidět vedle tématu."
@@ -2800,9 +2795,6 @@ cs:
private_messages_short: "Zpráv"
private_messages_title: "Zprávy"
mobile_title: "Mobilní verze"
- space_free: "{{size}} prázdné"
- uploads: "Nahrané soubory"
- backups: "zálohy"
traffic_short: "Provoz"
traffic: "Webové požadavky na aplikaci"
page_views: "Zobrazení stránky "
@@ -3447,9 +3439,6 @@ cs:
title: "Vyhledat přihlášení"
term: "Období"
searches: "Vyhledávání"
- click_through: "kliknutí "
- unique: "Unikátní"
- unique_title: "Unikátní uživatel provádějící vyhledávání"
types:
all_search_types: "Vyhledávat všechny typy"
header: "Hlavička"
diff --git a/config/locales/client.da.yml b/config/locales/client.da.yml
index 3f9907d72a..e175842c50 100644
--- a/config/locales/client.da.yml
+++ b/config/locales/client.da.yml
@@ -255,7 +255,6 @@ da:
choose_topic:
none_found: "Ingen emner fundet."
title:
- search: "Søg efter et emne efter navn, url eller UD:"
placeholder: "indtast emnets titel her"
queue:
topic: "Emne:"
@@ -382,7 +381,6 @@ da:
description: "Du får beskeder om hvert nyt indlæg i hver besked og antallet af nye svar bliver vist."
watching_first_post:
title: "Ser på første indlæg"
- description: "Du får kun besked om første indlæg i hvert nyt emne i denne gruppe."
tracking:
title: "Følger"
description: "Du får besked hvis nogen nævner dit @navn eller svarer dig og antallet af nye svar bliver vist."
@@ -1455,7 +1453,6 @@ da:
split_topic:
title: "Flyt til nyt emne"
action: "flyt til nyt emne"
- topic_name: "Navn på nyt emne"
error: "Der opstod en fejl under flytningen af indlæg til det nye emne."
instructions:
one: "Du er ved at oprette et nyt emne med det valgte indlæg."
@@ -1733,7 +1730,6 @@ da:
description: "Du overvåger automatisk alle emner i disse kategorier. Du får besked om hvert nyt indlæg i hvert emne og antallet af nye svar bliver vist."
watching_first_post:
title: "Overvåger Første Indlæg"
- description: "Du vil kun blive notificeret om det første indlæg under hvert nyt emne i denne kategori."
tracking:
title: "Følger"
description: "Du tracker automatisk alle emner i disse kategorier. Du får besked hvis nogen nævner dit @navn eller svarer dig, og antallet af nye svar bliver vist."
@@ -2133,9 +2129,6 @@ da:
private_messages_short: "Beskeder"
private_messages_title: "Beskeder"
mobile_title: "Mobil"
- space_free: "{{size}} tilbage"
- uploads: "uploads"
- backups: "backups"
lastest_backup: "Senest: %{date}"
traffic_short: "Trafik"
traffic: "Applikation web forespørgsler"
diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml
index 12ce0a5a60..c015825a9a 100644
--- a/config/locales/client.de.yml
+++ b/config/locales/client.de.yml
@@ -291,7 +291,6 @@ de:
choose_topic:
none_found: "Keine Themen gefunden."
title:
- search: "Suche nach Thema anhand von Name, URL oder ID:"
placeholder: "Gib hier den Titel des Themas ein"
queue:
topic: "Thema:"
@@ -467,7 +466,6 @@ de:
description: "Du wirst über jeden neuen Beitrag in jeder Nachricht benachrichtigt und die Anzahl neuer Antworten wird angezeigt."
watching_first_post:
title: "Ersten Beitrag beobachten"
- description: "Du erhältst nur eine Benachrichtigung für den ersten Beitrag in jedem neuen Thema in dieser Gruppe."
tracking:
title: "Verfolgen"
description: "Du wirst benachrichtigt, wenn jemand deinen @Namen erwähnt oder auf deinen Beitrag antwortet, und die Anzahl neuer Antworten wird angezeigt."
@@ -1818,7 +1816,6 @@ de:
split_topic:
title: "In neues Thema verschieben"
action: "in ein neues Thema verschieben"
- topic_name: "Bezeichnung des neuen Themas"
error: "Beim Verschieben der Beiträge ins neue Thema ist ein Fehler aufgetreten."
instructions:
one: "Du bist dabei, ein neues Thema zu erstellen und den ausgewählten Beitrag dorthin zu verschieben."
@@ -2158,7 +2155,6 @@ de:
description: "Du wirst automatisch alle neuen Themen in diesen Kategorien beobachten. Du wirst über alle neuen Beiträge in allen Themen benachrichtigt und die Anzahl der neuen Antworten wird angezeigt."
watching_first_post:
title: "Ersten Beitrag beobachten"
- description: "Du erhältst eine Benachrichtigung nur für den ersten Beitrag in jedem neuen Thema in diesen Kategorien."
tracking:
title: "Verfolgen"
description: "Du wirst automatisch alle neuen Themen in diesen Kategorien verfolgen. Du wirst benachrichtigt, wenn jemand deinen @name erwähnt oder dir antwortet, und die Anzahl der neuen Antworten wird angezeigt."
@@ -2509,7 +2505,6 @@ de:
description: "Du wirst automatisch alle Themen mit diesem Schlagwort beobachten. Du wirst über alle neuen Beiträge und Themen benachrichtigt werden. Außerdem wird die Anzahl der ungelesenen und neuen Beiträge neben dem Thema erscheinen."
watching_first_post:
title: "Ersten Beitrag beobachten"
- description: "Du erhältst nur eine Benachrichtigung für den ersten Beitrag in jedem neuen Thema mit diesem Schlagwort."
tracking:
title: "Verfolgen"
description: "Du wirst automatisch allen Themen mit diesem Schlagwort folgen. Die Anzahl der ungelesenen und neuen Beiträge wird neben dem Thema erscheinen."
@@ -2597,9 +2592,6 @@ de:
private_messages_short: "Nachr."
private_messages_title: "Nachrichten"
mobile_title: "Mobilgerät"
- space_free: "{{size}} frei"
- uploads: "Uploads"
- backups: "Backups"
lastest_backup: "Letztes: %{date}"
traffic_short: "Traffic"
traffic: "Web Requests der Applikation"
@@ -2956,7 +2948,6 @@ de:
title: "Die Datenbank auf den letzten funktionierenden Zustand zurücksetzen"
confirm: "Möchtest du wirklich die Datenbank auf den letzten funktionierenden Stand zurücksetzen?"
location:
- local: "Lokal"
s3: "Amazon S3"
export_csv:
success: "Der Export wurde gestartet. Du erhältst eine Nachricht, sobald der Vorgang abgeschlossen ist."
@@ -3350,9 +3341,6 @@ de:
title: "Suchprotokolle"
term: "Suchbegriff"
searches: "Suchen"
- click_through: "Durchklicken"
- unique: "Eindeutig"
- unique_title: "einzelne Benutzer mit dieser Suche"
types:
all_search_types: "Alle Sucharten"
header: "Überschrift"
diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml
index 3626aee7b3..321f093676 100644
--- a/config/locales/client.el.yml
+++ b/config/locales/client.el.yml
@@ -257,7 +257,6 @@ el:
choose_topic:
none_found: "Δεν βρέθηκαν νήματα."
title:
- search: "Ψάξε για ένα Νήμα με το όνομα, το url ή το id του:"
placeholder: "γράψε εδώ τον τίτλο του νήματος"
queue:
topic: "Νήμα:"
@@ -370,7 +369,6 @@ el:
description: "Θα λαμβάνεις ειδοποιήσεις για κάθε καινούρια ανάρτηση σε κάθε μήνυμα και θα εμφανίζεται ο αριθμός των καινούριων απαντήσεων ."
watching_first_post:
title: "Επιτήρηση Πρώτης Ανάρτησης"
- description: "Θα λαμβάνεις ειδοποιήσεις μόνο για την πρώτη ανάρτηση σε κάθε νέο νήμα σε αυτή την ομάδα."
tracking:
title: "Παρακολουθείται"
description: "Θα λαμβάνεις ειδοποιήσεις εάν κάποιος αναφέρει το @όνομά σου ή απαντήσει σε εσένα και θα εμφανίζεται ο αριθμός των καινούριων απαντήσεων."
@@ -1514,7 +1512,6 @@ el:
split_topic:
title: "Μεταφορά σε Νέο Νήμα "
action: "μεταφορά σε νέο νήμα "
- topic_name: "Τίτλος Νέου Νήματος"
error: "Παρουσιάστηκε σφάλμα κατά τη μεταφορά των αναρτήσεων στο νέο νήμα."
instructions:
one: "Ετοιμάζεσαι να δημιουργήσεις ένα νέο νήμα και να μεταφέρεις σε αυτό την επιλεγμένη ανάρτηση."
@@ -1804,7 +1801,6 @@ el:
description: "Θα επιτηρείς αυτόματα όλα τα νήματα σε αυτές τις κατηγορίες. Θα λαμβάνεις ειδοποιήσεις για κάθε νέα ανάρτηση και ένας μετρητής για το πλήθος των νέων απαντήσεων θα εμφανίζεται."
watching_first_post:
title: "Επιτήρηση Πρώτης Ανάρτησης"
- description: "Θα λαμβάνεις ειδοποίηση μόνο για την πρώτη ανάρτηση σε κάθε νέο νήμα σε αυτές τις κατηγορίες."
tracking:
title: "Παρακολουθείται"
description: "Θα παρακολουθείς αυτόματα όλα τα νήματα σε αυτές τις κατηγορίες. Θα λαμβάνεις ειδοποιήσεις εάν κάποιος αναφέρει το @όνομά σου ή απαντήσει σε εσένα και ένας μετρητής για το πλήθος των νέων απαντήσεων θα εμφανίζεται."
@@ -2224,9 +2220,6 @@ el:
private_messages_short: "Μνμτ."
private_messages_title: "Μηνύματα"
mobile_title: "Κινητό"
- space_free: "{{size}} ελεύθερα"
- uploads: "ανεβάσματα"
- backups: "αντίγραφα ασφαλείας"
traffic_short: "Κίνηση"
traffic: "Web requests της εφαρμογής"
page_views: "Προβολές σελίδας"
@@ -2786,7 +2779,6 @@ el:
title: "Αναζήτηση καταγραφών"
term: "Όρος"
searches: "Αναζητήσεις"
- unique: "Μοναδικό"
logster:
title: "Error Logs"
watched_words:
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 548fd10194..bb24a2ff5a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -213,6 +213,7 @@ en:
submit: "Submit"
generic_error: "Sorry, an error has occurred."
generic_error_with_reason: "An error occurred: %{error}"
+ go_ahead: "Go ahead"
sign_up: "Sign Up"
log_in: "Log In"
age: "Age"
@@ -231,6 +232,7 @@ en:
privacy: "Privacy"
tos: "Terms of Service"
rules: "Rules"
+ conduct: "Code of Conduct"
mobile_view: "Mobile View"
desktop_view: "Desktop View"
you: "You"
@@ -345,9 +347,15 @@ en:
choose_topic:
none_found: "No topics found."
title:
- search: "Search for a Topic by name, url or id:"
+ search: "Search for a Topic by title, url or id:"
placeholder: "type the topic title here"
+ choose_message:
+ none_found: "No messages found."
+ title:
+ search: "Search for a Message by title:"
+ placeholder: "type the message title here"
+
queue:
topic: "Topic:"
approve: 'Approve'
@@ -530,7 +538,7 @@ en:
description: "You will be notified of every new post in every message, and a count of new replies will be shown."
watching_first_post:
title: "Watching First Post"
- description: "You will only be notified of the first post in each new topic in this group."
+ description: "You will be notified of new topics in this group but not replies to the topics."
tracking:
title: "Tracking"
description: "You will be notified if someone mentions your @name or replies to you, and a count of new replies will be shown."
@@ -995,6 +1003,7 @@ en:
text: "Bulk Invite from File"
success: "File uploaded successfully, you will be notified via message when the process is complete."
error: "Sorry, file should be CSV format."
+ confirmation_message: "You’re about to email invites to everyone in the uploaded file."
password:
title: "Password"
@@ -1240,6 +1249,7 @@ en:
submit_new_email: "Update Email Address"
sent_activation_email_again: "We sent another activation email to you at {{currentEmail}}. It might take a few minutes for it to arrive; be sure to check your spam folder."
+ sent_activation_email_again_generic: "We sent another activation email. It might take a few minutes for it to arrive; be sure to check your spam folder."
to_continue: "Please Log In"
preferences: "You need to be logged in to change your user preferences."
forgot: "I don't recall my account details"
@@ -2000,10 +2010,16 @@ en:
other: "{{count}} posts"
cancel: "Remove filter"
+ move_to:
+ title: "Move to"
+ action: "move to"
+ error: "There was an error moving posts."
+
split_topic:
title: "Move to New Topic"
action: "move to new topic"
- topic_name: "New Topic Name"
+ topic_name: "New Topic Title"
+ radio_label: "New Topic"
error: "There was an error moving posts to the new topic."
instructions:
one: "You are about to create a new topic and populate it with the post you've selected."
@@ -2013,10 +2029,30 @@ en:
title: "Move to Existing Topic"
action: "move to existing topic"
error: "There was an error moving posts into that topic."
+ radio_label: "Existing Topic"
instructions:
one: "Please choose the topic you'd like to move that post to."
other: "Please choose the topic you'd like to move those {{count}} posts to."
+ move_to_new_message:
+ title: "Move to New Message"
+ action: "move to new message"
+ message_title: "New Message Title"
+ radio_label: "New Message"
+ participants: "Participants"
+ instructions:
+ one: "You are about to create a new message and populate it with the post you've selected."
+ other: "You are about to create a new message and populate it with the {{count}} posts you've selected."
+
+ move_to_existing_message:
+ title: "Move to Existing Message"
+ action: "move to existing message"
+ radio_label: "Existing Message"
+ participants: "Participants"
+ instructions:
+ one: "Please choose the message you'd like to move that post to."
+ other: "Please choose the message you'd like to move those {{count}} posts to."
+
merge_posts:
title: "Merge Selected Posts"
action: "merge selected posts"
@@ -2166,6 +2202,7 @@ 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: "delete topic"
actions:
flag: 'Flag'
@@ -2365,7 +2402,7 @@ en:
description: "You will automatically watch all topics in these categories. You will be notified of every new post in every topic, and a count of new replies will be shown."
watching_first_post:
title: "Watching First Post"
- description: "You will only be notified of the first post in each new topic in these categories."
+ description: "You will be notified of new topics in this category but not replies to the topics."
tracking:
title: "Tracking"
description: "You will automatically track all topics in these categories. You will be notified if someone mentions your @name or replies to you, and a count of new replies will be shown."
@@ -2740,7 +2777,7 @@ en:
description: "You will automatically watch all topics with this tag. You will be notified of all new posts and topics, plus the count of unread and new posts will also appear next to the topic."
watching_first_post:
title: "Watching First Post"
- description: "You will only be notified of the first post in each new topic with this tag."
+ description: "You will be notified of new topics in this tag but not replies to the topics."
tracking:
title: "Tracking"
description: "You will automatically track all topics with this tag. A count of unread and new posts will appear next to the topic."
@@ -2838,9 +2875,13 @@ en:
private_messages_short: "Msgs"
private_messages_title: "Messages"
mobile_title: "Mobile"
- space_free: "{{size}} free"
- uploads: "uploads"
- backups: "backups"
+ space_used: "%{usedSize} used"
+ space_used_and_free: "%{usedSize} (%{freeSize} free)"
+ uploads: "Uploads"
+ backups: "Backups"
+ backup_count:
+ one: "%{count} backup on %{location}"
+ other: "%{count} backups on %{location}"
lastest_backup: "Latest: %{date}"
traffic_short: "Traffic"
traffic: "Application web requests"
@@ -2855,11 +2896,13 @@ en:
general_tab: "General"
moderation_tab: "Moderation"
security_tab: "Security"
+ reports_tab: "Reports"
disabled: Disabled
timeout_error: Sorry, query is taking too long, please pick a shorter interval
exception_error: Sorry, an error occurred while executing the query
too_many_requests: You’ve performed this action too many times. Please wait before trying again.
not_found_error: Sorry, this report doesn’t exist
+ filter_reports: Filter reports
reports:
trend_title: "%{percent} change. Currently %{current}, was %{prev} in previous period."
@@ -3215,7 +3258,7 @@ en:
title: "Rollback the database to previous working state"
confirm: "Are you sure you want to rollback the database to the previous working state?"
location:
- local: "Local"
+ local: "Local Storage"
s3: "Amazon S3"
export_csv:
@@ -3623,9 +3666,7 @@ en:
title: "Search Logs"
term: "Term"
searches: "Searches"
- click_through: "Click Through"
- unique: "Unique"
- unique_title: "unique users performing the search"
+ click_through_rate: "CTR"
types:
all_search_types: "All search types"
header: "Header"
@@ -3985,6 +4026,7 @@ en:
tags: "Tags"
search: "Search"
groups: "Groups"
+ dashboard: "Dashboard"
secret_list:
invalid_input: "Input fields cannot be empty or contain vertical bar character."
diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml
index 575425b5dc..bfbdd17467 100644
--- a/config/locales/client.es.yml
+++ b/config/locales/client.es.yml
@@ -180,6 +180,7 @@ es:
submit: "Enviar"
generic_error: "Lo sentimos, ha ocurrido un error."
generic_error_with_reason: "Ha ocurrido un error: %{error}"
+ go_ahead: "Continuar"
sign_up: "Registrarse"
log_in: "Iniciar sesión"
age: "Edad"
@@ -198,6 +199,7 @@ es:
privacy: "Privacidad"
tos: "Condiciones de uso"
rules: "Reglas"
+ conduct: "Código de conducta"
mobile_view: "Versión móvil"
desktop_view: "Versión de escritorio"
you: "Tú"
@@ -257,6 +259,10 @@ es:
new_topic: "Nuevo tema borrador"
new_private_message: "Nuevo mensaje privado borrador"
topic_reply: "Respuesta borrador"
+ abandon:
+ confirm: "Ya has abierto otro borrador en este tema. ¿Seguro que quieres abandonarlo?"
+ yes_value: "Sí, abandonar"
+ no_value: "No, mantener"
topic_count_latest:
one: "Ver {{count}} tema nuevo o actualizado"
other: "Ver {{count}} temas nuevos o actualizados"
@@ -291,7 +297,6 @@ es:
choose_topic:
none_found: "Ningún tema encontrado."
title:
- search: "Buscar un Tema por nombre, url o id:"
placeholder: "escribe el título de tema aquí"
queue:
topic: "Tema:"
@@ -467,7 +472,7 @@ es:
description: "Se te notificará de cada nuevo post en este mensaje y se mostrará un contador de nuevos posts."
watching_first_post:
title: "Vigilar Primer Post"
- description: "Sólo se te notificará del primer post en cada nuevo tema en este grupo."
+ description: "Se te notificará acerca de nuevos temas en este grupo, pero no cuando haya nuevas respuestas."
tracking:
title: "Siguiendo"
description: "Se te notificará si alguien menciona tu @nombre o te responde, y un contador de nuevos mensajes será mostrado."
@@ -708,7 +713,7 @@ es:
label: "Código"
rate_limit: "Por favor, espera antes de volver a intentar otro código de autenticación."
enable_description: |
- Escanee este código QR en una app soportada (Android – iOS e ingrese su código de autenticación.
+ Escanea este código QR en una app soportada (Android – iOS e introduce su código de autenticación.
disable_description: "Por favor ingrese el código de autenticación desde su aplicación"
show_key_description: "Ingrese Manualmente"
extended_description: |
@@ -732,7 +737,7 @@ es:
title: "Cambiar tu imagen de perfil"
gravatar: "Gravatar, basado en"
gravatar_title: "Cambia tu avatar en la web de Gravatar"
- gravatar_failed: "No pudimos encontrar tu Gravatar con esta dirección de correo."
+ gravatar_failed: "Hemos podido encontrar tu Gravatar con esta dirección de correo."
refresh_gravatar_title: "Actualizar tu Gravatar"
letter_based: "Imagen de perfil asignada por el sistema"
uploaded_avatar: "Foto personalizada"
@@ -898,6 +903,7 @@ es:
text: "Invitación masiva desde archivo"
success: "Archivo subido correctamente, se te notificará con un mensaje cuando se complete el proceso."
error: "Lo siento, el archivo debe tener formato CSV. "
+ confirmation_message: "Estás a punto de enviar invitaciones por correo a todo el mundo en el archivo subido."
password:
title: "Contraseña"
too_short: "Tu contraseña es demasiada corta."
@@ -1176,7 +1182,7 @@ es:
categories_with_featured_topics: "Categorías y temas destacados"
categories_and_latest_topics: "Categorías y temas recientes"
categories_and_top_topics: "Categorías y Temas Top"
- categories_boxes: "Cajas con Subcategorías"
+ categories_boxes: "Cajas con subcategorías"
categories_boxes_with_topics: "Cajas con temas destacados"
shortcut_modifier_key:
shift: 'Shift'
@@ -1255,7 +1261,7 @@ es:
category_missing: "Debes escoger una categoría."
tags_missing: "Debes seleccionar al menos {{count}} etiquetas"
save_edit: "Guardar edición"
- overwrite_edit: "Sobrescribir Edición"
+ overwrite_edit: "Sobrescribir edición"
reply_original: "Responder en el Tema Original"
reply_here: "Responder Aquí"
reply: "Responder"
@@ -1819,7 +1825,6 @@ es:
split_topic:
title: "Mover a un tema nuevo"
action: "mover a un tema nuevo"
- topic_name: "Nombre del tema nuevo"
error: "Hubo un error moviendo los posts al nuevo tema"
instructions:
one: "Estas a punto de crear un tema nuevo y rellenarlo con el post que has seleccionado."
@@ -1836,7 +1841,7 @@ es:
action: "unir posts seleccionados"
error: "Hubo un error al unir los posts seleccionados."
change_owner:
- title: "Cambiar Dueño"
+ title: "Cambiar dueño"
action: "cambiar dueño"
error: "Hubo un error cambiando la autoría de los posts."
placeholder: "nombre de usuario del nuevo dueño"
@@ -1966,6 +1971,7 @@ es:
unlock_post_description: "permitir al usuario que publicó, editar este post"
delete_topic_disallowed_modal: "No tienes permiso para borrar este tema. Si de verdad quieres que se elimine, repórtalo y explica los motivos en el propio reporte para que lo estudiemos."
delete_topic_disallowed: "no tienes permiso para borrar este tema"
+ delete_topic: "borrar tema"
actions:
flag: 'Reportar'
defer_flags:
@@ -2159,7 +2165,7 @@ es:
description: "Vigilarás automáticamente todos los temas en estas categorías. Se te notificará de cada nuevo post en cada tema, y se mostrará un contador de nuevas respuestas."
watching_first_post:
title: "Vigilar Primer Post"
- description: "Se te notificará del primer post de cada nuevo tema en estas categorías."
+ description: "Se te notificará acerca de nuevos temas en esta categoría, pero no cuando haya nuevas respuestas."
tracking:
title: "Seguir"
description: "Seguirás automáticamente todos los temas en estas categorías. Se te notificará si alguien menciona tu @nombre o te responde, y se mostrará un contador de nuevas respuestas."
@@ -2490,10 +2496,10 @@ es:
sort_by_name: "nombre"
manage_groups: "Administrar grupos de etiquetas"
manage_groups_description: "Definir grupos para organizar etiquetas"
- upload: "Subir Etiquetas"
+ upload: "Subir etiquetas"
upload_description: "Sube un archivo csv para crear etiquetas en masa"
upload_instructions: "Una por línea, opcional con un grupo de etiquetas en el formato 'tag_name,tag_group'."
- upload_successful: "Etiquetas subidas exitosamente"
+ upload_successful: "Etiquetas subidas con éxito"
delete_unused_confirmation:
one: "%{count} etiqueta será eliminada: %{tags}"
other: "%{count} etiquetas serán eliminadas: %{tags}"
@@ -2514,7 +2520,7 @@ es:
description: "Vigilarás automáticamente todos los temas en esta etiqueta. Se te notificará de todos los nuevos temas y posts, y además aparecerá el contador de posts sin leer y nuevos posts al lado del tema."
watching_first_post:
title: "Vigilar Primer Post"
- description: "Se te notificará únicamente en el primer post de cada nuevo tema con esta etiqueta."
+ description: "Se te notificará acerca de nuevos temas con esta etiqueta, pero no cuando haya nuevas respuestas."
tracking:
title: "Seguir"
description: "Seguirás automáticamente todos los temas con esta etiqueta. Aparecerá un contador de posts sin leer y nuevos posts al lado del tema."
@@ -2602,9 +2608,13 @@ es:
private_messages_short: "Mensajes privados"
private_messages_title: "Mensajes"
mobile_title: "Móvil"
- space_free: "{{size}} libre"
- uploads: "subidas"
- backups: "backups"
+ space_used: "%{usedSize} usado"
+ space_used_and_free: "%{usedSize} (%{freeSize} libre)"
+ uploads: "Archivos subidos"
+ backups: "Copias de seguridad"
+ backup_count:
+ one: "%{count} copia de seguridad en %{location}"
+ other: "%{count} copias de seguridad en %{location}"
lastest_backup: "Recientes: %{date}"
traffic_short: "Tráfico"
traffic: "Peticiones web de la app"
@@ -2618,11 +2628,14 @@ es:
all_reports: "Todos los reportes"
general_tab: "General"
moderation_tab: "Moderación"
+ security_tab: "Seguridad"
+ reports_tab: "Reportes"
disabled: Desactivado
timeout_error: "Lo sentimos, la solicitud está durando demasiado, por favor selecciona un periodo más corto"
exception_error: "Lo siento, ha ocurrido un error al ejecutar la consulta"
too_many_requests: "Has realizado esta acción demasiadas veces. Por favor, espera antes de intentarlo de nuevo."
not_found_error: "Lo sentimos, este reporte no existe"
+ filter_reports: Filtrar reporte
reports:
trend_title: "%{percent} de cambio. Actualmente %{current}, era %{prev} en el periodo previo."
today: "Hoy"
@@ -2645,7 +2658,7 @@ es:
no_data: "Ningún dato a mostrar."
trending_search:
more: 'Buscar logs'
- disabled: 'El reporte de Tendencias de Búsquedas está inhabilitado. Habilita el log consultas de búsquedas para colectar datos.'
+ disabled: 'El reporte de Tendencias de búsqueda está inhabilitado. Habilita el log consultas de búsquedas para recolectar datos.'
commits:
latest_changes: "Cambios recientes: ¡actualiza a menudo!"
by: "por"
@@ -2963,7 +2976,7 @@ es:
title: "Regresar la base de datos al estado funcional anterior"
confirm: "¿Seguro que quieres retornar la base de datos al estado funcional previo?"
location:
- local: "Local"
+ local: "Almacenamiento local"
s3: "Amazon S3"
export_csv:
success: "Exportación iniciada, se te notificará a través de un mensaje cuando el proceso se haya completado."
@@ -3010,12 +3023,12 @@ es:
theme: "Tema"
component: "Componente"
components: "Componentes"
- theme_name: "Nombre del Theme"
- component_name: "Nombre del Componente"
- themes_intro: "Selecciona un theme existente o crear nuevo para empezar"
- beginners_guide_title: "Guía para novatos usando Discourse Themes"
- developers_guide_title: "Guía para desarrolladores para Discourse Themes"
- browse_themes: "Navegar themes de la comunidad"
+ theme_name: "Nombre del tema"
+ component_name: "Nombre del componente"
+ themes_intro: "Selecciona un tema existente o crear nuevo para empezar"
+ beginners_guide_title: "Guía para novatos usando temas de Discourse"
+ developers_guide_title: "Guía para desarrolladores para temas de Discourse"
+ browse_themes: "Ver temas de la comunidad"
import_theme: "Importar Theme"
customize_desc: "Personalizar:"
title: "Themes"
@@ -3038,14 +3051,14 @@ es:
custom_sections: "Personalizaciones:"
theme_components: "Componentes del Theme"
convert: "Convertir"
- convert_component_alert: "¿Estás seguro que quieres convertir este componente en theme? Será eliminado como componente desde %{relatives}."
- convert_component_tooltip: "Convertir este componente en theme"
+ convert_component_alert: "¿Estás seguro de que quieres convertir este componente en tema? Será eliminado como componente desde %{relatives}."
+ convert_component_tooltip: "Convertir este componente en tema"
convert_theme_alert: "¿Estás seguro que quieres convertir este theme en componente? Será eliminado como principal desde %{relatives}."
- convert_theme_tooltip: "Convertir este theme en componente"
- inactive_themes: "Themes inactivos:"
- broken_theme_tooltip: "Este theme tiene errores en su CSS, HTML o YAML"
- default_theme_tooltip: "Este theme es el theme por defecto del sitio"
- updates_available_tooltip: "Hay actualizaciones disponibles para este theme"
+ convert_theme_tooltip: "Convertir este tema en componente"
+ inactive_themes: "Tema inactivos:"
+ broken_theme_tooltip: "Este tema tiene errores en su CSS, HTML o YAML"
+ default_theme_tooltip: "Este tema es el tema por defecto del sitio"
+ updates_available_tooltip: "Hay actualizaciones disponibles para este tema"
and_x_more: "y {{count}} más."
collapse: Colapsar
uploads: "Subidos"
@@ -3087,7 +3100,7 @@ es:
one: "Theme está 1 commit detrás!"
other: "Theme está {{count}} commits detrás!"
compare_commits: "(Ver nuevos commits)"
- repo_unreachable: "No se pudo contactar con el repositorio Git de este theme. Mensaje de error:"
+ repo_unreachable: "No se ha podido contactar con el repositorio Git de este tema. Mensaje de error:"
scss:
text: "CSS"
title: "Ingresa tu CSS, aceptamos estilos válidos de CSS y SCSS"
@@ -3161,12 +3174,12 @@ es:
title: "Emails"
settings: "Ajustes"
templates: "Plantillas"
- preview_digest: "Vista previa de Resumen"
+ preview_digest: "Vista previa de resumen"
advanced_test:
- title: "Prueba Avanzada"
- desc: "Vea como Discourse procesa los correos electrónicos recibidos. Para poder procesar correctamente el correo electrónico, pegue debajo del mensaje de correo electrónico original completo."
- email: "Mensaje Original"
- run: "Correr Prueba"
+ title: "Prueba avanzada"
+ desc: "Observa cómo Discourse procesa los correos electrónicos recibidos. Para poder procesar correctamente el correo electrónico, pega debajo del mensaje de correo electrónico original completo."
+ email: "Mensaje original"
+ run: "Realizar prueba"
text: "Cuerpo del mensaje seleccionado"
elided: "Texto elidido"
sending_test: "Enviando e-mail de prueba..."
@@ -3235,7 +3248,7 @@ es:
silence_user: "Usuario Silenciado"
delete_post: "Post Borrado"
delete_topic: "Tema Borrado"
- post_approved: "Post Aprobado"
+ post_approved: "Post aprobado"
logs:
title: "Logs"
action: "Acción"
@@ -3364,9 +3377,7 @@ es:
title: "Buscar Logs"
term: "Término"
searches: "Búsquedas"
- click_through: "A través del clic"
- unique: "Único"
- unique_title: "usuarios únicos realizando la búsqueda"
+ click_through_rate: "CTR"
types:
all_search_types: "Todos los tipos de búsqueda"
header: "Encabezado"
@@ -3479,6 +3490,8 @@ es:
suspended_until: "(hasta %{until})"
cant_suspend: "Este usuario no puede ser suspendido."
delete_all_posts: "Eliminar todos los posts"
+ delete_posts_progress: "Borrando mensajes..."
+ delete_posts_failed: "Ha habido un problema borrando los mensajes."
penalty_post_actions: "¿Qué te gustaría hacer con la publicación asociada?"
penalty_post_delete: "Borrar el post"
penalty_post_edit: "Editar el post"
diff --git a/config/locales/client.et.yml b/config/locales/client.et.yml
index b7f580e9e4..deb744b8ee 100644
--- a/config/locales/client.et.yml
+++ b/config/locales/client.et.yml
@@ -153,6 +153,7 @@ et:
ap_southeast_1: "Aasia ja Vaikne ookean (Singapur)"
ap_southeast_2: "Aasia ja Vaikne ookean (Sydney)"
cn_north_1: "Hiina (Beijing)"
+ cn_northwest_1: "Hiina (Ningxia)"
eu_central_1: "EL (Frankfurt)"
eu_west_1: "EL (Iirimaa)"
eu_west_2: "EU (London)"
@@ -188,6 +189,7 @@ et:
privacy: "Privaatsus"
tos: "Teenuse tingimused"
rules: "Reeglid"
+ conduct: "Käitumise juhised"
mobile_view: "Mobiilne vaade"
desktop_view: "Täisvaade"
you: "Sina"
@@ -208,6 +210,8 @@ et:
character_count:
one: "{{count}} sümbol"
other: "{{count}} sümbolit"
+ related_messages:
+ title: "Seotud sõnumid"
suggested_topics:
title: "Soovitatud teemad"
pm_title: "Soovitatud sõnumid"
@@ -242,7 +246,20 @@ et:
resume: "Taasta"
remove: "Eemalda"
new_topic: "Uus teema mustand"
+ new_private_message: "Uus privaatsõnumi mustand"
topic_reply: "Mustandi vastus"
+ abandon:
+ yes_value: "Jah, hülga"
+ no_value: "Ei, hoia alles"
+ topic_count_latest:
+ one: "Vaata {{count}} uut või uuendatud teemat"
+ other: "Vaata {{count}} uut või uuendatud teemat"
+ topic_count_unread:
+ one: "Vaata {{count}} lugemata teemat"
+ other: "Vaata {{count}} lugemata teemat"
+ topic_count_new:
+ one: "Vaata {{count}} uut teemat"
+ other: "Vaata {{count}} uut teemat"
preview: "eelvaade"
cancel: "tühista"
save: "Salvesta muudatused"
@@ -250,6 +267,7 @@ et:
saved: "Salvestatud!"
upload: "Lae üles"
uploading: "Laen üles..."
+ uploading_filename: "Üleslaadimine: {{filename}}..."
clipboard: "lõikelaud"
uploaded: "Üles laetud!"
pasting: "Asetamine..."
@@ -267,7 +285,6 @@ et:
choose_topic:
none_found: "Teemasid ei leitud."
title:
- search: "Otsi teemasid nime, url või id järgi:"
placeholder: "kirjuta teema pealkiri siia"
queue:
topic: "Teema:"
@@ -333,14 +350,17 @@ et:
make_user_group_owner: "Määra omanikuks"
remove_user_as_group_owner: "Eemalda omanik"
groups:
+ member_added: "Lisatud"
add_members:
title: "Lisa liikmeid"
+ description: "Halda selle grupi liikmeid"
usernames: "Kasutajanimed"
manage:
title: 'Halda'
name: 'Nimi'
full_name: 'Täisnimi'
add_members: "Lisa liikmeid"
+ delete_member_confirm: "Kas eemaldada '%{username}' grupist '%{group}'?"
profile:
title: Profiil
interaction:
@@ -383,7 +403,11 @@ et:
owner: "omanik"
index:
title: "Grupid"
+ all: "Kõik grupid"
empty: "Ühtegi nähtavat gruppi pole."
+ filter: "Filtreeri grupi liigi järgi"
+ owner_groups: "Grupid, mille omanik ma olen"
+ close_groups: "Suletud grupid"
automatic_groups: "Automaatsed grupid"
automatic: "Automaatne"
closed: "Suletud"
@@ -405,14 +429,18 @@ et:
filter_placeholder_admin: "kasutajanimi või e-post"
filter_placeholder: "kasutajanimi"
remove_member: "Eemalda liige"
+ remove_member_description: "Eemalda %{username} sellest grupist"
make_owner: "Määra omanikuks"
+ make_owner_description: "Tee %{username} selle grupi omanikuks"
remove_owner: "Eemalda omanik"
+ remove_owner_description: "Eemalda %{username} selle grupi omanikustaatusest"
owner: "Omanik"
topics: "Teemat"
posts: "Postitused"
mentions: "Mainimisi"
messages: "Sõnumid"
alias_levels:
+ mentionable: "Kes võivad seda gruppi mainida?"
messageable: "Kes saab sellele grupile sõnumeid saata?"
nobody: "Mitte keegi"
only_admins: "Vaid adminid"
@@ -425,7 +453,6 @@ et:
description: "Sind teavitatakse igast uuest postitusest. Ühtlasi kuvatakse uute vastuste arv."
watching_first_post:
title: "Vaatan esimest postitust"
- description: "Sind teavitatakse vaid esimesest postitusest igas uues teemas selles grupis."
tracking:
title: "Jälgin"
description: "Sind teavitatakse, kui keegi sinu @name mainib või sulle vastab. Ühtlasi kuvatakse uute vastuste arv."
@@ -478,6 +505,7 @@ et:
topic_sentence:
one: "1 teema"
other: "%{count} teemat"
+ n_more: "Kategooriad (veel %{count}) ..."
ip_lookup:
title: IP-aadressi Otsing
hostname: Hostinimi
@@ -511,6 +539,7 @@ et:
private_messages: "Sõnumid"
activity_stream: "Tegevused"
preferences: "Eelistused"
+ profile_hidden: "Selle kasutaja avalik profiil on peidetud."
expand_profile: "Näita veel"
collapse_profile: "Ahenda"
bookmarks: "Järjehoidjad"
@@ -590,6 +619,7 @@ et:
revoke_access: "Tühista ligipääs"
undo_revoke_access: "Tühista ligipääsu tühistamine"
api_approved: "Heakskiidetud:"
+ api_last_used_at: "Viimati kasutatud:"
theme: "Teema"
home: "aikimisi avaleht"
staff_counters:
@@ -631,8 +661,11 @@ et:
regenerate: "Genereeri uus"
disable: "Keela"
enable: "Luba"
+ copied_to_clipboard: "Kopeeritud lõikelauale"
second_factor:
+ confirm_password_description: "Palun kinnita jätkamiseks oma parooli"
label: "Kood"
+ rate_limit: "Palun oota enne kui proovid mõnda teist autentimise koodi."
show_key_description: "Sisesta käsitsi"
change_about:
title: "Muuda minu andmeid"
@@ -650,6 +683,7 @@ et:
title: "Muuda oma profiilipilti"
gravatar: "Gravatar, baseerub"
gravatar_title: "Oma avatari muuda Gravatar'i veebilehel"
+ gravatar_failed: "Selle e-posti aadressi küljest Gravatari ei leitud."
refresh_gravatar_title: "Värskenda oma Gravatar'i"
letter_based: "Süsteemi poolt määratud profiilipilt"
uploaded_avatar: "Individuaalne pilt"
@@ -715,6 +749,8 @@ et:
show_all: "Näita kõiki ({{count}})"
show_few: "Näita vähem"
was_this_you: "Kas see olid sina?"
+ secure_account: "Turva minu konto"
+ latest_post: "Sa postitasid viimati..."
last_posted: "Viimane postitus"
last_emailed: "Viimati meilitud"
last_seen: "Vaadatud"
@@ -1082,6 +1118,8 @@ et:
medium_tone: Keskmine nahatoon
medium_dark_tone: Keskmiselt tume nahatoon
dark_tone: Tume nahatoon
+ shared_drafts:
+ publishing: "Teema avaldamine..."
composer:
emoji: "Emoji :)"
more_emoji: "veel..."
@@ -1098,6 +1136,7 @@ et:
saved_local_draft_tip: "salvestatud lokaalselt"
similar_topics: "Sinu teema sarnaneb..."
drafts_offline: "mustandid vallasrežiimis"
+ edit_conflict: 'muuda konflikti'
group_mentioned:
one: "Mainides {{group}}, oled teavitamas 1 inimest – oled selles kindel?"
other: "Mainides {{group}}, oled teavitamas {{count}} inimest – oled selles kindel?"
@@ -1110,8 +1149,11 @@ et:
title_too_long: "Pealkiri ei saa olla pikem kui {{max}} sümbolit"
post_missing: "Positus ei saa olla tühi"
post_length: "Postitus peab olema vähemalt {{min}} sümbolit pikk"
+ try_like: "Oled sa proovinud {{heart}} nuppu?"
category_missing: "Pead valima foorumi"
+ tags_missing: "Sa pead valima vähemalt {{count}} sildi"
save_edit: "Salvesta muudatused"
+ overwrite_edit: "Kirjuta muutmine üle"
reply_original: "Vasta algsesse teemasse"
reply_here: "Vasta siin"
reply: "Vasta"
@@ -1119,6 +1161,8 @@ et:
create_topic: "Loo teema"
create_pm: "Sõnum"
create_whisper: "Sosistamine"
+ create_shared_draft: "Loo jagatud mustand"
+ edit_shared_draft: "Muuda jagatud mustandit"
title: "Või vajuta Ctrl+Enter"
users_placeholder: "Lisa kasutaja"
title_placeholder: "Kuidas seda vestlust ühe lausega kirjeldada?"
@@ -1157,6 +1201,7 @@ et:
olist_title: "Numberloend"
ulist_title: "Täpploend"
list_item: "Loendi element"
+ toggle_direction: "Muuda suunda"
help: "Markdown-i redaktori spikker"
modal_ok: "OK"
modal_cancel: "Tühista"
@@ -1443,6 +1488,7 @@ et:
title: "Sulge ajutiselt"
auto_close:
title: "Sulge teema automaatselt"
+ error: "Palun sisesta korrektne väärtus."
auto_delete:
title: "Kustuta teema automaatselt"
reminder:
@@ -1521,6 +1567,7 @@ et:
visible: "Avalda teemade nimekirjas"
reset_read: "Nulli andmed teema lugemise kohta"
make_public: "Loo avalik teema"
+ make_private: "Loo isiklik sõnum"
feature:
pin: "Tõsta teema esile"
unpin: "Eemalda teema esiletõstmine"
@@ -1605,7 +1652,6 @@ et:
split_topic:
title: "Liiguta uue teema alla"
action: "liiguta uue teema alla"
- topic_name: "Uue teema nimi"
error: "Postituste uude teemasse liigutamisel tekkis viga."
instructions:
one: "Oled loomas uut teemat ja lisamas sinna just valitud postitust."
@@ -1622,10 +1668,12 @@ et:
action: "ühenda valitud postitused"
error: "Valitud postituste ühendamisel tekkis viga."
change_owner:
+ title: "Muuda omanikku"
action: "muuda omanikku"
error: "Postituste omanikuvahetusel tekkis viga."
placeholder: "uue omaniku kasutajanimi"
change_timestamp:
+ title: "Muuda ajatemplit..."
action: "muuda ajatemplit"
invalid_timestamp: "Ajatempel ei saa olla tulevikus."
error: "Teema ajatempli muutmisel tekkis viga."
@@ -1635,6 +1683,7 @@ et:
selected: 'valitud ({{count}})'
select_post:
label: 'vali'
+ title: 'Lisa postitus valikusse'
selected_post:
label: 'valitud'
delete: kustuta valitud
@@ -1724,6 +1773,7 @@ et:
unhide: "Too nähtavale"
change_owner: "Omanikuvahetus"
lock_post: "Lukusta postitus"
+ delete_topic: "kustuta teema"
actions:
flag: 'Tähis'
undo:
@@ -1792,6 +1842,14 @@ et:
like:
one: "ühele see meeldis"
other: "{{count}}-le see meeldis"
+ delete:
+ confirm:
+ one: "Oled sa kindel, et soovid seda postitust kustutada?"
+ other: "Oled sa kindel, et soovid neid {{count}} postitust kustutada?"
+ merge:
+ confirm:
+ one: "Oled kindel, et soovid need postitused ühendada?"
+ other: "Oled kindel, et soovid need {{count}} postitust ühendada?"
revisions:
controls:
first: "Esimene redaktsioon"
@@ -1815,6 +1873,8 @@ et:
button: 'Töötlemata'
raw_email:
displays:
+ raw:
+ button: 'Töötlemata'
text_part:
button: 'Tekst'
html_part:
@@ -1879,7 +1939,6 @@ et:
description: "Sa vaatled nendes foorumites kõiki teemasid automaatselt. Sind teavitatakse igast uuest postitusest igas teemas. Ühtlasi kuvatakse uute vastuste arv."
watching_first_post:
title: "Vaatan esimest postitust"
- description: "Sind teavitatakse vaid esimesest postitusest igas uues teemas nendes foorumites."
tracking:
title: "Jälgimine"
description: "Sa vaatled kõiki nende foorumite teemasid automaatselt. Sind teavitatakse, kui keegi sinu @name mainib või sulle vastab. Ühtlasi kuvatakse uute vastuste arv."
@@ -2277,9 +2336,6 @@ et:
private_messages_short: "Sõnumid"
private_messages_title: "Sõnumid"
mobile_title: "Mobiil"
- space_free: "{{size}} vaba"
- uploads: "üleslaadimisi"
- backups: "varundusi"
traffic_short: "Liiklus"
traffic: "Rakenduse veebipäringud"
page_views: "Vaatamisi"
@@ -2542,7 +2598,6 @@ et:
title: "Pööra andmebaas tagasi viimati toiminud olekusse"
confirm: "Oled kindel, et soovid andmebaasi tagasi viimati toiminud olekusse pöörata?"
location:
- local: "Kohalik"
s3: "Amazon S3"
export_csv:
success: "Eksportimine käivitatud. Protsessi lõppemisel saadetakse Sulle teade."
@@ -2839,8 +2894,6 @@ et:
title: "Otsingu logid"
term: "Termin"
searches: "Otsingud"
- click_through: "Kliki läbi"
- unique: "Unikaalne"
types:
all_search_types: "Kõik otsinguliigid"
header: "Päis"
diff --git a/config/locales/client.fa_IR.yml b/config/locales/client.fa_IR.yml
index 5c6d261057..b323fa47c1 100644
--- a/config/locales/client.fa_IR.yml
+++ b/config/locales/client.fa_IR.yml
@@ -275,7 +275,6 @@ fa_IR:
choose_topic:
none_found: "موضوعی یافت نشد."
title:
- search: "جستجو در موضوعات از روی نام، نشانی url یا شناسه id"
placeholder: "عنوان موضوع را اینجا بنویسید"
queue:
topic: "موضوع:"
@@ -443,7 +442,6 @@ fa_IR:
description: "در صورت ارسال شدن پست جدید در هر پیام یک اعلان برای شما ارسال میشود و تعداد پاسخهای جدید در آن نمایش داده میشود."
watching_first_post:
title: "در حال مشاهده نوشته اول"
- description: "تنها برای اولین نوشته در هر مبحث جدید به شما اطلاع رسانی خواهد شد."
tracking:
title: "پیگیری"
description: "در صورت اشاره شدن به @نام شما توسط اشخاص دیگر و یا دریافت پاسخ، اعلانی برای شما ارسال میشود و تعداد پاسخهای جدید نمایش داده میشود."
@@ -1525,7 +1523,6 @@ fa_IR:
split_topic:
title: "انتقال به موضوع جدید"
action: "انتقال به موضوع جدید"
- topic_name: "نام موضوع جدید"
error: "یک مشکل در انتقال نوشتهها به موضوع جدید پیش آمد."
instructions:
one: "شما در حال ایجاد موضوع جدید و افزودن {{count}} نوشته انتخاب شده به آن هستید."
@@ -1802,7 +1799,6 @@ fa_IR:
description: "شما به صورت خودکار تمام موضوعات این دستهبندی را مشاهده میکنید و از هر نوشته جدید در موضوعات آگاه خواهید شد، همچنین تعداد پاسخهای جدید نمایش داده میشوند."
watching_first_post:
title: "درحال مشاهده نوشتهی اول"
- description: "به شما بابت پستهای اول هر موضوع در این دستهبندیها یک اعلان ارسال خواهد شد."
tracking:
title: "پیگیری"
description: "شما به صورت خودکار تمام موضوعات این دستهبندی را پیگیری خواهید کرد. اگر فردی به صورت @نام به نام شما اشاره کند یا به شما پاسخ دهد، تعداد پاسخها به شما نمایش داده خواهد شد. "
@@ -2203,9 +2199,6 @@ fa_IR:
private_messages_short: "پیامها"
private_messages_title: "پیامها"
mobile_title: "موبایل"
- space_free: "{{size}} آزاد"
- uploads: "بارگذاریها"
- backups: "نسخههای پشتیبان"
traffic_short: "ترافیک"
traffic: "درخواست های نرم افزار وب"
page_views: "بازدید"
diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml
index 06ca40ab9d..3dc3919eb5 100644
--- a/config/locales/client.fi.yml
+++ b/config/locales/client.fi.yml
@@ -180,6 +180,7 @@ fi:
submit: "Lähetä"
generic_error: "On tapahtunut virhe."
generic_error_with_reason: "Tapahtui virhe: %{error}"
+ go_ahead: "Jatka"
sign_up: "Luo tili"
log_in: "Kirjaudu"
age: "Ikä"
@@ -198,6 +199,7 @@ fi:
privacy: "Tietosuoja"
tos: "Käyttöehdot"
rules: "Säännöt"
+ conduct: "Käytössäännöt"
mobile_view: "Mobiilinäkymä"
desktop_view: "Työpöytänäkymä"
you: "Sinä"
@@ -218,6 +220,8 @@ fi:
character_count:
one: "{{count}} merkki"
other: "{{count}} merkkiä"
+ related_messages:
+ title: "Aiheeseen liittyviä keskusteluja"
suggested_topics:
title: "Ketjuehdotuksia"
pm_title: "Keskusteluehdotuksia"
@@ -255,6 +259,10 @@ fi:
new_topic: "Uusi ketjuluonnos"
new_private_message: "Uusi yksityisviestiluonnos"
topic_reply: "Vastausluonnos"
+ abandon:
+ confirm: "Sinulla on keskeneräinen luonnos tässä ketjussa. Haluatko varmasti hylätä sen?"
+ yes_value: "Kyllä, hylkään"
+ no_value: "Ei, säilytän"
topic_count_latest:
one: "Katso {{count}} uusi tai päivittynyt ketju"
other: "Katso {{count}} uutta tai päivittynyttä ketjua"
@@ -289,8 +297,13 @@ fi:
choose_topic:
none_found: "Yhtään ketjua ei löydetty."
title:
- search: "Etsi ketjua nimen, url:n tai id:n perusteella"
+ search: "Etsi ketjua nimen, url:n tai id:n perusteella:"
placeholder: "kirjoita ketjun otsikko tähän"
+ choose_message:
+ none_found: "Keskusteluja ei löytynyt."
+ title:
+ search: "Etsi keskustelua otsikon perusteella:"
+ placeholder: "kirjoita keskustelun otsikko tähän"
queue:
topic: "Ketju:"
approve: 'Hyväksy'
@@ -465,7 +478,7 @@ fi:
description: "Saat ilmoituksen uusista viesteistä jokaisessa viestiketjussa, ja uusien vastausten lukumäärä näytetään."
watching_first_post:
title: "Uudet ketjut tarkkailussa"
- description: "Saat ilmoituksen vain ketjunaloituksista tässä ryhmässä."
+ description: "Saat ilmoituksia uusista tämän ryhmän ketjuista, muttet ketjuihin tulevista vastauksista."
tracking:
title: "Seurannassa"
description: "Saat ilmoituksen, jos joku mainitsee @nimesi tai vastaa sinulle, ja uusien vastausten lukumäärä näytetään."
@@ -477,6 +490,7 @@ fi:
description: "Et saa ilmoituksia uusista ketjuista tässä ryhmässä."
flair_url: "Avatarpinssin kuva"
flair_url_placeholder: "(Ei-pakollinen) kuvan URL tai Font Awesome -luokka"
+ flair_url_description: "Käytä neliönmuotoista, kooltaan vähintään 20px kertaa 20px kuvaa tai FontAwesome ikonia (hyväksytyt muodot: \"fa-icon\", \"far fa-icon\" tai \"fab fa-icon\")."
flair_bg_color: "Avatar-pinssin taustaväri"
flair_bg_color_placeholder: "(Ei-pakollinen) värin Hex-arvo"
flair_color: "Avatarpinssin väri"
@@ -518,6 +532,12 @@ fi:
topic_sentence:
one: "1 ketju"
other: "%{count} ketjua"
+ topic_stat_sentence_week:
+ one: "%{count} uusi ketju viimeisimmän viikon aikana"
+ other: "%{count} uutta ketjua viimeisimmän viikon aikana"
+ topic_stat_sentence_month:
+ one: "%{count} uusi ketju viimeisimmän kuukauden aikana"
+ other: "%{count} uutta ketjua viimeisimmän kuukauden aikana"
n_more: "Alueet (%{count} muuta)..."
ip_lookup:
title: IP-osoitteen haku
@@ -890,6 +910,7 @@ fi:
text: "Lähetä massakutsu tiedostosta"
success: "Tiedoston lähettäminen onnistui. Saat viestin, kun prosessi on valmis."
error: "Pahoittelut, tiedoston tulee olla CSV-muodossa."
+ confirmation_message: "Olet lähettämässä kutsun kaikkiin sähköpostiosoitteisiin, jotka lähettämässäsi tiedostossa on."
password:
title: "Salasana"
too_short: "Salasanasi on liian lyhyt."
@@ -1114,6 +1135,7 @@ fi:
provide_new_email: "Anna uusi osoite niin lähetämme sinulle vahvistamissähköpostin."
submit_new_email: "Päivitä sähköpostiosoite"
sent_activation_email_again: "Lähetimme uuden vahvistusviestin sinulle osoitteeseen {{currentEmail}}. Viestin saapumisessa voi kestää muutama minuutti; muista tarkastaa myös roskapostikansio."
+ sent_activation_email_again_generic: "Lähetimme uuden aktivointisähköpostin. Sen saapumisessa voi kestää joitakin minuutteja; tarkistathan myös roskapostikansiosi."
to_continue: "Ole hyvä ja kirjaudu sisään"
preferences: "Sinun täytyy olla kirjautuneena sisään muokataksesi tilisi asetuksia"
forgot: "En muista käyttäjätilini tietoja"
@@ -1228,6 +1250,7 @@ fi:
saved_local_draft_tip: "tallennettu omalla koneella"
similar_topics: "Tämä ketju vaikuttaa samalta kuin.."
drafts_offline: "offline luonnokset"
+ edit_conflict: 'muokkauskonflikti'
group_mentioned_limit: "Varoitus! Mainitsit ryhmän {{group}}, mutta ylläpitäjä on määrittänyt mainintojen enimmäismääräksi {{max}} käyttäjää, minkä tämän ryhmän jäsenmäärä ylittää. Kukaan ei saa ilmoitusta. "
group_mentioned:
one: "Jos mainitset ryhmän {{group}}, 1 käyttäjä saa ilmoituksen – oletko varma?"
@@ -1242,6 +1265,7 @@ fi:
title_too_long: "Otsikko voi olla korkeintaan {{max}} merkkiä pitkä"
post_missing: "Viesti ei voi olla tyhjä"
post_length: "Viestissä täytyy olla vähintään {{min}} merkkiä"
+ try_like: "Oletko kokeillut {{heart}}-nappia?"
category_missing: "Sinun täytyy valita viestille alue"
tags_missing: "Valitse vähintään {{count}} tunnistetta"
save_edit: "Tallenna"
@@ -1298,6 +1322,7 @@ fi:
toggle_direction: "Vaihda suuntaa"
help: "Markdown apu"
collapse: "pienennä kirjoitusalue"
+ open: "avaa viestikenttä"
abandon: "sulje kirjoitusalue ja poista luonnos"
enter_fullscreen: "siirry koko ruudun kirjoitustilaan"
exit_fullscreen: "poistu koko ruudun kirjoitustilasta"
@@ -1805,10 +1830,15 @@ fi:
one: "1 viesti"
other: "{{count}} viestiä"
cancel: "Poista suodatin"
+ move_to:
+ title: "Siirrä"
+ action: "siirrä"
+ error: "Viestien siirtäminen epäonnistui."
split_topic:
title: "Siirrä uuteen ketjuun"
action: "siirrä uuteen ketjuun"
topic_name: "Uuden ketjun otsikko"
+ radio_label: "Uusi ketju"
error: "Viestien siirtämisessä uuteen ketjuun tapahtui virhe."
instructions:
one: "Olet luomassa uutta ketjua valitsemastasi viestistä."
@@ -1817,9 +1847,27 @@ fi:
title: "Siirrä olemassa olevaan ketjuun"
action: "siirrä olemassa olevaan ketjuun"
error: "Viestien siirtämisessä ketjuun tapahtui virhe."
+ radio_label: "Olemassa oleva ketju"
instructions:
one: "Valitse ketju, johon haluat siirtää valitun viestin."
other: "Valitse ketju, johon haluat siirtää valitut {{count}} viestiä."
+ move_to_new_message:
+ title: "Siirrä uuteen keskusteluun"
+ action: "siirrä uuteen keskusteluun"
+ message_title: "Uuden keskustelun otsikko"
+ radio_label: "Uusi keskustelu"
+ participants: "Osallistujat"
+ instructions:
+ one: "Olet luomassa uutta keskustelua, jossa olisi viesti, jonka olet valinnut."
+ other: "Olet luomassa uutta keskustelua, jossa olisi {{count}} viestiä, jotka olet valinnut."
+ move_to_existing_message:
+ title: "Siirrä olemassa olevaan keskusteluun"
+ action: "siirrä olemassa olevaan keskusteluun"
+ radio_label: "Olemassa oleva keskustelu"
+ participants: "Osallistujat"
+ instructions:
+ one: "Valitse keskustelu, johon haluat siirtää tämän viestin."
+ other: "Valitse keskustelu, johon haluat siirtää nämä {{count}} viestiä."
merge_posts:
title: "Yhdistä valitut viestit"
action: "yhdistä valitut viestit"
@@ -1955,6 +2003,7 @@ fi:
unlock_post_description: "salli kirjoittajan muokata viestiä"
delete_topic_disallowed_modal: "Et saa poistaa tätä ketjua. Jos haluat sen poistetuksi, liputa se perustelujen kera, ja valvoja päättää poistamisesta."
delete_topic_disallowed: "et saa poistaa ketjua"
+ delete_topic: "poista ketju"
actions:
flag: 'Liputa'
defer_flags:
@@ -2048,6 +2097,7 @@ fi:
revert: "Palaa tähän revisioon"
edit_wiki: "Muokkaa wikiä"
edit_post: "Muokkaa viestiä"
+ comparing_previous_to_current_out_of_total: "{{previous}} {{icon}} {{current}} / {{total}}"
displays:
inline:
title: "Näytä lisäykset ja poistot tekstin osana"
@@ -2147,7 +2197,7 @@ fi:
description: "Tarkkailet kaikkia ketjuja näillä alueilla. Saat ilmoituksen jokaisesta uudesta viestistä missä tahansa ketjussa, ja uusien vastausten määrä näytetään."
watching_first_post:
title: "Tarkkaile uusia ketjuja"
- description: "Saat ilmoituksen vain ketjunaloituksista näillä alueilla."
+ description: "Saat ilmoituksia uusista tämän alueen ketjuista, muttet ketjuihin tulevista vastauksista."
tracking:
title: "Seuraa"
description: "Seuraat kaikkia ketjuja näillä alueilla. Saat ilmoituksen jos joku mainitsee @nimesi tai vastaa sinulle, ja uusien vastausten määrä näytetään."
@@ -2479,8 +2529,18 @@ fi:
manage_groups: "Hallinnoi tunnisteryhmiä"
manage_groups_description: "Määrittele ryhmiä tunnisteiden järjestämiseksi"
upload: "Lataa tunnisteita"
+ upload_description: "Luo iso joukko tunnisteita kerralla lähettämällä CSV-tiedosto."
upload_instructions: "Yksi per rivi. Mukana voi olla tunnisteryhmä, tällöin muoto on \"tunnisteen_nimi,tunnisteryhmä\"."
upload_successful: "Tunnisteiden lisäys onnistui"
+ delete_unused_confirmation:
+ one: "%{count} tunniste poistetaan: %{tags}"
+ other: "%{count} tunnistetta poistetaan: %{tags}"
+ delete_unused_confirmation_more_tags:
+ one: "%{tags} ja %{count} muu"
+ other: "%{tags} ja %{count} muuta"
+ delete_unused: "Poista käyttämättömät tunnisteet"
+ delete_unused_description: "Poista kaikki tunnisteet jotka eivät ole jonkun ketjun tai yksityiskeskustelun käytössä"
+ cancel_delete_unused: "Peru"
filters:
without_category: "%{filter} %{tag} ketjut"
with_category: "%{filter} %{tag} ketjut alueella %{category}"
@@ -2492,7 +2552,7 @@ fi:
description: "Ketjut joilla on tämä tunniste asetetaan automaattisesti tarkkailuun. Saat ilmoituksen kaikista uusista viesteistä ja ketjuista ja uusien viestien lukumäärä näytetään ketjun otsikon vieressä.\n "
watching_first_post:
title: "Tarkkaile uusia ketjuja"
- description: "Saat ilmoituksen vain ketjunaloituksista, jolla on tämä tunniste."
+ description: "Saat ilmoituksia uusista ketjuista, joilla on tämä tunniste, muttet ketjuihin tulevista vastauksista."
tracking:
title: "Seuraa"
description: "Seuraat automaattisesti ketjuja joilla on tämä tunniste. Uusien ja lukemattomien viestien lukumäärä näytetään ketjun yhteydessä."
@@ -2580,9 +2640,13 @@ fi:
private_messages_short: "YV:t"
private_messages_title: "Viestit"
mobile_title: "Mobiili"
- space_free: "{{size}} vapaata"
- uploads: "lataukset"
- backups: "varmuuskopiot"
+ space_used: "%{usedSize} käytetty"
+ space_used_and_free: "%{usedSize} (%{freeSize} vapaana)"
+ uploads: "Lataukset"
+ backups: "Varmuuskopiot"
+ backup_count:
+ one: "%{count} varmuuskopio kohteessa %{location}"
+ other: "%{count} varmuuskopiota kohteessa %{location}"
lastest_backup: "Viimeisin: %{date}"
traffic_short: "Liikenne"
traffic: "Sovelluksen web-pyynnöt"
@@ -2596,10 +2660,14 @@ fi:
all_reports: "Kaikki raport"
general_tab: "Yleistä"
moderation_tab: "Valvonta"
+ security_tab: "Tietoturva"
+ reports_tab: "Raportit"
disabled: Pois käytöstä
timeout_error: Kyselyssä kestää liian kauan. Valitse lyhyempi ajanjakso.
exception_error: "Pahoittelut, kyselyä tehtäessä tapahtui virhe"
too_many_requests: Olet tehnyt näin liian monta kertaa. Odota ennen kuin yrität uudelleen.
+ not_found_error: "Pahoittelut, raporttia ei ole olemassa"
+ filter_reports: Suodata raportteja
reports:
trend_title: "%{percent} muutos. Nyt %{current}, viime jaksossa %{prev}."
today: "Tänään"
@@ -3067,11 +3135,11 @@ fi:
text: "CSS"
title: "Lisää mukautettua CSS:ää, hyväksymme käyvät CSS- ja SCSS-tyylit"
header:
- text: "Ylätunniste"
- title: "Lisää HTML:ää, joka näytetään sivuston headerin yläpuolella"
+ text: "Yläpalkki"
+ title: "Lisää HTML:ää, joka näytetään sivuston yläpalkin yläpuolella"
after_header:
- text: "Headerin jälkeen"
- title: "Lisää HTML:ää, joka näytetään sivuston headerin jälkeen"
+ text: "Yläpalkin jälkeen"
+ title: "Lisää HTML:ää, joka näytetään sivuston yläpalkin jälkeen"
footer:
text: "Footer"
title: "Lisää HTML:ää, joka näytetään sivuston footerissa"
@@ -3115,11 +3183,11 @@ fi:
name: "neljäs väri"
description: "Navigaatiolinkit."
header_background:
- name: "headerin tausta"
- description: "Sivuston headerin taustaväri."
+ name: "yläpalkin tausta"
+ description: "Sivuston yläpalkin taustaväri."
header_primary:
- name: "headerin ensisijainen"
- description: "Headerin teksti ja ikonit."
+ name: "yläpalkin ensisijainen"
+ description: "Sivuston yläpalkin tekstit ja ikonit."
highlight:
name: 'korostus'
description: 'Korostettujen elementtien, kuten viestien ja ketjujen, taustaväri.'
@@ -3137,6 +3205,13 @@ fi:
settings: "Asetukset"
templates: "Viestipohjat"
preview_digest: "Esikatsele tiivistelmä"
+ advanced_test:
+ title: "Edistynyt testi"
+ desc: "Tarkastele kuinka Discourse prosessoi saapuvia sähköposteja. Jotta prosessointi tapahtuu oikein, liitä alle sähköpostiviesti kokonaisuudessaan."
+ email: "Alkuperäinen viesti"
+ run: "Aja testi"
+ text: "Valittu leipäteksti"
+ elided: "Karsittu teksti"
sending_test: "Lähetetään testisähköpostia..."
error: "VIRHE - %{server_error}"
test_error: "Testisähköpostin lähettäminen ei onnistunut. Tarkista uudelleen sähköpostiasetukset, varmista, että palveluntarjoajasi ei estä sähköpostiyhteyksiä ja kokeile sitten uudestaan."
@@ -3203,6 +3278,7 @@ fi:
silence_user: "Käyttäjä hiljennettiin"
delete_post: "Viesti poistettiin"
delete_topic: "Ketju poistettiin"
+ post_approved: "Viesti hyväksytty"
logs:
title: "Lokit"
action: "Toiminto"
@@ -3271,6 +3347,7 @@ fi:
revoke_moderation: "peru valvojan oikeudet"
backup_create: "luo varmuuskopio"
deleted_tag: "poistettu tunniste"
+ deleted_unused_tags: "poista käyttämättömät tunnisteet"
renamed_tag: "uudelleen nimetty tunniste"
revoke_email: "peru sähköpostiosoite"
lock_trust_level: "lukitse luottamustaso"
@@ -3294,6 +3371,8 @@ fi:
change_badge: "muuta ansiomerkkiä"
delete_badge: "poista ansiomerkki"
merge_user: "yhdistä käyttäjät"
+ entity_export: "vie tietokokonaisuus"
+ change_name: "muuta nimeä"
screened_emails:
title: "Seulottavat sähköpostiosoitteet"
description: "Uuden käyttäjätunnuksen luonnin yhteydessä annettua sähköpostiosoitetta verrataan alla olevaan listaan ja tarvittaessa tunnuksen luonti joko estetään tai suoritetaan muita toimenpiteitä."
@@ -3328,15 +3407,13 @@ fi:
title: "Hakuloki"
term: "Hakusana"
searches: "Haut"
- click_through: "Klikkaussuhde"
- unique: "Uniikki"
- unique_title: "uniikit käyttäjät, jotka hakivat"
+ click_through_rate: "KS"
types:
all_search_types: "Kaikki hakutyypit"
- header: "Otsikko"
+ header: "Yläpalkki"
full_page: "Koko sivu"
click_through_only: "Kaikki (vain klikkaussuhde)"
- header_search_results: "Otsikkohaun tulokset"
+ header_search_results: "Yläpalkkihaun tulokset"
logster:
title: "Virhelokit"
watched_words:
@@ -3443,6 +3520,8 @@ fi:
suspended_until: "(%{until} asti)"
cant_suspend: "Käyttäjää ei voi hyllyttää."
delete_all_posts: "Poista kaikki viestit"
+ delete_posts_progress: "Poistetaan viestejä..."
+ delete_posts_failed: "Viestien poistaminen epäonnistui."
penalty_post_actions: "Mitä haluat tehdä kyseiselle viestille?"
penalty_post_delete: "Poista viesti"
penalty_post_edit: "Muokkaa viestiä"
@@ -3677,6 +3756,9 @@ fi:
tags: "Tunnisteet"
search: "Haku"
groups: "Ryhmät"
+ dashboard: "Hallintapaneeli"
+ secret_list:
+ invalid_input: "Syöttökenttä ei voi olla tyhjä tai sisältää putkimerkkiä."
badges:
title: Ansiomerkit
new_badge: Uusi ansiomerkki
@@ -3799,6 +3881,7 @@ fi:
wizard_js:
wizard:
done: "Valmis"
+ finish: "Valmis"
back: "Edellinen"
next: "Seuraava"
step: "%{current}/%{total}"
diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml
index 6339584a07..ddbca91a6d 100644
--- a/config/locales/client.fr.yml
+++ b/config/locales/client.fr.yml
@@ -291,7 +291,6 @@ fr:
choose_topic:
none_found: "Aucun sujet trouvé."
title:
- search: "Rechercher un sujet par son nom, URL ou ID :"
placeholder: "entrez ici le titre du sujet"
queue:
topic: "Sujet :"
@@ -467,7 +466,6 @@ fr:
description: "Vous serez notifié de chaque nouvelle réponse dans chaque message, et le nombre de nouvelles réponses sera affiché."
watching_first_post:
title: "Surveiller les nouveaux sujets"
- description: "Vous serez uniquement notifié du premier message de chaque sujet de ce groupe."
tracking:
title: "Suivre"
description: "Vous serez notifié si quelqu'un mentionne votre @pseudo ou vous répond, et le nombre de nouvelles réponses sera affiché."
@@ -1819,7 +1817,6 @@ fr:
split_topic:
title: "Déplacer vers un nouveau sujet"
action: "déplacer vers un nouveau sujet"
- topic_name: "Titre du nouveau sujet"
error: "Il y a eu une erreur en déplaçant les messages vers un nouveau sujet."
instructions:
one: "Vous êtes sur le point de créer un nouveau sujet avec le message que vous avez sélectionné."
@@ -2159,7 +2156,6 @@ fr:
description: "Vous surveillerez automatiquement tous les sujets dans ces catégories. Vous serez notifié des nouveaux messages dans tous les sujets, et le nombre de nouvelles réponses sera affiché."
watching_first_post:
title: "Surveiller les nouveaux sujets"
- description: "Vous serez uniquement notifié du premier message de chaque sujet dans ces catégories."
tracking:
title: "Suivi"
description: "Vous allez suivre automatiquement tous les sujets dans ces catégories. Vous serez notifié lorsque quelqu'un mentionne votre @pseudo ou vous répond, et le nombre de nouvelles réponses sera affiché."
@@ -2527,7 +2523,6 @@ fr:
description: "Vous surveillerez automatiquement tous les sujets avec ce tag. Vous serez notifié de tous les nouveaux messages et sujets, et le nombre de messages non lus et nouveaux apparaîtra à côté du sujet."
watching_first_post:
title: "Surveiller les nouveaux sujets"
- description: "Vous serez uniquement notifié du premier message de chaque sujet avec ce tag."
tracking:
title: "Suivi"
description: "Vous allez suivre automatiquement tous les sujets avec ce tag. Le nombre de messages non lus et nouveaux apparaîtra à côté du sujet."
@@ -2615,9 +2610,6 @@ fr:
private_messages_short: "Msgs"
private_messages_title: "Messages"
mobile_title: "Mobile"
- space_free: "{{size}} libre"
- uploads: "fichiers envoyés"
- backups: "sauvegardes"
lastest_backup: "Plus récent: %{date}"
traffic_short: "Trafic"
traffic: "Requêtes Web Application"
@@ -2976,7 +2968,6 @@ fr:
title: "Restaurer (RollBack) la base de données à l'état de travail précédent"
confirm: "Êtes-vous sûr de vouloir restaurer la base de données à l'état de fonctionnement précédent?"
location:
- local: "Locale"
s3: "Amazon S3"
export_csv:
success: "L'exportation a été démarrée. Vous serez notifié par message lorsque le traitement sera terminé."
@@ -3370,9 +3361,6 @@ fr:
title: "Journaux de recherche"
term: "Terme"
searches: "Recherches"
- click_through: "Taux de clics"
- unique: "Unique"
- unique_title: "utilisateurs uniques ayant recherché"
types:
all_search_types: "Tous les types de recherche"
header: "Entête"
diff --git a/config/locales/client.gl.yml b/config/locales/client.gl.yml
index 60fb04f7eb..43f11e81fc 100644
--- a/config/locales/client.gl.yml
+++ b/config/locales/client.gl.yml
@@ -246,7 +246,6 @@ gl:
choose_topic:
none_found: "Non se atoparon temas."
title:
- search: "Buscar un tema polo nome, URL ou ID:"
placeholder: "escribe o título do tema aquí"
queue:
topic: "Tema:"
@@ -1093,7 +1092,6 @@ gl:
split_topic:
title: "Mover ao tema novo"
action: "mover ao tema novo"
- topic_name: "Nome do tema novo"
error: "Produciuse un erro movendo as publicacións ao novo tema."
instructions:
one: "Vas crear un novo tema e enchelo coa publicación que seleccionaches."
@@ -1523,9 +1521,6 @@ gl:
private_messages_short: "Mensaxes"
private_messages_title: "Mensaxes"
mobile_title: "Móbil"
- space_free: "{{size}} libres"
- uploads: "subidas"
- backups: "copias de seguranza"
traffic_short: "Tráfico"
traffic: "Peticións web de aplicativos"
show_traffic_report: "Amosar o informe detallado do tráfico"
diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml
index d32cc2eb89..4b0f915e39 100644
--- a/config/locales/client.he.yml
+++ b/config/locales/client.he.yml
@@ -292,7 +292,6 @@ he:
choose_topic:
none_found: "לא נמצאו נושאים."
title:
- search: "חיפוש נושא לפי שם, כתובת או מזהה:"
placeholder: "הקלידו את כותרת הנושא כאן"
queue:
topic: "נושא:"
@@ -401,7 +400,6 @@ he:
description: "תקבלו התראה על כל פוסט חדש במסגרת כל הודעה, וסך התשובות יוצג."
watching_first_post:
title: "צפייה בפוסט הראשון"
- description: "תקבלו התראה על הפוסט הראשון בכל נושא חדש בקבוצה זו."
tracking:
title: "במעקב"
description: "תקבלו התראה אם מישהו מזכיר את @שמכם או עונה לכם, ותופיע ספירה של תגובות חדשות."
@@ -1517,7 +1515,6 @@ he:
split_topic:
title: "העבר לנושא חדש"
action: "העבר לנושא חדש"
- topic_name: "שם הנושא החדש"
error: "הייתה שגיאה בהעברת הפוסטים לנושא החדש."
instructions:
one: "אתם עומדים ליצור נושא חדש ולמלא אותו עם הפוסטים שבחרתם."
@@ -1848,7 +1845,6 @@ he:
description: "תצפו באופן אוטומטי בכל הנושאים שבקטגוריות אלו. תקבלו התראה על כל פוסט חדש בכל אחד מהנושאים בקטגוריה ואת מספר התגובות לכל אחד מהם."
watching_first_post:
title: "צפייה בפוסט הראשון"
- description: "תקבלו התראה על הפוסט הראשון בכל אחד מהנושאים בקטגוריות אלו."
tracking:
title: "עוקב"
description: "אתם תעקבו אוטומטית אחרי כל הנושאים בקטגוריות אלו. אתם תיודעו אם מישהו מזכיר את @שמכם או עונה לכם, וספירה של תגובות חדשות תופיע לכם."
@@ -2287,9 +2283,6 @@ he:
private_messages_short: "הודעות"
private_messages_title: "הודעות"
mobile_title: "נייד"
- space_free: "{{size}} חופשיים"
- uploads: "העלאות"
- backups: "גיבויים"
traffic_short: "תנועה"
traffic: "בקשות יישום web"
page_views: "צפיות-דף"
diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml
index f05b16ed85..527769264e 100644
--- a/config/locales/client.hu.yml
+++ b/config/locales/client.hu.yml
@@ -287,7 +287,6 @@ hu:
choose_topic:
none_found: "Nem találhatók témák."
title:
- search: "Témakör keresése név, URL-cím vagy azonosító alapján:"
placeholder: "ide írd be a témakör címét"
queue:
topic: "Téma:"
@@ -463,7 +462,6 @@ hu:
description: "Értesítést fogsz kapni minden üzenetben lévő új bejegyzésről és megjelenik az olvasatlan válaszok száma is."
watching_first_post:
title: "Első hozzászólások figyelése"
- description: "Csak minden új téma első hozzászólásáról kapsz értesítést ebben a csoportban."
tracking:
title: "Követés"
description: "Csak akkor leszel értesítve, ha valaki megemlíti a @nevedet vagy válaszol neked, és megjelenik az olvasatlan válaszok száma is."
@@ -1605,7 +1603,6 @@ hu:
split_topic:
title: "Áthelyezés új témába"
action: "áthelyezés új témába"
- topic_name: "Új témakör címe"
error: "Hiba lépett fel a bejegyzés új témakörbe való helyezése során!"
merge_topic:
title: "Áthelyezés létező témába"
@@ -1802,7 +1799,6 @@ hu:
description: "Automatikusan figyelni fogsz minden témakört ezekben a kategóriákban. Értesítést kapsz az összes témakör minden új hozzászólásáról és látni fogod az új hozzászólások számát."
watching_first_post:
title: "Első hozzászólás figyelése"
- description: "Csak minden új témakör legelső hozzászólásáról fogsz értesítést kapni ezekben a kategóriákban."
tracking:
title: "Követés"
description: "Automatikusan követni fogsz minden témakört ezekben a kategóriákban. Látni fogod az új hozzászólások számát és értesítve leszel ha valaki a megemlíti a @nevedet vagy válaszol neked."
@@ -2159,9 +2155,6 @@ hu:
private_messages_short: "Üzenetek"
private_messages_title: "Üzenetek"
mobile_title: "Mobiltelefon"
- space_free: "{{size}} szabad"
- uploads: "Feltöltések"
- backups: "Biztonsági mentések"
lastest_backup: "Legutóbbi: %{date}"
traffic_short: "Forgalom"
page_views: "Oldal látogatások"
@@ -2712,7 +2705,6 @@ hu:
search_logs:
term: "Kifejezés"
searches: "Keresések"
- unique: "Egyedi"
types:
header: "Fejléc"
full_page: "Teljes oldal"
diff --git a/config/locales/client.id.yml b/config/locales/client.id.yml
index 9c7af1a400..3dea5240d1 100644
--- a/config/locales/client.id.yml
+++ b/config/locales/client.id.yml
@@ -235,7 +235,6 @@ id:
choose_topic:
none_found: "Tidak ada topik ditemukan."
title:
- search: "Cari Topik berdasarkan nama, url atau id:"
placeholder: "tulis judul topik disini"
queue:
topic: "Topik:"
@@ -889,7 +888,6 @@ id:
split_topic:
title: "Pindahkan sebagai Topik Baru"
action: "pindahkan sebagai topik baru"
- topic_name: "Nama Topik Baru"
error: "Ada kesalahan dalam memindahkan posting sebagai topik baru"
change_owner:
placeholder: "username pemilik yang baru"
diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml
index 9e4ac2350e..f9bd160c6a 100644
--- a/config/locales/client.it.yml
+++ b/config/locales/client.it.yml
@@ -282,7 +282,6 @@ it:
choose_topic:
none_found: "Nessun argomento trovato."
title:
- search: "Cerca un Argomento per nome, url o id:"
placeholder: "digita il titolo dell'argomento"
queue:
topic: "Argomento:"
@@ -457,7 +456,6 @@ it:
description: "Verrai avvertito per ogni nuovo invio in qualsiasi messaggio, e verrà mostrato il conteggio delle nuove risposte."
watching_first_post:
title: "Osservando Primo Messaggio"
- description: "Sarai avvertito soltanto per il primo messaggio in ogni nuovo argomento in questo gruppo."
tracking:
title: "Seguendo"
description: "Verrai avvertito se qualcuno menziona il tuo @nome o ti risponde, e verrà mostrato un conteggio delle nuove risposte."
@@ -1769,7 +1767,6 @@ it:
split_topic:
title: "Sposta in un nuovo argomento"
action: "sposta in un nuovo argomento"
- topic_name: "Nome Nuovo Argomento"
error: "Si è verificato un errore spostando il messaggio nel nuovo argomento."
instructions:
one: "Stai per creare un nuovo argomento riempiendolo con il messaggio che hai selezionato."
@@ -2101,7 +2098,6 @@ it:
description: "Osserverai automaticamente tutti gli argomenti in queste categorie. Riceverai notifiche per ogni nuovo messaggio in ogni argomento, e apparirà il conteggio delle nuove risposte."
watching_first_post:
title: "Osservando il Primo Messaggio"
- description: "Riceverai una notifica soltanto per il primo messaggio di ogni nuovo argomento in queste categorie."
tracking:
title: "Seguendo"
description: "Seguirai automaticamente tutti gli argomenti in tali categorie. Ti verrà inviata una notifica se qualcuno menziona il tuo @nome o ti risponde, e apparirà un conteggio delle nuove risposte."
@@ -2342,7 +2338,7 @@ it:
dismiss_topics: 'x, t Marca gli Argomenti come letti'
log_out: 'shift+zshift+z Disconnetti'
composing:
- title: 'Scrivendo'
+ title: 'Scrittura'
actions:
title: 'Azioni'
bookmark_topic: 'f Aggiungi/togli argomento nei segnalibri'
@@ -2516,9 +2512,6 @@ it:
private_messages_short: "MP"
private_messages_title: "Messaggi"
mobile_title: "Mobile"
- space_free: "{{size}} liberi"
- uploads: "caricamenti"
- backups: "backup"
lastest_backup: "Recenti: %{date}"
traffic_short: "Traffico"
traffic: "Richieste web dell'applicazione"
@@ -3188,9 +3181,6 @@ it:
title: "Log Ricerca"
term: "Termine"
searches: "Ricerche"
- click_through: "Cliccate"
- unique: "Unico"
- unique_title: "utenti unici che stanno effettuando la ricerca"
types:
all_search_types: "Tutti i tipi di ricerca"
logster:
diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml
index 06f9ffa75d..e2b474fa86 100644
--- a/config/locales/client.ko.yml
+++ b/config/locales/client.ko.yml
@@ -262,7 +262,6 @@ ko:
choose_topic:
none_found: "토픽을 찾을 수 없습니다."
title:
- search: "이름, url, ID로 토픽 검색"
placeholder: "토픽 제목을 입력하세요"
queue:
topic: "토픽:"
@@ -405,7 +404,6 @@ ko:
description: "이 메시지에 새로운 답글이 있을 때 알림을 받게 되며 새로운 답글의 개수는 표시됩니다."
watching_first_post:
title: "첫번째 글 보기"
- description: "이 그룹의 신규 토픽에 대해서만 알림을 받게 됩니다."
tracking:
title: "추적 중"
description: "누군가 당신의 @아이디 로 언급했거나 당신의 글에 답글이 달릴 때 알림을 받게 됩니다."
@@ -1591,7 +1589,6 @@ ko:
split_topic:
title: "새로운 주제로 이동"
action: "새로운 주제로 이동"
- topic_name: "새로운 주제 이름"
error: "새로운 주제로 이동시키는데 문제가 발생하였습니다."
instructions:
other: "새로운 주제를 생성하여, 선택한 {{count}}개의 글로 채우려고 합니다."
@@ -1868,7 +1865,6 @@ ko:
description: "이 카테고리의 모든 토픽을 주시하도록 자동 설정됩니다. 모든 토픽과 게시글에 대하여 알림을 받게 되며, 새로운 게시글의 수가 표시됩니다."
watching_first_post:
title: "첫번째 글 보기"
- description: "이 카테고리에 새로운 토픽이 생길때마다 알림을 받습니다."
tracking:
title: "추적 중"
description: "이 카테고리의 모든 토픽을 추적하도록 자동 설정됩니다. 다른 사용자가 당신의 @이름 을 언급하거나 당신의 게시글에 답글을 달 때 알림을 받게 되며 새로운 게시글의 수가 표시됩니다."
@@ -2270,9 +2266,6 @@ ko:
private_messages_short: "메시지"
private_messages_title: "메시지"
mobile_title: "모바일"
- space_free: "{{size}} free"
- uploads: "업로드"
- backups: "백업"
lastest_backup: "최근: %{date}"
traffic_short: "트래픽"
traffic: "어플리케이션 웹 요청"
diff --git a/config/locales/client.lt.yml b/config/locales/client.lt.yml
index 4f3cb8e62d..c6f36339dc 100644
--- a/config/locales/client.lt.yml
+++ b/config/locales/client.lt.yml
@@ -323,7 +323,6 @@ lt:
choose_topic:
none_found: "Nerasta temų."
title:
- search: "Ieškokite temų pagal pavadinimą, url arba id:"
placeholder: "Įveskite temos pavadinimą čia"
queue:
topic: "Tema"
@@ -494,7 +493,6 @@ lt:
description: "Būsi perspėtas dėl kiekvieno naujo įrašo kiekvienoje žinutėje."
watching_first_post:
title: "Stebime Pirmą Įrašą"
- description: "Būsite perspėjami apie naujus įrašus šioje grupėje."
tracking:
title: "Sekamos"
description: "Būsi perspėtas kai kažkas paminės tavo @vardą ar tau atsakys."
@@ -1650,7 +1648,6 @@ lt:
split_topic:
title: "Perkelti į naują temą"
action: "perkelti į naują temą"
- topic_name: "Naujos temos pavadinimas"
instructions:
one: "Jūs tuoj sukursite naują temą ir įkesite įrašą, kurį pažymėjote."
few: "Jūs tuoj sukursite naują temą ir įkesite {{count}} įrašus, kuriuos pažymėjote."
@@ -2307,9 +2304,6 @@ lt:
private_messages_short: "Msgs"
private_messages_title: "Žinutės"
mobile_title: "Mobili "
- space_free: "{{size}} laisva"
- uploads: "Įkėlimai"
- backups: "Atsarginės kopijos"
lastest_backup: "Naujausi: %{date}"
traffic_short: "Srautas"
traffic: "Application web requests"
@@ -2799,7 +2793,6 @@ lt:
title: "Sukurti naujus Atskirus Tinklus blokuotus sąrašus jeigu yra bent 'min_ban_entries_for_roll_up' įrašai."
search_logs:
title: "Paieškos žurnalas"
- unique: "Unikalus"
types:
full_page: "Pilnas puslapis"
logster:
diff --git a/config/locales/client.lv.yml b/config/locales/client.lv.yml
index 7c7b0767cb..857b02b887 100644
--- a/config/locales/client.lv.yml
+++ b/config/locales/client.lv.yml
@@ -272,7 +272,6 @@ lv:
choose_topic:
none_found: "Tēmas netika atrastas."
title:
- search: "Meklē tēmu pēc vārda, URL vai ID:"
placeholder: "ierakstiet tēmas nosaukumu šeit"
queue:
topic: "Tēma:"
@@ -378,7 +377,6 @@ lv:
description: "Jums paziņos par katru jaunu ierakstu katrā ziņā, kā arī parādīs jauno atbilžu skaitu."
watching_first_post:
title: "Seko pirmajam ierakstam"
- description: "Jums paziņos tikai par pirmo ierakstu katrā jaunajā tēmā šai grupā."
tracking:
title: "Sekošana"
description: "Jums paziņos, ja kāds pieminēs jūsu @vārdu vai atbildēs jums. Jūs redzēsiet jauno atbilžu skaitu."
@@ -1478,7 +1476,6 @@ lv:
split_topic:
title: "Pārvietot uz jaunu tēmu"
action: "pārvietot uz jaunu tēmu"
- topic_name: "Jaunās tēmas nosaukums"
error: "Pārvietojot ierakstus uz jauno tēmu, notika kļūda."
instructions:
zero: "Jūs tūlīt izveidosiet jaunu tēmu bez ierakstiem."
@@ -1774,7 +1771,6 @@ lv:
description: "Jūs automātiski novērosiet visas tēmas šajās sadaļās. Jums paziņos par katru jaunu ierakstu katrā tēmā, kā arī parādīs jauno atbilžu skaitu."
watching_first_post:
title: "Novērot pirmo ierakstu"
- description: "Jums paziņos tikai par pirmo ierakstu katrā šo sadaļu jaunajā tēmā."
tracking:
title: "Sekošana"
description: "Jūs automātiski sekosiet visām tēmām šajās sadaļās. Jums paziņos, ja kāds pieminēs jūsu @lietotājvārdu vai atbildēs jums, kā arī parādīs jauno atbilžu skaitu."
@@ -2187,9 +2183,6 @@ lv:
private_messages_short: "Ziņas"
private_messages_title: "Ziņās"
mobile_title: "Mobilais"
- space_free: "{{size}} par brīvu"
- uploads: "augšuplādes"
- backups: "rezerves kopijas"
traffic_short: "Datu plūsma"
page_views: "Lapu skatījumi"
page_views_short: "Lapu skatījumi"
diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml
index cfad360661..596bb9a4b4 100644
--- a/config/locales/client.nb_NO.yml
+++ b/config/locales/client.nb_NO.yml
@@ -283,7 +283,6 @@ nb_NO:
choose_topic:
none_found: "Ingen emner funnet."
title:
- search: "Søk etter et emne med navn, URL eller ID:"
placeholder: "skriv tittelen på emnet her"
queue:
topic: "Emne:"
@@ -457,7 +456,6 @@ nb_NO:
description: "Du vil bli varslet om hvert nye innlegg i hver beskjed, og antallet nye svar vil bli vist."
watching_first_post:
title: "Følger første innlegg"
- description: "Du vil bare bli varslet om det første innlegget i hvert nye emne i denne gruppen."
tracking:
title: "Overvåker"
description: "Du vil få beskjeddersom nevner @navnet ditt eller svarer deg, og antallet nye svar vil bli vist."
@@ -1752,7 +1750,6 @@ nb_NO:
split_topic:
title: "Flytt til nytt emne"
action: "flytt til nytt emne"
- topic_name: "Nytt navn på emne"
error: "Det oppstod en feil ved flytting av innlegg til det nye emnet."
instructions:
one: "Du er i ferd med å opprette et nytt emne basert på innlegget du har valgt.."
@@ -2086,7 +2083,6 @@ nb_NO:
description: "Du vil automatisk følge alle emnene i disse kategoriene. Du vil bli varslet om alle nye innlegg i hvert emne, og antall nye svar vil bli vist."
watching_first_post:
title: "Følger første innlegg"
- description: "Du vil bare bli varslet om det første innlegget i hvert nye emne i disse kategoriene."
tracking:
title: "Overvåkning"
description: "Du vil automatisk overvåke alle emner i disse kategoriene. Du vil bli varslet dersom noen nevner @navnet ditt eller svarer deg, og antallet nye svar vil bli vist."
@@ -2426,7 +2422,6 @@ nb_NO:
description: "Du vil automatisk følge alle emnene med dette stikkordet. Du vil bli varslet om alle nye innlegg og emner; i tillegg vil antallet uleste og nye innlegg vises ved siden av emnet."
watching_first_post:
title: "Følger første innlegg"
- description: "Du vil bare bli varslet om det første innlegget i hvert nye emne med dette stikkordet."
tracking:
title: "Overvåkning"
description: "Du vil automatisk overvåke alle emner med dette stikkordet. Antallet uleste og nye innlegg vil vises ved siden av emnet."
@@ -2513,9 +2508,6 @@ nb_NO:
private_messages_short: "Meldinger"
private_messages_title: "Meldinger"
mobile_title: "Mobil"
- space_free: "{{size}} ledig"
- uploads: "opplastinger"
- backups: "sikkerhetskopier"
lastest_backup: "Siste: %{date}"
traffic_short: "Trafikk"
traffic: "Applikasjon webforespørsler"
@@ -3209,9 +3201,6 @@ nb_NO:
title: "Søkelogger"
term: "Term"
searches: "Søk"
- click_through: "Klikk gjennom"
- unique: "Unike"
- unique_title: "unike brukere som har utført søket"
types:
all_search_types: "Alle søketyper"
header: "Hode"
diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml
index 8d57e0ef73..4a15c7ffa2 100644
--- a/config/locales/client.nl.yml
+++ b/config/locales/client.nl.yml
@@ -280,7 +280,6 @@ nl:
choose_topic:
none_found: "Geen topics gevonden."
title:
- search: "Zoeken naar een topic op naam, url of id:"
placeholder: "typ hier de titel van het topic"
queue:
topic: "Topic:"
@@ -439,7 +438,6 @@ nl:
description: "U ontvangt een melding bij elk nieuw bericht, en het aantal nieuwe antwoorden wordt weergeven."
watching_first_post:
title: "Eerste bericht in de gaten houden"
- description: "U ontvangt alleen een melding van het eerste bericht in elk nieuw topic in deze groep."
tracking:
title: "Volgen"
description: "U ontvangt een melding wanneer iemand uw @naam noemt of een bericht van u beantwoordt, en het aantal nieuwe antwoorden wordt weergeven."
@@ -1564,7 +1562,6 @@ nl:
split_topic:
title: "Verplaatsen naar nieuw topic"
action: "verplaatsen naar nieuw topic"
- topic_name: "Naam van nieuw topic"
error: "Er is een fout opgetreden bij het verplaatsen van berichten naar het nieuwe topic."
instructions:
one: "U gaat een nieuw topic aanmaken en met het geselecteerde bericht vullen."
@@ -1849,7 +1846,6 @@ nl:
description: "U houdt automatisch alle topics in deze categorieën in de gaten. U ontvangt meldingen voor elk nieuw bericht in elk topic, en het aantal nieuwe antwoorden verschijnt naast het topic."
watching_first_post:
title: "Eerste bericht in de gaten houden"
- description: "U ontvangt alleen een melding van het eerste bericht in elk nieuw topic in deze categorieën."
tracking:
title: "Volgen"
description: "U volgt automatisch alle topics in deze categorieën. U ontvangt een melding als iemand uw @naam noemt of een bericht van u beantwoordt, en het aantal nieuwe antwoorden wordt weergegeven."
@@ -2254,9 +2250,6 @@ nl:
private_messages_short: "PB's"
private_messages_title: "Berichten"
mobile_title: "Mobiel"
- space_free: "{{size}} beschikbaar"
- uploads: "uploads"
- backups: "back-ups"
traffic_short: "Verkeer"
traffic: "Applicatiewebverzoeken"
page_views: "Paginaweergaven"
diff --git a/config/locales/client.pl_PL.yml b/config/locales/client.pl_PL.yml
index b6bc50ac74..308bbfa040 100644
--- a/config/locales/client.pl_PL.yml
+++ b/config/locales/client.pl_PL.yml
@@ -220,6 +220,7 @@ pl_PL:
submit: "Prześlij"
generic_error: "Przepraszamy, wystąpił błąd."
generic_error_with_reason: "Wystąpił błąd: %{error}"
+ go_ahead: "Idź dalej"
sign_up: "Rejestracja"
log_in: "Logowanie"
age: "Wiek"
@@ -240,6 +241,7 @@ pl_PL:
privacy: "Prywatność"
tos: "Warunki użytkowania serwisu"
rules: "Zasady"
+ conduct: "Regulamin"
mobile_view: "Wersja mobilna"
desktop_view: "Wersja komputerowa"
you: "Ty"
@@ -301,6 +303,9 @@ pl_PL:
new_topic: "Nowy szkic tematu "
new_private_message: "Nowy szkic wiadomości prywatnej"
topic_reply: "Szkic odpowiedzi"
+ abandon:
+ yes_value: "Tak, porzuć"
+ no_value: "Nie, zatrzymaj"
topic_count_latest:
one: "Zobacz {{count}} nowy albo zaktualizowany temat"
few: "Zobacz {{count}} nowe albo zaktualizowane tematy"
@@ -341,7 +346,6 @@ pl_PL:
choose_topic:
none_found: "Nie znaleziono tematów."
title:
- search: "Szukaj tematu po nazwie, URL-u albo ID:"
placeholder: "tutaj wpisz tytuł tematu"
queue:
topic: "Temat:"
@@ -525,7 +529,6 @@ pl_PL:
description: "Dostaniesz powiadomienie o każdym nowym wpisie w każdej dyskusji, zobaczysz również ilość odpowiedzi."
watching_first_post:
title: "Oglądasz pierwszy post"
- description: "Zostaniesz powiadomiony tylko o pierwszym wpisie w każdym nowym temacie w tej grupie."
tracking:
title: "Śledzenie"
description: "Dostaniesz powiadomienie, gdy ktoś ci odpowie lub wspomni twoją @nazwę, zobaczysz również liczbę odpowiedzi."
@@ -683,7 +686,7 @@ pl_PL:
delete_account: "Usuń moje konto"
delete_account_confirm: "Czy na pewno chcesz usunąć swoje konto? To nieodwracalne!"
deleted_yourself: "Twoje konto zostało usunięte."
- delete_yourself_not_allowed: "Skontaktuj się z członkiem ekipy jeśli chcesz, aby twoje konto zostało usunięte."
+ delete_yourself_not_allowed: "Skontaktuj się z członkiem zespołu jeśli chcesz, aby twoje konto zostało usunięte."
unread_message_count: "Wiadomości"
admin_delete: "Usuń"
users: "Użytkownicy"
@@ -740,6 +743,7 @@ pl_PL:
disable: "Wyłącz"
enable: "Włącz"
copied_to_clipboard: "Skopiowane do schowka"
+ copy_to_clipboard_error: "Wystąpił błąd w trakcie kopiowania do schowka"
second_factor:
title: "Dwuskładnikowe uwierzytelnianie"
disable: "Wyłącz dwuskładnikowe uwierzytelnianie"
@@ -752,6 +756,7 @@ pl_PL:
error: "Wystąpił błąd podczas zmiany tej wartości."
change_username:
title: "Zmień nazwę użytkownika"
+ confirm: "Czy jesteś absolutnie pewien że chcesz zmienić swoją nazwę użytkownika?"
taken: "Przykro nam, ale ta nazwa jest zajęta."
invalid: "Ta nazwa jest niepoprawna. Powinna zawierać jedynie liczby i litery."
change_email:
@@ -793,8 +798,10 @@ pl_PL:
many: "Otrzymasz e-mail tylko jeśli nie widzieliśmy Cię w ciągu ostatnich {{count}} minut."
other: "Otrzymasz e-mail tylko jeśli nie widzieliśmy Cię w ciągu ostatnich {{count}} minut."
associated_accounts:
+ title: "Powiązane konta"
connect: "Połącz"
revoke: "Unieważnij"
+ not_connected: "(nie połączony)"
name:
title: "Pełna nazwa"
instructions: "twoja pełna nazwa (opcjonalnie)"
@@ -821,9 +828,16 @@ pl_PL:
title: "Powtórz hasło"
auth_tokens:
title: "Ostatnio używane urządzenia"
+ ip: "IP"
+ details: "Detale"
log_out_all: "Wyloguj się wszędzie"
active: "teraz aktywna"
not_you: "To nie ty?"
+ show_all: "Pokaż wszystko ({{count}})"
+ show_few: "Pokaż mniej"
+ was_this_you: "To byłes Ty?"
+ secure_account: "Zabezpiecz moje konto"
+ latest_post: "Twój ostatni wpis ..."
last_posted: "Ostatni wpis"
last_emailed: "Ostatnio otrzymał email"
last_seen: "Ostatnio widziano"
@@ -1085,6 +1099,8 @@ pl_PL:
disable: "Pokaż usunięte wpisy."
private_message_info:
title: "Wiadomość"
+ invite: "Zaproś innych"
+ edit: "Dodaj lub usuń"
remove_allowed_user: "Czy naprawdę chcesz usunąć {{name}} z tej dyskusji?"
remove_allowed_group: "Czy naprawdę chcesz usunąć {{name}} z tej wiadomości?"
email: 'Email'
@@ -1118,6 +1134,7 @@ pl_PL:
password: "Hasło"
second_factor_title: "Dwuskładnikowe uwierzytelnianie"
second_factor_description: "Podaj kod uwierzytelniający ze swojej aplikacji:"
+ second_factor_backup_description: "Proszę wprowadź jeden ze swoich zapasowych kodów:"
email_placeholder: "adres email lub nazwa użytkownika"
caps_lock_warning: "Caps Lock jest włączony"
error: "Nieznany błąd"
@@ -1276,6 +1293,7 @@ pl_PL:
cancel: "Anuluj"
create_topic: "Utwórz temat"
create_pm: "Wiadomość"
+ create_whisper: "Szept"
title: "Lub naciśnij Ctrl+Enter"
users_placeholder: "Dodaj osobę"
title_placeholder: "O czym jest ta dyskusja w jednym zwartym zdaniu. "
@@ -1320,7 +1338,10 @@ pl_PL:
yourself_confirm:
title: "Nie zapomniałeś dodać odbiorców?"
body: "Aktualnie ta wiadomość będzie wysłana tylko do ciebie!"
- admin_options_title: "Opcjonalne ustawienia obsługi dla tego tematu"
+ admin_options_title: "Opcjonalne ustawienia zespołu dla tego tematu"
+ composer_actions:
+ reply: Odpowiedź
+ edit: Edycja
notifications:
tooltip:
regular:
@@ -1805,7 +1826,6 @@ pl_PL:
split_topic:
title: "Przenieś do nowego tematu"
action: "przenieś do nowego tematu"
- topic_name: "Nazwa Nowego Tematu"
error: "Wystąpił błąd podczas przenoszenia wpisów do nowego tematu."
instructions:
one: "Masz zamiar utworzyć nowy temat, składający się z wybranego przez ciebie wpisu."
@@ -2151,7 +2171,6 @@ pl_PL:
description: "Będziesz automatycznie śledzić wszystkie tematy w tych kategoriach. Będziesz otrzymywać powiadomienie o każdym nowym wpisie w każdym temacie, a liczba nowych wpisów będzie wyświetlana."
watching_first_post:
title: "Oglądasz pierwszy post"
- description: "Zostaniesz powiadomiony o pierwszym wpisie w każdym nowym temacie w tych kategoriach."
tracking:
title: "Śledzona"
description: "Będziesz automatycznie śledzić wszystkie nowe tematy w tych kategoriach. Dostaniesz powiadomienie, gdy ktoś ci odpowie lub wspomni twoją @nazwę. Zobaczysz również liczbę odpowiedzi."
@@ -2537,6 +2556,9 @@ pl_PL:
save: "Zapisz"
delete: "Usuń"
confirm_delete: "Czy na pewno chcesz usunąć ten tag grupy?"
+ everyone_can_use: "Tagi mogą być wykorzystywane przez wszystkich"
+ usable_only_by_staff: "Tagi są widoczne dla zespołu, ale jedynie obsługa serwisu może ich używać"
+ visible_only_to_staff: "Tagi są widoczne jedynie dla zespołu"
topics:
none:
unread: "Nie masz nieprzeczytanych tematów."
@@ -2592,9 +2614,6 @@ pl_PL:
private_messages_short: "Wiad."
private_messages_title: "Wiadomości"
mobile_title: "Mobile"
- space_free: "{{size}} wolne"
- uploads: "załączniki"
- backups: "kopie zapasowe"
traffic_short: "Ruch"
traffic: "Zapytania do aplikacji"
page_views: "Wyświetlenia strony"
@@ -2672,6 +2691,10 @@ pl_PL:
notify_user: "niestandardowe"
notify_moderators: "niestandardowe"
groups:
+ manage:
+ interaction:
+ visibility_levels:
+ staff: "Właściciele grupy i zespół"
primary: "Główna grupa"
no_primary: "(brak podstawowej grupy)"
title: "Grupy"
@@ -3080,6 +3103,7 @@ pl_PL:
filter: "Filtruj:"
title: "Działania obsługi"
clear_filters: "Pokaż wszystko"
+ staff_user: "Użytkownik"
target_user: "Użytkownik będący Obiektem"
subject: "Temat"
when: "Kiedy"
@@ -3131,6 +3155,7 @@ pl_PL:
backup_destroy: "Zniszcz kopię zapasową "
reviewed_post: "przejrzane posty"
custom_staff: "spersonalizowana akcja wtyczki"
+ change_name: "zmień nazwe"
screened_emails:
title: "Ekranowane emaile"
description: "Kiedy ktoś próbuje założyć nowe konto, jego adres email zostaje sprawdzony i rejestracja zostaje zablokowana, lub inna akcja jest podejmowana."
@@ -3165,8 +3190,6 @@ pl_PL:
title: "Logi wyszukiwań"
term: "Szukana fraza"
searches: "Wyszukiwania"
- click_through: "Kliknij przez"
- unique: "Unikalny"
types:
all_search_types: "Wszystkie typy wyszukiwania"
header: "Nagłówek"
@@ -3621,11 +3644,13 @@ pl_PL:
wizard_js:
wizard:
done: "Zrobione"
+ finish: "Zakończ"
back: "Poprzednia"
next: "Następna"
step: "%{current} z %{total}"
upload: "Prześlij"
uploading: "Przesyłanie…"
+ upload_error: "Przepraszamy, wystąpił błąd podczas wysyłania tego pliku. Proszę spróbuj ponownie."
quit: "Może później"
staff_count:
one: "W twojej społeczności jest 1 członek załogi (Ty)."
diff --git a/config/locales/client.pt.yml b/config/locales/client.pt.yml
index 389611e57b..28669077bd 100644
--- a/config/locales/client.pt.yml
+++ b/config/locales/client.pt.yml
@@ -165,8 +165,10 @@ pt:
eu_central_1: "U.E. (Francoforte)"
eu_west_1: "U.E. (Irlanda)"
eu_west_2: "UE (Londres)"
+ eu_west_3: "UE (Paris)"
sa_east_1: "América do Sul (São Paulo)"
us_east_1: "Este dos E.U.A. (Virgínia do Norte)"
+ us_east_2: "Este dos E.U.A. (Ohio)"
us_gov_west_1: "AWS GovCloud (E.U.A.)"
us_west_1: "Oeste dos E.U.A. (California do Norte)"
us_west_2: "Oeste dos E.U.A. (Óregon)"
@@ -174,6 +176,7 @@ pt:
not_implemented: "Essa funcionalidade ainda não foi implementada, pedimos desculpa!"
no_value: "Não"
yes_value: "Sim"
+ submit: "Submeter"
generic_error: "Pedimos desculpa, ocorreu um erro."
generic_error_with_reason: "Ocorreu um erro: %{error}"
sign_up: "Inscrever-se"
@@ -193,6 +196,8 @@ pt:
privacy_policy: "Política de Privacidade"
privacy: "Privacidade"
tos: "Termos de Serviço"
+ rules: "Regras"
+ conduct: "Código de Conduta"
mobile_view: "Visualização Mobile"
desktop_view: "Visualização Desktop"
you: "Você"
@@ -213,6 +218,8 @@ pt:
character_count:
one: "{{count}} caracter"
other: "{{count}} caracteres"
+ related_messages:
+ title: "Mensagens Relacionadas"
suggested_topics:
title: "Tópicos Sugeridos"
pm_title: "Mensagens Sugeridas"
@@ -224,6 +231,8 @@ pt:
our_moderators: "Os Nossos Moderadores"
stat:
all_time: "Sempre"
+ last_7_days: "Últimos 7 Dias"
+ last_30_days: "Últimos 30 Dias"
like_count: "Gostos"
topic_count: "Tópicos"
post_count: "Publicações"
@@ -239,7 +248,21 @@ pt:
unbookmark: "Clique para remover todos os marcadores deste tópico"
bookmarks:
created: "adicionou esta publicação aos marcadores"
+ not_bookmarked: "adicionar esta publicação aos marcadores"
remove: "Remover Marcador"
+ confirm_clear: "De certeza que pretende remover todos os marcadores deste tópico?"
+ drafts:
+ resume: "Continuar"
+ remove: "Remover"
+ new_topic: "Novo rascunho de tópico"
+ new_private_message: "Novo rascunho de mensagem privada"
+ topic_reply: "Rascunho da resposta"
+ abandon:
+ yes_value: "Sim, abandonar"
+ no_value: "Não, manter"
+ topic_count_latest:
+ one: "Ver {{count}} tópico novo ou atualizado"
+ other: "Ver {{count}} tópicos novos ou atualizados"
preview: "pré-visualizar"
cancel: "cancelar"
save: "Guardar alterações"
@@ -248,8 +271,10 @@ pt:
upload: "Carregar"
uploading: "A carregar…"
uploaded: "Carregado!"
+ pasting: "Colando..."
enable: "Ativar "
disable: "Desativar"
+ continue: "Continuar"
undo: "Desfazer"
revert: "Reverter"
failed: "Falhou"
@@ -261,7 +286,7 @@ pt:
choose_topic:
none_found: "Nenhum tópico encontrado."
title:
- search: "Procurar Tópico por nome, URL ou id:"
+ search: "Procurar Tópico por título, URL ou id:"
placeholder: "digite o título do tópico aqui"
queue:
topic: "Tópico:"
@@ -367,7 +392,6 @@ pt:
description: "Será notificado de cada nova publicação em cada mensagem, e uma contagem de novas respostas será exibida."
watching_first_post:
title: "A Vigiar a Primeira Publicação"
- description: "Será apenas notificado acerca da primeira publicação em cada tópico deste grupo."
tracking:
title: "A Seguir"
description: "Será notificado se alguém mencionar o seu @nome ou lhe responder, e uma contagem de novas respostas será exibida."
@@ -1482,7 +1506,6 @@ pt:
split_topic:
title: "Mover para um Novo Tópico"
action: "mover para um novo tópico"
- topic_name: "Nome do Novo Tópico"
error: "Ocorreu um erro ao mover as publicações para um novo tópico."
instructions:
one: "Está prestes a criar um novo tópico e populá-lo com a publicação que selecionou."
@@ -1734,7 +1757,6 @@ pt:
description: "Irá vigiar automaticamente todos os novos tópicos nestas categorias. Será notificado de todas as novas respostas em cada tópico, e uma contagem de novas respostas será mostrada."
watching_first_post:
title: "A Vigiar a Primeira Publicação"
- description: "Será notificado acerca da primeira resposta em cada novo tópico nestas categorias."
tracking:
title: "A Seguir"
description: "Irá seguir automaticamente todos os novos tópicos nestas categorias. Será notificado sempre que alguém menciona o seu @nome ou responde a si, e uma contagem de novas respostas será mostrada."
@@ -2137,9 +2159,6 @@ pt:
private_messages_short: "Msgs"
private_messages_title: "Mensagens"
mobile_title: "Móvel"
- space_free: "{{size}} livre"
- uploads: "carregamentos"
- backups: "fazer cópias de segurança"
traffic_short: "Tráfego"
traffic: "Pedidos de aplicação web"
show_traffic_report: "Mostrar Relatório Detalhado do Tráfego"
diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml
index 3715f5333d..df48ccfcda 100644
--- a/config/locales/client.pt_BR.yml
+++ b/config/locales/client.pt_BR.yml
@@ -284,7 +284,6 @@ pt_BR:
choose_topic:
none_found: "Nenhum tópico encontrado."
title:
- search: "Procurar por um Tópico pelo nome, url ou id:"
placeholder: "digite o título do tópico aqui"
queue:
topic: "Tópico:"
@@ -458,7 +457,6 @@ pt_BR:
description: "Você será notificado sobre toda nova postagem em toda mensagem, e uma contagem de novas mensagens será mostrada."
watching_first_post:
title: "Observando o primeiro post"
- description: "Você somente será notificado sobre a primeira postagem em cada novo tópico neste grupo."
tracking:
title: "Monitorando"
description: "Você será notificado se alguém mencionar seu @name ou responder a você, e uma contagem de novas respostas será mostrada."
@@ -1758,7 +1756,6 @@ pt_BR:
split_topic:
title: "Mover para novo tópico"
action: "mover para novo tópico"
- topic_name: "Nome do tópico novo"
error: "Houve um erro ao mover as mensagens para o novo tópico."
instructions:
one: "Você está prestes a criar um novo tópico e populá-lo com a resposta que você selecionou."
@@ -2096,7 +2093,6 @@ pt_BR:
description: "Você vai observar automaticamente todos os tópicos dessas categorias. Você será notificado de todas as novas mensagens em todos os tópicos. Além disso, a contagem de novas respostas também será exibida."
watching_first_post:
title: "Observando o primeiro post"
- description: "Você somente será notificado sobre a primeira postagem em cada novo tópico destas categorias."
tracking:
title: "Monitorar"
description: "Você vai monitorar automaticamente todos os tópicos dessas categorias. Você será notificado se alguém mencionar o seu @nome ou responder para você. Além disso, a contagem de novas respostas também será exibida."
@@ -2436,7 +2432,6 @@ pt_BR:
description: "Você assistirá automaticamente todos os tópicos com esta tag. Você será notificado sobre todas as novas postagens e tópicos, além da contagem de postagens não lidas e novas também serão exibidas ao lado do tópico."
watching_first_post:
title: "Observando o primeiro post"
- description: "Você só será notificado da primeira postagem em cada novo tópico com essa tag."
tracking:
title: "Monitorando"
description: "Você acompanhará automaticamente todos os tópicos com essa tag. Uma contagem de postagens não lidas e novas será exibida ao lado do tópico."
@@ -2523,9 +2518,6 @@ pt_BR:
private_messages_short: "Msgs"
private_messages_title: "Mensagens"
mobile_title: "Mobile"
- space_free: "{{size}} livre"
- uploads: "uploads"
- backups: "backups"
lastest_backup: "Mais recentes: %{date}"
traffic_short: "Tráfego"
traffic: "Solicitações do aplicativo pela web"
@@ -3219,9 +3211,6 @@ pt_BR:
title: "Logs de pesquisa"
term: "Termo"
searches: "Pesquisas"
- click_through: "Clique através"
- unique: "único"
- unique_title: "usuários únicos realizando a pesquisa"
types:
all_search_types: "Todos os tipos de pesquisa"
header: "Cabeçalho"
diff --git a/config/locales/client.ro.yml b/config/locales/client.ro.yml
index c13c2f6e31..455690297a 100644
--- a/config/locales/client.ro.yml
+++ b/config/locales/client.ro.yml
@@ -274,6 +274,9 @@ ro:
new_topic: "Ciornă subiect nou"
new_private_message: "Ciornă mesaj privat nou"
topic_reply: "Răspuns ciornă"
+ abandon:
+ yes_value: "Da, abandonează."
+ no_value: "Nu, păstrează."
topic_count_latest:
one: "Un subiect nou sau actualizat."
few: "{{count}} subiecte noi sau actualizate."
@@ -309,7 +312,6 @@ ro:
choose_topic:
none_found: "Nu au fost găsite subiecte noi."
title:
- search: "Căutați un subiect după nume, url sau id:"
placeholder: "Scrieți aici titlul subiectului"
queue:
topic: "Subiect:"
@@ -484,7 +486,6 @@ ro:
description: "Veți fi notificat pentru fiecare nouă postare în fiecare mesaj, și va fi afișat un contor al noilor răspunsuri."
watching_first_post:
title: "Urmărind activ prima postare"
- description: "Vei fi notificat doar pentru prima postare din fiecare nou subiect al acestui grup."
tracking:
title: "Urmărind"
description: "Vei fi notificat dacă cineva menționează @numele tău sau îți răspunde și va fi afișat un contor al noilor răspunsuri."
@@ -1038,6 +1039,7 @@ ro:
caps_lock_warning: "Tasta Caps Lock este activă"
error: "Eroare necunoscută"
rate_limit: "Te rog așteaptă înainte de a te reconecta."
+ blank_username: "Vă rugăm să introduceți adresa dvs. de email sau numele de utilizator."
blank_username_or_password: "Introdu emailul sau numele de utilizator și parola."
reset_password: 'Resetare parolă'
logging_in: "În curs de autentificare..."
@@ -1174,6 +1176,7 @@ ro:
topic_featured_link_placeholder: "Introdu link afișat cu titlu."
remove_featured_link: "Eliminați link-ul din discuție"
reply_placeholder: "Scrie aici. Utilizează formatarea Markdown, BBCode sau HTML. Trage sau lipește imagini."
+ reply_placeholder_choose_category: "Trebuie să selectați o categorie înainte de a scrie aici."
view_new_post: "Vezi noua ta postare."
saving: "Se salvează"
saved: "Salvat!"
@@ -1677,7 +1680,6 @@ ro:
split_topic:
title: "Mutare în subiect nou."
action: "mută în subiect nou"
- topic_name: "Numele subiectului nou"
error: "A apărut o eroare la mutarea postărilor în subiectul nou."
instructions:
one: "Vei crea o nouă discuţie care va fi populată cu postarea selectată."
@@ -2010,7 +2012,6 @@ ro:
description: "Vei urmări automat toate subiectele din aceste categorii. Vei fi notificat cu privire la fiecare nouă postare din fiecare subiect, și va fi afișat un contor al noilor răspunsuri."
watching_first_post:
title: "Urmărind activ prima postare"
- description: "Vei fi notificat doar cu privire la primele postări din fiecare nou subiect din aceste categorii."
tracking:
title: "Urmărire"
description: "Vei urmări automat toate subiectele din aceste categorii. Vei fi notificat dacă cineva îți menționează @numele sau îți răspunde, și va fi afișat un contor al noilor răspunsuri."
@@ -2360,7 +2361,6 @@ ro:
description: "Vei urmări activ în mod automat toate discuțiile cu această etichetă. Vei primi notificări cu privire la toate noile postări și discuții, și în plus, un contor al postărilor noi și al celor necitite va apărea în dreptul discuției."
watching_first_post:
title: "Urmărind activ prima postare"
- description: "Vei primi notificări doar cu privire la prima postare din fiecare discuție nouă cu această etichetă."
tracking:
title: "Urmărind"
description: "Vei urmări automat toate discuțiile cu această etichetă. Un contor cu postările necitite și cu cele noi va apărea lângă discuție."
@@ -2438,9 +2438,6 @@ ro:
private_messages_short: "Mesje"
private_messages_title: "Mesaje"
mobile_title: "Mobil"
- space_free: "{{size}} liber"
- uploads: "încărcări"
- backups: "back-up"
traffic_short: "Trafic"
traffic: "Cereri web"
page_views: "Vizualizări"
@@ -2991,7 +2988,6 @@ ro:
search_logs:
term: "Termen"
searches: "Căutări"
- unique: "Unic"
types:
all_search_types: "Toate tipurile de căutare"
header: "Antet"
diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml
index 7e02f6d36c..e9158a36ac 100644
--- a/config/locales/client.ru.yml
+++ b/config/locales/client.ru.yml
@@ -28,9 +28,9 @@ ru:
thousands: "{{number}} тыс."
millions: "{{number}} млн."
dates:
- time: "HH:mm"
- timeline_date: "MMM YYYY"
- long_no_year: "D MMM HH:mm"
+ time: "ЧЧ:мм"
+ timeline_date: "MMM ГГГГ"
+ long_no_year: "Д MMM ЧЧ:мм"
long_no_year_no_time: "D MMM"
full_no_year_no_time: "D MMM"
long_with_year: "D MMM YYYY, HH:mm"
@@ -219,6 +219,7 @@ ru:
submit: "Представлять"
generic_error: "Извините, произошла ошибка."
generic_error_with_reason: "Произошла ошибка: %{error}"
+ go_ahead: "Продолжить"
sign_up: "Зарегистрироваться"
log_in: "Войти"
age: "Возраст"
@@ -300,6 +301,9 @@ ru:
new_topic: "Новый черновик темы"
new_private_message: "Черновик для нового личного сообщения"
topic_reply: "Черновик ответа"
+ abandon:
+ confirm: "Вы уже открыли другой проект в этой теме. Уверены, что хотите бросить его?"
+ no_value: "Нет, пропустить"
topic_count_latest:
one: "Есть {{count}} новая или обновленная тема"
few: "Есть {{count}} новых или обновленных тем"
@@ -340,7 +344,6 @@ ru:
choose_topic:
none_found: "Не найдено ни одной темы."
title:
- search: "Искать тему по названию, ссылке или уникальному номеру:"
placeholder: "введите название темы здесь"
queue:
topic: "Тема:"
@@ -524,7 +527,6 @@ ru:
description: "Уведомлять по каждому ответу на это сообщение и показывать счётчик новых непрочитанных ответов."
watching_first_post:
title: "Просмотр Первого сообщения"
- description: "Уведомлять только о первом сообщении в каждой новой теме в этой группе."
tracking:
title: "Следить"
description: "Вы будете уведомлены если кто-то упомянет ваше @name или ответит вам. А так же вам будет показано общее количество новых ответов"
@@ -1827,7 +1829,6 @@ ru:
split_topic:
title: "Переместить в новую тему"
action: "переместить в новую тему"
- topic_name: "Название новой темы"
error: "Во время перемещения сообщений в новую тему возникла ошибка."
instructions:
one: "Сейчас вы создадите новую тему и в неё переместится выбранное вами {{count}} сообщение."
@@ -2179,7 +2180,6 @@ ru:
description: "Наблюдать за всеми темами этого раздела. Уведомлять о каждом новом сообщении в любой из тем и показывать счётчик новых ответов."
watching_first_post:
title: "Наблюдать за первым сообщением"
- description: "Вы будете уведомлены только о первом сообщении в каждой новой теме этого раздела."
tracking:
title: "Следить"
description: "Отслеживать все темы этого раздела. Уведомлять если кто-то упомянет ваше @name или ответит вам, показывать счётчик новых ответов."
@@ -2637,9 +2637,6 @@ ru:
private_messages_short: "Сообщ."
private_messages_title: "Сообщений"
mobile_title: "Мобильный"
- space_free: "свободно {{size}}"
- uploads: "Загрузки"
- backups: "Резервные копии"
traffic_short: "Трафик"
traffic: "Трафик (веб-запросы)"
page_views: "Просмотров Страниц"
@@ -3259,9 +3256,6 @@ ru:
title: "Логи поиска"
term: "Правило"
searches: "Поиски"
- click_through: "Пролистать"
- unique: "Уникальный"
- unique_title: "уникальные пользователи, выполняющие поиск"
types:
all_search_types: "Все типы поиска"
header: "Шапка"
diff --git a/config/locales/client.sk.yml b/config/locales/client.sk.yml
index 0d352e4162..69deaad8e7 100644
--- a/config/locales/client.sk.yml
+++ b/config/locales/client.sk.yml
@@ -338,7 +338,6 @@ sk:
choose_topic:
none_found: "Nenašli sa žiadne témy."
title:
- search: "Hľadaj tému podľa názvu, url alebo id:"
placeholder: "sem napíšte názov témy"
queue:
topic: "Téma:"
@@ -522,7 +521,6 @@ sk:
description: "Budete upozornený na každý nový príspevok vo všetkých správach a zobrazí sa počet nových odpovedí."
watching_first_post:
title: "Sledovať prvý príspevok"
- description: "Budete upozornený iba na prvý príspevok v každej téme v tejto skupine."
tracking:
title: "Pozorovať"
description: "Budete upozornený ak niekto zmieni Vaše @meno alebo Vám odpovie a zobrazí sa počet nových odpovedí."
@@ -1632,7 +1630,6 @@ sk:
split_topic:
title: "Presuň na novú tému"
action: "presuň na novú tému"
- topic_name: "Názov novej témy"
error: "Nastala chyba pri presune príspevku na novú tému."
instructions:
one: "Vytvárate novú tému do ktorej bude vložený príspevok, ktorý ste označili. "
@@ -2312,9 +2309,6 @@ sk:
private_messages_short: "Správy"
private_messages_title: "Správy"
mobile_title: "Mobil"
- space_free: "{{size}} voľné"
- uploads: "nahraté"
- backups: "zálohy"
traffic_short: "Vyťaženie"
traffic: "Požiadavky webových aplikácií"
show_traffic_report: "Zobraziť detaily vyťaženia"
diff --git a/config/locales/client.sl.yml b/config/locales/client.sl.yml
index 0f449fc6b1..057908c6e0 100644
--- a/config/locales/client.sl.yml
+++ b/config/locales/client.sl.yml
@@ -217,11 +217,11 @@ sl:
generic_error: "Ups, prišlo je do napake."
generic_error_with_reason: "Napaka: %{error}"
sign_up: "Prijava"
- log_in: "Vpis"
+ log_in: "Prijava"
age: "Starost"
joined: "Pridružen"
admin_title: "Administrator"
- flags_title: "Zastave"
+ flags_title: "Prijave"
show_more: "Več"
show_help: "možnosti"
links: "Povezave"
@@ -261,10 +261,10 @@ sl:
related_messages:
title: "Povezana sporočila"
suggested_topics:
- title: "Predlagane Teme"
- pm_title: "Predlagana Sporočila"
+ title: "Predlagane teme"
+ pm_title: "Predlagana sporočila"
about:
- simple_title: "O"
+ simple_title: "O nas"
title: "O %{title}"
stats: "Statistike Strani"
our_admins: "Naši Administratorji"
@@ -289,13 +289,13 @@ sl:
bookmarks:
created: "ustvarili ste zaznamek te objave"
not_bookmarked: "Zaznamuj objavo"
- remove: "Odstrani Zaznamek"
+ remove: "Odstrani zaznamek"
confirm_clear: "Ste prepričani, da želite počistiti vse svoje zaznamke iz te teme?"
drafts:
resume: "Nadaljuj"
remove: "Odstrani"
new_topic: "Nov osnutek teme"
- new_private_message: "Nov osnutek osebnega sporočila"
+ new_private_message: "Nov osnutek zasebnega sporočila"
topic_reply: "Osnutek odgovora"
abandon:
confirm: "V tej temi že imate drug osnutek. Ste prepričani, da ga želite opustiti?"
@@ -318,7 +318,7 @@ sl:
other: "Poglej {{count}} novih tem"
preview: "predogled"
cancel: "prekliči"
- save: "Shrani Spremembe"
+ save: "Shrani spremembe"
saving: "Shranjujem..."
saved: "Shranjeno!"
upload: "Naloži"
@@ -339,16 +339,15 @@ sl:
close: "Zavrni ta banner."
edit: "Uredi ta banner >>"
choose_topic:
- none_found: "Nobena tema najdena."
+ none_found: "Ni tem."
title:
- search: "Išči Temo po imenu, url ali id:"
placeholder: "vpiši naslov teme tukaj"
queue:
topic: "Tema:"
approve: 'Odobri'
reject: 'Zavrni'
delete_user: 'Izbriši uporabnika'
- title: "Potrebuje Odobritev"
+ title: "Potrebuje odobritev"
none: "Ni objav za pregled."
edit: "Uredi"
cancel: "Prekliči"
@@ -358,10 +357,10 @@ sl:
two: "Ta tema ima 2 objavi, ki čakata odobritev"
few: "Ta tema ima {{count}} objav, ki čakajo odobritev"
other: "Ta tema ima {{count}} objavo, ki čakajo odobritev"
- confirm: "Shrani Spremembe"
+ confirm: "Shrani spremembe"
delete_prompt: "Ali ste prepričani, da želite izbrisati %{username}? To bo odstranilo vse njihove objave ter blokiralo njihov e-poštni naslov in IP naslov."
approval:
- title: "Objava Potrebuje Odobritev."
+ title: "Objava potrebuje odobritev."
description: "Prejeli smo vašo novo objavo vendar potrebuje odobritev iz strani moderatorja preden bo objavljena. Prosimo za potrpežljivost."
pending_posts:
one: "Imate 1 čakajočo objavo."
@@ -389,10 +388,10 @@ sl:
likes_given: "Dano"
likes_received: "Prejeto"
topics_entered: "Ogledano"
- topics_entered_long: "Ogledane Teme"
+ topics_entered_long: "Ogledane teme"
time_read: "Prebrano"
topic_count: "Teme"
- topic_count_long: "Ustvarjene Teme"
+ topic_count_long: "Ustvarjene teme"
post_count: "Odgovori"
post_count_long: "Objavljeni Odgovori"
no_results: "Noben rezultat ni bil najden."
@@ -524,7 +523,6 @@ sl:
description: "Obveščeni boste o vsaki novi objavi v vsakem sporočilu in seštevek novih odgovorov bo prikazan."
watching_first_post:
title: "Opazuješ prvo objavo"
- description: "Obveščeni boste samo ob prvi objavi v vsaki novi temi v tej skupini."
tracking:
title: "Sledi"
description: "Obveščeni boste, če nekdo omeni vaše @ime ali vam odgovori. Prikazano bo tudi število novih odgovorov."
@@ -601,29 +599,36 @@ sl:
confirm: "Si prepričan/a, da želiš prenesti svoje objave?"
success: "Prenos se je začel, obveščen/a boš s sporočilom, ki bo prenos končan."
rate_limit_error: "Objave so lahko prevešene enkrat dnevno, poskusi ponovno jutri."
- new_private_message: "Novo Sporočilo"
- private_message: "Sporočilo"
- private_messages: "Sporočila"
+ new_private_message: "Novo zasebno sporočilo"
+ private_message: "Zasebno sporočilo"
+ private_messages: "Zasebna sporočila"
activity_stream: "Aktivnost"
preferences: "Nastavitve"
+ profile_hidden: "Profil tega uporabnika je skrit."
expand_profile: "Razširi"
+ collapse_profile: "Skrči"
bookmarks: "Zaznamki"
bio: "O meni"
- invited_by: "Povabljen od"
+ invited_by: "Povabljen/a od"
trust_level: "Nivo zaupanja"
notifications: "Obvestila"
statistics: "Statistika"
desktop_notifications:
- not_supported: "Oprosti. Obvestila niso podprta s tem brekalnikom."
- perm_default: "Vklopi Obvestila"
+ label: "Obvestila v brskalniku"
+ not_supported: "Oprosti. Obvestila niso podprta v tem brskalniku."
+ perm_default: "Vklopi obvestila"
perm_denied_btn: "Dovoljenje Zavrnjeno"
perm_denied_expl: "Zavrnili ste dovoljenje za obvestila. Omogočite obvestila v nastavitvah vašega brskalnika."
- disable: "Onemogoči Obvestila"
- enable: "Omogoči Obvestila"
+ disable: "Onemogoči obvestila"
+ enable: "Omogoči obvestila"
each_browser_note: "Opomba: To nastavitev morate spremeniti v vseh brskalnikih, ki jih uporabljate."
+ dismiss: 'Zavrži'
+ dismiss_notifications: "Zavrži vse"
dismiss_notifications_tooltip: "Označi vsa neprebrana sporočila kot Prebrana"
+ first_notification: "Vaše prvo obvestilo! Izberite ga za začetek."
disable_jump_reply: "Ne skoči na mojo objavo po tem ko odgovorim"
dynamic_favicon: "Pokaži števec novih /posodobljenih tem na ikoni brskalnika"
+ theme_default_on_all_devices: "Nastavi kot privzeto temo na vseh mojih napravah"
allow_private_messages: "Dovoli drugim uporabnikom da mi pošiljajo zasebna sporočila."
external_links_in_new_tab: "Odpri vse zunanje povezave v novem zavihku"
enable_quoting: "Omogoči odgovarjanje s citiranjem za poudarjen tekst"
@@ -639,11 +644,22 @@ sl:
github_profile: "Github"
email_activity_summary: "Povzetek aktivnosti"
mailing_list_mode:
+ label: "E-sporočilo za vsako objavo"
+ enabled: "Vklopi pošiljanje e-sporočila za vsako objavo"
instructions: |
Ta nastavitev spremeni povzetek aktivnosti.
Izključene teme in kategorije niso vključene v ta obvestila.
- individual: "Pošlji email za vsako novo objavo"
- individual_no_echo: "Pošlji email za vsako novo objavo, razen mojih"
+ individual: "Pošlji e-sporočilo za vsako novo objavo"
+ individual_no_echo: "Pošlji e-sporočilo za vsako novo objavo, razen mojih"
+ many_per_day: "Pošlji mi e-sporočilo za vsako novo objavo (okvirno {{dailyEmailEstimate}} na dan)"
+ few_per_day: "Pošlji mi e-sporočilo za vsako novo objavo (okvirno 2 na dan)"
+ warning: "Vkopljeno pošiljanje e-sporočila za vsako objavo. Povzetek aktivnosti izklopljen."
+ tag_settings: "Oznake"
+ watched_tags: "Opazovano"
+ watched_tags_instructions: "Avtomatsko boste spremljali vse teme v teh kategorijah. Obveščeni boste o vseh novih temah in objavah. Število novih objav bo prikazano ob temi."
+ tracked_tags: "Sledeno"
+ tracked_tags_instructions: "Avtomatsko boste sledili vse teme v teh kategorijah. Število novih objav bo prikazano ob temi."
+ muted_tags: "Utišano"
watched_categories: "Spremljano"
tracked_categories: "Sledena"
watched_first_post_tags: "Opazuješ prvo objavo"
@@ -652,6 +668,7 @@ sl:
delete_account: "Izbriši Moj Račun"
delete_account_confirm: "Si prepričan, da želiš trajno izbrisati svoj račun? Tega postopka ni mogoče razveljaviti!"
deleted_yourself: "Vaš račun je bil uspešno izbrisan."
+ delete_yourself_not_allowed: "Če želite izbrisati račun, prosim kontaktirajte osebje."
unread_message_count: "Sporočila"
admin_delete: "Izbriši"
users: "Uporabniki"
@@ -663,6 +680,8 @@ sl:
automatically_unpin_topics: "Samodejno odpni temo ko dosežem dno teme."
apps: "Aplikacije"
api_approved: "Odobreno"
+ api_last_used_at: "Zadnjič uporabljeno:"
+ theme: "Tema"
staff_counters:
flags_given: "oznake v pomoč"
flagged_posts: "označene objave"
@@ -677,22 +696,28 @@ sl:
bulk_select: "Izberi Sporočila"
move_to_inbox: "Premakni v Prejeto"
move_to_archive: "Arhiv"
+ select_all: "Izberi vse"
+ tags: "Oznake"
preferences_nav:
+ account: "Račun"
profile: "Profil"
- emails: "Emaili"
+ emails: "E-sporočila"
notifications: "Obvestila"
categories: "Kategorije"
tags: "Oznake"
interface: "Uporabniški vmesnik"
apps: "Aplikacije"
change_password:
- success: "(email poslan)"
- in_progress: "(pošiljanje emaila)"
+ success: "(e-sporočilo poslano)"
+ in_progress: "(pošiljanje e-sporočila)"
error: "(napaka)"
- action: "Pošlji sporočilo za ponastavitev gesla"
+ action: "Pošlji e-sporočilo za ponastavitev gesla"
set_password: "Nastavi geslo"
choose_new: "Izberite novo geslo"
choose: "Izberite geslo"
+ second_factor_backup:
+ disable: "Onemogoči"
+ enable: "Omogoči"
change_about:
title: "Spremeni O meni"
change_username:
@@ -700,9 +725,9 @@ sl:
taken: "Oprosti, to uporabniško ime je zasedeno."
invalid: "Uporabniško ime ni pravilno. Vsebuje lahko samo črke in številke. Preslednica in posebni znaki niso dovoljeni."
change_email:
- title: "Spremeni email"
- taken: "Ta email ni na voljo"
- error: "Prišlo je do napake pri menjavi emaila. Je ta email že v uporabi?"
+ title: "Spremeni e-naslov"
+ taken: "Ta e-naslov ni na voljo."
+ error: "Prišlo je do napake pri menjavi e-naslova. Je ta e-naslov že v uporabi?"
change_avatar:
title: "Menjaj profilno sliko"
gravatar_title: "Spremeni svoj avatar na Gravatar strani"
@@ -718,10 +743,16 @@ sl:
instructions: "Ozadje profila bo centrirano in imelo širino 850px."
change_card_background:
title: "Ozadje kartice uporabnika"
+ instructions: "Ozadje kartice bo centrirano in imelo širino 590px."
email:
- title: "Email"
+ title: "E-pošta"
instructions: "nikoli ne pokaži javno"
- invalid: "Prosim vnesite veljaven email naslov"
+ invalid: "Prosim vnesite veljaven e-naslov"
+ frequency:
+ one: "E-sporočilo bo poslano samo če niste bili aktivni v zadnji minuti."
+ two: "E-sporočilo bo poslano samo če niste bili aktivni v zadnji minuti."
+ few: "E-sporočilo bo poslano samo če niste bili aktivni v zadnji minuti."
+ other: "E-sporočilo bo poslano samo če niste bili aktivni vsaj {{count}} minut."
associated_accounts:
title: "Povezani računi"
connect: "Poveži"
@@ -740,7 +771,7 @@ sl:
too_short: "Vaše uporabniško ime je prekratko"
too_long: "Vaše uporabniško ime je predolgo"
checking: "Preverjam če je uporabniško ime prosto..."
- prefilled: "Email ustreza uporabniškemu imenu"
+ prefilled: "E-naslov ustreza uporabniškemu imenu"
locale:
title: "Jezik vmesnika"
instructions: "Jezik uporabniškega vmesnika. Zamenjal se bo, ko boste osvežili stran."
@@ -748,42 +779,54 @@ sl:
any: "katerokoli"
password_confirmation:
title: "Geslo - ponovno"
+ auth_tokens:
+ title: "Nedavno uporabljene naprave"
+ log_out_all: "Odjavi vse naprave"
+ show_all: "Prikaži vse ({{count}})"
+ show_few: "Prikaži manj"
last_posted: "Zadnja objava"
last_seen: "Viden/a"
created: "Pridružen/a"
- log_out: "Izpis"
+ log_out: "Odjava"
location: "Lokacija"
website: "Spletna stran"
- email_settings: "Email"
+ email_settings: "E-pošta"
+ hide_profile_and_presence: "Skrij moj javni profil and prisotnost"
like_notification_frequency:
title: "Obvesti o všečkih"
- always: "Zmeraj"
+ always: "Vedno"
first_time_and_daily: "Prvič ko je objava všečkana in dnevno"
first_time: "Prvič ko je objava všečkana"
never: "Nikoli"
email_previous_replies:
- title: "Vključi prejšnje odgovore na koncu emailov"
+ title: "Vključi prejšnje odgovore na koncu e-sporočila"
unless_emailed: "če ni že poslano"
- always: "zmeraj"
+ always: "vedno"
never: "nikoli"
email_digests:
+ title: "Če se ne oglasim na spletni strani, me pošljite e-sporočilo z povzetkom popularnih tem in objav."
every_30_minutes: "vsakih 30 minut"
every_hour: "vsako uro"
daily: "dnevno"
every_three_days: "vsake tri dni"
weekly: "tedensko"
every_two_weeks: "vsaka dva tedna"
- include_tl0_in_digests: "V preglednih emailih vključi vsebino, ki so jo dodali novi uporabniki "
- email_direct: "Pošlji mi email, kadar me kdo citira, odgovori na mojo objavo, omeni moje @uporabniško ime ali pa me povabi k temi"
- email_private_messages: "Pošlji mi email, kadar prejmem sporočilo"
- email_always: "Pošlji mi email obvestila tudi kadar sem aktivna/en na strani"
+ include_tl0_in_digests: "V e-sporočilo z povzetki vključi vsebino, ki so jo dodali novi uporabniki "
+ email_in_reply_to: "Vključi povzetek objave v e-sporočilo"
+ email_direct: "Pošlji mi e-sporočilo, kadar me kdo citira, odgovori na mojo objavo, omeni moje @uporabniško ime ali pa me povabi k temi"
+ email_private_messages: "Pošlji mi e-sporočilo, kadar prejmem zasebno sporočilo"
+ email_always: "Pošlji mi e-sporočilo tudi kadar sem aktivna/en na strani"
other_settings: "Ostalo"
categories_settings: "Kategorije"
new_topic_duration:
+ label: "Obravnavaj temo kot novo"
+ not_viewed: "je še neprebrana"
+ last_here: "ustvarjeno po mojem zadnjem obisku"
after_1_day: "ustvarjeno v zadnjem dnevu"
after_2_days: "ustvarjeno v zadnjih 2 dnevih"
after_1_week: "ustvarjeno v zadnjem tednu"
after_2_weeks: "ustvarjeno v zadnjih 2 tednih"
+ auto_track_topics: "Samodejno sledi temam, ki jih berem"
auto_track_options:
never: "nikoli"
immediately: "takoj"
@@ -794,6 +837,7 @@ sl:
after_4_minutes: "po 4 minutah"
after_5_minutes: "po 5 minutah"
after_10_minutes: "po 10 minutah"
+ notification_level_when_replying: "Ko objavim v določeni temi, nastavi temo na"
invited:
title: "Povabila"
user: "Povabljen uporabnik"
@@ -826,11 +870,31 @@ sl:
stats: "Statistika"
time_read: "čas branja"
recent_time_read: "nedaven čas branja"
+ topic_count:
+ one: "ustvarjena tema"
+ two: "ustvarjeni temi"
+ few: "ustvarjenih tem"
+ other: "ustvarjenih tem"
post_count:
one: "ustvarjena objava"
two: "ustvarjeni objavi"
few: "ustvarjenih objav"
other: "ustvarjenih objav"
+ likes_given:
+ one: "dan"
+ two: "dana"
+ few: "danih"
+ other: "danih"
+ likes_received:
+ one: "sprejet"
+ two: "sprejeta"
+ few: "sprejetih"
+ other: "sprejetih"
+ days_visited:
+ one: "dan obiska"
+ two: "dni obiska"
+ few: "dni obiskov"
+ other: "dni obiskov"
topics_entered:
one: "ogledana tema"
two: "ogledani temi"
@@ -841,16 +905,31 @@ sl:
two: "prebrani objavi"
few: "prebranih objav"
other: "prebranih objav"
+ bookmark_count:
+ one: "zaznamek"
+ two: "zaznamka"
+ few: "zaznamki"
+ other: "zaznamki"
top_replies: "Najboljši odgovori"
+ no_replies: "Ni odgovorov."
top_topics: "Najboljše teme"
no_topics: "Ni tem."
more_topics: "Več tem"
top_badges: "Najboljše značke"
+ no_badges: "Ni značk."
top_links: "Najboljše povezave"
+ no_links: "Ni povezav."
most_liked_by: "Največ všečkov od"
most_liked_users: "Največkrat všečkal/a"
+ no_likes: "Ni všečkov."
+ avatar:
+ title: "Profilna slika"
+ header_title: "profil, zasebna sporočila, zaznamki in nastavitve"
+ filters:
+ all: "Vse"
stream:
posted_by: "Objavil"
+ private_message: "zasebno sporočilo"
the_topic: "tema"
errors:
reasons:
@@ -863,6 +942,7 @@ sl:
back: "Nazaj"
close: "Zapri"
logout: "Bili ste odjavljeni."
+ refresh: "Osveži"
all_time_desc: 'skupaj ustvarjenih tem'
year: 'leto'
year_desc: 'teme, ustvarjene v preteklih 365 dneh'
@@ -885,28 +965,41 @@ sl:
disabled_description: "Izbrisane objave so prikazane."
enable: "Skrij izbrisane objave"
disable: "Prikaži izbrisane objave"
+ private_message_info:
+ edit: "Dodaj ali odstrani..."
username: 'Uporabniško ime'
- search_hint: 'uporabniško ime, email ali IP naslov'
+ trust_level: 'Nivo zaupanja'
+ search_hint: 'uporabniško ime, e-naslov ali IP naslov'
create_account:
- failed: "Nekaj je šlo narobe, morda je ta email naslov že registriran, poskusite povezavo za pozabljeno geslo"
+ failed: "Nekaj je šlo narobe, morda je ta e-naslov že registriran, poskusite povezavo za pozabljeno geslo"
forgot_password:
title: "Ponastavitev gesla"
action: "Pozabil sem geslo"
- invite: "Vpišite uporabniško ime ali email in poslali vam bomo email za ponastavitev gesla."
+ invite: "Vpišite uporabniško ime ali e-naslov in poslali vam bomo e-sporočilo za ponastavitev gesla."
reset: "Ponastavi geslo"
+ complete_username_found: "Račun z uporabniškim imenom %{username}obstaja. V kratkem bi morali prejeti e-sporočilo z navodili za zamenjavo gesla."
+ complete_email_found: "Račun z e-naslovom %{email}obstaja. V kratkem bi morali prejeti e-sporočilo z navodili za zamenjavo gesla."
+ complete_username_not_found: "Račun z uporabniškim imenom %{username} ne obstaja."
+ complete_email_not_found: "Račun z e-naslovom %{email}ne obstaja."
help: "Ali elektronska pošta ne prihaja? Najprej preverite mapo z neželeno pošto.
Ne veste kateri elektronski naslov ste uporabili? Vpišite elektronski naslov in povedali vam bomo če obstaja pri nas.
Če nimate več dostopa do elektronskega naslova vašega računa kontaktirajte o našo ekipo.
"
+ email_login:
+ button_label: "preko e-pošte"
+ complete_username_found: "Račun z uporabniškim imenom %{username} obstaja. V kratkem bi morali prejeti e-sporočilo z povezavo za avtomatsko prijavo."
+ complete_email_found: "Račun z e-naslovom %{email} obstaja. V kratkem bi morali prejeti e-sporočilo z povezavo za avtomatsko prijavo."
+ complete_username_not_found: "Račun z uporabniškim imenom %{username}ne obstaja."
login:
title: "Prijava"
username: "Uporabnik"
password: "Geslo"
- email_placeholder: "email ali uporabniško ime"
- blank_username_or_password: "Prosimo vpišite email ali uporabniško ime in geslo."
+ email_placeholder: "e-naslov ali uporabniško ime"
+ blank_username: "Vnesite uporabniško ime ali e-naslov."
+ blank_username_or_password: "Vpišite e-naslov ali uporabniško ime in geslo."
reset_password: 'Ponastavi geslo'
- awaiting_activation: "Vaš uporabniški račun čaka aktivacijo, uporabite povezavo za pozabljeno geslo če želite prejeti še en email za aktivacijo."
+ awaiting_activation: "Vaš uporabniški račun čaka aktivacijo, uporabite povezavo za pozabljeno geslo če želite prejeti še en e-sporočilo za aktivacijo."
provide_new_email: "Vpišite nov naslov in ponovno vam bomo poslali potrditveno sporočilo."
to_continue: "Prosimo če se prijavite"
preferences: "Za spremembo nastavitev morate biti prijavljeni."
- not_approved: "Vaš uporabniški račun še ni bil potrjen. Obveščeni boste preko emaila, ko bo pripravljen za prijavo."
+ not_approved: "Vaš uporabniški račun še ni bil potrjen. Ko bo pripravljen za prijavo boste obveščenipreko e-sporočila."
google_oauth2:
message: "Overjanje z Googlom (pazi, da pojavno okno ne bo preprečeno)"
twitter:
@@ -918,10 +1011,14 @@ sl:
github:
message: "Overjanje z GitHubom (pazi, da pojavno okno ne bo preprečeno)"
invites:
+ welcome_to: "Dobrodošli na %{site_name}!"
password_label: "Nastavi geslo"
category_page_style:
categories_with_featured_topics: "Kategorije z izpostavljenimi temami"
categories_and_latest_topics: "Kategorije in najnovejše teme"
+ categories_and_top_topics: "Kategorije in najboljše teme"
+ select_kit:
+ filter_placeholder: Išči...
emoji_picker:
activity: Aktivnost
composer:
@@ -936,13 +1033,19 @@ sl:
reply: "Odgovori"
create_topic: "Ustvari temo"
users_placeholder: "Dodaj uporabnika"
+ title_placeholder: "Kaj je tema sporočila v kratkem stavku?"
title_or_link_placeholder: "Na tem mestu vpiši naslov ali prilepi povezavo "
reply_placeholder: "Tu lahko pišeš. Možna je uporaba Markdown, BBcode ali HTML za oblikovanje. Sem lahko povlečeš ali prilepiš sliko."
view_new_post: "Oglej si tvojo novo objavo"
saved_draft: "Osnutek objave v pripravi. Izberi za nadaljevanje."
+ show_preview: 'prikaži predogled »'
+ hide_preview: '« skrij predogled'
quote_post_title: "Citiraj celo objavo"
paste_code_text: "vpiši ali prilepi kodo"
composer_actions:
+ reply_as_private_message:
+ label: Novo zasebno sporočilo
+ desc: Novo zasebno sporočilo
toggle_whisper:
desc: Šepeti so vidni samo skrbnikom
notifications:
@@ -976,6 +1079,7 @@ sl:
clear_all: "Počisti vse"
too_short: "Niz za iskanje je prekratek"
title: "išči po temah, objavah, uporabnikih ali kategorijah"
+ full_page_title: "išči teme ali objave"
no_results: "Iskanje nima zadetkov."
no_more_results: "Ni več zadetkov iskanja."
searching: "Iščem ..."
@@ -992,7 +1096,7 @@ sl:
user: "Išči objave od @{{username}}"
category: "Išči po #{{category}} kategoriji"
topic: "Išči po tej temi"
- private_messages: "Išči po sporočilih"
+ private_messages: "Išči po zasebnih sporočilih"
advanced:
title: Napredno iskanje
posted_by:
@@ -1007,6 +1111,7 @@ sl:
label: Označeno
filters:
label: Vrni samo teme/objave...
+ title: se ujema naslov
likes: Ki sem jih všečkal
posted: Objavil sem v
watching: Ki jih opazujem
@@ -1056,9 +1161,12 @@ sl:
none:
unread: "Nimate neprebranih tem."
new: "Nimate novih tem."
+ read: "Niste prebrali še nobene teme."
+ posted: "Niste objavili še v nobeni temi."
latest: "Ni najnovejših tem. "
- bookmarks: "Zaenkrat še nimate tem z zaznamki."
+ bookmarks: "Nimate tem z zaznamki."
category: "Ni tem za kategorijo {{category}} ."
+ top: "Ni najboljših tem."
educate:
new: '
Tu se prikažejo vaše nove teme.
Privzeto se teme prikažejo kot nove in bodo imele oznako novo če so bile ustvarjene v zadnjih 2 dneh.
'
@@ -1075,6 +1183,10 @@ sl:
topic:
create: 'Nova tema'
create_long: 'Ustvari novo temo'
+ private_message: 'Novo zasebno sporočilo'
+ move_to_inbox:
+ title: 'Premakni v Prejeto'
+ help: 'Premakni sporočilo nazaj v Prejeto'
new: 'nova tema'
new_topics:
one: '1 nova tema'
@@ -1122,7 +1234,7 @@ sl:
notifications:
title: spremenite kako pogosto želite biti obveščeni o tej temi
reasons:
- mailing_list_mode: "Omogočen imate način seznama elektronske pošte, zato boste o tej temi obveščeni preko elektronske pošte."
+ mailing_list_mode: "Omogočeno imate e-sporočilo za vsako objavo, zato boste o tej temi obveščeni preko e-pošte."
"3_6": 'Prejemali boste obvestila ker opazujete to kategorijo.'
"2_8": 'Videli boste število novih odgovorov, ker sledite tej kategoriji.'
"2_4": 'Videli boste število novih odgovorov, ker ste objavili odgovor na to temo.'
@@ -1203,8 +1315,8 @@ sl:
other: "Trenutno globalno pripete teme: {{count}}"
invite_private:
title: 'Povabi k sporočilu'
- email_or_username: "Email ali uporabniško ime vabljenega"
- email_or_username_placeholder: "email ali uporabniško ime "
+ email_or_username: "E-naslov ali uporabniško ime vabljenega"
+ email_or_username_placeholder: "e-naslov ali uporabniško ime "
action: "Povabi"
success: "Povabili smo uporabnika/co, da sodeluje v tem pogovoru."
success_group: "Povabili smo skupino, da sodeluje v tem pogovoru."
@@ -1220,9 +1332,13 @@ sl:
split_topic:
title: "Prestavi v novo temo"
action: "prestavi v novo temo"
- topic_name: "Ime nove teme"
+ multi_select:
+ select_all: izberi vse
post:
quote_reply: "Citiraj"
+ reply_as_new_private_message: "Odgovori kot zasebno sporočilo z enakimi naslovniki"
+ collapse: "skrči"
+ expand_collapse: "razširi/skrči"
abandon:
no_value: "Ne, ohrani"
yes_value: "Ja, zavrži"
@@ -1321,7 +1437,7 @@ sl:
rows_with_featured_topics: "Vrstice z izpostavljenimi temami"
flagging:
title: 'Hvala, da pomagate ohraniti civilizirano skupnost!'
- action: 'Postavi zastavico na objavo'
+ action: 'Prijavi objavo'
take_action: "Ukrepaj"
notify_action: 'Sporočilo'
official_warning: 'Uradno opozorilo'
@@ -1334,13 +1450,15 @@ sl:
spam: "Je nezaželeno"
flagging_topic:
title: "Hvala, da pomagate ohraniti civilizirano skupnost!"
- action: "Postavi zastavico na objavo"
+ action: "Prijavi temo"
notify_action: "Sporočilo"
topic_map:
title: "Povzetek teme"
links_title: "Popularne povezave"
links_shown: "prikaži več povezav..."
topic_statuses:
+ unpinned:
+ title: "Odpeto"
pinned_globally:
title: "Pripeto globalno"
help: "Ta tema je pripeta globalno; prikazala se bo na vrhu zadnjih in njene kategorije"
@@ -1425,7 +1543,7 @@ sl:
help: "teme v katerih ste objavljali"
bookmarks:
title: "Zaznamki"
- help: "teme v zaznamkih"
+ help: "teme z zaznamki"
category:
title: "{{categoryName}}"
title_with_count:
@@ -1457,10 +1575,62 @@ sl:
today: "Danes"
other_periods: "poglej najboljše"
keyboard_shortcuts_help:
+ title: 'Bližnjice na tipkovnici'
jump_to:
- top: 'Na vrh'
+ title: 'Skoči na'
+ home: 'g, h Domov'
+ latest: 'g, l Najnovejše'
+ new: 'g, n Novo'
+ unread: 'g, u Neprebrano'
+ categories: 'g, c Kategorije'
+ top: 'g, t Na vrh'
+ bookmarks: 'g, b Zaznamki'
+ profile: 'g, p Profil'
+ messages: 'g, m Zasebna sporočila'
+ drafts: 'g, d Osnutki'
+ navigation:
+ title: 'Navigacija'
+ jump: '# Pojdi na objavo #'
+ back: 'u Nazaj'
+ up_down: 'k/j Prestavi izbiro ↑ ↓'
+ open: 'o or Enter Odpri izbrano temo'
+ next_prev: 'shift+j/shift+k Naslednja/predhodna sekcija'
+ application:
+ title: 'Aplikacija'
+ create: 'c Objavi novo temo'
+ notifications: 'n Odpri obvestila'
+ hamburger_menu: '= Odpri meni'
+ user_profile_menu: 'p Odpri uporabniški meni'
+ show_incoming_updated_topics: '. Prikaži osvežene teme'
+ search: '/ or ctrl+alt+f Iskalnik'
+ help: '? Odpri bližnjice na tipkovnici'
+ dismiss_new_posts: 'x, r Prekliči nov/objavo'
+ dismiss_topics: 'x, t Prekliči teme'
+ log_out: 'shift+zshift+z Odjava'
+ composing:
+ title: 'Urejevalnik'
+ return: 'shift+c Vrni se v urejevalnik'
+ fullscreen: 'shift+F11 Celostranski urejevalnik'
actions:
+ title: 'Akcije'
+ bookmark_topic: 'fDodaj/odstrani zaznamek na temi'
+ pin_unpin_topic: 'shift+p Pripni/Odpni temo'
+ share_topic: 'shift+sDeli temo'
+ share_post: 's Deli objavo'
+ reply_as_new_topic: 't Odgovori kot povezana tema'
+ reply_topic: 'shift+r Odgovori v temo'
+ reply_post: 'r Odgovori na objavo'
+ quote_post: 'q Citiraj objavo'
+ like: 'l Všečkaj objavo'
flag: '! Prijavi objavo'
+ bookmark: 'b Dodaj zaznamek'
+ edit: 'e Uredi objavo'
+ delete: 'd Zbriši objavo'
+ mark_muted: 'm, m Utišaj temo'
+ mark_regular: 'm, r Normalna tema'
+ mark_tracking: 'm, t Sledi temi'
+ mark_watching: 'm, w Spremljaj temo'
+ print: 'ctrl+p Natisni temo'
badges:
title: Značke
multiple_grant: "To lahko osvojite večkrat."
@@ -1482,7 +1652,9 @@ sl:
none:
unread: "Nimate neprebranih tem."
new: "Nimate novih tem."
- bookmarks: "Zaenkrat še nimate tem z zaznamki."
+ read: "Niste prebrali še nobene teme."
+ posted: "Niste objavili še v nobeni temi."
+ bookmarks: "Nimate tem z zaznamki."
bottom:
latest: "Ni več zadnjih tem."
hot: "Ni več vročih tem."
@@ -1502,9 +1674,10 @@ sl:
version: "Verzija"
installed_version: "Nameščeno"
latest_version: "Najnovejše"
+ refresh_problems: "Osveži"
moderators: 'Moderatorji:'
admins: 'Admini:'
- backups: "varnostne kopije"
+ private_messages_title: "Zasebna sporočila"
page_views: "Ogledov strani"
page_views_short: "Ogledov strani"
reports:
@@ -1514,6 +1687,7 @@ sl:
30_days_ago: "pred 30 dnevi"
view_table: "tabela"
view_graph: "graf"
+ refresh_report: "Osveži poročilo"
start_date: "Začetni datum"
end_date: "Končni datum"
flags:
@@ -1524,14 +1698,14 @@ sl:
ignore_flag: "Ignoriraj"
delete: "Izbriši"
delete_flag_modal_title: "Izbriši in..."
- disagree_flag_unhide_post_title: "Odstrani zastave s te objave in naredi objavo spet vidno"
+ disagree_flag_unhide_post_title: "Odstrani prijave s te objave in naredi objavo spet vidno"
more: "(več odgovorov...)"
suspend_user: "Suspendiraj uporabnika"
system: "Sistem"
error: "Prišlo je do napake"
reply_message: "Odgovori"
show_full: "pokaži celo objavo"
- show_details: "Pokaži podrobnosti zastave"
+ show_details: "Pokaži podrobnosti prijave"
details: "podrobnosti"
flagged_topics:
topic: "Tema"
@@ -1629,9 +1803,9 @@ sl:
copied_to_clipboard: "Kopirano v odložišče"
copy_to_clipboard_error: "Prišlo je do napake pri kopiranju v odložišče"
email_templates:
- title: "Email predloge"
+ title: "E-sporočila predloge"
subject: "Zadeva"
- multiple_subjects: "Ta email predloga ima več zadev."
+ multiple_subjects: "Ta e-sporočilo predloga ima več zadev."
revert: "Prekliči spremembe"
revert_confirm: "Ste prepričani, da želite preklicati spremembe?"
theme:
@@ -1644,6 +1818,7 @@ sl:
mobile: "Mobilno"
preview: "Predogled"
color_scheme: "Barvna shema"
+ collapse: Skrči
uploads: "Prenosi"
upload: "Prenesi"
css_html: "CSS/HTML po meri"
@@ -1679,16 +1854,17 @@ sl:
success:
name: 'uspeh'
email:
- title: "Emaili"
+ title: "E-pošta"
settings: "Nastavitve"
templates: "Predloge"
- sending_test: "Pošiljam testni email..."
+ sending_test: "Pošiljam testno e-sporočilo..."
sent: "Poslano"
received: "Prejeto"
rejected: "Zavrnjeno"
sent_at: "Poslano"
time: "Čas"
user: "Uporabnik"
+ refresh: "Osveži"
logs:
filters:
user_placeholder: "uporabniško ime"
@@ -1727,7 +1903,7 @@ sl:
unsuspend_user: "prekini suspenzijo uporabnika"
grant_badge: "podeli značko"
revoke_badge: "odvzemi značko"
- check_email: "preveri email"
+ check_email: "preveri e-sporočila"
delete_topic: "izbriši temo"
delete_post: "izbriši objavo"
anonymize_user: "anonimiziraj uporabnika"
@@ -1750,7 +1926,7 @@ sl:
check_personal_message: "preveri osebno sporočilo"
post_approved: "objava odobrena"
screened_emails:
- email: "Email naslov"
+ email: "E-naslov"
actions:
allow: "Dovoli"
screened_urls:
@@ -1788,7 +1964,7 @@ sl:
upload: "Prenesi"
users:
title: 'Uporabniki'
- show_emails: "Pokaži emaile"
+ show_emails: "Pokaži e-sporočila"
nav:
new: "Novi"
active: "Aktivni"
@@ -1822,13 +1998,13 @@ sl:
admin: "Admin?"
show_admin_profile: "Admin"
show_public_profile: "Pokaži javni profil"
- log_out: "Izpiši"
- grant_admin_confirm: "Poslali smo ti email za potrditev novega administratorja. Odpri ga in sledi navodilom."
+ log_out: "Odjava"
+ grant_admin_confirm: "Poslali smo ti e-sporočilo za potrditev novega administratorja. Odpri ga in sledi navodilom."
unsuspend: 'Prekliči suspenz'
suspend: 'Suspendiraj'
- show_flags_received: "Pokaži prejete zastave"
- flags_received_by: "Zastave prejete od %{username}"
- flags_received_none: "Ta uporabnik ni prejel nobene zastave."
+ show_flags_received: "Pokaži prejete prijave"
+ flags_received_by: "Prijave prejete od %{username}"
+ flags_received_none: "Ta uporabnik ni prejel nobene prijave."
permissions: Dovoljenja
activity: Aktivnost
like_count: Podeljeni / prejeti všečki
@@ -1837,20 +2013,20 @@ sl:
posts_read_count: Prebrane objave
post_count: Ustvarjene objave
topics_entered: Ogledane teme
- flags_given_count: Podeljene zastave
- flags_received_count: Prejete zastave
+ flags_given_count: Podeljene prijave
+ flags_received_count: Prejete prijave
warnings_received_count: Prejeta opozorila
- flags_given_received_count: 'Podeljene/prejete zastave'
+ flags_given_received_count: 'Podeljene/prejete prijave'
approve: 'Potrdi'
approved_by: "potrjen od"
- approve_success: "Uporabnik/ca je potrjen/a, email z navodili za aktivacijo računa je bil poslan."
+ approve_success: "Uporabnik/ca je potrjen/a, e-sporočilo z navodili za aktivacijo računa je bil poslan."
approve_bulk_success: "Uspeh! Vsi izbrani uporabniki/ce, so bili potrjeni in obveščeni."
time_read: "Čas branja"
anonymize: "Anonimiziraj uporabnika"
delete: "Izbriši uporabnika"
delete_forbidden_because_staff: "Adminov in moderatorjev se ne da izbrisati."
delete_confirm: "Si prepričan/a, da želiš izbrisati tega uporabnika? Sprememba bo trajna!"
- delete_and_block: "Izbriši in blokiraj ta email in IP naslov"
+ delete_and_block: "Izbriši in blokiraj ta e-naslov in IP naslov"
deleted: "Ta uporabnik je bil izbrisan"
activate: "Aktiviraj račun"
activate_failed: "Prišlo je do napake pri aktivaciji uporabnika."
@@ -1867,7 +2043,8 @@ sl:
sso:
external_username: "Uporabniško ime"
external_name: "Ime"
- external_email: "Email"
+ external_email: "E-naslov"
+ external_avatar_url: "URL do profilne slike"
user_fields:
title: "Podatki uporabnika"
create: "Dodaj podatek uporabnika"
diff --git a/config/locales/client.sq.yml b/config/locales/client.sq.yml
index b3b429ea49..a98ec436ee 100644
--- a/config/locales/client.sq.yml
+++ b/config/locales/client.sq.yml
@@ -247,7 +247,6 @@ sq:
choose_topic:
none_found: "Asnjë temë nuk u gjet."
title:
- search: "Kërko për një temë sipas titullit, adresës URL apo id:"
placeholder: "shkruaj titullin e temës këtu"
queue:
topic: "Tema:"
@@ -341,7 +340,6 @@ sq:
description: "Ju do të njoftoheni për çdo postim të ri në çdo mesazh, dhe numri i ri i përgjigjeve të reja do të tregohet."
watching_first_post:
title: "Postimi i parë nën vëzhgim"
- description: "Ju do të njoftoheni vetëm për postimin e parë të çdo teme nën këtë etiketë."
tracking:
title: "Në gjurmim"
description: "Ju do të njoftoheni në qoftë se dikush ju pëmend me @emri ose ju përgjigjet, gjithashtu numri i përgjigjeve të reja do të tregohet."
@@ -1272,7 +1270,6 @@ sq:
split_topic:
title: "Ktheje në një temë të re"
action: "ktheje në një temë të re"
- topic_name: "Titulli i temës së re"
error: "Pati një gabim gjatë transfertës drejt një teme të re."
instructions:
one: "Jeni duke krijuar një temë të re dhe duke e populluar atë me postimin që keni përzgjedhur."
@@ -1527,7 +1524,6 @@ sq:
title: "Në vëzhgim"
watching_first_post:
title: "Postimi i parë nën vëzhgim"
- description: "Ju do të njoftoheni vetëm për postimin e parë të çdo teme nën këto kategori."
tracking:
title: "Në gjurmim"
regular:
@@ -1892,9 +1888,6 @@ sq:
private_messages_short: "Msgs"
private_messages_title: "Mesazhet"
mobile_title: "Mobile"
- space_free: "{{size}} lirë"
- uploads: "ngarkime"
- backups: "backupe"
traffic_short: "Trafik"
traffic: "Kërkesat web të aplikimit"
show_traffic_report: "Trego raportin e detajuar të trafikut"
diff --git a/config/locales/client.sr.yml b/config/locales/client.sr.yml
index 06e453b231..2e9bc71b1e 100644
--- a/config/locales/client.sr.yml
+++ b/config/locales/client.sr.yml
@@ -281,7 +281,6 @@ sr:
choose_topic:
none_found: "Nema pronađenih tema."
title:
- search: "Traži temu prema imenu, adresi ili id-u."
placeholder: "unesite naslov teme ovde"
queue:
topic: "Tema:"
@@ -1034,7 +1033,6 @@ sr:
split_topic:
title: "Prebaci u Novu Temu"
action: "prebacu u novu temu"
- topic_name: "Ime Nove Teme"
error: "Dogodila se greška pri prebacivanju poruka u novu temu."
instructions:
one: "Otvorićete novu temu popunjenu s {{count}} poruka koje ste izabrali."
diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml
index 248f48d0f6..44f1688030 100644
--- a/config/locales/client.sv.yml
+++ b/config/locales/client.sv.yml
@@ -263,7 +263,6 @@ sv:
choose_topic:
none_found: "Inga ämnen hittades."
title:
- search: "Sök efter ett Ämne baserat på namn, url eller id:"
placeholder: "skriv ämnets rubrik här"
queue:
topic: "Ämne:"
@@ -383,7 +382,6 @@ sv:
description: "Du kommer att notifieras om varje nytt inlägg i det här meddelandet, och en räknare med antalet nya svar kommer att visas."
watching_first_post:
title: "Bevakar första inlägget"
- description: "Du kommer att bli notifierad om första inlägget i varje nytt ämne i den här gruppen."
tracking:
title: "Följer"
description: "Du kommer att notifieras om någon nämner ditt @namn eller svarar på ditt inlägg, och en räknare med antalet nya svar kommer att visas."
@@ -1387,7 +1385,6 @@ sv:
split_topic:
title: "Flytta till nytt ämne"
action: "flytta till nytt ämne"
- topic_name: "Nytt ämnesnamn"
error: "Ett fel inträffade då inläggen skulle flyttas till det nya ämnet."
instructions:
one: "Du är påväg att skapa ett nytt ämne och lägga inlägget du har valt i den."
@@ -1658,7 +1655,6 @@ sv:
description: "Du kommer automatiskt att bevaka alla ämnen i de här kategorierna. Du blir notifierad om varje nytt inlägg i alla ämnen, och en räknare över antalet nya inlägg visas. "
watching_first_post:
title: "Bevakar första inlägget"
- description: "Du kommer att bli notifierad om första inlägget i varje nytt ämne i de här kategorierna."
tracking:
title: "Följer"
description: "Du kommer automatiskt att följa alla ämnen i de här kategorierna. Du blir notifierad om någon nämner ditt @namn eller svarar på ditt inlägg, och en räknare över antalet nya inlägg visas."
@@ -2055,9 +2051,6 @@ sv:
private_messages_short: "Meddelanden"
private_messages_title: "Meddelanden"
mobile_title: "Mobil"
- space_free: "{{size}} ledigt"
- uploads: "uppladdningar"
- backups: "säkerhetskopior"
traffic_short: "Trafik"
traffic: "Applikations-webbegäran"
page_views: "Sidvisningar"
diff --git a/config/locales/client.sw.yml b/config/locales/client.sw.yml
index 070c0a746c..fc303413ff 100644
--- a/config/locales/client.sw.yml
+++ b/config/locales/client.sw.yml
@@ -279,7 +279,6 @@ sw:
choose_topic:
none_found: "Hakuna mada zilizopatikana."
title:
- search: "Tafuta Mada kwa jina, anwani au utambulisho:"
placeholder: "andika kichwa cha mada hapa"
queue:
topic: "Mada:"
@@ -453,7 +452,6 @@ sw:
description: "Utajulishwa kuhusu kila chapisho jipya kwenye kila ujumbe, na idadi ya majibu mapya itaonyeshwa."
watching_first_post:
title: "Chapisho la Kwanza Linaangaliwa"
- description: "Utajulishwa kuhusu chapisho la kwanza kwenye kila mada ndani ya kikundi hichi."
tracking:
title: "Inafuatiliwa"
description: "Utajulishwa kama mtu akitaja @jina lako au akikujibu, na idadi ya majibu mapya itaonyeshwa."
@@ -1726,7 +1724,6 @@ sw:
split_topic:
title: "Hamisha kwenda Mada Mpya"
action: "hamisha kwenda mada mpya"
- topic_name: "Jina la mada mpya"
error: "Hitilafu imetokea wakati wa kuhamisha machapisho kwenda mada mpya."
merge_topic:
title: "Hamisha kwenda kwenye Mada Iliyopo"
@@ -1987,7 +1984,6 @@ sw:
description: "Utaangalia mada zote kwenye kategoria hizi. Utajulishwa kuhusiana na machapisho mapya ndani ya mada zote, na namba ya majibu itaonyeshwa."
watching_first_post:
title: "Chapisho la Kwanza Linaangaliwa"
- description: "Utajulishwa kuhusu chapisho la kwanza tu kwenye kila mada ndani ya vikundi hivi."
tracking:
title: "Fuatilia"
description: "Ufuatilia mada zote kwenye kategoria hizi. Utajulishwa kama mtu akitaja @jina lako au akikujibu, na namba ya majibu itaonyeshwa."
@@ -2276,7 +2272,6 @@ sw:
description: "Otomatikali utaangalia mada zote zenye lebo hii. Utajulishwa kuhusiana na mada na machapisho mapya, pia namba za machapisho ambayo hayajasomwa na mapya itatokea pembeni ya mada."
watching_first_post:
title: "Chapisho la Kwanza Linaangaliwa"
- description: "Utajulishwa kuhusu chapisho la kwanza kwenye kila mada zenye lebo hii."
tracking:
title: "Fuatilia"
description: "Utafuatilia mada zote zenye lebo hizi. Namba za machapisho ambayo hayajasomwa na mapya itatokea pembeni ya mada."
@@ -2359,9 +2354,6 @@ sw:
private_messages_short: "Ujumbe"
private_messages_title: "Ujumbe"
mobile_title: "Kifaa cha kiganjani"
- space_free: "iko wazi {{size}}"
- uploads: "vilivyopakiwa"
- backups: "machelezo"
lastest_backup: "Hivi karibuni: %{date}"
traffic_short: "Utembeleaji"
page_views: "Ukurasa Umeonwa Mara"
@@ -2980,9 +2972,6 @@ sw:
title: "Batli ya Utafiti"
term: "Neno"
searches: "Utafiti"
- click_through: "Bofya"
- unique: "Kipekee"
- unique_title: "watumiaji wakipekee wanaofanya utafiti"
types:
all_search_types: "Aina zote za matokeo"
full_page: "Ukurasa Wote"
diff --git a/config/locales/client.te.yml b/config/locales/client.te.yml
index 87dcb2e680..6af53b23cc 100644
--- a/config/locales/client.te.yml
+++ b/config/locales/client.te.yml
@@ -190,7 +190,6 @@ te:
choose_topic:
none_found: "ఎటువంటి విషయాలూ కనపడలేదు."
title:
- search: "పేరు, యూఆర్ యల్, ఐడీ లను బట్టి విషయాన్ని వెతుకు."
placeholder: "ఇక్కడ విషయపు శీర్షిక రాయండి"
queue:
approve: 'అంగీకరించు'
@@ -812,7 +811,6 @@ te:
split_topic:
title: "కొత్త విషయానికి జరుపు"
action: "కొత్త విషయానికి జరుపు"
- topic_name: "కొత్త విషయపు పేరు"
error: "టపాలను కొత్త విషయానికి జరిపేటప్పుడు దోషం తలెత్తింది"
instructions:
one: "మీరు కొత్త విషయం సృష్టించి దాన్ని మీరు ఈ టపాతో నింపబోతున్నారు."
@@ -1134,9 +1132,6 @@ te:
moderators: 'నిర్వాహకులు:'
admins: 'అధికారులు:'
suspended: 'సస్పెండయిన:'
- space_free: "{{size}} ఖాలీ"
- uploads: "ఎగుమతులు"
- backups: "బ్యాకప్లు"
traffic_short: "ట్రాఫిక్"
traffic: "అనువర్తన జాల రిక్వెస్టులు"
show_traffic_report: "సవివరణ ట్రాఫిక్ రిపోర్టు చూపు"
diff --git a/config/locales/client.th.yml b/config/locales/client.th.yml
index 11bec5a570..d1b38d8693 100644
--- a/config/locales/client.th.yml
+++ b/config/locales/client.th.yml
@@ -232,7 +232,6 @@ th:
choose_topic:
none_found: "ไม่พบกระทู้ใดๆ"
title:
- search: "ค้นหากระทู้โดยชื่อ url หรือ id"
placeholder: "ระบุชื่อกระทู้ที่นี่"
queue:
topic: "กระทู้:"
@@ -351,7 +350,6 @@ th:
description: "คุณจะถูกแจ้งเตือนทุกๆโพสใหม่ในทุกๆข้อความและจำนวนใหม่จะถูกนับอละแสดง"
watching_first_post:
title: "ดูโพสต์แรก"
- description: "คุณได้รับการแจ้งเตือนเกี่ยวกับโพสต์ใหม่ในกระทู้ในกลุ่มนี้"
tracking:
title: "ติดตาม"
description: "คุณจะได้รับการแจ้งเตือนเมื่อใครก็ตามเอ่ยชื่อของคุณ @name หรือตอบคุณ และจำนวนของการตอบจะถูกแสดง"
@@ -1095,7 +1093,6 @@ th:
split_topic:
title: "ย้ายไปหัวข้อใหม่"
action: "ย้ายไปหัวข้อใหม่"
- topic_name: "ชื่อหัวข้อใหม่"
merge_topic:
title: "ย้ายไปหัวข้อที่มีอยู่แล้ว"
action: "ย้ายไปหัวข้อที่มีอยู่แล้ว"
@@ -1313,9 +1310,6 @@ th:
private_messages_short: "ข้อความ"
private_messages_title: "ข้อความ"
mobile_title: "โทรศัพท์"
- space_free: "{{size}} ว่าง"
- uploads: "อัพโหลด"
- backups: "สำรอง"
traffic_short: "การจราจร"
reports:
today: "วันนี้"
diff --git a/config/locales/client.tr_TR.yml b/config/locales/client.tr_TR.yml
index 88f70c8783..49e9accadc 100644
--- a/config/locales/client.tr_TR.yml
+++ b/config/locales/client.tr_TR.yml
@@ -289,7 +289,6 @@ tr_TR:
choose_topic:
none_found: "Hiçbir konu bulunamadı."
title:
- search: "İsim, url ya da id ile konu ara:"
placeholder: "konu başlığını buraya yaz"
queue:
topic: "Konu:"
@@ -464,7 +463,6 @@ tr_TR:
description: "Her mesajda her yeni gönderi hakkında bilgilendirileceksin ve yeni cevap sayısı gösterilecek."
watching_first_post:
title: "İlk Gönderi İzlenmesi"
- description: "Bu gruptaki her yeni konu ile ilgili olarak sadece ilk gönderide bilgilendirileceksin"
tracking:
title: "Takip ediliyor"
description: "Herhangi biri @isminden bahseder ya da sana cevap verirse bildirim alacaksın ve yeni cevap sayısını göreceksin."
@@ -1760,7 +1758,6 @@ tr_TR:
split_topic:
title: "Yeni Konuya Geç"
action: "yeni konuya geç"
- topic_name: "Yeni Konu Adı"
error: "Gönderileri yeni konuya taşırken bir hata oluştu."
instructions:
one: "Yeni bir konu oluşturmak ve bu konuyu seçtiğiniz {{count}} gönderi ile doldurmak üzeresiniz."
@@ -2077,7 +2074,6 @@ tr_TR:
description: "Bu kategorilerdeki tüm konuları otomatik olarak izleyeceksin. Tüm konulardaki her yeni gönderiden haberdar olacak ve yeni cevapların sayısını göreceksin. "
watching_first_post:
title: "İlk Gönderi İzleme"
- description: "Bu kategorilerde bulunan tüm konuların sadece ilk gönderilerinde bildirim alacaksın."
tracking:
title: "Takip Ediliyor"
description: "Bu kategorilerdeki tüm yeni konuları otomatik olarak takip edeceksin. Birisi @isminden bahsederse veya gönderine cevap verirse bildirim alacak, ayrıca yeni cevapların sayısını da göreceksin. "
@@ -2413,7 +2409,6 @@ tr_TR:
description: "Bu etiketi içeren tüm konuları otomatik olarak izleyeceksin. Tüm yeni mesajlar ve konulardan haberdar edileceksin, ayrıca konuların yanında okunmamış ve yeni mesajların sayısı da görünecek."
watching_first_post:
title: "İlk gönderi izlemesi"
- description: "Bu etiketle yalnızca her yeni konudaki ilk gönderiden haberdar edileceksin."
tracking:
title: "Takip ediliyor"
description: "Bu etiketi içeren tüm konuları otomatik olarak takip edeceksin. Konunun yanında okunmamış ve yeni yayınların sayısı görünecek."
@@ -2500,9 +2495,6 @@ tr_TR:
private_messages_short: "Mesajlar"
private_messages_title: "Mesajlar"
mobile_title: "Mobil"
- space_free: "{{size}} boş"
- uploads: "yüklemeler"
- backups: "yedekler"
lastest_backup: "Güncel: %{date}"
traffic_short: "Akış"
traffic: "Uygulama web istekleri"
@@ -3195,9 +3187,6 @@ tr_TR:
title: "Arama Günlükleri"
term: "Dönem"
searches: "Aramalar"
- click_through: "Buraya tıklayın"
- unique: "Benzersiz"
- unique_title: "aramayı yapan benzersiz kullanıcılar"
types:
all_search_types: "Tüm arama türleri"
header: "Üst kısım"
diff --git a/config/locales/client.uk.yml b/config/locales/client.uk.yml
index 9005fcafb4..5d2f2d7e81 100644
--- a/config/locales/client.uk.yml
+++ b/config/locales/client.uk.yml
@@ -174,7 +174,6 @@ uk:
choose_topic:
none_found: "Не знайдено тем."
title:
- search: "Шукати тему за назвою, url або id:"
placeholder: "введіть назву теми"
queue:
topic: "Тема:"
@@ -796,7 +795,6 @@ uk:
split_topic:
title: "Перенесення до нової теми"
action: "перенести до нової теми"
- topic_name: "Назва нової теми"
error: "Під час перенесення дописів до нової теми трапилася помилка."
merge_topic:
title: "Перенесення до наявної теми"
diff --git a/config/locales/client.ur.yml b/config/locales/client.ur.yml
index fb2f398a48..9271de72f8 100644
--- a/config/locales/client.ur.yml
+++ b/config/locales/client.ur.yml
@@ -273,7 +273,6 @@ ur:
choose_topic:
none_found: "کوئی ٹاپک نہیں ملے۔"
title:
- search: "ٹاپک کو اُس کے نام، یو آر ایل یا آئی ڈی کے زریعے سے تلاش کریں:"
placeholder: "یہاں ٹاپک کا عنوان لکھیں"
queue:
topic: "ٹاپک:"
@@ -436,7 +435,6 @@ ur:
description: "آپ کو ہر پیغام میں ہونے والی ہر نئی پوسٹ کے بارے میں مطلع کیا جائے گا، اور نئے جوابات کی گنتی دیکھائی جائے گی۔"
watching_first_post:
title: "پہلی پوسٹ پر نظر رکھی ہوئی ہے"
- description: "آپ کو اِس گروپ کے ہر نئے ٹاپک میں صرف پہلی پوسٹ کے بارے میں مطلع کیا جائے گا۔"
tracking:
title: "ٹریک کیا جا رہا"
description: "اگر کوئی آپ کا @نام زکر کرتا ہے یا کوئی جواب دیتا ہے تو آپ کو مطلع کر دیا جائے گا، اور نئے جوابات کی گنتی دیکھائی جائے گی۔"
@@ -1666,7 +1664,6 @@ ur:
split_topic:
title: "نئے ٹاپک پر منتقل کریں"
action: "نئے ٹاپک پر منتقل کریں"
- topic_name: "نئے ٹاپک کا نام"
error: "نئے ٹاپک پر پوسٹس منتقل کرنے میں ایک خرابی کا سامنا کرنا پڑا۔"
instructions:
one: "آپ ایک نیا ٹاپک بنانے اور اپنی منتخب کردہ پوسٹ سے اِسے آباد کرنے والے ہیں۔"
@@ -1990,7 +1987,6 @@ ur:
description: "آپ خود کار طریقے سے اِن زمرہ جات میں موجود تمام ٹاپکس کو دیکھیں گے۔ تمام ٹاپکس میں ہر نئی پوسٹ کے بارے میں آپ کو مطلع کیا جائے گا، اور نئے جوابات کی گنتی دیکھائی جائے گی۔"
watching_first_post:
title: "پہلی پوسٹ پر نظر رکھی ہوئی ہے"
- description: "آپ کو اِن زمرہ جات میں ہر نئے ٹاپک کی صرف پہلی پوسٹ کے بارے میں مطلع کیا جائے گا۔"
tracking:
title: "ٹریک کیا جا رہا"
description: "آپ خود کار طریقے سے اِن زمرہ جات میں موجود تمام ٹاپکس کو ٹریک کریں گے۔ اگر کوئی آپ کا @نام زکر کرتا ہے یا کوئی جواب دیتا ہے تو آپ کو مطلع کر دیا جائے گا، اور نئے جوابات کی گنتی دیکھائی جائے گی۔"
@@ -2329,7 +2325,6 @@ ur:
description: "آپ خود کار طریقے سے اِس ٹیگ والے تمام ٹاپکس پر نظر رکھیں گے۔ تمام نئی پوسٹ اور ٹاپکس کے بارے میں مطلع کیا جائے گا، اور نئی پوسٹس کی گنتی بھی ٹاپک کے ساتھ دکھائی جائے گی۔"
watching_first_post:
title: "پہلی پوسٹ پر نظر رکھی ہوئی ہے"
- description: "آپ کو اِس ٹیگ والے ہر نئے ٹاپک کی صرف پہلی پوسٹ کے بارے میں مطلع کیا جائے گا۔"
tracking:
title: "ٹریک کیا جا رہا"
description: "آپ خود کار طریقے سے اِس ٹیگ کے ساتھ موجود تمام ٹاپکس کو ٹریک کریں گے۔ بغیر پڑھی اور نئی پوسٹس کی گنتی ٹاپک کے ساتھ دکھائی جائے گی۔"
@@ -2409,9 +2404,6 @@ ur:
private_messages_short: "پغم"
private_messages_title: "پیغامات"
mobile_title: "موبائل"
- space_free: "{{size}} فارغ"
- uploads: "اَپ لوڈز"
- backups: "بیک اَپس"
traffic_short: "ٹریفک"
traffic: "ایپلیکیشن ویب رِیکَویسٹس"
page_views: "صفحہ کے ملاحظات"
@@ -3019,9 +3011,6 @@ ur:
title: "لاگز سرچ کریں"
term: "اصطلاح"
searches: "سرچز"
- click_through: "کلِک تھرُو"
- unique: "منفرد"
- unique_title: "سرچ کرنے والے منفرد صارفین"
types:
all_search_types: "تمام سرچ ٹاپکس"
header: "ہَیڈر"
diff --git a/config/locales/client.vi.yml b/config/locales/client.vi.yml
index 064f7d8f59..ab5aae9259 100644
--- a/config/locales/client.vi.yml
+++ b/config/locales/client.vi.yml
@@ -128,6 +128,7 @@ vi:
topic_admin_menu: "Lựa chọn quản lí chủ đề."
wizard_required: "Chào mừng bạn đến với Discourse! Hãy bắt đầu với hướng dẫn cài đặt ✨"
emails_are_disabled: "Ban quản trị đã tắt mọi email gửi đi. Sẽ không có bất kỳ thông báo nào qua email được gửi đi."
+ bootstrap_mode_disabled: "Chế độ bootstrap sẽ bị vô hiệu trong 24 giờ tới."
themes:
default_description: "Mặc định"
s3:
@@ -238,7 +239,6 @@ vi:
choose_topic:
none_found: "Không tìm thấy chủ đề nào"
title:
- search: "Tìm kiếm chủ đề dựa vào tên, url hoặc id:"
placeholder: "viết chủ đề của chủ đề thảo luận ở đây"
queue:
topic: "Chủ đề:"
@@ -353,7 +353,6 @@ vi:
description: "Bạn sẽ được thông báo khi có bài viết mới trong mỗi tin nhắn, và số lượng trả lời mới sẽ được hiển thị"
watching_first_post:
title: "Theo dõi chủ đề đầu tiên"
- description: "Bạn sẽ được thông báo về bài viết đầu tiên của mỗi chủ đề mới trong nhóm này"
tracking:
title: "Đang theo dõi"
description: "Bạn sẽ được thông báo nếu ai đó đề cập đến @tên bạn hoặc trả lời bạn, và số lượng trả lời mới sẽ được hiển thị"
@@ -1326,7 +1325,6 @@ vi:
split_topic:
title: "Di chuyển tới Chủ đề mới"
action: "di chuyển tới chủ đề mới"
- topic_name: "Tên chủ đề mới"
error: "Có lỗi khi di chuyển bài viết tới chủ đề mới."
instructions:
other: "Bạn muốn tạo chủ đề mới và phổ biến nó với {{count}} bài viết đã chọn."
@@ -1830,9 +1828,6 @@ vi:
private_messages_short: "Tin nhắn"
private_messages_title: "Tin nhắn"
mobile_title: "Điện thoại"
- space_free: "{{size}} trống"
- uploads: "tải lên"
- backups: "sao lưu"
traffic_short: "Băng thông"
traffic: "Application web requests"
show_traffic_report: "Xem chi tiết Báo cáo Lưu lượng"
diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml
index e16b60fc60..033cf65169 100644
--- a/config/locales/client.zh_CN.yml
+++ b/config/locales/client.zh_CN.yml
@@ -177,6 +177,7 @@ zh_CN:
privacy: "隐私"
tos: "服务条款"
rules: "规则"
+ conduct: "行为准则"
mobile_view: "移动版"
desktop_view: "桌面版"
you: "你"
@@ -266,7 +267,6 @@ zh_CN:
choose_topic:
none_found: "没有找到主题。"
title:
- search: "通过标题、URL 或者 ID 搜索主题:"
placeholder: "在此输入主题标题"
queue:
topic: "主题:"
@@ -438,7 +438,6 @@ zh_CN:
description: "你将会在该私信中的每个新帖子发布后收到通知,并且会显示新回复数量。"
watching_first_post:
title: "跟踪"
- description: "你只会收到此组中每个新主题的第一帖的通知。"
tracking:
title: "跟踪"
description: "你会在别人@你或回复你时收到通知,并且新帖数量也将在这些主题后显示。"
@@ -599,9 +598,9 @@ zh_CN:
muted_categories: "静音"
muted_categories_instructions: "你不会收到有关这些分类中新主题的任何通知,也不会出现在类别或最新页面上。"
no_category_access: "无法保存,作为审核人你仅具有受限的 分类 访问权限"
- delete_account: "删除我的帐号"
- delete_account_confirm: "你真的要永久删除自己的帐号吗?删除之后无法恢复!"
- deleted_yourself: "你的帐号已被删除。"
+ delete_account: "删除我的账户"
+ delete_account_confirm: "你真的要永久删除自己的账户吗?删除之后无法恢复!"
+ deleted_yourself: "你的账户已被删除。"
delete_yourself_not_allowed: "想删除账户请联系管理人员。"
unread_message_count: "私信"
admin_delete: "删除"
@@ -639,7 +638,7 @@ zh_CN:
select_all: "全选"
tags: "标签"
preferences_nav:
- account: "帐号"
+ account: "账户"
profile: "个人信息"
emails: "邮件"
notifications: "通知"
@@ -671,7 +670,7 @@ zh_CN:
second_factor:
title: "双重验证"
disable: "停用双重验证"
- enable: "启用两步认证加强账号安全"
+ enable: "启用两步认证加强账户安全"
confirm_password_description: "确认密码以继续"
label: "编码"
rate_limit: "请等待另一个验证码。"
@@ -854,7 +853,7 @@ zh_CN:
reinvited_all: "所有邀请已重新发送!"
time_read: "阅读时间"
days_visited: "访问天数"
- account_age_days: "帐号建立天数"
+ account_age_days: "账户建立天数"
create: "发送邀请"
generate_link: "复制邀请链接"
link_generated: "邀请链接生成成功!"
@@ -991,7 +990,7 @@ zh_CN:
hide_forever: "不了"
hidden_for_session: "好的,我会在明天提醒你。不过你随时都可以使用“登录”来创建账户。"
intro: "你好!看起来你正在享受讨论,但你尚未注册帐户。"
- value_prop: "当你创建了帐号,我们就可以准确地记录你的阅读进度。这样你再次访问时就可以回到上次离开的地方。只要有人回复你,你也可以通过此处和电子邮件收到通知。而且你还可以赞帖子,向他人分享感激之情。:heartbeat:"
+ value_prop: "当你创建了账户,我们就可以准确地记录你的阅读进度。这样你再次访问时就可以回到上次离开的地方。只要有人回复你,你也可以通过此处和电子邮件收到通知。而且你还可以赞帖子,向他人分享感激之情。:heartbeat:"
summary:
enabled_description: "你正在查看主题的精简摘要版本:一些社区公认有意思的帖子。"
description: "有 {{replyCount}} 个回复。"
@@ -1019,7 +1018,7 @@ zh_CN:
search_hint: '用户名、电子邮件或 IP 地址'
create_account:
disclaimer: "注册即表示你同意隐私策略和服务条款。"
- title: "创建新帐号"
+ title: "创建新账户"
failed: "出问题了,有可能这个邮箱已经被注册了。试试忘记密码链接?"
forgot_password:
title: "重置密码"
@@ -1032,7 +1031,7 @@ zh_CN:
complete_email_found: "你的账户 %{email} 存在,你将马上收到一封电子邮件,以重置密码。"
complete_username_not_found: "没有找到用户 %{username}"
complete_email_not_found: "没有找到用户 %{email} "
- help: "没收到邮件?请先查看你的垃圾邮件文件夹。
"
button_ok: "OK"
button_help: "帮助"
email_login:
@@ -1053,7 +1052,7 @@ zh_CN:
second_factor_backup: "使用备用码登录"
second_factor_backup_title: "两步验证备份"
second_factor_backup_description: "请输入你的备份码:"
- second_factor: "使用Authenticator app登陆"
+ second_factor: "使用Authenticator app登录"
email_placeholder: "电子邮件或者用户名"
caps_lock_warning: "大写锁定开启"
error: "未知错误"
@@ -1065,10 +1064,10 @@ zh_CN:
logging_in: "登录中…"
or: "或"
authenticating: "验证中…"
- awaiting_activation: "你的帐号尚未激活,点击忘记密码链接以重新发送激活邮件。"
- awaiting_approval: "你的帐号尚未被论坛版主审核。请等待一段时间,当你的帐号被审核时会收到一封电子邮件。"
+ awaiting_activation: "你的账户尚未激活,点击忘记密码链接以重新发送激活邮件。"
+ awaiting_approval: "你的账户尚未被论坛版主审核。请等待一段时间,当你的账户被审核时会收到一封电子邮件。"
requires_invite: "抱歉,本论坛仅接受邀请注册。"
- not_activated: "你还不能登录。我们已经发送了一封邮件至 {{sentTo}},请打开它并完成帐号激活。"
+ not_activated: "你还不能登录。我们已经发送了一封邮件至 {{sentTo}},请打开它并完成账户激活。"
not_allowed_from_ip_address: "你使用的 IP 地址已被封禁。"
admin_not_allowed_from_ip_address: "你不能从这个 IP 地址以管理员身份登录。"
resend_activation_email: "点击此处重新发送激活邮件。"
@@ -1080,8 +1079,8 @@ zh_CN:
sent_activation_email_again: "我们又向 {{currentEmail}} 发送了一封激活邮件,邮件送达可能需要几分钟;请检查一下你邮箱的垃圾邮件文件夹。"
to_continue: "请登录"
preferences: "需要登入后更改设置"
- forgot: "我记不清帐号详情了"
- not_approved: "你的帐号还未通过审核。一旦审核通过,我们将邮件通知你。"
+ forgot: "我记不清账户详情了"
+ not_approved: "你的账户还未通过审核。一旦审核通过,我们将邮件通知你。"
google_oauth2:
name: "Google"
title: "Google 登录"
@@ -1110,10 +1109,10 @@ zh_CN:
accept_title: "邀请"
welcome_to: "欢迎来到%{site_name}!"
invited_by: "邀请你的是:"
- social_login_available: "你也可以通过任何使用这个邮箱的社交网站登陆。"
+ social_login_available: "你也可以通过任何使用这个邮箱的社交网站登录。"
your_email: "你的帐户邮箱地址是%{email}。"
accept_invite: "接受邀请"
- success: "已创建你的帐号,你现在可以登录了。"
+ success: "已创建你的账户,你现在可以登录了。"
name_label: "昵称"
password_label: "设置密码"
optional_description: "(可选)"
@@ -1755,7 +1754,6 @@ zh_CN:
split_topic:
title: "拆分主题"
action: "拆分主题"
- topic_name: "新主题名"
error: "拆分主题时发生错误。"
instructions:
other: "你将创建一个新的主题,并包含你选择的 {{count}} 个帖子。"
@@ -2032,7 +2030,7 @@ zh_CN:
special_warning: "警告:这个分类是已经自动建立好的分类,它的安全设置不能被更改。如果你不想要使用这个分类,直接删除它,而不是另作他用。"
images: "图片"
email_in: "自定义进站电子邮件地址:"
- email_in_allow_strangers: "接受无帐号的匿名用户的邮件"
+ email_in_allow_strangers: "接受无账户的匿名用户的邮件"
email_in_disabled: "站点设置中已经禁用通过邮件发表新主题。欲启用通过邮件发表新主题,"
email_in_disabled_click: '启用“邮件发表”设置。'
mailinglist_mirror: "分类镜像了一个邮件列表"
@@ -2065,7 +2063,6 @@ zh_CN:
description: "你将自动关注这些分类中的所有主题。每一个主题的每一个新帖,将通知你,还将显示新回复的数量。"
watching_first_post:
title: "监看头一帖"
- description: "在这些分类里面,只是每一个新主题的第一帖,才会通知你。"
tracking:
title: "跟踪"
description: "你将自动跟踪这些分类中的所有主题。如果有人@你或回复你,将通知你,还将显示新回复的数量。"
@@ -2232,7 +2229,7 @@ zh_CN:
category:
title: "{{categoryName}}"
title_with_count:
- other: "{{categoryName}}({{count}})"
+ other: "{{categoryName}} ({{count}})"
help: "{{categoryName}}分类中热门的主题"
top:
title: "热门"
@@ -2398,7 +2395,6 @@ zh_CN:
description: "你将自动监看该标签中的所有主题。你将收到所有新帖子和主题的通知,此外,主题旁边还会显示未读和新帖子的数量。"
watching_first_post:
title: "关注第一条帖子"
- description: "你只会收到此标签中每个新主题中第一篇帖子的通知。"
tracking:
title: "跟踪"
description: "你将使用此标签自动跟踪所有主题。未读和新帖计数将显示在主题旁。"
@@ -2486,7 +2482,6 @@ zh_CN:
private_messages_short: "私信"
private_messages_title: "私信"
mobile_title: "移动"
- space_free: "{{size}} 空闲"
uploads: "上传"
backups: "备份"
lastest_backup: "最新:%{date}"
@@ -2728,7 +2723,7 @@ zh_CN:
details: "当有新回复、编辑、帖子被删除或者恢复时。"
user_event:
name: "用户事件"
- details: "当用户登陆,退出,创建,批准或更新时。"
+ details: "当用户登录,退出,创建,批准或更新时。"
group_event:
name: "群组事件"
details: "当群组创建,更新或销毁时。"
@@ -2839,7 +2834,7 @@ zh_CN:
title: "将数据库回滚到之前的工作状态"
confirm: "你确定要将数据库回滚到之前的工作状态吗?"
location:
- local: "本地"
+ local: "本地存储"
s3: "Amazon S3"
export_csv:
success: "导出开始,完成后你将被通过私信通知。"
@@ -3239,9 +3234,6 @@ zh_CN:
title: "搜索日志"
term: "术语"
searches: "搜索"
- click_through: "点击通过"
- unique: "独特"
- unique_title: "执行搜索的特定用户"
types:
all_search_types: "所有搜索类型"
header: "头部"
@@ -3278,7 +3270,7 @@ zh_CN:
upload_successful: "上传成功。敏感词已添加。"
impersonate:
title: "检视用户视角"
- help: "使用此工具来检视其他用户的帐号以方便调试。你应该在完成后立即退出。"
+ help: "使用此工具来检视其他用户的账户以方便调试。你应该在完成后立即退出。"
not_found: "无法找到该用户。"
invalid: "抱歉,你不能以此用户角度检视。"
users:
@@ -3425,9 +3417,9 @@ zh_CN:
send_activation_email: "发送激活邮件"
activation_email_sent: "激活邮件已发送。"
send_activation_email_failed: "在发送激活邮件时发生了错误。"
- activate: "激活帐号"
+ activate: "激活账户"
activate_failed: "在激活用户帐号时发生了错误。"
- deactivate_account: "停用帐号"
+ deactivate_account: "停用账户"
deactivate_failed: "在停用用户帐号时发生了错误。"
unsilence_failed: '解除禁言时出错了。'
silence_failed: '禁言用户时出错了。'
diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml
index c7fb17b99a..575d3d3b26 100644
--- a/config/locales/client.zh_TW.yml
+++ b/config/locales/client.zh_TW.yml
@@ -263,7 +263,6 @@ zh_TW:
choose_topic:
none_found: "未找到任何討論話題。"
title:
- search: "以名稱、URL 或 ID 搜尋討論話題:"
placeholder: "請在這裡輸入討論標題"
queue:
topic: "話題:"
@@ -435,7 +434,6 @@ zh_TW:
description: "當任何訊息中有新貼文時你會收到通知。並且會顯示新回覆的數量。"
watching_first_post:
title: "關注第一則貼文"
- description: "你只會收到此組中每個新主題第一則貼文的通知。"
tracking:
title: "追蹤"
description: "當其他人通過 @名字 提及你或回覆時你會收到通知。並且會顯示新回覆的數量。"
@@ -1594,7 +1592,6 @@ zh_TW:
split_topic:
title: "移至新討論話題"
action: "移至新討論話題"
- topic_name: "新討論話題的名稱"
error: "將討論話題移至新討論話題時發生錯誤。"
instructions:
other: "你即將建立一個新討論話題,並填入 {{count}} 篇你已選擇的文章。"
@@ -1838,7 +1835,6 @@ zh_TW:
description: "你將自動關注這些分類中的所有主題。每一個主題的每一個新帖,將通知你,還將顯示新回覆的數量。"
watching_first_post:
title: "關注新的發文"
- description: "在這些分類裡面,只是每一個新主題的第一帖,才會通知你。"
tracking:
title: "追蹤"
description: "你將自動跟蹤這些分類中的所有主題。如果有人@你或回覆你,將通知你,還將顯示新回覆的數量。"
@@ -2214,9 +2210,6 @@ zh_TW:
private_messages_short: "訊息"
private_messages_title: "訊息"
mobile_title: "行動裝置"
- space_free: "{{size}} 可用空間"
- uploads: "上傳"
- backups: "備份檔"
traffic_short: "流量"
traffic: "網頁應用程式請求數"
page_views: "瀏覽數"
diff --git a/config/locales/server.ar.yml b/config/locales/server.ar.yml
index 96335d345e..791513a907 100644
--- a/config/locales/server.ar.yml
+++ b/config/locales/server.ar.yml
@@ -730,10 +730,6 @@ ar:
title: "العلامات"
xaxis: "اليوم"
yaxis: "عدد العلامات الجديدة"
- starred:
- title: "متألق"
- xaxis: "يوم"
- yaxis: "عدد المواضيع المتألقة الجديدة"
users_by_trust_level:
title: "الأعضاء في مستوى الثقة"
xaxis: "مستوى الثقة"
@@ -1195,21 +1191,6 @@ ar:
errors:
different_topics: "المشاركات من موضوعين مختلفين لايُمكن دمجهم"
different_users: "المشاركات من اعضاء مختلفين لايُمكن دمجهم"
- move_posts:
- new_topic_moderator_post:
- zero: "المشاركه التي انقسمت الي موضوع جديد: %{topic_link}"
- one: "المشاركه التي انقسمت الي موضوع جديد : %{topic_link}"
- two: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] "
- few: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] "
- many: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] "
- other: "%{count} المشاركات التي انقسمت الي موضوع جديد: %{topic_link] "
- existing_topic_moderator_post:
- zero: "المشاركه التي اندمجت الي موضوع واحد %{topic_link}"
- one: "المشاركه التي اندمجت الي موضوع واحد %{topic_link}"
- two: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}"
- few: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}"
- many: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}"
- other: "%{count} المشاركات التي اندمجت الي مشاركه واحده %{topic_link}"
topic_statuses:
archived_enabled: "هذا الموضوع مؤرشف الأن. لن تستطيع أن تعدل عليه بأي طريقة."
archived_disabled: "هذا الموضوع غير مؤرشف الأن.لم يجمد، و تستطيع أن تعدل عليه."
diff --git a/config/locales/server.bg.yml b/config/locales/server.bg.yml
index be759eb906..729da17da1 100644
--- a/config/locales/server.bg.yml
+++ b/config/locales/server.bg.yml
@@ -468,10 +468,6 @@ bg:
title: "Отметки"
xaxis: "Ден"
yaxis: "Брой нови отметки"
- starred:
- title: "Любими"
- xaxis: "Ден"
- yaxis: "Брой нови любими теми"
users_by_trust_level:
title: "Потребители с Ниво на доверие"
xaxis: "Ниво на Доверие"
diff --git a/config/locales/server.bs_BA.yml b/config/locales/server.bs_BA.yml
index 4c7368baf2..4dcff631b4 100644
--- a/config/locales/server.bs_BA.yml
+++ b/config/locales/server.bs_BA.yml
@@ -288,10 +288,6 @@ bs_BA:
title: "Bookmarks"
xaxis: "Day"
yaxis: "Number of new bookmarks"
- starred:
- title: "Starred"
- xaxis: "Day"
- yaxis: "Number of new starred topics"
users_by_trust_level:
title: "Users per Trust Level"
xaxis: "Trust Level"
diff --git a/config/locales/server.ca.yml b/config/locales/server.ca.yml
index 4764eac8a2..9ea6490138 100644
--- a/config/locales/server.ca.yml
+++ b/config/locales/server.ca.yml
@@ -139,7 +139,6 @@ ca:
continue: "Continua la discussió"
error: "Errada en incrustar"
referer: "Referent:"
- mismatch: "El referent no coincideix amb cap dels següents amfitrions:"
no_hosts: "No s'hi han establert amfitrions per inscrustar."
configure: "Configurar la incrustació"
more_replies:
@@ -598,10 +597,6 @@ ca:
title: "Preferits"
xaxis: "Dia"
yaxis: "Quantitat de nous preferits"
- starred:
- title: "Estrellat"
- xaxis: "Dia"
- yaxis: "Quantitat de nous temes estrellats"
users_by_trust_level:
title: "Persones usuàries per nivell de confiança"
xaxis: "Nivell de confiança"
@@ -1160,13 +1155,6 @@ ca:
errors:
different_topics: "Les publicacions que pertanyen a diferents temes no es poden fusionar."
different_users: "Les publicacions que pertanyen a diferents participants no es poden fusionar."
- move_posts:
- new_topic_moderator_post:
- one: "Una publicació ha estat dividida a un nou tema: %{topic_link}"
- other: "%{count} publicacions han estat dividides a un nou tema: %{topic_link}"
- existing_topic_moderator_post:
- one: "Una publicació ha estat fusionada a un tema existent: %{topic_link}"
- other: "%{count} publicacions han estat fusionades a un tema existent: %{topic_link}"
topic_statuses:
archived_enabled: "Aquest tema ara està arxivat. Està congelat i no es pot canviar de cap manera."
archived_disabled: "Aquest tema ara està desarxivat. Ja no està congelat i es pot canviar."
diff --git a/config/locales/server.cs.yml b/config/locales/server.cs.yml
index f8f60722ee..c3506ef299 100644
--- a/config/locales/server.cs.yml
+++ b/config/locales/server.cs.yml
@@ -652,10 +652,6 @@ cs:
title: "Záložky"
xaxis: "Den"
yaxis: "Počet nových záložek"
- starred:
- title: "Oblíbená"
- xaxis: "Den"
- yaxis: "Počet nových oblíbených témat"
users_by_trust_level:
title: "Uživatelé podle věrohodnosti"
xaxis: "Věrohodnost"
@@ -807,12 +803,6 @@ cs:
redirected_to_top_reasons:
new_user: "Vítejte v naší komunitě! Tohle jsou poslední populární témata."
not_seen_in_a_month: "Vítejte zprátky! Chvíli jsme se neviděli. Tohle jsou nejpopulárnější témata od vaší poslední návštěvy."
- move_posts:
- new_topic_moderator_post:
- one: "Příspěvek byl oddělen do nového tématu: %{topic_link}"
- few: "%{count} příspěvky byly odděleny do nového tématu: %{topic_link}"
- many: "%{count} příspěvky byly odděleny do nového tématu: %{topic_link}"
- other: "%{count} příspěvky byly odděleny do nového tématu: %{topic_link}"
topic_statuses:
archived_enabled: "Toto téma je archivováno. Je zmraženo a již nemůže být nijak změněno."
archived_disabled: "Toto téma je vráceno z archivu. Již není zmraženo a může být měněno."
diff --git a/config/locales/server.da.yml b/config/locales/server.da.yml
index 09768eb1e6..79a22b4879 100644
--- a/config/locales/server.da.yml
+++ b/config/locales/server.da.yml
@@ -135,7 +135,6 @@ da:
continue: "Fortsæt diskussion"
error: "Fejl ved indlejring"
referer: "Henviser:"
- mismatch: "Henviseren matcher ikke nogen af følgende værter:"
no_hosts: "Ingen værter var konfigureret til indlejring."
configure: "Konfigurér indlejring"
more_replies:
@@ -619,10 +618,6 @@ da:
title: "Bogmærker"
xaxis: "Dag"
yaxis: "Antal nye bogmærker"
- starred:
- title: "Favoritter"
- xaxis: "Dag"
- yaxis: "Antal nye favoritter"
users_by_trust_level:
title: "Brugere pr. tillidsniveau"
xaxis: "Tillidsniveau"
diff --git a/config/locales/server.de.yml b/config/locales/server.de.yml
index 827d01921b..2352196425 100644
--- a/config/locales/server.de.yml
+++ b/config/locales/server.de.yml
@@ -194,7 +194,6 @@ de:
continue: "Diskussion fortsetzen"
error: "Fehler bei der Einbettung"
referer: "Referrer:"
- mismatch: "Der Referrer entsprach keiner der folgenden Hostnamen:"
no_hosts: "Es wurden keine Hostnamen für die Einbettung konfiguriert."
configure: "Einbettung konfigurieren"
more_replies:
@@ -788,7 +787,6 @@ de:
percent: Prozent
day: Tag
post_edits:
- title: "Beitragsbearbeitungen"
labels:
post: Beitrag
editor: Bearbeiter
@@ -803,7 +801,6 @@ de:
ignored_flags: Ignorierte Meldungen
score: Wertung
moderators_activity:
- title: "Moderator-Aktivität"
labels:
moderator: Moderator
flag_count: Überprüfte Meldungen
@@ -813,7 +810,6 @@ de:
pm_count: Erstellte Nachrichten
revision_count: Revisionen
flags_status:
- title: "Meldungsstatus"
values:
agreed: Zugestimmt
disagreed: Abgelehnt
@@ -833,22 +829,18 @@ de:
title: "Neue Benutzer"
xaxis: "Tag"
yaxis: "Anzahl neuer Benutzer"
- description: "Neue Registrierungen in dieser Zeit"
new_contributors:
title: "Neue Mitwirkende"
xaxis: "Tag"
yaxis: "Anzahl neuer Mitwirkender"
- description: "Anzahl der Benutzer, die ihren ersten Beitrag in dieser Zeit erstellt haben"
dau_by_mau:
title: "TAB/MAB"
xaxis: "Tag"
yaxis: "TAB/MAB"
- description: "Anzahl der Mitglieder, die sich am vergangenen Tag angemeldet haben, geteilt durch die Anzahl der Mitglieder, die sich im letzten Monat angemeldet haben – liefert eine Prozentzahl, die das „Klebeverhalten“ der Community angibt. Erstrebenswert sind 30 % oder mehr."
daily_engaged_users:
title: "Täglich engagierte Benutzer"
xaxis: "Tag"
yaxis: "Engagierte Benutzer"
- description: "Anzahl der Benutzer, die innerhalb des letzten Tages einen Beitrag geschrieben oder mit einem Like markiert haben"
profile_views:
title: "Profilaufrufe"
xaxis: "Tag"
@@ -857,7 +849,6 @@ de:
title: "Themen"
xaxis: "Tag"
yaxis: "Anzahl neuer Themen"
- description: "Neue Themen, die in dieser Zeit erstellt wurden"
posts:
title: "Beiträge"
xaxis: "Tag"
@@ -875,10 +866,6 @@ de:
title: "Lesezeichen"
xaxis: "Tag"
yaxis: "Anzahl der Lesezeichen"
- starred:
- title: "Favoriten"
- xaxis: "Tag"
- yaxis: "Anzahl der Favoriten"
users_by_trust_level:
title: "Benutzer nach Stufe"
xaxis: "Stufe"
@@ -897,7 +884,6 @@ de:
suspended: Gesperrt
silenced: Stummgeschaltet
trending_search:
- title: Suchtrends
labels:
term: Begriff
searches: Suchen
@@ -1115,10 +1101,6 @@ de:
inline_onebox_domains_whitelist: "Eine Liste von Domains, die in verkleinerter Form in eine Onebox umgewandelt werden, wenn sie ohne Titel verlinkt werden"
enable_inline_onebox_on_all_domains: "Ignoriere die `inline_onebox_domain_whitelist`-Seiteneinstellung und erlaube Inline-Oneboxen für alle Domains"
max_oneboxes_per_post: "Maximale Anzahl von Oneboxes in einem Beitrag."
- logo: "Das Logo oben links auf deiner Site sollte eine breite, rechteckige Form haben. Wenn du kein Logo auswählst, wird stattdessen `title` angezeigt."
- logo_small: "Dein Logo in klein für die obere linke Seite deiner Site, in Form eines rechteckigen Quadrates. Es wird angezeigt, wenn der Benutzer scrollt. Wenn du dieses Feld frei lässt, wird stattdessen ein Haus-Symbol angezeigt."
- digest_logo: "Das alternative Logo oben in den E-Mail-Zusammenfassungen deiner Site. Sollte eine breite, rechteckige Form haben. Sollte kein SVG-Bild sein. Wenn leer, wird `logo_url` verwendet."
- mobile_logo: "Benutzerdefiniertes Logo für die mobile Seitenversion. Falls leer, wird `logo_url` verwendet. Beispiel: https://example.com/uploads/default/logo.png"
large_icon: "Bild wird als Logo/Startbild auf Android verwendet. Empfohlene Größe beträgt 512px mal 512px."
favicon: "Favicon für deine Seite, siehe https://de.wikipedia.org/wiki/Favicon. Damit es auch über ein CDN richtig funktioniert, muss es im PNG-Format vorliegen."
apple_touch_icon: "Icon für berührungsempfindliche Apple-Geräte. Empfohlene Grösse ist 144px auf 144px."
@@ -1685,13 +1667,6 @@ de:
errors:
different_topics: "Beiträge unterschiedlicher Themen können nicht zusammengeführt werden."
different_users: "Beiträge unterschiedlicher Benutzer können nicht zusammengeführt werden."
- move_posts:
- new_topic_moderator_post:
- one: "Ein Beitrag wurde in ein neues Thema verschoben: %{topic_link}"
- other: "%{count} Beiträge wurden in ein neues Thema verschoben: %{topic_link}"
- existing_topic_moderator_post:
- one: "Ein Beitrag wurde in ein neues Thema verschoben: %{topic_link}"
- other: "%{count} Beiträge wurden in ein neues Thema verschoben: %{topic_link}"
change_owner:
post_revision_text: "Eigentümer übertragen."
topic_statuses:
@@ -1788,7 +1763,6 @@ de:
email:
sent_test: "gesendet!"
sent_test_disabled: "kann nicht gesendet werden da E-Mails deaktiviert sind"
- sent_test_disabled_for_non_staff: "kann nicht gesendet werden da E-Mails für Nicht-Moderatoren deaktiviert sind"
user:
deactivated: "Deaktiviert wegen zu vielen unzustellbaren E-Mails an '%{email}'."
deactivated_by_staff: "Deaktiviert vom Team"
@@ -3680,10 +3654,8 @@ de:
fields:
logo:
label: "Hauptlogo"
- description: "Das Logobild für den linken, oberen Bereich deiner Seite. Verwende eine weite, rechteckige Form."
logo_small:
label: "Kompaktes Logo"
- description: "Eine kompakte Version deines Logos, das auf deiner Seite oben links angezeigt wird, wenn man herunterscollt. Verwende eine quadratische Form."
icons:
title: "Icons"
fields:
diff --git a/config/locales/server.el.yml b/config/locales/server.el.yml
index 92d7a76619..00841cb341 100644
--- a/config/locales/server.el.yml
+++ b/config/locales/server.el.yml
@@ -142,7 +142,6 @@ el:
continue: "Συνέχεια Συζήτησης"
error: "Σφάλμα Ενσωμάτωσης"
referer: "Παραπέμπων:"
- mismatch: "Ο παραπέμπων δεν ταιριάζει με κανένα από τους παρακάτω hosts:"
no_hosts: "Δεν υπάρχουν ορισμένοι hosts για ενσωμάτωση."
configure: "Ρύθμιση Ενσωμάτωσης"
more_replies:
@@ -728,10 +727,6 @@ el:
title: "Σελιδοδείκτες"
xaxis: "Ημέρα"
yaxis: "Αριθμός νέων σελιδοδικτών "
- starred:
- title: "Επιλεγμένα"
- xaxis: "Ημέρα"
- yaxis: "Αριθμός νέων νημάτων με αστερίσκο"
users_by_trust_level:
title: "Χρήστες ανάλογα με το Επίπεδο Εμπιστοσύνης"
xaxis: "Επίπεδο Εμπιστοσύνης"
@@ -1339,13 +1334,6 @@ el:
errors:
different_topics: "Δημοσιεύσεις που ανήκουν σε διαφορετικά νήματα δεν μπορούν να συγχωνευθούν."
different_users: "Δημοσιεύσεις που ανήκουν σε διαφορετικούς χρήστες δεν μπορούν να συγχωνευθούν."
- move_posts:
- new_topic_moderator_post:
- one: "Μια ανάρτηση διαχωρίστηκε σε ένα νέο νήμα: %{topic_link}"
- other: "%{count} αναρτήσεις διαχωρίσθηκαν σε ένα νέο νήμα: %{topic_link}"
- existing_topic_moderator_post:
- one: "Μία ανάρτηση συγχωνεύθηκε σε ένα υπάρχον νήμα: %{topic_link}"
- other: "%{count} αναρτήσεις συγχωνεύθηκαν σε ένα υπάρχον νήμα: %{topic_link}"
topic_statuses:
archived_enabled: "Αυτό το νήμα είναι τώρα αρχειοθετημένο. Έχει παγώσει και δεν μπορεί να τροποποιηθεί με κανένα τρόπο."
archived_disabled: "Αυτό το νήμα δεν είναι τώρα αρχειοθετημένο. Δεν είναι πια παγωμένο και μπορεί να τροποποιηθεί."
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index f3eacbe9b6..c8e16216a4 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -169,6 +169,7 @@ en:
default_categories_already_selected: "You cannot select a category used in another list."
s3_upload_bucket_is_required: "You cannot enable uploads to S3 unless you've provided the 's3_upload_bucket'."
s3_backup_requires_s3_settings: "You cannot use S3 as backup location unless you've provided the '%{setting_name}'."
+ s3_bucket_reused: "You cannot use the same bucket for 's3_upload_bucket' and 's3_backup_bucket'. Choose a different bucket or use a different path for each bucket."
conflicting_google_user_id: 'The Google Account ID for this account has changed; staff intervention is required for security reasons. Please contact staff and point them to https://meta.discourse.org/t/76575'
activemodel:
@@ -190,7 +191,7 @@ en:
error: "There was an error uploading that file. Please try again later."
topic_invite:
- failed_to_invite: "The user cannot be invited into this topic without a group membership in either one of the following groups: %{group_names}."
+ failed_to_invite: "The user cannot be invited into this topic without a group membership in either one of the following groups: %{group_names}."
user_exists: "Sorry, that user has already been invited. You may only invite a user to a topic once."
backup:
@@ -230,7 +231,7 @@ en:
continue: "Continue Discussion"
error: "Error Embedding"
referer: "Referer:"
- mismatch: "The referer did not match any of the following hosts:"
+ mismatch: "The referer was either not sent, or did not match any of the following hosts:"
no_hosts: "No hosts were set up for embedding."
configure: "Configure Embedding"
more_replies:
@@ -893,12 +894,13 @@ en:
percent: Percent
day: Day
post_edits:
- title: "Post edits"
+ title: "Post Edits"
labels:
post: Post
editor: Editor
author: Author
edit_reason: Reason
+ description: "Number of new post edits."
user_flagging_ratio:
title: "User Flagging Ratio"
labels:
@@ -907,8 +909,9 @@ en:
disagreed_flags: Disagreed flags
ignored_flags: Ignored flags
score: Score
+ description: "List of users ordered by ratio of staff response to their flags (disagreed to agreed)."
moderators_activity:
- title: "Moderators activity"
+ title: "Moderator Activity"
labels:
moderator: Moderator
flag_count: Flags reviewed
@@ -917,8 +920,9 @@ en:
post_count: Posts created
pm_count: PMs created
revision_count: Revisions
+ description: List of moderator activity including flags reviewed, reading time, topics created, posts created, private messages created, and revisions.
flags_status:
- title: "Flags status"
+ title: "Flags Status"
values:
agreed: Agreed
disagreed: Disagreed
@@ -930,39 +934,42 @@ en:
poster: Poster
flagger: Flagger
time_to_resolution: Resolution time
+ description: "List of flags' statuses including type of flag, poster, flagger, and time to resolution."
visits:
title: "User Visits"
xaxis: "Day"
yaxis: "Number of visits"
+ description: "Number of all unique user visits."
signups:
title: "Signups"
xaxis: "Day"
yaxis: "Number of signups"
- description: "New account registrations for this period"
+ description: "New account registrations for this period."
new_contributors:
title: "New Contributors"
xaxis: "Day"
yaxis: "Number of new contributors"
- description: "Number of users who made their first post during this period"
+ description: "Number of users who made their first post during this period."
dau_by_mau:
title: "DAU/MAU"
xaxis: "Day"
yaxis: "DAU/MAU"
- description: "No. of members that logged in in the last day divided by no of members that logged in in the last month – returns a % which indicates community 'stickiness'. Aim for >30%."
+ description: "Number of members that logged in in the last day divided by number of members that logged in in the last month – returns a % which indicates community 'stickiness'. Aim for >30%."
daily_engaged_users:
title: "Daily Engaged Users"
xaxis: "Day"
yaxis: "Engaged Users"
- description: "Number of users that have liked or posted in the last day"
+ description: "Number of users that have liked or posted in the last day."
profile_views:
title: "User Profile Views"
xaxis: "Day"
yaxis: "Number of user profiles viewed"
+ description: "Total new views of user profiles."
topics:
title: "Topics"
xaxis: "Day"
yaxis: "Number of new topics"
- description: "New topics created during this period"
+ description: "New topics created during this period."
posts:
title: "Posts"
xaxis: "Day"
@@ -972,24 +979,24 @@ en:
title: "Likes"
xaxis: "Day"
yaxis: "Number of new likes"
+ description: "Number of new likes."
flags:
title: "Flags"
xaxis: "Day"
yaxis: "Number of flags"
+ description: "Number of new flags."
bookmarks:
title: "Bookmarks"
xaxis: "Day"
yaxis: "Number of new bookmarks"
- starred:
- title: "Starred"
- xaxis: "Day"
- yaxis: "Number of new starred topics"
+ description: "Number of new topics and posts bookmarked."
users_by_trust_level:
title: "Users per Trust Level"
xaxis: "Trust Level"
yaxis: "Number of Users"
labels:
level: Level
+ description: "Number of users grouped by trust level."
users_by_type:
title: "Users per Type"
xaxis: "Type"
@@ -1001,40 +1008,49 @@ en:
moderator: Moderator
suspended: Suspended
silenced: Silenced
+ description: "Number of users grouped by admin, moderator, suspended, and silenced."
trending_search:
- title: Trending search
+ title: Trending Search Terms
labels:
term: Term
searches: Searches
click_through: CTR
+ description: "Most popular search terms with their click-through rates."
emails:
title: "Emails Sent"
xaxis: "Day"
yaxis: "Number of Emails"
+ description: "Number of new emails sent."
user_to_user_private_messages:
title: "User-to-User (excluding replies)"
xaxis: "Day"
yaxis: "Number of messages"
+ description: "Number of newly initiated private messages."
user_to_user_private_messages_with_replies:
title: "User-to-User (with replies)"
xaxis: "Day"
yaxis: "Number of messages"
+ description: "Number of all new private messages and responses."
system_private_messages:
title: "System"
xaxis: "Day"
yaxis: "Number of messages"
+ description: "Number of private messages sent automatically by the System."
moderator_warning_private_messages:
title: "Moderator Warning"
xaxis: "Day"
yaxis: "Number of messages"
+ description: "Number of warnings sent by private messages from moderators."
notify_moderators_private_messages:
title: "Notify Moderators"
xaxis: "Day"
yaxis: "Number of messages"
+ description: "Number of times moderators have been privately notified by a flag."
notify_user_private_messages:
title: "Notify User"
xaxis: "Day"
yaxis: "Number of messages"
+ description: "Number of times users have been privately notified by a flag."
top_referrers:
title: "Top Referrers"
xaxis: "User"
@@ -1044,6 +1060,7 @@ en:
user: "User"
num_clicks: "Clicks"
num_topics: "Topics"
+ description: "Users listed by number of clicks on links they have shared."
top_traffic_sources:
title: "Top Traffic Sources"
xaxis: "Domain"
@@ -1054,35 +1071,43 @@ en:
domain: Domain
num_clicks: Clicks
num_topics: Topics
+ description: "External sources that have linked to this site the most."
top_referred_topics:
title: "Top Referred Topics"
labels:
num_clicks: "Clicks"
topic: "Topic"
+ description: "Topics that have received the most clicks from external sources."
page_view_anon_reqs:
title: "Anonymous"
xaxis: "Day"
yaxis: "Anonymous Pageviews"
+ description: "Number of new page views by visitors not logged in to an account."
page_view_logged_in_reqs:
title: "Logged In"
xaxis: "Day"
yaxis: "Logged In Pageviews"
+ description: "Number of new pageviews from logged in users."
page_view_crawler_reqs:
title: "Web Crawlers"
xaxis: "Day"
yaxis: "Web Crawler Pageviews"
+ description: "Webcrawlers listed by number of new pageviews they have generated."
page_view_total_reqs:
title: "Pageviews"
xaxis: "Day"
yaxis: "Total Pageviews"
+ description: "Number of new pageviews from all visitors."
page_view_logged_in_mobile_reqs:
title: "Logged In Pageviews"
xaxis: "Day"
yaxis: "Mobile Logged In Pageviews"
+ description: "Number of new pageviews from users on mobile devices and logged in to an account."
page_view_anon_mobile_reqs:
title: "Anon Pageviews"
xaxis: "Day"
yaxis: "Mobile Anon Pageviews"
+ description: "Number of new pageviews from visitors on a mobile device who are not logged in."
http_background_reqs:
title: "Background"
xaxis: "Day"
@@ -1111,19 +1136,23 @@ en:
title: "Time to first response"
xaxis: "Day"
yaxis: "Average time (hours)"
+ description: "Average time (in hours) of the first response to new topics."
topics_with_no_response:
title: "Topics with no response"
xaxis: "Day"
yaxis: "Total"
+ description: "Number of new topics created that did not receive a response."
mobile_visits:
title: "User Visits (mobile)"
xaxis: "Day"
yaxis: "Number of visits"
+ description: "Number of unique users who visited using a mobile device."
web_crawlers:
title: "Web Crawler Requests"
labels:
user_agent: "User Agent"
page_views: "Pageviews"
+ description: "Number of new pageviews generated by all webcrawlers."
suspicious_logins:
title: "Suspicious Logins"
labels:
@@ -1134,12 +1163,22 @@ en:
device: Device
os: Operating System
login_time: Login Time
+ description: "Details of new logins that differ suspiciously from previous logins."
staff_logins:
- title: "Staff logins"
+ title: "Staff Logins"
labels:
user: User
location: Location
login_at: Login at
+ description: "List of admin and moderator login times with locations."
+ top_uploads:
+ title: "Top Uploads"
+ labels:
+ filename: Filename
+ extension: Extension
+ author: Author
+ filesize: File size
+ description: "List all uploads by extension, filesize and author."
dashboard:
rails_env_warning: "Your server is running in %{env} mode."
@@ -1231,10 +1270,10 @@ en:
enable_inline_onebox_on_all_domains: "Ignore inline_onebox_domain_whitelist site setting and allow inline onebox on all domains."
max_oneboxes_per_post: "Maximum number of oneboxes in a post."
- logo: "The logo image at the top left of your site, should be a wide rectangle shape. If left blank site title text will be shown."
- logo_small: "The small logo image at the top left of your site, should be a square shape, seen when scrolling down. If left blank a home glyph will be shown."
- digest_logo: "The alternate logo image used at the top of your site's email summary. Should be a wide rectangle shape. Should not be an SVG image. If left blank `logo_url` will be used."
- mobile_logo: "Custom logo url used on mobile version of your site. If left blank, `logo_url` will be used. eg: https://example.com/uploads/default/logo.png"
+ logo: "The logo image at the top left of your site. Use a wide rectangular image with a height of 120px and an aspect ratio greater than 3:1. If left blank, the site title text will be shown."
+ logo_small: "The small logo image at the top left of your site, seen when scrolling down. Use a square image. The recommended size is 120px by 120px. If left blank, a home glyph will be shown."
+ digest_logo: "The alternate logo image used at the top of your site's email summary. Use a wide rectangle image. Don't use an SVG image. If left blank, the image from the `logo` setting will be used."
+ mobile_logo: "The logo used on mobile version of your site. Use a wide rectangular image with a height of 120px and an aspect ratio greater than 3:1. If left blank, the image from the `logo` setting will be used."
large_icon: "Image used as logo/splash image on Android. Recommended size is 512px by 512px."
favicon: "A favicon for your site, see https://en.wikipedia.org/wiki/Favicon. To work correctly over a CDN it must be a png."
apple_touch_icon: "Icon used for Apple touch devices. Recommended size is 144px by 144px."
@@ -1872,6 +1911,8 @@ en:
short_title: "The short title will be used on the user's home screen, launcher, or other places where space may be limited. A maximum of 12 characters is recommended."
+ dashboard_general_tab_activity_metrics: "Choose reports to be displayed as activity metrics on the general tab."
+
errors:
invalid_email: "Invalid email address."
invalid_username: "There's no user with that username."
@@ -1947,11 +1988,11 @@ en:
move_posts:
new_topic_moderator_post:
- one: "A post was split to a new topic: %{topic_link}"
- other: "%{count} posts were split to a new topic: %{topic_link}"
+ one: "A post was split to a new %{entity}: %{topic_link}"
+ other: "%{count} posts were split to a new %{entity}: %{topic_link}"
existing_topic_moderator_post:
- one: "A post was merged into an existing topic: %{topic_link}"
- other: "%{count} posts were merged into an existing topic: %{topic_link}"
+ one: "A post was merged into an existing %{entity}: %{topic_link}"
+ other: "%{count} posts were merged into an existing %{entity}: %{topic_link}"
change_owner:
post_revision_text: "Ownership transferred"
@@ -2054,7 +2095,6 @@ en:
email:
sent_test: "sent!"
sent_test_disabled: "cannot send because emails are disabled"
- sent_test_disabled_for_non_staff: "cannot send because emails are disabled for non-staff"
user:
deactivated: "Was deactivated due to too many bounced emails to '%{email}'."
@@ -4141,10 +4181,10 @@ en:
fields:
logo:
label: "Primary Logo"
- description: "The logo image at the top left of your site. Use a wide rectangle shape."
+ description: "The logo image at the top left of your site. Use a wide rectangular image with a height of 120px and an aspect ratio greater than 3:1"
logo_small:
label: "Compact Logo"
- description: "A compact version of your logo, shown at the top left of your site when scrolling down. Use a square shape."
+ description: "A compact version of your logo, shown at the top left of your site when scrolling down. Use a square image. The recommended size is 120x120"
icons:
title: "Icons"
diff --git a/config/locales/server.es.yml b/config/locales/server.es.yml
index c321596b31..b8ed4c98a5 100644
--- a/config/locales/server.es.yml
+++ b/config/locales/server.es.yml
@@ -118,7 +118,7 @@ es:
odd: debe ser impar
record_invalid: 'Error en la validación: %{errors}'
max_emojis: "no puede tener más de %{max_emojis_count} emoji"
- emojis_disabled: "no puede tener emoji"
+ emojis_disabled: "no puede tener emojis"
ip_address_already_screened: "ya se está incluido en una regla existente"
restrict_dependent_destroy:
one: "No se pudo eliminar el registro porque existe otro %{record} dependiente"
@@ -198,7 +198,6 @@ es:
continue: "Continuar discusión"
error: "Error al insertar"
referer: "Referente:"
- mismatch: "El referente no ha coincidido con ninguno de los siguientes hosts:"
no_hosts: "No se han definido hosts para el insertado"
configure: "Configurar insertado"
more_replies:
@@ -813,12 +812,13 @@ es:
percent: Porcentaje
day: Día
post_edits:
- title: "Ediciones"
+ title: "Ediciones del mensaje"
labels:
post: Post
editor: Editor
author: Autor
edit_reason: Motivo
+ description: "Número de nuevas ediciones."
user_flagging_ratio:
title: "Proporción de reportes del usuario"
labels:
@@ -828,7 +828,7 @@ es:
ignored_flags: Reportes ignorados
score: Puntuación
moderators_activity:
- title: "Actividad de moderadores"
+ title: "Actividad de moderación"
labels:
moderator: Moderador
flag_count: Reportes revisados
@@ -838,7 +838,7 @@ es:
pm_count: Mensajes privados creados
revision_count: Ediciones
flags_status:
- title: "Estado de los reportes"
+ title: "Estado de reportes"
values:
agreed: De acuerdo
disagreed: En desacuerdo
@@ -858,22 +858,18 @@ es:
title: "Registros"
xaxis: "Día"
yaxis: "Número de Registros"
- description: "Nuevos registros de cuentas para este periodo"
new_contributors:
title: "Nuevos Colaboradores"
xaxis: "Día"
yaxis: "Número de nuevos colaboradores"
- description: "Número de usuarios quienes hicieron su primer post en este periodo"
dau_by_mau:
title: "UAD/UAM"
xaxis: "Día"
yaxis: "UAD/UAM"
- description: "Nro. de miembros que se loguearon en el último día dividido el núm. de miembros que se loguearon en el último mes – devuelve un % que indica la 'adhesión' de la comunidad. Apunta al >30%."
daily_engaged_users:
title: "Usuarios comprometidos a diario"
xaxis: "Día"
yaxis: "Usuarios Comprometidos"
- description: "Número de usuarios que le han gustado o publicado en el último día"
profile_views:
title: "Visitas a perfil de usuario"
xaxis: "Día"
@@ -882,7 +878,6 @@ es:
title: "Temas nuevos"
xaxis: "Día"
yaxis: "Número de topics nuevos"
- description: "Nuevos temas creados durante este período"
posts:
title: "Nuevos posts"
xaxis: "día"
@@ -900,10 +895,6 @@ es:
title: "Marcadores"
xaxis: "Día"
yaxis: "Número de nuevos marcadores"
- starred:
- title: "Favorito"
- xaxis: "Día"
- yaxis: "Número de temas nuevos favoritos"
users_by_trust_level:
title: "Usuarios por nivel de confianza"
xaxis: "Día"
@@ -922,7 +913,7 @@ es:
suspended: Suspendidos
silenced: Silenciados
trending_search:
- title: Trending search
+ title: Tendencias de búsqueda
labels:
term: Término
searches: Búsquedas
@@ -1054,6 +1045,12 @@ es:
device: Dispositivo
os: Sistema operativo
login_time: Fecha y hora de inicio de sesión
+ staff_logins:
+ title: "Inicios de sesión del staff"
+ labels:
+ user: Usuario
+ location: Ubicación
+ login_at: Inicio de sesión el
dashboard:
rails_env_warning: "Tu servidor está funcionando en modo de %{env}."
host_names_warning: "Tu archivo 'config/database.yml' está utilizando un hostname predeterminado de 'localhost'. Actualízalo para usar el hostname de tu sitio."
@@ -1111,6 +1108,7 @@ es:
educate_until_posts: "Cuando el usuario comienza a escribir su primer o primeros (n) posts, mostrar un pop-up con el panel de consejos para nuevos usuarios en el editor."
title: "El nombre de este sitio, utilizado en la etiqueta title."
site_description: "Describe este sitio en una frase, utilizada en la etiqueta meta description."
+ short_site_description: "Descripción breve, usada como el título de la etiqueta en la página principal."
contact_email: "Dirección de correo electrónico del responsable de este sitio. Utilizado para notificaciones críticas, así como en la página /about de contacto para asuntos urgentes."
contact_url: "URL de contacto para este sitio. Usada en la página /about como formulario de contacto para temas urgentes."
crawl_images: "Recuperar imágenes desde URLs remotas para insertarlas con las dimensiones correctas de ancho y de largo."
@@ -1140,10 +1138,6 @@ es:
inline_onebox_domains_whitelist: "Lista de dominios que serán oneboxed en miniatura si son enlazados sin un título"
enable_inline_onebox_on_all_domains: "Ignorar configuración inline_onebox_domain_whitelist site y permitir inline onebox en todos los dominios."
max_oneboxes_per_post: "Número máximo de oneboxes en un tema."
- logo: "La imagen del logotipo en la parte superior izquierda de su sitio debe ser una forma de rectángulo ancho. Si se deja en blanco el texto del título del sitio se mostrará."
- logo_small: "La pequeña imagen del logotipo en la parte superior izquierda de su sitio debe ser una forma cuadrada, que se ve cuando se desplaza hacia abajo. Si se deja en blanco, se mostrará el home glyph."
- digest_logo: "La imagen de logotipo alternativa utilizada en la parte superior del resumen de correo electrónico de su sitio. Debe ser una forma de rectángulo ancho. No debe ser una imagen SVG. Si se deja en blanco, se utilizará `logo_url`."
- mobile_logo: "URL del logo personalizado utilizado en la versión móvil de su sitio. Si se deja en blanco, se usará `logo_url`. Ej: https://example.com/uploads/default/logo.png"
large_icon: "Imagen utilizada como logotipo o imagen de bienvenida en Android. El tamaño recomendado es de 512px por 512px."
favicon: "El favicon para tu sitio, lee https://es.wikipedia.org/wiki/Favicon. Para trabajar correctamente sobre un CDN debe ser un png."
apple_touch_icon: "Icono utilizado para dispositivos táctiles de Apple. El tamaño recomendado es de 144px por 144px."
@@ -1207,6 +1201,7 @@ es:
content_security_policy_report_only: EXPERIMENTAL - Turn on Content-Security-Policy-Report-Only
content_security_policy_collect_reports: Habilitar CSP violation report collection en /csp_reports
content_security_policy_script_src: Fuentes adicionales de script en la lista blanca. El host actual y CDN se incluyen de forma predeterminada.
+ invalidate_inactive_admin_email_after_days: "Las cuentas administrativas que no hayan visitado la página en este número de días deberán validar de nuevo su dirección de correo electrónico antes de iniciar sesión. Establecer a 0 para desactivar."
top_menu: "Determinar que items aparecen en la navegación de la home y en qué orden. Ejemplo latest|new|unread|categories|top|read|posted|bookmarks"
post_menu: "Determina qué elementos aparecen en el menú de un post y en qué orden. Ejemplo: like|edit|flag|delete|share|bookmark|reply"
post_menu_hidden_items: "Los elementos del menú a ocultar por defecto en el menú de cada post a menos que se haga clic en el botón para expandir las opciones."
@@ -1216,6 +1211,7 @@ es:
send_tl1_welcome_message: "Enviar a los nuevos usuarios con nivel de confianza 1 un mensaje de bienvenida."
suppress_reply_directly_below: "No mostrar el contador de respuestas desplegable en un post si solo hay una única respuesta y está justamente debajo del post en cuestión."
suppress_reply_directly_above: "No mostrar el en-respuesta-a desplegable en un post cuando solo hay una sola respuesta justo encima del post."
+ remove_full_quote: "Quitar automáticamente citas completas en respuestas directas"
suppress_reply_when_quoting: "No mostrar el desplegable en-respuesta-a en un post cuando el post cite la respuesta."
max_reply_history: "Número máximo de respuestas a mostrar al expandir en-respuesta-a"
topics_per_period_in_top_summary: "Número de mejores temas mostrados en el resumen de mejores temas."
@@ -1390,6 +1386,7 @@ es:
title_min_entropy: "La mínima entropía permitida (caracteres únicos) requerida para el título de un tema."
body_min_entropy: "La mínima entropía permitida (caracteres únicos) requerida para el cuerpo de un post."
allow_uppercase_posts: "Permite todas mayúsculas en el título de un tema o en el cuerpo del mensaje"
+ max_consecutive_replies: "Número de mensajes que un usuario debe hacer seguidos antes de que se le prohíba añadir una nueva respuesta."
min_title_similar_length: "La extensión mínima que un título debe tener antes de revisar temas similares."
desktop_category_page_style: "Estilo visual de la página de /categorías."
category_colors: "Una lista de valores en hexadecimal de los colores para las categorías."
@@ -1616,6 +1613,7 @@ es:
min_trust_level_to_tag_topics: "Nivel mínimo requerido para etiquetar temas"
suppress_overlapping_tags_in_list: "Si alguna etiqueta coinciden con palabras en el título de los temas, ocultarla."
remove_muted_tags_from_latest: "No mostrar temas con etiquetas silenciadas en la lista de temas recientes."
+ force_lowercase_tags: "Forzar que todas las etiquetas sean completamente en minúscula"
company_name: "Nombre de Compañía"
governing_law: "Ley que rige"
city_for_disputes: "Ciudad de Disputas"
@@ -1682,13 +1680,6 @@ es:
errors:
different_topics: "Mensajes que pertenecen a temas diferentes no pueden ser juntados."
different_users: "Mensajes que pertenecen a diferentes usuarios no pueden ser juntados."
- move_posts:
- new_topic_moderator_post:
- one: "un post fue trasladado a un nuevo tema: %{topic_link}"
- other: "%{count} posts fueron trasladados a un nuevo tema: %{topic_link}"
- existing_topic_moderator_post:
- one: "Un post fue trasladado al siguiente tema: %{topic_link}"
- other: "%{count} posts fueron trasladados al siguiente tema: %{topic_link}"
change_owner:
post_revision_text: "Se ha transferido la propiedad"
topic_statuses:
@@ -2109,6 +2100,13 @@ es:
csv_export_succeeded:
title: "Exportación de datos en CSV completada con éxito"
subject_template: "[%{export_title}] exportación de datos completa"
+ text_body_template: |
+ ¡La exportación de los datos ha finalizado correctamente! :dvd:
+
+ %{file_name} (%{file_size})
+ El enlace de arriba funcionará durante las próximas 48 horas.
+
+ Los datos han sido comprimidos en un archivo gzip. Si no consigues extraerlo al abrirlo, usa las herramientas que encontrarás aquí: https://www.gzip.org/#faq4
csv_export_failed:
title: "Error en la exportación de datos en CSV"
subject_template: "La exportación de datos falló"
@@ -2233,6 +2231,10 @@ es:
email_reject_post_too_short:
title: "Emails Rechazados Post muy corto"
subject_template: "[%{email_prefix}] Problemas con el correo -- Post muy corto"
+ text_body_template: |
+ Lo sentimos, pero tu correo para %{destination} (con título %{former_title}) no ha funcionado.
+
+ Para asegurar la calidad de las conversaciones, las respuestas muy cortas no están permitidas. ¿Podrías responder con al menos %{count} caracteres? Alternativamente, puedes darle me gusta a un mensaje respondiendo por email con "+1".
email_reject_invalid_post_action:
title: "Email Rechazado, Acción de Post Inválida"
subject_template: "[%{email_prefix}] Problema con el correo -- Acción de Post Inválida"
@@ -2266,6 +2268,10 @@ es:
email_reject_old_destination:
title: "Correo electrónico rechazo antiguo destino"
subject_template: "[%{email_prefix}] Problema con el correo -- Estás intentando responder a una notificación antigua"
+ text_body_template: |
+ Lo sentimos, pero tu correo para %{destination} (titulado %{former_title}) no ha funcionado.
+
+ Solo aceptamos respuestas a las notificaciones originales durante %{number_of_days} días. Por favor, [visita el tema](%{short_url}) para seguir con la conversación.
email_reject_topic_not_found:
title: "Email rechazado, tema no encontrado"
subject_template: "[%{email_prefix}] Problema con el correo -- Tema no encontrado"
@@ -2309,6 +2315,13 @@ es:
email_reject_attachment:
title: "Adjuntos del Email Rechazados"
subject_template: "[%{email_prefix}] Problema con el correo -- Adjuntos rechazados"
+ text_body_template: |
+ Lamentablemente, algunos archivos adjuntos de tu correo a %{destination} (titulado %{former_title}) han sido rechazados.
+
+ Detalles:
+ %{rejected_errors}
+
+ Si crees que se trata de un error, [contacta con un miembro del staff](%{base_url}/about).
email_error_notification:
title: "Email, error de notificación"
subject_template: "[%{email_prefix}] Problema con el correo -- Error de autenticación POP"
@@ -3770,6 +3783,9 @@ es:
site_description:
label: "Describe tu comunidad en una frase corta"
placeholder: "Un sitio para Jane y sus amigos donde discutir cosas interesantes"
+ short_site_description:
+ label: "Describe tu comunidad en pocas palabras"
+ placeholder: "La mejor comunidad del mundo"
introduction:
title: "Introducción"
fields:
@@ -3831,10 +3847,8 @@ es:
fields:
logo:
label: "Logo Principal"
- description: "La imagen del logotipo en la parte superior izquierda de su sitio. Usa una forma de rectángulo ancho."
logo_small:
label: "Logo Compacto"
- description: "Una versión compacta de su logotipo, que se muestra en la parte superior izquierda de su sitio cuando se desplaza hacia abajo. Usa una forma cuadrada."
icons:
title: "Iconos"
fields:
diff --git a/config/locales/server.et.yml b/config/locales/server.et.yml
index eebb293726..e9e1852dd7 100644
--- a/config/locales/server.et.yml
+++ b/config/locales/server.et.yml
@@ -130,7 +130,6 @@ et:
continue: "Jätka arutelu"
error: "Sängitamine ebaõnnestus"
referer: "Sissetulev link:"
- mismatch: "Sissetulev link ei kattu ühegagi järgmistest hostidest:"
no_hosts: "Sängitamiseks pole ühtegi hosti seadistatud."
configure: "Sängitamise seadistamine"
more_replies:
@@ -547,13 +546,11 @@ et:
percent: Protsent
day: Päev
post_edits:
- title: "Postituse muutmised"
labels:
post: Postitus
author: Autor
edit_reason: Põhjus
moderators_activity:
- title: "Modereerimise tegevus"
labels:
moderator: Moderaator
time_read: Lugemise aeg
@@ -604,8 +601,6 @@ et:
title: "Järjehoidjad"
xaxis: "Päev"
yaxis: "Uute järjehoidjate arv"
- starred:
- xaxis: "Päev"
users_by_trust_level:
xaxis: "Usaldustase"
yaxis: "Kasutajate arv"
diff --git a/config/locales/server.fa_IR.yml b/config/locales/server.fa_IR.yml
index ef4681fc0b..8e8cd74927 100644
--- a/config/locales/server.fa_IR.yml
+++ b/config/locales/server.fa_IR.yml
@@ -136,7 +136,6 @@ fa_IR:
continue: "ادامهی بحث"
error: "خطایی در جاسازی رخ داده است"
referer: "ارجاع دهنده:"
- mismatch: "ارجاع دهنده با هیچ یک از میزبانهای زیر همخوانی ندارد:"
no_hosts: "میزبانی برای قرارگیری فایلها تعبیه نشده."
configure: "پیکربندی کدهای جاسازی شده"
more_replies:
@@ -642,10 +641,6 @@ fa_IR:
title: "نشانکها"
xaxis: "روز"
yaxis: "تعداد نشانکهای جدید"
- starred:
- title: "ستاره دار شد"
- xaxis: "روز"
- yaxis: "تعداد موضوعات تازه آغاز شده"
users_by_trust_level:
title: "کاربران بر اساس سطحاعتماد"
xaxis: "سطحاعتماد"
@@ -1219,13 +1214,6 @@ fa_IR:
errors:
different_topics: "نوشتهها به موضوعات مختلفی تعلق دارند و امکان ترکیب آنها وجود ندارد."
different_users: "نوشتهها به کاربران مختلفی تعلق دارند و امکان ترکیب آنها وجود ندارد."
- move_posts:
- new_topic_moderator_post:
- one: "%{count} نوشته تبدیل به یک موضوع جدید شدند: %{topic_link}"
- other: "%{count} نوشته تبدیل به یک موضوع جدید شدند: %{topic_link}"
- existing_topic_moderator_post:
- one: "%{count} نوشته با یک موضوع موجود ترکیب شدند: %{topic_link}"
- other: "%{count} نوشته با یک موضوع موجود ترکیب شدند: %{topic_link}"
topic_statuses:
archived_enabled: "این موضوع بایگانی شده است. غیر قابل دسترسی است و قادر به تغییر آن نیستید."
archived_disabled: "این موضوع الان از بایگانی خارج شد.این دیگر غیر قابل دسترسی نیست و قادر به عوض شدن است."
@@ -2091,7 +2079,7 @@ fa_IR:
name: الههی مهر
description: در 20 روز، روزانه 50 نوشته را پسندیده است.
thank_you:
- name: سپاسگذاریم
+ name: سپاسگزاریم
description: 20 نوشته پسندیده شده دارد و 10 نوشته را پسندیده است.
long_description: |
این مدال زمانی به شما تعلق میگیرد که نوشتههایتان 20 بار پسندیده شده باشد و شما هم 10 نوشته را بپسندید. وقتی شما نوشتههای دیگری را میپسندید زمان پسندیدن نوشتههای سایرین را نیز پیدای میکنید.
diff --git a/config/locales/server.fi.yml b/config/locales/server.fi.yml
index fc969c1d34..ad17ddcac4 100644
--- a/config/locales/server.fi.yml
+++ b/config/locales/server.fi.yml
@@ -147,6 +147,7 @@ fi:
default_categories_already_selected: "Et voi valita aluetta, joka on käytössä toisella listalla"
s3_upload_bucket_is_required: "Et voi ottaa s3 latausta käyttöön, jos et ole määrittänyt 's3_upload_bucket'."
s3_backup_requires_s3_settings: "Et voi käyttää S3:a varmuuskopiosijaintina, jos %{setting_name} ei ole määritetty."
+ s3_bucket_reused: "Sama säiliö ei voi olla sekä 's3_upload_bucket' että 's3_backup_bucket'. Valitse eri säiliö tai määritä eri polku jokaiselle säiliölle."
conflicting_google_user_id: 'Tämän käyttäjätilin Google Account ID on muuttunut. Henkilökunnan toimenpiteet ovat tarpeen tietoturvasyistä. Ota yhteyttä henkilökuntaan ja ohjaa heidät osoitteeseen https://meta.discourse.org/t/76575'
activemodel:
errors:
@@ -198,7 +199,7 @@ fi:
continue: "Siirry keskusteluun"
error: "Virhe upotettaessa"
referer: "Referer-viite:"
- mismatch: "Referer-viite ei täsmää mihinkään näistä isännistä:"
+ mismatch: "Referer-viitettä ei lähetetty tai se ei täsmää mihinkään näistä isännistä:"
no_hosts: "Isäntiä ei ole määritetty upotusta varten."
configure: "Määritä upottaminen"
more_replies:
@@ -240,6 +241,7 @@ fi:
not_accepting_pms: "Pahoittelut, %{username} ei ota vastaan yksityisviestejä tällä hetkellä."
max_pm_recepients: "Pahoittelut, voit lähettää viestin enintään %{recipients_limit} vastaanottajalle."
pm_reached_recipients_limit: "Pahoittelut, yksityisviestillä ei voi olla yli %{recipients_limit} vastaanottajaa."
+ removed_direct_reply_full_quotes: "Jos edellinen viesti lainataan kokonaan, poista lainaus automaattisesti."
just_posted_that: "on liian samanlainen kuin aiempi viestisi"
invalid_characters: "sisältää epäkelpoja merkkejä"
is_invalid: "vaikuttaa epäselvältä, olihan se kokonainen virke?"
@@ -813,12 +815,13 @@ fi:
percent: Prosenttia
day: Päivä
post_edits:
- title: "Viestin muokkauksia"
+ title: "Viestimuokkaukset"
labels:
post: Viesti
editor: Muokkaaja
author: Kirjoittaja
edit_reason: Syy
+ description: "Kuinka monesti viestejä muokattiin."
user_flagging_ratio:
title: "Käyttäjän liputussuhde"
labels:
@@ -827,8 +830,9 @@ fi:
disagreed_flags: Hylättyjä lippuja
ignored_flags: Sivuutettuja lippuja
score: Luku
+ description: "Luettelo käyttäjistä järjestettynä sen mukaan, miten henkilökunta suhtautuu heidän liputuksiinsa (hylätyt jaettuna hyväksytyillä)."
moderators_activity:
- title: "Valvojien toiminta"
+ title: "Valvoja-aktiivisuus"
labels:
moderator: Valvoja
flag_count: Arvioituja lippuja
@@ -837,8 +841,9 @@ fi:
post_count: Kirjoitetut viestit
pm_count: Kirjoitetut yksityisviestit
revision_count: Revisiot
+ description: "Luettelo valvojien aktiivisuudesta, sisältäen käsitellyt liput, lukuajan, luodut ketjut, lähetetyt viestit, lähetetyt yksityisviestit ja tarkastukset."
flags_status:
- title: "Lippujen tilanne"
+ title: "Lippujen status"
values:
agreed: Samaa mieltä
disagreed: Eri mieltä
@@ -850,39 +855,42 @@ fi:
poster: Kirjoittaja
flagger: Liputtaja
time_to_resolution: Selvitysaika
+ description: "Luettelo lippujen statuksista sisältäen lippujen tyypit, kirjoittajat, liputtajat ja selvitysajat."
visits:
title: "Vierailut"
xaxis: "Päivä"
yaxis: "Vierailujen määrä"
+ description: "Uniikin kävijäkerrat"
signups:
title: "Liittymiset"
xaxis: "Päivä"
yaxis: "Liittymisten lukumäärä"
- description: "Uusien tunnusten luonnit tänä aikana"
+ description: "Uudet rekisteröidyt käyttäjät tällä ajanjaksolla."
new_contributors:
title: "Uusia kirjoittajia"
xaxis: "Päivä"
yaxis: "Uusien kirjoittajien lukumäärä"
- description: "Kuinka moni käyttäjä kirjoitti ensimmäisen viestinsä tänä aikana"
+ description: "Kuinka moni kirjoitti ensimmäisen viestinsä tällä ajanjaksolla."
dau_by_mau:
- title: "DAU/MAU"
+ title: "PAK/KAK"
xaxis: "Päivä"
- yaxis: "Päivittäin aktiivisten käyttäjien osuus kuukausittain aktiivisista"
- description: "Viimeisenä päivänä kirjautuneiden käyttäjien lukumäärän suhde viimeisen kuukauden aikana kirjautuneisiin. Tämä prosenttiluku mittaa yhteisön pysyvyyttä. Sopiva tavoite: >30%."
+ yaxis: "PAK/KAK"
+ description: "Käyttäjät jotka kirjautuivat viimeisen päivän aikana jaettuna käyttäjillä, jotka kirjautuivat kuukauden aikana - tuottaa %-luvun joka kuvastaa yhteisön \"tarttuvuutta\". Tähtää yli 30 %:iin."
daily_engaged_users:
title: "Sitoutuneet päivittäiskäyttäjät"
xaxis: "Päivä"
yaxis: "Sitoutuneet käyttäjät"
- description: "Viimeisen päivän aikana tykänneiden tai kirjoittaneiden käyttäjien lukumäärä"
+ description: "Kuinka moni tykkäsi tai kirjoitti viimeisen päivän aikana."
profile_views:
title: "Käyttäjäprofiilin katselut"
xaxis: "Päivä"
yaxis: "Katseltujen käyttäjäprofiilien määrä"
+ description: "Uudet käyttäjäprofiilikatselut."
topics:
title: "Ketjut"
xaxis: "Päivä"
yaxis: "Uusien ketjujen lukumäärä"
- description: "Tänä aikana aloitettujen ketjujen määrä"
+ description: "Kuinka monta ketjua aloitettiin tällä ajanjaksolla."
posts:
title: "Viestit"
xaxis: "Päivä"
@@ -892,24 +900,24 @@ fi:
title: "Tykkäyksiä"
xaxis: "Päivä"
yaxis: "Uusien tykkäysten lukumäärä"
+ description: "Uudet tykkäykset."
flags:
title: "Liputukset"
xaxis: "Päivä"
yaxis: "Liputusten lukumäärä"
+ description: "Uudet liputukset."
bookmarks:
title: "Kirjanmerkit"
xaxis: "Päivä"
yaxis: "Uusien kirjanmerkkien lukumäärä"
- starred:
- title: "Tähdet"
- xaxis: "Päivä"
- yaxis: "Tähdellisten ketjujen lukumäärä"
+ description: "Kuinka monta ketjua tai viestiä lisättiin kirjanmerkkeihin."
users_by_trust_level:
title: "Käyttäjiä luottamustasoilla"
xaxis: "Luottamustaso"
yaxis: "Käyttäjien lukumäärä"
labels:
level: Taso
+ description: "Kuinka monta käyttäjää on milläkin luottamustasolla."
users_by_type:
title: "Käyttäjät tyypeittäin"
xaxis: "Tyyppi"
@@ -921,40 +929,49 @@ fi:
moderator: Valvoja
suspended: Hyllytetty
silenced: Hiljennetty
+ description: "Kuinka monta käyttäjää on ylläpitäjinä, valvojina, hyllytettynä ja hiljennettynä."
trending_search:
- title: Kuumat haut
+ title: Nousussa olevat hakusanat
labels:
term: Hakusana
searches: Hakuja
click_through: KS
+ description: "Suosituimmat hakusanat ja niiden klikkaussuhteet."
emails:
title: "Lähetettyjä sähköposteja"
xaxis: "Päivä"
yaxis: "Sähköpostien lukumäärä"
+ description: "Kuinka monta uutta sähköpostia lähetetty."
user_to_user_private_messages:
title: "Käyttäjältä käyttäjälle (pl. vastaukset)"
xaxis: "Päivä"
yaxis: "Viestien lukumäärä"
+ description: "Kuinka monta yksityiskeskustelua aloitettu."
user_to_user_private_messages_with_replies:
title: "Käyttäjältä käyttäjälle (ml. vastaukset)"
xaxis: "Päivä"
yaxis: "Viestien määrä"
+ description: "Kuinka monta uutta yksityisviestiä."
system_private_messages:
title: "Järjestelmä"
xaxis: "Päivä"
yaxis: "Viestien lukumäärä"
+ description: "Kuinka monta yksityisviestiä systeemi on lähettänyt automaattisesti."
moderator_warning_private_messages:
title: "Varoitus valvojalle"
xaxis: "Päivä"
yaxis: "Viestien lukumäärä"
+ description: "Kuinka monta yksityistä varoitusta valvojat ovat lähettäneet."
notify_moderators_private_messages:
title: "Ilmoita valvojille"
xaxis: "Päivä"
yaxis: "Viestien lukumäärä"
+ description: "Kuinka monta kertaa valvojia on yksityisesti huomautettu lipuista."
notify_user_private_messages:
title: "Ilmoita käyttäjälle"
xaxis: "Päivä"
yaxis: "Viestien lukumäärä"
+ description: "Kuinka monesti käyttäjiä huomautettiin yksityisesti lipuista."
top_referrers:
title: "Parhaat viittaajat"
xaxis: "Käyttäjä"
@@ -964,6 +981,7 @@ fi:
user: "Käyttäjä"
num_clicks: "Klikkauksia"
num_topics: "Ketjuja"
+ description: "Käyttäjät järjestettynä sen mukaan, montako kertaa heidän jakamiaan linkkejä on klikattu."
top_traffic_sources:
title: "Parhaat liikenteen lähteet"
xaxis: "Verkkotunnus"
@@ -974,35 +992,43 @@ fi:
domain: Verkkotunnus
num_clicks: Klikkaukset
num_topics: Ketjut
+ description: "Ulkoiset lähteet jotka ovat linkittäneet tälle sivustolle eniten."
top_referred_topics:
title: "Parhaat viitatut ketjut"
labels:
num_clicks: "Klikkaukset"
topic: "Ketju"
+ description: "Ketjut jotka ovat saaneet eniten klikkauksia ulkoisilta sivustoilta."
page_view_anon_reqs:
title: "Anonyymejä"
xaxis: "Päivä"
yaxis: "Anonyymien sivunlataukset"
+ description: "Kuinka monta sivunkatselua kirjautumattomilta vierailta."
page_view_logged_in_reqs:
title: "Kirjautuneita"
xaxis: "Päivä"
- yaxis: "Kirjautuneiden sivunlataukset"
+ yaxis: "Kirjautuneiden sivukatselut"
+ description: "Kuinka monta sivunkatselua kirjautuneilta käyttäjiltä."
page_view_crawler_reqs:
title: "Hakurobotteja"
xaxis: "Päivä"
yaxis: "Hakurobottien sivunlataukset"
+ description: "Hakurobotit järjestettynä sivukatseluiden mukaan."
page_view_total_reqs:
title: "Sivunkatselua"
xaxis: "Päivä"
yaxis: "Sivunlatauksia yhteensä"
+ description: "Kaikkien kävijöiden sivunkatselut."
page_view_logged_in_mobile_reqs:
title: "Kirjautuneiden sivunlataukset"
xaxis: "Päivä"
yaxis: "Mobiilikäyttäjien sivunlataukset"
+ description: "Kirjautuneiden mobiilikäyttäjien sivunkatselut."
page_view_anon_mobile_reqs:
title: "Anonyymien sivunlataukset"
xaxis: "Päivä"
yaxis: "Anonyymien mobiilikäyttäjien sivunlataukset"
+ description: "Kirjautumattomien vieraiden mobiilikäyttäjien sivunkatselut."
http_background_reqs:
title: "Taustajärjestelmä"
xaxis: "Päivä"
@@ -1031,19 +1057,23 @@ fi:
title: "Aika ensimmäiseen vastaukseen"
xaxis: "Päivä"
yaxis: "Keskimääräinen aika (tuntia)"
+ description: "Keskimääräinen aika (tunteina) siihen, kun uuteen ketjuun vastataan ensimmäisen kerran."
topics_with_no_response:
title: "Ketjut, joihin ei ole vastattu"
xaxis: "Päivä"
yaxis: "Yhteensä"
+ description: "Kuinka moneen uuteen ketjuun ei vastattu ollenkaan."
mobile_visits:
title: "Käyttäjävierailut (mobiili)"
xaxis: "Päivä"
yaxis: "Vierailujen määrä"
+ description: "Uniikkien mobiilikävijöiden määrä."
web_crawlers:
title: "Hakurobottien pyynnöt"
labels:
user_agent: "Käyttäjäagentti"
page_views: "Sivunkatselut"
+ description: "Hakurobottien sivunkatselut."
suspicious_logins:
title: "Epäilyttävät kirjautumiset"
labels:
@@ -1054,6 +1084,22 @@ fi:
device: Laite
os: Käyttöjärjestelmä
login_time: Kirjautumisaika
+ description: "Yksityiskohdat uusista kirjautumisista, jotka epäilyttävällä tavalla poikkesivat aiemmista kirjautumisista."
+ staff_logins:
+ title: "Henkilökunnan kirjautumiset"
+ labels:
+ user: Käyttäjä
+ location: Paikka
+ login_at: Kirjautumisaika
+ description: "Luettelu ylläpitäjien ja valvojien kirjautumisista sekä niiden ajoista ja paikoista."
+ top_uploads:
+ title: "Parhaat lataukset"
+ labels:
+ filename: Tiedostonimi
+ extension: Tiedostopääte
+ author: Käyttäjä
+ filesize: Tiedoston koko
+ description: "Luettelo kaikista latauksista järjestettynä tiedostopäätteen, tiedoston koon ja käyttäjän mukaan."
dashboard:
rails_env_warning: "Palvelintasi ajetaan %{env} moodissa."
host_names_warning: "Sivuston config/database.yml tiedosto käyttää oletusisäntänimeä. Päivitä se käyttämään sivuston isäntänimeä."
@@ -1061,6 +1107,14 @@ fi:
sidekiq_warning: 'Sidekiq ei ole käynnissä. Monet tehtävät, kuten sähköpostien lähettäminen, suoritetaan asynkronisesti sidekiqin avulla. Varmista, että vähintään yksi sidekiq prosessi on käynnissä. Opiskele lisää Sidekiqista täältä.'
queue_size_warning: 'Jonossa olevien tehtävien määrä on %{queue_size}, joka on korkea. Tämä voi olla merkki ongelmista Sidekiq prosess(e)issa tai sinun voi täytyä lisätä Sidekiq workerien määrää.'
memory_warning: 'Palvelimella on alle 1GB muistia. Vähintään 1 GB muistia on suositeltavaa.'
+ google_oauth2_config_warning: 'Palvelin on konfiguroitu hyväksymään liittyminen ja kirjautuminen Google OAuth2:n kautta (enable_google_oauth2_logins), mutta client id- ja client secret -arvoja ei ole asetettu. Päivitä arvot sivuston asetuksissaVoit lukea lisää tästä oppaasta.'
+ facebook_config_warning: 'Palvelin on konfiguroitu hyväksymään liittyminen ja kirjautuminen Facebookin kautta (enable_facebook_logins), mutta app id- ja app secret -arvoja ei ole asetettu. Päivitä arvot sivuston asetuksissa. Voit lukea lisää tästä oppaasta.'
+ twitter_config_warning: 'Palvelin on konfiguroitu hyväksymään liittyminen ja kirjautuminen Twitterin kautta (enable_twitter_logins), mutta key- ja secret-salausarvoja ei ole asetettu. Päivitä arvot sivuston asetuksissa. Voit lukea lisää tästä oppaasta.'
+ github_config_warning: 'Palvelin on konfiguroitu hyväksymään liittyminen ja kirjautuminen GitHubin kautta (enable_github_logins), mutta client id- ja app secret -arvoja ei ole asetettu. Päivitä arvot sivuston asetuksissa. Voit lukea lisää tästä oppaasta.'
+ s3_config_warning: 'Palvelin on konfiguroitu tallentamaan sivustolle ladatut tiedostot S3:een, mutta vähintään yksi tiedoista on asettamatta: s3_access_key_id, s3_secret_access_key, s3_use_iam_profile tai s3_upload_bucket. Päivitä ne sivuston asetuksissa. Lisätietoa englanninkielisestä ketjusta "How to set up image uploads to S3?".'
+ s3_backup_config_warning: 'Palvelin on konfiguroitu tallentamaan varmuuskopiot S3:een, mutta vähintään yksi tiedoista on asettamatta: s3_access_key_id, s3_secret_access_key tai s3_upload_bucket . Päivitä ne sivuston asetuksissa. Lisätietoa englanninkielisestä ketjusta "How to set up image uploads to S3?".'
+ image_magick_warning: 'Palvelin on konfiguroitu luomaan esikatselukuvia suurista kuvista, mutta ImageMagickia ei ole asennettu. Asenna ImageMagick paketinhallinnasta tai lataa uusin versio.'
+ failing_emails_warning: '%{num_failed_jobs} sähköpostitehtävää on epäonnistunut. Tarkista app.yml ja varmista, että sähköpostipalvelimen asetukset ovat kunnossa. Katsele epäonnistuneita tehtäviä Sidekiqissa.'
subfolder_ends_in_slash: "Alihakemiston asetuksesi ei kelpaa; DISCOURSE_RELATIVE_URL_ROOT päättyy vinoviivaan."
email_polling_errored_recently:
one: "Sähköpostin pollaus on aiheuttanut virheen edellisen 24 tunnin aikana. Tarkastele lokeja saadaksesi lisätietoja."
@@ -1103,6 +1157,7 @@ fi:
educate_until_posts: "Näytä uuden käyttäjän ohje, kun käyttäjä alkaa kirjoittamaan ensimmäistä (n) viestiään viestikenttään."
title: "Palstan nimi, käytetään title tagissa."
site_description: "Kuvaile sivustoa yhdellä lauseella, jota käytetään meta description tagissa."
+ short_site_description: "Lyhyt kuvaus, jota käytetään etusivulla otsikkotagissa (title tag)."
contact_email: "Sivustosta vastaavan henkilön sähköpostiosoite. Siihen lähetetään kriittiset ilmoitukset ja se näkyy /about-sivulla kiireellisiä yhteydenottoja varten."
contact_url: "Verkko-osoite, jonka kautta voi ottaa yhteyttä sivustoon. Näkyy /about-sivulla kiireellisiä yhteydenottoja varten."
crawl_images: "Lataa linkatut kuvat kuvan dimensioiden määrittamiseksi."
@@ -1131,6 +1186,13 @@ fi:
inline_onebox_domains_whitelist: "Verkko-osoitteet, joista luodaan Onebox-esikatselu, jos niihin linkitetään määrittämättä otsikkoa."
enable_inline_onebox_on_all_domains: "Poista inline_onebox_domain_whitelist -sivustoasetus käytöstä ja salli rivi-oneboxit kaikista verkko-osoitteista."
max_oneboxes_per_post: "Oneboxien enimmäismäärä yhdessä viestissä"
+ logo_small: "Kuva, joka toimii sivuston pienenä logona sivuston yläkulmassa, kun vieritetään alaspäin. Valitse neliönmuotoinen kuva. Suositeltu koko on 120px kertaa 120px. Jos jätät tyhjäksi, tilalla näytetään koti-merkki."
+ digest_logo: "Vaihtoehtoinen logo, jota käytetään sivustosi sähköpostitiivistelmien yläosassa. Valitse leveä suorakulmainen kuva. Älä käytä SVG-kuvaa. Jos jätät tyhjäksi, käytetään \"logo\"-asetuksen kuvaa."
+ large_icon: "Kuva, jota käytetään logo/splash -kuvana Androidissa. Suositeltu koko on 512x512 pikseliä."
+ favicon: "Palstan favicon, ks. https://fi.wikipedia.org/wiki/Favicon. Täytyy olla png, jotta toimii CDN:n kanssa."
+ apple_touch_icon: "Applen kosketuslaitteiden käyttämä ikoni. Suositeltu koko on 144px kertaa 144px."
+ opengraph_image: "Oletuksena käytettävä opengraph-kuva, käytetään kun sivulla ei ole muuta sopivaa kuvaa eikä sivuston logoa."
+ twitter_summary_large_image: "Oletuksena käytettävän Twitter-tiivistelmäkortin kuva (tulisi olla ainakin 280 px leveä ja ainakin 150 px korkea)."
notification_email: "Sähköpostiosoite, josta kaikki tärkeät järjestelmän lähettämät sähköpostiviestit lähetetään. Verkkotunnuksen SPF, DKIM ja reverse PTR tietueiden täytyy olla kunnossa, jotta sähköpostit menevät perille."
email_custom_headers: "Pystyviivalla eroteltu lista mukautetuista sähköpostin tunnisteista"
force_https: "Pakota sivusto käyttämään vain HTTPS:ää. VAROITUS: älä ota tätä käyttöön ennen kuin HTTPS on täysin käytössä ja toimii täysin kaikkialla! Tarkastitko käyttämäsi CDN, kaikki sosiaaliset kirjautumiset ja kaikki ulkoiset logot / muut riippuvuudet ovat myös HTTPS-yhteensopivia?"
@@ -1629,13 +1691,6 @@ fi:
errors:
different_topics: "Eri ketjuissa olevia viestejä ei voi yhdistää."
different_users: "Eri käyttäjien viestejä ei voi yhdistää."
- move_posts:
- new_topic_moderator_post:
- one: "Yksi viesti siirrettiin uuteen ketjuun: %{topic_link}"
- other: "%{count} viestiä siirrettiin uuteen ketjuun: %{topic_link}"
- existing_topic_moderator_post:
- one: "Yksi viesti siirrettiin toiseen ketjuun: %{topic_link}"
- other: "%{count} viestiä siirrettiin toiseen ketjuun: %{topic_link}"
change_owner:
post_revision_text: "Vaihtoi omistajaa"
topic_statuses:
@@ -1732,7 +1787,6 @@ fi:
email:
sent_test: "lähetettiin!"
sent_test_disabled: "ei voida lähettää, koska sähköpostien lähettäminen ei ole käytössä"
- sent_test_disabled_for_non_staff: "ei voida lähettää, koska sähköposteja ei lähetetä muille kuin henkilökunnalle"
user:
deactivated: "Käyttäjätili poistettiin käytöstä osoitteesta '%{email}' palautettujen sähköpostien vuoksi."
deactivated_by_staff: "Henkilökunta poisti käytöstä"
diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml
index c2d60b620f..ce0ab91123 100644
--- a/config/locales/server.fr.yml
+++ b/config/locales/server.fr.yml
@@ -198,7 +198,6 @@ fr:
continue: "Continuer la discussion"
error: "Erreur d'intégration"
referer: "Référent :"
- mismatch: "Le référent ne correspondait à aucun des hôtes suivants :"
no_hosts: "Aucun hôte n'a été configuré pour l'intégration."
configure: "Configurer l'intégration"
more_replies:
@@ -879,7 +878,6 @@ fr:
percent: Pourcentage
day: Jour
post_edits:
- title: "Modifications de message"
labels:
post: Message
editor: Éditeur
@@ -894,7 +892,6 @@ fr:
ignored_flags: Signalements ignorés
score: Score
moderators_activity:
- title: "Activité des modérateurs"
labels:
moderator: Modérateur
flag_count: Signalements examinés
@@ -904,7 +901,6 @@ fr:
pm_count: Messages privés créés
revision_count: Révisions
flags_status:
- title: "Statut des signalements"
values:
agreed: Acceptés
disagreed: Rejetés
@@ -924,22 +920,18 @@ fr:
title: "Inscriptions"
xaxis: "Jour"
yaxis: "Nombre d'inscriptions"
- description: "Nouvelles créations de compte pour cette période"
new_contributors:
title: "Nouveaux contributeurs"
xaxis: "Jour"
yaxis: "Nombre de nouveaux contributeurs"
- description: "Nombre d'utilisateurs qui ont fait leur première contribution durant cette période"
dau_by_mau:
title: "DAU/MAU"
xaxis: "Jour"
yaxis: "DAU/MAU"
- description: "Nombre d'utilisateurs qui se sont connectés dans la dernière journée, divisé par le nombre d'utilisateurs qui se sont connectés dans le dernier mois – en % qui représente le taux \"d'adhérence\" de la communauté. Viser > 30%."
daily_engaged_users:
title: "Utilisateurs impliqués au quotidien"
xaxis: "Jour"
yaxis: "Utilisateurs impliqués"
- description: "Nombre d'utilisateurs ayant aimé ou publié un message dans les derniers 24h"
profile_views:
title: "Vues des profils d'utilisateurs"
xaxis: "Jour"
@@ -948,7 +940,6 @@ fr:
title: "Sujets"
xaxis: "Jour"
yaxis: "Nombre de nouveaux sujets"
- description: "Nouveaux sujets crées pour cette période"
posts:
title: "Messages"
xaxis: "Jour"
@@ -966,10 +957,6 @@ fr:
title: "Signets"
xaxis: "Jour"
yaxis: "Nombre de nouveaux signets"
- starred:
- title: "Favoris"
- xaxis: "Jour"
- yaxis: "Nombre de nouveaux sujets favoris"
users_by_trust_level:
title: "Utilisateurs par niveau de confiance"
xaxis: "Niveau de confiance"
@@ -988,7 +975,6 @@ fr:
suspended: Suspendu
silenced: Sous silence
trending_search:
- title: Recherche tendance
labels:
term: Terme
searches: Recherches
@@ -1206,10 +1192,6 @@ fr:
inline_onebox_domains_whitelist: "Une liste de domaines qui seront transformé en Onebox s'ils ont été liés sans titre"
enable_inline_onebox_on_all_domains: "Ignorer le paramètre inline_onebox_domain_whitelist et permettre des onebox 'inline' pour tous les domaines."
max_oneboxes_per_post: "Nombre maximum de Onebox dans un message."
- logo: "L'image de votre logo situé en haut à gauche de votre site doit être de forme rectangulaire large. Si vous laissez ce champ vide, le nom de votre site apparaitra."
- logo_small: "Le petit logo situé en haut à gauche de votre site doit être de forme carré. Si vous laissez ce champ vide, un logo de maison apparaîtra."
- digest_logo: "L'image alternative de votre logo utilisée en haut du résumé par courriel de votre site. Elle devrait idéalement être en forme de large rectangle et ne pas être une image SVG. Si vide, `logo_url` sera utilisé."
- mobile_logo: "Logo personnalisé utilisé sur la version mobile de votre site. Si non renseigné, `logo_url` sera utilisé, par exemple : https://example.com/uploads/default/logo.png"
large_icon: "Image utilisée comme logo sur Android. Taille recommandée : 512 px par 512 px."
favicon: "Un favicon pour votre site, voir https://fr.wikipedia.org/wiki/Favicon, pour fonctionner correctement à travers un CDN il doit être PNG."
apple_touch_icon: "Icône utilisée pour les appareils d'Apple. Taille recommandée 144 px par 144 px."
@@ -1775,13 +1757,6 @@ fr:
errors:
different_topics: "Des messages appartenant à différents sujets ne peuvent pas être fusionnés."
different_users: "Des messages appartenant à différents utilisateurs ne peuvent pas être fusionnés."
- move_posts:
- new_topic_moderator_post:
- one: "Un message a été déplacé vers un nouveau sujet : %{topic_link}"
- other: "%{count} messages ont été déplacés vers un nouveau sujet : %{topic_link}"
- existing_topic_moderator_post:
- one: "Un message a été intégré dans un sujet existant : %{topic_link}"
- other: "%{count} messages ont été intégrés dans un sujet existant : %{topic_link}"
change_owner:
post_revision_text: "Propriété transférée"
topic_statuses:
@@ -1878,7 +1853,6 @@ fr:
email:
sent_test: "envoyé !"
sent_test_disabled: "impossible d'envoyer, les courriels sont désactivés"
- sent_test_disabled_for_non_staff: "impossible d'envoyer, les courriels sont désactivés sauf pour les responsables"
user:
deactivated: "A été désactivé à cause de trop de courriels rejetés vers '%{email}'."
deactivated_by_staff: "Désactivé par un responsable"
@@ -4340,10 +4314,8 @@ fr:
fields:
logo:
label: "Logo principal"
- description: "L'image logo du coin supérieur gauche de votre site. Utilisez une forme rectangulaire large."
logo_small:
label: "Logo compact"
- description: "Une version compacte de votre logo qui est affichée dans le coin supérieur gauche de votre site quand vous défilez la page vers le bas. Utilisez une forme carrée."
icons:
title: "Icônes"
fields:
diff --git a/config/locales/server.gl.yml b/config/locales/server.gl.yml
index 834d4aec6f..8243299f10 100644
--- a/config/locales/server.gl.yml
+++ b/config/locales/server.gl.yml
@@ -285,10 +285,6 @@ gl:
title: "Marcadores"
xaxis: "Día"
yaxis: "Número de novos marcadores"
- starred:
- title: "Con estrela"
- xaxis: "Día"
- yaxis: "Número de novos temas con estrela"
users_by_trust_level:
title: "Usuarios por nivel de confianza"
xaxis: "Nivel de confianza"
diff --git a/config/locales/server.he.yml b/config/locales/server.he.yml
index 514992c1c2..859941156b 100644
--- a/config/locales/server.he.yml
+++ b/config/locales/server.he.yml
@@ -150,7 +150,6 @@ he:
continue: "המשך דיון"
error: "תקלה בשילוב"
referer: "מפנה:"
- mismatch: "המפנה לא התאים לאף אחד מהשרתים הבאים:"
no_hosts: "אף שרת לא הוגדר לשילוב."
configure: "הגדרת שילוב (Embedding)"
more_replies:
@@ -721,10 +720,6 @@ he:
title: "סימניות"
xaxis: "יום"
yaxis: "מספר סימניות חדשות"
- starred:
- title: "כוכב"
- xaxis: "יום"
- yaxis: "מספר נושאים חדשים שכוכבו"
users_by_trust_level:
title: "משתמשים בכל רמת אמון"
xaxis: "רמת אמון"
@@ -1305,17 +1300,6 @@ he:
errors:
different_topics: "לא ניתן למזג פוסטים ששייכים לנושאים שונים."
different_users: "לא ניתן למזג פוסטים ששייכים למשתמשים שונים."
- move_posts:
- new_topic_moderator_post:
- one: "תגובה פוצלה לנושא חדש: %{topic_link}"
- two: "%{count} תגובות פוצלו לנושא חדש: %{topic_link}"
- many: "%{count} תגובות פוצלו לנושא חדש: %{topic_link}"
- other: "%{count} תגובות פוצלו לנושא חדש: %{topic_link}"
- existing_topic_moderator_post:
- one: "תגובה אוחדה לנושא קיים: %{topic_link}"
- two: "%{count} תגובות אוחדו לנושא קיים: %{topic_link}"
- many: "%{count} תגובות אוחדו לנושא קיים: %{topic_link}"
- other: "%{count} תגובות אוחדו לנושא קיים: %{topic_link}"
topic_statuses:
archived_enabled: "הנושא הזה נכנס לארכיון. הוא קפוא ולא ניתן לשינוי בכל דרך."
archived_disabled: "הנושא הזה הוצא מהארכיון. הוא כבר לא קפוא, וניתן לשינוי."
diff --git a/config/locales/server.hu.yml b/config/locales/server.hu.yml
index 69df59730f..56ef49c423 100644
--- a/config/locales/server.hu.yml
+++ b/config/locales/server.hu.yml
@@ -504,10 +504,6 @@ hu:
title: "Könyvjelzők"
xaxis: "Nap"
yaxis: "Új könyvjelzők száma"
- starred:
- title: "Csillagozott"
- xaxis: "Nap"
- yaxis: "Új csillagozott témák száma"
users_by_trust_level:
title: "Felhasználok láma bizalmi szintenként"
xaxis: "Bizalmi szint"
diff --git a/config/locales/server.id.yml b/config/locales/server.id.yml
index fec9f0146c..2e72d47ce4 100644
--- a/config/locales/server.id.yml
+++ b/config/locales/server.id.yml
@@ -324,10 +324,6 @@ id:
bookmarks:
xaxis: "Hari"
yaxis: "Jumlah bookmark baru"
- starred:
- title: "Bertanda Bintang"
- xaxis: "Hari"
- yaxis: "Jumlah topik berbintang baru"
users_by_trust_level:
title: "Pengguna per Level Kepercayaan"
xaxis: "Level Kepercayaan"
diff --git a/config/locales/server.it.yml b/config/locales/server.it.yml
index 4bc9542d95..4dd4ed9f9d 100644
--- a/config/locales/server.it.yml
+++ b/config/locales/server.it.yml
@@ -187,7 +187,6 @@ it:
continue: "Continua Discussione"
error: "Errore Inclusione"
referer: "Referer:"
- mismatch: "Il referer non corrisponde con nessuno dei seguenti host:"
no_hosts: "Non sono stati configurati host per l'inclusione."
configure: "Configura Inclusione"
more_replies:
@@ -197,6 +196,9 @@ it:
permalink: "Collegamento permanente"
imported_from: "Questo è un argomento collegato all'originale su: %{link}"
in_reply_to: "▶ %{username}"
+ replies:
+ one: "%{count} risposta"
+ other: "%{count} risposte"
no_mentions_allowed: "Spiacenti, non puoi menzionare altri utenti."
too_many_mentions:
one: "Spiacenti, puoi menzionare al massimo un utente in un messaggio."
@@ -693,6 +695,7 @@ it:
self: "Non hai ancora nessuna attività."
others: "Nessuna attività."
no_bookmarks:
+ self: "Non hai messaggi nei Segnalibri. I Segnalibri ti consentono di accedere velocemente a specifici messaggi."
others: "Nessun segnalibro."
no_likes_given:
self: "Non hai messo \"Mi piace\" ad alcun messaggio."
@@ -700,6 +703,8 @@ it:
no_replies:
self: "Non hai risposto a nessun messaggio."
others: "Nessuna risposta."
+ no_drafts:
+ self: "Non hai bozze. Iniziando a scrivere una risposta a un qualsiasi Argomento, questa verrà automaticamente salvata come nuova Bozza."
topic_flag_types:
spam:
title: 'Spam'
@@ -761,7 +766,6 @@ it:
percent: Per cento
day: Giorno
post_edits:
- title: "Modifica messaggio"
labels:
post: Messaggio
author: Autore
@@ -774,7 +778,6 @@ it:
ignored_flags: Segnalazioni ignorate
score: Punteggio
moderators_activity:
- title: "Attività dei Moderatori"
labels:
moderator: Moderatore
flag_count: Segnalazioni revisionate
@@ -789,7 +792,6 @@ it:
daily_engaged_users:
xaxis: "Giorno"
yaxis: "Utenti Impegnati"
- description: "Numero di utenti che hanno scritto o messo Mi piace nell'ultimo giorno"
profile_views:
title: "Visite Profilo Utente"
xaxis: "Giorno"
@@ -798,7 +800,6 @@ it:
title: "Argomenti"
xaxis: "Giorno"
yaxis: "Numero di nuovi argomenti"
- description: "Nuovi argomenti creati durante questo periodo"
posts:
title: "Messaggi"
xaxis: "Giorno"
@@ -816,10 +817,6 @@ it:
title: "Segnalibri"
xaxis: "Giorno"
yaxis: "Numero di nuovi segnalibri"
- starred:
- title: "Preferiti"
- xaxis: "Giorno"
- yaxis: "Numero di nuovi argomenti preferiti"
users_by_trust_level:
title: "Utenti per Livello Esperienza"
xaxis: "Livello Esperienza"
@@ -838,7 +835,6 @@ it:
suspended: Sospeso
silenced: Silenziato
trending_search:
- title: Ricerche di tendenza
labels:
term: Termine
searches: Ricerche
@@ -1468,13 +1464,6 @@ it:
errors:
different_topics: "I messaggi appartenenti a diversi argomenti non possono essere fusi."
different_users: "I messaggi appartenenti a diversi utenti non possono essere fusi."
- move_posts:
- new_topic_moderator_post:
- one: "Un messaggio è stato spostato in un nuovo argomento: %{topic_link}"
- other: "%{count} messaggi sono stati spostati in un nuovo argomento: %{topic_link}"
- existing_topic_moderator_post:
- one: "Un messaggio è stato fuso in un argomento esistente: %{topic_link}"
- other: "%{count} messaggi sono stati fusi in un argomento esistente: %{topic_link}"
topic_statuses:
archived_enabled: "Questo argomento è stato archiviato. È congelato e non può più essere modificato."
archived_disabled: "Questo argomento non è più archiviato. Non è più congelato e può essere modificato."
diff --git a/config/locales/server.ko.yml b/config/locales/server.ko.yml
index 83f3d75ef2..47e1ba729a 100644
--- a/config/locales/server.ko.yml
+++ b/config/locales/server.ko.yml
@@ -141,7 +141,6 @@ ko:
continue: "토론 계속하기"
error: "삽입에 실패했습니다."
referer: "리퍼러(Referer):"
- mismatch: "리퍼러가 다음 호스트와 일치하지 않습니다:"
no_hosts: "임베딩을 위한 호스트가 설정되지 않았습니다."
configure: "삽입 설정"
more_replies:
@@ -701,10 +700,6 @@ ko:
title: "북마크들"
xaxis: "일"
yaxis: "새로운 북마크 수"
- starred:
- title: "즐겨찾기"
- xaxis: "일"
- yaxis: "새로운 즐겨찾기 수"
users_by_trust_level:
title: "회원등급당 사용자 수"
xaxis: "회원등급"
diff --git a/config/locales/server.lt.yml b/config/locales/server.lt.yml
index 52c071e770..77e6aa121f 100644
--- a/config/locales/server.lt.yml
+++ b/config/locales/server.lt.yml
@@ -447,7 +447,6 @@ lt:
labels:
day: Diena
post_edits:
- title: "Įrašo redagavimai"
labels:
editor: Redaktorius
author: Autorius
@@ -494,9 +493,6 @@ lt:
bookmarks:
title: "Žymės"
xaxis: "Diena"
- starred:
- title: "Pažymėta žvaigždute"
- xaxis: "Diena"
users_by_trust_level:
xaxis: "Patikimumo lygis"
labels:
diff --git a/config/locales/server.lv.yml b/config/locales/server.lv.yml
index 9fcdf2f398..3bea1144ef 100644
--- a/config/locales/server.lv.yml
+++ b/config/locales/server.lv.yml
@@ -79,8 +79,6 @@ lv:
bookmarks:
title: "Grāmatzīmes"
xaxis: "Diena"
- starred:
- xaxis: "Diena"
emails:
title: "E-pasti Izsūtīti "
user_to_user_private_messages:
diff --git a/config/locales/server.nb_NO.yml b/config/locales/server.nb_NO.yml
index 8e1e4dbbfc..93bf2c25e4 100644
--- a/config/locales/server.nb_NO.yml
+++ b/config/locales/server.nb_NO.yml
@@ -184,7 +184,6 @@ nb_NO:
continue: "Fortsett diskusjon"
error: "Feil ved innbygging"
referer: "Referer:"
- mismatch: "Referer passet ikke med noen av de følgende vertsnavnene:"
no_hosts: "Vertsnavn for innbygging."
configure: "Sett opp innbygging"
more_replies:
@@ -738,14 +737,12 @@ nb_NO:
percent: Prosent
day: Dag
post_edits:
- title: "Innlegg redigert"
labels:
post: Innlegg
editor: Tekstbehandler
author: Forfatter
edit_reason: Begrunnelse
moderators_activity:
- title: "Moderatoraktivitet"
labels:
moderator: Moderator
flag_count: Flagg vurdert
@@ -755,7 +752,6 @@ nb_NO:
pm_count: Meldinger opprettet
revision_count: Revisjoner
flags_status:
- title: "Flagg status"
values:
agreed: Enig
disagreed: Uenig
@@ -775,22 +771,18 @@ nb_NO:
title: "Nye brukere"
xaxis: "Dag"
yaxis: "Antall nye brukere"
- description: "Nyregistrerte brukere i denne perioden"
new_contributors:
title: "Nye Bidragsytere"
xaxis: "Dag"
yaxis: "Antall nye bidragsytere"
- description: "Antall brukere som skrev sitt første innlegg i denne perioden"
dau_by_mau:
title: "DAB/MAB"
xaxis: "Dag"
yaxis: "DAB/MAB"
- description: "Antall medlemmer som logget inn siste døgn delt på antall brukere som logget inn siste måned – returnerer en % som indikerer samfunnets evne til å engasjere brukere. Sikt mot >30%."
daily_engaged_users:
title: "Daglige Engasjerte Brukere"
xaxis: "Dag"
yaxis: "Engasjerte Brukere"
- description: "Antall brukere som har likt eller skrevet innlegg siste døgn"
profile_views:
title: "Visninger av brukerprofil"
xaxis: "Dag"
@@ -799,7 +791,6 @@ nb_NO:
title: "Tråder"
xaxis: "Dag"
yaxis: "Antall nye tråder"
- description: "Nye emner opprettet i denne perioden"
posts:
title: "Innlegg"
xaxis: "Dag"
@@ -817,10 +808,6 @@ nb_NO:
title: "Bokmerker"
xaxis: "Dag"
yaxis: "Antall nye bokmerker"
- starred:
- title: "Favoritt"
- xaxis: "Dag"
- yaxis: "Antall nye favoritt-tråder"
users_by_trust_level:
title: "Brukere per tillitsnivå"
xaxis: "Tillitsnivå"
@@ -839,7 +826,6 @@ nb_NO:
suspended: Utestengt
silenced: Dempet
trending_search:
- title: Søketrender
labels:
term: Uttrykk
searches: Søk
diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml
index ecd795fca0..bae8bddda9 100644
--- a/config/locales/server.nl.yml
+++ b/config/locales/server.nl.yml
@@ -136,7 +136,6 @@ nl:
continue: "Discussie voortzetten"
error: "Fout bij embedden"
referer: "Verwijzing:"
- mismatch: "De verwijzing kwam met geen van de volgende hosts overeen:"
no_hosts: "Er zijn geen hosts ingesteld voor embedding."
configure: "Embedden configureren"
more_replies:
@@ -639,10 +638,6 @@ nl:
title: "Bladwijzers"
xaxis: "Dag"
yaxis: "Aantal nieuwe bladwijzers"
- starred:
- title: "Met ster"
- xaxis: "Dag"
- yaxis: "Aantal nieuwe topics met ster"
users_by_trust_level:
title: "Gebruikers per vertrouwensniveau"
xaxis: "Vertrouwensniveau"
@@ -1111,13 +1106,6 @@ nl:
redirected_to_top_reasons:
new_user: "Welkom bij onze gemeenschap! Dit zijn de meest populaire recente topics."
not_seen_in_a_month: "Welkom terug! We hebben u een tijdje niet gezien. Dit zijn de meest populaire topics sinds uw afwezigheid."
- move_posts:
- new_topic_moderator_post:
- one: "Een bericht is gesplitst naar een nieuw topic: %{topic_link}"
- other: "%{count} berichten zijn gesplitst naar een nieuw topic: %{topic_link}"
- existing_topic_moderator_post:
- one: "Een bericht is samengevoegd in een bestaand topic: %{topic_link}"
- other: "%{count} berichten zijn samengevoegd in een bestaand topic: %{topic_link}"
topic_statuses:
archived_enabled: "Dit topic is nu gearchiveerd. Het is bevroren en kan op geen enkele manier worden gewijzigd."
archived_disabled: "Dit topic is nu niet meer gearchiveerd. Het is niet meer bevroren en kan worden gewijzigd."
diff --git a/config/locales/server.pl_PL.yml b/config/locales/server.pl_PL.yml
index ccd5a820d6..7f1517fa4e 100644
--- a/config/locales/server.pl_PL.yml
+++ b/config/locales/server.pl_PL.yml
@@ -147,7 +147,6 @@ pl_PL:
continue: "Kontynuuj dyskusję"
error: "Błąd osadzenia"
referer: "Odnośnik:"
- mismatch: "Odnośnik nie pasuje do żadnego z następujących hostów:"
no_hosts: "Brak ustawionych hostów do osadzenia."
configure: "Konfiguracja osadzenia"
more_replies:
@@ -702,10 +701,6 @@ pl_PL:
title: "Zakładki"
xaxis: "Dzień"
yaxis: "Liczba nowych zakładek"
- starred:
- title: "Oznaczone"
- xaxis: "Dzień"
- yaxis: "Liczba oznaczonych tematów"
users_by_trust_level:
title: "Użytkownicy wg poziomu zaufania"
xaxis: "Poziom zaufania"
@@ -1291,17 +1286,6 @@ pl_PL:
errors:
different_topics: "Nie można połączyć postów z różnych wątków."
different_users: "Nie można połączyć postów różnych użytkowników."
- move_posts:
- new_topic_moderator_post:
- one: "Wydzielono 1 post tworząc nowy wątek: %{topic_link}"
- few: "Wydzielono %{count} posty tworząc nowy wątek: %{topic_link}"
- many: "Wydzielono %{count} postów tworząc nowy wątek: %{topic_link}"
- other: "Wydzielono %{count} postów tworząc nowy wątek: %{topic_link}"
- existing_topic_moderator_post:
- one: "1 post przeniesiono do wątku: %{topic_link}"
- few: "%{count} posty przeniesiono do wątku: %{topic_link}"
- many: "%{count} postów przeniesiono do wątku: %{topic_link}"
- other: "%{count} postów przeniesiono do wątku: %{topic_link}"
topic_statuses:
archived_enabled: "Temat został zarchiwizowany. Został zablokowany i nie może być zmieniany. "
archived_disabled: "Temat został przywrócony z archiwum. Został odblokowany i może ponownie być zmieniany."
diff --git a/config/locales/server.pt.yml b/config/locales/server.pt.yml
index a554506058..1e457402cb 100644
--- a/config/locales/server.pt.yml
+++ b/config/locales/server.pt.yml
@@ -604,10 +604,6 @@ pt:
title: "Marcadores"
xaxis: "Dia"
yaxis: "Número de novos marcadores"
- starred:
- title: "Favoritos"
- xaxis: "Dia"
- yaxis: "Número de novos tópicos favoritos"
users_by_trust_level:
title: "Utilizadores por Nível de Confiança"
xaxis: "Nível de Confiança"
@@ -1156,13 +1152,6 @@ pt:
errors:
different_topics: "Publicações pertencentes a tópicos diferentes não podem ser unidas."
different_users: "Publicações pertencentes a utilizadores diferentes não podem ser unidas."
- move_posts:
- new_topic_moderator_post:
- one: "Uma mensagem foi dividida em um novo tópico: %{topic_link}"
- other: "%{count} mensagens foram divididas em um novo tópico: %{topic_link}"
- existing_topic_moderator_post:
- one: "Uma mensagem foi unida em um tópico existente: %{topic_link}"
- other: "%{count} mensagens foram unidas em um tópico existente: %{topic_link}"
topic_statuses:
archived_enabled: "Este tópico está agora arquivado. Está congelado e não pode ser alterado de qualquer maneira."
archived_disabled: "Este tópico foi agora desarquivado. Já não está congelado, e pode ser alterado."
diff --git a/config/locales/server.pt_BR.yml b/config/locales/server.pt_BR.yml
index d97ba0ec30..9462cfe7a8 100644
--- a/config/locales/server.pt_BR.yml
+++ b/config/locales/server.pt_BR.yml
@@ -179,7 +179,6 @@ pt_BR:
continue: "Continuar Discussão"
error: "Erro na Incorporação"
referer: "Referer:"
- mismatch: "O referenciador não encontrou nenhum dos seguintes hosts:"
no_hosts: "Nenhum host foi configurado para incorporação."
configure: "Configurar incorporação"
more_replies:
@@ -730,14 +729,12 @@ pt_BR:
percent: Porcento
day: Dia
post_edits:
- title: "Publicar edições"
labels:
post: Publicar
editor: Editor
author: Autor
edit_reason: Razão
moderators_activity:
- title: "Atividade de moderadores"
labels:
moderator: Moderador
flag_count: sinalizações revisadas
@@ -747,7 +744,6 @@ pt_BR:
pm_count: MPs criadas
revision_count: Revisões
flags_status:
- title: " status de Sinalização"
values:
agreed: Acordado
disagreed: Discordado
@@ -767,22 +763,18 @@ pt_BR:
title: "Inscrições"
xaxis: "Dia"
yaxis: "Número de Inscrições"
- description: "Novos registros de conta para este período"
new_contributors:
title: "Novos contribuidores"
xaxis: "Dia"
yaxis: "Número de novos colaboradores"
- description: "Número de usuários que fizeram sua primeira postagem durante esse período"
dau_by_mau:
title: "DAU/MAU"
xaxis: "Dia"
yaxis: "DAU/MAU"
- description: "Número de membros que se conectaram no último dia dividido por nenhum dos membros que efetuaram login no último mês - retorna um% que indica a 'rigidez' da comunidade. Apontar para> 30%."
daily_engaged_users:
title: "Usuários engajados diariamente"
xaxis: "Dia"
yaxis: "Usuários Engajados"
- description: "Número de usuários que curtiram ou postaram no último dia"
profile_views:
title: "Visualizações de Perfil de Usuário"
xaxis: "Dia"
@@ -791,7 +783,6 @@ pt_BR:
title: "Tópicos"
xaxis: "Dia"
yaxis: "Número de tópicos novos"
- description: "Novos tópicos criados durante este período"
posts:
title: "Posts"
xaxis: "Dia"
@@ -809,10 +800,6 @@ pt_BR:
title: "Marcadores Adicionados"
xaxis: "Dia"
yaxis: "Número de novos marcadores adicionados"
- starred:
- title: "Favorito"
- xaxis: "Dia"
- yaxis: "Número de novos tópicos favoritos"
users_by_trust_level:
title: "Usuários por Nível de Confiança"
xaxis: "Nível de Confiança"
@@ -831,7 +818,6 @@ pt_BR:
suspended: Suspenso
silenced: Silenciado
trending_search:
- title: Pesquisa em tendência
labels:
term: Termo
searches: Pesquisas
@@ -1524,13 +1510,6 @@ pt_BR:
errors:
different_topics: "Publicações que pertencem a tópicos diferentes não podem ser combinadas."
different_users: "Publicações que pertencem a usuários diferentes não podem ser combinadas."
- move_posts:
- new_topic_moderator_post:
- one: "Uma publicação foi dividida em um novo tópico : %{topic_link}"
- other: "%{count} publicações foram divididas em um novo tópico: %{topic_link}"
- existing_topic_moderator_post:
- one: "Uma publicação foi combinada em um tópico existente: %{topic_link}"
- other: "%{count} publicações foram combinadas em um tópico existente: %{topic_link}"
topic_statuses:
archived_enabled: "Este tópico está agora arquivado. Está congelado e não pode ser alterado de qualquer forma."
archived_disabled: "Este tópico foi agora desarquivado. Já não está congelado, e pode ser alterado."
diff --git a/config/locales/server.ro.yml b/config/locales/server.ro.yml
index 7048bfea9c..19366494e9 100644
--- a/config/locales/server.ro.yml
+++ b/config/locales/server.ro.yml
@@ -132,7 +132,6 @@ ro:
continue: "Continuă discuție"
error: "Eroare la încorporare"
referer: "Referent:"
- mismatch: "Referentul nu corespunde nici uneia dintre următoarele gazde:"
no_hosts: "Nu s-a setat nicio gazdă pentru încorporare."
configure: "Configurează încorporarea"
more_replies:
@@ -579,7 +578,6 @@ ro:
title: "Subiecte"
xaxis: "Zi"
yaxis: "Număr de subiecte noi"
- description: "Discuții noi create în această perioadă"
posts:
title: "Postări"
xaxis: "Zi"
@@ -596,10 +594,6 @@ ro:
title: "Semne de carte"
xaxis: "Zi"
yaxis: "Număr de semne de carte noi"
- starred:
- title: "Marcat cu steluță"
- xaxis: "Zi"
- yaxis: "Număr de subiecte noi marcate cu steluță"
users_by_trust_level:
title: "Utilizatori pe nivel de încredre"
xaxis: "Nivel de încredere"
@@ -1154,15 +1148,6 @@ ro:
errors:
different_topics: "Postările care aparțin unor subiecte diferite nu pot fi comasate."
different_users: "Postările care aparțin unor utilizatori diferiți nu pot fi comasate."
- move_posts:
- new_topic_moderator_post:
- one: "o postare a fost mutată într-un nou subiect: %{topic_link}"
- few: "%{count} postări au fost mutate într-un nou subiect: %{topic_link}"
- other: "%{count} de postări au fost mutate într-un nou subiect: %{topic_link}"
- existing_topic_moderator_post:
- one: "o postare a fost comasată într-un subiect existent: %{topic_link}"
- few: "%{count} postări au fost comasate într-un subiect existent: %{topic_link}"
- other: "%{count} de postări au fost comasate într-un subiect existent: %{topic_link}"
topic_statuses:
archived_enabled: "Acest subiect este acum arhivat. Este înghețat și nu mai poate fi schimbat în nici un fel."
archived_disabled: "Acest subiect este acum dezarhivat. Nu mai este înghețat și poate fi modificat."
diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml
index 8426930b03..1da01e3783 100644
--- a/config/locales/server.ru.yml
+++ b/config/locales/server.ru.yml
@@ -147,7 +147,6 @@ ru:
continue: "Продолжить обсуждение"
error: "Внутренняя ошибка"
referer: "Источник:"
- mismatch: "Источник не совпал с каким-либо из следующих хостов:"
no_hosts: "Никаких хостов не настроено для встраивания."
configure: "Настроить Встраивание"
more_replies:
@@ -668,10 +667,6 @@ ru:
title: "Закладки"
xaxis: "Дата"
yaxis: "Количество новых закладок"
- starred:
- title: "Избранное"
- xaxis: "Дата"
- yaxis: "Тем добавлено в избранное"
users_by_trust_level:
title: "Пользователи по уровню доверия"
xaxis: "Уровень доверия"
@@ -1091,17 +1086,6 @@ ru:
errors:
different_topics: "Сообщения, принадлежащие другой теме, не могут быть объединены."
different_users: "Сообщения, принадлежащие разным пользователям, не могут быть объединены."
- move_posts:
- new_topic_moderator_post:
- one: "Сообщение перенесено в новую тему: %{topic_link}"
- few: "%{count} сообщения перенесены в новую тему: %{topic_link}"
- many: "%{count} сообщений перенесены в новую тему: %{topic_link}"
- other: "%{count} сообщений перенесены в новую тему: %{topic_link}"
- existing_topic_moderator_post:
- one: "Сообщение перенесено в тему %{topic_link}"
- few: "%{count} сообщения перенесены в тему %{topic_link}"
- many: "%{count} сообщений перенесены в тему %{topic_link}"
- other: "%{count} сообщений перенесены в тему %{topic_link}"
topic_statuses:
archived_enabled: "Эта тема отправлена в Архив. Она заморожена и не может быть изменена."
archived_disabled: "Эта тема разархивирована. Она более не заморожена, и может быть изменена."
diff --git a/config/locales/server.sk.yml b/config/locales/server.sk.yml
index 49f2fdb021..de0743f32f 100644
--- a/config/locales/server.sk.yml
+++ b/config/locales/server.sk.yml
@@ -630,10 +630,6 @@ sk:
title: "Záložky"
xaxis: "Deň"
yaxis: "Počet nových záložiek"
- starred:
- title: "Obľúbené"
- xaxis: "Deň"
- yaxis: "Počet nových ohviezdičkovaných tém"
users_by_trust_level:
title: "Používatelia podľa stupňa dôvery"
xaxis: "Stupeň dôvery"
@@ -1046,17 +1042,6 @@ sk:
redirected_to_top_reasons:
new_user: "Vitajte v našej komunite! Toto sú najpopulárnejšie z posledných tém."
not_seen_in_a_month: "Vitajte späť! Nevideli sme Vás už nejaký čas. Toto sú najpopulárnejšie témy odkedy ste boli preč."
- move_posts:
- new_topic_moderator_post:
- one: "Príspevok bol oddelený do novej témy: %{topic_link}"
- few: "%{count} príspevky boli oddelené do novej témy: %{topic_link}"
- many: "%{count} príspevkov bolo oddelených do novej témy: %{topic_link}"
- other: "%{count} príspevkov bolo oddelených do novej témy: %{topic_link}"
- existing_topic_moderator_post:
- one: "Príspevok bol pripojený k existujúcej téme: %{topic_link}"
- few: "%{count} príspevky boli pripojené k existujúcej téme: %{topic_link}"
- many: "%{count} príspevkov bolo pripojených k existujúcej téme: %{topic_link}"
- other: "%{count} príspevkov bolo pripojených k existujúcej téme: %{topic_link}"
topic_statuses:
archived_enabled: "Táto téma je archivovaná. Je zmrazená a už sa nedá nijako meniť. "
archived_disabled: "Táto téma je vyňatá z archivu. Už nie je zmrazená a môže sa opäť meniť. "
diff --git a/config/locales/server.sl.yml b/config/locales/server.sl.yml
index 8932a7f26b..619cc402e0 100644
--- a/config/locales/server.sl.yml
+++ b/config/locales/server.sl.yml
@@ -25,6 +25,7 @@ sl:
pm: "pm"
title: "Discourse"
topics: "Teme"
+ posts: "objave"
loading: "Nalagam"
log_in: "Vpis"
errors: &errors
@@ -81,6 +82,10 @@ sl:
user_notifications:
signup_after_approval:
subject_template: "Potrjeni ste bili na %{site_name}!"
+ login_required:
+ welcome_message: |
+ ## [Dobrodošli na %{title}](#welcome)
+ Za dostop morate biti prijavljeni. Ustvarite račun in se prijavite za nadaljevanje.
badges:
anniversary:
name: Obletnica
diff --git a/config/locales/server.sq.yml b/config/locales/server.sq.yml
index c513d31323..7f71263980 100644
--- a/config/locales/server.sq.yml
+++ b/config/locales/server.sq.yml
@@ -389,8 +389,6 @@ sq:
title: "Të preferuarat"
xaxis: "Ditë"
yaxis: "Numri i postimeve të preferuar të rinj"
- starred:
- xaxis: "Ditë"
users_by_trust_level:
title: "Përdorues për Nivel Besimi"
xaxis: "Niveli i besimit"
@@ -756,13 +754,6 @@ sq:
redirected_to_top_reasons:
new_user: "Welcome to our community! These are the most popular recent topics."
not_seen_in_a_month: "Welcome back! We haven't seen you in a while. These are the most popular topics since you've been away."
- move_posts:
- new_topic_moderator_post:
- one: "A post was split to a new topic: %{topic_link}"
- other: "%{count} posts were split to a new topic: %{topic_link}"
- existing_topic_moderator_post:
- one: "A post was merged into an existing topic: %{topic_link}"
- other: "%{count} posts were merged into an existing topic: %{topic_link}"
topic_statuses:
archived_enabled: "This topic is now archived. It is frozen and cannot be changed in any way."
archived_disabled: "This topic is now unarchived. It is no longer frozen, and can be changed."
diff --git a/config/locales/server.sv.yml b/config/locales/server.sv.yml
index 913bc5f77e..0d06647d6b 100644
--- a/config/locales/server.sv.yml
+++ b/config/locales/server.sv.yml
@@ -130,7 +130,6 @@ sv:
continue: "Fortsätt diskussion"
error: "Fel vid inbäddningen"
referer: "Hänvisning:"
- mismatch: "Hänvisningarna matchade inte någon av följande ägare"
no_hosts: "Inga värdar var uppsatta för textinbäddningen"
configure: "konfigurera inbäddningen"
more_replies:
@@ -544,10 +543,6 @@ sv:
title: "Bokmärken"
xaxis: "Dag"
yaxis: "Antal nya bokmärken"
- starred:
- title: "Favoriserad"
- xaxis: "Dag"
- yaxis: "Antal favoriserade ämnen"
users_by_trust_level:
title: "Användare per förtroendenivå"
xaxis: "Förtroendenivå"
@@ -1085,13 +1080,6 @@ sv:
errors:
different_topics: "Inlägg som härrör sig till olika ämnen kan inte läggas ihop."
different_users: "Inlägg som hör till olika användare kan inte lägga ihop."
- move_posts:
- new_topic_moderator_post:
- one: "Ett inlägg har delats in i ett nytt ämne: %{topic_link}"
- other: "%{count} inlägg delades in i ett nytt ämne: %{topic_link}"
- existing_topic_moderator_post:
- one: "Ett inlägg har sammanfogats med ett existerande ämne: %{topic_link}"
- other: "%{count} inlägg har sammanfogats med ett existerande ämne: %{topic_link}"
topic_statuses:
archived_enabled: "Detta ämne är nu arkiverat. Det är fryst och kan inte förändras på något sätt."
archived_disabled: "Detta ämne är nu oarkiverat. Det är inte längre fruset, och kan ändras."
diff --git a/config/locales/server.sw.yml b/config/locales/server.sw.yml
index 35dc357fde..29d2c3e73d 100644
--- a/config/locales/server.sw.yml
+++ b/config/locales/server.sw.yml
@@ -555,7 +555,6 @@ sw:
yaxis: "Idadi ya utembeleaji"
signups:
xaxis: "Siku"
- description: "Akaunti mpya zilizosajiliwa katika kipindi hiki"
new_contributors:
title: "Wachangiaji Wapya"
xaxis: "Siku"
@@ -568,7 +567,6 @@ sw:
title: "Mada"
xaxis: "Siku"
yaxis: "Idadi ya mada mpya"
- description: "Mada mpya zilizotengenezwa katika kipindi hiki"
posts:
title: "Machapisho"
xaxis: "Siku"
@@ -586,10 +584,6 @@ sw:
title: "Mialamisho"
xaxis: "Siku"
yaxis: "Idadi ya mialamisho mapya"
- starred:
- title: "Wekewa nyota"
- xaxis: "Siku"
- yaxis: "Idadi ya mada mpya zenye nyota"
users_by_trust_level:
title: "Watumiaji kwenye Kila Kiwango cha Uaminifu"
xaxis: "Kiwango cha Uaminifu"
@@ -604,7 +598,6 @@ sw:
suspended: Alisitishwa
silenced: Amenyamazishwa
trending_search:
- title: Utafiti maarufu
labels:
term: Neno
searches: Utafiti
diff --git a/config/locales/server.te.yml b/config/locales/server.te.yml
index 7f900c07e7..59cebd70da 100644
--- a/config/locales/server.te.yml
+++ b/config/locales/server.te.yml
@@ -354,10 +354,6 @@ te:
title: "పేజీకలు"
xaxis: "రోజు"
yaxis: "కొత్త పేజీకల సంఖ్య"
- starred:
- title: "చుక్కేసినవి"
- xaxis: "రోజు"
- yaxis: "కొత్తగా చుక్కపెట్టిన విషయాలు"
users_by_trust_level:
title: "నమ్మకపు స్థాయి వారీగా సభ్యులు"
xaxis: "నమ్మకం స్థాయి"
diff --git a/config/locales/server.tr_TR.yml b/config/locales/server.tr_TR.yml
index 61c8c351c3..1d7377a352 100644
--- a/config/locales/server.tr_TR.yml
+++ b/config/locales/server.tr_TR.yml
@@ -497,10 +497,6 @@ tr_TR:
title: "İmlenenler"
xaxis: "Gün"
yaxis: "Yeni imlenenlerin sayısı"
- starred:
- title: "Yıldızlı"
- xaxis: "Gün"
- yaxis: "Yeni yıldızlı konuların sayısı"
users_by_trust_level:
title: "Güven Seviyesine ait Kullanıcı Sayısı"
xaxis: "Güven Seviyesi"
@@ -967,13 +963,6 @@ tr_TR:
errors:
different_topics: "Farklı konulara ait gönderiler birleştirilemez."
different_users: "Farklı kullanıcılara ait gönderiler birleştirilemez."
- move_posts:
- new_topic_moderator_post:
- one: "%{count} gönderi yeni bir konu için ayıklandı: %{topic_link}"
- other: "%{count} gönderi yeni bir konu için ayıklandı: %{topic_link}"
- existing_topic_moderator_post:
- one: "%{count} gönderi var olan bir konu içinde birleştirildi: %{topic_link}"
- other: "%{count} gönderi var olan bir konu içinde birleştirildi: %{topic_link}"
topic_statuses:
archived_enabled: "Konu şimdi arşivlendi. Donduruldu ve herhangi bir şekilde değişiklik yapılamaz."
archived_disabled: "Konu şimdi arşivden çıkarıldı. Artık donmuş değil, değiştirilebilir."
diff --git a/config/locales/server.uk.yml b/config/locales/server.uk.yml
index c14acf4238..f53703e5ae 100644
--- a/config/locales/server.uk.yml
+++ b/config/locales/server.uk.yml
@@ -248,10 +248,6 @@ uk:
title: "Закладки"
xaxis: "День"
yaxis: "Кількість нових закладок"
- starred:
- title: "Зірочки"
- xaxis: "День"
- yaxis: "Кількість нових тем, позначених зірочкою"
users_by_trust_level:
title: "Користувачі за Рівнем довіри"
xaxis: "Рівень довіри"
diff --git a/config/locales/server.ur.yml b/config/locales/server.ur.yml
index b2e0c49cfa..2fe683d034 100644
--- a/config/locales/server.ur.yml
+++ b/config/locales/server.ur.yml
@@ -167,7 +167,6 @@ ur:
continue: "بحث جاری رکھیں"
error: "اَیمبَیڈ کرنے میں خرابی"
referer: "حوالہ دہندہ:"
- mismatch: "حوالہ دہندہ مندرجہ ذیل ہوسٹس میں سے کسی کے ساتھ بھی مَیچ نہیں کیا:"
no_hosts: "اَیمبَیڈ کرنے کیلئے کوئی ہوسٹس سَیٹ نہیں کیے گئے۔"
configure: "اَیمبَیڈ کرنا ترتیب دیں"
more_replies:
@@ -725,10 +724,6 @@ ur:
title: "بُک مارکس"
xaxis: "دن"
yaxis: "نئے بُک مارکس کی تعداد"
- starred:
- title: "ستارہ شُدہ"
- xaxis: "دن"
- yaxis: "نئے ستارہ شُدہ ٹاپکس کی تعداد"
users_by_trust_level:
title: "صارفین فی ٹرسٹ لَیول"
xaxis: "ٹرسٹ لَیول"
@@ -1379,13 +1374,6 @@ ur:
errors:
different_topics: "مختلف ٹاپکس کی پوسٹس کو ضم نہیں کیا جا سکتا۔"
different_users: "مختلف صارفین کی پوسٹس کو ضم نہیں کیا جا سکتا۔"
- move_posts:
- new_topic_moderator_post:
- one: "پوسٹ کو ایک نئے ٹاپک پر تقسیم کر دیا گیا تھا: %{topic_link}"
- other: "%{count} پوسٹس کو ایک نئے ٹاپک پر تقسیم کر دیا گیا تھا: %{topic_link}"
- existing_topic_moderator_post:
- one: "پوسٹ کو ایک موجودہ ٹاپک میں ضم کر دیا گیا تھا: %{topic_link}"
- other: "%{count} پوسٹس کو ایک موجودہ ٹاپک میں ضم کر دیا گیا تھا: %{topic_link}"
topic_statuses:
archived_enabled: "یہ ٹاپک اب آرکائیو کیا ہوا ہے۔ یہ منجمد ہے اور کسی طرح سے بھی تبدیل نہیں کیا جاسکتا۔"
archived_disabled: "یہ ٹاپک اب غیر آرکائیو شدہ ہے۔ یہ اب منجمد نہیں اور تبدیل کیا جاسکتا ہے۔"
diff --git a/config/locales/server.vi.yml b/config/locales/server.vi.yml
index f31dc1307a..37920f8ff6 100644
--- a/config/locales/server.vi.yml
+++ b/config/locales/server.vi.yml
@@ -454,10 +454,6 @@ vi:
title: "Các đánh dấu"
xaxis: "Ngày"
yaxis: "Số đánh dấu mới"
- starred:
- title: "Bắt đầu"
- xaxis: "Ngày"
- yaxis: "Số chủ đề được tạo."
users_by_trust_level:
title: "Thành viên ở mõi bậc tin tưởng"
xaxis: "Bậc tin tưởng"
@@ -903,11 +899,6 @@ vi:
redirected_to_top_reasons:
new_user: "Chào mừng đến với cộng dồng của chúng tôi! Ở đây có những chủ để phổ biến."
not_seen_in_a_month: "Chào mừng quay trở lại! Chúng tôi thấy bạn truy cập một khoảng thời gian. Ở đây có những bài viết phổ biến từ lúc bạn đ."
- move_posts:
- new_topic_moderator_post:
- other: "%{count} bài viết đã được chia thành chủ đề mới: %{topic_link}"
- existing_topic_moderator_post:
- other: "%{count} bài viết đã được hợp nhất vào chủ đề: %{topic_link}"
topic_statuses:
archived_enabled: "Chủ đề này được đưa vào lưu trữ. Nó sẽ không được sửa đổi nữa. "
archived_disabled: "Chủ đề này được đưa khỏi lưu trữ. Nó có thể được sửa đổi."
diff --git a/config/locales/server.zh_CN.yml b/config/locales/server.zh_CN.yml
index 42ee025135..360572ce9d 100644
--- a/config/locales/server.zh_CN.yml
+++ b/config/locales/server.zh_CN.yml
@@ -142,6 +142,7 @@ zh_CN:
default_categories_already_selected: "不能选择一个已经用于另一个列表的分类。"
s3_upload_bucket_is_required: "你没有填写“s3_upload_bucket”,不能开启上传至 S3。"
s3_backup_requires_s3_settings: "除非你提供了'%{setting_name}',否则无法将S3用作备份位置。"
+ s3_bucket_reused: "您不可把同一个 bucket 既用作 ‘s3_upload_bucket’ 又用作 ‘s3_backup_bucket’。请选择不同的 bucket 或为每个 bucket 使用不同的路径。"
conflicting_google_user_id: '此帐户的Google帐户ID已更改; 出于安全原因,需要管理人员干预。请联系工作人员并将其指向https://meta.discourse.org/t/76575'
activemodel:
errors:
@@ -154,7 +155,7 @@ zh_CN:
+ search_logs:
+ graph_title: "搜索计数"
+ joined: "已加入"
+ discourse_push_notifications:
+ popup:
+ mentioned: '%{username}在“%{topic}”提到了你 - %{site_title}'
+ group_mentioned: '%{username}在“%{topic}”提到了你 - %{site_title}'
+ quoted: '%{username}在“%{topic}”引用了你的帖子 - %{site_title}'
+ replied: '%{username}在“%{topic}”回复了你 - %{site_title}'
+ posted: '%{username}在“%{topic}”中发布了帖子 - %{site_title}'
+ private_message: '%{username}在“%{topic}”中给你发送了一个私信 - %{site_title}'
+ linked: '%{username}在“%{topic}”中链接了你的帖子 - %{site_title}'
+ confirm_title: '通知已启用 - %{site_title}'
+ confirm_body: '成功!通知已启用。'
+ staff_action_logs:
+ not_found: "未找到"
+ unknown: "未知"
+ user_merged: "%{username}已合并到此账户"
+ user_delete_self: "已从%{url}自行删除"
diff --git a/config/locales/server.zh_TW.yml b/config/locales/server.zh_TW.yml
index 9a5ca10e76..1d30b69a43 100644
--- a/config/locales/server.zh_TW.yml
+++ b/config/locales/server.zh_TW.yml
@@ -127,7 +127,6 @@ zh_TW:
continue: "繼續討論"
error: "嵌入時遭遇錯誤"
referer: "Referer:"
- mismatch: "referer 來源與下列任何主機不符:"
no_hosts: "未設置內嵌用的主機。"
configure: "配置嵌入"
more_replies:
@@ -567,10 +566,6 @@ zh_TW:
title: "書籤"
xaxis: "天"
yaxis: "新書籤數量"
- starred:
- title: "關注"
- xaxis: "天"
- yaxis: "新關注數量"
users_by_trust_level:
title: "按信任等級區分的使用者統計"
xaxis: "信任等級"
@@ -1125,11 +1120,6 @@ zh_TW:
errors:
different_topics: "無法合並不同主題的帖子。"
different_users: "無法合並不同用戶的帖子。"
- move_posts:
- new_topic_moderator_post:
- other: "%{count} 個帖子被分離到了新主題:%{topic_link}"
- existing_topic_moderator_post:
- other: "%{count} 個帖子被合併到現存主題:%{topic_link}"
topic_statuses:
archived_enabled: "此討論話題已封存,即已經凍結,無法修改。"
archived_disabled: "此討論話題已被解除封存,即不再凍結,可以修改。"
diff --git a/config/routes.rb b/config/routes.rb
index c46c943ca6..74c24af19b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -242,6 +242,7 @@ Discourse::Application.routes.draw do
get "dashboard/general" => "dashboard_next#general"
get "dashboard/moderation" => "dashboard_next#moderation"
get "dashboard/security" => "dashboard_next#security"
+ get "dashboard/reports" => "dashboard_next#reports"
get "dashboard-old" => "dashboard#index"
@@ -329,13 +330,15 @@ Discourse::Application.routes.draw do
get "login" => "static#show", id: "login", constraints: { format: /(json|html)/ }
get "password-reset" => "static#show", id: "password_reset", constraints: { format: /(json|html)/ }
get "faq" => "static#show", id: "faq", constraints: { format: /(json|html)/ }
- get "guidelines" => "static#show", id: "guidelines", as: 'guidelines', constraints: { format: /(json|html)/ }
- get "rules" => "static#show", id: "rules", as: 'rules', constraints: { format: /(json|html)/ }
get "tos" => "static#show", id: "tos", as: 'tos', constraints: { format: /(json|html)/ }
get "privacy" => "static#show", id: "privacy", as: 'privacy', constraints: { format: /(json|html)/ }
get "signup" => "static#show", id: "signup", constraints: { format: /(json|html)/ }
get "login-preferences" => "static#show", id: "login", constraints: { format: /(json|html)/ }
+ %w{guidelines rules conduct}.each do |faq_alias|
+ get faq_alias => "static#show", id: "guidelines", as: faq_alias, constraints: { format: /(json|html)/ }
+ end
+
get "my/*path", to: 'users#my_redirect'
get "user_preferences" => "users#user_preferences_redirect"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 7be81be577..ebfd493e87 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -1898,3 +1898,19 @@ tags:
force_lowercase_tags:
default: true
client: true
+
+dashboard:
+ dashboard_general_tab_activity_metrics:
+ client: true
+ type: list
+ list_type: compact
+ default: "page_view_total_reqs|visits|time_to_first_response|likes|flags|user_to_user_private_messages_with_replies"
+ allow_any: false
+ choices:
+ - page_view_total_reqs
+ - visits
+ - time_to_first_response
+ - likes
+ - flags
+ - user_to_user_private_messages_with_replies
+ - signups
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index 27d4fefd37..d865aa51e2 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -21,12 +21,14 @@ Discourse uses the PBKDF2 algorithm to encrypt salted passwords. This algorithm
The main vector for [XSS](https://en.wikipedia.org/wiki/Cross-site_scripting) attacks is via the post composer, as we allow users to enter Markdown, HTML (a safe subset thereof), and BBCode to format posts.
-There are 2 main scenarios we protect against:
+There are 3 main scenarios we protect against:
1. **Markdown preview invokes an XSS.** This is possibly severe in one specific case: when a forum staff member edits a user's post, seeing the raw markup, where a malicious user may have inserted code to run JavaScript. This code would only show up in the preview, but it would run in the context of a forum staff member, which is *very* bad.
2. **Markdown displayed on the page invokes an XSS.** To protect against client side preview XSS, Discourse uses [Google Caja](https://developers.google.com/caja/) in the preview window.
+3. **CSP is on by default** for [all Discourse installations](https://meta.discourse.org/t/mitigate-xss-attacks-with-content-security-policy/104243) as of Discourse 2.2. It can be switched off in the site settings, but it is default on.
+
On the server side we run a whitelist based sanitizer, implemented using the [Sanitize gem](https://github.com/rgrove/sanitize). See the [relevant Discourse code](https://github.com/discourse/discourse/blob/master/lib/pretty_text.rb).
In addition, titles and all other places where non-admins can enter code are protected either using the Handlebars library or standard Rails XSS protection.
diff --git a/lib/backup_restore/backup_store.rb b/lib/backup_restore/backup_store.rb
index 3ca9d70529..e9c6340e64 100644
--- a/lib/backup_restore/backup_store.rb
+++ b/lib/backup_restore/backup_store.rb
@@ -18,7 +18,7 @@ module BackupRestore
# @return [Array]
def files
- unsorted_files.sort_by { |file| -file.last_modified.to_i }
+ @files ||= unsorted_files.sort_by { |file| -file.last_modified.to_i }
end
# @return [BackupFile]
@@ -26,6 +26,11 @@ module BackupRestore
files.first
end
+ def reset_cache
+ @files = nil
+ Report.clear_cache(:storage_stats)
+ end
+
def delete_old
return unless cleanup_allowed?
return if (backup_files = files).size <= SiteSetting.maximum_backups
@@ -33,6 +38,8 @@ module BackupRestore
backup_files[SiteSetting.maximum_backups..-1].each do |file|
delete_file(file.filename)
end
+
+ reset_cache
end
def remote?
@@ -60,6 +67,15 @@ module BackupRestore
fail NotImplementedError
end
+ def stats
+ {
+ used_bytes: used_bytes,
+ free_bytes: free_bytes,
+ count: files.size,
+ last_backup_taken_at: latest_file&.last_modified
+ }
+ end
+
private
# @return [Array]
@@ -70,5 +86,13 @@ module BackupRestore
def cleanup_allowed?
true
end
+
+ def used_bytes
+ files.sum { |file| file.size }
+ end
+
+ def free_bytes
+ fail NotImplementedError
+ end
end
end
diff --git a/lib/backup_restore/backuper.rb b/lib/backup_restore/backuper.rb
index 376c0a862c..49c4252ffe 100644
--- a/lib/backup_restore/backuper.rb
+++ b/lib/backup_restore/backuper.rb
@@ -1,4 +1,3 @@
-require "disk_space"
require "mini_mime"
module BackupRestore
@@ -277,18 +276,18 @@ module BackupRestore
end
def notify_user
+ return if @success && @user.id == Discourse::SYSTEM_USER_ID
+
log "Notifying '#{@user.username}' of the end of the backup..."
status = @success ? :backup_succeeded : :backup_failed
- post = SystemMessage.create_from_system_user(@user, status,
- logs: Discourse::Utils.pretty_logs(@logs)
+ post = SystemMessage.create_from_system_user(
+ @user, status, logs: Discourse::Utils.pretty_logs(@logs)
)
- if !@success && @user.id == Discourse::SYSTEM_USER_ID
+ if @user.id == Discourse::SYSTEM_USER_ID
post.topic.invite_group(@user, Group[:admins])
end
-
- post
rescue => ex
log "Something went wrong while notifying user.", ex
end
@@ -304,7 +303,7 @@ module BackupRestore
def refresh_disk_space
log "Refreshing disk stats..."
- DiskSpace.reset_cached_stats
+ @store.reset_cache
rescue => ex
log "Something went wrong while refreshing disk stats.", ex
end
diff --git a/lib/backup_restore/local_backup_store.rb b/lib/backup_restore/local_backup_store.rb
index fce72d27ec..ae1c06e915 100644
--- a/lib/backup_restore/local_backup_store.rb
+++ b/lib/backup_restore/local_backup_store.rb
@@ -34,7 +34,7 @@ module BackupRestore
if File.exists?(path)
FileUtils.remove_file(path, force: true)
- DiskSpace.reset_cached_stats
+ reset_cache
end
end
@@ -63,5 +63,9 @@ module BackupRestore
source: include_download_source ? path : nil
)
end
+
+ def free_bytes
+ DiskSpace.free(@base_directory)
+ end
end
end
diff --git a/lib/backup_restore/s3_backup_store.rb b/lib/backup_restore/s3_backup_store.rb
index 2fae590b70..3ee6388f81 100644
--- a/lib/backup_restore/s3_backup_store.rb
+++ b/lib/backup_restore/s3_backup_store.rb
@@ -24,7 +24,11 @@ module BackupRestore
def delete_file(filename)
obj = @s3_helper.object(filename)
- obj.delete if obj.exists?
+
+ if obj.exists?
+ obj.delete
+ reset_cache
+ end
end
def download_file(filename, destination_path, failure_message = nil)
@@ -38,12 +42,14 @@ module BackupRestore
raise BackupFileExists.new if obj.exists?
obj.upload_file(source_path, content_type: content_type)
+ reset_cache
end
def generate_upload_url(filename)
obj = @s3_helper.object(filename)
raise BackupFileExists.new if obj.exists?
+ ensure_cors!
presigned_url(obj, :put, UPLOAD_URL_EXPIRES_AFTER_SECONDS)
end
@@ -74,7 +80,6 @@ module BackupRestore
end
def presigned_url(obj, method, expires_in_seconds)
- ensure_cors!
obj.presigned_url(method, expires_in: expires_in_seconds)
end
@@ -100,5 +105,9 @@ module BackupRestore
SiteSetting.s3_backup_bucket
end
end
+
+ def free_bytes
+ nil
+ end
end
end
diff --git a/lib/cache.rb b/lib/cache.rb
index 597ae2ce1d..2f3689a02c 100644
--- a/lib/cache.rb
+++ b/lib/cache.rb
@@ -22,7 +22,7 @@ class Cache < ActiveSupport::Cache::Store
end
def keys(pattern = "*")
- redis.keys("#{@namespace}:#{pattern}")
+ redis.scan_each(match: "#{@namespace}:#{pattern}").to_a
end
def clear
diff --git a/lib/cooked_post_processor.rb b/lib/cooked_post_processor.rb
index 9c9ed222bb..1e8b0a0f31 100644
--- a/lib/cooked_post_processor.rb
+++ b/lib/cooked_post_processor.rb
@@ -10,6 +10,8 @@ class CookedPostProcessor
INLINE_ONEBOX_LOADING_CSS_CLASS = "inline-onebox-loading"
INLINE_ONEBOX_CSS_CLASS = "inline-onebox"
+ LOADING_SIZE = 10
+ LOADING_COLORS = 32
attr_reader :cooking_options, :doc
@@ -27,6 +29,8 @@ class CookedPostProcessor
@doc = Nokogiri::HTML::fragment(post.cook(post.raw, @cooking_options))
@has_oneboxes = post.post_analyzer.found_oneboxes?
@size_cache = {}
+
+ @disable_loading_image = !!opts[:disable_loading_image]
end
def post_process(bypass_bump = false)
@@ -95,7 +99,7 @@ class CookedPostProcessor
prev = Post.where('post_number < ? AND topic_id = ? AND post_type = ? AND not hidden', @post.post_number, @post.topic_id, Post.types[:regular]).order('post_number desc').limit(1).pluck(:raw).first
return if !prev
- new_raw = @post.raw.gsub(/\[quote[^\]]*\]\s*#{Regexp.quote(prev.strip)}\s*\[\/quote\]/, '')
+ new_raw = @post.raw.gsub(/\A\s*\[quote[^\]]*\]\s*#{Regexp.quote(prev.strip)}\s*\[\/quote\]/, '')
return if @post.raw == new_raw
PostRevisor.new(@post).revise!(
@@ -322,21 +326,29 @@ class CookedPostProcessor
end
if upload = Upload.get_from_url(src)
- upload.create_thumbnail!(width, height, crop)
+ upload.create_thumbnail!(width, height, crop: crop)
each_responsive_ratio do |ratio|
resized_w = (width * ratio).to_i
resized_h = (height * ratio).to_i
if upload.width && resized_w <= upload.width
- upload.create_thumbnail!(resized_w, resized_h, crop)
+ upload.create_thumbnail!(resized_w, resized_h, crop: crop)
end
end
+
+ unless @disable_loading_image
+ upload.create_thumbnail!(LOADING_SIZE, LOADING_SIZE, format: 'png', colors: LOADING_COLORS)
+ end
end
add_lightbox!(img, original_width, original_height, upload, cropped: crop)
end
+ def loading_image(upload)
+ upload.thumbnail(LOADING_SIZE, LOADING_SIZE)
+ end
+
def is_a_hyperlink?(img)
parent = img.parent
while parent
@@ -398,6 +410,10 @@ class CookedPostProcessor
else
img["src"] = upload.url
end
+
+ if small_upload = loading_image(upload)
+ img["data-small-upload"] = small_upload.url
+ end
end
# then, some overlay informations
@@ -405,7 +421,7 @@ class CookedPostProcessor
img.add_next_sibling(meta)
filename = get_filename(upload, img["src"])
- informations = "#{original_width}x#{original_height}"
+ informations = "#{original_width}×#{original_height}"
informations << " #{number_to_human_size(upload.filesize)}" if upload
a["title"] = CGI.escapeHTML(img["title"] || filename)
@@ -513,7 +529,7 @@ class CookedPostProcessor
end
upload_id = downloaded_images[src]
- upload = Upload.find(upload_id) if upload_id
+ upload = Upload.find_by_id(upload_id) if upload_id
img["src"] = upload.url if upload.present?
# make sure we grab dimensions for oneboxed images
diff --git a/lib/db_helper.rb b/lib/db_helper.rb
index 04c9f8d3fb..19cdfe5d81 100644
--- a/lib/db_helper.rb
+++ b/lib/db_helper.rb
@@ -1,60 +1,91 @@
class DbHelper
- REMAP_SQL ||= "
+ REMAP_SQL ||= <<~SQL
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = 'public'
AND is_updatable = 'YES'
AND (data_type LIKE 'char%' OR data_type LIKE 'text%')
- ORDER BY table_name, column_name"
+ ORDER BY table_name, column_name
+ SQL
- def self.remap(from, to, anchor_left: false, anchor_right: false, exclude_tables: [])
- results = DB.query(REMAP_SQL).to_a
+ def self.remap(from, to, anchor_left: false, anchor_right: false, excluded_tables: [])
like = "#{anchor_left ? '' : "%"}#{from}#{anchor_right ? '' : "%"}"
+ text_columns = Hash.new { |h, k| h[k] = [] }
- remappable_columns = {}
-
- results.each do |result|
- remappable_columns[result.table_name] ||= []
- remappable_columns[result.table_name] << result.column_name
+ DB.query(REMAP_SQL).each do |r|
+ text_columns[r.table_name] << r.column_name
end
- exclude_tables = exclude_tables.map(&:to_s)
+ text_columns.each do |table, columns|
+ next if excluded_tables.include?(table)
- remappable_columns.each do |table_name, column_names|
- next if exclude_tables.include?(table_name)
- set_clause = column_names.map do |column_name|
- "#{column_name} = REPLACE(#{column_name}, :from, :to)"
+ set = columns.map do |column|
+ "#{column} = REPLACE(#{column}, :from, :to)"
end.join(", ")
- where_clause = column_names.map do |column_name|
- "#{column_name} LIKE :like"
+ where = columns.map do |column|
+ "#{column} IS NOT NULL AND #{column} LIKE :like"
end.join(" OR ")
DB.exec(<<~SQL, from: from, to: to, like: like)
- UPDATE #{table_name}
- SET #{set_clause}
- WHERE #{where_clause}
+ UPDATE #{table}
+ SET #{set}
+ WHERE #{where}
SQL
end
SiteSetting.refresh!
end
- def self.find(needle, anchor_left = false, anchor_right = false)
- connection = ActiveRecord::Base.connection.raw_connection
- text_columns = connection.async_exec(REMAP_SQL).to_a
- args = ["#{anchor_left ? '' : "%"}#{needle}#{anchor_right ? '' : "%"}"]
- found = {}
+ def self.regexp_replace(pattern, replacement, flags: "gi", match: "~*", excluded_tables: [])
+ text_columns = Hash.new { |h, k| h[k] = [] }
- text_columns.each do |rc|
- table_name = rc["table_name"]
- column_name = rc["column_name"]
- result = connection.async_exec("SELECT #{column_name} FROM #{table_name} WHERE #{column_name} LIKE $1", args) rescue nil
- if result&.ntuples > 0
- found["#{table_name}.#{column_name}"] = result.map { |r| r[column_name] }
+ DB.query(REMAP_SQL).each do |r|
+ text_columns[r.table_name] << r.column_name
+ end
+
+ text_columns.each do |table, columns|
+ next if excluded_tables.include?(table)
+
+ set = columns.map do |column|
+ "#{column} = REGEXP_REPLACE(#{column}, :pattern, :replacement, :flags)"
+ end.join(", ")
+
+ where = columns.map do |column|
+ "#{column} IS NOT NULL AND #{column} #{match} :pattern"
+ end.join(" OR ")
+
+ puts pattern, replacement, flags, match
+
+ DB.exec(<<~SQL, pattern: pattern, replacement: replacement, flags: flags, match: match)
+ UPDATE #{table}
+ SET #{set}
+ WHERE #{where}
+ SQL
+ end
+
+ SiteSetting.refresh!
+ end
+
+ def self.find(needle, anchor_left: false, anchor_right: false, excluded_tables: [])
+ found = {}
+ like = "#{anchor_left ? '' : "%"}#{needle}#{anchor_right ? '' : "%"}"
+
+ DB.query(REMAP_SQL).each do |r|
+ next if excluded_tables.include?(r.table_name)
+
+ rows = DB.query(<<~SQL, like: like)
+ SELECT #{r.column_name}
+ FROM #{r.table_name}
+ WHERE #{r.column_name} LIKE :like
+ SQL
+
+ if rows.size > 0
+ found["#{r.table_name}.#{r.column_name}"] = rows.map { |row| row.send(r.column_name) }
end
end
+
found
end
diff --git a/lib/discourse.rb b/lib/discourse.rb
index cab68f014f..cd0858f847 100644
--- a/lib/discourse.rb
+++ b/lib/discourse.rb
@@ -313,6 +313,7 @@ module Discourse
end
MessageBus.publish(readonly_channel, true)
+ Site.clear_anon_cache!
true
end
@@ -346,6 +347,7 @@ module Discourse
def self.disable_readonly_mode(key = READONLY_MODE_KEY)
$redis.del(key)
MessageBus.publish(readonly_channel, false)
+ Site.clear_anon_cache!
true
end
@@ -354,12 +356,12 @@ module Discourse
end
def self.last_read_only
- @last_read_only ||= {}
+ @last_read_only ||= DistributedCache.new('last_read_only', namespace: false)
end
def self.recently_readonly?
- return false unless read_only = last_read_only[$redis.namespace]
- read_only > 15.seconds.ago
+ read_only = last_read_only[$redis.namespace]
+ read_only.present? && read_only > 15.seconds.ago
end
def self.received_readonly!
diff --git a/lib/discourse_redis.rb b/lib/discourse_redis.rb
index 00647242b8..2a67c0515f 100644
--- a/lib/discourse_redis.rb
+++ b/lib/discourse_redis.rb
@@ -201,6 +201,31 @@ class DiscourseRedis
end
end
+ def scan_each(options = {}, &block)
+ DiscourseRedis.ignore_readonly do
+ match = options[:match].presence || '*'
+
+ options[:match] =
+ if @namespace
+ "#{namespace}:#{match}"
+ else
+ match
+ end
+
+ if block
+ @redis.scan_each(options) do |key|
+ key = remove_namespace(key) if @namespace
+ block.call(key)
+ end
+ else
+ @redis.scan_each(options).map do |key|
+ key = remove_namespace(key) if @namespace
+ key
+ end
+ end
+ end
+ end
+
def keys(pattern = nil)
DiscourseRedis.ignore_readonly do
pattern = pattern || '*'
@@ -253,4 +278,10 @@ class DiscourseRedis
Cache.new
end
+ private
+
+ def remove_namespace(key)
+ key[(namespace.length + 1)..-1]
+ end
+
end
diff --git a/lib/discourse_updates.rb b/lib/discourse_updates.rb
index 782a03ab0b..b0c1aaaff5 100644
--- a/lib/discourse_updates.rb
+++ b/lib/discourse_updates.rb
@@ -115,32 +115,32 @@ module DiscourseUpdates
private
- def last_installed_version_key
- 'last_installed_version'
- end
+ def last_installed_version_key
+ 'last_installed_version'
+ end
- def latest_version_key
- 'discourse_latest_version'
- end
+ def latest_version_key
+ 'discourse_latest_version'
+ end
- def critical_updates_available_key
- 'critical_updates_available'
- end
+ def critical_updates_available_key
+ 'critical_updates_available'
+ end
- def missing_versions_count_key
- 'missing_versions_count'
- end
+ def missing_versions_count_key
+ 'missing_versions_count'
+ end
- def updated_at_key
- 'last_version_check_at'
- end
+ def updated_at_key
+ 'last_version_check_at'
+ end
- def missing_versions_list_key
- 'missing_versions'
- end
+ def missing_versions_list_key
+ 'missing_versions'
+ end
- def missing_versions_key_prefix
- 'missing_version'
- end
+ def missing_versions_key_prefix
+ 'missing_version'
+ end
end
end
diff --git a/lib/disk_space.rb b/lib/disk_space.rb
index bf9bb0db0f..2f8d010dc5 100644
--- a/lib/disk_space.rb
+++ b/lib/disk_space.rb
@@ -1,10 +1,4 @@
class DiskSpace
-
- extend ActionView::Helpers::NumberHelper
-
- DISK_SPACE_STATS_CACHE_KEY ||= 'disk_space_stats'.freeze
- DISK_SPACE_STATS_UPDATED_CACHE_KEY ||= 'disk_space_stats_updated'.freeze
-
def self.uploads_used_bytes
# used(uploads_path)
# temporary (on our internal setup its just too slow to iterate)
@@ -15,51 +9,6 @@ class DiskSpace
free(uploads_path)
end
- def self.backups_used_bytes
- used(backups_path)
- end
-
- def self.backups_free_bytes
- free(backups_path)
- end
-
- def self.backups_path
- BackupRestore::LocalBackupStore.base_directory
- end
-
- def self.uploads_path
- "#{Rails.root}/public/uploads/#{RailsMultisite::ConnectionManagement.current_db}"
- end
-
- def self.stats
- {
- uploads_used: number_to_human_size(uploads_used_bytes),
- uploads_free: number_to_human_size(uploads_free_bytes),
- backups_used: number_to_human_size(backups_used_bytes),
- backups_free: number_to_human_size(backups_free_bytes)
- }
- end
-
- def self.reset_cached_stats
- Discourse.cache.delete(DISK_SPACE_STATS_UPDATED_CACHE_KEY)
- Discourse.cache.delete(DISK_SPACE_STATS_CACHE_KEY)
- end
-
- def self.cached_stats
- stats = Discourse.cache.read(DISK_SPACE_STATS_CACHE_KEY)
- updated_at = Discourse.cache.read(DISK_SPACE_STATS_UPDATED_CACHE_KEY)
-
- unless updated_at && (Time.now.to_i - updated_at.to_i) < 30.minutes
- Jobs.enqueue(:update_disk_space)
- end
-
- if stats
- JSON.parse(stats)
- end
- end
-
- protected
-
def self.free(path)
`df -Pk #{path} | awk 'NR==2 {print $4;}'`.to_i * 1024
end
@@ -67,4 +16,9 @@ class DiskSpace
def self.used(path)
`du -s #{path}`.to_i * 1024
end
+
+ def self.uploads_path
+ "#{Rails.root}/public/uploads/#{RailsMultisite::ConnectionManagement.current_db}"
+ end
+ private_class_method :uploads_path
end
diff --git a/lib/email/sender.rb b/lib/email/sender.rb
index c4daf17ec4..4cc6bcf25e 100644
--- a/lib/email/sender.rb
+++ b/lib/email/sender.rb
@@ -30,10 +30,6 @@ module Email
return skip(SkippedEmailLog.reason_types[:sender_message_blank]) if @message.blank?
return skip(SkippedEmailLog.reason_types[:sender_message_to_blank]) if @message.to.blank?
- if SiteSetting.disable_emails == "non-staff"
- return unless User.find_by_email(to_address)&.staff?
- end
-
if @message.text_part
if @message.text_part.body.to_s.blank?
return skip(SkippedEmailLog.reason_types[:sender_text_part_body_blank])
diff --git a/lib/file_helper.rb b/lib/file_helper.rb
index f146b8b139..3f148fc278 100644
--- a/lib/file_helper.rb
+++ b/lib/file_helper.rb
@@ -82,7 +82,12 @@ class FileHelper
tmp
end
- def self.optimize_image!(filename)
+ def self.optimize_image!(filename, allow_pngquant: false)
+ pngquant_options = false
+ if allow_pngquant
+ pngquant_options = { allow_lossy: true }
+ end
+
ImageOptim.new(
# GLOBAL
timeout: 15,
@@ -92,7 +97,7 @@ class FileHelper
advpng: false,
pngcrush: false,
pngout: false,
- pngquant: false,
+ pngquant: pngquant_options,
# JPG
jpegoptim: { strip: SiteSetting.strip_image_metadata ? "all" : "none" },
jpegtran: false,
diff --git a/lib/file_store/base_store.rb b/lib/file_store/base_store.rb
index bdc4856244..9043205c88 100644
--- a/lib/file_store/base_store.rb
+++ b/lib/file_store/base_store.rb
@@ -98,7 +98,7 @@ module FileStore
def get_path_for(type, id, sha, extension)
depth = get_depth_for(id)
- tree = File.join(*sha[0, depth].split(""), "")
+ tree = File.join(*sha[0, depth].chars, "")
"#{type}/#{depth + 1}X/#{tree}#{sha}#{extension}"
end
@@ -107,8 +107,7 @@ module FileStore
if upload.extension
".#{upload.extension}"
else
- # Maintain backward compatibility before Jobs::MigrateUploadExtensions
- # runs
+ # Maintain backward compatibility before Jobs::MigrateUploadExtensions runs
File.extname(upload.original_filename)
end
diff --git a/lib/file_store/s3_store.rb b/lib/file_store/s3_store.rb
index 83932da198..da309f3400 100644
--- a/lib/file_store/s3_store.rb
+++ b/lib/file_store/s3_store.rb
@@ -12,7 +12,9 @@ module FileStore
attr_reader :s3_helper
def initialize(s3_helper = nil)
- @s3_helper = s3_helper || S3Helper.new(s3_bucket, TOMBSTONE_PREFIX)
+ @s3_helper = s3_helper || S3Helper.new(s3_bucket,
+ Rails.configuration.multisite ? multisite_tombstone_prefix : TOMBSTONE_PREFIX
+ )
end
def store_upload(file, upload, content_type = nil)
@@ -87,6 +89,10 @@ module FileStore
@s3_helper.update_tombstone_lifecycle(grace_period)
end
+ def multisite_tombstone_prefix
+ File.join("uploads", "tombstone", RailsMultisite::ConnectionManagement.current_db, "/")
+ end
+
def path_for(upload)
url = upload.try(:url)
FileStore::LocalStore.new.path_for(upload) if url && url[/^\/[^\/]/]
diff --git a/lib/final_destination.rb b/lib/final_destination.rb
index 4f474e970e..c3fff9dcb5 100644
--- a/lib/final_destination.rb
+++ b/lib/final_destination.rb
@@ -34,6 +34,7 @@ class FinalDestination
@opts = opts || {}
@force_get_hosts = @opts[:force_get_hosts] || []
+ @preserve_fragment_url_hosts = @opts[:preserve_fragment_url_hosts] || []
@opts[:max_redirects] ||= 5
@opts[:lookup_ip] ||= lambda { |host| FinalDestination.lookup_ip(host) }
@@ -59,6 +60,7 @@ class FinalDestination
@limited_ips = []
@verbose = @opts[:verbose] || false
@timeout = @opts[:timeout] || nil
+ @preserve_fragment_url = @preserve_fragment_url_hosts.any? { |host| hostname_matches?(host) }
end
def self.connection_timeout
@@ -210,6 +212,7 @@ class FinalDestination
if location
old_port = @uri.port
+ location = "#{location}##{@uri.fragment}" if @preserve_fragment_url && @uri.fragment.present?
location = "#{@uri.scheme}://#{@uri.host}#{location}" if location[0] == "/"
@uri = uri(location)
@limit -= 1
diff --git a/lib/global_path.rb b/lib/global_path.rb
index cea5985079..10d1fbdda5 100644
--- a/lib/global_path.rb
+++ b/lib/global_path.rb
@@ -22,4 +22,10 @@ module GlobalPath
end
end
+ def full_cdn_url(url)
+ uri = URI.parse(UrlHelper.absolute(upload_cdn_path(url)))
+ uri.scheme = SiteSetting.scheme if uri.scheme.blank?
+ uri.to_s
+ end
+
end
diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb
index 8a7fd9d117..d11c877ff5 100644
--- a/lib/guardian/topic_guardian.rb
+++ b/lib/guardian/topic_guardian.rb
@@ -148,6 +148,6 @@ module TopicGuardian
end
def can_update_bumped_at?
- is_staff?
+ is_staff? || @user.has_trust_level?(TrustLevel[4])
end
end
diff --git a/lib/hijack.rb b/lib/hijack.rb
index e0641182d1..3fa4b72dc4 100644
--- a/lib/hijack.rb
+++ b/lib/hijack.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_dependency 'method_profiler'
+require 'method_profiler'
# This module allows us to hijack a request and send it to the client in the deferred job queue
# For cases where we are making remote calls like onebox or proxying files and so on this helps
diff --git a/lib/html_to_markdown.rb b/lib/html_to_markdown.rb
index a213611ea3..b16f647183 100644
--- a/lib/html_to_markdown.rb
+++ b/lib/html_to_markdown.rb
@@ -16,8 +16,10 @@ class HtmlToMarkdown
end
# If a `
` is within a `` that's invalid, so let's hoist the `
` up
+ INLINE_ELEMENTS ||= %w{span font}
+ BLOCK_ELEMENTS ||= %w{div p}
def fix_span_elements(node)
- if node.name == 'span' && node.at('div')
+ if (INLINE_ELEMENTS.include?(node.name) && BLOCK_ELEMENTS.any? { |e| node.at(e) })
node.swap(node.children)
end
@@ -32,6 +34,7 @@ class HtmlToMarkdown
node.content = node.content.gsub(/\A[[:space:]]+/, "") if node.previous_element.nil? && node.parent.description&.block?
node.content = node.content.gsub(/[[:space:]]+\z/, "") if node.next_element&.description&.block?
node.content = node.content.gsub(/[[:space:]]+\z/, "") if node.next_element.nil? && node.parent.description&.block?
+ node.content = node.content.gsub(/\r\n?/, "\n")
node.remove if node.content.empty?
end
end
@@ -200,7 +203,7 @@ class HtmlToMarkdown
@stack[-1].markdown << " " if node.text[0] == " "
@stack[-1].markdown << delimiter
traverse(node)
- @stack[-1].markdown.chomp!
+ @stack[-1].markdown.gsub!(/\n+$/, "")
if @stack[-1].markdown[-1] == " "
@stack[-1].markdown.chomp!(" ")
append_space = true
diff --git a/lib/js_locale_helper.rb b/lib/js_locale_helper.rb
index 0207aee1b4..ae7c6f42ea 100644
--- a/lib/js_locale_helper.rb
+++ b/lib/js_locale_helper.rb
@@ -116,9 +116,7 @@ module JsLocaleHelper
I18n.locale = locale_sym
translations =
- if Rails.env.development?
- load_translations(locale_sym, force: true)
- elsif locale_sym == :en
+ if locale_sym == :en
load_translations(locale_sym)
elsif locale_sym == site_locale || site_locale == :en
load_translations_merged(locale_sym, fallback_locale, :en)
diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb
index 7bfcc6408e..71a1c67917 100644
--- a/lib/middleware/request_tracker.rb
+++ b/lib/middleware/request_tracker.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-require_dependency 'middleware/anonymous_cache'
-require_dependency 'method_profiler'
+require 'method_profiler'
+require 'middleware/anonymous_cache'
class Middleware::RequestTracker
diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb
index f2781dd5a5..72d697c756 100644
--- a/lib/oneboxer.rb
+++ b/lib/oneboxer.rb
@@ -259,9 +259,13 @@ module Oneboxer
SiteSetting.onebox_domains_blacklist.split("|")
end
+ def self.preserve_fragment_url_hosts
+ @preserve_fragment_url_hosts ||= ['http://github.com']
+ end
+
def self.external_onebox(url)
Rails.cache.fetch(onebox_cache_key(url), expires_in: 1.day) do
- fd = FinalDestination.new(url, ignore_redirects: ignore_redirects, ignore_hostnames: blacklisted_domains, force_get_hosts: force_get_hosts)
+ fd = FinalDestination.new(url, ignore_redirects: ignore_redirects, ignore_hostnames: blacklisted_domains, force_get_hosts: force_get_hosts, preserve_fragment_url_hosts: preserve_fragment_url_hosts)
uri = fd.resolve
return blank_onebox if uri.blank? || blacklisted_domains.map { |hostname| uri.hostname.match?(hostname) }.any?
diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb
index 917f322e1f..b19d6cedb9 100644
--- a/lib/plugin/instance.rb
+++ b/lib/plugin/instance.rb
@@ -511,7 +511,7 @@ class Plugin::Instance
provider = Auth::AuthProvider.new
Auth::AuthProvider.auth_attributes.each do |sym|
- provider.send "#{sym}=", opts.delete(sym)
+ provider.send "#{sym}=", opts.delete(sym) if opts.has_key?(sym)
end
begin
diff --git a/lib/post_creator.rb b/lib/post_creator.rb
index 62d2c39dd6..c5c09d04d1 100644
--- a/lib/post_creator.rb
+++ b/lib/post_creator.rb
@@ -476,7 +476,8 @@ class PostCreator
def save_post
@post.disable_rate_limits! if skip_validations?
- saved = @post.save(validate: !skip_validations?)
+ @post.skip_validation = skip_validations?
+ saved = @post.save
rollback_from_errors!(@post) unless saved
end
diff --git a/lib/s3_helper.rb b/lib/s3_helper.rb
index f12497bc1a..708c232133 100644
--- a/lib/s3_helper.rb
+++ b/lib/s3_helper.rb
@@ -24,8 +24,7 @@ class S3Helper
def upload(file, path, options = {})
path = get_path_for_s3_upload(path)
- obj = s3_bucket.object(path)
- obj.upload_file(file, options)
+ s3_bucket.object(path).upload_file(file, options)
path
end
@@ -39,14 +38,27 @@ class S3Helper
end
# delete the file
+ s3_filename.prepend(multisite_upload_path) if Rails.configuration.multisite
s3_bucket.object(get_path_for_s3_upload(s3_filename)).delete
rescue Aws::S3::Errors::NoSuchKey
end
def copy(source, destination, options: {})
+ if !Rails.configuration.multisite
+ options[:copy_source] = File.join(@s3_bucket_name, source)
+ else
+ if @s3_bucket_folder_path
+ bucket_folder, filename = begin
+ source.split("/".freeze, 2)
+ end
+ options[:copy_source] = File.join(@s3_bucket_name, bucket_folder, multisite_upload_path, filename)
+ else
+ options[:copy_source] = File.join(@s3_bucket_name, multisite_upload_path, source)
+ end
+ end
s3_bucket
.object(destination)
- .copy_from(options.merge(copy_source: File.join(@s3_bucket_name, source)))
+ .copy_from(options)
end
# make sure we have a cors config for assets
@@ -80,7 +92,6 @@ class S3Helper
end
def update_lifecycle(id, days, prefix: nil, tag: nil)
-
filter = {}
if prefix
@@ -158,14 +169,15 @@ class S3Helper
end
def object(path)
- path = get_path_for_s3_upload(path)
- s3_bucket.object(path)
+ s3_bucket.object(get_path_for_s3_upload(path))
end
def self.s3_options(obj)
- opts = { region: obj.s3_region,
- endpoint: SiteSetting.s3_endpoint,
- force_path_style: SiteSetting.s3_force_path_style }
+ opts = {
+ region: obj.s3_region,
+ endpoint: SiteSetting.s3_endpoint,
+ force_path_style: SiteSetting.s3_force_path_style
+ }
unless obj.s3_use_iam_profile
opts[:access_key_id] = obj.s3_access_key_id
@@ -194,6 +206,10 @@ class S3Helper
path
end
+ def multisite_upload_path
+ File.join("uploads", RailsMultisite::ConnectionManagement.current_db, "/")
+ end
+
def s3_resource
Aws::S3::Resource.new(@s3_options)
end
diff --git a/lib/single_sign_on.rb b/lib/single_sign_on.rb
index fe9d2bf1be..c1f1074b19 100644
--- a/lib/single_sign_on.rb
+++ b/lib/single_sign_on.rb
@@ -52,13 +52,13 @@ class SingleSignOn
def self.parse(payload, sso_secret = nil)
sso = new
+ sso.sso_secret = sso_secret if sso_secret
parsed = Rack::Utils.parse_query(payload)
decoded = Base64.decode64(parsed["sso"])
decoded_hash = Rack::Utils.parse_query(decoded)
return_sso_url = decoded_hash['return_sso_url']
- sso.sso_secret = sso_secret || (provider_secret(return_sso_url) if return_sso_url)
if sso.sign(parsed["sso"]) != parsed["sig"]
diags = "\n\nsso: #{parsed["sso"]}\n\nsig: #{parsed["sig"]}\n\nexpected sig: #{sso.sign(parsed["sso"])}"
@@ -69,9 +69,6 @@ class SingleSignOn
end
end
- decoded = Base64.decode64(parsed["sso"])
- decoded_hash = Rack::Utils.parse_query(decoded)
-
ACCESSORS.each do |k|
val = decoded_hash[k.to_s]
val = val.to_i if FIXNUMS.include? k
@@ -90,19 +87,6 @@ class SingleSignOn
sso
end
- def self.provider_secret(return_sso_url)
- provider_secrets = SiteSetting.sso_provider_secrets.split(/[|\n]/)
- provider_secrets_hash = Hash[*provider_secrets]
- return_url_host = URI.parse(return_sso_url).host
- # moves wildcard domains to the end of hash
- sorted_secrets = provider_secrets_hash.sort_by { |k, _| k }.reverse.to_h
-
- secret = sorted_secrets.select do |domain, _|
- WildcardDomainChecker.check_domain(domain, return_url_host)
- end
- secret.present? ? secret.values.first : nil
- end
-
def diagnostics
SingleSignOn::ACCESSORS.map { |a| "#{a}: #{send(a)}" }.join("\n")
end
@@ -119,8 +103,8 @@ class SingleSignOn
@custom_fields ||= {}
end
- def sign(payload, provider_secret = nil)
- secret = provider_secret || sso_secret
+ def sign(payload, secret = nil)
+ secret = secret || sso_secret
OpenSSL::HMAC.hexdigest("sha256", secret, payload)
end
@@ -129,9 +113,9 @@ class SingleSignOn
"#{base}#{base.include?('?') ? '&' : '?'}#{payload}"
end
- def payload(provider_secret = nil)
+ def payload(secret = nil)
payload = Base64.strict_encode64(unsigned_payload)
- "sso=#{CGI::escape(payload)}&sig=#{sign(payload, provider_secret)}"
+ "sso=#{CGI::escape(payload)}&sig=#{sign(payload, secret)}"
end
def unsigned_payload
diff --git a/lib/single_sign_on_provider.rb b/lib/single_sign_on_provider.rb
new file mode 100644
index 0000000000..a6b6a9e39d
--- /dev/null
+++ b/lib/single_sign_on_provider.rb
@@ -0,0 +1,33 @@
+require_dependency 'single_sign_on'
+
+class SingleSignOnProvider < SingleSignOn
+
+ def self.parse(payload, sso_secret = nil)
+ set_return_sso_url(payload)
+
+ super
+ end
+
+ def self.set_return_sso_url(payload)
+ parsed = Rack::Utils.parse_query(payload)
+ decoded = Base64.decode64(parsed["sso"])
+ decoded_hash = Rack::Utils.parse_query(decoded)
+
+ @return_sso_url = decoded_hash['return_sso_url']
+ end
+
+ def self.sso_secret
+ return nil unless @return_sso_url && SiteSetting.enable_sso_provider
+
+ provider_secrets = SiteSetting.sso_provider_secrets.split(/[|\n]/)
+ provider_secrets_hash = Hash[*provider_secrets]
+ return_url_host = URI.parse(@return_sso_url).host
+ # moves wildcard domains to the end of hash
+ sorted_secrets = provider_secrets_hash.sort_by { |k, _| k }.reverse.to_h
+
+ secret = sorted_secrets.select do |domain, _|
+ WildcardDomainChecker.check_domain(domain, return_url_host)
+ end
+ secret.present? ? secret.values.first : nil
+ end
+end
diff --git a/lib/site_settings/validations.rb b/lib/site_settings/validations.rb
index 0d50f488b3..2d03fefc3c 100644
--- a/lib/site_settings/validations.rb
+++ b/lib/site_settings/validations.rb
@@ -62,4 +62,33 @@ module SiteSettings::Validations
validate_error(:s3_backup_requires_s3_settings, setting_name: "s3_secret_access_key") if SiteSetting.s3_secret_access_key.blank?
end
end
+
+ def validate_s3_upload_bucket(new_val)
+ validate_bucket_setting("s3_upload_bucket", new_val, SiteSetting.s3_backup_bucket)
+ end
+
+ def validate_s3_backup_bucket(new_val)
+ validate_bucket_setting("s3_backup_bucket", SiteSetting.s3_upload_bucket, new_val)
+ end
+
+ private
+
+ def validate_bucket_setting(setting_name, upload_bucket, backup_bucket)
+ return if upload_bucket.blank? || backup_bucket.blank?
+
+ backup_bucket_name, backup_prefix = split_s3_bucket(backup_bucket)
+ upload_bucket_name, upload_prefix = split_s3_bucket(upload_bucket)
+
+ return if backup_bucket_name != upload_bucket_name
+
+ if backup_prefix == upload_prefix || backup_prefix.blank? || upload_prefix&.start_with?(backup_prefix)
+ validate_error(:s3_bucket_reused, setting_name: setting_name)
+ end
+ end
+
+ def split_s3_bucket(s3_bucket)
+ bucket_name, prefix = s3_bucket.downcase.split("/", 2)
+ prefix&.chomp!("/")
+ [bucket_name, prefix]
+ end
end
diff --git a/lib/tasks/admin.rake b/lib/tasks/admin.rake
index d2ff44b366..6c8ae0b8f4 100644
--- a/lib/tasks/admin.rake
+++ b/lib/tasks/admin.rake
@@ -56,8 +56,12 @@ task "admin:create" => :environment do
admin.email = email
admin.username = UserNameSuggester.suggest(admin.email)
begin
- password = ask("Password: ") { |q| q.echo = false }
- password_confirmation = ask("Repeat password: ") { |q| q.echo = false }
+ if ENV["RANDOM_PASSWORD"] == "1"
+ password = password_confirmation = SecureRandom.hex
+ else
+ password = ask("Password: ") { |q| q.echo = false }
+ password_confirmation = ask("Repeat password: ") { |q| q.echo = false }
+ end
end while password != password_confirmation
admin.password = password
end
diff --git a/lib/tasks/docker.rake b/lib/tasks/docker.rake
index 65276b759c..d2c99decf1 100644
--- a/lib/tasks/docker.rake
+++ b/lib/tasks/docker.rake
@@ -34,6 +34,16 @@ def run_or_fail(command)
$?.exitstatus == 0
end
+def run_or_fail_prettier(*patterns)
+ if patterns.any? { |p| Dir[p].any? }
+ patterns = patterns.map { |p| "'#{p}'" }.join(' ')
+ run_or_fail("yarn prettier --list-different #{patterns}")
+ else
+ puts "Skipping prettier. Pattern not found."
+ true
+ end
+end
+
desc 'Run all tests (JS and code in a standalone environment)'
task 'docker:test' do
begin
@@ -49,7 +59,7 @@ task 'docker:test' do
@good &&= run_or_fail("yarn eslint --ext .es6 plugins/#{ENV['SINGLE_PLUGIN']}")
puts "Listing prettier offenses in #{ENV['SINGLE_PLUGIN']}:"
- @good &&= run_or_fail("yarn prettier --list-different 'plugins/#{ENV['SINGLE_PLUGIN']}/**/*.scss' 'plugins/#{ENV['SINGLE_PLUGIN']}/**/*.es6'")
+ @good &&= run_or_fail_prettier("plugins/#{ENV['SINGLE_PLUGIN']}/**/*.scss", "plugins/#{ENV['SINGLE_PLUGIN']}/**/*.es6")
else
@good &&= run_or_fail("bundle exec rubocop --parallel") unless ENV["SKIP_CORE"]
@good &&= run_or_fail("yarn eslint app/assets/javascripts test/javascripts") unless ENV["SKIP_CORE"]
diff --git a/lib/tasks/uploads.rake b/lib/tasks/uploads.rake
index cf17515022..73f0b3372d 100644
--- a/lib/tasks/uploads.rake
+++ b/lib/tasks/uploads.rake
@@ -203,9 +203,6 @@ end
################################################################################
task "uploads:migrate_to_s3" => :environment do
- require "file_store/s3_store"
- require "file_store/local_store"
-
ENV["RAILS_DB"] ? migrate_to_s3 : migrate_to_s3_all_sites
end
@@ -214,93 +211,176 @@ def migrate_to_s3_all_sites
end
def migrate_to_s3
- # make sure s3 is enabled
- if !SiteSetting.Upload.enable_s3_uploads
- puts "You must enable s3 uploads before running that task"
- return
- end
-
db = RailsMultisite::ConnectionManagement.current_db
- puts "Migrating uploads to S3 (#{SiteSetting.Upload.s3_upload_bucket}) for '#{db}'..."
+ dry_run = !!ENV["DRY_RUN"]
- # will throw an exception if the bucket is missing
- s3 = FileStore::S3Store.new
- local = FileStore::LocalStore.new
+ puts "*" * 30 + " DRY RUN " + "*" * 30 if dry_run
+ puts "Migrating uploads to S3 for '#{db}'..."
- exclude_tables = %i{
- incoming_emails
- stylesheet_cache
- search_logs
- post_search_data
- notifications
- email_logs
- }
+ if Upload.where("url NOT LIKE '//%' AND url NOT LIKE '/uploads/#{db}/original/_X/%'").exists?
+ puts <<~TEXT
+ Some uploads were not migrated to the new scheme. Please run these commands in the rails console
- # Migrate all uploads
- file_uploads = Upload.where.not(sha1: nil).where("url NOT LIKE '#{s3.absolute_base_url}%'")
- image_uploads = file_uploads.where("lower(extension) NOT IN (?)", FileHelper.supported_images.to_a)
+ SiteSetting.migrate_to_new_scheme = true
+ Jobs::MigrateUploadScheme.new.execute(nil)
+ TEXT
+ exit 1
+ end
- [image_uploads, file_uploads].each do |uploads|
- uploads.find_in_batches(batch_size: 100) do |batch|
- batch.each do |upload|
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
- # remove invalid uploads
- if upload.url.blank?
- upload.destroy!
- next
- end
- # store the old url
- from = upload.url
- # retrieve the path to the local file
- path = local.path_for(upload)
- # make sure the file exists locally
- if !path || !File.exists?(path)
- puts "#{from} does not exist locally"
- next
- end
+ unless GlobalSetting.use_s3?
+ puts <<~TEXT
+ Please provide the following environment variables
+ - DISCOURSE_S3_BUCKET
+ - DISCOURSE_S3_REGION
+ - DISCOURSE_S3_ACCESS_KEY_ID
+ - DISCOURSE_S3_SECRET_ACCESS_KEY
+ TEXT
+ exit 2
+ end
- begin
- file = File.open(path)
- content_type = `file --mime-type -b #{path}`.strip
- to = s3.store_upload(file, upload, content_type)
- rescue => e
- puts "Encountered an error while migrating #{upload.url}: #{e.class}: #{e.message}"
- next
- ensure
- file&.close
- end
+ if SiteSetting.Upload.s3_cdn_url.blank?
+ puts "Please provide the 'DISCOURSE_S3_CDN_URL' environment variable"
+ exit 3
+ end
- # remap the URL
- DbHelper.remap(from, to, exclude_tables: exclude_tables)
- upload.optimized_images.destroy_all
- puts "Migrating #{from} --> #{to} took #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - now} seconds"
- end
+ s3 = Aws::S3::Client.new(S3Helper.s3_options(GlobalSetting))
- [
- Discourse.asset_host,
- Discourse.base_url_no_prefix
- ].each do |from|
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
- from = "#{from}#{SiteSetting.Upload.s3_base_url}"
- to = SiteSetting.s3_cdn_url
- DbHelper.remap(from, to, exclude_tables: exclude_tables)
- puts "Remapping #{from} --> #{to} took #{Process.clock_gettime(Process::CLOCK_MONOTONIC) - now} seconds"
+ begin
+ s3.head_bucket(bucket: GlobalSetting.s3_bucket)
+ rescue Aws::S3::Errors::NotFound
+ puts "Bucket '#{GlobalSetting.s3_bucket}' not found. Creating it..."
+ s3.create_bucket(bucket: GlobalSetting.s3_bucket) unless dry_run
+ end
+
+ puts "Uploading files to S3..."
+ print " - Listing local files"
+
+ local_files = []
+ IO.popen("cd public && find uploads/#{db}/original -type f").each do |file|
+ local_files << file.chomp
+ putc "." if local_files.size % 1000 == 0
+ end
+
+ puts " => #{local_files.size} files"
+ print " - Listing S3 files"
+
+ s3_objects = []
+ prefix = Rails.configuration.multisite ? "#{db}/original/" : "original/"
+ options = { bucket: GlobalSetting.s3_bucket, prefix: prefix }
+
+ loop do
+ response = s3.list_objects_v2(options)
+ s3_objects.concat(response.contents)
+ putc "."
+ break if response.next_continuation_token.blank?
+ options[:continuation_token] = response.next_continuation_token
+ end
+
+ puts " => #{s3_objects.size} files"
+ print " - Syncing files to S3"
+
+ synced = 0
+ failed = []
+
+ local_files.each do |file|
+ path = File.join("public", file)
+ name = File.basename(path)
+ etag = Digest::MD5.file(path).hexdigest
+
+ if s3_object = s3_objects.find { |obj| file.ends_with?(obj.key) }
+ next if File.size(path) == s3_object.size && s3_object.etag[etag]
+ end
+
+ options = {
+ acl: "public-read",
+ body: File.open(path, "rb"),
+ bucket: GlobalSetting.s3_bucket,
+ content_type: MiniMime.lookup_by_filename(name)&.content_type,
+ key: file[file.index(prefix)..-1],
+ }
+
+ if !FileHelper.is_supported_image?(name)
+ options[:content_disposition] = %Q{attachment; filename="#{name}"}
+ end
+
+ if dry_run
+ puts "#{file} => #{options[:key]}"
+ synced += 1
+ elsif s3.put_object(options).etag[etag]
+ putc "."
+ synced += 1
+ else
+ putc "X"
+ failed << path
+ end
+ end
+
+ puts
+
+ if failed.size > 0
+ puts "Failed to upload #{failed.size} files"
+ puts failed.join("\n")
+ elsif s3_objects.size + synced >= local_files.size
+ puts "Updating the URLs in the database..."
+
+ excluded_tables = %w{
+ email_logs
+ incoming_emails
+ notifications
+ post_search_data
+ search_logs
+ stylesheet_cache
+ user_auth_token_logs
+ user_auth_tokens
+ web_hooks_events
+ }
+
+ from = "/uploads/#{db}/original/(\\dX/(?:[a-f0-9]/)*[a-f0-9]{40}[a-z0-9\\.]*)"
+ to = "#{SiteSetting.Upload.s3_base_url}/#{prefix}\\1"
+
+ if dry_run
+ puts "REPLACING '#{from}' WITH '#{to}'"
+ else
+ DbHelper.regexp_replace(from, to, excluded_tables: excluded_tables)
+ end
+
+ # Uploads that were on base hostname will now be on S3 CDN
+ from = "#{Discourse.base_url}#{SiteSetting.Upload.s3_base_url}"
+ to = SiteSetting.Upload.s3_cdn_url
+
+ if dry_run
+ puts "REMAPPING '#{from}' TO '#{to}'"
+ else
+ DbHelper.remap(from, to, excluded_tables: excluded_tables)
+ end
+
+ if Discourse.asset_host.present?
+ # Uploads that were on local CDN will now be on S3 CDN
+ from = "#{Discourse.asset_host}#{SiteSetting.Upload.s3_base_url}"
+ to = SiteSetting.Upload.s3_cdn_url
+
+ if dry_run
+ puts "REMAPPING '#{from}' TO '#{to}'"
+ else
+ DbHelper.remap(from, to, excluded_tables: excluded_tables)
end
end
end
+
+ puts "Done!"
end
################################################################################
-# clean_up #
+# clean_up #
################################################################################
task "uploads:clean_up" => :environment do
- if ENV["RAILS_DB"]
- clean_up_uploads
- else
- RailsMultisite::ConnectionManagement.each_connection { clean_up_uploads }
- end
+ ENV["RAILS_DB"] ? clean_up_uploads : clean_up_uploads_all_sites
+end
+
+def clean_up_uploads_all_sites
+ RailsMultisite::ConnectionManagement.each_connection { clean_up_uploads }
end
def clean_up_uploads
diff --git a/lib/theme_settings_manager.rb b/lib/theme_settings_manager.rb
index 8080271944..81c2a7ef0c 100644
--- a/lib/theme_settings_manager.rb
+++ b/lib/theme_settings_manager.rb
@@ -45,7 +45,9 @@ class ThemeSettingsManager
end
def db_record
- ThemeSetting.where(name: @name, data_type: type, theme: @theme).first
+ # theme.theme_settings will already be preloaded, so it is better to use
+ # `find` on an array, rather than make a round trip to the database
+ theme.theme_settings.to_a.find { |i| i.name.to_s == @name.to_s && i.data_type.to_s == type.to_s }
end
def has_record?
diff --git a/lib/version.rb b/lib/version.rb
index a1e0f1bd37..30aa4a0156 100644
--- a/lib/version.rb
+++ b/lib/version.rb
@@ -5,7 +5,7 @@ module Discourse
MAJOR = 2
MINOR = 2
TINY = 0
- PRE = 'beta6'
+ PRE = 'beta7'
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end
diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6 b/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6
index 3bf9486b68..e7432f9411 100644
--- a/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6
+++ b/plugins/discourse-local-dates/assets/javascripts/discourse-local-dates.js.no-module.es6
@@ -128,7 +128,9 @@
const sameTimezone = _isEqualZones(displayedTimezone, moment.tz.guess());
const inCalendarRange = dateTime.isBetween(
moment().subtract(2, "days"),
- moment().add(2, "days")
+ moment()
+ .add(1, "days")
+ .endOf("day")
);
if (options.calendar && inCalendarRange) {
diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6 b/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6
index fb59ae304e..3379de2634 100644
--- a/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6
+++ b/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6
@@ -137,6 +137,10 @@ export default Ember.Component.extend({
text += ` timezones="${config.timezones.join("|")}"`;
}
+ if (config.recurring) {
+ text += ` recurring="${config.recurring}"`;
+ }
+
text += `]`;
return text;
diff --git a/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss b/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss
index 8c1b72e7ec..f0fcd4c889 100644
--- a/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss
+++ b/plugins/discourse-local-dates/assets/stylesheets/common/discourse-local-dates.scss
@@ -66,7 +66,7 @@
flex-direction: row;
.form {
- flex: 1;
+ flex: 1 0 0;
label {
font-weight: 700;
@@ -118,7 +118,7 @@
}
.preview {
- flex: 1;
+ flex: 1 0 0;
margin-top: 16px;
text-align: center;
}
diff --git a/plugins/discourse-local-dates/config/locales/client.sl.yml b/plugins/discourse-local-dates/config/locales/client.sl.yml
index 154d194f86..d303bfaf8d 100644
--- a/plugins/discourse-local-dates/config/locales/client.sl.yml
+++ b/plugins/discourse-local-dates/config/locales/client.sl.yml
@@ -5,4 +5,13 @@
# To work with us on translations, join this project:
# https://www.transifex.com/projects/p/discourse-org/
-sl: {}
+sl:
+ js:
+ discourse_local_dates:
+ relative_dates:
+ today: danes %{time}
+ tomorrow: jutri %{time}
+ yesterday: včeraj %{time}
+ title: Vnesi datum
+ create:
+ modal_title: Vnesi datum
diff --git a/plugins/discourse-local-dates/config/locales/server.ru.yml b/plugins/discourse-local-dates/config/locales/server.ru.yml
index cd8414ecf7..1174c4c944 100644
--- a/plugins/discourse-local-dates/config/locales/server.ru.yml
+++ b/plugins/discourse-local-dates/config/locales/server.ru.yml
@@ -7,5 +7,6 @@
ru:
site_settings:
+ discourse_local_dates_enabled: "Включить функцию discourse-local-dates. Это добавит поддержку дат с указанием местного часового пояса в сообщениях с использованием элемента [date] . "
discourse_local_dates_default_formats: "Часто используемые форматы даты и времени, см.: формат строки momentjs"
discourse_local_dates_default_timezones: "Список часовых поясов по умолчанию должен быть допустимым TZ"
diff --git a/plugins/discourse-local-dates/plugin.rb b/plugins/discourse-local-dates/plugin.rb
index 27f95158f5..02486ec4e6 100644
--- a/plugins/discourse-local-dates/plugin.rb
+++ b/plugins/discourse-local-dates/plugin.rb
@@ -28,7 +28,7 @@ after_initialize do
date = {}
cooked_date.attributes.values.each do |attribute|
data_name = attribute.name&.gsub('data-', '')
- if data_name && ['date', 'time', 'timezone'].include?(data_name)
+ if data_name && ['date', 'time', 'timezone', 'recurring'].include?(data_name)
unless attribute.value == 'undefined'
date[data_name] = CGI.escapeHTML(attribute.value || "")
end
diff --git a/plugins/discourse-narrative-bot/config/locales/server.ar.yml b/plugins/discourse-narrative-bot/config/locales/server.ar.yml
index fcb4fac04f..4cb5c58cfb 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.ar.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.ar.yml
@@ -137,8 +137,6 @@ ar:
new_user_narrative:
reset_trigger: "تدريب العضو جديد"
cert_title: "في اعتراف بأتمام ناجح للدرس التعليمي للاعضاء الجدد"
- hello:
- title: ":robot: تحياتي!"
onebox:
instructions: |-
التالي, هل يمكنك ان تشارك احد هذه الروابط معي؟ رد مع **وضع الرابط في سطر منفصل**, و سيظهر ملخص للصفحة المشير اليها.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.bg.yml b/plugins/discourse-narrative-bot/config/locales/server.bg.yml
index 2320c60eea..770fc9768a 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.bg.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.bg.yml
@@ -10,7 +10,3 @@ bg:
certified:
long_description: |
Тази значка се присъжда при успешното преминаване на интерактивното обучение за нови потребители. Вие поехте инциативата да научите основните инструменти на дискусията и сега сте сертифицирани за това.
- discourse_narrative_bot:
- new_user_narrative:
- hello:
- title: ":robot: Привет!"
diff --git a/plugins/discourse-narrative-bot/config/locales/server.ca.yml b/plugins/discourse-narrative-bot/config/locales/server.ca.yml
index 5fc6fa9913..87cc446226 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.ca.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.ca.yml
@@ -140,8 +140,6 @@ ca:
new_user_narrative:
reset_trigger: "nou usuari"
cert_title: "En reconeixement per haver completat satisfactòriament el tutorial de nou usuari"
- hello:
- title: ":robot: Salutacions!"
onebox:
instructions: |-
Seguidament, pots compartir un d'aquests enllaços amb mi? Respon amb **un enllaç d'una línia sense res més**, i s'expandirà automàticament per incloure un breu resum.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.cs.yml b/plugins/discourse-narrative-bot/config/locales/server.cs.yml
index 28f8553f9c..1b10ca71a2 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.cs.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.cs.yml
@@ -119,7 +119,5 @@ cs:
Bohužel nejsem naprogramován nijak skvěle, takže jsem tomuhle neporozumněl. :frowning:
new_user_narrative:
reset_trigger: "nový uživatel"
- hello:
- title: ":robot: Zdravím!"
advanced_user_narrative:
reset_trigger: 'pokročilý uživatel'
diff --git a/plugins/discourse-narrative-bot/config/locales/server.de.yml b/plugins/discourse-narrative-bot/config/locales/server.de.yml
index 602ab20aba..96b675fe4b 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.de.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.de.yml
@@ -144,7 +144,6 @@ de:
title: "Abschlußzertifikat für neue Benutzer Lektionen"
cert_title: "In Anerkennung deines erfolgreichen Abschlusses eines Tutorials für neue Benutzer"
hello:
- title: ":robot: Grüß dich!"
message: |-
Danke, dass du %{title}beigetreten bist. Willkommen!
diff --git a/plugins/discourse-narrative-bot/config/locales/server.el.yml b/plugins/discourse-narrative-bot/config/locales/server.el.yml
index 6c8bb527cb..fb08bd4665 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.el.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.el.yml
@@ -140,8 +140,6 @@ el:
new_user_narrative:
reset_trigger: "νέος χρήστης"
cert_title: "Σε αναγνώριση της επιτυχούς ολοκλήρωσης\nτης εκπαίδευσης νέου χρήστη."
- hello:
- title: ":robot: Χαιρετώ!"
onebox:
instructions: |-
Στην συνέχεια, θα ήθελες να μοιραστείς έναν από τους παρακάτω συνδέσμους μαζί μου; Απάντησέ μου και βάλε έναν **σύνδεσμο σε μια γραμμή μόνο του** ο οποίος αυτόματα θα επεκταθεί ώστε να εμπεριέχει και μια σύντομη περίληψη.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.en.yml b/plugins/discourse-narrative-bot/config/locales/server.en.yml
index f7df201c73..87c794f23c 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.en.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.en.yml
@@ -149,7 +149,7 @@ en:
cert_title: "In recognition of successful completion of the new user tutorial"
hello:
- title: ":robot: Greetings!"
+ title: "Greetings!"
message: |-
Thanks for joining %{title}, and welcome!
diff --git a/plugins/discourse-narrative-bot/config/locales/server.es.yml b/plugins/discourse-narrative-bot/config/locales/server.es.yml
index 431fa851a2..30ce533929 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.es.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.es.yml
@@ -142,7 +142,6 @@ es:
title: "Certificado de completación para el nuevo usuario"
cert_title: "En reconocimiento de la finalización exitosa del tutorial de usuario nuevo"
hello:
- title: ":robot: Saludos!"
message: |-
Gracias por unirte a %{title}, y bienvenido!
diff --git a/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml b/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml
index db4cc4605e..c3fb93f2f7 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.fa_IR.yml
@@ -139,8 +139,6 @@ fa_IR:
new_user_narrative:
reset_trigger: "کاربر جدید"
cert_title: "با توجه به اتمام موفقیت آمیز راهنمای جدید کاربران"
- hello:
- title: ":robot: با درود!"
onebox:
instructions: |-
در ادامه، میتوانی یکی از پیوند های زیر را با من به اشتراک بگذاری؟ اگر طوری پاسخ بدهی که پیوند **تنها و بدون پس و پیش در یک خط** باشد، به طور اتوماتیک باز میشود و خلاصه ای از محتوای آن اضافه میگردد.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.fi.yml b/plugins/discourse-narrative-bot/config/locales/server.fi.yml
index 766b18933a..1900e30619 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.fi.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.fi.yml
@@ -141,8 +141,6 @@ fi:
new_user_narrative:
reset_trigger: "peruskurssi"
cert_title: "Tunnustuksena palstan käytön peruskurssin menestyksekkäästä suorittamisesta"
- hello:
- title: ":robot: Tervehdys!"
onebox:
instructions: |-
Voisitko nyt jakaa jonkun seuraavista linkeistä minulle? Laita vastaukseesi **linkin osoite yksin omalle rivilleen**, jolloin siitä muotoillaan automaattisesti kätevä tiivistelmälaatikko.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.fr.yml b/plugins/discourse-narrative-bot/config/locales/server.fr.yml
index 3285ae1a70..9fe70f7cdf 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.fr.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.fr.yml
@@ -144,7 +144,6 @@ fr:
title: "Certificat de complétion des étapes de nouvel utilisateur"
cert_title: "En reconnaissance de l'accomplissement avec succès du tutoriel nouvel utilisateur"
hello:
- title: ":robot: Bienvenue !"
message: |-
Merci de vous être inscrit à %{title}, et bienvenue !
diff --git a/plugins/discourse-narrative-bot/config/locales/server.he.yml b/plugins/discourse-narrative-bot/config/locales/server.he.yml
index fbce599dd5..764dde8f80 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.he.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.he.yml
@@ -139,8 +139,6 @@ he:
new_user_narrative:
reset_trigger: "משתמש/ת חדש/ה"
cert_title: "כהוקרה על סיום מוצלח של מדריך המשתמשים החדש"
- hello:
- title: ":robot: ברכות!"
onebox:
instructions: |-
כעת, האם תוכלו לשתף את אחד הקישורים האלו איתי? השיבו עם **קישור בשורה בפני עצמו**, והוא יפתח לכלול סיכום.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.id.yml b/plugins/discourse-narrative-bot/config/locales/server.id.yml
index 70ddf91dc6..812f7e3a73 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.id.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.id.yml
@@ -139,8 +139,6 @@ id:
new_user_narrative:
reset_trigger: "pengguna baru"
cert_title: "Tercatat telah berhasil menyelesaikan panduan pengguna baru."
- hello:
- title: ":robot: Salam!"
onebox:
instructions: |-
Selanjutnya, bisakah Anda membagikan salah satu tautan ini kepada saya? Balas dengan **a link on a line by itself**, otomatis tautan itu akan menjelaskan ringkasan yang menarik.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.it.yml b/plugins/discourse-narrative-bot/config/locales/server.it.yml
index bf2514097d..56c4c303fc 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.it.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.it.yml
@@ -94,6 +94,7 @@ it:
'8': "Le prospettive sono buone"
'9': "Sì"
'10': "I segni indicano di sì"
+ '11': "E' difficile rispondere, prova di nuovo"
'12': "Rifai la domanda più tardi"
'13': "Meglio non risponderti adesso"
'14': "Ora non lo posso prevedere"
@@ -133,7 +134,7 @@ it:
Sfortunatamente, essendo un robot mal programmato, non l'ho capita bene. :frowning:
track_response: Puoi provare di nuovo, o se vuoi saltare questo passaggio, dimmi `%{skip_trigger}`. Altrimenti per ricominciare, dimmi `%{reset_trigger}`.
second_response: |-
- Oh, scusa. Non sto ancora capendo. :anguished:
+ Oh, scusa. Non ho ancora capito. :anguished:
Sono solo un bot, ma se vuoi contattare una persona reale, guarda [la nostra pagina dei contatti](%{base_path}/about).
@@ -142,7 +143,14 @@ it:
reset_trigger: "nuovo utente"
cert_title: "In riconoscimento del successo nel completamento del tutorial come nuovo utente"
hello:
- title: ":robot: Benvenuto!"
+ message: |-
+ Grazie per esserti unito a %{title}, e benvenuto!
+
+ - Io sono solo un robot, ma [il nostro amichevole staff](%{base_uri}/about) è qui per aiutarti se hai bisogno di contattare una persona.
+
+ - Per ragioni di sicurezza, abbiamo temporaneamente limitato ciò che i nuovi utenti possono fare. Potrai guadagnare nuove abilità (e [distintivi](%{base_uri}/badges)) appena ti conosceremo meglio.
+
+ - Noi crediamo in una [comunità dal comportamento civile](%{base_uri}/guidelines) in ogni momento.
onebox:
instructions: |-
Adesso, puoi condividere uno di questi collegamenti con me? Rispondi con **un collegamento su una riga a sé stante**, e verrà automaticamente espanso per includere un bel sommario.
@@ -153,7 +161,7 @@ it:
- https://it.wikipedia.org/wiki/Pagina_principale
- https://it.wikipedia.org/wiki/Traduzione
reply: |-
- Fantastico! Questo funzionerà per la maggior parte dei collegamenti. Ricorda, deve essere su una riga _da solo_, con niente davanti o dietro.
+ Fantastico! Funziona per la maggior parte dei collegamenti. Ricorda, deve essere su una riga _da solo_, senza niente né davanti né dietro.
not_found: |-
Mi dispiace, non riesco a trovare il collegamento nella tua risposta! :cry:
@@ -255,6 +263,8 @@ it:
> :imp: Ho scritto qualcosa di brutto qui
Penso che tu sappia cosa fare. Va avanti e **segnala questo messaggio** come inappropriato!
+ reply: |-
+ [Il nostro staff](%{base_uri}/groups/staff) verrà notificato privatamente della tua segnalazione. Se abbastanza membri della comunità segnalano un messaggio, quest'ultimo verrà automaticamente nascosto per precauzione. (Dal momento che non ho scritto veramente qualcosa di brutto :angel:, ho rimosso la segnalazione per ora.)
not_found: |-
Oh no, il mio brutto messaggio non è ancora stato segnalato. :worried: Puoi segnalarlo come inappropriato usando il pulsante **segnala** ? Non dimenticare di usare il pulsante "mostra altro" per rivelare gli altri pulsanti.
search:
@@ -278,12 +288,25 @@ it:
- Se hai una :keyboard: fisica, premi ? per visualizzare delle comode scorciatoie da tastiera.
not_found: |-
Hmm… sembra che tu abbia qualche problema. Mi dispiace. Hai cercato il termine **capibara**?
+ end:
+ message: |-
+ Grazie per avermi seguito finora @%{username}! Ho fatto questo certificato per te, penso che te lo sia guadagnato:
+
+ %{certificate}
+
+ E' tutto per ora! Controlla [**gli argomenti delle nostre ultime discussioni**](%{base_uri}/latest) o [**le categorie di discussione**](%{base_uri}/categories). :sunglasses:
+
+ (Se vuoi parlare con me ancora per imparare altro, inviami un messaggio o menzionami scrivendo `@%{discobot_username}` quando vuoi!)
certificate:
alt: 'Attestato di Merito'
advanced_user_narrative:
reset_trigger: 'utente avanzato'
cert_title: "In riconoscimento del completamento con successo del tutorial utente avanzato"
title: ':arrow_up: Funzioni utente avanzato'
+ start_message: |-
+ Come utente _avanzato_, hai già visitato [la pagina delle preferenze](%{base_uri}my/preferences) @%{username}? Ci sono molti modi per personalizzare la tua esperienza, ad esempio selezionando un tema scuro o uno chiaro.
+
+ Ma sto divagando, cominciamo!
edit:
bot_created_post_raw: "@%{discobot_username} è, di gran lunga, il bot più interessante che conosco :wink:"
instructions: |-
@@ -353,7 +376,7 @@ it:
not_found: |-
Ooops! Non c'è nessun sondaggio nella tua risposta.
- Usa il pulsante opzioni nell'editor, oppure copia e incolla questo sondaggio nella tua prossima risposta:
+ Usa l'icona a ingranaggio nell'editor, oppure copia e incolla questo sondaggio nella tua prossima risposta:
```text
[poll]
diff --git a/plugins/discourse-narrative-bot/config/locales/server.ko.yml b/plugins/discourse-narrative-bot/config/locales/server.ko.yml
index 65aec52e61..807018f1fd 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.ko.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.ko.yml
@@ -140,8 +140,6 @@ ko:
new_user_narrative:
reset_trigger: "새로운 사용자"
cert_title: "새로운 사용자 튜토리얼을 성공적으로 마친 것에 대한 보상으로"
- hello:
- title: ":robot: 반가워!"
onebox:
instructions: |-
자 이제, 이 링크 중 하나를 나한테 공유해볼래? 한줄 당 링크 하나씩 써서 답글을 써봐. 그러면 자동으로 해당 페이지의 요약이 추가된단다.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.nb_NO.yml b/plugins/discourse-narrative-bot/config/locales/server.nb_NO.yml
index d64f940e75..e8655fd88e 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.nb_NO.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.nb_NO.yml
@@ -140,8 +140,6 @@ nb_NO:
new_user_narrative:
reset_trigger: "ny bruker"
cert_title: "Som bevis for vellykket gjennomføring av introduksjonen for nye brukere"
- hello:
- title: ":robot: Heisann!"
onebox:
instructions: |-
Neste steg, kan du dele en av disse lenkene med meg? Svar med **en frittstående lenke på en ny linje**, da vil den automatisk utvides til å inkludere et stilig sammendrag.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml b/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml
index 917c58b94f..1059bd4557 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.pl_PL.yml
@@ -140,8 +140,6 @@ pl_PL:
new_user_narrative:
reset_trigger: "nowy użytkownik"
cert_title: "W podziękowaniu za skończony samouczek"
- hello:
- title: ":robot: Witaj!"
onebox:
instructions: |-
Następnie, czy możesz udostępnić mi jeden z poniższych linków? Odpowiedz przy użyciu **link**, a automatycznie zostanie dodane dobre podsumowanie.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.pt.yml b/plugins/discourse-narrative-bot/config/locales/server.pt.yml
index b2fc4df1d3..7e016a6227 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.pt.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.pt.yml
@@ -76,8 +76,6 @@ pt:
help_trigger: 'exibir ajuda'
new_user_narrative:
reset_trigger: "novo utilizador"
- hello:
- title: ":robot: Saudações!"
certificate:
alt: 'Certificado de Realização'
advanced_user_narrative:
diff --git a/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml b/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml
index b3104a897a..cf111945fb 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.pt_BR.yml
@@ -141,7 +141,6 @@ pt_BR:
reset_trigger: "novo usuário"
cert_title: "Como forma de reconhecimento por completar com sucesso o tutorial do novo usuário."
hello:
- title: "Olá! :robot:"
message: |-
Obrigado por entrar em %{title}, e seja bem-vindo!
diff --git a/plugins/discourse-narrative-bot/config/locales/server.ru.yml b/plugins/discourse-narrative-bot/config/locales/server.ru.yml
index bbddcc8468..9a36b0b99b 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.ru.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.ru.yml
@@ -140,7 +140,6 @@ ru:
reset_trigger: "новый пользователь"
cert_title: "В знак признания успешного завершения нового руководство пользователя"
hello:
- title: ":robot: Поздравляю!"
message: |-
Спасибо %{title}, и Добро пожаловать!
diff --git a/plugins/discourse-narrative-bot/config/locales/server.sk.yml b/plugins/discourse-narrative-bot/config/locales/server.sk.yml
index ea7b2d2957..c003c117d2 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.sk.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.sk.yml
@@ -138,8 +138,6 @@ sk:
Zatiaľ sa Ti nebudem pliesť do cesty.
new_user_narrative:
reset_trigger: "nový používateľ"
- hello:
- title: ":robot: Zdravím!"
quoting:
reply: |-
Pekná práca, vybrali ste môj obľúbený citát! :left_speech_bubble:
diff --git a/plugins/discourse-narrative-bot/config/locales/server.sl.yml b/plugins/discourse-narrative-bot/config/locales/server.sl.yml
index 7112b7e81a..365bed0ff9 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.sl.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.sl.yml
@@ -41,5 +41,3 @@ sl:
help_trigger: 'prikaži pomoč'
new_user_narrative:
reset_trigger: "novi uporabnik"
- hello:
- title: ":robot: Pozdrav!"
diff --git a/plugins/discourse-narrative-bot/config/locales/server.sr.yml b/plugins/discourse-narrative-bot/config/locales/server.sr.yml
index 8568cd918f..c45cee5415 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.sr.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.sr.yml
@@ -8,8 +8,6 @@
sr:
discourse_narrative_bot:
new_user_narrative:
- hello:
- title: ":robot: Pozdrav!"
bookmark:
instructions: |-
Ako želiš da saznaš više, klikni na ispod i **markiraj ovu privatnu poruku**. Ako to uradiš, možda dobiješ jedan :gift: uskoro!
diff --git a/plugins/discourse-narrative-bot/config/locales/server.sw.yml b/plugins/discourse-narrative-bot/config/locales/server.sw.yml
index d19202b69b..deacf8ec98 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.sw.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.sw.yml
@@ -137,8 +137,6 @@ sw:
new_user_narrative:
reset_trigger: "mtumiaji mpya"
cert_title: "Utambulisho wa kumaliza mafunzo ya mtumiaji mpya"
- hello:
- title: ":robot: Habari!"
onebox:
instructions: |-
Baada ya hapo, je unaweza kunipa hivi viungo na mimi? Jibu na **kila kiungo kwenye mstari wake**, na itaongezeka kuonyesha muhtasari kuhusiana na hicho kiungo.
diff --git a/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml b/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml
index 19153f9dba..b52590cda2 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.tr_TR.yml
@@ -74,8 +74,6 @@ tr_TR:
help_trigger: 'yardımı görüntüle'
new_user_narrative:
reset_trigger: "yeni kullanıcı"
- hello:
- title: ":robot: Selamlar!"
quoting:
reply: |-
Güzel iş, benim en favori alıntımı aldınız! :left_speech_bubble:
diff --git a/plugins/discourse-narrative-bot/config/locales/server.ur.yml b/plugins/discourse-narrative-bot/config/locales/server.ur.yml
index 1461975152..5d9c97db6b 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.ur.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.ur.yml
@@ -140,8 +140,6 @@ ur:
new_user_narrative:
reset_trigger: "نیا صارف"
cert_title: "نیا صارف ٹیوٹوریل کی کامیاب تکمیل کے اعتراف میں"
- hello:
- title: ":robot: خوش آمدید!"
onebox:
instructions: |-
پھر، کیا آپ اِن لنکس میں سے ایک میرے ساتھ شئیر کر سکتے ہیں؟ **ایک لائن پر ایک لنک** کے طور پر جواب دیں، اور یہ خود بخود اُس کا ایک اچھا خلاصہ شامل کردے گا۔
diff --git a/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml b/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml
index 20e47b249a..1a2b22e92d 100644
--- a/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml
+++ b/plugins/discourse-narrative-bot/config/locales/server.zh_CN.yml
@@ -28,7 +28,7 @@ zh_CN:
bio: "你好,我不是真人,而是一个教你如何使用站点的机器人。你可以给我发消息或在任意地方提及 **`@%{discobot_username}`** 与我交互。"
timeout:
message: |-
- 你好,@%{username}。好久没见到你了,一切可好?
+ 你好, @%{username}。好久没见到你了,一切可好?
- 你可以在任意时候回复我。
@@ -112,7 +112,7 @@ zh_CN:
help_trigger: '帮助'
random_mention:
reply: |-
- 你好!想看看我能做什么吗?说`@%{discobot_username} %{help_trigger}`。
+ 你好!想看看我能做什么吗?说 `@%{discobot_username} %{help_trigger}`。
tracks: |-
我现在知道怎么做这些:
@@ -144,15 +144,14 @@ zh_CN:
title: "新用户欢迎教程完毕证明"
cert_title: "你已经成功完成新用户教程了"
hello:
- title: ":robot: 你好!"
message: |-
感谢你加入%{title},欢迎!
- - 我只是一个机器人,但[我们友好的工作人员](%{base_uri} / about)也可以帮到你,如果你需要接触一个人。
+ - 我只是一个机器人,但[我们友好的工作人员](%{base_uri}/ 关于)也可以帮到你,如果你需要接触一个人。
- - 出于安全原因,我们暂时限制新用户可以执行的操作。 当我们了解你时,你将获得新的能力(以及[徽章](%{base_uri} /徽章))。
+ - 出于安全原因,我们暂时限制新用户可以执行的操作。 当我们了解你时,你将获得新的能力(以及[徽章](%{base_uri}/badges))。
- - 我们始终相信[文明社区行为](%{base_uri} /指南)。
+ - 我们始终相信[文明社区行为](%{base_uri}/指南)。
onebox:
instructions: |-
接下来,你能分享下列链接给我么?回复**单独一行的链接**,然后它会自动展开显示一个有用的摘要。
@@ -255,7 +254,7 @@ zh_CN:
reply: |-
**谁在找我!?** :raised_hand: 我觉得是你! :wave: 好吧,我在这儿!感谢你提到我。 :ok_hand:
not_found: |-
- 我没有在任何地方看到我的名字 :frowning: 你可以再`@%{discobot_username}` 我试试?
+ 我没有在任何地方看到我的名字 :frowning: 你可以再 `@%{discobot_username}` 我试试?
(是的,我的用户名是**disco**,和 1970 年流行的舞蹈同名)
flag:
@@ -266,7 +265,7 @@ zh_CN:
我猜你知道要做什么了。点击**标记该贴**为不合适!
reply: |-
- [我们的管理人员](%{base_uri}/组/管理人员)将私下通知你的标记。如果有足够的社区成员标记帖子,作为预防措施它也会自动隐藏。(因为我实际上并没有写一篇讨厌的帖子:angel:我现在已经取消了标记。)
+ [我们的管理人员](%{base_uri}/groups/staff)将私下通知你的标记。如果有足够的社区成员标记帖子,作为预防措施它也会自动隐藏。(因为我实际上并没有写一篇讨厌的帖子:angel:我现在已经取消了标记。)
not_found: |-
啊不,我糟糕的帖子还没被标记。 :worried: 你能**标记**其为不合适的吗?不要忘记用显示更多按钮来显示每个帖子的更多操作。
search:
@@ -292,11 +291,11 @@ zh_CN:
哈…看起来你碰到了些麻烦。抱歉。你试过搜索**capybara**了吗?
end:
message: |-
- 感谢你坚持使用@%{username}!我为你做了这个,我想你已经赢得了它:
+ 感谢你坚持使用 @%{username}!我为你做了这个,我想你已经赢得了它:
%{certificate}
- 目前为止就这样了!查看[**我们最新的讨论主题**](%{base_uri}/最新)或[**在分类讨论**](%{base_uri}/分类)。:sunglasses:
+ 目前为止就这样了!查看[**我们最新的讨论主题**](%{base_uri}/latest)或[**在分类讨论**](%{base_uri}/categories)。:sunglasses:
(如果你想再次与我交谈以了解更多信息,请随时留言或提及`@%{discobot_username}`!)
certificate:
@@ -306,7 +305,7 @@ zh_CN:
cert_title: "你已经成功完成高级用户教程了"
title: ':arrow_up: 高级用户特性'
start_message: |-
- 作为_advanced_用户,你是否访问过[你的偏好设置页面](%{base_uri}/我的/设置)@%{username}? 有很多方法可以自定义你的体验,例如选择深色或浅色主题
+ 作为_advanced_用户,你是否访问过[你的偏好设置页面](%{base_uri}/my/preferences) @%{username}? 有很多方法可以自定义你的体验,例如选择深色或浅色主题
但我离题了,让我们开始吧!
edit:
@@ -335,7 +334,7 @@ zh_CN:
为了讨论的连贯性,删除不会立即发生,所以帖子将在之后移除。
recover:
- deleted_post_raw: '为什么@%{discobot_username}删除了我的帖子?:anguished:'
+ deleted_post_raw: '为什么 @%{discobot_username}删除了我的帖子?:anguished:'
instructions: |-
哦不!看起来我不小心删除了我刚刚给你创建的一个新帖子。
@@ -371,7 +370,7 @@ zh_CN:
reply: |-
太棒了!我希望你没有把这个话题静音,因为我有时会说话有点健谈:grin:。
- 请注意,当你回复主题或阅读主题超过几分钟时,它会自动设置为“跟踪”的通知级别。 你可以在[你的用户首选项](%{base_uri}/我的/设置)中更改此设置。
+ 请注意,当你回复主题或阅读主题超过几分钟时,它会自动设置为“跟踪”的通知级别。 你可以在[你的用户首选项](%{base_uri}/my/preferences)中更改此设置。
poll:
instructions: |-
你知道你可以在任何帖子中添加投票吗?试试用编辑器中的齿轮图标来**发起投票**。
diff --git a/plugins/discourse-narrative-bot/plugin.rb b/plugins/discourse-narrative-bot/plugin.rb
index c321a28f2c..b51e318147 100644
--- a/plugins/discourse-narrative-bot/plugin.rb
+++ b/plugins/discourse-narrative-bot/plugin.rb
@@ -204,7 +204,7 @@ after_initialize do
end
self.add_model_callback(PostAction, :after_commit, on: :create) do
- if self.user.enqueue_narrative_bot_job?
+ if self.post && self.user.enqueue_narrative_bot_job?
input =
case self.post_action_type_id
when *PostActionType.flag_types_without_custom.values
diff --git a/plugins/discourse-presence/config/locales/server.ru.yml b/plugins/discourse-presence/config/locales/server.ru.yml
index e74ddb2b05..b11ac84378 100644
--- a/plugins/discourse-presence/config/locales/server.ru.yml
+++ b/plugins/discourse-presence/config/locales/server.ru.yml
@@ -7,5 +7,5 @@
ru:
site_settings:
- presence_enabled: 'Показывать пользователей, которые отвечают или редактируют текущий пост прямо сейчас?'
+ presence_enabled: 'Показать пользователей, которые отвечают на текущую тему или редактируют текущий пост прямо сейчас?'
presence_max_users_shown: 'Максимальное отражаемое количество пользователей, которые отвечают прямо сейчас.'
diff --git a/plugins/discourse-presence/config/locales/server.sl.yml b/plugins/discourse-presence/config/locales/server.sl.yml
index 1d087b5e6a..f22db5aeed 100644
--- a/plugins/discourse-presence/config/locales/server.sl.yml
+++ b/plugins/discourse-presence/config/locales/server.sl.yml
@@ -8,4 +8,4 @@
sl:
site_settings:
presence_enabled: 'Pokaži uporabnice/ke, ki trenutno odgovarjajo na odprto temo ali urejujejo objavo?'
- presence_max_users_shown: 'Maksimalno prikazano število uporabnikov'
+ presence_max_users_shown: 'Maksimalno prikazano število uporabnikov.'
diff --git a/plugins/discourse-presence/config/locales/server.vi.yml b/plugins/discourse-presence/config/locales/server.vi.yml
index fa809fe162..a4eeab0d58 100644
--- a/plugins/discourse-presence/config/locales/server.vi.yml
+++ b/plugins/discourse-presence/config/locales/server.vi.yml
@@ -5,4 +5,6 @@
# To work with us on translations, join this project:
# https://www.transifex.com/projects/p/discourse-org/
-vi: {}
+vi:
+ site_settings:
+ presence_max_users_shown: 'Số lượng người dùng tối đa được hiển thị.'
diff --git a/plugins/poll/app/models/poll.rb b/plugins/poll/app/models/poll.rb
index 192e599ef8..fd5422a620 100644
--- a/plugins/poll/app/models/poll.rb
+++ b/plugins/poll/app/models/poll.rb
@@ -29,7 +29,7 @@ class Poll < ActiveRecord::Base
everyone: 1,
}
- validates :min, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
+ validates :min, numericality: { allow_nil: true, only_integer: true, greater_than_or_equal_to: 0 }
validates :max, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
validates :step, numericality: { allow_nil: true, only_integer: true, greater_than: 0 }
diff --git a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6 b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
index 8819590565..0ac240dee6 100644
--- a/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
+++ b/plugins/poll/assets/javascripts/widgets/discourse-poll.js.es6
@@ -8,6 +8,7 @@ import evenRound from "discourse/plugins/poll/lib/even-round";
import { avatarFor } from "discourse/widgets/post";
import round from "discourse/lib/round";
import { relativeAge } from "discourse/lib/formatter";
+import { userPath } from "discourse/lib/url";
function optionHtml(option) {
return new RawHtml({ html: `${option.html}` });
@@ -143,6 +144,7 @@ createWidget("discourse-poll-voters", {
return h("li", [
avatarFor("tiny", {
username: user.username,
+ url: userPath(user.username),
template: user.avatar_template
}),
" "
@@ -560,8 +562,8 @@ export default createWidget("discourse-poll", {
min() {
let min = parseInt(this.attrs.poll.get("min"), 10);
- if (isNaN(min) || min < 1) {
- min = 1;
+ if (isNaN(min) || min < 0) {
+ min = 0;
}
return min;
},
diff --git a/plugins/poll/config/locales/client.fa_IR.yml b/plugins/poll/config/locales/client.fa_IR.yml
index 4d09af82fc..5be5fb1c4b 100644
--- a/plugins/poll/config/locales/client.fa_IR.yml
+++ b/plugins/poll/config/locales/client.fa_IR.yml
@@ -15,6 +15,8 @@ fa_IR:
one: "مجموع آرا"
other: "مجموع آرا"
average_rating: "میانگین امتیاز: %{average}."
+ public:
+ title: "آرا عمومی هستند."
multiple:
help:
at_least_min_options:
diff --git a/plugins/poll/config/locales/client.it.yml b/plugins/poll/config/locales/client.it.yml
index f60dedebde..80b043b569 100644
--- a/plugins/poll/config/locales/client.it.yml
+++ b/plugins/poll/config/locales/client.it.yml
@@ -15,6 +15,8 @@ it:
one: "voto totale"
other: "voti totali"
average_rating: "Voto medio: %{average}."
+ public:
+ title: "I voti sono pubblici."
multiple:
help:
at_least_min_options:
diff --git a/plugins/poll/config/locales/server.en.yml b/plugins/poll/config/locales/server.en.yml
index 6d5274d641..8e4abe64a8 100644
--- a/plugins/poll/config/locales/server.en.yml
+++ b/plugins/poll/config/locales/server.en.yml
@@ -22,6 +22,8 @@ en:
poll_minimum_trust_level_to_create: "Define the minimum trust level needed to create polls."
poll:
+ invalid_argument: "Invalid value '%{value}' for argument '%{argument}'."
+
multiple_polls_without_name: "There are multiple polls without a name. Use the 'name' attribute to uniquely identify your polls."
multiple_polls_with_same_name: "There are multiple polls with the same name: %{name}. Use the 'name' attribute to uniquely identify your polls."
diff --git a/plugins/poll/config/locales/server.zh_CN.yml b/plugins/poll/config/locales/server.zh_CN.yml
index 4e7ab35096..19c789fc16 100644
--- a/plugins/poll/config/locales/server.zh_CN.yml
+++ b/plugins/poll/config/locales/server.zh_CN.yml
@@ -25,6 +25,9 @@ zh_CN:
default_poll_with_multiple_choices_has_invalid_parameters: "多选投票有无效选项。"
named_poll_with_multiple_choices_has_invalid_parameters: "“%{name}”多选投票有无效参数。"
requires_at_least_1_valid_option: "你必须选择至少 1 个有效的选项。"
+ edit_window_expired:
+ cannot_edit_default_poll_with_votes: "在创建%{minutes}分钟后,你无法更改投票。"
+ cannot_edit_named_poll_with_votes: "在创建%{minutes}分钟后,你无法更改投票名${name}。"
no_poll_with_this_name: "投票“%{name}”没有被关联到帖子。"
post_is_deleted: "帖子已经删除,无法操作。"
user_cant_post_in_topic: "你无法投票,因为你无法在这个主题里发帖"
diff --git a/plugins/poll/jobs/regular/close_poll.rb b/plugins/poll/jobs/regular/close_poll.rb
index 6e75241aab..1c3c77c9af 100644
--- a/plugins/poll/jobs/regular/close_poll.rb
+++ b/plugins/poll/jobs/regular/close_poll.rb
@@ -14,7 +14,8 @@ module Jobs
args[:post_id],
args[:poll_name],
"closed",
- Discourse.system_user
+ Discourse.system_user,
+ false
)
end
diff --git a/plugins/poll/lib/polls_updater.rb b/plugins/poll/lib/polls_updater.rb
index d90c6625f5..26acdf14bd 100644
--- a/plugins/poll/lib/polls_updater.rb
+++ b/plugins/poll/lib/polls_updater.rb
@@ -37,6 +37,7 @@ module DiscoursePoll
attributes = new_poll.slice(*POLL_ATTRIBUTES)
attributes["visibility"] = new_poll["public"] == "true" ? "everyone" : "secret"
attributes["close_at"] = Time.zone.parse(new_poll["close"]) rescue nil
+ attributes["status"] = old_poll["status"]
poll = ::Poll.new(attributes)
if is_different?(old_poll, poll, new_poll_options)
diff --git a/plugins/poll/lib/polls_validator.rb b/plugins/poll/lib/polls_validator.rb
index 790d8e2c01..fca71f89fc 100644
--- a/plugins/poll/lib/polls_validator.rb
+++ b/plugins/poll/lib/polls_validator.rb
@@ -1,5 +1,8 @@
module DiscoursePoll
class PollsValidator
+
+ MAX_VALUE = 2_147_483_647
+
def initialize(post)
@post = post
end
@@ -8,6 +11,8 @@ module DiscoursePoll
polls = {}
DiscoursePoll::Poll::extract(@post.raw, @post.topic_id, @post.user_id).each do |poll|
+ return false unless valid_arguments?(poll)
+ return false unless valid_numbers?(poll)
return false unless unique_poll_name?(polls, poll)
return false unless unique_options?(poll)
return false unless at_least_two_options?(poll)
@@ -21,6 +26,27 @@ module DiscoursePoll
private
+ def valid_arguments?(poll)
+ valid = true
+
+ if poll["type"].present? && !::Poll.types.has_key?(poll["type"])
+ @post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "type", value: poll["type"]))
+ valid = false
+ end
+
+ if poll["status"].present? && !::Poll.statuses.has_key?(poll["status"])
+ @post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "status", value: poll["status"]))
+ valid = false
+ end
+
+ if poll["results"].present? && !::Poll.results.has_key?(poll["results"])
+ @post.errors.add(:base, I18n.t("poll.invalid_argument", argument: "results", value: poll["results"]))
+ valid = false
+ end
+
+ valid
+ end
+
def unique_poll_name?(polls, poll)
if polls.has_key?(poll["name"])
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
@@ -96,5 +122,45 @@ module DiscoursePoll
true
end
+
+ def valid_numbers?(poll)
+ return true if poll["type"] != "number"
+
+ valid = true
+
+ min = poll["min"].to_i
+ max = (poll["max"].presence || MAX_VALUE).to_i
+ step = (poll["step"].presence || 1).to_i
+
+ if min < 0
+ @post.errors.add(:base, "Min #{I18n.t("errors.messages.greater_than", count: 0)}")
+ valid = false
+ elsif min > MAX_VALUE
+ @post.errors.add(:base, "Min #{I18n.t("errors.messages.less_than", count: MAX_VALUE)}")
+ valid = false
+ end
+
+ if max < min
+ @post.errors.add(:base, "Max #{I18n.t("errors.messages.greater_than", count: "min")}")
+ valid = false
+ elsif max > MAX_VALUE
+ @post.errors.add(:base, "Max #{I18n.t("errors.messages.less_than", count: MAX_VALUE)}")
+ valid = false
+ end
+
+ if step <= 0
+ @post.errors.add(:base, "Step #{I18n.t("errors.messages.greater_than", count: 0)}")
+ valid = false
+ elsif ((max - min + 1) / step) < 2
+ if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
+ @post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_2_options"))
+ else
+ @post.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_2_options", name: poll["name"]))
+ end
+ valid = false
+ end
+
+ valid
+ end
end
end
diff --git a/plugins/poll/plugin.rb b/plugins/poll/plugin.rb
index 9d62323c55..b0bc4cd56d 100644
--- a/plugins/poll/plugin.rb
+++ b/plugins/poll/plugin.rb
@@ -108,28 +108,34 @@ after_initialize do
end
end
- def toggle_status(post_id, poll_name, status, user)
+ def toggle_status(post_id, poll_name, status, user, raise_errors = true)
Poll.transaction do
post = Post.find_by(id: post_id)
# post must not be deleted
if post.nil? || post.trashed?
- raise StandardError.new I18n.t("poll.post_is_deleted")
+ raise StandardError.new I18n.t("poll.post_is_deleted") if raise_errors
+ return
end
# topic must not be archived
if post.topic&.archived
- raise StandardError.new I18n.t("poll.topic_must_be_open_to_toggle_status")
+ raise StandardError.new I18n.t("poll.topic_must_be_open_to_toggle_status") if raise_errors
+ return
end
# either staff member or OP
unless post.user_id == user&.id || user&.staff?
- raise StandardError.new I18n.t("poll.only_staff_or_op_can_toggle_status")
+ raise StandardError.new I18n.t("poll.only_staff_or_op_can_toggle_status") if raise_errors
+ return
end
poll = Poll.find_by(post_id: post_id, name: poll_name)
- raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) unless poll
+ if !poll
+ raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) if raise_errors
+ return
+ end
poll.status = status
poll.save!
diff --git a/plugins/poll/spec/lib/polls_updater_spec.rb b/plugins/poll/spec/lib/polls_updater_spec.rb
index 461f68839e..43b32840f8 100644
--- a/plugins/poll/spec/lib/polls_updater_spec.rb
+++ b/plugins/poll/spec/lib/polls_updater_spec.rb
@@ -59,6 +59,34 @@ describe DiscoursePoll::PollsUpdater do
expect(message).to be(nil)
end
+ describe "when editing" do
+
+ let(:raw) do
+ <<~RAW
+ This is a new poll with three options.
+
+ [poll type=multiple results=always min=1 max=2]
+ * first
+ * second
+ * third
+ [/poll]
+ RAW
+ end
+
+ let(:post) { Fabricate(:post, raw: raw) }
+
+ it "works if poll is closed and unmodified" do
+ DiscoursePoll::Poll.vote(post.id, "poll", ["e55de753c08b93d04d677ce05e942d3c"], post.user)
+ DiscoursePoll::Poll.toggle_status(post.id, "poll", "closed", post.user)
+
+ freeze_time (SiteSetting.poll_edit_window_mins + 1).minutes.from_now
+ update(post, DiscoursePoll::PollsValidator.new(post).validate_polls)
+
+ expect(post.errors[:base].size).to equal(0)
+ end
+
+ end
+
describe "deletes polls" do
it "that were removed" do
diff --git a/plugins/poll/spec/lib/polls_validator_spec.rb b/plugins/poll/spec/lib/polls_validator_spec.rb
index 56cd7d17b8..ee2d4b37e7 100644
--- a/plugins/poll/spec/lib/polls_validator_spec.rb
+++ b/plugins/poll/spec/lib/polls_validator_spec.rb
@@ -5,6 +5,53 @@ describe ::DiscoursePoll::PollsValidator do
subject { described_class.new(post) }
describe "#validate_polls" do
+ it "ensures that polls have valid arguments" do
+ raw = <<~RAW
+ [poll type=not_good1 status=not_good2 results=not_good3]
+ * 1
+ * 2
+ [/poll]
+ RAW
+
+ post.raw = raw
+ expect(post.valid?).to eq(false)
+
+ expect(post.errors[:base]).to include(I18n.t("poll.invalid_argument", argument: "type", value: "not_good1"))
+ expect(post.errors[:base]).to include(I18n.t("poll.invalid_argument", argument: "status", value: "not_good2"))
+ expect(post.errors[:base]).to include(I18n.t("poll.invalid_argument", argument: "results", value: "not_good3"))
+ end
+
+ it "ensures that all possible values are valid" do
+ raw = <<~RAW
+ [poll type=regular result=always]
+ * 1
+ * 2
+ [/poll]
+ RAW
+
+ post.raw = raw
+ expect(post.valid?).to eq(true)
+
+ raw = <<~RAW
+ [poll type=multiple result=on_vote min=1 max=2]
+ * 1
+ * 2
+ * 3
+ [/poll]
+ RAW
+
+ post.raw = raw
+ expect(post.valid?).to eq(true)
+
+ raw = <<~RAW
+ [poll type=number result=on_close min=3 max=7]
+ [/poll]
+ RAW
+
+ post.raw = raw
+ expect(post.valid?).to eq(true)
+ end
+
it "ensure that polls have unique names" do
raw = <<~RAW
[poll]
@@ -18,7 +65,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.multiple_polls_without_name")
@@ -36,7 +84,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.multiple_polls_with_same_name", name: "test")
@@ -51,7 +100,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_must_have_different_options")
@@ -64,7 +114,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.named_poll_must_have_different_options", name: "test")
@@ -78,7 +129,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_must_have_at_least_2_options")
@@ -90,7 +142,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.named_poll_must_have_at_least_2_options", name: "test")
@@ -108,7 +161,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(I18n.t(
"poll.default_poll_must_have_less_options",
@@ -123,7 +177,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(I18n.t(
"poll.named_poll_must_have_less_options",
@@ -141,7 +196,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
@@ -155,7 +211,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: "test")
@@ -170,7 +227,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
@@ -185,14 +243,15 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
)
end
- it "ensure that min > 0" do
+ it "ensure that min >= 0" do
raw = <<~RAW
[poll type=multiple min=-1]
* 1
@@ -200,14 +259,15 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
)
end
- it "ensure that min != 0" do
+ it "ensure that min cannot be 0" do
raw = <<~RAW
[poll type=multiple min=0]
* 1
@@ -215,11 +275,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
-
- expect(post.errors[:base]).to include(
- I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
- )
+ post.raw = raw
+ expect(post.valid?).to eq(false)
end
it "ensure that min != number of options" do
@@ -230,7 +287,8 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
@@ -245,12 +303,37 @@ describe ::DiscoursePoll::PollsValidator do
[/poll]
RAW
- expect(post.update_attributes(raw: raw)).to eq(false)
+ post.raw = raw
+ expect(post.valid?).to eq(false)
expect(post.errors[:base]).to include(
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
)
end
end
+
+ it "number type polls are validated" do
+ raw = <<~RAW
+ [poll type=number min=-5 max=-10 step=-1]
+ [/poll]
+ RAW
+
+ post.raw = raw
+ expect(post.valid?).to eq(false)
+ expect(post.errors[:base]).to include("Min #{I18n.t("errors.messages.greater_than", count: 0)}")
+ expect(post.errors[:base]).to include("Max #{I18n.t("errors.messages.greater_than", count: "min")}")
+ expect(post.errors[:base]).to include("Step #{I18n.t("errors.messages.greater_than", count: 0)}")
+
+ raw = <<~RAW
+ [poll type=number min=9999999999 max=9999999999 step=1]
+ [/poll]
+ RAW
+
+ post.raw = raw
+ expect(post.valid?).to eq(false)
+ expect(post.errors[:base]).to include("Min #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}")
+ expect(post.errors[:base]).to include("Max #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}")
+ expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_at_least_2_options"))
+ end
end
end
diff --git a/plugins/poll/test/javascripts/acceptance/polls-test.js.es6 b/plugins/poll/test/javascripts/acceptance/polls-test.js.es6
index 1f4dbf4f5b..4a235701c7 100644
--- a/plugins/poll/test/javascripts/acceptance/polls-test.js.es6
+++ b/plugins/poll/test/javascripts/acceptance/polls-test.js.es6
@@ -2023,6 +2023,11 @@ test("Public number poll", async assert => {
"it should display the right number of voters"
);
+ assert.ok(
+ find(".poll-voters:first li:first a").attr("href"),
+ "user URL exists"
+ );
+
await click(".poll-voters-toggle-expand:first a");
assert.equal(
diff --git a/public/403.sl.html b/public/403.sl.html
index 49d071366c..84755de9d1 100644
--- a/public/403.sl.html
+++ b/public/403.sl.html
@@ -1,7 +1,7 @@
-Tega ne morete storitii (403)
+To dejanje ni dovoljeno (403)
")).to eq("")
end
- it "handles divs within spans" do
- html = "
1st paragraph
2nd paragraph
"
- expect(html_to_markdown(html)).to eq("1st paragraph\n2nd paragraph")
+ it "handles
and
within " do
+ html = "
1st paragraph
2nd paragraph
3rd paragraph
"
+ expect(html_to_markdown(html)).to eq("1st paragraph\n2nd paragraph\n\n3rd paragraph")
+ end
+
+ it "handles
and
within " do
+ html = "1st paragraph 2nd paragraph
3rd paragraph
4th paragraph
"
+ expect(html_to_markdown(html)).to eq("1st paragraph\n2nd paragraph\n3rd paragraph\n\n4th paragraph")
end
context "with an oddly placed " do
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 64ce1a3a40..1e0cfe9c73 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -287,7 +287,7 @@ describe PrettyText do
end
it 'should not convert mentions to links' do
- user = Fabricate(:user)
+ _user = Fabricate(:user)
expect(PrettyText.cook('hi @user')).to eq('
hi @user
')
end
@@ -579,6 +579,17 @@ describe PrettyText do
expect(PrettyText.excerpt(nil, 100)).to eq('')
end
+ it "handles custom bbcode excerpt" do
+ raw = <<~RAW
+ [excerpt]
+ hello [site](https://site.com)
+ [/excerpt]
+ more stuff
+ RAW
+ post = Fabricate(:post, raw: raw)
+ expect(post.excerpt).to eq("hello site")
+ end
+
it "handles span excerpt at the beginning of a post" do
expect(PrettyText.excerpt("hi test", 100)).to eq('hi')
post = Fabricate(:post, raw: "hi test")
diff --git a/spec/components/theme_settings_manager_spec.rb b/spec/components/theme_settings_manager_spec.rb
index 1dd8a93d06..feb0382298 100644
--- a/spec/components/theme_settings_manager_spec.rb
+++ b/spec/components/theme_settings_manager_spec.rb
@@ -3,8 +3,8 @@ require 'theme_settings_manager'
describe ThemeSettingsManager do
+ let(:theme) { Fabricate(:theme) }
let(:theme_settings) do
- theme = Fabricate(:theme)
yaml = File.read("#{Rails.root}/spec/fixtures/theme_settings/valid_settings.yaml")
theme.set_field(target: :settings, name: "yaml", value: yaml)
theme.save!
@@ -37,12 +37,15 @@ describe ThemeSettingsManager do
expect(bool_setting.value).to eq(true) # default
bool_setting.value = "true"
+ theme.reload
expect(bool_setting.value).to eq(true)
bool_setting.value = "falsse" # intentionally misspelled
+ theme.reload
expect(bool_setting.value).to eq(false)
bool_setting.value = true
+ theme.reload
expect(bool_setting.value).to eq(true)
end
end
@@ -51,15 +54,19 @@ describe ThemeSettingsManager do
it "is always an integer" do
int_setting = find_by_name(:integer_setting)
int_setting.value = 1.6
+ theme.reload
expect(int_setting.value).to eq(1)
int_setting.value = "4.3"
+ theme.reload
expect(int_setting.value).to eq(4)
int_setting.value = "10"
+ theme.reload
expect(int_setting.value).to eq(10)
int_setting.value = "text"
+ theme.reload
expect(int_setting.value).to eq(0)
end
@@ -69,9 +76,11 @@ describe ThemeSettingsManager do
expect { int_setting.value = 61 }.to raise_error(Discourse::InvalidParameters)
int_setting.value = 60
+ theme.reload
expect(int_setting.value).to eq(60)
int_setting.value = 1
+ theme.reload
expect(int_setting.value).to eq(1)
end
end
@@ -80,12 +89,15 @@ describe ThemeSettingsManager do
it "is always a float" do
float_setting = find_by_name(:float_setting)
float_setting.value = 1.615
+ theme.reload
expect(float_setting.value).to eq(1.615)
float_setting.value = "3.1415"
+ theme.reload
expect(float_setting.value).to eq(3.1415)
float_setting.value = 10
+ theme.reload
expect(float_setting.value).to eq(10)
end
@@ -96,6 +108,7 @@ describe ThemeSettingsManager do
expect { float_setting.value = "text" }.to raise_error(Discourse::InvalidParameters)
float_setting.value = 9.521
+ theme.reload
expect(float_setting.value).to eq(9.521)
end
end
@@ -106,9 +119,11 @@ describe ThemeSettingsManager do
expect { string_setting.value = "a" }.to raise_error(Discourse::InvalidParameters)
string_setting.value = "ab"
+ theme.reload
expect(string_setting.value).to eq("ab")
string_setting.value = "ab" * 10
+ theme.reload
expect(string_setting.value).to eq("ab" * 10)
expect { string_setting.value = ("a" * 21) }.to raise_error(Discourse::InvalidParameters)
diff --git a/spec/components/wizard/wizard_builder_spec.rb b/spec/components/wizard/wizard_builder_spec.rb
index 78dc44aa4d..bcdf257983 100644
--- a/spec/components/wizard/wizard_builder_spec.rb
+++ b/spec/components/wizard/wizard_builder_spec.rb
@@ -1,6 +1,11 @@
require 'rails_helper'
require 'wizard'
require 'wizard/builder'
+require 'global_path'
+
+class GlobalPathInstance
+ extend GlobalPath
+end
describe Wizard::Builder do
let(:moderator) { Fabricate.build(:moderator) }
@@ -49,9 +54,9 @@ describe Wizard::Builder do
logo_small_field = fields.last
expect(logo_field.id).to eq('logo')
- expect(logo_field.value).to eq(upload.url)
+ expect(logo_field.value).to eq(GlobalPathInstance.full_cdn_url(upload.url))
expect(logo_small_field.id).to eq('logo_small')
- expect(logo_small_field.value).to eq(upload2.url)
+ expect(logo_small_field.value).to eq(GlobalPathInstance.full_cdn_url(upload2.url))
end
end
@@ -70,9 +75,9 @@ describe Wizard::Builder do
apple_touch_icon_field = fields.last
expect(favicon_field.id).to eq('favicon')
- expect(favicon_field.value).to eq(upload.url)
+ expect(favicon_field.value).to eq(GlobalPathInstance.full_cdn_url(upload.url))
expect(apple_touch_icon_field.id).to eq('apple_touch_icon')
- expect(apple_touch_icon_field.value).to eq(upload2.url)
+ expect(apple_touch_icon_field.value).to eq(GlobalPathInstance.full_cdn_url(upload2.url))
end
end
diff --git a/spec/fixtures/images/pngquant.png b/spec/fixtures/images/pngquant.png
new file mode 100644
index 0000000000..2ffb274f20
Binary files /dev/null and b/spec/fixtures/images/pngquant.png differ
diff --git a/spec/lib/backup_restore/s3_backup_store_spec.rb b/spec/lib/backup_restore/s3_backup_store_spec.rb
index 3020cabe0c..3bba9b9c32 100644
--- a/spec/lib/backup_restore/s3_backup_store_spec.rb
+++ b/spec/lib/backup_restore/s3_backup_store_spec.rb
@@ -81,11 +81,19 @@ describe BackupRestore::S3BackupStore do
before { create_backups }
after(:all) { remove_backups }
- it "doesn't delete files when cleanup is disabled" do
- SiteSetting.maximum_backups = 1
- SiteSetting.s3_disable_cleanup = true
+ describe "#delete_old" do
+ it "doesn't delete files when cleanup is disabled" do
+ SiteSetting.maximum_backups = 1
+ SiteSetting.s3_disable_cleanup = true
- expect { store.delete_old }.to_not change { store.files }
+ expect { store.delete_old }.to_not change { store.files }
+ end
+ end
+
+ describe "#stats" do
+ it "returns nil for 'free_bytes'" do
+ expect(store.stats[:free_bytes]).to be_nil
+ end
end
end
diff --git a/spec/lib/backup_restore/shared_examples_for_backup_store.rb b/spec/lib/backup_restore/shared_examples_for_backup_store.rb
index 9a66186bcc..921139a2c8 100644
--- a/spec/lib/backup_restore/shared_examples_for_backup_store.rb
+++ b/spec/lib/backup_restore/shared_examples_for_backup_store.rb
@@ -29,6 +29,17 @@ shared_examples "backup store" do
expect(store.latest_file).to be_nil
end
end
+
+ describe "#stats" do
+ it "works when there are no files" do
+ stats = store.stats
+
+ expect(stats[:used_bytes]).to eq(0)
+ expect(stats).to have_key(:free_bytes)
+ expect(stats[:count]).to eq(0)
+ expect(stats[:last_backup_taken_at]).to be_nil
+ end
+ end
end
context "with backup files" do
@@ -69,6 +80,18 @@ shared_examples "backup store" do
end
end
+ describe "#reset_cache" do
+ it "resets the storage stats report" do
+ report_type = "storage_stats"
+ report = Report.find(report_type)
+ Report.cache(report, 35.minutes)
+ expect(Report.find_cached(report_type)).to be_present
+
+ store.reset_cache
+ expect(Report.find_cached(report_type)).to be_nil
+ end
+ end
+
describe "#delete_old" do
it "does nothing if the number of files is <= maximum_backups" do
SiteSetting.maximum_backups = 3
@@ -166,6 +189,17 @@ shared_examples "backup store" do
end
end
end
+
+ describe "#stats" do
+ it "returns the correct stats" do
+ stats = store.stats
+
+ expect(stats[:used_bytes]).to eq(57)
+ expect(stats).to have_key(:free_bytes)
+ expect(stats[:count]).to eq(3)
+ expect(stats[:last_backup_taken_at]).to eq(Time.parse("2018-09-13T15:10:00Z"))
+ end
+ end
end
end
diff --git a/spec/lib/db_helper_spec.rb b/spec/lib/db_helper_spec.rb
index e9d796257c..ef9105ca03 100644
--- a/spec/lib/db_helper_spec.rb
+++ b/spec/lib/db_helper_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe DbHelper do
it 'allows tables to be excluded from scanning' do
post = Fabricate(:post, cooked: "test")
- DbHelper.remap("test", "something else", exclude_tables: %w{posts})
+ DbHelper.remap("test", "something else", excluded_tables: %w{posts})
expect(post.reload.cooked).to eq('test')
end
diff --git a/spec/lib/site_settings/validations_spec.rb b/spec/lib/site_settings/validations_spec.rb
new file mode 100644
index 0000000000..66de975da0
--- /dev/null
+++ b/spec/lib/site_settings/validations_spec.rb
@@ -0,0 +1,80 @@
+require 'rails_helper'
+require 'site_settings/validations'
+
+describe SiteSettings::Validations do
+ subject { Class.new.include(described_class).new }
+
+ context "s3 buckets reusage" do
+ let(:error_message) { I18n.t("errors.site_settings.s3_bucket_reused") }
+
+ shared_examples "s3 bucket validation" do
+ def change_bucket_value(value)
+ SiteSetting.public_send("#{other_setting_name}=", value)
+ end
+
+ it "shouldn't raise an error when both buckets are blank" do
+ change_bucket_value("")
+ validate("")
+ end
+
+ it "shouldn't raise an error when only one bucket is set" do
+ change_bucket_value("")
+ validate("my-awesome-bucket")
+ end
+
+ it "shouldn't raise an error when both buckets are equal, but use a different path" do
+ change_bucket_value("my-awesome-bucket/foo")
+ validate("my-awesome-bucket/bar")
+ end
+
+ it "should raise an error when both buckets are equal" do
+ change_bucket_value("my-awesome-bucket")
+ expect { validate("my-awesome-bucket") }.to raise_error(Discourse::InvalidParameters, error_message)
+ end
+
+ it "should raise an error when both buckets are equal except for a trailing slash" do
+ change_bucket_value("my-awesome-bucket/")
+ expect { validate("my-awesome-bucket") }.to raise_error(Discourse::InvalidParameters, error_message)
+
+ change_bucket_value("my-awesome-bucket")
+ expect { validate("my-awesome-bucket/") }.to raise_error(Discourse::InvalidParameters, error_message)
+ end
+ end
+
+ describe "#validate_s3_backup_bucket" do
+ let(:other_setting_name) { "s3_upload_bucket" }
+
+ def validate(new_value)
+ subject.validate_s3_backup_bucket(new_value)
+ end
+
+ it_behaves_like "s3 bucket validation"
+
+ it "shouldn't raise an error when the 's3_backup_bucket' is a subdirectory of 's3_upload_bucket'" do
+ SiteSetting.s3_upload_bucket = "my-awesome-bucket"
+ validate("my-awesome-bucket/backups")
+
+ SiteSetting.s3_upload_bucket = "my-awesome-bucket/foo"
+ validate("my-awesome-bucket/foo/backups")
+ end
+ end
+
+ describe "#validate_s3_upload_bucket" do
+ let(:other_setting_name) { "s3_backup_bucket" }
+
+ def validate(new_value)
+ subject.validate_s3_upload_bucket(new_value)
+ end
+
+ it_behaves_like "s3 bucket validation"
+
+ it "should raise an error when the 's3_upload_bucket' is a subdirectory of 's3_backup_bucket'" do
+ SiteSetting.s3_backup_bucket = "my-awesome-bucket"
+ expect { validate("my-awesome-bucket/uploads") }.to raise_error(Discourse::InvalidParameters, error_message)
+
+ SiteSetting.s3_backup_bucket = "my-awesome-bucket/foo"
+ expect { validate("my-awesome-bucket/foo/uploads") }.to raise_error(Discourse::InvalidParameters, error_message)
+ end
+ end
+ end
+end
diff --git a/spec/lib/upload_creator_spec.rb b/spec/lib/upload_creator_spec.rb
index 04ac72f00b..897c946aee 100644
--- a/spec/lib/upload_creator_spec.rb
+++ b/spec/lib/upload_creator_spec.rb
@@ -98,6 +98,27 @@ RSpec.describe UploadCreator do
end
end
+ describe 'pngquant' do
+ let(:filename) { "pngquant.png" }
+ let(:file) { file_from_fixtures(filename) }
+
+ it 'should apply pngquant to optimized images' do
+ upload = UploadCreator.new(file, filename,
+ pasted: true,
+ force_optimize: true
+ ).create_for(user.id)
+
+ # no optimisation possible without losing details
+ expect(upload.filesize).to eq(9558)
+
+ thumbnail_size = upload.get_optimized_image(upload.width, upload.height, {}).filesize
+
+ # pngquant will lose some colors causing some extra size reduction
+ expect(thumbnail_size).to be < 7500
+ end
+
+ end
+
describe 'converting to jpeg' do
let(:filename) { "should_be_jpeg.png" }
let(:file) { file_from_fixtures(filename) }
diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb
index 89a66214a2..2406c25a40 100644
--- a/spec/models/category_spec.rb
+++ b/spec/models/category_spec.rb
@@ -332,6 +332,15 @@ describe Category do
expect(Permalink.count).to eq(0)
end
+ it "correctly creates permalink when category slug is changed in subfolder install" do
+ GlobalSetting.stubs(:relative_url_root).returns('/forum')
+ Discourse.stubs(:base_uri).returns("/forum")
+ old_url = @category.url
+ @category.update_attributes(slug: 'new-category')
+ permalink = Permalink.last
+ expect(permalink.url).to eq(old_url[1..-1])
+ end
+
it "should not set its description topic to auto-close" do
category = Fabricate(:category, name: 'Closing Topics', auto_close_hours: 1)
expect(category.topic.public_topic_timer).to eq(nil)
diff --git a/spec/models/optimized_image_spec.rb b/spec/models/optimized_image_spec.rb
index 1eefbb01c3..35e95cd9be 100644
--- a/spec/models/optimized_image_spec.rb
+++ b/spec/models/optimized_image_spec.rb
@@ -6,7 +6,7 @@ describe OptimizedImage do
unless ENV["TRAVIS"]
describe '.crop' do
- it 'should work correctly' do
+ it 'should produce cropped images (requires ImageMagick 7)' do
tmp_path = "/tmp/cropped.png"
begin
@@ -17,18 +17,36 @@ describe OptimizedImage do
5
)
- fixture_path = "#{Rails.root}/spec/fixtures/images/cropped.png"
- fixture_hex = Digest::MD5.hexdigest(File.read(fixture_path))
+ # we don't want to deal with something new here every time image magick
+ # is upgraded or pngquant is upgraded, lets just test the basics ...
+ # cropped image should be less than 120 bytes
- cropped_hex = Digest::MD5.hexdigest(File.read(tmp_path))
+ cropped_size = File.size(tmp_path)
+
+ expect(cropped_size).to be < 120
+ expect(cropped_size).to be > 50
- expect(cropped_hex).to eq(fixture_hex)
ensure
File.delete(tmp_path) if File.exists?(tmp_path)
end
end
end
+ describe ".resize_instructions" do
+ let(:image) { "#{Rails.root}/spec/fixtures/images/logo.png" }
+
+ it "doesn't return any color options by default" do
+ instructions = described_class.resize_instructions(image, image, "50x50")
+ expect(instructions).to_not include('-colors')
+ end
+
+ it "supports an optional color option" do
+ instructions = described_class.resize_instructions(image, image, "50x50", colors: 12)
+ expect(instructions).to include('-colors')
+ end
+
+ end
+
describe '.resize' do
it 'should work correctly when extension is bad' do
@@ -113,7 +131,7 @@ describe OptimizedImage do
end
describe '.downsize' do
- it 'should work correctly' do
+ it 'should downsize logo (requires ImageMagick 7)' do
tmp_path = "/tmp/downsized.png"
begin
@@ -123,12 +141,10 @@ describe OptimizedImage do
"100x100\>"
)
- fixture_path = "#{Rails.root}/spec/fixtures/images/downsized.png"
- fixture_hex = Digest::MD5.hexdigest(File.read(fixture_path))
+ info = FastImage.new(tmp_path)
+ expect(info.size).to eq([100, 27])
+ expect(File.size(tmp_path)).to be < 2300
- downsized_hex = Digest::MD5.hexdigest(File.read(tmp_path))
-
- expect(downsized_hex).to eq(fixture_hex)
ensure
File.delete(tmp_path) if File.exists?(tmp_path)
end
@@ -186,7 +202,6 @@ describe OptimizedImage do
describe ".create_for" do
it "is able to 'optimize' an svg" do
-
# we don't really optimize anything, we simply copy
# but at least this confirms this actually works
@@ -239,6 +254,11 @@ describe OptimizedImage do
expect(oi.url).to eq("/internally/stored/optimized/image.png")
end
+ it "is able to change the format" do
+ oi = OptimizedImage.create_for(upload, 100, 200, format: 'gif')
+ expect(oi.url).to eq("/internally/stored/optimized/image.gif")
+ end
+
end
end
diff --git a/spec/models/post_mover_spec.rb b/spec/models/post_mover_spec.rb
index 667302353b..b4c6095e92 100644
--- a/spec/models/post_mover_spec.rb
+++ b/spec/models/post_mover_spec.rb
@@ -18,532 +18,647 @@ describe PostMover do
end
end
- context 'move_posts' do
- let(:user) { Fabricate(:user, admin: true) }
- let(:another_user) { Fabricate(:evil_trout) }
- let(:category) { Fabricate(:category, user: user) }
- let!(:topic) { Fabricate(:topic, user: user) }
- let!(:p1) { Fabricate(:post, topic: topic, user: user, created_at: 3.hours.ago) }
+ describe 'move_posts' do
+ context 'topics' do
+ let(:user) { Fabricate(:user, admin: true) }
+ let(:another_user) { Fabricate(:evil_trout) }
+ let(:category) { Fabricate(:category, user: user) }
+ let!(:topic) { Fabricate(:topic, user: user) }
+ let!(:p1) { Fabricate(:post, topic: topic, user: user, created_at: 3.hours.ago) }
- let!(:p2) do
- Fabricate(:post,
- topic: topic,
- user: another_user,
- raw: "Has a link to [evil trout](http://eviltrout.com) which is a cool site.",
- reply_to_post_number: p1.post_number)
- end
-
- let!(:p3) { Fabricate(:post, topic: topic, reply_to_post_number: p1.post_number, user: user) }
- let!(:p4) { Fabricate(:post, topic: topic, reply_to_post_number: p2.post_number, user: user) }
- let!(:p5) { Fabricate(:post) }
- let(:p6) { Fabricate(:post, topic: topic) }
-
- before do
- SiteSetting.tagging_enabled = true
- SiteSetting.queue_jobs = false
- p1.replies << p3
- p2.replies << p4
- UserActionCreator.enable
- @like = PostAction.act(another_user, p4, PostActionType.types[:like])
- end
-
- context 'success' do
-
- it "correctly handles notifications and bread crumbs" do
- old_topic = p2.topic
-
- old_topic_id = p2.topic_id
-
- topic.move_posts(user, [p2.id, p4.id, p6.id], title: "new testing topic name")
-
- p2.reload
- expect(p2.topic_id).not_to eq(old_topic_id)
- expect(p2.reply_to_post_number).to eq(nil)
- expect(p2.reply_to_user_id).to eq(nil)
-
- notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
-
- expect(notification.topic_id).to eq(p2.topic_id)
- expect(notification.post_number).to eq(1)
-
- # no message for person who made the move
- expect(p4.user.notifications.where(notification_type: Notification.types[:moved_post]).length).to eq(0)
-
- # notify at the right spot in the stream
- notification = p6.user.notifications.where(notification_type: Notification.types[:moved_post]).first
-
- expect(notification.topic_id).to eq(p2.topic_id)
-
- # this is the 3rd post we moved
- expect(notification.post_number).to eq(3)
-
- old_topic.reload
- move_message = old_topic.posts.find_by(post_number: 2)
- expect(move_message.post_type).to eq(Post.types[:small_action])
- expect(move_message.raw).to include("3 posts were split")
+ let!(:p2) do
+ Fabricate(:post,
+ topic: topic,
+ user: another_user,
+ raw: "Has a link to [evil trout](http://eviltrout.com) which is a cool site.",
+ reply_to_post_number: p1.post_number)
end
- end
+ let!(:p3) { Fabricate(:post, topic: topic, reply_to_post_number: p1.post_number, user: user) }
+ let!(:p4) { Fabricate(:post, topic: topic, reply_to_post_number: p2.post_number, user: user) }
+ let!(:p5) { Fabricate(:post) }
+ let(:p6) { Fabricate(:post, topic: topic) }
- context "errors" do
-
- it "raises an error when one of the posts doesn't exist" do
- expect { topic.move_posts(user, [1003], title: "new testing topic name") }.to raise_error(Discourse::InvalidParameters)
- end
-
- it "raises an error and does not create a topic if no posts were moved" do
- Topic.count.tap do |original_topic_count|
- expect {
- topic.move_posts(user, [], title: "new testing topic name")
- }.to raise_error(Discourse::InvalidParameters)
-
- expect(Topic.count).to eq original_topic_count
- end
- end
- end
-
- context "successfully moved" do
before do
- TopicUser.update_last_read(user, topic.id, p4.post_number, p4.post_number, 0)
- TopicLink.extract_from(p2)
+ SiteSetting.tagging_enabled = true
+ SiteSetting.queue_jobs = false
+ p1.replies << p3
+ p2.replies << p4
+ UserActionCreator.enable
+ @like = PostAction.act(another_user, p4, PostActionType.types[:like])
end
- context "post replies" do
- describe "when a post with replies is moved" do
- it "should update post replies correctly" do
- topic.move_posts(
- user,
- [p2.id],
- title: 'GOT is a very addictive show', category_id: category.id
- )
+ context 'success' do
- expect(p2.reload.replies).to eq([])
- end
+ it "correctly handles notifications and bread crumbs" do
+ old_topic = p2.topic
- it "doesn't raise errors with deleted replies" do
- p4.trash!
- topic.move_posts(
- user,
- [p2.id],
- title: 'GOT is a very addictive show', category_id: category.id
- )
+ old_topic_id = p2.topic_id
- expect(p2.reload.replies).to eq([])
- end
- end
-
- describe "when replies of a post have been moved" do
- it "should update post replies correctly" do
- p5 = Fabricate(
- :post,
- topic: topic,
- reply_to_post_number: p2.post_number,
- user: another_user
- )
-
- p2.replies << p5
-
- topic.move_posts(
- user,
- [p4.id],
- title: 'GOT is a very addictive show', category_id: category.id
- )
-
- expect(p2.reload.replies).to eq([p5])
- end
- end
-
- describe "when only one reply is left behind" do
- it "should update post replies correctly" do
- p5 = Fabricate(
- :post,
- topic: topic,
- reply_to_post_number: p2.post_number,
- user: another_user
- )
-
- p2.replies << p5
-
- topic.move_posts(
- user,
- [p2.id, p4.id],
- title: 'GOT is a very addictive show', category_id: category.id
- )
-
- expect(p2.reload.replies).to eq([p4])
- end
- end
- end
-
- context "to a new topic" do
-
- it "works correctly" do
- topic.expects(:add_moderator_post).once
- new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id, tags: ["tag1", "tag2"])
-
- expect(TopicUser.find_by(user_id: user.id, topic_id: topic.id).last_read_post_number).to eq(p3.post_number)
-
- expect(new_topic).to be_present
- expect(new_topic.featured_user1_id).to eq(p4.user_id)
- expect(new_topic.like_count).to eq(1)
-
- expect(new_topic.category).to eq(category)
- expect(new_topic.tags.pluck(:name)).to contain_exactly("tag1", "tag2")
- expect(topic.featured_user1_id).to be_blank
- expect(new_topic.posts.by_post_number).to match_array([p2, p4])
-
- new_topic.reload
- expect(new_topic.posts_count).to eq(2)
- expect(new_topic.highest_post_number).to eq(2)
-
- p4.reload
- expect(new_topic.last_post_user_id).to eq(p4.user_id)
- expect(new_topic.last_posted_at).to eq(p4.created_at)
- expect(new_topic.bumped_at).to eq(p4.created_at)
+ topic.move_posts(user, [p2.id, p4.id, p6.id], title: "new testing topic name")
p2.reload
- expect(p2.sort_order).to eq(1)
- expect(p2.post_number).to eq(1)
- expect(p2.topic_links.first.topic_id).to eq(new_topic.id)
-
- expect(p4.post_number).to eq(2)
- expect(p4.sort_order).to eq(2)
-
- topic.reload
- expect(topic.featured_user1_id).to be_blank
- expect(topic.like_count).to eq(0)
- expect(topic.posts_count).to eq(2)
- expect(topic.posts.by_post_number).to match_array([p1, p3])
- expect(topic.highest_post_number).to eq(p3.post_number)
-
- # both the like and was_liked user actions should be correct
- action = UserAction.find_by(user_id: another_user.id)
- expect(action.target_topic_id).to eq(new_topic.id)
- end
-
- it "moving all posts will close the topic" do
- topic.expects(:add_moderator_post).twice
- new_topic = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id], title: "new testing topic name", category_id: category.id)
- expect(new_topic).to be_present
-
- topic.reload
- expect(topic.closed).to eq(true)
- end
-
- it 'does not move posts that do not belong to the existing topic' do
- new_topic = topic.move_posts(
- user, [p2.id, p3.id, p5.id], title: 'Logan is a pretty good movie'
- )
-
- expect(new_topic.posts.pluck(:id).sort).to eq([p2.id, p3.id].sort)
- end
-
- it "uses default locale for moderator post" do
- I18n.locale = 'de'
-
- new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id)
- post = Post.find_by(topic_id: topic.id, post_type: Post.types[:small_action])
-
- expected_text = I18n.with_locale(:en) do
- I18n.t("move_posts.new_topic_moderator_post",
- count: 2,
- topic_link: "[#{new_topic.title}](#{new_topic.relative_url})")
- end
-
- expect(post.raw).to eq(expected_text)
- end
-
- it "does not try to move small action posts" do
- small_action = Fabricate(:post, topic: topic, raw: "A small action", post_type: Post.types[:small_action])
- new_topic = topic.move_posts(user, [p2.id, p4.id, small_action.id], title: "new testing topic name", category_id: category.id)
-
- expect(new_topic.posts_count).to eq(2)
- expect(small_action.topic_id).to eq(topic.id)
-
- moderator_post = topic.posts.last
- expect(moderator_post.raw).to include("2 posts were split")
- end
-
- it "forces resulting topic owner to watch the new topic" do
- new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id)
-
- expect(new_topic.posts_count).to eq(2)
-
- expect(TopicUser.exists?(
- user_id: another_user,
- topic_id: new_topic.id,
- notification_level: TopicUser.notification_levels[:watching],
- notifications_reason_id: TopicUser.notification_reasons[:created_topic]
- )).to eq(true)
- end
- end
-
- context "to an existing topic" do
- let!(:destination_topic) { Fabricate(:topic, user: another_user) }
- let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: another_user) }
-
- it "works correctly" do
- topic.expects(:add_moderator_post).once
- moved_to = topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id)
- expect(moved_to).to eq(destination_topic)
-
- # Check out new topic
- moved_to.reload
- expect(moved_to.posts_count).to eq(3)
- expect(moved_to.highest_post_number).to eq(3)
- expect(moved_to.user_id).to eq(destination_op.user_id)
- expect(moved_to.like_count).to eq(1)
- expect(moved_to.category_id).to eq(SiteSetting.uncategorized_category_id)
- p4.reload
- expect(moved_to.last_post_user_id).to eq(p4.user_id)
- expect(moved_to.last_posted_at).to eq(p4.created_at)
- expect(moved_to.bumped_at).to eq(p4.created_at)
-
- # Posts should be re-ordered
- p2.reload
- expect(p2.sort_order).to eq(2)
- expect(p2.post_number).to eq(2)
- expect(p2.topic_id).to eq(moved_to.id)
- expect(p2.reply_count).to eq(1)
+ expect(p2.topic_id).not_to eq(old_topic_id)
expect(p2.reply_to_post_number).to eq(nil)
+ expect(p2.reply_to_user_id).to eq(nil)
- expect(p4.post_number).to eq(3)
- expect(p4.sort_order).to eq(3)
- expect(p4.topic_id).to eq(moved_to.id)
- expect(p4.reply_count).to eq(0)
- expect(p4.reply_to_post_number).to eq(2)
-
- # Check out the original topic
- topic.reload
- expect(topic.posts_count).to eq(2)
- expect(topic.highest_post_number).to eq(3)
- expect(topic.featured_user1_id).to be_blank
- expect(topic.like_count).to eq(0)
- expect(topic.posts_count).to eq(2)
- expect(topic.posts.by_post_number).to match_array([p1, p3])
- expect(topic.highest_post_number).to eq(p3.post_number)
-
- # Should notify correctly
notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
expect(notification.topic_id).to eq(p2.topic_id)
- expect(notification.post_number).to eq(p2.post_number)
+ expect(notification.post_number).to eq(1)
- # Should update last reads
- expect(TopicUser.find_by(user_id: user.id, topic_id: topic.id).last_read_post_number).to eq(p3.post_number)
- end
+ # no message for person who made the move
+ expect(p4.user.notifications.where(notification_type: Notification.types[:moved_post]).length).to eq(0)
- it "moving all posts will close the topic" do
- topic.expects(:add_moderator_post).twice
- moved_to = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id], destination_topic_id: destination_topic.id)
- expect(moved_to).to be_present
+ # notify at the right spot in the stream
+ notification = p6.user.notifications.where(notification_type: Notification.types[:moved_post]).first
- topic.reload
- expect(topic.closed).to eq(true)
- end
+ expect(notification.topic_id).to eq(p2.topic_id)
- it "does not try to move small action posts" do
- small_action = Fabricate(:post, topic: topic, raw: "A small action", post_type: Post.types[:small_action])
- moved_to = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id, small_action.id], destination_topic_id: destination_topic.id)
+ # this is the 3rd post we moved
+ expect(notification.post_number).to eq(3)
- moved_to.reload
- expect(moved_to.posts_count).to eq(5)
- expect(small_action.topic_id).to eq(topic.id)
-
- moderator_post = topic.posts.find_by(post_number: 2)
- expect(moderator_post.raw).to include("4 posts were merged")
+ old_topic.reload
+ move_message = old_topic.posts.find_by(post_number: 2)
+ expect(move_message.post_type).to eq(Post.types[:small_action])
+ expect(move_message.raw).to include("3 posts were split")
end
end
- shared_examples "moves email related stuff" do
- it "moves incoming email" do
- Fabricate(:incoming_email, user: old_post.user, topic: old_post.topic, post: old_post)
+ context "errors" do
- new_topic = topic.move_posts(user, [old_post.id], title: "new testing topic name")
- new_post = new_topic.first_post
- email = new_post.incoming_email
-
- expect(email).to be_present
- expect(email.topic_id).to eq(new_topic.id)
- expect(email.post_id).to eq(new_post.id)
-
- expect(old_post.reload.incoming_email).to_not be_present unless old_post.id == new_post.id
+ it "raises an error when one of the posts doesn't exist" do
+ non_existent_post_id = Post.maximum(:id)&.next || 1
+ expect { topic.move_posts(user, [non_existent_post_id], title: "new testing topic name") }.to raise_error(Discourse::InvalidParameters)
end
- it "moves email log entries" do
- old_topic = old_post.topic
+ it "raises an error and does not create a topic if no posts were moved" do
+ Topic.count.tap do |original_topic_count|
+ expect {
+ topic.move_posts(user, [], title: "new testing topic name")
+ }.to raise_error(Discourse::InvalidParameters)
- 2.times do
- Fabricate(:email_log,
- user: old_post.user,
- post: old_post,
- email_type: :mailing_list
- )
+ expect(Topic.count).to eq original_topic_count
+ end
+ end
+ end
+
+ context "successfully moved" do
+ before do
+ TopicUser.update_last_read(user, topic.id, p4.post_number, p4.post_number, 0)
+ TopicLink.extract_from(p2)
+ end
+
+ context "post replies" do
+ describe "when a post with replies is moved" do
+ it "should update post replies correctly" do
+ topic.move_posts(
+ user,
+ [p2.id],
+ title: 'GOT is a very addictive show', category_id: category.id
+ )
+
+ expect(p2.reload.replies).to eq([])
+ end
+
+ it "doesn't raise errors with deleted replies" do
+ p4.trash!
+ topic.move_posts(
+ user,
+ [p2.id],
+ title: 'GOT is a very addictive show', category_id: category.id
+ )
+
+ expect(p2.reload.replies).to eq([])
+ end
end
- some_post = Fabricate(:post)
+ describe "when replies of a post have been moved" do
+ it "should update post replies correctly" do
+ p5 = Fabricate(
+ :post,
+ topic: topic,
+ reply_to_post_number: p2.post_number,
+ user: another_user
+ )
- Fabricate(:email_log,
- user: some_post.user,
- post: some_post,
- email_type: :mailing_list
- )
+ p2.replies << p5
- expect(EmailLog.where(post_id: old_post.id).count).to eq(2)
+ topic.move_posts(
+ user,
+ [p4.id],
+ title: 'GOT is a very addictive show', category_id: category.id
+ )
- new_topic = old_topic.move_posts(
- user,
- [old_post.id],
- title: "new testing topic name"
- )
+ expect(p2.reload.replies).to eq([p5])
+ end
+ end
- new_post = new_topic.first_post
+ describe "when only one reply is left behind" do
+ it "should update post replies correctly" do
+ p5 = Fabricate(
+ :post,
+ topic: topic,
+ reply_to_post_number: p2.post_number,
+ user: another_user
+ )
- expect(EmailLog.where(post_id: new_post.id).count).to eq(2)
+ p2.replies << p5
+
+ topic.move_posts(
+ user,
+ [p2.id, p4.id],
+ title: 'GOT is a very addictive show', category_id: category.id
+ )
+
+ expect(p2.reload.replies).to eq([p4])
+ end
+ end
end
- it "preserves post attributes" do
- old_post.update_columns(cook_method: Post.cook_methods[:email], via_email: true, raw_email: "raw email content")
+ context "to a new topic" do
- new_topic = old_post.topic.move_posts(user, [old_post.id], title: "new testing topic name")
- new_post = new_topic.first_post
+ it "works correctly" do
+ topic.expects(:add_moderator_post).once
+ new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id, tags: ["tag1", "tag2"])
- expect(new_post.cook_method).to eq(Post.cook_methods[:email])
- expect(new_post.via_email).to eq(true)
- expect(new_post.raw_email).to eq("raw email content")
+ expect(TopicUser.find_by(user_id: user.id, topic_id: topic.id).last_read_post_number).to eq(p3.post_number)
+
+ expect(new_topic).to be_present
+ expect(new_topic.featured_user1_id).to eq(p4.user_id)
+ expect(new_topic.like_count).to eq(1)
+
+ expect(new_topic.category).to eq(category)
+ expect(new_topic.tags.pluck(:name)).to contain_exactly("tag1", "tag2")
+ expect(topic.featured_user1_id).to be_blank
+ expect(new_topic.posts.by_post_number).to match_array([p2, p4])
+
+ new_topic.reload
+ expect(new_topic.posts_count).to eq(2)
+ expect(new_topic.highest_post_number).to eq(2)
+
+ p4.reload
+ expect(new_topic.last_post_user_id).to eq(p4.user_id)
+ expect(new_topic.last_posted_at).to eq(p4.created_at)
+ expect(new_topic.bumped_at).to eq(p4.created_at)
+
+ p2.reload
+ expect(p2.sort_order).to eq(1)
+ expect(p2.post_number).to eq(1)
+ expect(p2.topic_links.first.topic_id).to eq(new_topic.id)
+
+ expect(p4.post_number).to eq(2)
+ expect(p4.sort_order).to eq(2)
+
+ topic.reload
+ expect(topic.featured_user1_id).to be_blank
+ expect(topic.like_count).to eq(0)
+ expect(topic.posts_count).to eq(2)
+ expect(topic.posts.by_post_number).to match_array([p1, p3])
+ expect(topic.highest_post_number).to eq(p3.post_number)
+
+ # both the like and was_liked user actions should be correct
+ action = UserAction.find_by(user_id: another_user.id)
+ expect(action.target_topic_id).to eq(new_topic.id)
+ end
+
+ it "moving all posts will close the topic" do
+ topic.expects(:add_moderator_post).twice
+ new_topic = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id], title: "new testing topic name", category_id: category.id)
+ expect(new_topic).to be_present
+
+ topic.reload
+ expect(topic.closed).to eq(true)
+ end
+
+ it 'does not move posts that do not belong to the existing topic' do
+ new_topic = topic.move_posts(
+ user, [p2.id, p3.id, p5.id], title: 'Logan is a pretty good movie'
+ )
+
+ expect(new_topic.posts.pluck(:id).sort).to eq([p2.id, p3.id].sort)
+ end
+
+ it "uses default locale for moderator post" do
+ I18n.locale = 'de'
+
+ new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id)
+ post = Post.find_by(topic_id: topic.id, post_type: Post.types[:small_action])
+
+ expected_text = I18n.with_locale(:en) do
+ I18n.t("move_posts.new_topic_moderator_post",
+ count: 2,
+ entity: "topic",
+ topic_link: "[#{new_topic.title}](#{new_topic.relative_url})")
+ end
+
+ expect(post.raw).to eq(expected_text)
+ end
+
+ it "does not try to move small action posts" do
+ small_action = Fabricate(:post, topic: topic, raw: "A small action", post_type: Post.types[:small_action])
+ new_topic = topic.move_posts(user, [p2.id, p4.id, small_action.id], title: "new testing topic name", category_id: category.id)
+
+ expect(new_topic.posts_count).to eq(2)
+ expect(small_action.topic_id).to eq(topic.id)
+
+ moderator_post = topic.posts.last
+ expect(moderator_post.raw).to include("2 posts were split")
+ end
+
+ it "forces resulting topic owner to watch the new topic" do
+ new_topic = topic.move_posts(user, [p2.id, p4.id], title: "new testing topic name", category_id: category.id)
+
+ expect(new_topic.posts_count).to eq(2)
+
+ expect(TopicUser.exists?(
+ user_id: another_user,
+ topic_id: new_topic.id,
+ notification_level: TopicUser.notification_levels[:watching],
+ notifications_reason_id: TopicUser.notification_reasons[:created_topic]
+ )).to eq(true)
+ end
end
- end
- context "moving the first post" do
+ context "to an existing topic" do
+ let!(:destination_topic) { Fabricate(:topic, user: another_user) }
+ let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: another_user) }
+
+ it "works correctly" do
+ topic.expects(:add_moderator_post).once
+ moved_to = topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id)
+ expect(moved_to).to eq(destination_topic)
+
+ # Check out new topic
+ moved_to.reload
+ expect(moved_to.posts_count).to eq(3)
+ expect(moved_to.highest_post_number).to eq(3)
+ expect(moved_to.user_id).to eq(destination_op.user_id)
+ expect(moved_to.like_count).to eq(1)
+ expect(moved_to.category_id).to eq(SiteSetting.uncategorized_category_id)
+ p4.reload
+ expect(moved_to.last_post_user_id).to eq(p4.user_id)
+ expect(moved_to.last_posted_at).to eq(p4.created_at)
+ expect(moved_to.bumped_at).to eq(p4.created_at)
+
+ # Posts should be re-ordered
+ p2.reload
+ expect(p2.sort_order).to eq(2)
+ expect(p2.post_number).to eq(2)
+ expect(p2.topic_id).to eq(moved_to.id)
+ expect(p2.reply_count).to eq(1)
+ expect(p2.reply_to_post_number).to eq(nil)
+
+ expect(p4.post_number).to eq(3)
+ expect(p4.sort_order).to eq(3)
+ expect(p4.topic_id).to eq(moved_to.id)
+ expect(p4.reply_count).to eq(0)
+ expect(p4.reply_to_post_number).to eq(2)
+
+ # Check out the original topic
+ topic.reload
+ expect(topic.posts_count).to eq(2)
+ expect(topic.highest_post_number).to eq(3)
+ expect(topic.featured_user1_id).to be_blank
+ expect(topic.like_count).to eq(0)
+ expect(topic.posts_count).to eq(2)
+ expect(topic.posts.by_post_number).to match_array([p1, p3])
+ expect(topic.highest_post_number).to eq(p3.post_number)
+
+ # Should notify correctly
+ notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
+
+ expect(notification.topic_id).to eq(p2.topic_id)
+ expect(notification.post_number).to eq(p2.post_number)
+
+ # Should update last reads
+ expect(TopicUser.find_by(user_id: user.id, topic_id: topic.id).last_read_post_number).to eq(p3.post_number)
+ end
+
+ it "moving all posts will close the topic" do
+ topic.expects(:add_moderator_post).twice
+ moved_to = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id], destination_topic_id: destination_topic.id)
+ expect(moved_to).to be_present
+
+ topic.reload
+ expect(topic.closed).to eq(true)
+ end
+
+ it "does not try to move small action posts" do
+ small_action = Fabricate(:post, topic: topic, raw: "A small action", post_type: Post.types[:small_action])
+ moved_to = topic.move_posts(user, [p1.id, p2.id, p3.id, p4.id, small_action.id], destination_topic_id: destination_topic.id)
+
+ moved_to.reload
+ expect(moved_to.posts_count).to eq(5)
+ expect(small_action.topic_id).to eq(topic.id)
+
+ moderator_post = topic.posts.find_by(post_number: 2)
+ expect(moderator_post.raw).to include("4 posts were merged")
+ end
+ end
+
+ shared_examples "moves email related stuff" do
+ it "moves incoming email" do
+ Fabricate(:incoming_email, user: old_post.user, topic: old_post.topic, post: old_post)
+
+ new_topic = topic.move_posts(user, [old_post.id], title: "new testing topic name")
+ new_post = new_topic.first_post
+ email = new_post.incoming_email
+
+ expect(email).to be_present
+ expect(email.topic_id).to eq(new_topic.id)
+ expect(email.post_id).to eq(new_post.id)
+
+ expect(old_post.reload.incoming_email).to_not be_present unless old_post.id == new_post.id
+ end
+
+ it "moves email log entries" do
+ old_topic = old_post.topic
+
+ 2.times do
+ Fabricate(:email_log,
+ user: old_post.user,
+ post: old_post,
+ email_type: :mailing_list
+ )
+ end
+
+ some_post = Fabricate(:post)
+
+ Fabricate(:email_log,
+ user: some_post.user,
+ post: some_post,
+ email_type: :mailing_list
+ )
+
+ expect(EmailLog.where(post_id: old_post.id).count).to eq(2)
+
+ new_topic = old_topic.move_posts(
+ user,
+ [old_post.id],
+ title: "new testing topic name"
+ )
+
+ new_post = new_topic.first_post
+
+ expect(EmailLog.where(post_id: new_post.id).count).to eq(2)
+ end
+
+ it "preserves post attributes" do
+ old_post.update_columns(cook_method: Post.cook_methods[:email], via_email: true, raw_email: "raw email content")
+
+ new_topic = old_post.topic.move_posts(user, [old_post.id], title: "new testing topic name")
+ new_post = new_topic.first_post
+
+ expect(new_post.cook_method).to eq(Post.cook_methods[:email])
+ expect(new_post.via_email).to eq(true)
+ expect(new_post.raw_email).to eq("raw email content")
+ end
+ end
+
+ context "moving the first post" do
+
+ it "copies the OP, doesn't delete it" do
+ topic.expects(:add_moderator_post).once
+ new_topic = topic.move_posts(user, [p1.id, p2.id], title: "new testing topic name")
+
+ expect(new_topic).to be_present
+ expect(new_topic.posts.by_post_number.first.raw).to eq(p1.raw)
+ expect(new_topic.posts_count).to eq(2)
+ expect(new_topic.highest_post_number).to eq(2)
+
+ # First post didn't move
+ p1.reload
+ expect(p1.sort_order).to eq(1)
+ expect(p1.post_number).to eq(1)
+ expect(p1.topic_id).to eq(topic.id)
+ expect(p1.reply_count).to eq(0)
+
+ # New first post
+ new_first = new_topic.posts.where(post_number: 1).first
+ expect(new_first.reply_count).to eq(1)
+ expect(new_first.created_at).to be_within(1.second).of(p1.created_at)
+
+ # Second post is in a new topic
+ p2.reload
+ expect(p2.post_number).to eq(2)
+ expect(p2.sort_order).to eq(2)
+ expect(p2.topic_id).to eq(new_topic.id)
+ expect(p2.reply_to_post_number).to eq(1)
+ expect(p2.reply_count).to eq(0)
+
+ topic.reload
+ expect(topic.posts.by_post_number).to match_array([p1, p3, p4])
+ expect(topic.highest_post_number).to eq(p4.post_number)
+ end
+
+ it "preserves post actions in the new post" do
+ PostAction.act(another_user, p1, PostActionType.types[:like])
+
+ new_topic = topic.move_posts(user, [p1.id], title: "new testing topic name")
+ new_post = new_topic.posts.where(post_number: 1).first
+
+ expect(new_topic.like_count).to eq(1)
+ expect(new_post.like_count).to eq(1)
+ expect(new_post.post_actions.size).to eq(1)
+ end
+
+ it "preserves the custom_fields in the new post" do
+ custom_fields = { "some_field" => 'payload' }
+ p1.custom_fields = custom_fields
+ p1.save_custom_fields
+
+ new_topic = topic.move_posts(user, [p1.id], title: "new testing topic name")
+
+ expect(new_topic.first_post.custom_fields).to eq(custom_fields)
+ end
+
+ include_examples "moves email related stuff" do
+ let!(:old_post) { p1 }
+ end
+ end
+
+ context "moving replies" do
+ include_examples "moves email related stuff" do
+ let!(:old_post) { p3 }
+ end
+ end
+
+ context "to an existing topic with a deleted post" do
+
+ before do
+ topic.expects(:add_moderator_post)
+ end
+
+ let!(:destination_topic) { Fabricate(:topic, user: user) }
+ let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: user) }
+ let!(:destination_deleted_reply) { Fabricate(:post, topic: destination_topic, user: another_user) }
+ let(:moved_to) { topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id) }
+
+ it "works correctly" do
+ destination_deleted_reply.trash!
+
+ expect(moved_to).to eq(destination_topic)
+
+ # Check out new topic
+ moved_to.reload
+ expect(moved_to.posts_count).to eq(3)
+ expect(moved_to.highest_post_number).to eq(4)
+
+ # Posts should be re-ordered
+ p2.reload
+ expect(p2.sort_order).to eq(3)
+ expect(p2.post_number).to eq(3)
+ expect(p2.topic_id).to eq(moved_to.id)
+ expect(p2.reply_count).to eq(1)
+ expect(p2.reply_to_post_number).to eq(nil)
+
+ p4.reload
+ expect(p4.post_number).to eq(4)
+ expect(p4.sort_order).to eq(4)
+ expect(p4.topic_id).to eq(moved_to.id)
+ expect(p4.reply_to_post_number).to eq(p2.post_number)
+ end
+ end
+
+ context "to an existing closed topic" do
+ let!(:destination_topic) { Fabricate(:topic, closed: true) }
+
+ it "works correctly for admin" do
+ admin = Fabricate(:admin)
+ moved_to = topic.move_posts(admin, [p1.id, p2.id], destination_topic_id: destination_topic.id)
+ expect(moved_to).to be_present
+
+ moved_to.reload
+ expect(moved_to.posts_count).to eq(2)
+ expect(moved_to.highest_post_number).to eq(2)
+ end
+ end
+
+ it "skips validations when moving posts" do
+ p1.update_attribute(:raw, "foo")
+ p2.update_attribute(:raw, "bar")
- it "copies the OP, doesn't delete it" do
- topic.expects(:add_moderator_post).once
new_topic = topic.move_posts(user, [p1.id, p2.id], title: "new testing topic name")
expect(new_topic).to be_present
expect(new_topic.posts.by_post_number.first.raw).to eq(p1.raw)
+ expect(new_topic.posts.by_post_number.last.raw).to eq(p2.raw)
expect(new_topic.posts_count).to eq(2)
- expect(new_topic.highest_post_number).to eq(2)
-
- # First post didn't move
- p1.reload
- expect(p1.sort_order).to eq(1)
- expect(p1.post_number).to eq(1)
- expect(p1.topic_id).to eq(topic.id)
- expect(p1.reply_count).to eq(0)
-
- # New first post
- new_first = new_topic.posts.where(post_number: 1).first
- expect(new_first.reply_count).to eq(1)
- expect(new_first.created_at).to be_within(1.second).of(p1.created_at)
-
- # Second post is in a new topic
- p2.reload
- expect(p2.post_number).to eq(2)
- expect(p2.sort_order).to eq(2)
- expect(p2.topic_id).to eq(new_topic.id)
- expect(p2.reply_to_post_number).to eq(1)
- expect(p2.reply_count).to eq(0)
-
- topic.reload
- expect(topic.posts.by_post_number).to match_array([p1, p3, p4])
- expect(topic.highest_post_number).to eq(p4.post_number)
- end
-
- it "preserves post actions in the new post" do
- PostAction.act(another_user, p1, PostActionType.types[:like])
-
- new_topic = topic.move_posts(user, [p1.id], title: "new testing topic name")
- new_post = new_topic.posts.where(post_number: 1).first
-
- expect(new_topic.like_count).to eq(1)
- expect(new_post.like_count).to eq(1)
- expect(new_post.post_actions.size).to eq(1)
- end
-
- it "preserves the custom_fields in the new post" do
- custom_fields = { "some_field" => 'payload' }
- p1.custom_fields = custom_fields
- p1.save_custom_fields
-
- new_topic = topic.move_posts(user, [p1.id], title: "new testing topic name")
-
- expect(new_topic.first_post.custom_fields).to eq(custom_fields)
- end
-
- include_examples "moves email related stuff" do
- let!(:old_post) { p1 }
end
end
+ end
- context "moving replies" do
- include_examples "moves email related stuff" do
- let!(:old_post) { p3 }
- end
+ context 'messages' do
+ let(:user) { Fabricate(:user) }
+ let(:admin) { Fabricate(:admin) }
+ let(:evil_trout) { Fabricate(:evil_trout) }
+ let(:another_user) { Fabricate(:user) }
+ let(:regular_user) { Fabricate(:trust_level_4) }
+ let(:topic) { Fabricate(:topic) }
+ let(:personal_message) { Fabricate(:private_message_topic, user: evil_trout) }
+ let!(:p1) { Fabricate(:post, topic: personal_message, user: user) }
+ let!(:p2) { Fabricate(:post, topic: personal_message, reply_to_post_number: p1.post_number, user: another_user) }
+ let!(:p3) { Fabricate(:post, topic: personal_message, reply_to_post_number: p1.post_number, user: user) }
+ let!(:p4) { Fabricate(:post, topic: personal_message, reply_to_post_number: p2.post_number, user: user) }
+ let!(:p5) { Fabricate(:post, topic: personal_message, user: evil_trout) }
+ let(:another_personal_message) do
+ Fabricate(:private_message_topic, user: user, topic_allowed_users: [
+ Fabricate.build(:topic_allowed_user, user: admin)
+ ])
+ end
+ let!(:p6) { Fabricate(:post, topic: another_personal_message, user: evil_trout) }
+
+ before do
+ SiteSetting.tagging_enabled = true
+ SiteSetting.queue_jobs = false
+ p1.replies << p3
+ p2.replies << p4
+ UserActionCreator.enable
+ @like = PostAction.act(another_user, p4, PostActionType.types[:like])
end
- context "to an existing topic with a deleted post" do
+ context 'move to new message' do
+ it "adds post users as topic allowed users" do
+ personal_message.move_posts(admin, [p2.id, p5.id], title: "new testing message name", tags: ["tag1", "tag2"], archetype: "private_message")
- before do
- topic.expects(:add_moderator_post)
+ p2.reload
+ expect(p2.topic.archetype).to eq(Archetype.private_message)
+ expect(p2.topic.topic_allowed_users.where(user_id: another_user.id).count).to eq(1)
+ expect(p2.topic.topic_allowed_users.where(user_id: evil_trout.id).count).to eq(1)
+ expect(p2.topic.tags.pluck(:name)).to eq([])
end
- let!(:destination_topic) { Fabricate(:topic, user: user) }
- let!(:destination_op) { Fabricate(:post, topic: destination_topic, user: user) }
- let!(:destination_deleted_reply) { Fabricate(:post, topic: destination_topic, user: another_user) }
- let(:moved_to) { topic.move_posts(user, [p2.id, p4.id], destination_topic_id: destination_topic.id) }
+ it "can add tags to new message when allow_staff_to_tag_pms is enabled" do
+ SiteSetting.allow_staff_to_tag_pms = true
+ personal_message.move_posts(admin, [p2.id, p5.id], title: "new testing message name", tags: ["tag1", "tag2"], archetype: "private_message")
- it "works correctly" do
- destination_deleted_reply.trash!
-
- expect(moved_to).to eq(destination_topic)
-
- # Check out new topic
- moved_to.reload
- expect(moved_to.posts_count).to eq(3)
- expect(moved_to.highest_post_number).to eq(4)
-
- # Posts should be re-ordered
p2.reload
- expect(p2.sort_order).to eq(3)
- expect(p2.post_number).to eq(3)
- expect(p2.topic_id).to eq(moved_to.id)
- expect(p2.reply_count).to eq(1)
+ expect(p2.topic.tags.pluck(:name)).to contain_exactly("tag1", "tag2")
+ end
+
+ it "correctly handles notifications" do
+ old_message = p2.topic
+ old_message_id = p2.topic_id
+
+ personal_message.move_posts(admin, [p2.id, p4.id], title: "new testing message name", archetype: "private_message")
+
+ p2.reload
+ expect(p2.topic_id).not_to eq(old_message_id)
expect(p2.reply_to_post_number).to eq(nil)
+ expect(p2.reply_to_user_id).to eq(nil)
- p4.reload
- expect(p4.post_number).to eq(4)
- expect(p4.sort_order).to eq(4)
- expect(p4.topic_id).to eq(moved_to.id)
- expect(p4.reply_to_post_number).to eq(p2.post_number)
+ notification = p2.user.notifications.where(notification_type: Notification.types[:moved_post]).first
+
+ expect(notification.topic_id).to eq(p2.topic_id)
+ expect(notification.post_number).to eq(1)
+
+ # no message for person who made the move
+ expect(admin.notifications.where(notification_type: Notification.types[:moved_post]).length).to eq(0)
+
+ old_message.reload
+ move_message = old_message.posts.find_by(post_number: 2)
+ expect(move_message.post_type).to eq(Post.types[:small_action])
+ expect(move_message.raw).to include("2 posts were split")
end
end
- context "to an existing closed topic" do
- let!(:destination_topic) { Fabricate(:topic, closed: true) }
+ context 'move to existing message' do
+ it "adds post users as topic allowed users" do
+ personal_message.move_posts(admin, [p2.id, p5.id], destination_topic_id: another_personal_message.id, archetype: "private_message")
- it "works correctly for admin" do
- admin = Fabricate(:admin)
- moved_to = topic.move_posts(admin, [p1.id, p2.id], destination_topic_id: destination_topic.id)
+ p2.reload
+ expect(p2.topic_id).to eq(another_personal_message.id)
+
+ another_personal_message.reload
+ expect(another_personal_message.topic_allowed_users.where(user_id: another_user.id).count).to eq(1)
+ expect(another_personal_message.topic_allowed_users.where(user_id: evil_trout.id).count).to eq(1)
+ end
+
+ it "can add additional participants" do
+ personal_message.move_posts(admin, [p2.id, p5.id], destination_topic_id: another_personal_message.id, participants: [regular_user.username], archetype: "private_message")
+
+ another_personal_message.reload
+ expect(another_personal_message.topic_allowed_users.where(user_id: another_user.id).count).to eq(1)
+ expect(another_personal_message.topic_allowed_users.where(user_id: evil_trout.id).count).to eq(1)
+ expect(another_personal_message.topic_allowed_users.where(user_id: regular_user.id).count).to eq(1)
+ end
+
+ it "does not allow moving regular topic posts in personal message" do
+ expect {
+ personal_message.move_posts(admin, [p2.id, p5.id], destination_topic_id: topic.id)
+ }.to raise_error(Discourse::InvalidParameters)
+ end
+
+ it "moving all posts will close the message" do
+ moved_to = personal_message.move_posts(admin, [p1.id, p2.id, p3.id, p4.id, p5.id], destination_topic_id: another_personal_message.id, archetype: "private_message")
expect(moved_to).to be_present
- moved_to.reload
- expect(moved_to.posts_count).to eq(2)
- expect(moved_to.highest_post_number).to eq(2)
+ personal_message.reload
+ expect(personal_message.closed).to eq(true)
+ expect(moved_to.posts_count).to eq(6)
end
end
-
- it "skips validations when moving posts" do
- p1.update_attribute(:raw, "foo")
- p2.update_attribute(:raw, "bar")
-
- new_topic = topic.move_posts(user, [p1.id, p2.id], title: "new testing topic name")
-
- expect(new_topic).to be_present
- expect(new_topic.posts.by_post_number.first.raw).to eq(p1.raw)
- expect(new_topic.posts.by_post_number.last.raw).to eq(p2.raw)
- expect(new_topic.posts_count).to eq(2)
- end
end
end
end
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index aa6d86ae5f..3001a647bf 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -396,11 +396,11 @@ describe Report do
it "returns a report with data" do
expect(report.data[0][:term]).to eq("ruby")
- expect(report.data[0][:unique_searches]).to eq(2)
+ expect(report.data[0][:searches]).to eq(3)
expect(report.data[0][:ctr]).to eq(33.4)
expect(report.data[1][:term]).to eq("php")
- expect(report.data[1][:unique_searches]).to eq(1)
+ expect(report.data[1][:searches]).to eq(1)
end
end
end
@@ -498,7 +498,7 @@ describe Report do
expect(report.data).to be_present
row = report.data[0]
- expect(row[:action_type]).to eq("spam")
+ expect(row[:post_type]).to eq("spam")
expect(row[:staff_username]).to eq(nil)
expect(row[:staff_id]).to eq(nil)
expect(row[:poster_username]).to eq(post.user.username)
@@ -1028,4 +1028,45 @@ describe Report do
end
end
end
+
+ describe "report_top_uploads" do
+ let(:report) { Report.find("top_uploads") }
+ let(:tarek) { Fabricate(:admin, username: "tarek") }
+ let(:khalil) { Fabricate(:admin, username: "khalil") }
+
+ context "with data" do
+ let!(:tarek_upload) do
+ Fabricate(:upload, user: tarek,
+ url: "/uploads/default/original/1X/tarek.jpg",
+ extension: "jpg",
+ original_filename: "tarek.jpg",
+ filesize: 1000)
+ end
+ let!(:khalil_upload) do
+ Fabricate(:upload, user: khalil,
+ url: "/uploads/default/original/1X/khalil.png",
+ extension: "png",
+ original_filename: "khalil.png",
+ filesize: 2000)
+ end
+
+ it "works" do
+ expect(report.data.length).to eq(2)
+ expect_row_to_be_equal(report.data[0], khalil, khalil_upload)
+ expect_row_to_be_equal(report.data[1], tarek, tarek_upload)
+ end
+
+ def expect_row_to_be_equal(row, user, upload)
+ expect(row[:author_id]).to eq(user.id)
+ expect(row[:author_username]).to eq(user.username)
+ expect(row[:author_avatar_template]).to eq(User.avatar_template(user.username, user.uploaded_avatar_id))
+ expect(row[:filesize]).to eq(upload.filesize)
+ expect(row[:extension]).to eq(upload.extension)
+ expect(row[:file_url]).to eq(Discourse.store.cdn_url(upload.url))
+ expect(row[:file_name]).to eq(upload.original_filename.truncate(25))
+ end
+ end
+
+ include_examples "no data"
+ end
end
diff --git a/spec/models/search_log_spec.rb b/spec/models/search_log_spec.rb
index 0ae475e6cb..c98dda028e 100644
--- a/spec/models/search_log_spec.rb
+++ b/spec/models/search_log_spec.rb
@@ -194,17 +194,16 @@ RSpec.describe SearchLog, type: :model do
end
it "considers time period" do
- expect(SearchLog.trending.count).to eq(4)
+ expect(SearchLog.trending.to_a.count).to eq(4)
SearchLog.where(term: 'swift').update_all(created_at: 1.year.ago)
- expect(SearchLog.trending(:monthly).count).to eq(3)
+ expect(SearchLog.trending(:monthly).to_a.count).to eq(3)
end
it "correctly returns trending data" do
top_trending = SearchLog.trending.first
expect(top_trending.term).to eq("ruby")
expect(top_trending.searches).to eq(3)
- expect(top_trending.unique_searches).to eq(2)
expect(top_trending.click_through).to eq(0)
SearchLog.where(term: 'ruby', ip_address: '127.0.0.1').update_all(search_result_id: 12)
diff --git a/spec/models/theme_spec.rb b/spec/models/theme_spec.rb
index 859421ce04..26c706ba79 100644
--- a/spec/models/theme_spec.rb
+++ b/spec/models/theme_spec.rb
@@ -355,6 +355,12 @@ HTML
expect(theme_field.javascript_cache.content).to eq(transpiled.strip)
end
+ it 'is empty when the settings are invalid' do
+ theme.set_field(target: :settings, name: :yaml, value: 'nil_setting: ')
+ theme.save!
+
+ expect(theme.settings).to be_empty
+ end
end
it 'correctly caches theme ids' do
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 6e64ee82b6..1fe964da54 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -43,7 +43,6 @@ describe Upload do
upload.reload
expect(upload.optimized_images.count).to eq(1)
end
-
end
it "can reconstruct dimensions on demand" do
@@ -56,6 +55,9 @@ describe Upload do
expect(upload.width).to eq(64250)
expect(upload.height).to eq(64250)
+ upload.reload
+ expect(upload.read_attribute(:width)).to eq(64250)
+
upload.update_columns(width: nil, height: nil, thumbnail_width: nil, thumbnail_height: nil)
expect(upload.thumbnail_width).to eq(500)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 37452a3620..93e979894f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1924,4 +1924,46 @@ describe User do
expect(user.next_best_title).to eq(nil)
end
end
+
+ describe 'check_site_contact_username' do
+ before { SiteSetting.site_contact_username = contact_user.username }
+
+ context 'admin' do
+ let(:contact_user) { Fabricate(:admin) }
+
+ it 'clears site_contact_username site setting when admin privilege is revoked' do
+ contact_user.revoke_admin!
+ expect(SiteSetting.site_contact_username).to eq(SiteSetting.defaults[:site_contact_username])
+ end
+ end
+
+ context 'moderator' do
+ let(:contact_user) { Fabricate(:moderator) }
+
+ it 'clears site_contact_username site setting when moderator privilege is revoked' do
+ contact_user.revoke_moderation!
+ expect(SiteSetting.site_contact_username).to eq(SiteSetting.defaults[:site_contact_username])
+ end
+ end
+
+ context 'admin and moderator' do
+ let(:contact_user) { Fabricate(:moderator, admin: true) }
+
+ it 'does not change site_contact_username site setting when admin privilege is revoked' do
+ contact_user.revoke_admin!
+ expect(SiteSetting.site_contact_username).to eq(contact_user.username)
+ end
+
+ it 'does not change site_contact_username site setting when moderator privilege is revoked' do
+ contact_user.revoke_moderation!
+ expect(SiteSetting.site_contact_username).to eq(contact_user.username)
+ end
+
+ it 'clears site_contact_username site setting when staff privileges are revoked' do
+ contact_user.revoke_admin!
+ contact_user.revoke_moderation!
+ expect(SiteSetting.site_contact_username).to eq(SiteSetting.defaults[:site_contact_username])
+ end
+ end
+ end
end
diff --git a/spec/models/user_summary_spec.rb b/spec/models/user_summary_spec.rb
index 4c0bfe779e..84fab05156 100644
--- a/spec/models/user_summary_spec.rb
+++ b/spec/models/user_summary_spec.rb
@@ -38,4 +38,22 @@ describe UserSummary do
expect(summary.top_categories.length).to eq(0)
end
+ it "is robust enough to handle bad data" do
+ UserActionCreator.enable
+
+ liked_post = create_post
+ user = Fabricate(:user)
+ PostAction.act(user, liked_post, PostActionType.types[:like])
+
+ users = UserSummary.new(user, Guardian.new).most_liked_users
+
+ expect(users.map(&:id)).to eq([liked_post.user_id])
+
+ # really we should not be corrupting stuff like this
+ # but in production dbs this can happens sometimes I guess
+ liked_post.user.delete
+
+ users = UserSummary.new(user, Guardian.new).most_liked_users
+ expect(users).to eq([])
+ end
end
diff --git a/spec/multisite/s3_store_spec.rb b/spec/multisite/s3_store_spec.rb
index 2c27a8169b..6c023d15b6 100644
--- a/spec/multisite/s3_store_spec.rb
+++ b/spec/multisite/s3_store_spec.rb
@@ -4,18 +4,7 @@ require 'file_store/s3_store'
RSpec.describe 'Multisite s3 uploads', type: :multisite do
let(:conn) { RailsMultisite::ConnectionManagement }
let(:uploaded_file) { file_from_fixtures("smallest.png") }
-
- let(:upload) do
- Fabricate(:upload, sha1: Digest::SHA1.hexdigest(File.read(uploaded_file)))
- end
-
- let(:s3_client) { Aws::S3::Client.new(stub_responses: true) }
-
- let(:s3_helper) do
- S3Helper.new(SiteSetting.s3_upload_bucket, '', client: s3_client)
- end
-
- let(:store) { FileStore::S3Store.new(s3_helper) }
+ let(:upload) { Fabricate(:upload, sha1: Digest::SHA1.hexdigest(File.read(uploaded_file))) }
context 'uploading to s3' do
before(:each) do
@@ -26,6 +15,10 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do
end
describe "#store_upload" do
+ let(:s3_client) { Aws::S3::Client.new(stub_responses: true) }
+ let(:s3_helper) { S3Helper.new(SiteSetting.s3_upload_bucket, '', client: s3_client) }
+ let(:store) { FileStore::S3Store.new(s3_helper) }
+
it "returns the correct url for default and second multisite db" do
conn.with_connection('default') do
expect(store.store_upload(uploaded_file, upload)).to eq(
@@ -41,4 +34,76 @@ RSpec.describe 'Multisite s3 uploads', type: :multisite do
end
end
end
+
+ context 'removal from s3' do
+ before do
+ SiteSetting.s3_region = 'us-west-1'
+ SiteSetting.s3_upload_bucket = "s3-upload-bucket"
+ SiteSetting.s3_access_key_id = "s3-access-key-id"
+ SiteSetting.s3_secret_access_key = "s3-secret-access-key"
+ SiteSetting.enable_s3_uploads = true
+ end
+
+ describe "#remove_upload" do
+ let(:store) { FileStore::S3Store.new }
+ let(:client) { Aws::S3::Client.new(stub_responses: true) }
+ let(:resource) { Aws::S3::Resource.new(client: client) }
+ let(:s3_bucket) { resource.bucket(SiteSetting.s3_upload_bucket) }
+ let(:s3_helper) { store.s3_helper }
+
+ it "removes the file from s3 on multisite" do
+ conn.with_connection('default') do
+ store.expects(:get_depth_for).with(upload.id).returns(0)
+ s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
+ upload.update_attributes!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/uploads/default/original/1X/#{upload.sha1}.png")
+ s3_object = stub
+
+ s3_bucket.expects(:object).with("uploads/tombstone/default/original/1X/#{upload.sha1}.png").returns(s3_object)
+ s3_object.expects(:copy_from).with(copy_source: "s3-upload-bucket/uploads/default/original/1X/#{upload.sha1}.png")
+ s3_bucket.expects(:object).with("uploads/default/original/1X/#{upload.sha1}.png").returns(s3_object)
+ s3_object.expects(:delete)
+
+ store.remove_upload(upload)
+ end
+ end
+
+ it "removes the file from s3 on another multisite db" do
+ conn.with_connection('second') do
+ store.expects(:get_depth_for).with(upload.id).returns(0)
+ s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
+ upload.update_attributes!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/uploads/second/original/1X/#{upload.sha1}.png")
+ s3_object = stub
+
+ s3_bucket.expects(:object).with("uploads/tombstone/second/original/1X/#{upload.sha1}.png").returns(s3_object)
+ s3_object.expects(:copy_from).with(copy_source: "s3-upload-bucket/uploads/second/original/1X/#{upload.sha1}.png")
+ s3_bucket.expects(:object).with("uploads/second/original/1X/#{upload.sha1}.png").returns(s3_object)
+ s3_object.expects(:delete)
+
+ store.remove_upload(upload)
+ end
+ end
+
+ describe "when s3_upload_bucket includes folders path" do
+ before do
+ SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads"
+ end
+
+ it "removes the file from s3 on multisite" do
+ conn.with_connection('default') do
+ store.expects(:get_depth_for).with(upload.id).returns(0)
+ s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once
+ upload.update_attributes!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/discourse-uploads/uploads/default/original/1X/#{upload.sha1}.png")
+ s3_object = stub
+
+ s3_bucket.expects(:object).with("discourse-uploads/uploads/tombstone/default/original/1X/#{upload.sha1}.png").returns(s3_object)
+ s3_object.expects(:copy_from).with(copy_source: "s3-upload-bucket/discourse-uploads/uploads/default/original/1X/#{upload.sha1}.png")
+ s3_bucket.expects(:object).with("discourse-uploads/uploads/default/original/1X/#{upload.sha1}.png").returns(s3_object)
+ s3_object.expects(:delete)
+
+ store.remove_upload(upload)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/admin/email_controller_spec.rb b/spec/requests/admin/email_controller_spec.rb
index 3df6f3a034..34b2faff5b 100644
--- a/spec/requests/admin/email_controller_spec.rb
+++ b/spec/requests/admin/email_controller_spec.rb
@@ -120,7 +120,7 @@ describe Admin::EmailController do
expect(incoming['sent_test_email_message']).to eq(I18n.t("admin.email.sent_test_disabled"))
end
- it 'sends mail to staff only when setting is "non-staff"' do
+ it 'sends mail to everyone when setting is "non-staff"' do
SiteSetting.disable_emails = 'non-staff'
post "/admin/email/test.json", params: { email_address: admin.email }
@@ -129,7 +129,7 @@ describe Admin::EmailController do
post "/admin/email/test.json", params: { email_address: eviltrout.email }
incoming = JSON.parse(response.body)
- expect(incoming['sent_test_email_message']).to eq(I18n.t("admin.email.sent_test_disabled_for_non_staff"))
+ expect(incoming['sent_test_email_message']).to eq(I18n.t("admin.email.sent_test"))
end
it 'sends mail to everyone when setting is "no"' do
diff --git a/spec/requests/admin/search_logs_spec.rb b/spec/requests/admin/search_logs_spec.rb
index b234ea2abf..ed393c5709 100644
--- a/spec/requests/admin/search_logs_spec.rb
+++ b/spec/requests/admin/search_logs_spec.rb
@@ -32,6 +32,8 @@ RSpec.describe Admin::SearchLogsController do
json = ::JSON.parse(response.body)
expect(json[0]['term']).to eq('ruby')
+ expect(json[0]['searches']).to eq(1)
+ expect(json[0]['ctr']).to eq(0)
end
end
diff --git a/spec/requests/admin/themes_controller_spec.rb b/spec/requests/admin/themes_controller_spec.rb
index 2d9d34d54d..6788b98fcc 100644
--- a/spec/requests/admin/themes_controller_spec.rb
+++ b/spec/requests/admin/themes_controller_spec.rb
@@ -76,6 +76,14 @@ describe Admin::ThemesController do
expect(theme.theme_fields.first.upload.original_filename).to eq(upload.original_filename)
end
+ it 'can import a theme from Git' do
+ post "/admin/themes/import.json", params: {
+ remote: ' https://github.com/discourse/discourse-brand-header '
+ }
+
+ expect(response.status).to eq(201)
+ end
+
it 'imports a theme' do
post "/admin/themes/import.json", params: { theme: theme_file }
expect(response.status).to eq(201)
diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb
index 6671cdd039..33d8e7b19e 100644
--- a/spec/requests/admin/users_controller_spec.rb
+++ b/spec/requests/admin/users_controller_spec.rb
@@ -241,9 +241,9 @@ RSpec.describe Admin::UsersController do
expect(AdminConfirmation.exists_for?(another_user.id)).to eq(false)
end
- it "returns a 403 if the username doesn't exist" do
+ it "returns a 404 if the username doesn't exist" do
put "/admin/users/123123/grant_admin.json"
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
it 'updates the admin flag' do
@@ -300,9 +300,9 @@ RSpec.describe Admin::UsersController do
expect(response.status).to eq(404)
end
- it "returns a 422 if the username doesn't exist" do
+ it "returns a 404 if the username doesn't exist" do
put "/admin/users/123123/trust_level.json"
- expect(response.status).to eq(422)
+ expect(response.status).to eq(404)
end
it "upgrades the user's trust level" do
@@ -347,9 +347,9 @@ RSpec.describe Admin::UsersController do
expect(response.status).to eq(404)
end
- it "returns a 403 if the username doesn't exist" do
+ it "returns a 404 if the username doesn't exist" do
put "/admin/users/123123/grant_moderation.json"
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
it 'updates the moderator flag' do
@@ -394,7 +394,7 @@ RSpec.describe Admin::UsersController do
it "returns a 404 if the user doesn't exist" do
put "/admin/users/123123/primary_group.json"
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
it "changes the user's primary group" do
@@ -554,9 +554,9 @@ RSpec.describe Admin::UsersController do
expect(reg_user).not_to be_silenced
end
- it "returns a 403 if the user doesn't exist" do
+ it "returns a 404 if the user doesn't exist" do
put "/admin/users/123123/silence.json"
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
it "punishes the user for spamming" do
@@ -626,7 +626,7 @@ RSpec.describe Admin::UsersController do
it "returns a 403 if the user doesn't exist" do
put "/admin/users/123123/unsilence.json"
- expect(response.status).to eq(403)
+ expect(response.status).to eq(404)
end
it "unsilences the user" do
@@ -943,6 +943,14 @@ RSpec.describe Admin::UsersController do
end
describe "#delete_posts_batch" do
+ describe 'when user is is invalid' do
+ it 'should return the right response' do
+ put "/admin/users/nothing/delete_posts_batch.json"
+
+ expect(response.status).to eq(404)
+ end
+ end
+
context "when there are user posts" do
before do
post = Fabricate(:post, user: user)
@@ -951,8 +959,6 @@ RSpec.describe Admin::UsersController do
end
it 'returns how many posts were deleted' do
- sign_in(admin)
-
put "/admin/users/#{user.id}/delete_posts_batch.json"
expect(response.status).to eq(200)
expect(JSON.parse(response.body)["posts_deleted"]).to eq(3)
@@ -961,8 +967,6 @@ RSpec.describe Admin::UsersController do
context "when there are no posts left to be deleted" do
it "returns correct json" do
- sign_in(admin)
-
put "/admin/users/#{user.id}/delete_posts_batch.json"
expect(response.status).to eq(200)
expect(JSON.parse(response.body)["posts_deleted"]).to eq(0)
diff --git a/spec/requests/posts_controller_spec.rb b/spec/requests/posts_controller_spec.rb
index e3e6490547..8290e1b445 100644
--- a/spec/requests/posts_controller_spec.rb
+++ b/spec/requests/posts_controller_spec.rb
@@ -661,6 +661,15 @@ describe PostsController do
put "/posts/#{post.id}/rebake.json"
expect(response.status).to eq(200)
end
+
+ it "will invalidate broken images cache" do
+ sign_in(Fabricate(:moderator))
+ post.custom_fields[Post::BROKEN_IMAGES] = ["https://example.com/image.jpg"].to_json
+ post.save_custom_fields
+ put "/posts/#{post.id}/rebake.json"
+ post.reload
+ expect(post.custom_fields[Post::BROKEN_IMAGES]).to be_nil
+ end
end
end
diff --git a/spec/requests/search_controller_spec.rb b/spec/requests/search_controller_spec.rb
index 3f2446c5ef..ca3e20fa84 100644
--- a/spec/requests/search_controller_spec.rb
+++ b/spec/requests/search_controller_spec.rb
@@ -127,6 +127,16 @@ describe SearchController do
end
context "#show" do
+ it "doesn't raise an error when search term not specified" do
+ get "/search"
+ expect(response.status).to eq(200)
+ end
+
+ it "raises an error when the search term length is less than required" do
+ get "/search.json", params: { q: 'ba' }
+ expect(response.status).to eq(400)
+ end
+
it "logs the search term" do
SiteSetting.log_search_queries = true
get "/search.json", params: { q: 'bantha' }
diff --git a/spec/requests/session_controller_spec.rb b/spec/requests/session_controller_spec.rb
index f81105ec6f..afc5a1e1de 100644
--- a/spec/requests/session_controller_spec.rb
+++ b/spec/requests/session_controller_spec.rb
@@ -557,6 +557,38 @@ RSpec.describe SessionController do
expect(response.status).to eq(419)
end
+ context "when sso provider is enabled" do
+ before do
+ SiteSetting.enable_sso_provider = true
+ SiteSetting.sso_provider_secrets = [
+ "*|secret,forAll",
+ "*.rainbow|wrongSecretForOverRainbow",
+ "www.random.site|secretForRandomSite",
+ "somewhere.over.rainbow|secretForOverRainbow",
+ ].join("\n")
+ end
+
+ it "doesn't break" do
+ sso = get_sso('/hello/world')
+ sso.external_id = '997'
+ sso.sso_url = "http://somewhere.over.com/sso_login"
+ sso.return_sso_url = "http://someurl.com"
+
+ user = Fabricate(:user)
+ user.create_single_sign_on_record(external_id: '997', last_payload: '')
+
+ get "/session/sso_login", params: Rack::Utils.parse_query(sso.payload), headers: headers
+
+ user.single_sign_on_record.reload
+ expect(user.single_sign_on_record.last_payload).to eq(sso.unsigned_payload)
+
+ expect(response).to redirect_to('/hello/world')
+ logged_on_user = Discourse.current_user_provider.new(request.env).current_user
+
+ expect(user.id).to eq(logged_on_user.id)
+ end
+ end
+
it 'returns the correct error code for invalid signature' do
sso = get_sso('/hello/world')
sso.external_id = '997'
@@ -652,7 +684,7 @@ RSpec.describe SessionController do
"somewhere.over.rainbow|secretForOverRainbow",
].join("\n")
- @sso = SingleSignOn.new
+ @sso = SingleSignOnProvider.new
@sso.nonce = "mynonce"
@sso.return_sso_url = "http://somewhere.over.rainbow/sso"
@@ -684,7 +716,7 @@ RSpec.describe SessionController do
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
payload = location.split("?")[1]
- sso2 = SingleSignOn.parse(payload)
+ sso2 = SingleSignOnProvider.parse(payload)
expect(sso2.email).to eq(@user.email)
expect(sso2.name).to eq(@user.name)
@@ -718,7 +750,7 @@ RSpec.describe SessionController do
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
payload = location.split("?")[1]
- sso2 = SingleSignOn.parse(payload)
+ sso2 = SingleSignOnProvider.parse(payload)
expect(sso2.email).to eq(@user.email)
expect(sso2.name).to eq(@user.name)
@@ -781,7 +813,7 @@ RSpec.describe SessionController do
expect(location).to match(/^http:\/\/somewhere.over.rainbow\/sso/)
payload = location.split("?")[1]
- sso2 = SingleSignOn.parse(payload)
+ sso2 = SingleSignOnProvider.parse(payload)
expect(sso2.avatar_url.blank?).to_not eq(true)
expect(sso2.profile_background_url.blank?).to_not eq(true)
diff --git a/spec/requests/static_controller_spec.rb b/spec/requests/static_controller_spec.rb
index 37a0f4ce6d..b3613bea94 100644
--- a/spec/requests/static_controller_spec.rb
+++ b/spec/requests/static_controller_spec.rb
@@ -164,7 +164,7 @@ describe StaticController do
SiteSetting.login_required = true
end
- ['faq', 'guidelines', 'rules'].each do |page_name|
+ ['faq', 'guidelines', 'rules', 'conduct'].each do |page_name|
it "#{page_name} page redirects to login page for anon" do
get "/#{page_name}"
expect(response).to redirect_to '/login'
@@ -174,9 +174,7 @@ describe StaticController do
get "/#{page_name}"
expect(response).to redirect_to '/login'
end
- end
- ['faq', 'guidelines', 'rules'].each do |page_name|
it "#{page_name} page loads for logged in user" do
sign_in(Fabricate(:user))
diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb
index aa636c82d1..c7851de900 100644
--- a/spec/requests/topics_controller_spec.rb
+++ b/spec/requests/topics_controller_spec.rb
@@ -241,6 +241,140 @@ RSpec.describe TopicsController do
end
end
end
+
+ describe 'moving to a new message' do
+ let(:user) { Fabricate(:user) }
+ let(:trust_level_4) { Fabricate(:trust_level_4) }
+ let(:moderator) { Fabricate(:moderator) }
+ let!(:message) { Fabricate(:private_message_topic) }
+ let!(:p1) { Fabricate(:post, user: user, post_number: 1, topic: message) }
+ let!(:p2) { Fabricate(:post, user: user, post_number: 2, topic: message) }
+
+ it "raises an error without post_ids" do
+ sign_in(moderator)
+ post "/t/#{message.id}/move-posts.json", params: { title: 'blah', archetype: 'private_message' }
+ expect(response.status).to eq(400)
+ end
+
+ it "raises an error when the user doesn't have permission to move the posts" do
+ sign_in(trust_level_4)
+
+ post "/t/#{message.id}/move-posts.json", params: {
+ title: 'blah', post_ids: [p1.post_number, p2.post_number], archetype: 'private_message'
+ }
+
+ expect(response.status).to eq(403)
+ result = ::JSON.parse(response.body)
+ expect(result['errors']).to be_present
+ end
+
+ context 'success' do
+ before { sign_in(Fabricate(:admin)) }
+
+ it "returns success" do
+ SiteSetting.allow_staff_to_tag_pms = true
+
+ expect do
+ post "/t/#{message.id}/move-posts.json", params: {
+ title: 'Logan is a good movie',
+ post_ids: [p2.id],
+ archetype: 'private_message',
+ tags: ["tag1", "tag2"]
+ }
+ end.to change { Topic.count }.by(1)
+
+ expect(response.status).to eq(200)
+
+ result = ::JSON.parse(response.body)
+
+ expect(result['success']).to eq(true)
+ expect(result['url']).to eq(Topic.last.relative_url)
+ expect(Tag.all.pluck(:name)).to contain_exactly("tag1", "tag2")
+ end
+
+ describe 'when message has been deleted' do
+ it 'should still be able to move posts' do
+ PostDestroyer.new(Fabricate(:admin), message.first_post).destroy
+
+ expect(message.reload.deleted_at).to_not be_nil
+
+ expect do
+ post "/t/#{message.id}/move-posts.json", params: {
+ title: 'Logan is a good movie',
+ post_ids: [p2.id],
+ archetype: 'private_message'
+ }
+ end.to change { Topic.count }.by(1)
+
+ expect(response.status).to eq(200)
+
+ result = JSON.parse(response.body)
+
+ expect(result['success']).to eq(true)
+ expect(result['url']).to eq(Topic.last.relative_url)
+ end
+ end
+ end
+
+ context 'failure' do
+ it "returns JSON with a false success" do
+ sign_in(moderator)
+ post "/t/#{message.id}/move-posts.json", params: {
+ post_ids: [p2.id],
+ archetype: 'private_message'
+ }
+ expect(response.status).to eq(200)
+ result = ::JSON.parse(response.body)
+ expect(result['success']).to eq(false)
+ expect(result['url']).to be_blank
+ end
+ end
+ end
+
+ describe 'moving to an existing message' do
+ let!(:user) { sign_in(Fabricate(:admin)) }
+ let(:trust_level_4) { Fabricate(:trust_level_4) }
+ let(:evil_trout) { Fabricate(:evil_trout) }
+ let(:message) { Fabricate(:private_message_topic) }
+ let(:p1) { Fabricate(:post, user: user, post_number: 1, topic: message) }
+ let(:p2) { Fabricate(:post, user: evil_trout, post_number: 2, topic: message) }
+
+ let(:dest_message) do
+ Fabricate(:private_message_topic, user: trust_level_4, topic_allowed_users: [
+ Fabricate.build(:topic_allowed_user, user: evil_trout)
+ ])
+ end
+
+ context 'success' do
+ it "returns success" do
+ user
+ post "/t/#{message.id}/move-posts.json", params: {
+ post_ids: [p2.id],
+ destination_topic_id: dest_message.id,
+ archetype: 'private_message'
+ }
+
+ expect(response.status).to eq(200)
+ result = ::JSON.parse(response.body)
+ expect(result['success']).to eq(true)
+ expect(result['url']).to be_present
+ end
+ end
+
+ context 'failure' do
+ it "returns JSON with a false success" do
+ post "/t/#{message.id}/move-posts.json", params: {
+ post_ids: [p2.id],
+ archetype: 'private_message'
+ }
+
+ expect(response.status).to eq(200)
+ result = ::JSON.parse(response.body)
+ expect(result['success']).to eq(false)
+ expect(result['url']).to be_blank
+ end
+ end
+ end
end
describe '#merge_topic' do
@@ -251,7 +385,7 @@ RSpec.describe TopicsController do
expect(response.status).to eq(403)
end
- describe 'moving to a new topic' do
+ describe 'merging into another topic' do
let(:moderator) { Fabricate(:moderator) }
let(:user) { Fabricate(:user) }
let(:p1) { Fabricate(:post, user: user) }
@@ -285,6 +419,53 @@ RSpec.describe TopicsController do
end
end
end
+
+ describe 'merging into another message' do
+ let(:moderator) { Fabricate(:moderator) }
+ let(:user) { Fabricate(:user) }
+ let(:trust_level_4) { Fabricate(:trust_level_4) }
+ let(:message) { Fabricate(:private_message_topic, user: user) }
+ let!(:p1) { Fabricate(:post, topic: message, user: trust_level_4) }
+ let!(:p2) { Fabricate(:post, topic: message, reply_to_post_number: p1.post_number, user: user) }
+
+ it "raises an error without destination_topic_id" do
+ sign_in(moderator)
+ post "/t/#{message.id}/merge-topic.json", params: {
+ archetype: 'private_message'
+ }
+ expect(response.status).to eq(400)
+ end
+
+ it "raises an error when the user doesn't have permission to merge" do
+ sign_in(trust_level_4)
+ post "/t/#{message.id}/merge-topic.json", params: {
+ destination_topic_id: 345,
+ archetype: 'private_message'
+ }
+ expect(response).to be_forbidden
+ end
+
+ let(:dest_message) do
+ Fabricate(:private_message_topic, user: trust_level_4, topic_allowed_users: [
+ Fabricate.build(:topic_allowed_user, user: moderator)
+ ])
+ end
+
+ context 'moves all the posts to the destination message' do
+ it "returns success" do
+ sign_in(moderator)
+ post "/t/#{message.id}/merge-topic.json", params: {
+ destination_topic_id: dest_message.id,
+ archetype: 'private_message'
+ }
+
+ expect(response.status).to eq(200)
+ result = ::JSON.parse(response.body)
+ expect(result['success']).to eq(true)
+ expect(result['url']).to be_present
+ end
+ end
+ end
end
describe '#change_post_owners' do
@@ -2439,7 +2620,7 @@ RSpec.describe TopicsController do
expect(response.status).to eq(403)
end
- [:user, :trust_level_4].each do |user|
+ [:user].each do |user|
it "denies access for #{user}" do
sign_in(Fabricate(user))
put "/t/#{topic.id}/reset-bump-date.json"
@@ -2454,7 +2635,7 @@ RSpec.describe TopicsController do
end
end
- [:admin, :moderator].each do |user|
+ [:admin, :moderator, :trust_level_4].each do |user|
it "should reset bumped_at as #{user}" do
sign_in(Fabricate(user))
topic = Fabricate(:topic, bumped_at: 1.hour.ago)
diff --git a/spec/serializers/admin_user_list_serializer_spec.rb b/spec/serializers/admin_user_list_serializer_spec.rb
index a8bf155f39..63bc97cfd5 100644
--- a/spec/serializers/admin_user_list_serializer_spec.rb
+++ b/spec/serializers/admin_user_list_serializer_spec.rb
@@ -5,8 +5,10 @@ describe AdminUserListSerializer do
context "emails" do
let(:admin) { Fabricate(:user_single_email, admin: true, email: "admin@email.com") }
+ let(:moderator) { Fabricate(:user_single_email, moderator: true, email: "moderator@email.com") }
let(:user) { Fabricate(:user_single_email, email: "user@email.com") }
let(:guardian) { Guardian.new(admin) }
+ let(:mod_guardian) { Guardian.new(moderator) }
let(:json) do
AdminUserListSerializer.new(user,
@@ -15,6 +17,13 @@ describe AdminUserListSerializer do
).as_json
end
+ let(:mod_json) do
+ AdminUserListSerializer.new(user,
+ scope: mod_guardian,
+ root: false
+ ).as_json
+ end
+
def fabricate_secondary_emails_for(u)
["first", "second"].each do |name|
Fabricate(:secondary_email, user: u, email: "#{name}@email.com")
@@ -57,6 +66,18 @@ describe AdminUserListSerializer do
include_examples "not shown"
end
+ context "when moderator makes a request with show_emails param set to true" do
+ before do
+ mod_guardian.can_see_emails = true
+ fabricate_secondary_emails_for(user)
+ end
+
+ it "doesn't contain emails" do
+ expect(mod_json[:email]).to eq(nil)
+ expect(mod_json[:secondary_emails]).to eq(nil)
+ end
+ end
+
context "with a normal user after clicking 'show emails'" do
before do
guardian.can_see_emails = true
diff --git a/spec/serializers/current_user_serializer_spec.rb b/spec/serializers/current_user_serializer_spec.rb
index 45982c374a..e0b6f7bed6 100644
--- a/spec/serializers/current_user_serializer_spec.rb
+++ b/spec/serializers/current_user_serializer_spec.rb
@@ -65,4 +65,24 @@ RSpec.describe CurrentUserSerializer do
expect(payload[:top_category_ids]).to eq([category2.id, category1.id])
end
end
+
+ context "#groups" do
+ let(:member) { Fabricate(:user) }
+ let :serializer do
+ CurrentUserSerializer.new(member, scope: Guardian.new, root: false)
+ end
+
+ it "should only show visible groups" do
+ Fabricate.build(:group, visibility_level: Group.visibility_levels[:public])
+ hidden_group = Fabricate.build(:group, visibility_level: Group.visibility_levels[:owners])
+ public_group = Fabricate.build(:group, visibility_level: Group.visibility_levels[:public])
+ hidden_group.add(member)
+ hidden_group.save!
+ public_group.add(member)
+ public_group.save!
+ payload = serializer.as_json
+
+ expect(payload[:groups]).to eq([{ id: public_group.id, name: public_group.name }])
+ end
+ end
end
diff --git a/test/javascripts/acceptance/admin-user-index-test.js.es6 b/test/javascripts/acceptance/admin-user-index-test.js.es6
new file mode 100644
index 0000000000..0e8ee0c661
--- /dev/null
+++ b/test/javascripts/acceptance/admin-user-index-test.js.es6
@@ -0,0 +1,43 @@
+import { acceptance } from "helpers/qunit-helpers";
+
+acceptance("Admin - User Index", { loggedIn: true });
+
+QUnit.test("can edit username", async assert => {
+ /* global server */
+ server.put("/users/sam/preferences/username", () => [
+ 200,
+ { "Content-Type": "application/json" },
+ { id: 2, username: "new-sam" }
+ ]);
+
+ await visit("/admin/users/2/sam");
+
+ assert.equal(
+ find(".display-row.username .value")
+ .text()
+ .trim(),
+ "sam"
+ );
+
+ // Trying cancel.
+ await click(".display-row.username button");
+ await fillIn(".display-row.username .value input", "new-sam");
+ await click(".display-row.username a");
+ assert.equal(
+ find(".display-row.username .value")
+ .text()
+ .trim(),
+ "sam"
+ );
+
+ // Doing edit.
+ await click(".display-row.username button");
+ await fillIn(".display-row.username .value input", "new-sam");
+ await click(".display-row.username button");
+ assert.equal(
+ find(".display-row.username .value")
+ .text()
+ .trim(),
+ "new-sam"
+ );
+});
diff --git a/test/javascripts/acceptance/composer-actions-test.js.es6 b/test/javascripts/acceptance/composer-actions-test.js.es6
index af74585448..40fdd40a83 100644
--- a/test/javascripts/acceptance/composer-actions-test.js.es6
+++ b/test/javascripts/acceptance/composer-actions-test.js.es6
@@ -1,5 +1,6 @@
import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
import { _clearSnapshots } from "select-kit/components/composer-actions";
+import { toggleCheckDraftPopup } from "discourse/controllers/composer";
acceptance("Composer Actions", {
loggedIn: true,
@@ -125,11 +126,16 @@ QUnit.test("replying to post - reply_as_new_topic", async assert => {
});
QUnit.test("shared draft", async assert => {
+ toggleCheckDraftPopup(true);
+
const composerActions = selectKit(".composer-actions");
await visit("/");
await click("#create-topic");
+ await fillIn("#reply-title", "This is the new text for the title");
+ await fillIn(".d-editor-input", "This is the new text for the post");
+
await composerActions.expand();
await composerActions.selectRowByValue("shared_draft");
@@ -138,6 +144,8 @@ QUnit.test("shared draft", async assert => {
I18n.t("composer.create_shared_draft")
);
assert.ok(find("#reply-control.composing-shared-draft").length === 1);
+
+ toggleCheckDraftPopup(false);
});
QUnit.test("hide component if no content", async assert => {
@@ -283,10 +291,10 @@ QUnit.test("replying to post as staff", async assert => {
assert.equal(composerActions.rowByIndex(4).value(), "toggle_topic_bump");
});
-QUnit.test("replying to post as regular user", async assert => {
+QUnit.test("replying to post as TL3 user", async assert => {
const composerActions = selectKit(".composer-actions");
- replaceCurrentUser({ staff: false, admin: false });
+ replaceCurrentUser({ staff: false, admin: false, trust_level: 3 });
await visit("/t/internationalization-localization/280");
await click("article#post_3 button.reply");
await composerActions.expand();
@@ -301,6 +309,18 @@ QUnit.test("replying to post as regular user", async assert => {
});
});
+QUnit.test("replying to post as TL4 user", async assert => {
+ const composerActions = selectKit(".composer-actions");
+
+ replaceCurrentUser({ staff: false, admin: false, trust_level: 4 });
+ await visit("/t/internationalization-localization/280");
+ await click("article#post_3 button.reply");
+ await composerActions.expand();
+
+ assert.equal(composerActions.rows().length, 4);
+ assert.equal(composerActions.rowByIndex(3).value(), "toggle_topic_bump");
+});
+
QUnit.test(
"replying to first post - reply_as_private_message",
async assert => {
diff --git a/test/javascripts/acceptance/dashboard-next-test.js.es6 b/test/javascripts/acceptance/dashboard-next-test.js.es6
index d142e32d72..6ceb52bc83 100644
--- a/test/javascripts/acceptance/dashboard-next-test.js.es6
+++ b/test/javascripts/acceptance/dashboard-next-test.js.es6
@@ -1,14 +1,34 @@
import { acceptance } from "helpers/qunit-helpers";
acceptance("Dashboard Next", {
- loggedIn: true
+ loggedIn: true,
+ settings: {
+ dashboard_general_tab_activity_metrics: "page_view_total_reqs"
+ }
});
-QUnit.test("Visit dashboard next page", async assert => {
+QUnit.test("Dashboard", async assert => {
+ await visit("/admin");
+ assert.ok(exists(".dashboard-next"), "has dashboard-next class");
+});
+
+QUnit.test("tabs", async assert => {
await visit("/admin");
- assert.ok(exists(".dashboard-next"), "has dashboard-next class");
+ assert.ok(exists(".dashboard-next .navigation-item.general"), "general tab");
+ assert.ok(
+ exists(".dashboard-next .navigation-item.moderation"),
+ "moderation tab"
+ );
+ assert.ok(
+ exists(".dashboard-next .navigation-item.security"),
+ "security tab"
+ );
+ assert.ok(exists(".dashboard-next .navigation-item.reports"), "reports tab");
+});
+QUnit.test("general tab", async assert => {
+ await visit("/admin");
assert.ok(exists(".admin-report.signups"), "signups report");
assert.ok(exists(".admin-report.posts"), "posts report");
assert.ok(exists(".admin-report.dau-by-mau"), "dau-by-mau report");
@@ -30,7 +50,7 @@ QUnit.test("Visit dashboard next page", async assert => {
);
});
-QUnit.test("it has counters", async assert => {
+QUnit.test("general tab - activity metrics", async assert => {
await visit("/admin");
assert.equal(
@@ -58,3 +78,37 @@ QUnit.test("it has counters", async assert => {
"80.8k"
);
});
+
+QUnit.test("reports tab", async assert => {
+ await visit("/admin");
+ await click(".dashboard-next .navigation-item.reports .navigation-link");
+
+ assert.equal(
+ find(".dashboard-next .reports-index.section .reports-list .report").length,
+ 1
+ );
+
+ await fillIn(".dashboard-next .filter-reports-input", "flags");
+
+ assert.equal(
+ find(".dashboard-next .reports-index.section .reports-list .report").length,
+ 0
+ );
+
+ await click(".dashboard-next .navigation-item.security .navigation-link");
+ await click(".dashboard-next .navigation-item.reports .navigation-link");
+
+ assert.equal(
+ find(".dashboard-next .reports-index.section .reports-list .report").length,
+ 1,
+ "navigating back and forth resets filter"
+ );
+
+ await fillIn(".dashboard-next .filter-reports-input", "activities");
+
+ assert.equal(
+ find(".dashboard-next .reports-index.section .reports-list .report").length,
+ 1,
+ "filter is case insensitive"
+ );
+});
diff --git a/test/javascripts/acceptance/topic-move-posts-test.js.es6 b/test/javascripts/acceptance/topic-move-posts-test.js.es6
new file mode 100644
index 0000000000..c86af80d31
--- /dev/null
+++ b/test/javascripts/acceptance/topic-move-posts-test.js.es6
@@ -0,0 +1,121 @@
+import { acceptance } from "helpers/qunit-helpers";
+acceptance("Topic move posts", { loggedIn: true });
+
+QUnit.test("default", async assert => {
+ await visit("/t/internationalization-localization");
+ await click(".toggle-admin-menu");
+ await click(".topic-admin-multi-select .btn");
+ await click("#post_11 .select-below");
+
+ assert.equal(
+ find(".selected-posts .move-to-topic")
+ .text()
+ .trim(),
+ I18n.t("topic.move_to.action"),
+ "it should show the move to button"
+ );
+
+ await click(".selected-posts .move-to-topic");
+
+ assert.ok(
+ find(".move-to-modal .title")
+ .html()
+ .includes(I18n.t("topic.move_to.title")),
+ "it opens move to modal"
+ );
+
+ assert.ok(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.split_topic.radio_label")),
+ "it shows an option to move to new topic"
+ );
+
+ assert.ok(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.merge_topic.radio_label")),
+ "it shows an option to move to existing topic"
+ );
+
+ assert.ok(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.move_to_new_message.radio_label")),
+ "it shows an option to move to new message"
+ );
+});
+
+QUnit.test("moving all posts", async assert => {
+ await visit("/t/internationalization-localization");
+ await click(".toggle-admin-menu");
+ await click(".topic-admin-multi-select .btn");
+ await click(".select-all");
+ await click(".selected-posts .move-to-topic");
+
+ assert.ok(
+ find(".move-to-modal .title")
+ .html()
+ .includes(I18n.t("topic.move_to.title")),
+ "it opens move to modal"
+ );
+
+ assert.not(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.split_topic.radio_label")),
+ "it does not show an option to move to new topic"
+ );
+
+ assert.ok(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.merge_topic.radio_label")),
+ "it shows an option to move to existing topic"
+ );
+
+ assert.not(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.move_to_new_message.radio_label")),
+ "it does not show an option to move to new message"
+ );
+});
+
+QUnit.test("moving posts from personal message", async assert => {
+ await visit("/t/pm-for-testing/12");
+ await click(".toggle-admin-menu");
+ await click(".topic-admin-multi-select .btn");
+ await click("#post_1 .select-post");
+
+ assert.equal(
+ find(".selected-posts .move-to-topic")
+ .text()
+ .trim(),
+ I18n.t("topic.move_to.action"),
+ "it should show the move to button"
+ );
+
+ await click(".selected-posts .move-to-topic");
+
+ assert.ok(
+ find(".move-to-modal .title")
+ .html()
+ .includes(I18n.t("topic.move_to.title")),
+ "it opens move to modal"
+ );
+
+ assert.ok(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.move_to_new_message.radio_label")),
+ "it shows an option to move to new message"
+ );
+
+ assert.ok(
+ find(".move-to-modal .radios")
+ .html()
+ .includes(I18n.t("topic.move_to_existing_message.radio_label")),
+ "it shows an option to move to existing message"
+ );
+});
diff --git a/test/javascripts/components/group-membership-button-test.js.es6 b/test/javascripts/components/group-membership-button-test.js.es6
index 1c46855150..23049904f9 100644
--- a/test/javascripts/components/group-membership-button-test.js.es6
+++ b/test/javascripts/components/group-membership-button-test.js.es6
@@ -56,6 +56,26 @@ QUnit.test("canLeaveGroup", function(assert) {
);
});
+QUnit.test("canRequestMembership", function(assert) {
+ this.subject().setProperties({
+ model: { allow_membership_requests: true, is_group_user: true }
+ });
+
+ assert.equal(
+ this.subject().get("canRequestMembership"),
+ false,
+ "can't request for membership if user is already in the group"
+ );
+
+ this.subject().set("model.is_group_user", false);
+
+ assert.equal(
+ this.subject().get("canRequestMembership"),
+ true,
+ "allowed to request for group membership"
+ );
+});
+
QUnit.test("userIsGroupUser", function(assert) {
this.subject().setProperties({
model: { is_group_user: true }
diff --git a/test/javascripts/controllers/topic-test.js.es6 b/test/javascripts/controllers/topic-test.js.es6
index ac3c132935..2ba53e1221 100644
--- a/test/javascripts/controllers/topic-test.js.es6
+++ b/test/javascripts/controllers/topic-test.js.es6
@@ -281,10 +281,6 @@ QUnit.test("Can split/merge topic", function(assert) {
const controller = this.subject({ model });
const selectedPostIds = controller.get("selectedPostIds");
- assert.not(
- controller.get("canSplitTopic"),
- "can't split topic when no posts are selected"
- );
assert.not(
controller.get("canMergeTopic"),
"can't merge topic when no posts are selected"
@@ -292,10 +288,6 @@ QUnit.test("Can split/merge topic", function(assert) {
selectedPostIds.pushObject(1);
- assert.not(
- controller.get("canSplitTopic"),
- "can't split topic when can't move posts"
- );
assert.not(
controller.get("canMergeTopic"),
"can't merge topic when can't move posts"
@@ -303,16 +295,11 @@ QUnit.test("Can split/merge topic", function(assert) {
model.set("details.can_move_posts", true);
- assert.ok(controller.get("canSplitTopic"), "can split topic");
assert.ok(controller.get("canMergeTopic"), "can merge topic");
selectedPostIds.removeObject(1);
selectedPostIds.pushObject(2);
- assert.not(
- controller.get("canSplitTopic"),
- "can't split topic when 1st post is not a regular post"
- );
assert.ok(
controller.get("canMergeTopic"),
"can merge topic when 1st post is not a regular post"
@@ -320,10 +307,6 @@ QUnit.test("Can split/merge topic", function(assert) {
selectedPostIds.pushObject(3);
- assert.not(
- controller.get("canSplitTopic"),
- "can't split topic when all posts are selected"
- );
assert.ok(
controller.get("canMergeTopic"),
"can merge topic when all posts are selected"
diff --git a/test/javascripts/fixtures/dashboard-next-general.js.es6 b/test/javascripts/fixtures/dashboard-next-general.js.es6
index b37472b239..5750b7a699 100644
--- a/test/javascripts/fixtures/dashboard-next-general.js.es6
+++ b/test/javascripts/fixtures/dashboard-next-general.js.es6
@@ -1,13 +1,6 @@
export default {
"/admin/dashboard/general.json": {
reports: [],
- last_backup_taken_at: "2018-04-13T12:51:19.926Z",
- updated_at: "2018-04-25T08:06:11.292Z",
- disk_space: {
- uploads_used: "74.5 KB",
- uploads_free: "117 GB",
- backups_used: "4.24 GB",
- backups_free: "117 GB"
- }
+ updated_at: "2018-04-25T08:06:11.292Z"
}
};
diff --git a/test/javascripts/lib/pretty-text-test.js.es6 b/test/javascripts/lib/pretty-text-test.js.es6
index 32670c4129..162ef49691 100644
--- a/test/javascripts/lib/pretty-text-test.js.es6
+++ b/test/javascripts/lib/pretty-text-test.js.es6
@@ -621,6 +621,12 @@ QUnit.test("Category hashtags", assert => {
'
#category-hashtag
',
"it works between HTML tags"
);
+
+ assert.cooked(
+ "Checkout #ụdị",
+ '