diff --git a/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs b/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs
index 7911b91ab8..ef36e4aa05 100644
--- a/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs
+++ b/app/assets/javascripts/admin/templates/modal/admin-badge-preview.hbs
@@ -26,7 +26,7 @@
{{#if countWarning}}
- {{d-icon "warning"}}
+ {{d-icon "exclamation-triangle"}}
{{i18n "admin.badges.preview.bad_count_warning.header"}}
diff --git a/app/assets/javascripts/confirm-new-email/confirm-new-email.js.es6 b/app/assets/javascripts/confirm-new-email/confirm-new-email.js.es6
new file mode 100644
index 0000000000..dbcfdd25c4
--- /dev/null
+++ b/app/assets/javascripts/confirm-new-email/confirm-new-email.js.es6
@@ -0,0 +1,23 @@
+import { getWebauthnCredential } from "discourse/lib/webauthn";
+
+document.getElementById("submit-security-key").onclick = function(e) {
+ e.preventDefault();
+ getWebauthnCredential(
+ document.getElementById("security-key-challenge").value,
+ document
+ .getElementById("security-key-allowed-credential-ids")
+ .value.split(","),
+ credentialData => {
+ document.getElementById("security-key-credential").value = JSON.stringify(
+ credentialData
+ );
+
+ $(e.target)
+ .parents("form")
+ .submit();
+ },
+ errorMessage => {
+ document.getElementById("security-key-error").innerText = errorMessage;
+ }
+ );
+};
diff --git a/app/assets/javascripts/confirm-new-email/confirm-new-email.no-module.js.es6 b/app/assets/javascripts/confirm-new-email/confirm-new-email.no-module.js.es6
new file mode 100644
index 0000000000..0dd05dd8ca
--- /dev/null
+++ b/app/assets/javascripts/confirm-new-email/confirm-new-email.no-module.js.es6
@@ -0,0 +1 @@
+require("confirm-new-email/confirm-new-email").default();
diff --git a/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6 b/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6
deleted file mode 100644
index d3d301212a..0000000000
--- a/app/assets/javascripts/discourse-common/lib/buffered-render.js.es6
+++ /dev/null
@@ -1,46 +0,0 @@
-import { scheduleOnce } from "@ember/runloop";
-// Ember 2.0 removes buffered rendering, but we can still implement it ourselves.
-// In the long term we'll want to remove this.
-
-const Mixin = {
- _customRender() {
- if (!this.element || this.isDestroying || this.isDestroyed) {
- return;
- }
-
- const buffer = [];
- this.buildBuffer(buffer);
- this.element.innerHTML = buffer.join("");
- },
-
- rerenderBuffer() {
- scheduleOnce("render", this, this._customRender);
- }
-};
-
-export function bufferedRender(obj) {
- if (!obj.buildBuffer) {
- Ember.warn("Missing `buildBuffer` method", {
- id: "discourse.buffered-render.missing-build-buffer"
- });
- return obj;
- }
-
- const caller = {};
-
- caller.didRender = function() {
- this._super(...arguments);
- this._customRender();
- };
-
- const triggers = obj.rerenderTriggers;
- if (triggers) {
- caller.init = function() {
- this._super(...arguments);
- triggers.forEach(k => this.addObserver(k, this.rerenderBuffer));
- };
- }
- delete obj.rerenderTriggers;
-
- return Ember.Mixin.create(Mixin, caller, obj);
-}
diff --git a/app/assets/javascripts/discourse-common/lib/helpers.js.es6 b/app/assets/javascripts/discourse-common/lib/helpers.js.es6
index 5b7d4fdc81..0f87040d95 100644
--- a/app/assets/javascripts/discourse-common/lib/helpers.js.es6
+++ b/app/assets/javascripts/discourse-common/lib/helpers.js.es6
@@ -1,5 +1,6 @@
import { get } from "@ember/object";
import Helper from "@ember/component/helper";
+import RawHandlebars from "discourse-common/lib/raw-handlebars";
export function makeArray(obj) {
if (obj === null || obj === undefined) {
@@ -88,5 +89,5 @@ export function registerUnbound(name, fn) {
_helpers[name] = Helper.extend({
compute: (params, args) => fn(...params, args)
});
- Handlebars.registerHelper(name, func);
+ RawHandlebars.registerHelper(name, func);
}
diff --git a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6 b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6
index 70e1ab152c..aa32589a3a 100644
--- a/app/assets/javascripts/discourse-common/lib/icon-library.js.es6
+++ b/app/assets/javascripts/discourse-common/lib/icon-library.js.es6
@@ -6,11 +6,11 @@ const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
let _renderers = [];
const REPLACEMENTS = {
- "d-tracking": "circle",
- "d-muted": "times-circle",
- "d-regular": "far-circle",
- "d-watching": "exclamation-circle",
- "d-watching-first": "far-dot-circle",
+ "d-tracking": "bell",
+ "d-muted": "discourse-bell-slash",
+ "d-regular": "far-bell",
+ "d-watching": "discourse-bell-exclamation",
+ "d-watching-first": "discourse-bell-one",
"d-drop-expanded": "caret-down",
"d-drop-collapsed": "caret-right",
"d-unliked": "far-heart",
@@ -29,11 +29,11 @@ const REPLACEMENTS = {
"notification.invited_to_private_message": "far-envelope",
"notification.invited_to_topic": "hand-point-right",
"notification.invitee_accepted": "user",
- "notification.moved_post": "sign-out",
+ "notification.moved_post": "sign-out-alt",
"notification.linked": "link",
"notification.granted_badge": "certificate",
"notification.topic_reminder": "far-clock",
- "notification.watching_first_post": "far-dot-circle",
+ "notification.watching_first_post": "discourse-bell-one",
"notification.group_message_summary": "users",
"notification.post_approved": "check",
"notification.membership_request_accepted": "user-plus",
@@ -580,21 +580,25 @@ function warnIfMissing(id) {
}
}
+const reportedIcons = [];
+
function warnIfDeprecated(oldId, newId) {
deprecated(
`Please replace all occurrences of "${oldId}"" with "${newId}". FontAwesome 4.7 icon names are now deprecated and will be removed in the next release.`
);
- if (!Discourse.testing) {
+ if (!Discourse.testing && !reportedIcons.includes(oldId)) {
const errorData = {
message: `FA icon deprecation: replace "${oldId}"" with "${newId}".`,
stacktrace: Error().stack
};
Ember.$.ajax(`${Discourse.BaseUri}/logs/report_js_error`, {
- errorData,
+ data: errorData,
type: "POST",
cache: false
});
+
+ reportedIcons.push(oldId);
}
}
diff --git a/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6 b/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6
index 432ce98853..ffaa721cf3 100644
--- a/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6
+++ b/app/assets/javascripts/discourse-common/lib/raw-handlebars-helpers.js.es6
@@ -1,14 +1,23 @@
import { get } from "@ember/object";
export function registerRawHelpers(hbs, handlebarsClass) {
- hbs.helper = function() {};
- hbs.helpers = Object.create(handlebarsClass.helpers);
+ if (!hbs.helpers) {
+ hbs.helpers = Object.create(handlebarsClass.helpers);
+ }
hbs.helpers["get"] = function(context, options) {
- var firstContext = options.contexts[0];
- var val = firstContext[context];
+ if (!context || !options.contexts) {
+ return;
+ }
- if (context.indexOf("controller.") === 0) {
+ if (typeof context !== "string") {
+ return context;
+ }
+
+ let firstContext = options.contexts[0];
+ let val = firstContext[context];
+
+ if (context.toString().indexOf("controller.") === 0) {
context = context.slice(context.indexOf(".") + 1);
}
diff --git a/app/assets/javascripts/discourse.js.es6 b/app/assets/javascripts/discourse.js.es6
index 38ea5dd7bb..7b21f2bec6 100644
--- a/app/assets/javascripts/discourse.js.es6
+++ b/app/assets/javascripts/discourse.js.es6
@@ -1,9 +1,6 @@
/*global Mousetrap:true*/
import { buildResolver } from "discourse-common/resolver";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { computed } from "@ember/object";
import FocusEvent from "discourse-common/mixins/focus-event";
import EmberObject from "@ember/object";
diff --git a/app/assets/javascripts/discourse/adapters/tag-info.js.es6 b/app/assets/javascripts/discourse/adapters/tag-info.js.es6
index ca04b6d212..c2bf608c23 100644
--- a/app/assets/javascripts/discourse/adapters/tag-info.js.es6
+++ b/app/assets/javascripts/discourse/adapters/tag-info.js.es6
@@ -2,6 +2,6 @@ import RESTAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
pathFor(store, type, id) {
- return "/tags/" + id + "/info";
+ return "/tag/" + id + "/info";
}
});
diff --git a/app/assets/javascripts/discourse/adapters/tag-notification.js.es6 b/app/assets/javascripts/discourse/adapters/tag-notification.js.es6
index 6d4a5f4e47..2c0b0c0bf5 100644
--- a/app/assets/javascripts/discourse/adapters/tag-notification.js.es6
+++ b/app/assets/javascripts/discourse/adapters/tag-notification.js.es6
@@ -2,6 +2,6 @@ import RESTAdapter from "discourse/adapters/rest";
export default RESTAdapter.extend({
pathFor(store, type, id) {
- return "/tags/" + id + "/notifications";
+ return "/tag/" + id + "/notifications";
}
});
diff --git a/app/assets/javascripts/discourse/components/badge-selector.js.es6 b/app/assets/javascripts/discourse/components/badge-selector.js.es6
index 3e02b0740e..0f5c22941b 100644
--- a/app/assets/javascripts/discourse/components/badge-selector.js.es6
+++ b/app/assets/javascripts/discourse/components/badge-selector.js.es6
@@ -1,8 +1,7 @@
import Component from "@ember/component";
-import {
+import discourseComputed, {
on,
- observes,
- default as discourseComputed
+ observes
} from "discourse-common/utils/decorators";
import { findRawTemplate } from "discourse/lib/raw-templates";
const { makeArray } = Ember;
diff --git a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6 b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6
index be4ec964c2..81c08adf61 100644
--- a/app/assets/javascripts/discourse/components/basic-topic-list.js.es6
+++ b/app/assets/javascripts/discourse/components/basic-topic-list.js.es6
@@ -1,4 +1,4 @@
-import discourseComputed from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias, not } from "@ember/object/computed";
import Component from "@ember/component";
@@ -16,9 +16,10 @@ export default Component.extend({
}
},
+ @observes("topicList.[]")
_topicListChanged: function() {
this._initFromTopicList(this.topicList);
- }.observes("topicList.[]"),
+ },
_initFromTopicList(topicList) {
if (topicList !== null) {
diff --git a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6 b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6
index 9d00a25272..d8eeba3b01 100644
--- a/app/assets/javascripts/discourse/components/bread-crumbs.js.es6
+++ b/app/assets/javascripts/discourse/components/bread-crumbs.js.es6
@@ -1,6 +1,6 @@
import { filter } from "@ember/object/computed";
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import deprecated from "discourse-common/lib/deprecated";
// A breadcrumb including category drop downs
diff --git a/app/assets/javascripts/discourse/components/categories-boxes.js.es6 b/app/assets/javascripts/discourse/components/categories-boxes.js.es6
index b4dcfde7e8..c3aadd638c 100644
--- a/app/assets/javascripts/discourse/components/categories-boxes.js.es6
+++ b/app/assets/javascripts/discourse/components/categories-boxes.js.es6
@@ -1,7 +1,6 @@
import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import Component from "@ember/component";
-import DiscourseURL from "discourse/lib/url";
export default Component.extend({
tagName: "section",
@@ -19,16 +18,5 @@ export default Component.extend({
@discourseComputed("categories.[].subcategories")
hasSubcategories() {
return this.categories.any(c => !isEmpty(c.get("subcategories")));
- },
-
- click(e) {
- if (!$(e.target).is("a")) {
- const url = $(e.target)
- .closest(".category-box")
- .data("url");
- if (url) {
- DiscourseURL.routeTo(url);
- }
- }
}
});
diff --git a/app/assets/javascripts/discourse/components/composer-action-title.js.es6 b/app/assets/javascripts/discourse/components/composer-action-title.js.es6
index 996b409f6b..86a968154a 100644
--- a/app/assets/javascripts/discourse/components/composer-action-title.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-action-title.js.es6
@@ -1,6 +1,6 @@
import { alias, equal } from "@ember/object/computed";
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import {
PRIVATE_MESSAGE,
CREATE_TOPIC,
diff --git a/app/assets/javascripts/discourse/components/composer-body.js.es6 b/app/assets/javascripts/discourse/components/composer-body.js.es6
index f2a6ae3bc8..9684e9f8db 100644
--- a/app/assets/javascripts/discourse/components/composer-body.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-body.js.es6
@@ -4,10 +4,7 @@ import { cancel } from "@ember/runloop";
import { scheduleOnce } from "@ember/runloop";
import { later } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Composer from "discourse/models/composer";
import afterTransition from "discourse/lib/after-transition";
import positioningWorkaround from "discourse/lib/safari-hacks";
@@ -70,7 +67,7 @@ export default Component.extend(KeyEnterEscape, {
return;
}
- const h = $("#reply-control").height() || 0;
+ const h = $("#reply-control:not(.saving)").height() || 0;
this.movePanels(h);
});
},
diff --git a/app/assets/javascripts/discourse/components/composer-editor.js.es6 b/app/assets/javascripts/discourse/components/composer-editor.js.es6
index 0881f577b9..3304b007cc 100644
--- a/app/assets/javascripts/discourse/components/composer-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-editor.js.es6
@@ -5,8 +5,7 @@ import { scheduleOnce } from "@ember/runloop";
import { later } from "@ember/runloop";
import Component from "@ember/component";
import userSearch from "discourse/lib/user-search";
-import {
- default as discourseComputed,
+import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";
diff --git a/app/assets/javascripts/discourse/components/composer-messages.js.es6 b/app/assets/javascripts/discourse/components/composer-messages.js.es6
index e9860973a2..cafae73058 100644
--- a/app/assets/javascripts/discourse/components/composer-messages.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-messages.js.es6
@@ -97,16 +97,11 @@ export default Component.extend({
const composer = this.composer;
if (composer.get("privateMessage")) {
- let usernames = composer.get("targetUsernames");
-
- if (usernames) {
- usernames = usernames.split(",");
- }
+ const recipients = composer.targetRecipientsArray;
if (
- usernames &&
- usernames.length === 1 &&
- usernames[0] === this.currentUser.get("username")
+ recipients.length > 0 &&
+ recipients.every(r => r.name === this.currentUser.get("username"))
) {
const message =
this._yourselfConfirm ||
diff --git a/app/assets/javascripts/discourse/components/composer-title.js.es6 b/app/assets/javascripts/discourse/components/composer-title.js.es6
index 2eb06cb071..bd559a59f1 100644
--- a/app/assets/javascripts/discourse/components/composer-title.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-title.js.es6
@@ -3,10 +3,7 @@ import { next } from "@ember/runloop";
import { debounce } from "@ember/runloop";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { load } from "pretty-text/oneboxer";
import { lookupCache } from "pretty-text/oneboxer-cache";
import { ajax } from "discourse/lib/ajax";
diff --git a/app/assets/javascripts/discourse/components/composer-user-selector.js.es6 b/app/assets/javascripts/discourse/components/composer-user-selector.js.es6
index 14ca7e3556..913a9356f7 100644
--- a/app/assets/javascripts/discourse/components/composer-user-selector.js.es6
+++ b/app/assets/javascripts/discourse/components/composer-user-selector.js.es6
@@ -1,9 +1,6 @@
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Component.extend({
showSelector: true,
diff --git a/app/assets/javascripts/discourse/components/d-button.js.es6 b/app/assets/javascripts/discourse/components/d-button.js.es6
index 1d0c9d04f8..1210432127 100644
--- a/app/assets/javascripts/discourse/components/d-button.js.es6
+++ b/app/assets/javascripts/discourse/components/d-button.js.es6
@@ -1,6 +1,6 @@
import { notEmpty, empty, equal } from "@ember/object/computed";
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import DiscourseURL from "discourse/lib/url";
export default Component.extend({
diff --git a/app/assets/javascripts/discourse/components/d-editor.js.es6 b/app/assets/javascripts/discourse/components/d-editor.js.es6
index 7a26db929d..48c788c0bf 100644
--- a/app/assets/javascripts/discourse/components/d-editor.js.es6
+++ b/app/assets/javascripts/discourse/components/d-editor.js.es6
@@ -6,8 +6,7 @@ import { later } from "@ember/runloop";
import { inject as service } from "@ember/service";
import Component from "@ember/component";
/*global Mousetrap:true */
-import {
- default as discourseComputed,
+import discourseComputed, {
on,
observes
} from "discourse-common/utils/decorators";
@@ -842,7 +841,7 @@ export default Component.extend({
}
const isComposer = $("#reply-control .d-editor-input").is(":focus");
- let { clipboard, canPasteHtml } = clipboardData(e, isComposer);
+ let { clipboard, canPasteHtml, canUpload } = clipboardData(e, isComposer);
let plainText = clipboard.getData("text/plain");
let html = clipboard.getData("text/html");
@@ -892,7 +891,7 @@ export default Component.extend({
}
}
- if (handled) {
+ if (handled || canUpload) {
e.preventDefault();
}
},
diff --git a/app/assets/javascripts/discourse/components/date-input.js.es6 b/app/assets/javascripts/discourse/components/date-input.js.es6
index 7d56b964a2..9da5a5e16c 100644
--- a/app/assets/javascripts/discourse/components/date-input.js.es6
+++ b/app/assets/javascripts/discourse/components/date-input.js.es6
@@ -2,10 +2,7 @@ import { next } from "@ember/runloop";
import Component from "@ember/component";
/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["d-date-input"],
diff --git a/app/assets/javascripts/discourse/components/date-picker.js.es6 b/app/assets/javascripts/discourse/components/date-picker.js.es6
index 8ca644e952..4a32928ac0 100644
--- a/app/assets/javascripts/discourse/components/date-picker.js.es6
+++ b/app/assets/javascripts/discourse/components/date-picker.js.es6
@@ -2,10 +2,7 @@ import { next } from "@ember/runloop";
import Component from "@ember/component";
/* global Pikaday:true */
import loadScript from "discourse/lib/load-script";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
const DATE_FORMAT = "YYYY-MM-DD";
diff --git a/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6 b/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6
index 85d6fd3cb9..55240d4ee2 100644
--- a/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6
+++ b/app/assets/javascripts/discourse/components/discourse-linked-text.js.es6
@@ -1,5 +1,5 @@
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "span",
diff --git a/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6 b/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6
index 57e5c1841a..13c8798631 100644
--- a/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6
+++ b/app/assets/javascripts/discourse/components/discourse-tag-bound.js.es6
@@ -13,6 +13,6 @@ export default Component.extend({
@discourseComputed("tagRecord.id")
href(tagRecordId) {
- return Discourse.getURL("/tags/" + tagRecordId);
+ return Discourse.getURL("/tag/" + tagRecordId);
}
});
diff --git a/app/assets/javascripts/discourse/components/edit-category-images.js.es6 b/app/assets/javascripts/discourse/components/edit-category-images.js.es6
index 94956edf36..953a1a62e7 100644
--- a/app/assets/javascripts/discourse/components/edit-category-images.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-images.js.es6
@@ -1,6 +1,6 @@
import EmberObject from "@ember/object";
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default buildCategoryPanel("images").extend({
@discourseComputed("category.uploaded_background.url")
diff --git a/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6 b/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6
index c3254cc3d6..9a5d7d7ab1 100644
--- a/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-category-topic-template.js.es6
@@ -1,12 +1,14 @@
import { scheduleOnce } from "@ember/runloop";
import { buildCategoryPanel } from "discourse/components/edit-category-panel";
+import { observes } from "discourse-common/utils/decorators";
export default buildCategoryPanel("topic-template", {
+ @observes("activeTab")
_activeTabChanged: function() {
if (this.activeTab) {
scheduleOnce("afterRender", () =>
this.element.querySelector(".d-editor-input").focus()
);
}
- }.observes("activeTab")
+ }
});
diff --git a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6 b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6
index 00fa6b2bad..18f2ac04f0 100644
--- a/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6
+++ b/app/assets/javascripts/discourse/components/edit-topic-timer-form.js.es6
@@ -2,8 +2,7 @@ import { isEmpty } from "@ember/utils";
import { alias, equal, or } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
+import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";
diff --git a/app/assets/javascripts/discourse/components/emoji-uploader.js.es6 b/app/assets/javascripts/discourse/components/emoji-uploader.js.es6
index fa7ee8fd10..77e2d5e9ff 100644
--- a/app/assets/javascripts/discourse/components/emoji-uploader.js.es6
+++ b/app/assets/javascripts/discourse/components/emoji-uploader.js.es6
@@ -1,6 +1,6 @@
import { notEmpty, not } from "@ember/object/computed";
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import UploadMixin from "discourse/mixins/upload";
export default Component.extend(UploadMixin, {
diff --git a/app/assets/javascripts/discourse/components/flat-button.js.es6 b/app/assets/javascripts/discourse/components/flat-button.js.es6
index af44422025..f2f38fdc27 100644
--- a/app/assets/javascripts/discourse/components/flat-button.js.es6
+++ b/app/assets/javascripts/discourse/components/flat-button.js.es6
@@ -1,5 +1,5 @@
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
tagName: "button",
diff --git a/app/assets/javascripts/discourse/components/future-date-input.js.es6 b/app/assets/javascripts/discourse/components/future-date-input.js.es6
index 05313f8ea0..2c1bb70b54 100644
--- a/app/assets/javascripts/discourse/components/future-date-input.js.es6
+++ b/app/assets/javascripts/discourse/components/future-date-input.js.es6
@@ -1,10 +1,7 @@
import { isEmpty } from "@ember/utils";
import { equal, and, empty } from "@ember/object/computed";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { FORMAT } from "select-kit/components/future-date-input-selector";
import { PUBLISH_TO_CATEGORY_STATUS_TYPE } from "discourse/controllers/edit-topic-timer";
diff --git a/app/assets/javascripts/discourse/components/global-notice.js.es6 b/app/assets/javascripts/discourse/components/global-notice.js.es6
index 7b80c97715..6096ebe1de 100644
--- a/app/assets/javascripts/discourse/components/global-notice.js.es6
+++ b/app/assets/javascripts/discourse/components/global-notice.js.es6
@@ -1,119 +1,233 @@
-import { bind } from "@ember/runloop";
+import { bind, cancel } from "@ember/runloop";
import Component from "@ember/component";
-import { on } from "discourse-common/utils/decorators";
-import { iconHTML } from "discourse-common/lib/icon-library";
import LogsNotice from "discourse/services/logs-notice";
-import { bufferedRender } from "discourse-common/lib/buffered-render";
+import EmberObject from "@ember/object";
-export default Component.extend(
- bufferedRender({
- rerenderTriggers: ["site.isReadOnly", "siteSettings.disable_emails"],
+const _pluginNotices = [];
- buildBuffer(buffer) {
+export function addGlobalNotice(text, id, options = {}) {
+ _pluginNotices.push(Notice.create({ text, id, options }));
+}
+
+const GLOBAL_NOTICE_DISMISSED_PROMPT_KEY = "dismissed-global-notice-v2";
+
+const Notice = EmberObject.extend({
+ text: null,
+ id: null,
+ options: null,
+
+ init() {
+ this._super(...arguments);
+
+ const defaults = {
+ // can this banner be hidden
+ dismissable: false,
+ // prepend html content
+ html: null,
+ // will define the style of the banner, follows alerts styling
+ level: "info",
+ // should the banner be permanently hidden?
+ persistentDismiss: true,
+ // callback function when dismissing a banner
+ onDismiss: null,
+ // show/hide banner function, will take precedence over everything
+ visibility: null,
+ // how long before banner should show again, eg: moment.duration(1, "week")
+ dismissDuration: null
+ };
+
+ this.options = this.set(
+ "options",
+ Object.assign(defaults, this.options || {})
+ );
+ }
+});
+
+export default Component.extend({
+ logNotice: null,
+
+ init() {
+ this._super(...arguments);
+
+ this._setupObservers();
+ },
+
+ willDestroyElement() {
+ this._super(...arguments);
+
+ this._tearDownObservers();
+ },
+
+ notices: Ember.computed(
+ "site.isReadOnly",
+ "siteSettings.disable_emails",
+ "logNotice.{id,text,hidden}",
+ function() {
let notices = [];
if ($.cookie("dosp") === "1") {
$.removeCookie("dosp", { path: "/" });
- notices.push([I18n.t("forced_anonymous"), "forced-anonymous"]);
+ notices.push(
+ Notice.create({
+ text: I18n.t("forced_anonymous"),
+ id: "forced-anonymous"
+ })
+ );
}
- if (this.session.get("safe_mode")) {
- notices.push([I18n.t("safe_mode.enabled"), "safe-mode"]);
+ if (this.session && this.session.safe_mode) {
+ notices.push(
+ Notice.create({ text: I18n.t("safe_mode.enabled"), id: "safe-mode" })
+ );
}
- if (this.site.get("isReadOnly")) {
- notices.push([I18n.t("read_only_mode.enabled"), "alert-read-only"]);
+ if (this.site.isReadOnly) {
+ notices.push(
+ Notice.create({
+ text: I18n.t("read_only_mode.enabled"),
+ id: "alert-read-only"
+ })
+ );
}
if (
this.siteSettings.disable_emails === "yes" ||
this.siteSettings.disable_emails === "non-staff"
) {
- notices.push([I18n.t("emails_are_disabled"), "alert-emails-disabled"]);
+ notices.push(
+ Notice.create({
+ text: I18n.t("emails_are_disabled"),
+ id: "alert-emails-disabled"
+ })
+ );
}
- if (this.site.get("wizard_required")) {
+ if (this.site.wizard_required) {
const requiredText = I18n.t("wizard_required", {
url: Discourse.getURL("/wizard")
});
- notices.push([requiredText, "alert-wizard"]);
+ notices.push(Notice.create({ text: requiredText, id: "alert-wizard" }));
}
if (
- this.currentUser &&
- this.currentUser.get("staff") &&
+ this.get("currentUser.staff") &&
this.siteSettings.bootstrap_mode_enabled
) {
if (this.siteSettings.bootstrap_mode_min_users > 0) {
- notices.push([
- I18n.t("bootstrap_mode_enabled", {
- min_users: this.siteSettings.bootstrap_mode_min_users
- }),
- "alert-bootstrap-mode"
- ]);
+ notices.push(
+ Notice.create({
+ text: I18n.t("bootstrap_mode_enabled", {
+ min_users: this.siteSettings.bootstrap_mode_min_users
+ }),
+ id: "alert-bootstrap-mode"
+ })
+ );
} else {
- notices.push([
- I18n.t("bootstrap_mode_disabled"),
- "alert-bootstrap-mode"
- ]);
+ notices.push(
+ Notice.create({
+ text: I18n.t("bootstrap_mode_disabled"),
+ id: "alert-bootstrap-mode"
+ })
+ );
}
}
- if (!_.isEmpty(this.siteSettings.global_notice)) {
- notices.push([this.siteSettings.global_notice, "alert-global-notice"]);
- }
-
- if (!LogsNotice.currentProp("hidden")) {
- notices.push([
- LogsNotice.currentProp("message"),
- "alert-logs-notice",
- `${iconHTML("times")} `
- ]);
- }
-
- if (notices.length > 0) {
- buffer.push(
- notices
- .map(n => {
- var html = `
`;
- if (n[2]) html += n[2];
- html += `${n[0]}
`;
- return html;
- })
- .join("")
+ if (
+ this.siteSettings.global_notice &&
+ this.siteSettings.global_notice.length
+ ) {
+ notices.push(
+ Notice.create({
+ text: this.siteSettings.global_notice,
+ id: "alert-global-notice"
+ })
);
}
- },
- @on("didInsertElement")
- _setupLogsNotice() {
- this._boundRerenderBuffer = bind(this, this.rerenderBuffer);
- LogsNotice.current().addObserver("hidden", this._boundRerenderBuffer);
-
- this._boundResetCurrentProp = bind(this, this._resetCurrentProp);
- $(this.element).on(
- "click.global-notice",
- ".alert-logs-notice .close",
- this._boundResetCurrentProp
- );
- },
-
- @on("willDestroyElement")
- _teardownLogsNotice() {
- if (this._boundResetCurrentProp) {
- $(this.element).off("click.global-notice", this._boundResetCurrentProp);
+ if (this.logNotice) {
+ notices.push(this.logNotice);
}
- if (this._boundRerenderBuffer) {
- LogsNotice.current().removeObserver(
- "hidden",
- this._boundRerenderBuffer
- );
- }
- },
+ return notices.concat(_pluginNotices).filter(notice => {
+ if (notice.options.visibility) {
+ return notice.options.visibility(notice);
+ } else {
+ const key = `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`;
+ const value = this.keyValueStore.get(key);
- _resetCurrentProp() {
- LogsNotice.currentProp("text", "");
+ // banner has never been dismissed
+ if (!value) {
+ return true;
+ }
+
+ // banner has no persistent dismiss and should always show on load
+ if (!notice.options.persistentDismiss) {
+ return true;
+ }
+
+ if (notice.options.dismissDuration) {
+ const resetAt = moment(value).add(notice.options.dismissDuration);
+ return moment().isAfter(resetAt);
+ } else {
+ return false;
+ }
+ }
+ });
}
- })
-);
+ ),
+
+ actions: {
+ dismissNotice(notice) {
+ if (notice.options.onDismiss) {
+ notice.options.onDismiss(notice);
+ }
+
+ if (notice.options.persistentDismiss) {
+ this.keyValueStore.set({
+ key: `${GLOBAL_NOTICE_DISMISSED_PROMPT_KEY}-${notice.id}`,
+ value: moment().toISOString(true)
+ });
+ }
+
+ const alert = document.getElementById(`global-notice-${notice.id}`);
+ if (alert) alert.style.display = "none";
+ }
+ },
+
+ _setupObservers() {
+ this._boundLogsNoticeHandler = bind(this, this._handleLogsNoticeUpdate);
+ LogsNotice.current().addObserver("hidden", this._boundLogsNoticeHandler);
+ LogsNotice.current().addObserver("text", this._boundLogsNoticeHandler);
+ },
+
+ _tearDownObservers() {
+ if (this._boundLogsNoticeHandler) {
+ LogsNotice.current().removeObserver("text", this._boundLogsNoticeHandler);
+ LogsNotice.current().removeObserver(
+ "hidden",
+ this._boundLogsNoticeHandler
+ );
+ cancel(this._boundLogsNoticeHandler);
+ }
+ },
+
+ _handleLogsNoticeUpdate() {
+ const logNotice = Notice.create({
+ text: LogsNotice.currentProp("message"),
+ id: "alert-logs-notice",
+ options: {
+ dismissable: true,
+ persistentDismiss: false,
+ visibility() {
+ return !LogsNotice.currentProp("hidden");
+ },
+ onDismiss() {
+ LogsNotice.currentProp("hidden", true);
+ LogsNotice.currentProp("text", "");
+ }
+ }
+ });
+
+ this.set("logNotice", logNotice);
+ }
+});
diff --git a/app/assets/javascripts/discourse/components/group-card-contents.js.es6 b/app/assets/javascripts/discourse/components/group-card-contents.js.es6
index 1838b32352..38565e2a76 100644
--- a/app/assets/javascripts/discourse/components/group-card-contents.js.es6
+++ b/app/assets/javascripts/discourse/components/group-card-contents.js.es6
@@ -1,7 +1,7 @@
import { alias, match, gt, or } from "@ember/object/computed";
import Component from "@ember/component";
import { setting } from "discourse/lib/computed";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import CardContentsBase from "discourse/mixins/card-contents-base";
import CleansUp from "discourse/mixins/cleans-up";
import { groupPath } from "discourse/lib/url";
diff --git a/app/assets/javascripts/discourse/components/group-membership-button.js.es6 b/app/assets/javascripts/discourse/components/group-membership-button.js.es6
index 7c4abeda18..7c808bc3eb 100644
--- a/app/assets/javascripts/discourse/components/group-membership-button.js.es6
+++ b/app/assets/javascripts/discourse/components/group-membership-button.js.es6
@@ -1,5 +1,5 @@
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
@@ -31,6 +31,14 @@ export default Component.extend({
$.cookie("destination_url", window.location.href);
},
+ removeFromGroup() {
+ this.model
+ .removeMember(this.currentUser)
+ .then(() => this.model.set("is_group_user", false))
+ .catch(popupAjaxError)
+ .finally(() => this.set("updatingMembership", false));
+ },
+
actions: {
joinGroup() {
if (this.currentUser) {
@@ -53,17 +61,21 @@ export default Component.extend({
leaveGroup() {
this.set("updatingMembership", true);
- const model = this.model;
- model
- .removeMember(this.currentUser)
- .then(() => {
- model.set("is_group_user", false);
- })
- .catch(popupAjaxError)
- .finally(() => {
- this.set("updatingMembership", false);
- });
+ if (this.model.public_admission) {
+ this.removeFromGroup();
+ } else {
+ return bootbox.confirm(
+ I18n.t("groups.confirm_leave"),
+ I18n.t("no_value"),
+ I18n.t("yes_value"),
+ result => {
+ result
+ ? this.removeFromGroup()
+ : this.set("updatingMembership", false);
+ }
+ );
+ }
},
showRequestMembershipForm() {
diff --git a/app/assets/javascripts/discourse/components/group-selector.js.es6 b/app/assets/javascripts/discourse/components/group-selector.js.es6
index 54b789664b..74c1c4b16f 100644
--- a/app/assets/javascripts/discourse/components/group-selector.js.es6
+++ b/app/assets/javascripts/discourse/components/group-selector.js.es6
@@ -1,9 +1,8 @@
import { isEmpty } from "@ember/utils";
import Component from "@ember/component";
-import {
+import discourseComputed, {
on,
- observes,
- default as discourseComputed
+ observes
} from "discourse-common/utils/decorators";
import { findRawTemplate } from "discourse/lib/raw-templates";
diff --git a/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6 b/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6
index f3ba2d92a4..62a7d17ece 100644
--- a/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6
+++ b/app/assets/javascripts/discourse/components/groups-form-interaction-fields.js.es6
@@ -1,5 +1,5 @@
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
init() {
diff --git a/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6 b/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6
index fda6dd8ec6..988e4ac843 100644
--- a/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6
+++ b/app/assets/javascripts/discourse/components/groups-form-profile-fields.js.es6
@@ -1,10 +1,7 @@
import { isEmpty } from "@ember/utils";
import { not } from "@ember/object/computed";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Group from "discourse/models/group";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseDebounce from "discourse/lib/debounce";
diff --git a/app/assets/javascripts/discourse/components/input-tip.js.es6 b/app/assets/javascripts/discourse/components/input-tip.js.es6
index 1a12d8ebc0..b48a5df8cf 100644
--- a/app/assets/javascripts/discourse/components/input-tip.js.es6
+++ b/app/assets/javascripts/discourse/components/input-tip.js.es6
@@ -4,7 +4,6 @@ import { iconHTML } from "discourse-common/lib/icon-library";
export default Component.extend({
classNameBindings: [":tip", "good", "bad"],
- rerenderTriggers: ["validation"],
tipIcon: null,
tipReason: null,
diff --git a/app/assets/javascripts/discourse/components/navigation-bar.js.es6 b/app/assets/javascripts/discourse/components/navigation-bar.js.es6
index 6051726395..3da0001ad9 100644
--- a/app/assets/javascripts/discourse/components/navigation-bar.js.es6
+++ b/app/assets/javascripts/discourse/components/navigation-bar.js.es6
@@ -1,9 +1,6 @@
import { next } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import DiscourseURL from "discourse/lib/url";
import { renderedConnectorsFor } from "discourse/lib/plugin-connectors";
import FilterModeMixin from "discourse/mixins/filter-mode";
diff --git a/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6 b/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6
index 1ffb99c0c2..97ee9b0aa7 100644
--- a/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6
+++ b/app/assets/javascripts/discourse/components/notification-consent-banner.js.es6
@@ -1,6 +1,6 @@
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { keyValueStore as pushNotificationKeyValueStore } from "discourse/lib/push-notifications";
-import { default as DesktopNotificationConfig } from "discourse/components/desktop-notification-config";
+import DesktopNotificationConfig from "discourse/components/desktop-notification-config";
const userDismissedPromptKey = "dismissed-prompt";
diff --git a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6 b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6
index 6254fe2f1e..08f6cf39af 100644
--- a/app/assets/javascripts/discourse/components/popup-input-tip.js.es6
+++ b/app/assets/javascripts/discourse/components/popup-input-tip.js.es6
@@ -1,10 +1,7 @@
import { alias, not } from "@ember/object/computed";
import Component from "@ember/component";
import { iconHTML } from "discourse-common/lib/icon-library";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Component.extend({
classNameBindings: [":popup-tip", "good", "bad", "lastShownAt::hide"],
diff --git a/app/assets/javascripts/discourse/components/private-message-glyph.js.es6 b/app/assets/javascripts/discourse/components/private-message-glyph.js.es6
new file mode 100644
index 0000000000..26e636b05d
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/private-message-glyph.js.es6
@@ -0,0 +1,8 @@
+import Component from "@ember/component";
+
+export default Component.extend({
+ tagName: null,
+ href: null,
+ title: null,
+ ariaLabel: null
+});
diff --git a/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6 b/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6
index cb11ef38b7..1dd4bef396 100644
--- a/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6
+++ b/app/assets/javascripts/discourse/components/pwa-install-banner.js.es6
@@ -1,9 +1,6 @@
import { bind } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
const USER_DISMISSED_PROMPT_KEY = "dismissed-pwa-install-banner";
diff --git a/app/assets/javascripts/discourse/components/related-messages.js.es6 b/app/assets/javascripts/discourse/components/related-messages.js.es6
index 5fec6f28ba..46732424f8 100644
--- a/app/assets/javascripts/discourse/components/related-messages.js.es6
+++ b/app/assets/javascripts/discourse/components/related-messages.js.es6
@@ -1,6 +1,5 @@
import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
-import { iconHTML } from "discourse-common/lib/icon-library";
export default Component.extend({
elementId: "related-messages",
@@ -31,14 +30,7 @@ export default Component.extend({
},
@discourseComputed("topic")
- relatedTitle(topic) {
- const href = this.currentUser && this.currentUser.pmPath(topic);
- return href
- ? `
${iconHTML("envelope", {
- class: "private-message-glyph"
- })} ${I18n.t("related_messages.title")} `
- : I18n.t("related_messages.title");
+ relatedTitleLink(topic) {
+ return this.currentUser && this.currentUser.pmPath(topic);
}
});
diff --git a/app/assets/javascripts/discourse/components/reviewable-user.js.es6 b/app/assets/javascripts/discourse/components/reviewable-user.js.es6
index 3dd3043371..5feb5d84f2 100644
--- a/app/assets/javascripts/discourse/components/reviewable-user.js.es6
+++ b/app/assets/javascripts/discourse/components/reviewable-user.js.es6
@@ -1,5 +1,5 @@
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
@discourseComputed("reviewable.user_fields")
diff --git a/app/assets/javascripts/discourse/components/second-factor-input.js.es6 b/app/assets/javascripts/discourse/components/second-factor-input.js.es6
index 45ed1f4b9e..98ca98ac01 100644
--- a/app/assets/javascripts/discourse/components/second-factor-input.js.es6
+++ b/app/assets/javascripts/discourse/components/second-factor-input.js.es6
@@ -19,6 +19,6 @@ export default Component.extend({
@discourseComputed("secondFactorMethod")
maxlength(secondFactorMethod) {
if (secondFactorMethod === SECOND_FACTOR_METHODS.TOTP) return "6";
- if (secondFactorMethod === SECOND_FACTOR_METHODS.BACKUP_CODE) return "16";
+ if (secondFactorMethod === SECOND_FACTOR_METHODS.BACKUP_CODE) return "32";
}
});
diff --git a/app/assets/javascripts/discourse/components/share-panel.js.es6 b/app/assets/javascripts/discourse/components/share-panel.js.es6
index 97e09c225e..78bc76db23 100644
--- a/app/assets/javascripts/discourse/components/share-panel.js.es6
+++ b/app/assets/javascripts/discourse/components/share-panel.js.es6
@@ -3,7 +3,7 @@ import { alias } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
import { escapeExpression } from "discourse/lib/utilities";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Sharing from "discourse/lib/sharing";
export default Component.extend({
diff --git a/app/assets/javascripts/discourse/components/share-popup.js.es6 b/app/assets/javascripts/discourse/components/share-popup.js.es6
index c2174068bd..ba32b26585 100644
--- a/app/assets/javascripts/discourse/components/share-popup.js.es6
+++ b/app/assets/javascripts/discourse/components/share-popup.js.es6
@@ -4,10 +4,7 @@ import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
import { wantsNewWindow } from "discourse/lib/intercept-click";
import { longDateNoYear } from "discourse/lib/formatter";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
import Sharing from "discourse/lib/sharing";
import { nativeShare } from "discourse/lib/pwa-utils";
diff --git a/app/assets/javascripts/discourse/components/suggested-topics.js.es6 b/app/assets/javascripts/discourse/components/suggested-topics.js.es6
index c8ea62e827..d6010eba93 100644
--- a/app/assets/javascripts/discourse/components/suggested-topics.js.es6
+++ b/app/assets/javascripts/discourse/components/suggested-topics.js.es6
@@ -2,24 +2,20 @@ import discourseComputed from "discourse-common/utils/decorators";
import { get } from "@ember/object";
import Component from "@ember/component";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
-import { iconHTML } from "discourse-common/lib/icon-library";
import Site from "discourse/models/site";
+import { computed } from "@ember/object";
export default Component.extend({
elementId: "suggested-topics",
classNames: ["suggested-topics"],
- @discourseComputed("topic")
- suggestedTitle(topic) {
- const href = this.currentUser && this.currentUser.pmPath(topic);
- return topic.get("isPrivateMessage") && href
- ? `
${iconHTML("envelope", {
- class: "private-message-glyph"
- })} ${I18n.t("suggested_topics.pm_title")} `
- : I18n.t("suggested_topics.title");
- },
+ suggestedTitleLabel: computed("topic", function() {
+ if (this.currentUser && this.currentUser.pmPath(this.topic)) {
+ return "suggested_topics.pm_title";
+ } else {
+ return "suggested_topics.title";
+ }
+ }),
@discourseComputed("topic", "topicTrackingState.messageCount")
browseMoreMessage(topic) {
diff --git a/app/assets/javascripts/discourse/components/tag-drop-link.js.es6 b/app/assets/javascripts/discourse/components/tag-drop-link.js.es6
index e8cfada41a..d26c68d0b3 100644
--- a/app/assets/javascripts/discourse/components/tag-drop-link.js.es6
+++ b/app/assets/javascripts/discourse/components/tag-drop-link.js.es6
@@ -14,11 +14,11 @@ export default Component.extend({
@discourseComputed("tagId", "category")
href(tagId, category) {
- var url = "/tags";
if (category) {
- url += category.url;
+ return "/tags" + category.url + "/" + tagId;
+ } else {
+ return "/tag/" + tagId;
}
- return url + "/" + tagId;
},
@discourseComputed("tagId")
diff --git a/app/assets/javascripts/discourse/components/tag-info.js.es6 b/app/assets/javascripts/discourse/components/tag-info.js.es6
index da1acd10a0..6421f47f59 100644
--- a/app/assets/javascripts/discourse/components/tag-info.js.es6
+++ b/app/assets/javascripts/discourse/components/tag-info.js.es6
@@ -1,7 +1,7 @@
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import showModal from "discourse/lib/show-modal";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
import { reads, and } from "@ember/object/computed";
import { isEmpty } from "@ember/utils";
@@ -76,7 +76,7 @@ export default Component.extend({
},
unlinkSynonym(tag) {
- ajax(`/tags/${this.tagInfo.name}/synonyms/${tag.id}`, {
+ ajax(`/tag/${this.tagInfo.name}/synonyms/${tag.id}`, {
type: "DELETE"
})
.then(() => this.tagInfo.synonyms.removeObject(tag))
@@ -98,7 +98,7 @@ export default Component.extend({
},
addSynonyms() {
- ajax(`/tags/${this.tagInfo.name}/synonyms`, {
+ ajax(`/tag/${this.tagInfo.name}/synonyms`, {
type: "POST",
data: {
synonyms: this.newSynonyms
diff --git a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6 b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6
index 85e8caf025..608d2e588c 100644
--- a/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6
+++ b/app/assets/javascripts/discourse/components/tags-admin-dropdown.js.es6
@@ -35,7 +35,7 @@ export default DropdownSelectBoxComponent.extend({
id: "deleteUnusedTags",
name: I18n.t("tagging.delete_unused"),
description: I18n.t("tagging.delete_unused_description"),
- icon: "trash",
+ icon: "trash-alt",
__sk_row_type: "noopRow"
}
];
diff --git a/app/assets/javascripts/discourse/components/text-overflow.js.es6 b/app/assets/javascripts/discourse/components/text-overflow.js.es6
index d5bc066fb0..90a9cc21a2 100644
--- a/app/assets/javascripts/discourse/components/text-overflow.js.es6
+++ b/app/assets/javascripts/discourse/components/text-overflow.js.es6
@@ -7,6 +7,7 @@ export default Component.extend({
const $this = $(this.element);
if ($this) {
+ $this.find("br").replaceWith(" ");
$this.find("hr").remove();
$this.ellipsis();
}
diff --git a/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6 b/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6
index 4a55fb8886..07db8b0fa8 100644
--- a/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-join-group-notice.js.es6
@@ -1,5 +1,5 @@
import Component from "@ember/component";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
classNames: ["topic-notice"],
diff --git a/app/assets/javascripts/discourse/components/topic-list-item.js.es6 b/app/assets/javascripts/discourse/components/topic-list-item.js.es6
index 670d45b64c..7bc2d64603 100644
--- a/app/assets/javascripts/discourse/components/topic-list-item.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-list-item.js.es6
@@ -1,8 +1,8 @@
-import discourseComputed from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { alias } from "@ember/object/computed";
import Component from "@ember/component";
+import { schedule } from "@ember/runloop";
import DiscourseURL from "discourse/lib/url";
-import { bufferedRender } from "discourse-common/lib/buffered-render";
import { findRawTemplate } from "discourse/lib/raw-templates";
import { wantsNewWindow } from "discourse/lib/intercept-click";
import { on } from "@ember/object/evented";
@@ -32,12 +32,25 @@ export function navigateToTopic(topic, href) {
return false;
}
-export const ListItemDefaults = {
+export default Component.extend({
tagName: "tr",
classNameBindings: [":topic-list-item", "unboundClassNames", "topic.visited"],
attributeBindings: ["data-topic-id"],
"data-topic-id": alias("topic.id"),
+ didReceiveAttrs() {
+ this._super(...arguments);
+ this.renderTopicListItem();
+ },
+
+ @observes("topic.pinned")
+ renderTopicListItem() {
+ const template = findRawTemplate("list/topic-list-item");
+ if (template) {
+ this.set("topicListItemContents", template(this).htmlSafe());
+ }
+ },
+
didInsertElement() {
this._super(...arguments);
@@ -194,15 +207,29 @@ export const ListItemDefaults = {
return this.unhandledRowClick(e, topic);
},
+ actions: {
+ toggleBookmark() {
+ this.topic.toggleBookmark().finally(() => this.renderTopicListItem());
+ }
+ },
+
+ unhandledRowClick() {},
+
navigateToTopic,
highlight(opts = { isLastViewedTopic: false }) {
- const $topic = $(this.element);
- $topic
- .addClass("highlighted")
- .attr("data-islastviewedtopic", opts.isLastViewedTopic);
+ schedule("afterRender", () => {
+ if (!this.element || this.isDestroying || this.isDestroyed) {
+ return;
+ }
- $topic.on("animationend", () => $topic.removeClass("highlighted"));
+ const $topic = $(this.element);
+ $topic
+ .addClass("highlighted")
+ .attr("data-islastviewedtopic", opts.isLastViewedTopic);
+
+ $topic.on("animationend", () => $topic.removeClass("highlighted"));
+ });
},
_highlightIfNeeded: on("didInsertElement", function() {
@@ -216,27 +243,4 @@ export const ListItemDefaults = {
this.highlight();
}
})
-};
-
-export default Component.extend(
- ListItemDefaults,
- bufferedRender({
- rerenderTriggers: ["bulkSelectEnabled", "topic.pinned"],
-
- actions: {
- toggleBookmark() {
- this.topic.toggleBookmark().finally(() => this.rerenderBuffer());
- }
- },
-
- buildBuffer(buffer) {
- const template = findRawTemplate("list/topic-list-item");
- if (template) {
- buffer.push(template(this));
- }
- },
-
- // Can be overwritten by plugins to handle clicks on other parts of the row
- unhandledRowClick() {}
- })
-);
+});
diff --git a/app/assets/javascripts/discourse/components/topic-list.js.es6 b/app/assets/javascripts/discourse/components/topic-list.js.es6
index d744b28a70..b4661eb68d 100644
--- a/app/assets/javascripts/discourse/components/topic-list.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-list.js.es6
@@ -1,10 +1,7 @@
import { alias, reads } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import LoadMore from "discourse/mixins/load-more";
import { on } from "@ember/object/evented";
diff --git a/app/assets/javascripts/discourse/components/topic-progress.js.es6 b/app/assets/javascripts/discourse/components/topic-progress.js.es6
index 3e055c96ed..61c4484be4 100644
--- a/app/assets/javascripts/discourse/components/topic-progress.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-progress.js.es6
@@ -1,10 +1,7 @@
import { alias } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Component.extend({
elementId: "topic-progress-wrapper",
diff --git a/app/assets/javascripts/discourse/components/topic-timeline.js.es6 b/app/assets/javascripts/discourse/components/topic-timeline.js.es6
index 64f0dbc2d8..233155f2f3 100644
--- a/app/assets/javascripts/discourse/components/topic-timeline.js.es6
+++ b/app/assets/javascripts/discourse/components/topic-timeline.js.es6
@@ -102,5 +102,6 @@ export default MountWidget.extend(Docking, {
}
this.dispatch("topic:current-post-scrolled", "timeline-scrollarea");
+ this.dispatch("topic:toggle-actions", "topic-admin-menu-button");
}
});
diff --git a/app/assets/javascripts/discourse/components/track-selected.js.es6 b/app/assets/javascripts/discourse/components/track-selected.js.es6
index 241cb64c7c..e8d0ec3428 100644
--- a/app/assets/javascripts/discourse/components/track-selected.js.es6
+++ b/app/assets/javascripts/discourse/components/track-selected.js.es6
@@ -1,6 +1,10 @@
import Component from "@ember/component";
+import { observes } from "discourse-common/utils/decorators";
+
export default Component.extend({
tagName: "span",
+
+ @observes("selected")
selectionChanged: function() {
const selected = this.selected;
const list = this.selectedList;
@@ -11,5 +15,5 @@ export default Component.extend({
} else {
list.removeObject(id);
}
- }.observes("selected")
+ }
});
diff --git a/app/assets/javascripts/discourse/components/user-card-contents.js.es6 b/app/assets/javascripts/discourse/components/user-card-contents.js.es6
index 6607fe7961..6d76c15306 100644
--- a/app/assets/javascripts/discourse/components/user-card-contents.js.es6
+++ b/app/assets/javascripts/discourse/components/user-card-contents.js.es6
@@ -2,10 +2,7 @@ import { isEmpty } from "@ember/utils";
import { alias, gte, and, gt, not, or } from "@ember/object/computed";
import EmberObject from "@ember/object";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import User from "discourse/models/user";
import { propertyNotEqual, setting } from "discourse/lib/computed";
import { durationTiny } from "discourse/lib/formatter";
diff --git a/app/assets/javascripts/discourse/components/user-selector.js.es6 b/app/assets/javascripts/discourse/components/user-selector.js.es6
index 85e5c61dd7..79de2262f3 100644
--- a/app/assets/javascripts/discourse/components/user-selector.js.es6
+++ b/app/assets/javascripts/discourse/components/user-selector.js.es6
@@ -62,14 +62,15 @@ export default TextField.extend({
allowEmails = bool("allowEmails"),
fullWidthWrap = bool("fullWidthWrap");
- const excludedUsernames = () => {
+ const allExcludedUsernames = () => {
// hack works around some issues with allowAny eventing
- const usernames = single ? [] : selected;
+ let usernames = single ? [] : selected;
if (currentUser && excludeCurrentUser) {
- return usernames.concat([currentUser.username]);
+ usernames.concat([currentUser.username]);
}
- return usernames;
+
+ return usernames.concat(this.excludedUsernames || []);
};
this.element.addEventListener("paste", this._paste);
@@ -90,7 +91,7 @@ export default TextField.extend({
return userSearch({
term,
topicId: userSelectorComponent.topicId,
- exclude: excludedUsernames(),
+ exclude: allExcludedUsernames(),
includeGroups,
allowedUsers,
includeMentionableGroups,
@@ -107,7 +108,7 @@ export default TextField.extend({
}
return v.username || v.name;
} else {
- const excludes = excludedUsernames();
+ const excludes = allExcludedUsernames();
return v.usernames.filter(item => excludes.indexOf(item) === -1);
}
},
@@ -158,7 +159,10 @@ export default TextField.extend({
(text || "").split(/[, \n]+/).forEach(val => {
val = val.replace(/^@+/, "").trim();
- if (val.length > 0) {
+ if (
+ val.length > 0 &&
+ (!this.excludedUsernames || !this.excludedUsernames.includes(val))
+ ) {
usernames.push(val);
}
});
diff --git a/app/assets/javascripts/discourse/components/user-stream.js.es6 b/app/assets/javascripts/discourse/components/user-stream.js.es6
index 79543dd394..62d9fe7f0d 100644
--- a/app/assets/javascripts/discourse/components/user-stream.js.es6
+++ b/app/assets/javascripts/discourse/components/user-stream.js.es6
@@ -7,6 +7,7 @@ import DiscourseURL from "discourse/lib/url";
import Draft from "discourse/models/draft";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { getOwner } from "discourse-common/lib/get-owner";
+import { observes } from "discourse-common/utils/decorators";
import { on } from "@ember/object/evented";
export default Component.extend(LoadMore, {
@@ -24,9 +25,10 @@ export default Component.extend(LoadMore, {
eyelineSelector: ".user-stream .item",
classNames: ["user-stream"],
+ @observes("stream.user.id")
_scrollTopOnModelChange: function() {
schedule("afterRender", () => $(document).scrollTop(0));
- }.observes("stream.user.id"),
+ },
_inserted: on("didInsertElement", function() {
this.bindScrolling({ name: "user-stream-view" });
diff --git a/app/assets/javascripts/discourse/controllers/badges/show.js.es6 b/app/assets/javascripts/discourse/controllers/badges/show.js.es6
index 807ea2b596..e9d70aa80f 100644
--- a/app/assets/javascripts/discourse/controllers/badges/show.js.es6
+++ b/app/assets/javascripts/discourse/controllers/badges/show.js.es6
@@ -3,10 +3,7 @@ import EmberObject from "@ember/object";
import Controller from "@ember/controller";
import Badge from "discourse/models/badge";
import UserBadge from "discourse/models/user-badge";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
queryParams: ["username"],
diff --git a/app/assets/javascripts/discourse/controllers/bookmark.js.es6 b/app/assets/javascripts/discourse/controllers/bookmark.js.es6
index e46cf63c20..fd61ec759d 100644
--- a/app/assets/javascripts/discourse/controllers/bookmark.js.es6
+++ b/app/assets/javascripts/discourse/controllers/bookmark.js.es6
@@ -90,7 +90,7 @@ export default Controller.extend(ModalFunctionality, {
nextWeekFormatted() {
return htmlSafe(
I18n.t("bookmarks.reminders.next_week", {
- date: this.nextWeek().format(I18n.t("dates.month_day_time"))
+ date: this.nextWeek().format(I18n.t("dates.long_no_year"))
})
);
},
@@ -99,7 +99,7 @@ export default Controller.extend(ModalFunctionality, {
nextMonthFormatted() {
return htmlSafe(
I18n.t("bookmarks.reminders.next_month", {
- date: this.nextMonth().format(I18n.t("dates.month_day_time"))
+ date: this.nextMonth().format(I18n.t("dates.long_no_year"))
})
);
},
diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6
index a379d8f7e1..6e5f10f3f1 100644
--- a/app/assets/javascripts/discourse/controllers/composer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/composer.js.es6
@@ -8,8 +8,7 @@ import DiscourseURL from "discourse/lib/url";
import Quote from "discourse/lib/quote";
import Draft from "discourse/models/draft";
import Composer from "discourse/models/composer";
-import {
- default as discourseComputed,
+import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";
@@ -25,6 +24,7 @@ import { SAVE_LABELS, SAVE_ICONS } from "discourse/models/composer";
import { Promise } from "rsvp";
import ENV from "discourse-common/config/environment";
import EmberObject, { computed } from "@ember/object";
+import deprecated from "discourse-common/lib/deprecated";
function loadDraft(store, opts) {
opts = opts || {};
@@ -129,7 +129,7 @@ export default Controller.extend({
@discourseComputed(
"model.replyingToTopic",
"model.creatingPrivateMessage",
- "model.targetUsernames",
+ "model.targetRecipients",
"model.composeState"
)
focusTarget(replyingToTopic, creatingPM, usernames, composeState) {
@@ -246,7 +246,10 @@ export default Controller.extend({
},
_setupPopupMenuOption(callback) {
- let option = callback();
+ let option = callback(this);
+ if (typeof option === "undefined") {
+ return null;
+ }
if (typeof option.condition === "undefined") {
option.condition = true;
@@ -287,14 +290,14 @@ export default Controller.extend({
);
return options.concat(
- _popupMenuOptionsCallbacks.map(callback =>
- this._setupPopupMenuOption(callback)
- )
+ _popupMenuOptionsCallbacks
+ .map(callback => this._setupPopupMenuOption(callback))
+ .filter(o => o)
);
}
},
- @discourseComputed("model.creatingPrivateMessage", "model.targetUsernames")
+ @discourseComputed("model.creatingPrivateMessage", "model.targetRecipients")
showWarning(creatingPrivateMessage, usernames) {
if (!this.get("currentUser.staff")) {
return false;
@@ -909,19 +912,24 @@ export default Controller.extend({
isWarning: false
});
- if (opts.usernames && !this.get("model.targetUsernames")) {
- this.set("model.targetUsernames", opts.usernames);
+ if (!this.model.targetRecipients) {
+ if (opts.usernames) {
+ deprecated("`usernames` is deprecated, use `recipients` instead.");
+ this.model.set("targetRecipients", opts.usernames);
+ } else if (opts.recipients) {
+ this.model.set("targetRecipients", opts.recipients);
+ }
}
if (
opts.topicTitle &&
opts.topicTitle.length <= this.siteSettings.max_topic_title_length
) {
- this.set("model.title", opts.topicTitle);
+ this.model.set("title", opts.topicTitle);
}
if (opts.topicCategoryId) {
- this.set("model.categoryId", opts.topicCategoryId);
+ this.model.set("categoryId", opts.topicCategoryId);
}
if (opts.topicTags && !this.site.mobileView && this.site.can_tag_topics) {
@@ -934,11 +942,11 @@ export default Controller.extend({
(array[index] = tag.substring(0, this.siteSettings.max_tag_length))
);
- this.set("model.tags", tags);
+ this.model.set("tags", tags);
}
if (opts.topicBody) {
- this.set("model.reply", opts.topicBody);
+ this.model.set("reply", opts.topicBody);
}
},
diff --git a/app/assets/javascripts/discourse/controllers/create-account.js.es6 b/app/assets/javascripts/discourse/controllers/create-account.js.es6
index 5bfe1ec139..84b3171cd6 100644
--- a/app/assets/javascripts/discourse/controllers/create-account.js.es6
+++ b/app/assets/javascripts/discourse/controllers/create-account.js.es6
@@ -5,8 +5,8 @@ import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { setting } from "discourse/lib/computed";
-import {
- default as discourseComputed,
+import discourseComputed, {
+ observes,
on
} from "discourse-common/utils/decorators";
import { emailValid } from "discourse/lib/utilities";
@@ -171,6 +171,7 @@ export default Controller.extend(
: providerName;
},
+ @observes("emailValidation", "accountEmail")
prefillUsername: function() {
if (this.prefilledUsername) {
// If username field has been filled automatically, and email field just changed,
@@ -189,7 +190,7 @@ export default Controller.extend(
// then look for a registered username that matches the email.
this.fetchExistingUsername();
}
- }.observes("emailValidation", "accountEmail"),
+ },
// Determines whether at least one login button is enabled
@discourseComputed
diff --git a/app/assets/javascripts/discourse/controllers/discovery.js.es6 b/app/assets/javascripts/discourse/controllers/discovery.js.es6
index 93237fa22b..fa374b4372 100644
--- a/app/assets/javascripts/discourse/controllers/discovery.js.es6
+++ b/app/assets/javascripts/discourse/controllers/discovery.js.es6
@@ -3,6 +3,7 @@ import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import DiscourseURL from "discourse/lib/url";
import Category from "discourse/models/category";
+import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
discoveryTopics: inject("discovery/topics"),
@@ -16,9 +17,10 @@ export default Controller.extend({
loadedAllItems: not("discoveryTopics.model.canLoadMore"),
+ @observes("loadedAllItems")
_showFooter: function() {
this.set("application.showFooter", this.loadedAllItems);
- }.observes("loadedAllItems"),
+ },
showMoreUrl(period) {
let url = "",
diff --git a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6 b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
index 14a6c597f8..047960ba8a 100644
--- a/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
+++ b/app/assets/javascripts/discourse/controllers/discovery/topics.js.es6
@@ -83,7 +83,6 @@ const controllerOpts = {
},
resetNew() {
- this.topicTrackingState.resetNew();
Topic.resetNew(this.category, !this.noSubcategories).then(() =>
this.send("refresh")
);
diff --git a/app/assets/javascripts/discourse/controllers/edit-category.js.es6 b/app/assets/javascripts/discourse/controllers/edit-category.js.es6
index 36c961c7e1..e014cd1ea2 100644
--- a/app/assets/javascripts/discourse/controllers/edit-category.js.es6
+++ b/app/assets/javascripts/discourse/controllers/edit-category.js.es6
@@ -3,8 +3,7 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import DiscourseURL from "discourse/lib/url";
import { extractError } from "discourse/lib/ajax-error";
-import {
- default as discourseComputed,
+import discourseComputed, {
on,
observes
} from "discourse-common/utils/decorators";
diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6
index c1473509f2..2c04b1ca1c 100644
--- a/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6
+++ b/app/assets/javascripts/discourse/controllers/edit-topic-timer.js.es6
@@ -1,6 +1,6 @@
import EmberObject from "@ember/object";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import TopicTimer from "discourse/models/topic-timer";
import { popupAjaxError } from "discourse/lib/ajax-error";
diff --git a/app/assets/javascripts/discourse/controllers/email-login.js.es6 b/app/assets/javascripts/discourse/controllers/email-login.js.es6
index 01612c766f..4eab7fc1ba 100644
--- a/app/assets/javascripts/discourse/controllers/email-login.js.es6
+++ b/app/assets/javascripts/discourse/controllers/email-login.js.es6
@@ -23,15 +23,13 @@ export default Controller.extend({
actions: {
finishLogin() {
- let data = {};
+ let data = { second_factor_method: this.secondFactorMethod };
if (this.securityKeyCredential) {
- data = { security_key_credential: this.securityKeyCredential };
+ data.second_factor_token = this.securityKeyCredential;
} else {
- data = {
- second_factor_token: this.secondFactorToken,
- second_factor_method: this.secondFactorMethod
- };
+ data.second_factor_token = this.secondFactorToken;
}
+
ajax({
url: `/session/email-login/${this.model.token}`,
type: "POST",
diff --git a/app/assets/javascripts/discourse/controllers/exception.js.es6 b/app/assets/javascripts/discourse/controllers/exception.js.es6
index f53eff91fd..e9a5f10d16 100644
--- a/app/assets/javascripts/discourse/controllers/exception.js.es6
+++ b/app/assets/javascripts/discourse/controllers/exception.js.es6
@@ -1,10 +1,7 @@
import { equal, gte, none, alias } from "@ember/object/computed";
import { schedule } from "@ember/runloop";
import Controller from "@ember/controller";
-import {
- on,
- default as discourseComputed
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
const ButtonBackBright = {
classes: "btn-primary",
diff --git a/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6 b/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6
index 4a0db76713..39c89b5f79 100644
--- a/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6
+++ b/app/assets/javascripts/discourse/controllers/feature-topic-on-profile.js.es6
@@ -13,6 +13,10 @@ export default Controller.extend(ModalFunctionality, {
this.set("newFeaturedTopic", null);
},
+ onShow() {
+ this.set("modal.modalClass", "choose-topic-modal");
+ },
+
actions: {
save() {
return ajax(`/u/${this.model.username}/feature-topic`, {
diff --git a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6 b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
index d62a2a8177..a143203720 100644
--- a/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
+++ b/app/assets/javascripts/discourse/controllers/full-page-search.js.es6
@@ -9,10 +9,7 @@ import {
getSearchKey,
isValidSearchTerm
} from "discourse/lib/search";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Category from "discourse/models/category";
import { escapeExpression } from "discourse/lib/utilities";
import { setTransient } from "discourse/lib/page-tracker";
diff --git a/app/assets/javascripts/discourse/controllers/group-index.js.es6 b/app/assets/javascripts/discourse/controllers/group-index.js.es6
index 887d5dbfcf..dcf5e07f3d 100644
--- a/app/assets/javascripts/discourse/controllers/group-index.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group-index.js.es6
@@ -1,9 +1,6 @@
import Controller, { inject } from "@ember/controller";
import { alias } from "@ember/object/computed";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseDebounce from "discourse/lib/debounce";
diff --git a/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6 b/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6
index 3aaf3fdb87..41a12c2c98 100644
--- a/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group-manage-logs.js.es6
@@ -1,10 +1,7 @@
import { inject } from "@ember/controller";
import EmberObject from "@ember/object";
import Controller from "@ember/controller";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
group: inject(),
diff --git a/app/assets/javascripts/discourse/controllers/group-manage.js.es6 b/app/assets/javascripts/discourse/controllers/group-manage.js.es6
index b99f8a70f9..7a9e7859eb 100644
--- a/app/assets/javascripts/discourse/controllers/group-manage.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group-manage.js.es6
@@ -1,6 +1,6 @@
import { inject as service } from "@ember/service";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default Controller.extend({
router: service(),
diff --git a/app/assets/javascripts/discourse/controllers/group-requests.js.es6 b/app/assets/javascripts/discourse/controllers/group-requests.js.es6
index 013c44f6d8..c1664f0c32 100644
--- a/app/assets/javascripts/discourse/controllers/group-requests.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group-requests.js.es6
@@ -1,8 +1,5 @@
import Controller, { inject } from "@ember/controller";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseDebounce from "discourse/lib/debounce";
diff --git a/app/assets/javascripts/discourse/controllers/group.js.es6 b/app/assets/javascripts/discourse/controllers/group.js.es6
index b6e9a7ab8f..71354f58d3 100644
--- a/app/assets/javascripts/discourse/controllers/group.js.es6
+++ b/app/assets/javascripts/discourse/controllers/group.js.es6
@@ -1,7 +1,9 @@
import EmberObject from "@ember/object";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
+import { inject as service } from "@ember/service";
+import { readOnly } from "@ember/object/computed";
const Tab = EmberObject.extend({
init() {
@@ -17,6 +19,8 @@ export default Controller.extend({
counts: null,
showing: "members",
destroying: null,
+ router: service(),
+ currentPath: readOnly("router._router.currentPath"),
@discourseComputed(
"showMessages",
diff --git a/app/assets/javascripts/discourse/controllers/groups-index.js.es6 b/app/assets/javascripts/discourse/controllers/groups-index.js.es6
index 0380bdffd8..00276d0215 100644
--- a/app/assets/javascripts/discourse/controllers/groups-index.js.es6
+++ b/app/assets/javascripts/discourse/controllers/groups-index.js.es6
@@ -1,10 +1,7 @@
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
application: inject(),
diff --git a/app/assets/javascripts/discourse/controllers/invites-show.js.es6 b/app/assets/javascripts/discourse/controllers/invites-show.js.es6
index 9be1de123e..58b7ca3685 100644
--- a/app/assets/javascripts/discourse/controllers/invites-show.js.es6
+++ b/app/assets/javascripts/discourse/controllers/invites-show.js.es6
@@ -1,7 +1,7 @@
import { isEmpty } from "@ember/utils";
import { alias, notEmpty } from "@ember/object/computed";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import getUrl from "discourse-common/lib/get-url";
import DiscourseURL from "discourse/lib/url";
import { ajax } from "discourse/lib/ajax";
diff --git a/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6 b/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6
index 9e1c216e13..7b3fbdcb5d 100644
--- a/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6
+++ b/app/assets/javascripts/discourse/controllers/keyboard-shortcuts-help.js.es6
@@ -167,6 +167,10 @@ export default Controller.extend(ModalFunctionality, {
defer: buildShortcut("actions.defer", {
keys1: [SHIFT, "u"],
keysDelimiter: PLUS
+ }),
+ topic_admin_actions: buildShortcut("actions.topic_admin_actions", {
+ keys1: [SHIFT, "a"],
+ keysDelimiter: PLUS
})
}
}
diff --git a/app/assets/javascripts/discourse/controllers/login.js.es6 b/app/assets/javascripts/discourse/controllers/login.js.es6
index 5eabad52fb..fe57a856ec 100644
--- a/app/assets/javascripts/discourse/controllers/login.js.es6
+++ b/app/assets/javascripts/discourse/controllers/login.js.es6
@@ -120,9 +120,9 @@ export default Controller.extend(ModalFunctionality, {
data: {
login: this.loginName,
password: this.loginPassword,
- second_factor_token: this.secondFactorToken,
+ second_factor_token:
+ this.securityKeyCredential || this.secondFactorToken,
second_factor_method: this.secondFactorMethod,
- security_key_credential: this.securityKeyCredential,
timezone: moment.tz.guess()
}
}).then(
@@ -130,12 +130,9 @@ export default Controller.extend(ModalFunctionality, {
// Successful login
if (result && result.error) {
this.set("loggingIn", false);
- const invalidSecurityKey = result.reason === "invalid_security_key";
- const invalidSecondFactor =
- result.reason === "invalid_second_factor";
if (
- (invalidSecondFactor || invalidSecurityKey) &&
+ (result.security_key_enabled || result.totp_enabled) &&
!this.secondFactorRequired
) {
document.getElementById("modal-alert").style.display = "none";
@@ -145,9 +142,9 @@ export default Controller.extend(ModalFunctionality, {
secondFactorRequired: true,
showLoginButtons: false,
backupEnabled: result.backup_enabled,
- showSecondFactor: invalidSecondFactor,
- showSecurityKey: invalidSecurityKey,
- secondFactorMethod: invalidSecurityKey
+ showSecondFactor: result.totp_enabled,
+ showSecurityKey: result.security_key_enabled,
+ secondFactorMethod: result.security_key_enabled
? SECOND_FACTOR_METHODS.SECURITY_KEY
: SECOND_FACTOR_METHODS.TOTP,
securityKeyChallenge: result.challenge,
diff --git a/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6 b/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6
index f80b659baa..b2b7dd7f10 100644
--- a/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/move-to-topic.js.es6
@@ -6,7 +6,7 @@ import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { movePosts, mergeTopic } from "discourse/models/topic";
import DiscourseURL from "discourse/lib/url";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { extractError } from "discourse/lib/ajax-error";
export default Controller.extend(ModalFunctionality, {
@@ -74,7 +74,7 @@ export default Controller.extend(ModalFunctionality, {
onShow() {
this.setProperties({
- "modal.modalClass": "move-to-modal",
+ "modal.modalClass": "choose-topic-modal",
saving: false,
selection: "new_topic",
categoryId: null,
diff --git a/app/assets/javascripts/discourse/controllers/password-reset.js.es6 b/app/assets/javascripts/discourse/controllers/password-reset.js.es6
index 7f3718482e..dcb1931ac0 100644
--- a/app/assets/javascripts/discourse/controllers/password-reset.js.es6
+++ b/app/assets/javascripts/discourse/controllers/password-reset.js.es6
@@ -1,6 +1,6 @@
-import { alias, or } from "@ember/object/computed";
+import { alias, or, readOnly } from "@ember/object/computed";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import DiscourseURL from "discourse/lib/url";
import { ajax } from "discourse/lib/ajax";
import PasswordValidation from "discourse/mixins/password-validation";
@@ -18,6 +18,7 @@ export default Controller.extend(PasswordValidation, {
"model.second_factor_required",
"model.security_key_required"
),
+ otherMethodAllowed: readOnly("model.multiple_second_factor_methods"),
@discourseComputed("model.security_key_required")
secondFactorMethod(security_key_required) {
return security_key_required
@@ -51,9 +52,9 @@ export default Controller.extend(PasswordValidation, {
type: "PUT",
data: {
password: this.accountPassword,
- second_factor_token: this.secondFactorToken,
- second_factor_method: this.secondFactorMethod,
- security_key_credential: this.securityKeyCredential
+ second_factor_token:
+ this.securityKeyCredential || this.secondFactorToken,
+ second_factor_method: this.secondFactorMethod
}
})
.then(result => {
diff --git a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6
index f09f94ae89..6cac06c1aa 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/account.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/account.js.es6
@@ -2,7 +2,7 @@ import { not, or, gt } from "@ember/object/computed";
import Controller from "@ember/controller";
import { iconHTML } from "discourse-common/lib/icon-library";
import CanCheckEmails from "discourse/mixins/can-check-emails";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { propertyNotEqual, setting } from "discourse/lib/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
diff --git a/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6
index 008ed9c8cd..2f7d0832c9 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/emails.js.es6
@@ -1,7 +1,7 @@
import { equal } from "@ember/object/computed";
import Controller from "@ember/controller";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
const EMAIL_LEVELS = {
diff --git a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6
index 370d5b9a4d..086e5cc0b6 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/interface.js.es6
@@ -2,10 +2,7 @@ import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { setDefaultHomepage } from "discourse/lib/utilities";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import {
listThemes,
previewTheme,
diff --git a/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6
index 017035323c..5d2e65f045 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/profile.js.es6
@@ -1,7 +1,7 @@
import { isEmpty } from "@ember/utils";
import EmberObject from "@ember/object";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { cookAsync } from "discourse/lib/text";
diff --git a/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6
index 21e9a55f38..67c7b58c83 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/second-factor.js.es6
@@ -1,8 +1,8 @@
import { alias, and } from "@ember/object/computed";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import CanCheckEmails from "discourse/mixins/can-check-emails";
-import { default as DiscourseURL, userPath } from "discourse/lib/url";
+import DiscourseURL, { userPath } from "discourse/lib/url";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { findAll } from "discourse/models/login-method";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
diff --git a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6 b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6
index 530d1a616e..3b5cbfc477 100644
--- a/app/assets/javascripts/discourse/controllers/preferences/username.js.es6
+++ b/app/assets/javascripts/discourse/controllers/preferences/username.js.es6
@@ -1,10 +1,7 @@
import { isEmpty } from "@ember/utils";
import { empty, or } from "@ember/object/computed";
import Controller from "@ember/controller";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { setting, propertyEqual } from "discourse/lib/computed";
import DiscourseURL from "discourse/lib/url";
import { userPath } from "discourse/lib/url";
diff --git a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6 b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6
index a858d605e9..db32c7c367 100644
--- a/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6
+++ b/app/assets/javascripts/discourse/controllers/reorder-categories.js.es6
@@ -5,10 +5,7 @@ import { ajax } from "discourse/lib/ajax";
import ModalFunctionality from "discourse/mixins/modal-functionality";
const BufferedProxy = window.BufferedProxy; // import BufferedProxy from 'ember-buffered-proxy/proxy';
import { popupAjaxError } from "discourse/lib/ajax-error";
-import {
- on,
- default as discourseComputed
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
import Ember from "ember";
export default Controller.extend(ModalFunctionality, Ember.Evented, {
diff --git a/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6 b/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6
index 35b24b07df..aa3e0c1143 100644
--- a/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6
+++ b/app/assets/javascripts/discourse/controllers/second-factor-backup-edit.js.es6
@@ -1,7 +1,7 @@
import { alias } from "@ember/object/computed";
import { later } from "@ember/runloop";
import Controller from "@ember/controller";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { SECOND_FACTOR_METHODS } from "discourse/models/user";
import ModalFunctionality from "discourse/mixins/modal-functionality";
diff --git a/app/assets/javascripts/discourse/controllers/tags-show.js.es6 b/app/assets/javascripts/discourse/controllers/tags-show.js.es6
index a34a368a1a..bc26a7febc 100644
--- a/app/assets/javascripts/discourse/controllers/tags-show.js.es6
+++ b/app/assets/javascripts/discourse/controllers/tags-show.js.es6
@@ -1,12 +1,9 @@
import { alias } from "@ember/object/computed";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import BulkTopicSelection from "discourse/mixins/bulk-topic-selection";
-import { default as NavItem } from "discourse/models/nav-item";
+import NavItem from "discourse/models/nav-item";
import FilterModeMixin from "discourse/mixins/filter-mode";
export default Controller.extend(BulkTopicSelection, FilterModeMixin, {
diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6
index 7b523ee768..cc0725a697 100644
--- a/app/assets/javascripts/discourse/controllers/topic.js.es6
+++ b/app/assets/javascripts/discourse/controllers/topic.js.es6
@@ -15,10 +15,7 @@ import Topic from "discourse/models/topic";
import discourseDebounce from "discourse/lib/debounce";
import isElementInViewport from "discourse/lib/is-element-in-viewport";
import { ajax } from "discourse/lib/ajax";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { extractLinkMeta } from "discourse/lib/render-topic-featured-link";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { spinnerHTML } from "discourse/helpers/loading-spinner";
@@ -727,6 +724,10 @@ export default Controller.extend(bufferedProperty("model"), {
},
jumpEnd() {
+ this.appEvents.trigger(
+ "topic:jump-to-post",
+ this.get("model.highest_post_number")
+ );
DiscourseURL.routeTo(this.get("model.lastPostUrl"), {
jumpEnd: true
});
diff --git a/app/assets/javascripts/discourse/controllers/upload-selector.js.es6 b/app/assets/javascripts/discourse/controllers/upload-selector.js.es6
index a8e61d86f5..eae9a3a2bb 100644
--- a/app/assets/javascripts/discourse/controllers/upload-selector.js.es6
+++ b/app/assets/javascripts/discourse/controllers/upload-selector.js.es6
@@ -1,7 +1,7 @@
import { equal } from "@ember/object/computed";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import {
allowsAttachments,
authorizedExtensions,
diff --git a/app/assets/javascripts/discourse/controllers/user-activity.js.es6 b/app/assets/javascripts/discourse/controllers/user-activity.js.es6
index 596125d221..473f24c753 100644
--- a/app/assets/javascripts/discourse/controllers/user-activity.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-activity.js.es6
@@ -3,6 +3,7 @@ import { inject as service } from "@ember/service";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import { exportUserArchive } from "discourse/lib/export-csv";
+import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
application: inject(),
@@ -12,6 +13,7 @@ export default Controller.extend({
canDownloadPosts: alias("user.viewingSelf"),
+ @observes("userActionType", "model.stream.itemsLoaded")
_showFooter: function() {
var showFooter;
if (this.userActionType) {
@@ -25,7 +27,7 @@ export default Controller.extend({
this.get("model.stream.itemsLoaded");
}
this.set("application.showFooter", showFooter);
- }.observes("userActionType", "model.stream.itemsLoaded"),
+ },
actions: {
exportUserArchive() {
diff --git a/app/assets/javascripts/discourse/controllers/user-card.js.es6 b/app/assets/javascripts/discourse/controllers/user-card.js.es6
index d0fa3abf1c..862fc92e73 100644
--- a/app/assets/javascripts/discourse/controllers/user-card.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-card.js.es6
@@ -1,11 +1,7 @@
import { inject as service } from "@ember/service";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
-import {
- default as DiscourseURL,
- userPath,
- groupPath
-} from "discourse/lib/url";
+import DiscourseURL, { userPath, groupPath } from "discourse/lib/url";
export default Controller.extend({
topic: inject(),
diff --git a/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6 b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
index 1fd6ec0bdf..9282265e78 100644
--- a/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-invited-show.js.es6
@@ -3,10 +3,7 @@ import Controller from "@ember/controller";
import Invite from "discourse/models/invite";
import discourseDebounce from "discourse/lib/debounce";
import { popupAjaxError } from "discourse/lib/ajax-error";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
user: null,
diff --git a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6 b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6
index 24e61aa73a..23efcfc214 100644
--- a/app/assets/javascripts/discourse/controllers/user-notifications.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-notifications.js.es6
@@ -1,13 +1,14 @@
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
+import { readOnly } from "@ember/object/computed";
+import { inject as service } from "@ember/service";
export default Controller.extend({
application: inject(),
+ router: service(),
+ currentPath: readOnly("router._router.currentPath"),
@observes("model.canLoadMore")
_showFooter() {
diff --git a/app/assets/javascripts/discourse/controllers/user-posts.js.es6 b/app/assets/javascripts/discourse/controllers/user-posts.js.es6
index fe001ad1c1..e5de7e9995 100644
--- a/app/assets/javascripts/discourse/controllers/user-posts.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-posts.js.es6
@@ -1,9 +1,12 @@
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
+import { observes } from "discourse-common/utils/decorators";
+
export default Controller.extend({
application: inject(),
+ @observes("model.canLoadMore")
_showFooter: function() {
this.set("application.showFooter", !this.get("model.canLoadMore"));
- }.observes("model.canLoadMore")
+ }
});
diff --git a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6 b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6
index 6c09d2e36b..f541ac01ca 100644
--- a/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6
+++ b/app/assets/javascripts/discourse/controllers/user-topics-list.js.es6
@@ -1,4 +1,4 @@
-import discourseComputed from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
@@ -22,9 +22,10 @@ export default Controller.extend({
this.session.set("topicListScrollPosition", $(window).scrollTop());
},
+ @observes("model.canLoadMore")
_showFooter: function() {
this.set("application.showFooter", !this.get("model.canLoadMore"));
- }.observes("model.canLoadMore"),
+ },
@discourseComputed("incomingCount")
hasIncoming(incomingCount) {
diff --git a/app/assets/javascripts/discourse/controllers/users.js.es6 b/app/assets/javascripts/discourse/controllers/users.js.es6
index 8fd3b3e157..820e50b0af 100644
--- a/app/assets/javascripts/discourse/controllers/users.js.es6
+++ b/app/assets/javascripts/discourse/controllers/users.js.es6
@@ -2,6 +2,7 @@ import { equal } from "@ember/object/computed";
import { inject } from "@ember/controller";
import Controller from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce";
+import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({
application: inject(),
@@ -15,13 +16,15 @@ export default Controller.extend({
showTimeRead: equal("period", "all"),
+ @observes("nameInput")
_setName: discourseDebounce(function() {
this.set("name", this.nameInput);
- }, 500).observes("nameInput"),
+ }, 500),
+ @observes("model.canLoadMore")
_showFooter: function() {
this.set("application.showFooter", !this.get("model.canLoadMore"));
- }.observes("model.canLoadMore"),
+ },
actions: {
loadMore() {
diff --git a/app/assets/javascripts/discourse/helpers/category-link.js.es6 b/app/assets/javascripts/discourse/helpers/category-link.js.es6
index 3cdb03c8f0..b32c0c9452 100644
--- a/app/assets/javascripts/discourse/helpers/category-link.js.es6
+++ b/app/assets/javascripts/discourse/helpers/category-link.js.es6
@@ -25,7 +25,9 @@ function categoryStripe(color, classes) {
@param {String} [opts.url] The url that we want the category badge to link to.
@param {Boolean} [opts.allowUncategorized] If false, returns an empty string for the uncategorized category.
@param {Boolean} [opts.link] If false, the category badge will not be a link.
- @param {Boolean} [opts.hideParaent] If true, parent category will be hidden in the badge.
+ @param {Boolean} [opts.hideParent] If true, parent category will be hidden in the badge.
+ @param {Boolean} [opts.recursive] If true, the function will be called recursively for all parent categories
+ @param {Number} [opts.depth] Current category depth, used for limiting recursive calls
**/
export function categoryBadgeHTML(category, opts) {
opts = opts || {};
@@ -38,6 +40,13 @@ export function categoryBadgeHTML(category, opts) {
)
return "";
+ const depth = (opts.depth || 1) + 1;
+ if (opts.recursive && depth <= Discourse.SiteSettings.max_category_nesting) {
+ const parentCategory = Category.findById(category.parent_category_id);
+ opts.depth = depth;
+ return categoryBadgeHTML(parentCategory, opts) + _renderer(category, opts);
+ }
+
return _renderer(category, opts);
}
diff --git a/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6 b/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6
index 62de97d18c..4aa8484a55 100644
--- a/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6
+++ b/app/assets/javascripts/discourse/helpers/raw-plugin-outlet.js.es6
@@ -1,6 +1,7 @@
import { rawConnectorsFor } from "discourse/lib/plugin-connectors";
+import RawHandlebars from "discourse-common/lib/raw-handlebars";
-Handlebars.registerHelper("raw-plugin-outlet", function(args) {
+RawHandlebars.registerHelper("raw-plugin-outlet", function(args) {
const connectors = rawConnectorsFor(args.hash.name);
if (connectors.length) {
const output = connectors.map(c => c.template({ context: this }));
diff --git a/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6 b/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6
index b024500a0c..4dcc12d3a4 100644
--- a/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6
+++ b/app/assets/javascripts/discourse/helpers/reviewable-status.js.es6
@@ -19,7 +19,7 @@ function dataFor(status) {
case IGNORED:
return { icon: "external-link-alt", name: "ignored" };
case DELETED:
- return { icon: "trash", name: "deleted" };
+ return { icon: "trash-alt", name: "deleted" };
}
}
diff --git a/app/assets/javascripts/discourse/initializers/badging.js.es6 b/app/assets/javascripts/discourse/initializers/badging.js.es6
index 004764056e..f406604dfa 100644
--- a/app/assets/javascripts/discourse/initializers/badging.js.es6
+++ b/app/assets/javascripts/discourse/initializers/badging.js.es6
@@ -4,7 +4,7 @@ export default {
after: "message-bus",
initialize(container) {
- if (!window.ExperimentalBadge) return; // must have the Badging API
+ if (!navigator.setAppBadge) return; // must have the Badging API
const user = container.lookup("current-user:main");
if (!user) return; // must be logged in
@@ -18,6 +18,6 @@ export default {
},
_updateBadge() {
- window.ExperimentalBadge.set(this.notifications);
+ navigator.setAppBadge(this.notifications);
}
};
diff --git a/app/assets/javascripts/discourse/lib/click-track.js.es6 b/app/assets/javascripts/discourse/lib/click-track.js.es6
index c3186937b0..534c04a6c4 100644
--- a/app/assets/javascripts/discourse/lib/click-track.js.es6
+++ b/app/assets/javascripts/discourse/lib/click-track.js.es6
@@ -8,21 +8,13 @@ import ENV from "discourse-common/config/environment";
import User from "discourse/models/user";
export function isValidLink($link) {
- // Do not track:
- // - lightboxes
- // - links with disabled tracking
- // - category links
- // - quote back button
+ // .hashtag == category/tag link
+ // .back == quote back ^ button
if ($link.is(".lightbox, .no-track-link, .hashtag, .back")) {
return false;
}
- // Do not track links in quotes or in elided part
- if ($link.parents("aside.quote, .elided").length !== 0) {
- return false;
- }
-
- if ($link.parents(".expanded-embed").length !== 0) {
+ if ($link.parents("aside.quote, .elided, .expanded-embed").length !== 0) {
return false;
}
diff --git a/app/assets/javascripts/discourse/lib/export-csv.js.es6 b/app/assets/javascripts/discourse/lib/export-csv.js.es6
index 9f604f6e45..39b19771f3 100644
--- a/app/assets/javascripts/discourse/lib/export-csv.js.es6
+++ b/app/assets/javascripts/discourse/lib/export-csv.js.es6
@@ -1,4 +1,6 @@
import { ajax } from "discourse/lib/ajax";
+import { popupAjaxError } from "discourse/lib/ajax-error";
+
function exportEntityByType(type, entity, args) {
return ajax("/export_csv/export_entity.json", {
method: "POST",
@@ -11,9 +13,7 @@ export function exportUserArchive() {
.then(function() {
bootbox.alert(I18n.t("user.download_archive.success"));
})
- .catch(function() {
- bootbox.alert(I18n.t("user.download_archive.rate_limit_error"));
- });
+ .catch(popupAjaxError);
}
export function exportEntity(entity, args) {
diff --git a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6 b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6
index 991644737a..409ebc3190 100644
--- a/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6
+++ b/app/assets/javascripts/discourse/lib/keyboard-shortcuts.js.es6
@@ -71,6 +71,7 @@ const bindings = {
"shift+z shift+z": { handler: "logout" },
"shift+f11": { handler: "fullscreenComposer", global: true },
"shift+u": { handler: "deferTopic" },
+ "shift+a": { handler: "toggleAdminActions" },
t: { postAction: "replyAsNewTopic" },
u: { handler: "goBack", anonymous: true },
"x r": {
@@ -638,5 +639,9 @@ export default {
deferTopic() {
this.container.lookup("controller:topic").send("deferTopic");
+ },
+
+ toggleAdminActions() {
+ this.appEvents.trigger("topic:toggle-actions");
}
};
diff --git a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6 b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6
index 284c5a3be4..a8f30a3998 100644
--- a/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6
+++ b/app/assets/javascripts/discourse/lib/lazy-load-images.js.es6
@@ -40,7 +40,18 @@ function show(image) {
image.srcset = copyImg.srcset;
}
image.classList.remove("d-lazyload-hidden");
- image.parentNode.removeChild(copyImg);
+
+ if (image.onload) {
+ // don't bother fighting with existing handler
+ // this can mean a slight flash on mobile
+ image.parentNode.removeChild(copyImg);
+ } else {
+ image.onload = () => {
+ image.parentNode.removeChild(copyImg);
+ image.onload = null;
+ };
+ }
+
copyImg.onload = null;
};
@@ -50,43 +61,23 @@ function show(image) {
copyImg.srcset = imageData.srcset;
}
+ // width of image may not match, use computed style which
+ // is the actual size of the image
+ const computedStyle = window.getComputedStyle(image);
+ const actualWidth = parseInt(computedStyle.width, 10);
+ const actualHeight = parseInt(computedStyle.height, 10);
+
copyImg.style.position = "absolute";
copyImg.style.top = `${image.offsetTop}px`;
copyImg.style.left = `${image.offsetLeft}px`;
+ copyImg.style.width = `${actualWidth}px`;
+ copyImg.style.height = `${actualHeight}px`;
+
copyImg.className = imageData.className;
- let inOnebox = false;
- let inQuote = false;
- for (let element = image; element; element = element.parentElement) {
- if (element.tagName === "ARTICLE" && element.dataset.postId) {
- break;
- }
- if (element.classList.contains("onebox")) {
- inOnebox = true;
- }
- if (element.tagName === "BLOCKQUOTE") {
- inQuote = true;
- }
- }
-
- if (!inOnebox) {
- copyImg.style.width = `${imageData.width}px`;
- copyImg.style.height = `${imageData.height}px`;
- }
-
- if (inQuote && imageData.width && imageData.height) {
- const computedStyle = window.getComputedStyle(image);
- const width = parseInt(computedStyle.width, 10);
- const height = width * (imageData.height / imageData.width);
-
- image.width = width;
- image.height = height;
-
- copyImg.style.width = `${width}px`;
- copyImg.style.height = `${height}px`;
- }
-
- image.parentNode.insertBefore(copyImg, image);
+ // insert after the current element so styling still will
+ // apply to original image firstChild selectors
+ image.parentNode.insertBefore(copyImg, image.nextSibling);
} else {
image.classList.remove("d-lazyload-hidden");
}
diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js.es6 b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
index e4f1362c86..09ebe31f43 100644
--- a/app/assets/javascripts/discourse/lib/plugin-api.js.es6
+++ b/app/assets/javascripts/discourse/lib/plugin-api.js.es6
@@ -8,6 +8,7 @@ import { includeAttributes } from "discourse/lib/transform-post";
import { registerHighlightJSLanguage } from "discourse/lib/highlight-syntax";
import { addToolbarCallback } from "discourse/components/d-editor";
import { addWidgetCleanCallback } from "discourse/components/mount-widget";
+import { addGlobalNotice } from "discourse/components/global-notice";
import {
createWidget,
reopenWidget,
@@ -47,9 +48,10 @@ import {
import { addCategorySortCriteria } from "discourse/components/edit-category-settings";
import { queryRegistry } from "discourse/widgets/widget";
import Composer from "discourse/models/composer";
+import { on } from "@ember/object/evented";
// If you add any methods to the API ensure you bump up this number
-const PLUGIN_API_VERSION = "0.8.36";
+const PLUGIN_API_VERSION = "0.8.37";
class PluginApi {
constructor(version, container) {
@@ -969,6 +971,18 @@ class PluginApi {
registerHighlightJSLanguage(name, fn) {
registerHighlightJSLanguage(name, fn);
}
+
+ /**
+ * Adds global notices to display.
+ *
+ * Example:
+ *
+ * api.addGlobalNotice("text", "foo", { html: "
bar
" })
+ *
+ **/
+ addGlobalNotice(id, text, options) {
+ addGlobalNotice(id, text, options);
+ }
}
let _pluginv01;
@@ -1046,12 +1060,12 @@ function decorate(klass, evt, cb, id) {
}
const mixin = {};
- mixin["_decorate_" + _decorateId++] = function($elem) {
+ mixin["_decorate_" + _decorateId++] = on(evt, function($elem) {
$elem = $elem || $(this.element);
if ($elem) {
cb($elem);
}
- }.on(evt);
+ });
klass.reopen(mixin);
}
diff --git a/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6 b/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6
index 3bf8c2457c..449c70ab79 100644
--- a/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6
+++ b/app/assets/javascripts/discourse/lib/posts-with-placeholders.js.es6
@@ -1,5 +1,5 @@
import EmberObject from "@ember/object";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export function Placeholder(viewName) {
this.viewName = viewName;
diff --git a/app/assets/javascripts/discourse/lib/render-tag.js.es6 b/app/assets/javascripts/discourse/lib/render-tag.js.es6
index 5206e2c900..eed6efddd6 100644
--- a/app/assets/javascripts/discourse/lib/render-tag.js.es6
+++ b/app/assets/javascripts/discourse/lib/render-tag.js.es6
@@ -20,7 +20,7 @@ function defaultRenderTag(tag, params) {
: User.current().username;
path = `/u/${username}/messages/tags/${tag}`;
} else {
- path = `/tags/${tag}`;
+ path = `/tag/${tag}`;
}
}
const href = path ? ` href='${Discourse.getURL(path)}' ` : "";
diff --git a/app/assets/javascripts/discourse/lib/search.js.es6 b/app/assets/javascripts/discourse/lib/search.js.es6
index 426a630483..d6c76dd56b 100644
--- a/app/assets/javascripts/discourse/lib/search.js.es6
+++ b/app/assets/javascripts/discourse/lib/search.js.es6
@@ -79,7 +79,7 @@ export function translateResults(results, opts) {
const tagName = Handlebars.Utils.escapeExpression(tag.name);
return EmberObject.create({
id: tagName,
- url: Discourse.getURL("/tags/" + tagName)
+ url: Discourse.getURL("/tag/" + tagName)
});
})
.compact();
diff --git a/app/assets/javascripts/discourse/lib/static-route-builder.js.es6 b/app/assets/javascripts/discourse/lib/static-route-builder.js.es6
index 8baa97fb4d..91cda0f0be 100644
--- a/app/assets/javascripts/discourse/lib/static-route-builder.js.es6
+++ b/app/assets/javascripts/discourse/lib/static-route-builder.js.es6
@@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse";
import StaticPage from "discourse/models/static-page";
-import { default as DiscourseURL, jumpToElement } from "discourse/lib/url";
+import DiscourseURL, { jumpToElement } from "discourse/lib/url";
const configs = {
faq: "faq_url",
diff --git a/app/assets/javascripts/discourse/lib/text.js.es6 b/app/assets/javascripts/discourse/lib/text.js.es6
index 7862ce53e0..7f75cb497d 100644
--- a/app/assets/javascripts/discourse/lib/text.js.es6
+++ b/app/assets/javascripts/discourse/lib/text.js.es6
@@ -1,4 +1,4 @@
-import { default as PrettyText, buildOptions } from "pretty-text/pretty-text";
+import PrettyText, { buildOptions } from "pretty-text/pretty-text";
import { performEmojiUnescape, buildEmojiUrl } from "pretty-text/emoji";
import WhiteLister from "pretty-text/white-lister";
import { sanitize as textSanitize } from "pretty-text/sanitizer";
diff --git a/app/assets/javascripts/discourse/lib/to-markdown.js.es6 b/app/assets/javascripts/discourse/lib/to-markdown.js.es6
index c7ba652984..163c895f83 100644
--- a/app/assets/javascripts/discourse/lib/to-markdown.js.es6
+++ b/app/assets/javascripts/discourse/lib/to-markdown.js.es6
@@ -102,6 +102,10 @@ export class Tag {
];
}
+ static whitelists() {
+ return ["ins", "del", "small", "big", "kbd", "ruby", "rt", "rb", "rp"];
+ }
+
static block(name, prefix, suffix) {
return class extends Tag {
constructor() {
@@ -149,7 +153,7 @@ export class Tag {
};
}
- static keep(name) {
+ static whitelist(name) {
return class extends Tag {
constructor() {
super(name, `<${name}>`, `${name}>`);
@@ -479,18 +483,12 @@ function tags() {
...Tag.headings().map((h, i) => Tag.heading(h, i + 1)),
...Tag.slices().map(s => Tag.slice(s, "\n")),
...Tag.emphases().map(e => Tag.emphasis(e[0], e[1])),
+ ...Tag.whitelists().map(t => Tag.whitelist(t)),
Tag.cell("td"),
Tag.cell("th"),
Tag.replace("br", "\n"),
Tag.replace("hr", "\n---\n"),
Tag.replace("head", ""),
- Tag.keep("ins"),
- Tag.keep("del"),
- Tag.keep("small"),
- Tag.keep("big"),
- Tag.keep("kbd"),
- Tag.keep("ruby"),
- Tag.keep("rt"),
Tag.li(),
Tag.link(),
Tag.image(),
diff --git a/app/assets/javascripts/discourse/lib/uploads.js.es6 b/app/assets/javascripts/discourse/lib/uploads.js.es6
index 40840647f8..5a0e81779b 100644
--- a/app/assets/javascripts/discourse/lib/uploads.js.es6
+++ b/app/assets/javascripts/discourse/lib/uploads.js.es6
@@ -225,7 +225,10 @@ function uploadLocation(url) {
url = Discourse.getURLWithCDN(url);
return /^\/\//.test(url) ? "http:" + url : url;
} else if (Discourse.S3BaseUrl) {
- return "https:" + url;
+ if (url.indexOf("secure-media-uploads") === -1) {
+ return "https:" + url;
+ }
+ return window.location.protocol + url;
} else {
var protocol = window.location.protocol + "//",
hostname = window.location.hostname,
diff --git a/app/assets/javascripts/discourse/lib/url.js.es6 b/app/assets/javascripts/discourse/lib/url.js.es6
index ae252facf8..6d7fa31966 100644
--- a/app/assets/javascripts/discourse/lib/url.js.es6
+++ b/app/assets/javascripts/discourse/lib/url.js.es6
@@ -99,9 +99,21 @@ const DiscourseURL = EmberObject.extend({
let holder;
if (opts.jumpEnd) {
- $(window).scrollTop($(document).height() - $(window).height());
- _transitioning = false;
- return;
+ let $holder = $(holderId);
+ let holderHeight = $holder.height();
+ let windowHeight = $(window).height() - offsetCalculator();
+
+ // scroll to the bottom of the post and if the post is yuge we go back up the
+ // timeline by a small % of the post height so we can see the bottom of the text.
+ //
+ // otherwise just jump to the top of the post using the lock & holder method.
+ if (holderHeight > windowHeight) {
+ $(window).scrollTop(
+ $holder.offset().top + (holderHeight - holderHeight / 10)
+ );
+ _transitioning = false;
+ return;
+ }
}
if (postNumber === 1 && !opts.anchor) {
diff --git a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6 b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6
index e9c0c0df56..c5cdcc4a16 100644
--- a/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6
+++ b/app/assets/javascripts/discourse/mixins/card-contents-base.js.es6
@@ -126,7 +126,7 @@ export default Mixin.create({
if (wantsNewWindow(e)) {
return;
}
- const $target = $(e.target);
+ const $target = $(e.currentTarget);
return this._show($target.text().replace(/^@/, ""), $target);
});
diff --git a/app/assets/javascripts/discourse/mixins/name-validation.js.es6 b/app/assets/javascripts/discourse/mixins/name-validation.js.es6
index b2594c97fe..3b377f095b 100644
--- a/app/assets/javascripts/discourse/mixins/name-validation.js.es6
+++ b/app/assets/javascripts/discourse/mixins/name-validation.js.es6
@@ -1,5 +1,5 @@
import { isEmpty } from "@ember/utils";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Mixin from "@ember/object/mixin";
import EmberObject from "@ember/object";
diff --git a/app/assets/javascripts/discourse/mixins/open-composer.js.es6 b/app/assets/javascripts/discourse/mixins/open-composer.js.es6
index 136cca6d50..480eae5783 100644
--- a/app/assets/javascripts/discourse/mixins/open-composer.js.es6
+++ b/app/assets/javascripts/discourse/mixins/open-composer.js.es6
@@ -39,10 +39,10 @@ export default Mixin.create({
});
},
- openComposerWithMessageParams(usernames, topicTitle, topicBody) {
+ openComposerWithMessageParams(recipients, topicTitle, topicBody) {
this.controllerFor("composer").open({
action: Composer.PRIVATE_MESSAGE,
- usernames,
+ recipients,
topicTitle,
topicBody,
archetypeId: "private_message",
diff --git a/app/assets/javascripts/discourse/mixins/password-validation.js.es6 b/app/assets/javascripts/discourse/mixins/password-validation.js.es6
index b313368f52..d660f5208b 100644
--- a/app/assets/javascripts/discourse/mixins/password-validation.js.es6
+++ b/app/assets/javascripts/discourse/mixins/password-validation.js.es6
@@ -1,5 +1,5 @@
import { isEmpty } from "@ember/utils";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Mixin from "@ember/object/mixin";
import EmberObject from "@ember/object";
diff --git a/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6 b/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6
index 2e8763097c..4dabeb3fa0 100644
--- a/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6
+++ b/app/assets/javascripts/discourse/mixins/preferences-tab-controller.js.es6
@@ -1,4 +1,4 @@
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Mixin from "@ember/object/mixin";
export default Mixin.create({
diff --git a/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6 b/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6
index 782ceca03d..5a7ff18808 100644
--- a/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6
+++ b/app/assets/javascripts/discourse/mixins/user-fields-validation.js.es6
@@ -1,9 +1,6 @@
import { isEmpty } from "@ember/utils";
import EmberObject from "@ember/object";
-import {
- on,
- default as discourseComputed
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
import Mixin from "@ember/object/mixin";
export default Mixin.create({
diff --git a/app/assets/javascripts/discourse/mixins/username-validation.js.es6 b/app/assets/javascripts/discourse/mixins/username-validation.js.es6
index 3e18f815c5..fcd0ea89ba 100644
--- a/app/assets/javascripts/discourse/mixins/username-validation.js.es6
+++ b/app/assets/javascripts/discourse/mixins/username-validation.js.es6
@@ -1,7 +1,7 @@
import { isEmpty } from "@ember/utils";
import discourseDebounce from "discourse/lib/debounce";
import { setting } from "discourse/lib/computed";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Mixin from "@ember/object/mixin";
import EmberObject from "@ember/object";
import User from "discourse/models/user";
diff --git a/app/assets/javascripts/discourse/models/category.js.es6 b/app/assets/javascripts/discourse/models/category.js.es6
index 3847a9ea10..738819bc60 100644
--- a/app/assets/javascripts/discourse/models/category.js.es6
+++ b/app/assets/javascripts/discourse/models/category.js.es6
@@ -308,11 +308,10 @@ Category.reopenClass({
},
findBySlugAndParent(slug, parentCategory) {
+ if (Discourse.SiteSettings.slug_generation_method === "encoded") {
+ slug = encodeURI(slug);
+ }
return Category.list().find(category => {
- if (Discourse.SiteSettings.slug_generation_method === "encoded") {
- slug = encodeURI(slug);
- }
-
return (
category.slug === slug &&
(category.parentCategory || null) === parentCategory
@@ -335,7 +334,11 @@ Category.reopenClass({
},
findBySlugPathWithID(slugPathWithID) {
- const parts = slugPathWithID.split("/");
+ let parts = slugPathWithID.split("/").filter(Boolean);
+ // slugs found by star/glob pathing in emeber do not automatically url decode - ensure that these are decoded
+ if (Discourse.SiteSettings.slug_generation_method === "encoded") {
+ parts = parts.map(urlPart => decodeURI(urlPart));
+ }
let category = null;
if (parts.length > 0 && parts[parts.length - 1].match(/^\d+$/)) {
diff --git a/app/assets/javascripts/discourse/models/composer.js.es6 b/app/assets/javascripts/discourse/models/composer.js.es6
index a630f521c4..991e580af2 100644
--- a/app/assets/javascripts/discourse/models/composer.js.es6
+++ b/app/assets/javascripts/discourse/models/composer.js.es6
@@ -9,18 +9,22 @@ import Topic from "discourse/models/topic";
import { throwAjaxError } from "discourse/lib/ajax-error";
import Quote from "discourse/lib/quote";
import Draft from "discourse/models/draft";
-import {
- default as discourseComputed,
+import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";
-import { escapeExpression, tinyAvatar } from "discourse/lib/utilities";
+import {
+ escapeExpression,
+ tinyAvatar,
+ emailValid
+} from "discourse/lib/utilities";
import { propertyNotEqual } from "discourse/lib/computed";
import { throttle } from "@ember/runloop";
import { Promise } from "rsvp";
import { set } from "@ember/object";
import Site from "discourse/models/site";
import User from "discourse/models/user";
+import deprecated from "discourse-common/lib/deprecated";
// The actions the composer can take
export const CREATE_TOPIC = "createTopic",
@@ -51,7 +55,7 @@ const CLOSED = "closed",
is_warning: "isWarning",
whisper: "whisper",
archetype: "archetypeId",
- target_usernames: "targetUsernames",
+ target_recipients: "targetRecipients",
typing_duration_msecs: "typingTime",
composer_open_duration_msecs: "composerTime",
tags: "tags",
@@ -77,7 +81,9 @@ const CLOSED = "closed",
composerTime: "composerTime",
typingTime: "typingTime",
postId: "post.id",
- usernames: "targetUsernames"
+ // TODO remove together with 'targetUsername' deprecations
+ usernames: "targetUsernames",
+ recipients: "targetRecipients"
},
_add_draft_fields = {},
FAST_REPLY_LENGTH_THRESHOLD = 10000;
@@ -340,11 +346,36 @@ const Composer = RestModel.extend({
return options;
},
+ @discourseComputed("targetRecipients")
+ targetUsernames(targetRecipients) {
+ deprecated(
+ "`targetUsernames` is deprecated, use `targetRecipients` instead."
+ );
+ return targetRecipients;
+ },
+
+ @discourseComputed("targetRecipients")
+ targetRecipientsArray(targetRecipients) {
+ const recipients = targetRecipients ? targetRecipients.split(",") : [];
+ const groups = new Set(this.site.groups.map(g => g.name));
+
+ return recipients.map(item => {
+ if (groups.has(item)) {
+ return { type: "group", name: item };
+ } else if (emailValid(item)) {
+ return { type: "email", name: item };
+ } else {
+ return { type: "user", name: item };
+ }
+ });
+ },
+
@discourseComputed(
"loading",
"canEditTitle",
"titleLength",
- "targetUsernames",
+ "targetRecipients",
+ "targetRecipientsArray",
"replyLength",
"categoryId",
"missingReplyCharacters",
@@ -357,7 +388,8 @@ const Composer = RestModel.extend({
loading,
canEditTitle,
titleLength,
- targetUsernames,
+ targetRecipients,
+ targetRecipientsArray,
replyLength,
categoryId,
missingReplyCharacters,
@@ -402,9 +434,7 @@ const Composer = RestModel.extend({
if (this.privateMessage) {
// need at least one user when sending a PM
- return (
- targetUsernames && (targetUsernames.trim() + ",").indexOf(",") === 0
- );
+ return targetRecipients && targetRecipientsArray.length === 0;
} else {
// has a category? (when needed)
return this.requiredCategoryMissing;
@@ -667,13 +697,17 @@ const Composer = RestModel.extend({
throw new Error("draft sequence is required");
}
+ if (opts.usernames) {
+ deprecated("`usernames` is deprecated, use `recipients` instead.");
+ }
+
this.setProperties({
draftKey: opts.draftKey,
draftSequence: opts.draftSequence,
composeState: opts.composerState || OPEN,
action: opts.action,
topic: opts.topic,
- targetUsernames: opts.usernames,
+ targetRecipients: opts.usernames || opts.recipients,
composerTotalOpened: opts.composerTime,
typingTime: opts.typingTime,
whisper: opts.whisper,
diff --git a/app/assets/javascripts/discourse/models/group.js.es6 b/app/assets/javascripts/discourse/models/group.js.es6
index 284719579a..c799f78314 100644
--- a/app/assets/javascripts/discourse/models/group.js.es6
+++ b/app/assets/javascripts/discourse/models/group.js.es6
@@ -1,10 +1,7 @@
import EmberObject from "@ember/object";
import { equal } from "@ember/object/computed";
import { isEmpty } from "@ember/utils";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax";
import Category from "discourse/models/category";
import GroupHistory from "discourse/models/group-history";
diff --git a/app/assets/javascripts/discourse/models/login-method.js.es6 b/app/assets/javascripts/discourse/models/login-method.js.es6
index 84b4cf4a52..2a3ab489c4 100644
--- a/app/assets/javascripts/discourse/models/login-method.js.es6
+++ b/app/assets/javascripts/discourse/models/login-method.js.es6
@@ -21,7 +21,7 @@ const LoginMethod = EmberObject.extend({
return this.message_override || I18n.t(`login.${this.name}.message`);
},
- doLogin({ reconnect = false } = {}) {
+ doLogin({ reconnect = false, params = {} } = {}) {
if (this.customLogin) {
this.customLogin();
return Promise.resolve();
@@ -35,7 +35,15 @@ const LoginMethod = EmberObject.extend({
let authUrl = Discourse.getURL(`/auth/${this.name}`);
if (reconnect) {
- authUrl += "?reconnect=true";
+ params["reconnect"] = true;
+ }
+
+ const paramKeys = Object.keys(params);
+ if (paramKeys.length > 0) {
+ authUrl += "?";
+ authUrl += paramKeys
+ .map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
+ .join("&");
}
return LoginMethod.buildPostForm(authUrl).then(form => form.submit());
diff --git a/app/assets/javascripts/discourse/models/post-stream.js.es6 b/app/assets/javascripts/discourse/models/post-stream.js.es6
index 614db16332..4accbbe268 100644
--- a/app/assets/javascripts/discourse/models/post-stream.js.es6
+++ b/app/assets/javascripts/discourse/models/post-stream.js.es6
@@ -5,7 +5,7 @@ import { ajax } from "discourse/lib/ajax";
import DiscourseURL from "discourse/lib/url";
import RestModel from "discourse/models/rest";
import PostsWithPlaceholders from "discourse/lib/posts-with-placeholders";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import { loadTopicView } from "discourse/models/topic";
import { Promise } from "rsvp";
import User from "discourse/models/user";
diff --git a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6 b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6
index 82fc8d6f03..8f79703ef7 100644
--- a/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6
+++ b/app/assets/javascripts/discourse/models/topic-tracking-state.js.es6
@@ -1,10 +1,7 @@
import { get } from "@ember/object";
import { isEmpty } from "@ember/utils";
import { NotificationLevels } from "discourse/lib/notification-levels";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
import PreloadStore from "preload-store";
import Category from "discourse/models/category";
import EmberObject from "@ember/object";
@@ -79,15 +76,6 @@ const TopicTrackingState = EmberObject.extend({
}
}
- // fill parent_category_id we need it for counting new/unread
- if (data.payload && data.payload.category_id) {
- var category = Category.findById(data.payload.category_id);
-
- if (category && category.parent_category_id) {
- data.payload.parent_category_id = category.parent_category_id;
- }
- }
-
if (data.message_type === "latest") {
tracker.notify(data);
}
@@ -367,38 +355,43 @@ const TopicTrackingState = EmberObject.extend({
this.incrementProperty("messageCount");
},
- countNew(category_id) {
+ getSubCategoryIds(categoryId) {
+ const result = [categoryId];
+ const categories = Category.list();
+
+ for (let i = 0; i < result.length; ++i) {
+ for (let j = 0; j < categories.length; ++j) {
+ if (result[i] === categories[j].parent_category_id) {
+ result[result.length] = categories[j].id;
+ }
+ }
+ }
+
+ return new Set(result);
+ },
+
+ countNew(categoryId) {
+ const subcategoryIds = this.getSubCategoryIds(categoryId);
return _.chain(this.states)
.filter(isNew)
.filter(
topic =>
topic.archetype !== "private_message" &&
!topic.deleted &&
- (topic.category_id === category_id ||
- topic.parent_category_id === category_id ||
- !category_id)
+ (!categoryId || subcategoryIds.has(topic.category_id))
)
.value().length;
},
- resetNew() {
- Object.keys(this.states).forEach(id => {
- if (this.states[id].last_read_post_number === null) {
- delete this.states[id];
- }
- });
- },
-
- countUnread(category_id) {
+ countUnread(categoryId) {
+ const subcategoryIds = this.getSubCategoryIds(categoryId);
return _.chain(this.states)
.filter(isUnread)
.filter(
topic =>
topic.archetype !== "private_message" &&
!topic.deleted &&
- (topic.category_id === category_id ||
- topic.parent_category_id === category_id ||
- !category_id)
+ (!categoryId || subcategoryIds.has(topic.category_id))
)
.value().length;
},
@@ -445,10 +438,6 @@ const TopicTrackingState = EmberObject.extend({
// I am taking some shortcuts here to avoid 500 gets for a large list
if (data) {
data.forEach(topic => {
- let category = Category.findById(topic.category_id);
- if (category && category.parent_category_id) {
- topic.parent_category_id = category.parent_category_id;
- }
states["t" + topic.topic_id] = topic;
});
}
diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6
index c392bc20ef..514c3564e6 100644
--- a/app/assets/javascripts/discourse/models/topic.js.es6
+++ b/app/assets/javascripts/discourse/models/topic.js.es6
@@ -13,8 +13,7 @@ import { censor } from "pretty-text/censored-words";
import { emojiUnescape } from "discourse/lib/text";
import PreloadStore from "preload-store";
import { userPath } from "discourse/lib/url";
-import {
- default as discourseComputed,
+import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";
@@ -467,16 +466,19 @@ const Topic = RestModel.extend({
// Delete this topic
destroy(deleted_by) {
- this.setProperties({
- deleted_at: new Date(),
- deleted_by: deleted_by,
- "details.can_delete": false,
- "details.can_recover": true
- });
return ajax(`/t/${this.id}`, {
data: { context: window.location.pathname },
type: "DELETE"
- });
+ })
+ .then(() => {
+ this.setProperties({
+ deleted_at: new Date(),
+ deleted_by: deleted_by,
+ "details.can_delete": false,
+ "details.can_recover": true
+ });
+ })
+ .catch(popupAjaxError);
},
// Recover this topic if deleted
diff --git a/app/assets/javascripts/discourse/models/user-stream.js.es6 b/app/assets/javascripts/discourse/models/user-stream.js.es6
index 19e3d2ee62..81af6b30f8 100644
--- a/app/assets/javascripts/discourse/models/user-stream.js.es6
+++ b/app/assets/javascripts/discourse/models/user-stream.js.es6
@@ -4,10 +4,7 @@ import RestModel from "discourse/models/rest";
import UserAction from "discourse/models/user-action";
import { emojiUnescape } from "discourse/lib/text";
import { Promise } from "rsvp";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
export default RestModel.extend({
loaded: false,
diff --git a/app/assets/javascripts/discourse/models/user.js.es6 b/app/assets/javascripts/discourse/models/user.js.es6
index 9b92f4bd03..1607ad1804 100644
--- a/app/assets/javascripts/discourse/models/user.js.es6
+++ b/app/assets/javascripts/discourse/models/user.js.es6
@@ -8,10 +8,7 @@ import UserStream from "discourse/models/user-stream";
import UserPostsStream from "discourse/models/user-posts-stream";
import Singleton from "discourse/mixins/singleton";
import { longDate } from "discourse/lib/formatter";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Badge from "discourse/models/badge";
import UserBadge from "discourse/models/user-badge";
import UserActionStat from "discourse/models/user-action-stat";
diff --git a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6 b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6
index eab9eb988c..924e6f6a50 100644
--- a/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6
+++ b/app/assets/javascripts/discourse/pre-initializers/inject-discourse-objects.js.es6
@@ -4,9 +4,8 @@ import Store from "discourse/models/store";
import DiscourseLocation from "discourse/lib/discourse-location";
import Discourse from "discourse";
import SearchService from "discourse/services/search";
-import {
- startTracking,
- default as TopicTrackingState
+import TopicTrackingState, {
+ startTracking
} from "discourse/models/topic-tracking-state";
import ScreenTrack from "discourse/lib/screen-track";
import Site from "discourse/models/site";
diff --git a/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6 b/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6
index 20b15a3ebd..dd301f8b72 100644
--- a/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6
+++ b/app/assets/javascripts/discourse/raw-views/list/post-count-or-badges.js.es6
@@ -1,6 +1,6 @@
import { or, and } from "@ember/object/computed";
import EmberObject from "@ember/object";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default EmberObject.extend({
postCountsPresent: or("topic.unread", "topic.displayNewPosts"),
diff --git a/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6 b/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6
index e5b44af68e..dfa6e7039c 100644
--- a/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6
+++ b/app/assets/javascripts/discourse/raw-views/topic-list-header-column.js.es6
@@ -1,5 +1,5 @@
import EmberObject from "@ember/object";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default EmberObject.extend({
@discourseComputed
diff --git a/app/assets/javascripts/discourse/routes/application.js.es6 b/app/assets/javascripts/discourse/routes/application.js.es6
index 793c8a6492..e877d8ceff 100644
--- a/app/assets/javascripts/discourse/routes/application.js.es6
+++ b/app/assets/javascripts/discourse/routes/application.js.es6
@@ -71,20 +71,20 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
},
composePrivateMessage(user, post) {
- const recipient = user ? user.get("username") : "",
- reply = post
- ? `${window.location.protocol}//${window.location.host}${post.url}`
- : null,
- title = post
- ? I18n.t("composer.reference_topic_title", {
- title: post.topic.title
- })
- : null;
+ const recipients = user ? user.get("username") : "";
+ const reply = post
+ ? `${window.location.protocol}//${window.location.host}${post.url}`
+ : null;
+ const title = post
+ ? I18n.t("composer.reference_topic_title", {
+ title: post.topic.title
+ })
+ : null;
// used only once, one less dependency
return this.controllerFor("composer").open({
action: Composer.PRIVATE_MESSAGE,
- usernames: recipient,
+ recipients,
archetypeId: "private_message",
draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY,
reply,
@@ -221,8 +221,8 @@ const ApplicationRoute = DiscourseRoute.extend(OpenComposer, {
);
},
- createNewMessageViaParams(username, title, body) {
- this.openComposerWithMessageParams(username, title, body);
+ createNewMessageViaParams(recipients, title, body) {
+ this.openComposerWithMessageParams(recipients, title, body);
}
},
diff --git a/app/assets/javascripts/discourse/routes/preferences-profile.js.es6 b/app/assets/javascripts/discourse/routes/preferences-profile.js.es6
index 7b794ef681..b56f1d7d78 100644
--- a/app/assets/javascripts/discourse/routes/preferences-profile.js.es6
+++ b/app/assets/javascripts/discourse/routes/preferences-profile.js.es6
@@ -3,8 +3,10 @@ import RestrictedUserRoute from "discourse/routes/restricted-user";
export default RestrictedUserRoute.extend({
showFooter: true,
setupController(controller, model) {
- model.user_option.timezone =
- model.user_option.timezone || moment.tz.guess();
+ if (!model.user_option.timezone) {
+ Ember.set(model, "user_option.timezone", moment.tz.guess());
+ }
+
controller.set("model", model);
}
});
diff --git a/app/assets/javascripts/discourse/routes/tags-show.js.es6 b/app/assets/javascripts/discourse/routes/tags-show.js.es6
index 44b6bc6e1d..08fe0b24d2 100644
--- a/app/assets/javascripts/discourse/routes/tags-show.js.es6
+++ b/app/assets/javascripts/discourse/routes/tags-show.js.es6
@@ -84,7 +84,7 @@ export default DiscourseRoute.extend(FilterModeMixin, {
filter = `tags/intersection/${tagId}/${this.additionalTags.join("/")}`;
} else {
this.set("category", null);
- filter = `tags/${tagId}/l/${topicFilter}`;
+ filter = `tag/${tagId}/l/${topicFilter}`;
}
return findTopicList(this.store, this.topicTrackingState, filter, params, {
diff --git a/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6 b/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6
index 74556b57fc..62339b6519 100644
--- a/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6
+++ b/app/assets/javascripts/discourse/routes/topic-by-slug-or-id.js.es6
@@ -1,5 +1,5 @@
import DiscourseRoute from "discourse/routes/discourse";
-import { default as Topic, ID_CONSTRAINT } from "discourse/models/topic";
+import Topic, { ID_CONSTRAINT } from "discourse/models/topic";
import DiscourseURL from "discourse/lib/url";
export default DiscourseRoute.extend({
diff --git a/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6 b/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6
index 392b9a6255..a004e6631e 100644
--- a/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6
+++ b/app/assets/javascripts/discourse/routes/user-activity-drafts.js.es6
@@ -12,9 +12,16 @@ export default DiscourseRoute.extend({
setupController(controller, model) {
controller.set("model", model);
+ },
+
+ activate() {
this.appEvents.on("draft:destroyed", this, this.refresh);
},
+ deactivate() {
+ this.appEvents.off("draft:destroyed", this, this.refresh);
+ },
+
actions: {
didTransition() {
this.controllerFor("user-activity")._showFooter();
diff --git a/app/assets/javascripts/discourse/services/logs-notice.js.es6 b/app/assets/javascripts/discourse/services/logs-notice.js.es6
index 9d124edf0b..7292e4465f 100644
--- a/app/assets/javascripts/discourse/services/logs-notice.js.es6
+++ b/app/assets/javascripts/discourse/services/logs-notice.js.es6
@@ -1,7 +1,6 @@
import { isEmpty } from "@ember/utils";
import EmberObject from "@ember/object";
-import {
- default as discourseComputed,
+import discourseComputed, {
on,
observes
} from "discourse-common/utils/decorators";
diff --git a/app/assets/javascripts/discourse/services/search.js.es6 b/app/assets/javascripts/discourse/services/search.js.es6
index c9e51f43c8..d7a8edcd10 100644
--- a/app/assets/javascripts/discourse/services/search.js.es6
+++ b/app/assets/javascripts/discourse/services/search.js.es6
@@ -1,9 +1,6 @@
import { get } from "@ember/object";
import EmberObject from "@ember/object";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
export default EmberObject.extend({
searchContextEnabled: false, // checkbox to scope search
diff --git a/app/assets/javascripts/discourse/templates/components/backup-codes.hbs b/app/assets/javascripts/discourse/templates/components/backup-codes.hbs
index c8ddb725be..bd591ee6c3 100644
--- a/app/assets/javascripts/discourse/templates/components/backup-codes.hbs
+++ b/app/assets/javascripts/discourse/templates/components/backup-codes.hbs
@@ -1,5 +1,5 @@
-
+
{{d-button
action=(action "copyToClipboard")
diff --git a/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs b/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs
index 8ae095c267..72ae9fd328 100644
--- a/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs
+++ b/app/assets/javascripts/discourse/templates/components/categories-boxes.hbs
@@ -13,7 +13,7 @@
-{{/each}}
\ No newline at end of file
+{{/each}}
diff --git a/app/assets/javascripts/discourse/templates/components/categories-only.hbs b/app/assets/javascripts/discourse/templates/components/categories-only.hbs
index 852745c80f..e918461082 100644
--- a/app/assets/javascripts/discourse/templates/components/categories-only.hbs
+++ b/app/assets/javascripts/discourse/templates/components/categories-only.hbs
@@ -14,17 +14,22 @@
{{category-title-link category=c}}
-
- {{{dir-span c.description_excerpt}}}
-
-
+ {{#if c.description_excerpt}}
+
+ {{{dir-span c.description_excerpt}}}
+
+ {{/if}}
{{#if c.isGrandParent}}
-
+
{{#each c.subcategories as |subcategory|}}
{{category-title-link tagName="h4" category=subcategory}}
-
+ {{#if subcategory.description_excerpt}}
+
+ {{{dir-span subcategory.description_excerpt}}}
+
+ {{/if}}
{{#if subcategory.subcategories}}
{{#each subcategory.subcategories as |subsubcategory|}}
@@ -37,9 +42,11 @@
{{/each}}
{{else}}
-
- {{{dir-span subcategory.description_excerpt}}}
-
+ {{#if subcategory.description_excerpt}}
+
+ {{{dir-span subcategory.description_excerpt}}}
+
+ {{/if}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs b/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs
index c157d22f0c..95e681f396 100644
--- a/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs
+++ b/app/assets/javascripts/discourse/templates/components/edit-category-general.hbs
@@ -1,6 +1,6 @@
{{#if category.isUncategorizedCategory}}
- {{d-icon "warning"}}
+ {{d-icon "exclamation-triangle"}}
{{{i18n 'category.uncategorized_general_warning' settingLink=uncategorizedSiteSettingLink customizeLink=customizeTextContentLink}}}
{{/if}}
@@ -12,7 +12,7 @@
{{i18n 'category.parent'}}
{{category-chooser
- none="category.none"
+ rootNone=true
value=category.parent_category_id
excludeCategoryId=category.id
categories=parentCategories
diff --git a/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs b/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs
index 8802d1c9b2..4d7cf58049 100644
--- a/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs
+++ b/app/assets/javascripts/discourse/templates/components/flag-action-type.hbs
@@ -18,7 +18,7 @@
{{i18n 'flagging.notify_staff'}}
{{/if}}
{{else}}
-
+
diff --git a/app/assets/javascripts/discourse/templates/components/future-date-input.hbs b/app/assets/javascripts/discourse/templates/components/future-date-input.hbs
index d1e9bce807..fc27997d5d 100644
--- a/app/assets/javascripts/discourse/templates/components/future-date-input.hbs
+++ b/app/assets/javascripts/discourse/templates/components/future-date-input.hbs
@@ -35,7 +35,7 @@
{{#if willCloseImmediately}}
- {{d-icon "warning"}}
+ {{d-icon "exclamation-triangle"}}
{{willCloseI18n}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/global-notice.hbs b/app/assets/javascripts/discourse/templates/components/global-notice.hbs
new file mode 100644
index 0000000000..c2b3b75e2e
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/global-notice.hbs
@@ -0,0 +1,19 @@
+{{#each notices as |notice|}}
+
+
+ {{#if notice.options.html}}
+ {{{notice.options.html}}}
+ {{/if}}
+ {{{notice.text}}}
+
+ {{#if notice.options.dismissable}}
+ {{d-button
+ class="btn-flat close"
+ icon="times"
+ action=(action "dismissNotice")
+ actionParam=notice
+ }}
+ {{/if}}
+
+
+{{/each}}
diff --git a/app/assets/javascripts/discourse/templates/components/input-tip.hbs b/app/assets/javascripts/discourse/templates/components/input-tip.hbs
index ade54357b5..60c2336dfc 100644
--- a/app/assets/javascripts/discourse/templates/components/input-tip.hbs
+++ b/app/assets/javascripts/discourse/templates/components/input-tip.hbs
@@ -1 +1,3 @@
-{{tipIcon}} {{tipReason}}
+{{#if tipReason}}
+ {{tipIcon}} {{tipReason}}
+{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/notification-consent-banner.hbs b/app/assets/javascripts/discourse/templates/components/notification-consent-banner.hbs
index 639456d461..463b1b4f17 100644
--- a/app/assets/javascripts/discourse/templates/components/notification-consent-banner.hbs
+++ b/app/assets/javascripts/discourse/templates/components/notification-consent-banner.hbs
@@ -1,8 +1,20 @@
{{#if showNotificationPromptBanner}}
- {{d-button icon="times" action="dismiss" class="btn btn-flat close" title="banner.close"}}
- {{i18n "user.desktop_notifications.consent_prompt"}} {{d-button display="link" action=(action "turnon") label="user.desktop_notifications.enable"}}.
+
+ {{i18n "user.desktop_notifications.consent_prompt"}}
+ {{d-button
+ display="link"
+ action=(action "turnon")
+ label="user.desktop_notifications.enable"
+ }}
+ .
+
+ {{d-button
+ icon="times"
+ action="dismiss"
+ class="btn btn-flat close" title="banner.close"
+ }}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/private-message-glyph.hbs b/app/assets/javascripts/discourse/templates/components/private-message-glyph.hbs
new file mode 100644
index 0000000000..a51a16661d
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/private-message-glyph.hbs
@@ -0,0 +1,11 @@
+{{#if href}}
+
+
+ {{d-icon "envelope" class="private-message-glyph"}}
+
+
+{{else}}
+
+ {{d-icon "envelope" class="private-message-glyph"}}
+
+{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/pwa-install-banner.hbs b/app/assets/javascripts/discourse/templates/components/pwa-install-banner.hbs
index d394724ab4..cbd24abe5a 100644
--- a/app/assets/javascripts/discourse/templates/components/pwa-install-banner.hbs
+++ b/app/assets/javascripts/discourse/templates/components/pwa-install-banner.hbs
@@ -1,10 +1,18 @@
{{#if showPWAInstallBanner}}
- {{d-button icon="times" action="dismiss" class="btn btn-flat close" title="banner.close"}}
- {{discourse-linked-text action=(action "turnOn") translatedText=(i18n "pwa.install_banner" title=siteSettings.title)}}
+ {{discourse-linked-text
+ action=(action "turnOn")
+ translatedText=(i18n "pwa.install_banner" title=siteSettings.title)
+ }}
+ {{d-button
+ icon="times"
+ action="dismiss"
+ class="btn btn-flat close"
+ title="banner.close"
+ }}
{{/if}}
diff --git a/app/assets/javascripts/discourse/templates/components/related-messages.hbs b/app/assets/javascripts/discourse/templates/components/related-messages.hbs
index dde8deaea1..84b7d42962 100644
--- a/app/assets/javascripts/discourse/templates/components/related-messages.hbs
+++ b/app/assets/javascripts/discourse/templates/components/related-messages.hbs
@@ -1,9 +1,12 @@
-
{{{relatedTitle}}}
+
+ {{i18n "related_messages.title"}}
+
+
-{{basic-topic-list
- hideCategory="true"
- showPosters="true"
- topics=topic.relatedMessages}}
+ {{basic-topic-list
+ hideCategory="true"
+ showPosters="true"
+ topics=topic.relatedMessages}}
{{#if targetUser}}
diff --git a/app/assets/javascripts/discourse/templates/components/suggested-topics.hbs b/app/assets/javascripts/discourse/templates/components/suggested-topics.hbs
index e70cfec46a..ffd142b1be 100644
--- a/app/assets/javascripts/discourse/templates/components/suggested-topics.hbs
+++ b/app/assets/javascripts/discourse/templates/components/suggested-topics.hbs
@@ -1,4 +1,7 @@
-
{{{suggestedTitle}}}
+
+ {{i18n suggestedTitleLabel}}
+
+
{{#if topic.isPrivateMessage}}
{{basic-topic-list
diff --git a/app/assets/javascripts/discourse/templates/components/topic-list-item.hbs b/app/assets/javascripts/discourse/templates/components/topic-list-item.hbs
new file mode 100644
index 0000000000..556c0177f9
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/topic-list-item.hbs
@@ -0,0 +1 @@
+{{topicListItemContents}}
diff --git a/app/assets/javascripts/discourse/templates/composer.hbs b/app/assets/javascripts/discourse/templates/composer.hbs
index f738efc362..4a5a178946 100644
--- a/app/assets/javascripts/discourse/templates/composer.hbs
+++ b/app/assets/javascripts/discourse/templates/composer.hbs
@@ -53,7 +53,7 @@
{{#if model.creatingPrivateMessage}}
{{composer-user-selector topicId=topicModel.id
- usernames=model.targetUsernames
+ usernames=model.targetRecipients
hasGroups=model.hasTargetGroups
focusTarget=focusTarget
class="users-input"}}
diff --git a/app/assets/javascripts/discourse/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
index 7cf20a817c..e7b2d385ad 100644
--- a/app/assets/javascripts/discourse/templates/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/discovery/topics.hbs
@@ -72,6 +72,7 @@
category=category
topics=model.topics
discoveryList=true
+ scrollOnLoad=true
onScroll=discoveryTopicList.saveScrollPosition}}
{{/if}}
{{/discovery-topics-list}}
diff --git a/app/assets/javascripts/discourse/templates/group.hbs b/app/assets/javascripts/discourse/templates/group.hbs
index 6a7d2e0b14..8385894c0c 100644
--- a/app/assets/javascripts/discourse/templates/group.hbs
+++ b/app/assets/javascripts/discourse/templates/group.hbs
@@ -56,7 +56,7 @@
- {{group-navigation group=model currentPath=router._router.currentPath tabs=tabs}}
+ {{group-navigation group=model currentPath=currentPath tabs=tabs}}
diff --git a/app/assets/javascripts/discourse/templates/groups/index.hbs b/app/assets/javascripts/discourse/templates/groups/index.hbs
index 955af19e21..64893e5c32 100644
--- a/app/assets/javascripts/discourse/templates/groups/index.hbs
+++ b/app/assets/javascripts/discourse/templates/groups/index.hbs
@@ -53,7 +53,7 @@
{{#group-membership-button tagName='' model=group showLogin=(route-action "showLogin")}}
{{#if group.is_group_owner}}
- {{d-icon "shield"}}
+ {{d-icon "shield-alt"}}
{{i18n "groups.index.is_group_owner"}}
{{else if group.is_group_user}}
diff --git a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
index 6c62f879de..ff07193e10 100644
--- a/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/templates/mobile/discovery/topics.hbs
@@ -1,4 +1,4 @@
-{{#discovery-topics-list model=model refresh=(action "refresh") incomingCount=topicTrackingState.incomingCount}}
+{{#discovery-topics-list model=model refresh=(action "refresh") incomingCount=topicTrackingState.incomingCount as |discoveryTopicList|}}
{{#if top}}
{{period-chooser period=period action=(action "changePeriod")}}
@@ -13,16 +13,17 @@
{{#if hasTopics}}
{{topic-list
- highlightLastVisited=true
- showPosters=true
- hideCategory=model.hideCategory
- order=order
- ascending=ascending
- topics=model.topics
- expandGloballyPinned=expandGloballyPinned
- expandAllPinned=expandAllPinned
- category=category
- }}
+ highlightLastVisited=true
+ showPosters=true
+ hideCategory=model.hideCategory
+ order=order
+ ascending=ascending
+ topics=model.topics
+ expandGloballyPinned=expandGloballyPinned
+ expandAllPinned=expandAllPinned
+ scrollOnLoad=true
+ onScroll=discoveryTopicList.saveScrollPosition
+ category=category}}
{{/if}}
{{/discovery-topics-list}}
diff --git a/app/assets/javascripts/discourse/templates/modal/feature-topic-on-profile.hbs b/app/assets/javascripts/discourse/templates/modal/feature-topic-on-profile.hbs
index c6f54a9340..751789e66d 100644
--- a/app/assets/javascripts/discourse/templates/modal/feature-topic-on-profile.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/feature-topic-on-profile.hbs
@@ -1,4 +1,4 @@
-{{#d-modal-body class="feature-topic-on-profile"}}
+{{#d-modal-body class="feature-topic-on-profile" id='choosing-topic'}}
{{choose-topic currentTopicId=model.featured_topic.id
selectedTopicId=newFeaturedTopicId
additionalFilters="in:created status:public"
@@ -6,8 +6,11 @@
topicChangedCallback=(action "newTopicSelected")
loadOnInit=true
}}
+{{/d-modal-body}}
+
+
diff --git a/app/assets/javascripts/discourse/templates/modal/ignore-duration-with-username.hbs b/app/assets/javascripts/discourse/templates/modal/ignore-duration-with-username.hbs
index 40a2826d9d..9d3dadbd71 100644
--- a/app/assets/javascripts/discourse/templates/modal/ignore-duration-with-username.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/ignore-duration-with-username.hbs
@@ -1,6 +1,6 @@
{{#d-modal-body title="user.user_notifications.ignore_duration_title" autoFocus="false"}}
- {{d-icon "eye-slash" class="icon"}} {{i18n "user.user_notifications.ignore_duration_username"}}
+ {{d-icon "far-eye-slash" class="icon"}} {{i18n "user.user_notifications.ignore_duration_username"}}
{{user-selector excludeCurrentUser=true
single="true"
usernames=ignoredUsername
diff --git a/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs b/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs
index 5342441af4..ffd6c2eb4f 100644
--- a/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/keyboard-shortcuts-help.hbs
@@ -67,6 +67,7 @@
{{{shortcuts.actions.mark_watching}}}
{{{shortcuts.actions.defer}}}
{{{shortcuts.actions.print}}}
+ {{{shortcuts.actions.topic_admin_actions}}}
diff --git a/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs b/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs
index 3a0335af40..0574ff843f 100644
--- a/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/move-to-topic.hbs
@@ -1,4 +1,4 @@
-{{#d-modal-body id='move-selected'}}
+{{#d-modal-body id='choosing-topic'}}
{{#if model.isPrivateMessage}}
diff --git a/app/assets/javascripts/discourse/templates/password-reset.hbs b/app/assets/javascripts/discourse/templates/password-reset.hbs
index c7c83156c5..d2126263c4 100644
--- a/app/assets/javascripts/discourse/templates/password-reset.hbs
+++ b/app/assets/javascripts/discourse/templates/password-reset.hbs
@@ -28,7 +28,7 @@
showSecurityKey=model.security_key_required
showSecondFactor=false
secondFactorMethod=secondFactorMethod
- otherMethodAllowed=secondFactorRequired
+ otherMethodAllowed=otherMethodAllowed
action=(action "authenticateSecurityKey")}}
{{/security-key-form}}
{{else}}
diff --git a/app/assets/javascripts/discourse/templates/preferences/profile.hbs b/app/assets/javascripts/discourse/templates/preferences/profile.hbs
index cd319406f3..47dbc7a143 100644
--- a/app/assets/javascripts/discourse/templates/preferences/profile.hbs
+++ b/app/assets/javascripts/discourse/templates/preferences/profile.hbs
@@ -68,7 +68,7 @@
{{d-button action=(action "showFeaturedTopicModal")
- class="btn-primary feature-topic-on-profile-btn"
+ class="btn-default feature-topic-on-profile-btn"
label="user.feature_topic_on_profile.open_search"}}
{{#if model.featured_topic}}
{{d-button action=(action "clearFeaturedTopicFromProfile")
diff --git a/app/assets/javascripts/discourse/templates/preferences/users.hbs b/app/assets/javascripts/discourse/templates/preferences/users.hbs
index c513c26176..c43db04fdd 100644
--- a/app/assets/javascripts/discourse/templates/preferences/users.hbs
+++ b/app/assets/javascripts/discourse/templates/preferences/users.hbs
@@ -2,7 +2,7 @@
{{#if ignoredEnabled}}
- {{d-icon "eye-slash" class="icon"}} {{i18n 'user.ignored_users'}}
+ {{d-icon "far-eye-slash" class="icon"}} {{i18n 'user.ignored_users'}}
{{ignored-user-list model=model items=model.ignored_usernames saving=saved}}
diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs
index 0a7671bf64..bcc3184d82 100644
--- a/app/assets/javascripts/discourse/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/templates/topic.hbs
@@ -17,9 +17,8 @@
{{#topic-title cancelled=(action "cancelEditingTopic") save=(action "finishedEditingTopic") model=model}}
{{#if editingTopic}}
- {{#if model.isPrivateMessage}}
-
{{d-icon "envelope"}}
- {{/if}}
+ {{private-message-glyph isVisible=model.isPrivateMessage}}
+
{{text-field id="edit-title" value=buffered.title maxlength=siteSettings.max_topic_title_length autofocus="true"}}
{{#if showCategoryChooser}}
@@ -56,15 +55,14 @@
{{#unless model.is_warning}}
{{#if siteSettings.enable_personal_messages}}
- {{#if model.isPrivateMessage}}
-
- {{d-icon "envelope"}}
-
- {{/if}}
+ {{private-message-glyph
+ isVisible=model.isPrivateMessage
+ href=pmPath
+ title="topic_statuses.personal_message.title"
+ ariaLabel="user.messages.inbox"
+ }}
{{else}}
- {{#if model.isPrivateMessage}}
- {{d-icon "envelope"}}
- {{/if}}
+ {{private-message-glyph isVisible=model.isPrivateMessage}}
{{/if}}
{{/unless}}
diff --git a/app/assets/javascripts/discourse/templates/user/notifications.hbs b/app/assets/javascripts/discourse/templates/user/notifications.hbs
index fa74e925d3..8dca10c91f 100644
--- a/app/assets/javascripts/discourse/templates/user/notifications.hbs
+++ b/app/assets/javascripts/discourse/templates/user/notifications.hbs
@@ -1,5 +1,5 @@
{{#d-section pageClass="user-notifications" class="user-secondary-navigation"}}
- {{#mobile-nav class='notifications-nav' desktopClass='notification-list action-list nav-stacked' currentPath=router._router.currentPath}}
+ {{#mobile-nav class='notifications-nav' desktopClass='notification-list action-list nav-stacked' currentPath=currentPath}}
{{#link-to 'userNotifications.index'}}
{{i18n 'user.filters.all'}}
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 02edb15cbe..2e73b3253b 100644
--- a/app/assets/javascripts/discourse/widgets/header-topic-info.js.es6
+++ b/app/assets/javascripts/discourse/widgets/header-topic-info.js.es6
@@ -69,8 +69,10 @@ export default createWidget("header-topic-info", {
heading.push(
h(
"a.private-message-glyph-wrapper",
- { attributes: { href } },
- h("span.private-message-glyph", iconNode("envelope"))
+ {
+ attributes: { href, "aria-label": I18n.t("user.messages.inbox") }
+ },
+ iconNode("envelope", { class: "private-message-glyph" })
)
);
}
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 6463bc33de..e5c8138673 100644
--- a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6
@@ -39,20 +39,21 @@ createWidget("topic-admin-menu-button", {
fixed: attrs.fixed,
topic: attrs.topic,
openUpwards: attrs.openUpwards,
- rightSide: attrs.rightSide,
+ rightSide: !this.site.mobileView && attrs.rightSide,
actionButtons: []
});
- // We don't show the button when expanded on the right side
+ // We don't show the button when expanded on the right side on desktop
if (
menu.attrs.actionButtons.length &&
- !(attrs.rightSide && state.expanded)
+ (!(attrs.rightSide && state.expanded) || this.site.mobileView)
) {
result.push(
this.attach("button", {
className:
"btn-default toggle-admin-menu" +
- (attrs.fixed ? " show-topic-admin" : ""),
+ (attrs.fixed ? " show-topic-admin" : "") +
+ (attrs.addKeyboardTargetClass ? " keyboard-target-admin-menu" : ""),
title: "topic_admin_menu",
icon: "wrench",
action: "showAdminMenu",
@@ -71,13 +72,24 @@ createWidget("topic-admin-menu-button", {
hideAdminMenu() {
this.state.expanded = false;
this.state.position = null;
+
+ if (this.site.mobileView && !this.attrs.rightSide) {
+ $(".header-cloak").css("display", "");
+ }
},
showAdminMenu(e) {
this.state.expanded = true;
+ let $button;
+
+ if (e === undefined) {
+ $button = $(".keyboard-target-admin-menu");
+ } else {
+ $button = $(e.target).closest("button");
+ }
- const $button = $(e.target).closest("button");
const position = $button.position();
+
const rtl = $("html").hasClass("rtl");
position.left = position.left;
position.outerHeight = $button.outerHeight();
@@ -89,7 +101,16 @@ createWidget("topic-admin-menu-button", {
if (this.attrs.fixed) {
position.left += $button.width() - 203;
}
+
+ if (this.site.mobileView && !this.attrs.rightSide) {
+ $(".header-cloak").css("display", "block");
+ }
+
this.state.position = position;
+ },
+
+ topicToggleActions() {
+ this.state.expanded ? this.hideAdminMenu() : this.showAdminMenu();
}
});
@@ -238,7 +259,7 @@ export default createWidget("topic-admin-menu", {
buildAttributes(attrs) {
let { top, left, outerHeight } = attrs.position;
- const position = attrs.fixed ? "fixed" : "absolute";
+ const position = attrs.fixed || this.site.mobileView ? "fixed" : "absolute";
if (attrs.rightSide) {
return;
@@ -253,6 +274,11 @@ export default createWidget("topic-admin-menu", {
bottom = bottom - (documentHeight - mainHeight) - outerHeight;
}
+ if (this.site.mobileView) {
+ bottom = 0;
+ left = 0;
+ }
+
return {
style: `position: ${position}; bottom: ${bottom}px; left: ${left}px;`
};
@@ -275,7 +301,17 @@ export default createWidget("topic-admin-menu", {
this.state
);
return [
- h("h3", I18n.t("topic.actions.title")),
+ h("div.header", [
+ h("h3", I18n.t("topic.actions.title")),
+ h(
+ "div",
+ this.attach("button", {
+ action: "clickOutside",
+ icon: "times",
+ className: "close-button"
+ })
+ )
+ ]),
h(
"ul",
attrs.actionButtons
diff --git a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6 b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
index 7707f94b12..72157d7910 100644
--- a/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
+++ b/app/assets/javascripts/discourse/widgets/topic-timeline.js.es6
@@ -263,7 +263,7 @@ createWidget("timeline-scrollarea", {
const position = this.position();
this.state.scrolledPost = position.current;
- if (position.current === position.scrollPosition || this.site.mobileView) {
+ if (position.current === position.scrollPosition) {
this.sendWidgetAction("jumpToIndex", position.current);
} else {
this.sendWidgetAction("jumpEnd");
@@ -322,7 +322,12 @@ createWidget("timeline-controls", {
const { fullScreen, currentUser, topic } = attrs;
if (!fullScreen && currentUser) {
- controls.push(this.attach("topic-admin-menu-button", { topic }));
+ controls.push(
+ this.attach("topic-admin-menu-button", {
+ topic,
+ addKeyboardTargetClass: true
+ })
+ );
}
return controls;
diff --git a/app/assets/javascripts/polyfills.js b/app/assets/javascripts/polyfills.js
index ae960cdea5..0129823fc8 100644
--- a/app/assets/javascripts/polyfills.js
+++ b/app/assets/javascripts/polyfills.js
@@ -13,4 +13,40 @@ if (RegExp.prototype.flags === undefined) {
});
}
+// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
+if (!String.prototype.padStart) {
+ String.prototype.padStart = function padStart(targetLength, padString) {
+ targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
+ padString = String(typeof padString !== "undefined" ? padString : " ");
+ if (this.length >= targetLength) {
+ return String(this);
+ } else {
+ targetLength = targetLength - this.length;
+ if (targetLength > padString.length) {
+ padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
+ }
+ return padString.slice(0, targetLength) + String(this);
+ }
+ };
+}
+
+// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
+if (!String.prototype.padEnd) {
+ String.prototype.padEnd = function padEnd(targetLength, padString) {
+ targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
+ padString = String(typeof padString !== "undefined" ? padString : " ");
+ if (this.length > targetLength) {
+ return String(this);
+ } else {
+ targetLength = targetLength - this.length;
+ if (targetLength > padString.length) {
+ padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
+ }
+ return String(this) + padString.slice(0, targetLength);
+ }
+ };
+}
+
/* eslint-enable */
diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6
index ba5033b812..eb48023397 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6
@@ -1,4 +1,4 @@
-import { default as WhiteLister } from "pretty-text/white-lister";
+import WhiteLister from "pretty-text/white-lister";
import { sanitize } from "pretty-text/sanitizer";
import guid from "pretty-text/guid";
diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6
index ba2c971541..7b4bee783d 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/emoji.js.es6
@@ -231,9 +231,68 @@ function applyEmoji(
result.push(text);
}
+ // we check for a result <= 5 because we support maximum 3 large emojis
+ // EMOJI SPACE EMOJI SPACE EMOJI => 5 tokens
+ if (result && result.length > 0 && result.length <= 5) {
+ // we ensure line starts and ends with an emoji
+ // and has no more than 3 emojis
+ if (
+ result[0].type === "emoji" &&
+ result[result.length - 1].type === "emoji" &&
+ result.filter(r => r.type === "emoji").length <= 3
+ ) {
+ let onlyEmojiLine = true;
+ let index = 0;
+
+ const checkNextToken = t => {
+ if (!t) {
+ return;
+ }
+
+ if (!["emoji", "text"].includes(t.type)) {
+ onlyEmojiLine = false;
+ }
+
+ // a text token should always have an emoji before
+ // and be a space
+ if (
+ t.type === "text" &&
+ ((result[index - 1] && result[index - 1].type !== "emoji") ||
+ t.content !== " ")
+ ) {
+ onlyEmojiLine = false;
+ }
+
+ // exit as soon as possible
+ if (onlyEmojiLine) {
+ index += 1;
+ checkNextToken(result[index]);
+ }
+ };
+
+ checkNextToken(result[index]);
+
+ if (onlyEmojiLine) {
+ result.forEach(r => {
+ if (r.type === "emoji") {
+ applyOnlyEmojiClass(r);
+ }
+ });
+ }
+ }
+ }
+
return result;
}
+function applyOnlyEmojiClass(token) {
+ token.attrs.forEach(attr => {
+ if (attr[0] === "class") {
+ attr[1] = `${attr[1]} only-emoji`;
+ }
+ });
+}
+
export function setup(helper) {
helper.registerOptions((opts, siteSettings, state) => {
opts.features.emoji = !state.disableEmojis && !!siteSettings.enable_emoji;
@@ -257,5 +316,10 @@ export function setup(helper) {
);
});
- helper.whiteList(["img[class=emoji]", "img[class=emoji emoji-custom]"]);
+ helper.whiteList([
+ "img[class=emoji]",
+ "img[class=emoji emoji-custom]",
+ "img[class=emoji emoji-custom only-emoji]",
+ "img[class=emoji only-emoji]"
+ ]);
}
diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/resize-controls.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/resize-controls.js.es6
index bb8e381d33..11458bdd02 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown/resize-controls.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/resize-controls.js.es6
@@ -22,14 +22,14 @@ function wrapImage(tokens, index, state, imgNumber) {
tokens.splice(
index,
0,
- buildToken(state, "wrap_image_open", "div", "image-wrapper", 1)
+ buildToken(state, "wrap_image_open", "span", "image-wrapper", 1)
);
const newElements = [];
const btnWrapper = buildToken(
state,
"wrap_button_open",
- "div",
+ "span",
"button-wrapper",
1
);
@@ -70,24 +70,14 @@ function wrapImage(tokens, index, state, imgNumber) {
newElements.push(buildToken(state, "separator_close", "span", "", -1));
}
});
- newElements.push(buildToken(state, "wrap_button_close", "div", "", -1));
+ newElements.push(buildToken(state, "wrap_button_close", "span", "", -1));
- newElements.push(buildToken(state, "wrap_image_close", "div", "", -1));
+ newElements.push(buildToken(state, "wrap_image_close", "span", "", -1));
const afterImageIndex = index + 2;
tokens.splice(afterImageIndex, 0, ...newElements);
}
-function removeParagraph(tokens, imageIndex) {
- if (
- tokens[imageIndex - 1] &&
- tokens[imageIndex - 1].type === "paragraph_open"
- )
- tokens.splice(imageIndex - 1, 1);
- if (tokens[imageIndex] && tokens[imageIndex].type === "paragraph_close")
- tokens.splice(imageIndex, 1);
-}
-
function updateIndexes(indexes, name) {
indexes[name].push(indexes.current);
indexes.current++;
@@ -97,7 +87,6 @@ function wrapImages(tokens, tokenIndexes, state, imgNumberIndexes) {
//We do this in reverse order because it's easier for #wrapImage to manipulate the tokens array.
for (let j = tokenIndexes.length - 1; j >= 0; j--) {
let index = tokenIndexes[j];
- removeParagraph(tokens, index);
wrapImage(tokens, index, state, imgNumberIndexes.pop());
}
}
@@ -123,7 +112,6 @@ function rule(state) {
const childrenImage = token.tag === "img";
if (childrenImage && isUpload(blockToken) && hasMetadata(token)) {
- removeParagraph(state.tokens, i);
childrenIndexes.push(j);
updateIndexes(indexNumbers, "childrens");
}
@@ -144,8 +132,8 @@ export function setup(helper) {
const opts = helper.getOptions();
if (opts.previewing) {
helper.whiteList([
- "div.image-wrapper",
- "div.button-wrapper",
+ "span.image-wrapper",
+ "span.button-wrapper",
"span[class=scale-btn]",
"span[class=scale-btn active]",
"span.separator",
diff --git a/app/assets/javascripts/select-kit/components/category-chooser.js.es6 b/app/assets/javascripts/select-kit/components/category-chooser.js.es6
index 8a91e9868e..07872a02ce 100644
--- a/app/assets/javascripts/select-kit/components/category-chooser.js.es6
+++ b/app/assets/javascripts/select-kit/components/category-chooser.js.es6
@@ -3,7 +3,7 @@ import discourseComputed from "discourse-common/utils/decorators";
import PermissionType from "discourse/models/permission-type";
import Category from "discourse/models/category";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
-const { get, isNone, isEmpty } = Ember;
+const { get, isPresent, isEmpty } = Ember;
export default ComboBoxComponent.extend({
pluginApiIdentifiers: ["category-chooser"],
@@ -55,15 +55,13 @@ export default ComboBoxComponent.extend({
@discourseComputed("rootNone", "rootNoneLabel")
none(rootNone, rootNoneLabel) {
- if (
+ if (isPresent(rootNone)) {
+ return rootNoneLabel || "category.none";
+ } else if (
this.siteSettings.allow_uncategorized_topics ||
this.allowUncategorized
) {
- if (!isNone(rootNone)) {
- return rootNoneLabel || "category.none";
- } else {
- return Category.findUncategorized();
- }
+ return Category.findUncategorized();
} else {
return "category.choose";
}
@@ -74,26 +72,12 @@ export default ComboBoxComponent.extend({
if (this.hasSelection) {
const category = Category.findById(content.value);
- const parentCategoryId = category.get("parent_category_id");
- const hasParentCategory = Ember.isPresent(parentCategoryId);
-
- let badge = "";
-
- if (hasParentCategory) {
- const parentCategory = Category.findById(parentCategoryId);
- badge += categoryBadgeHTML(parentCategory, {
- link: false,
- allowUncategorized: true
- }).htmlSafe();
- }
-
- badge += categoryBadgeHTML(category, {
+ content.label = categoryBadgeHTML(category, {
link: false,
- hideParent: hasParentCategory ? true : false,
- allowUncategorized: true
+ hideParent: !!category.parent_category_id,
+ allowUncategorized: true,
+ recursive: true
}).htmlSafe();
-
- content.label = badge;
}
return content;
diff --git a/app/assets/javascripts/select-kit/components/category-drop.js.es6 b/app/assets/javascripts/select-kit/components/category-drop.js.es6
index 165818ffb2..f2ad904d3e 100644
--- a/app/assets/javascripts/select-kit/components/category-drop.js.es6
+++ b/app/assets/javascripts/select-kit/components/category-drop.js.es6
@@ -1,7 +1,7 @@
import { alias, not } from "@ember/object/computed";
import ComboBoxComponent from "select-kit/components/combo-box";
import DiscourseURL from "discourse/lib/url";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import Category from "discourse/models/category";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
import Site from "discourse/models/site";
diff --git a/app/assets/javascripts/select-kit/components/category-row.js.es6 b/app/assets/javascripts/select-kit/components/category-row.js.es6
index dee781b294..452aad22d5 100644
--- a/app/assets/javascripts/select-kit/components/category-row.js.es6
+++ b/app/assets/javascripts/select-kit/components/category-row.js.es6
@@ -45,7 +45,7 @@ export default SelectKitRowComponent.extend({
return categoryBadgeHTML(category, {
link: this.categoryLink,
allowUncategorized: this.allowUncategorized,
- hideParent: parentCategory ? true : false
+ hideParent: !!parentCategory
}).htmlSafe();
},
@@ -53,7 +53,8 @@ export default SelectKitRowComponent.extend({
badgeForParentCategory(parentCategory) {
return categoryBadgeHTML(parentCategory, {
link: this.categoryLink,
- allowUncategorized: this.allowUncategorized
+ allowUncategorized: this.allowUncategorized,
+ recursive: true
}).htmlSafe();
},
diff --git a/app/assets/javascripts/select-kit/components/color-palettes/color-palettes-row.js.es6 b/app/assets/javascripts/select-kit/components/color-palettes/color-palettes-row.js.es6
index 7aa53eb596..b888612264 100644
--- a/app/assets/javascripts/select-kit/components/color-palettes/color-palettes-row.js.es6
+++ b/app/assets/javascripts/select-kit/components/color-palettes/color-palettes-row.js.es6
@@ -1,6 +1,6 @@
import { escapeExpression } from "discourse/lib/utilities";
import SelectKitRowComponent from "select-kit/components/select-kit/select-kit-row";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default SelectKitRowComponent.extend({
layoutName:
diff --git a/app/assets/javascripts/select-kit/components/combo-box.js.es6 b/app/assets/javascripts/select-kit/components/combo-box.js.es6
index 71928d314f..e68dab3c5f 100644
--- a/app/assets/javascripts/select-kit/components/combo-box.js.es6
+++ b/app/assets/javascripts/select-kit/components/combo-box.js.es6
@@ -1,8 +1,5 @@
import SingleSelectComponent from "select-kit/components/single-select";
-import {
- on,
- default as discourseComputed
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
export default SingleSelectComponent.extend({
pluginApiIdentifiers: ["combo-box"],
diff --git a/app/assets/javascripts/select-kit/components/group-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/group-dropdown.js.es6
index 0e6ccfac0f..2abd0a0bb9 100644
--- a/app/assets/javascripts/select-kit/components/group-dropdown.js.es6
+++ b/app/assets/javascripts/select-kit/components/group-dropdown.js.es6
@@ -1,7 +1,7 @@
import { alias } from "@ember/object/computed";
import ComboBoxComponent from "select-kit/components/combo-box";
import DiscourseURL from "discourse/lib/url";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default ComboBoxComponent.extend({
pluginApiIdentifiers: ["group-dropdown"],
diff --git a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6 b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6
index f0ebb00151..dc8518edb9 100644
--- a/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6
+++ b/app/assets/javascripts/select-kit/components/mini-tag-chooser.js.es6
@@ -2,7 +2,7 @@ import { empty, alias } from "@ember/object/computed";
import Category from "discourse/models/category";
import ComboBox from "select-kit/components/combo-box";
import TagsMixin from "select-kit/mixins/tags";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
import renderTag from "discourse/lib/render-tag";
import { escapeExpression } from "discourse/lib/utilities";
import { makeArray } from "discourse-common/lib/helpers";
diff --git a/app/assets/javascripts/select-kit/components/multi-select.js.es6 b/app/assets/javascripts/select-kit/components/multi-select.js.es6
index 40a5dc7f0b..9a28a53cbc 100644
--- a/app/assets/javascripts/select-kit/components/multi-select.js.es6
+++ b/app/assets/javascripts/select-kit/components/multi-select.js.es6
@@ -1,8 +1,5 @@
import SelectKitComponent from "select-kit/components/select-kit";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
const { get, isNone, isEmpty, makeArray, run } = Ember;
import {
applyOnSelectPluginApiCallbacks,
diff --git a/app/assets/javascripts/select-kit/components/notifications-button.js.es6 b/app/assets/javascripts/select-kit/components/notifications-button.js.es6
index 172282ba0b..e6eeb542f6 100644
--- a/app/assets/javascripts/select-kit/components/notifications-button.js.es6
+++ b/app/assets/javascripts/select-kit/components/notifications-button.js.es6
@@ -1,7 +1,6 @@
import { alias } from "@ember/object/computed";
import DropdownSelectBoxComponent from "select-kit/components/dropdown-select-box";
-import {
- default as discourseComputed,
+import discourseComputed, {
observes,
on
} from "discourse-common/utils/decorators";
diff --git a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6 b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6
index a50db81e0a..7ab8ff112f 100644
--- a/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6
+++ b/app/assets/javascripts/select-kit/components/select-kit/select-kit-row.js.es6
@@ -1,9 +1,6 @@
import { alias, or } from "@ember/object/computed";
import Component from "@ember/component";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
const { run, isPresent, makeArray, isEmpty } = Ember;
import UtilsMixin from "select-kit/mixins/utils";
diff --git a/app/assets/javascripts/select-kit/components/single-select.js.es6 b/app/assets/javascripts/select-kit/components/single-select.js.es6
index 14047fbb03..42ccc4fa66 100644
--- a/app/assets/javascripts/select-kit/components/single-select.js.es6
+++ b/app/assets/javascripts/select-kit/components/single-select.js.es6
@@ -1,8 +1,5 @@
import SelectKitComponent from "select-kit/components/select-kit";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
const { get, isNone, isEmpty, isPresent, run, makeArray } = Ember;
import {
diff --git a/app/assets/javascripts/select-kit/components/tag-drop.js.es6 b/app/assets/javascripts/select-kit/components/tag-drop.js.es6
index 5de47f178b..3e80f3ce8a 100644
--- a/app/assets/javascripts/select-kit/components/tag-drop.js.es6
+++ b/app/assets/javascripts/select-kit/components/tag-drop.js.es6
@@ -4,7 +4,7 @@ import { makeArray } from "discourse-common/lib/helpers";
import ComboBoxComponent from "select-kit/components/combo-box";
import DiscourseURL from "discourse/lib/url";
import TagsMixin from "select-kit/mixins/tags";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
const { isEmpty, run } = Ember;
import Category from "discourse/models/category";
import deprecated from "discourse-common/lib/deprecated";
@@ -185,12 +185,12 @@ export default ComboBoxComponent.extend(TagsMixin, {
} else if (tagId === "no-tags") {
url = Discourse.getURL(this.noTagsUrl);
} else {
- url = "/tags";
-
if (this.currentCategory) {
- url += `/c/${Category.slugFor(this.currentCategory)}/${
+ url = `/tags/c/${Category.slugFor(this.currentCategory)}/${
this.currentCategory.id
}`;
+ } else {
+ url = "/tag";
}
if (tag && tag.targetTagId) {
diff --git a/app/assets/javascripts/select-kit/components/timezone-input.js.es6 b/app/assets/javascripts/select-kit/components/timezone-input.js.es6
index cc837e1d66..8c52564f05 100644
--- a/app/assets/javascripts/select-kit/components/timezone-input.js.es6
+++ b/app/assets/javascripts/select-kit/components/timezone-input.js.es6
@@ -1,5 +1,5 @@
import ComboBoxComponent from "select-kit/components/combo-box";
-import { default as discourseComputed } from "discourse-common/utils/decorators";
+import discourseComputed from "discourse-common/utils/decorators";
export default ComboBoxComponent.extend({
pluginApiIdentifiers: ["timezone-input"],
diff --git a/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6 b/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6
index 1001c7884d..b81c7a43a8 100644
--- a/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6
+++ b/app/assets/javascripts/select-kit/components/topic-notifications-options.js.es6
@@ -1,8 +1,5 @@
import NotificationOptionsComponent from "select-kit/components/notifications-button";
-import {
- default as discourseComputed,
- on
-} from "discourse-common/utils/decorators";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
import { topicLevels } from "discourse/lib/notification-levels";
export default NotificationOptionsComponent.extend({
diff --git a/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6 b/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6
index 2551e20b61..1141395a6d 100644
--- a/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6
+++ b/app/assets/javascripts/select-kit/components/user-notifications-dropdown.js.es6
@@ -26,7 +26,7 @@ export default DropdownSelectBox.extend({
if (this.get("user.can_ignore_user")) {
content.push({
- icon: "eye-slash",
+ icon: "far-eye-slash",
id: "changeToIgnored",
description: I18n.t("user.user_notifications.ignore_option_title"),
label: I18n.t("user.user_notifications.ignore_option")
diff --git a/app/assets/javascripts/select-kit/templates/components/category-row.hbs b/app/assets/javascripts/select-kit/templates/components/category-row.hbs
index 94948bc70e..b35247b47e 100644
--- a/app/assets/javascripts/select-kit/templates/components/category-row.hbs
+++ b/app/assets/javascripts/select-kit/templates/components/category-row.hbs
@@ -1,22 +1,15 @@
{{#if category}}
- {{#if hasParentCategory}}
-
+
+ {{#if hasParentCategory}}
{{#unless hideParentCategory}}
{{badgeForParentCategory}}
{{/unless}}
- {{badgeForCategory}}
-
- × {{topicCount}}
-
-
- {{else}}
-
- {{badgeForCategory}}
-
- × {{topicCount}}
-
-
- {{/if}}
+ {{/if}}
+ {{badgeForCategory}}
+
+ × {{topicCount}}
+
+
{{#if shouldDisplayDescription}}
{{{dir-span description}}}
diff --git a/app/assets/javascripts/template_include.js b/app/assets/javascripts/template_include.js
new file mode 100644
index 0000000000..461d711ea3
--- /dev/null
+++ b/app/assets/javascripts/template_include.js
@@ -0,0 +1 @@
+//= require handlebars.runtime
diff --git a/app/assets/javascripts/template_include.js.erb b/app/assets/javascripts/template_include.js.erb
index ff7c323aea..e69de29bb2 100644
--- a/app/assets/javascripts/template_include.js.erb
+++ b/app/assets/javascripts/template_include.js.erb
@@ -1,8 +0,0 @@
-<%
-if Rails.env.development? || Rails.env.test?
- require_asset ("handlebars.js")
- require_asset ("ember-template-compiler.js")
-else
- require_asset ("handlebars.runtime.js")
-end
-%>
diff --git a/app/assets/javascripts/wizard/components/wizard-step.js.es6 b/app/assets/javascripts/wizard/components/wizard-step.js.es6
index c5008ac2ac..7884a27005 100644
--- a/app/assets/javascripts/wizard/components/wizard-step.js.es6
+++ b/app/assets/javascripts/wizard/components/wizard-step.js.es6
@@ -1,10 +1,7 @@
import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component";
import getUrl from "discourse-common/lib/get-url";
-import {
- default as discourseComputed,
- observes
-} from "discourse-common/utils/decorators";
+import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { htmlSafe } from "@ember/template";
jQuery.fn.wiggle = function(times, duration) {
diff --git a/app/assets/stylesheets/common/admin/badges.scss b/app/assets/stylesheets/common/admin/badges.scss
index 57df3636bd..f7d2b367f2 100644
--- a/app/assets/stylesheets/common/admin/badges.scss
+++ b/app/assets/stylesheets/common/admin/badges.scss
@@ -119,6 +119,34 @@
}
}
+.award-badge {
+ margin: 15px 0 0 15px;
+ float: left;
+
+ .badge-preview {
+ min-height: 110px;
+ max-width: 300px;
+ display: flex;
+ align-items: center;
+ background-color: $primary-very-low;
+ border: 1px solid $primary-low;
+ padding: 0 10px 0 10px;
+
+ img,
+ svg {
+ width: 60px;
+ height: 60px;
+ }
+
+ .badge-display-name {
+ margin-left: 5px;
+ }
+ }
+ .badge-required {
+ font-weight: bold;
+ }
+}
+
// badge-grouping modal
.badge-groupings-modal {
.badge-groupings {
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index e96f6cf903..4ec13b753a 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -440,7 +440,7 @@
bottom: 10px;
}
.ace-wrapper {
- height: calc(100% - 200px);
+ height: calc(100vh - 200px);
}
}
diff --git a/app/assets/stylesheets/common/base/alert.scss b/app/assets/stylesheets/common/base/alert.scss
index f605d993cf..40541aab0b 100644
--- a/app/assets/stylesheets/common/base/alert.scss
+++ b/app/assets/stylesheets/common/base/alert.scss
@@ -1,14 +1,15 @@
.alert {
- padding: 8px 32px 8px 16px;
+ padding: 0.5em 1em;
background-color: $danger-low;
color: $primary;
position: relative;
.close {
+ font-size: $font-up-3;
position: absolute;
top: 8px;
right: 8px;
- font-size: $font-up-3;
+
.d-icon {
color: $primary-low-mid;
}
diff --git a/app/assets/stylesheets/common/base/category-list.scss b/app/assets/stylesheets/common/base/category-list.scss
index 7ad6f5044b..22c99387d6 100644
--- a/app/assets/stylesheets/common/base/category-list.scss
+++ b/app/assets/stylesheets/common/base/category-list.scss
@@ -11,6 +11,13 @@
vertical-align: text-top;
line-height: $line-height-medium;
}
+ &.with-topics {
+ .subcategories-with-subcategories {
+ .category-description {
+ display: none;
+ }
+ }
+ }
}
.category-boxes,
@@ -23,16 +30,13 @@
width: 100%;
.category-box {
+ position: relative;
display: flex;
flex-direction: row;
align-content: flex-start;
- cursor: pointer;
-
box-sizing: border-box;
-
border-width: 0;
border-left-width: 6px;
-
border-style: solid;
border-color: $primary-low;
@@ -40,6 +44,19 @@
width: 100%;
}
+ .parent-box-link {
+ // This avoids an issue with nested links by layering links instead
+ &:before {
+ content: "";
+ position: absolute;
+ z-index: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ }
+
.logo.aspect-image img {
display: block;
width: auto;
@@ -50,6 +67,7 @@
@supports (--custom: property) {
.logo.aspect-image img {
--height: 40px;
+ height: var(--height);
width: calc(var(--height) * var(--aspect-ratio));
max-width: 100%;
}
@@ -121,6 +139,58 @@
overflow: hidden;
}
+ h4 a {
+ color: $primary;
+ }
+
+ .subcategory.with-subcategories {
+ position: relative;
+ border: none;
+ border-left-width: 4px;
+ border-left-style: solid;
+ margin-bottom: 0.25em;
+
+ .category-title-link {
+ display: flex;
+ .category-logo {
+ flex: 1 0 auto;
+ margin: 0.25em 0.5em 0.5em 0;
+ --max-height: 40px;
+ }
+ .category-text-title {
+ order: 2;
+ line-height: $line-height-medium;
+ overflow: hidden;
+ margin-bottom: 0.25em;
+ word-wrap: break-word;
+ }
+ &:before {
+ // This avoids an issue with nested links by layering links instead
+ content: "";
+ position: absolute;
+ z-index: 0;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+ }
+
+ .subcategory-box-inner {
+ border: 1px solid $primary-low;
+ border-left: none;
+ padding: 0.5em 0.5em 0.4em 0.5em;
+ }
+
+ &:not(:last-of-type) {
+ margin-bottom: 0.75em;
+ }
+
+ .subcategory {
+ margin-bottom: 0.25em;
+ }
+ }
+
.subcategories {
display: flex;
flex-flow: wrap;
@@ -128,11 +198,14 @@
display: flex;
align-items: center;
@include ellipsis;
- margin-right: 1em;
margin-bottom: 0.6em;
+ .badge-wrapper {
+ overflow: hidden;
+ }
.subcategory-image-placeholder {
display: inline-block;
margin-right: 0.6em;
+ flex: 1 0 auto;
}
.subcategory-link {
min-width: 0;
@@ -140,8 +213,9 @@
}
.logo img {
display: inline-block;
- height: 20px;
- width: 20px;
+ --height: 20px;
+ height: var(--height);
+ width: calc(var(--height) * var(--aspect-ratio));
margin: 0;
}
}
@@ -195,12 +269,24 @@
}
.categories-list .category {
- h3 .d-icon {
- color: $primary-medium;
- height: 0.76em;
- width: 0.76em;
- vertical-align: baseline;
- margin-right: 0.15em;
+ h3,
+ h4 {
+ margin-bottom: 0;
+ .d-icon {
+ color: $primary-high;
+ height: 0.76em;
+ width: 0.76em;
+ vertical-align: baseline;
+ margin-right: 0.1em;
+ }
+ }
+ .category-description {
+ margin-top: 0.5em;
+ overflow: hidden;
+ color: $primary-high;
+ }
+ .category-logo.aspect-image {
+ margin-top: 0.5em;
}
}
@@ -210,3 +296,15 @@
margin-right: 0;
}
}
+
+.category-list.subcategories-with-subcategories {
+ margin-top: 1em;
+ margin-bottom: 0;
+ border-top: 1px solid $primary-low;
+ .category-description {
+ font-size: $font-down-1;
+ }
+ .category-logo.aspect-image {
+ --max-height: 75px;
+ }
+}
diff --git a/app/assets/stylesheets/common/base/d-icon.scss b/app/assets/stylesheets/common/base/d-icon.scss
index bc7c952626..e753171789 100644
--- a/app/assets/stylesheets/common/base/d-icon.scss
+++ b/app/assets/stylesheets/common/base/d-icon.scss
@@ -2,7 +2,7 @@
.d-icon.d-icon-d-muted,
.d-icon.d-icon-d-watching-first,
.d-icon.d-icon-d-watching-first-post {
- color: dark-light-choose($primary-medium, $secondary-medium);
+ color: $primary-high;
}
.d-icon.d-icon-d-tracking,
diff --git a/app/assets/stylesheets/common/base/emoji.scss b/app/assets/stylesheets/common/base/emoji.scss
index 0c59b17842..0ab643ebc5 100644
--- a/app/assets/stylesheets/common/base/emoji.scss
+++ b/app/assets/stylesheets/common/base/emoji.scss
@@ -4,6 +4,19 @@ img.emoji {
vertical-align: middle;
}
+img.emoji.only-emoji {
+ width: 32px;
+ height: 32px;
+ margin: 0.5em 0;
+}
+
+.md-table {
+ img.emoji.only-emoji {
+ width: 20px;
+ height: 20px;
+ }
+}
+
small img.emoji,
sub img.emoji,
sup img.emoji {
diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss
index f68600c08b..00fda9854e 100644
--- a/app/assets/stylesheets/common/base/modal.scss
+++ b/app/assets/stylesheets/common/base/modal.scss
@@ -619,7 +619,22 @@
}
// move-to topic modal
-.move-to-modal {
+.choose-topic-modal {
+ #split-topic-name,
+ #choose-topic-title,
+ #choose-message-title {
+ width: 100%;
+ }
+
+ .category-chooser {
+ margin-bottom: 9px;
+ width: 100% !important;
+ }
+
+ .controls.existing-topic {
+ margin-bottom: 0.75em;
+ }
+
// move to existing topic
.existing-topic {
.radio {
diff --git a/app/assets/stylesheets/common/base/topic-admin-menu.scss b/app/assets/stylesheets/common/base/topic-admin-menu.scss
index 57b81ce1eb..6985f94ad6 100644
--- a/app/assets/stylesheets/common/base/topic-admin-menu.scss
+++ b/app/assets/stylesheets/common/base/topic-admin-menu.scss
@@ -29,6 +29,42 @@
width: 100%;
margin-bottom: 5px;
}
+
+ .header {
+ .close-button {
+ display: none;
+ }
+ }
+
+ @include breakpoint(mobile-extra-large) {
+ width: 100%;
+ padding: 0;
+ padding-bottom: env(safe-area-inset-bottom);
+ z-index: 1000;
+
+ @keyframes slideUp {
+ 0% {
+ transform: translateY(100%);
+ }
+ 100% {
+ transform: translateY(0);
+ }
+ }
+ animation: slideUp 0.3s;
+
+ .header {
+ padding: 10px 0 0 10px;
+ display: flex;
+ justify-content: space-between;
+ .close-button {
+ display: block;
+ background: transparent;
+ }
+ }
+ ul {
+ margin-top: 0;
+ }
+ }
}
.modal-body.feature-topic {
diff --git a/app/assets/stylesheets/common/base/topic.scss b/app/assets/stylesheets/common/base/topic.scss
index a4f1bdc34b..927ee0e250 100644
--- a/app/assets/stylesheets/common/base/topic.scss
+++ b/app/assets/stylesheets/common/base/topic.scss
@@ -111,9 +111,6 @@ a.badge-category {
#edit-title {
flex: 1 1 auto;
}
- .private-message-glyph {
- margin: 5px 5px 0 0;
- }
.category-chooser,
.mini-tag-chooser {
flex: 1 1 49%;
@@ -133,18 +130,37 @@ a.badge-category {
.private-message-glyph {
color: dark-light-choose($primary-low-mid, $secondary-high);
- margin-right: 5px;
}
-.archetype-private_message #topic-title .edit-topic-title .tag-chooser {
- margin-left: 19px;
+.private-message-glyph-wrapper {
+ float: left;
+
+ .private-message-glyph {
+ margin: 0 5px 0 0;
+ display: inline-block;
+ }
}
.private_message {
#topic-title {
.edit-topic-title {
+ position: relative;
+ .private-message-glyph {
+ position: absolute;
+ left: 0.75em;
+ top: 6px;
+ }
#edit-title {
width: calc(100% - 50px);
+ padding-left: 2.25em;
+ }
+ .mini-tag-chooser {
+ margin-left: 0;
+ @include breakpoint(mobile-large, min-width) {
+ .selected-name {
+ max-width: 500px;
+ }
+ }
}
}
}
diff --git a/app/assets/stylesheets/common/base/user.scss b/app/assets/stylesheets/common/base/user.scss
index b1a37b966c..ceb4c80a92 100644
--- a/app/assets/stylesheets/common/base/user.scss
+++ b/app/assets/stylesheets/common/base/user.scss
@@ -594,9 +594,9 @@
.wrapper {
display: inline-block;
position: relative;
- padding: 10px;
border-radius: 3px;
border: 1px solid $primary-low;
+ width: 100%;
}
.backup-codes-area {
@@ -604,11 +604,10 @@
padding: 0;
height: auto;
text-align: center;
- width: 250px;
+ width: 100%;
background: white;
border: 0;
cursor: auto;
- overflow: hidden;
outline: none;
font-family: monospace;
diff --git a/app/assets/stylesheets/desktop/category-list.scss b/app/assets/stylesheets/desktop/category-list.scss
index af20bf971a..50801d9d34 100644
--- a/app/assets/stylesheets/desktop/category-list.scss
+++ b/app/assets/stylesheets/desktop/category-list.scss
@@ -151,4 +151,10 @@
.topic-featured-link {
padding-left: 8px;
}
+
+ .subcategories-with-subcategories {
+ .category-description {
+ display: none;
+ }
+ }
}
diff --git a/app/assets/stylesheets/desktop/components/user-card.scss b/app/assets/stylesheets/desktop/components/user-card.scss
index f4aa977068..7649972e73 100644
--- a/app/assets/stylesheets/desktop/components/user-card.scss
+++ b/app/assets/stylesheets/desktop/components/user-card.scss
@@ -5,7 +5,7 @@
z-index: z("usercard");
&.fixed {
position: fixed;
- z-index: z("composer", "content") + 1;
+ z-index: z("header") + 1;
}
&.docked-card {
z-index: z("header") + 1;
diff --git a/app/assets/stylesheets/desktop/modal.scss b/app/assets/stylesheets/desktop/modal.scss
index c897e3fbb4..9a74a03e4c 100644
--- a/app/assets/stylesheets/desktop/modal.scss
+++ b/app/assets/stylesheets/desktop/modal.scss
@@ -110,13 +110,13 @@
}
}
-.move-to-modal {
+.choose-topic-modal {
.modal-body {
position: relative;
height: 350px;
}
- #move-selected {
+ #choosing-topic {
// prevents content from moving when user selects different move options 525px
// is the same width we set on category edit modal
width: 525px;
@@ -142,18 +142,9 @@
width: 300px;
}
- .category-chooser {
- margin-bottom: 9px;
- }
-
form {
width: 95%;
margin-top: 20px;
- #split-topic-name,
- #choose-topic-title,
- #choose-message-title {
- width: 100%;
- }
.participant-selector {
width: 100%;
diff --git a/app/assets/stylesheets/desktop/topic.scss b/app/assets/stylesheets/desktop/topic.scss
index 279960bf07..87d74169a8 100644
--- a/app/assets/stylesheets/desktop/topic.scss
+++ b/app/assets/stylesheets/desktop/topic.scss
@@ -33,9 +33,7 @@
color: $primary;
}
}
- .private-message-glyph {
- display: none;
- }
+
.remove-featured-link {
float: right;
text-transform: lowercase;
@@ -44,14 +42,6 @@
}
}
-.private-message-glyph {
- float: left;
-}
-
-.private_message #topic-title .private-message-glyph {
- display: inline;
-}
-
.topic-error,
.topic-notice {
padding: 18px;
diff --git a/app/assets/stylesheets/mobile/modal.scss b/app/assets/stylesheets/mobile/modal.scss
index 4cee126a13..33f4efc19b 100644
--- a/app/assets/stylesheets/mobile/modal.scss
+++ b/app/assets/stylesheets/mobile/modal.scss
@@ -43,7 +43,7 @@
font-size: $font-up-4;
}
-#move-selected {
+#choosing-topic {
p {
margin-top: 0;
}
diff --git a/app/assets/stylesheets/mobile/topic-list.scss b/app/assets/stylesheets/mobile/topic-list.scss
index f8091d8759..c0e42a9be7 100644
--- a/app/assets/stylesheets/mobile/topic-list.scss
+++ b/app/assets/stylesheets/mobile/topic-list.scss
@@ -218,7 +218,6 @@
// Category list
// --------------------------------------------------
.categories-list .category-list {
- margin-left: -10px;
margin-bottom: 2em;
td {
@@ -265,10 +264,22 @@
}
.subcategory-list-item.category {
+ display: block;
+ width: calc(100% + 20px);
+ margin: 1.25em 10px 0;
+ border-bottom: none !important;
+ border-top: 1px solid $primary-low !important;
+ &:last-of-type {
+ margin-top: 0;
+ margin-bottom: 1.25em;
+ border-bottom: 1px solid $primary-low !important;
+ }
td:first-of-type {
padding: 12px 0px;
}
-
+ .category-logo.aspect-image {
+ display: none;
+ }
.subcategories {
padding-left: 10px;
}
@@ -283,9 +294,8 @@ tr.category-topic-link {
.category-list-item,
.subcategory-list-item {
- padding: 5px 0 2px 3px;
+ padding: 0 0 0 3px;
border-left: 6px solid;
- border-top: 1px solid;
h3,
h4 {
@@ -326,8 +336,11 @@ tr.category-topic-link {
padding-bottom: 15px;
}
- .subcategories-list td {
- padding-top: 15px;
+ .subcategories-list {
+ border-bottom: none;
+ td {
+ padding-top: 15px;
+ }
}
.topic-list > tbody > tr {
@@ -376,6 +389,13 @@ tr.category-topic-link {
}
}
+.category-list-item {
+ padding: 0.5em 0 0.25em;
+ border-top: 1px solid $primary-low !important;
+ border-bottom: 1px solid $primary-low !important;
+ margin-bottom: 2em;
+}
+
.category-box {
h3 {
margin: 0 0 0.5em 0;
diff --git a/app/assets/stylesheets/mobile/topic.scss b/app/assets/stylesheets/mobile/topic.scss
index c1367e3454..42cddb00bc 100644
--- a/app/assets/stylesheets/mobile/topic.scss
+++ b/app/assets/stylesheets/mobile/topic.scss
@@ -26,13 +26,6 @@
.title-category-wrapper {
margin-top: 5px;
}
- .private-message-glyph {
- display: none;
- }
-}
-
-.private_message #topic-title .private-message-glyph {
- display: inline;
}
.topic-status-info {
diff --git a/app/assets/stylesheets/mobile/user.scss b/app/assets/stylesheets/mobile/user.scss
index 42c630652e..99f1fe16da 100644
--- a/app/assets/stylesheets/mobile/user.scss
+++ b/app/assets/stylesheets/mobile/user.scss
@@ -79,6 +79,11 @@
text-align: center;
}
+ .featured-topic {
+ text-align: center;
+ font-size: $font-0;
+ }
+
.location-and-website {
display: flex;
flex-wrap: wrap;
@@ -127,6 +132,21 @@
margin: 0 auto s(2);
max-width: 700px;
text-align: center;
+
+ ol {
+ margin-left: 25px;
+ }
+
+ ul,
+ ol {
+ display: inline-block;
+ padding-left: 0;
+ text-align: left;
+ }
+ }
+
+ .public-user-fields .public-user-field {
+ text-align: center;
}
}
diff --git a/app/controllers/admin/badges_controller.rb b/app/controllers/admin/badges_controller.rb
index fe67475e26..85937e4653 100644
--- a/app/controllers/admin/badges_controller.rb
+++ b/app/controllers/admin/badges_controller.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'csv'
+
class Admin::BadgesController < Admin::AdminController
def index
@@ -33,6 +35,38 @@ class Admin::BadgesController < Admin::AdminController
def show
end
+ def award
+ end
+
+ def mass_award
+ csv_file = params.permit(:file).fetch(:file, nil)
+ badge = Badge.find_by(id: params[:badge_id])
+ raise Discourse::InvalidParameters if csv_file.try(:tempfile).nil? || badge.nil?
+
+ batch_number = 1
+ batch = []
+
+ File.open(csv_file) do |csv|
+ csv.each_line do |email_line|
+ batch.concat CSV.parse_line(email_line)
+
+ # Split the emails in batches of 200 elements.
+ full_batch = csv.lineno % (BadgeGranter::MAX_ITEMS_FOR_DELTA * batch_number) == 0
+ last_batch_item = full_batch || csv.eof?
+
+ if last_batch_item
+ Jobs.enqueue(:mass_award_badge, user_emails: batch, badge_id: badge.id)
+ batch = []
+ batch_number += 1
+ end
+ end
+ end
+
+ head :ok
+ rescue CSV::MalformedCSVError
+ render_json_error I18n.t('badges.mass_award.errors.invalid_csv'), status: 400
+ end
+
def badge_types
badge_types = BadgeType.all.to_a
render_serialized(badge_types, BadgeTypeSerializer, root: "badge_types")
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb
index 20b97a6f25..864512c451 100644
--- a/app/controllers/admin/reports_controller.rb
+++ b/app/controllers/admin/reports_controller.rb
@@ -88,8 +88,12 @@ class Admin::ReportsController < Admin::AdminController
private
def parse_params(report_params)
- start_date = (report_params[:start_date].present? ? Time.parse(report_params[:start_date]).to_date : 1.days.ago).beginning_of_day
- end_date = (report_params[:end_date].present? ? Time.parse(report_params[:end_date]).to_date : start_date + 30.days).end_of_day
+ begin
+ start_date = (report_params[:start_date].present? ? Time.parse(report_params[:start_date]).to_date : 1.days.ago).beginning_of_day
+ end_date = (report_params[:end_date].present? ? Time.parse(report_params[:end_date]).to_date : start_date + 30.days).end_of_day
+ rescue ArgumentError => e
+ raise Discourse::InvalidParameters.new(e.message)
+ end
facets = nil
if Array === report_params[:facets]
diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb
index d9bb61f6ad..d6bc528c3f 100644
--- a/app/controllers/admin/themes_controller.rb
+++ b/app/controllers/admin/themes_controller.rb
@@ -23,24 +23,12 @@ class Admin::ThemesController < Admin::AdminController
if upload.errors.count > 0
render_json_error upload
else
- # we assume a user intends to make some media public
- # if they are uploading it to a theme component
- mark_upload_insecure(upload) if upload.secure?
render json: { upload_id: upload.id }, status: :created
end
end
end
end
- def mark_upload_insecure(upload)
- upload.update_secure_status(secure_override_value: false)
- StaffActionLogger.new(current_user).log_change_upload_secure_status(
- upload_id: upload.id,
- new_value: false
- )
- Jobs.enqueue(:rebake_posts_for_upload, id: upload.id)
- end
-
def generate_key_pair
require 'sshkey'
k = SSHKey.generate
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 72ad4a1f5c..932de723b6 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -301,7 +301,7 @@ class Admin::UsersController < Admin::AdminController
@user.deactivate(current_user)
StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'), params.slice(:context))
refresh_browser @user
- render body: nil
+ render json: success_json
end
def silence
@@ -423,6 +423,8 @@ class Admin::UsersController < Admin::AdminController
render_serialized(user, AdminDetailedUserSerializer, root: false)
rescue ActiveRecord::RecordInvalid => ex
render json: failed_json.merge(message: ex.message), status: 403
+ rescue DiscourseSingleSignOn::BlankExternalId => ex
+ render json: failed_json.merge(message: I18n.t('sso.blank_id_error')), status: 422
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5dcd7ae2e1..27810f9e71 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -483,7 +483,7 @@ class ApplicationController < ActionController::Base
def post_ids_including_replies
post_ids = params[:post_ids].map(&:to_i)
- post_ids |= PostReply.where(post_id: params[:reply_post_ids]).pluck(:reply_id) if params[:reply_post_ids]
+ post_ids |= PostReply.where(post_id: params[:reply_post_ids]).pluck(:reply_post_id) if params[:reply_post_ids]
post_ids
end
@@ -745,23 +745,22 @@ class ApplicationController < ActionController::Base
return
end
- check_totp = current_user &&
- !request.format.json? &&
- !is_api? &&
- !current_user.anonymous? &&
- ((SiteSetting.enforce_second_factor == 'staff' && current_user.staff?) ||
- SiteSetting.enforce_second_factor == 'all') &&
- !current_user.totp_enabled?
+ return if !current_user
+ return if !should_enforce_2fa?
- if check_totp
- redirect_path = "#{GlobalSetting.relative_url_root}/u/#{current_user.username}/preferences/second-factor"
- if !request.fullpath.start_with?(redirect_path)
- redirect_to path(redirect_path)
- nil
- end
+ redirect_path = "#{GlobalSetting.relative_url_root}/u/#{current_user.username}/preferences/second-factor"
+ if !request.fullpath.start_with?(redirect_path)
+ redirect_to path(redirect_path)
+ nil
end
end
+ def should_enforce_2fa?
+ disqualified_from_2fa_enforcement = request.format.json? || is_api? || current_user.anonymous?
+ enforcing_2fa = ((SiteSetting.enforce_second_factor == 'staff' && current_user.staff?) || SiteSetting.enforce_second_factor == 'all')
+ !disqualified_from_2fa_enforcement && enforcing_2fa && !current_user.has_any_second_factor_methods_enabled?
+ end
+
def block_if_readonly_mode
return if request.fullpath.start_with?(path "/admin/backups")
raise Discourse::ReadOnly.new if !(request.get? || request.head?) && @readonly_mode
diff --git a/app/controllers/export_csv_controller.rb b/app/controllers/export_csv_controller.rb
index 8c72494725..6ac0412551 100644
--- a/app/controllers/export_csv_controller.rb
+++ b/app/controllers/export_csv_controller.rb
@@ -9,6 +9,8 @@ class ExportCsvController < ApplicationController
Jobs.enqueue(:export_csv_file, entity: export_params[:entity], user_id: current_user.id, args: export_params[:args])
StaffActionLogger.new(current_user).log_entity_export(export_params[:entity])
render json: success_json
+ rescue Discourse::InvalidAccess
+ render_json_error I18n.t("csv_export.rate_limit_error")
end
private
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 44dd116c19..534f6924d5 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -675,7 +675,9 @@ class PostsController < ApplicationController
:topic_id,
:archetype,
:category,
+ # TODO remove together with 'targetUsername' deprecations
:target_usernames,
+ :target_recipients,
:reply_to_post_number,
:auto_track,
:typing_duration_msecs,
@@ -684,8 +686,17 @@ class PostsController < ApplicationController
:draft_key
]
- Post.plugin_permitted_create_params.each do |key, plugin|
- permitted << key if plugin.enabled?
+ Post.plugin_permitted_create_params.each do |key, value|
+ if value[:plugin].enabled?
+ permitted << case value[:type]
+ when :string
+ key.to_sym
+ when :array
+ { key => [] }
+ when :hash
+ { key => {} }
+ end
+ end
end
# param munging for WordPress
@@ -749,17 +760,27 @@ class PostsController < ApplicationController
result[:user_agent] = request.user_agent
result[:referrer] = request.env["HTTP_REFERER"]
- if usernames = result[:target_usernames]
- usernames = usernames.split(",")
- groups = Group.messageable(current_user).where('name in (?)', usernames).pluck('name')
- usernames -= groups
- emails = usernames.select { |user| user.match(/@/) }
- usernames -= emails
- result[:target_usernames] = usernames.join(",")
+ if recipients = result[:target_usernames]
+ Discourse.deprecate("`target_usernames` is deprecated, use `target_recipients` instead.", output_in_test: true)
+ else
+ recipients = result[:target_recipients]
+ end
+
+ if recipients
+ recipients = recipients.split(",")
+ groups = Group.messageable(current_user).where('name in (?)', recipients).pluck('name')
+ recipients -= groups
+ emails = recipients.select { |user| user.match(/@/) }
+ recipients -= emails
+ result[:target_usernames] = recipients.join(",")
result[:target_emails] = emails.join(",")
result[:target_group_names] = groups.join(",")
end
+ if (recipients.blank? || result[:target_usernames].blank?) && params[:archetype] == Archetype.private_message
+ Rails.logger.warn("Missing recipients for PM! result: #{result.inspect} | params: #{params.inspect}")
+ end
+
result.permit!
result.to_h
end
diff --git a/app/controllers/reviewable_claimed_topics_controller.rb b/app/controllers/reviewable_claimed_topics_controller.rb
index eb8b0d712b..37f66cd9b5 100644
--- a/app/controllers/reviewable_claimed_topics_controller.rb
+++ b/app/controllers/reviewable_claimed_topics_controller.rb
@@ -6,7 +6,10 @@ class ReviewableClaimedTopicsController < ApplicationController
def create
topic = Topic.find_by(id: params[:reviewable_claimed_topic][:topic_id])
guardian.ensure_can_claim_reviewable_topic!(topic)
- ReviewableClaimedTopic.create!(user_id: current_user.id, topic_id: topic.id)
+ ReviewableClaimedTopic.create(user_id: current_user.id, topic_id: topic.id)
+ render json: success_json
+ rescue ActiveRecord::RecordNotUnique
+ # This is just in case the validation fails under concurrency
render json: success_json
end
diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb
index b7a3fcba17..6103d01220 100644
--- a/app/controllers/session_controller.rb
+++ b/app/controllers/session_controller.rb
@@ -1,13 +1,9 @@
# frozen_string_literal: true
class SessionController < ApplicationController
- class LocalLoginNotAllowed < StandardError; end
- rescue_from LocalLoginNotAllowed do
- render body: nil, status: 500
- end
-
- before_action :check_local_login_allowed, only: %i(create forgot_password email_login email_login_info)
+ before_action :check_local_login_allowed, only: %i(create forgot_password)
before_action :rate_limit_login, only: %i(create email_login)
+ before_action :rate_limit_second_factor_totp, only: %i(create email_login)
skip_before_action :redirect_to_login_if_required
skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login sso_provider destroy one_time_password)
@@ -258,10 +254,6 @@ class SessionController < ApplicationController
end
def create
- unless params[:second_factor_token].blank?
- RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
- end
-
params.require(:login)
params.require(:password)
@@ -293,62 +285,22 @@ class SessionController < ApplicationController
end
if payload = login_error_check(user)
- render json: payload
- else
- if user.security_keys_enabled? && params[:second_factor_token].blank?
- security_key_valid = ::Webauthn::SecurityKeyAuthenticationService.new(
- user,
- params[:security_key_credential],
- challenge: Webauthn.challenge(user, secure_session),
- rp_id: Webauthn.rp_id(user, secure_session),
- origin: Discourse.base_url
- ).authenticate_security_key
- return invalid_security_key(user) if !security_key_valid
- return (user.active && user.email_confirmed?) ? login(user) : not_activated(user)
- end
-
- if user.totp_enabled?
- invalid_second_factor = !user.authenticate_second_factor(params[:second_factor_token], params[:second_factor_method].to_i)
- if (params[:security_key_credential].blank? || !user.security_keys_enabled?) && invalid_second_factor
- return render json: failed_json.merge(
- error: I18n.t("login.invalid_second_factor_code"),
- reason: "invalid_second_factor",
- backup_enabled: user.backup_codes_enabled?,
- multiple_second_factor_methods: user.has_multiple_second_factor_methods?
- )
- end
- elsif user.security_keys_enabled?
- # if we have gotten this far then the user has provided the totp
- # params for a security-key-only account
- return render json: failed_json.merge(
- error: I18n.t("login.invalid_second_factor_code"),
- reason: "invalid_second_factor",
- backup_enabled: user.backup_codes_enabled?,
- multiple_second_factor_methods: user.has_multiple_second_factor_methods?
- )
- end
-
- (user.active && user.email_confirmed?) ? login(user) : not_activated(user)
+ return render json: payload
end
- rescue ::Webauthn::SecurityKeyError => err
- invalid_security_key(user, err.message)
- end
- def invalid_security_key(user, err_message = nil)
- Webauthn.stage_challenge(user, secure_session) if !params[:security_key_credential]
- render json: failed_json.merge(
- error: err_message || I18n.t("login.invalid_security_key"),
- reason: "invalid_security_key",
- backup_enabled: user.backup_codes_enabled?,
- multiple_second_factor_methods: user.has_multiple_second_factor_methods?
- ).merge(Webauthn.allowed_credentials(user, secure_session))
+ if !authenticate_second_factor(user)
+ return render(json: @second_factor_failure_payload)
+ end
+
+ (user.active && user.email_confirmed?) ? login(user) : not_activated(user)
end
def email_login_info
- raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
-
token = params[:token]
matched_token = EmailToken.confirmable(token)
+ user = matched_token&.user
+
+ check_local_login_allowed(user: user, check_login_via_email: true)
if matched_token
response = {
@@ -382,37 +334,14 @@ class SessionController < ApplicationController
end
def email_login
- raise Discourse::NotFound if !SiteSetting.enable_local_logins_via_email
- second_factor_token = params[:second_factor_token]
- second_factor_method = params[:second_factor_method].to_i
- security_key_credential = params[:security_key_credential]
token = params[:token]
matched_token = EmailToken.confirmable(token)
+ user = matched_token&.user
- if security_key_credential.present?
- if matched_token&.user&.security_keys_enabled?
- security_key_valid = ::Webauthn::SecurityKeyAuthenticationService.new(
- matched_token&.user,
- params[:security_key_credential],
- challenge: Webauthn.challenge(matched_token&.user, secure_session),
- rp_id: Webauthn.rp_id(matched_token&.user, secure_session),
- origin: Discourse.base_url
- ).authenticate_security_key
- return invalid_security_key(matched_token&.user) if !security_key_valid
- end
- else
- if matched_token&.user&.totp_enabled?
- if !second_factor_token.present?
- return render json: { error: I18n.t('login.invalid_second_factor_code') }
- elsif !matched_token.user.authenticate_second_factor(second_factor_token, second_factor_method)
- RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
- return render json: { error: I18n.t('login.invalid_second_factor_code') }
- end
- elsif matched_token&.user&.security_keys_enabled?
- # this means the user only has security key enabled
- # but has not provided credentials
- return render json: { error: I18n.t('login.invalid_second_factor_code') }
- end
+ check_local_login_allowed(user: user, check_login_via_email: true)
+
+ if user.present? && !authenticate_second_factor(user)
+ return render(json: @second_factor_failure_payload)
end
if user = EmailToken.confirm(token)
@@ -427,8 +356,6 @@ class SessionController < ApplicationController
end
render json: { error: I18n.t('email_login.invalid_token') }
- rescue ::Webauthn::SecurityKeyError => err
- invalid_security_key(matched_token&.user, err.message)
end
def one_time_password
@@ -499,14 +426,34 @@ class SessionController < ApplicationController
protected
- def check_local_login_allowed
- if SiteSetting.enable_sso || !SiteSetting.enable_local_logins
- raise LocalLoginNotAllowed, "SSO takes over local login or the local login is disallowed."
+ def check_local_login_allowed(user: nil, check_login_via_email: false)
+ # admin-login can get around enabled SSO/disabled local logins
+ return if user&.admin?
+
+ if (check_login_via_email && !SiteSetting.enable_local_logins_via_email) ||
+ SiteSetting.enable_sso ||
+ !SiteSetting.enable_local_logins
+ raise Discourse::InvalidAccess, "SSO takes over local login or the local login is disallowed."
end
end
private
+ def authenticate_second_factor(user)
+ second_factor_authentication_result = user.authenticate_second_factor(params, secure_session)
+ if !second_factor_authentication_result.ok
+ failure_payload = second_factor_authentication_result.to_h
+ if user.security_keys_enabled?
+ Webauthn.stage_challenge(user, secure_session)
+ failure_payload.merge!(Webauthn.allowed_credentials(user, secure_session))
+ end
+ @second_factor_failure_payload = failed_json.merge(failure_payload)
+ return false
+ end
+
+ true
+ end
+
def login_error_check(user)
return failed_to_login(user) if user.suspended?
@@ -589,6 +536,11 @@ class SessionController < ApplicationController
).performed!
end
+ def rate_limit_second_factor_totp
+ return if params[:second_factor_token].blank?
+ RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
+ end
+
def render_sso_error(status:, text:)
@sso_error = text
render status: status, layout: 'no_ember'
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index e7ff3c62b1..5cc8b8121c 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -191,10 +191,10 @@ class TagsController < ::ApplicationController
discourse_expires_in 1.minute
tag_id = params[:tag_id]
- @link = "#{Discourse.base_url}/tags/#{tag_id}"
+ @link = "#{Discourse.base_url}/tag/#{tag_id}"
@description = I18n.t("rss_by_tag", tag: tag_id)
@title = "#{SiteSetting.title} - #{@description}"
- @atom_link = "#{Discourse.base_url}/tags/#{tag_id}.rss"
+ @atom_link = "#{Discourse.base_url}/tag/#{tag_id}.rss"
query = TopicQuery.new(current_user, tags: [tag_id])
latest_results = query.latest_results
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index e5989bc139..b8931c4dd9 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -557,6 +557,8 @@ class TopicsController < ApplicationController
PostDestroyer.new(current_user, first_post, context: params[:context]).destroy
render body: nil
+ rescue Discourse::InvalidAccess
+ render_json_error I18n.t("delete_topic_failed")
end
def recover
diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb
index 0436c8fcc7..04840a36a1 100644
--- a/app/controllers/uploads_controller.rb
+++ b/app/controllers/uploads_controller.rb
@@ -102,6 +102,8 @@ class UploadsController < ApplicationController
sha1 = Upload.sha1_from_base62_encoded(params[:base62])
if upload = Upload.find_by(sha1: sha1)
+ return handle_secure_upload_request(upload, Discourse.store.get_path_for_upload(upload)) if upload.secure? && SiteSetting.secure_media?
+
if Discourse.store.internal?
send_file_local_upload(upload)
else
@@ -115,12 +117,36 @@ class UploadsController < ApplicationController
def show_secure
# do not serve uploads requested via XHR to prevent XSS
return xhr_not_allowed if request.xhr?
+ return render_404 if !Discourse.store.external?
- if SiteSetting.secure_media?
- redirect_to Discourse.store.signed_url_for_path("#{params[:path]}.#{params[:extension]}")
- else
- render_404
+ path_with_ext = "#{params[:path]}.#{params[:extension]}"
+
+ sha1 = File.basename(path_with_ext, File.extname(path_with_ext))
+ # this takes care of optimized image requests
+ sha1 = sha1.partition("_").first if sha1.include?("_")
+
+ upload = Upload.find_by(sha1: sha1)
+ return render_404 if upload.blank?
+
+ signed_secure_url = Discourse.store.signed_url_for_path(path_with_ext)
+ return handle_secure_upload_request(upload, path_with_ext) if SiteSetting.secure_media?
+
+ # we don't want to 404 here if secure media gets disabled
+ # because all posts with secure uploads will show broken media
+ # until rebaked, which could take some time
+ #
+ # if the upload is still secure, that means the ACL is probably still
+ # private, so we don't want to go to the CDN url just yet otherwise we
+ # will get a 403. if the upload is not secure we assume the ACL is public
+ redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url)
+ end
+
+ def handle_secure_upload_request(upload, path_with_ext)
+ if upload.access_control_post_id.present?
+ raise Discourse::InvalidAccess if !guardian.can_see?(upload.access_control_post)
end
+
+ redirect_to Discourse.store.signed_url_for_path(path_with_ext)
end
def metadata
diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 1984932057..36548585f3 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -53,7 +53,9 @@ class Users::OmniauthCallbacksController < ApplicationController
rescue URI::Error
end
- if parsed && (parsed.host == nil || parsed.host == Discourse.current_hostname)
+ if parsed && # Valid
+ (parsed.host == nil || parsed.host == Discourse.current_hostname) && # Local
+ !parsed.path.starts_with?(Discourse.base_uri("/auth/")) # Not /auth URL
@origin = +"#{parsed.path}"
@origin << "?#{parsed.query}" if parsed.query
end
@@ -103,7 +105,7 @@ class Users::OmniauthCallbacksController < ApplicationController
end
def user_found(user)
- if user.totp_enabled?
+ if user.has_any_second_factor_methods_enabled?
@auth_result.omniauth_disallow_totp = true
@auth_result.email = user.email
return
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index d7eba65bc1..774b7ed82c 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -14,7 +14,7 @@ class UsersController < ApplicationController
]
skip_before_action :check_xhr, only: [
- :show, :badges, :password_reset, :update, :account_created,
+ :show, :badges, :password_reset_show, :password_reset_update, :update, :account_created,
:activate_account, :perform_account_activation, :user_preferences_redirect, :avatar,
:my_redirect, :toggle_anon, :admin_login, :confirm_admin, :email_login, :summary,
:feature_topic, :clear_featured_topic
@@ -40,7 +40,8 @@ class UsersController < ApplicationController
:perform_account_activation,
:send_activation_email,
:update_activation_email,
- :password_reset,
+ :password_reset_show,
+ :password_reset_update,
:confirm_email_token,
:email_login,
:admin_login,
@@ -72,6 +73,8 @@ class UsersController < ApplicationController
track_visit_to_user_profile
end
+ response.headers['X-Robots-Tag'] = 'noindex'
+
# This is a hack to get around a Rails issue where values with periods aren't handled correctly
# when used as part of a route.
if params[:external_id] && params[:external_id].ends_with?('.json')
@@ -214,12 +217,13 @@ class UsersController < ApplicationController
user.save!
log_params = {
- revoke_reason: 'user title was same as revoked badge name or custom badge name',
previous_value: previous_title
}
if current_user.staff? && current_user != user
- StaffActionLogger.new(current_user).log_title_revoke(user, log_params)
+ StaffActionLogger
+ .new(current_user)
+ .log_title_revoke(user, log_params.merge(revoke_reason: 'user title was same as revoked badge name or custom badge name'))
else
UserHistory.create!(log_params.merge(target_user_id: user.id, action: UserHistory.actions[:revoke_title]))
end
@@ -501,68 +505,79 @@ class UsersController < ApplicationController
}
end
- def password_reset
+ def password_reset_show
expires_now
-
token = params[:token]
+ password_reset_find_user(token, committing_change: false)
- if EmailToken.valid_token_format?(token)
- @user = if request.put?
- EmailToken.confirm(token)
- else
- EmailToken.confirmable(token)&.user
- end
-
- if @user
- secure_session["password-#{token}"] = @user.id
- else
- user_id = secure_session["password-#{token}"].to_i
- @user = User.find(user_id) if user_id > 0
- end
+ if !@error
+ security_params = {
+ is_developer: UsernameCheckerService.is_developer?(@user.email),
+ admin: @user.admin?,
+ second_factor_required: @user.totp_enabled?,
+ security_key_required: @user.security_keys_enabled?,
+ backup_enabled: @user.backup_codes_enabled?,
+ multiple_second_factor_methods: @user.has_multiple_second_factor_methods?
+ }
end
- second_factor_token = params[:second_factor_token]
- second_factor_method = params[:second_factor_method].to_i
- security_key_credential = params[:security_key_credential]
+ respond_to do |format|
+ format.html do
+ return render 'password_reset', layout: 'no_ember' if @error
- if second_factor_token.present? && UserSecondFactor.methods[second_factor_method]
+ Webauthn.stage_challenge(@user, secure_session)
+ store_preloaded(
+ "password_reset",
+ MultiJson.dump(security_params.merge(Webauthn.allowed_credentials(@user, secure_session)))
+ )
+
+ render 'password_reset'
+ end
+
+ format.json do
+ return render json: { message: @error } if @error
+
+ Webauthn.stage_challenge(@user, secure_session)
+ render json: security_params.merge(Webauthn.allowed_credentials(@user, secure_session))
+ end
+ end
+ end
+
+ def password_reset_update
+ expires_now
+ token = params[:token]
+ password_reset_find_user(token, committing_change: true)
+
+ if params[:second_factor_token].present?
RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
- second_factor_authenticated = @user&.authenticate_second_factor(second_factor_token, second_factor_method)
- elsif security_key_credential.present?
- security_key_authenticated = ::Webauthn::SecurityKeyAuthenticationService.new(
- @user,
- security_key_credential,
- challenge: Webauthn.challenge(@user, secure_session),
- rp_id: Webauthn.rp_id(@user, secure_session),
- origin: Discourse.base_url
- ).authenticate_security_key
end
- second_factor_totp_disabled = !@user&.totp_enabled?
- if second_factor_authenticated || second_factor_totp_disabled || security_key_authenticated
- secure_session["second-factor-#{token}"] = "true"
- end
+ # no point doing anything else if we can't even find
+ # a user from the token
+ if @user
- security_key_disabled = !@user&.security_keys_enabled?
- if security_key_authenticated || security_key_disabled
- secure_session["security-key-#{token}"] = "true"
- end
+ if !secure_session["second-factor-#{token}"]
+ second_factor_authentication_result = @user.authenticate_second_factor(params, secure_session)
+ if !second_factor_authentication_result.ok
+ user_error_key = second_factor_authentication_result.reason == "invalid_security_key" ? :user_second_factors : :security_keys
+ @user.errors.add(user_error_key, :invalid)
+ @error = second_factor_authentication_result.error
+ else
- valid_second_factor = secure_session["second-factor-#{token}"] == "true"
- valid_security_key = secure_session["security-key-#{token}"] == "true"
+ # this must be set because the first call we authenticate e.g. TOTP, and we do
+ # not want to re-authenticate on the second call to change the password as this
+ # will cause a TOTP error saying the code has already been used
+ secure_session["second-factor-#{token}"] = true
+ end
+ end
- if !@user
- @error = I18n.t('password_reset.no_token')
- elsif request.put?
- if !valid_second_factor
- @user.errors.add(:user_second_factors, :invalid)
- @error = I18n.t('login.invalid_second_factor_code')
- elsif !valid_security_key
- @user.errors.add(:security_keys, :invalid)
- @error = I18n.t('login.invalid_security_key')
- elsif @invalid_password = params[:password].blank? || params[:password].size > User.max_password_length
+ if @invalid_password = params[:password].blank? || params[:password].size > User.max_password_length
@user.errors.add(:password, :invalid)
- else
+ end
+
+ # if we have run into no errors then the user is a-ok to
+ # change the password
+ if @user.errors.empty?
@user.password = params[:password]
@user.password_required!
@user.user_auth_tokens.destroy_all
@@ -582,69 +597,45 @@ class UsersController < ApplicationController
respond_to do |format|
format.html do
- if @error
- render layout: 'no_ember'
- else
- Webauthn.stage_challenge(@user, secure_session)
- store_preloaded(
- "password_reset",
- MultiJson.dump(
- {
- is_developer: UsernameCheckerService.is_developer?(@user.email),
- admin: @user.admin?,
- second_factor_required: !valid_second_factor,
- security_key_required: !valid_security_key,
- backup_enabled: @user.backup_codes_enabled?
- }.merge(Webauthn.allowed_credentials(@user, secure_session))
- )
- )
- end
+ return render 'password_reset', layout: 'no_ember' if @error
- return redirect_to(wizard_path) if request.put? && Wizard.user_requires_completion?(@user)
+ Webauthn.stage_challenge(@user, secure_session)
+
+ security_params = {
+ is_developer: UsernameCheckerService.is_developer?(@user.email),
+ admin: @user.admin?,
+ second_factor_required: @user.totp_enabled?,
+ security_key_required: @user.security_keys_enabled?,
+ backup_enabled: @user.backup_codes_enabled?,
+ multiple_second_factor_methods: @user.has_multiple_second_factor_methods?
+ }.merge(Webauthn.allowed_credentials(@user, secure_session))
+
+ store_preloaded("password_reset", MultiJson.dump(security_params))
+
+ return redirect_to(wizard_path) if Wizard.user_requires_completion?(@user)
+
+ render 'password_reset'
end
format.json do
- if request.put?
- if @error || @user&.errors&.any?
- render json: {
- success: false,
- message: @error,
- errors: @user&.errors&.to_hash,
- is_developer: UsernameCheckerService.is_developer?(@user&.email),
- admin: @user&.admin?
- }
- else
- render json: {
- success: true,
- message: @success,
- requires_approval: !Guardian.new(@user).can_access_forum?,
- redirect_to: Wizard.user_requires_completion?(@user) ? wizard_path : nil
- }
- end
+ if @error || @user&.errors&.any?
+ render json: {
+ success: false,
+ message: @error,
+ errors: @user&.errors&.to_hash,
+ is_developer: UsernameCheckerService.is_developer?(@user&.email),
+ admin: @user&.admin?
+ }
else
- if @error || @user&.errors&.any?
- render json: {
- message: @error,
- errors: @user&.errors&.to_hash
- }
- else
- Webauthn.stage_challenge(@user, secure_session) if !valid_security_key && !security_key_credential.present?
- render json: {
- is_developer: UsernameCheckerService.is_developer?(@user.email),
- admin: @user.admin?,
- second_factor_required: !valid_second_factor,
- security_key_required: !valid_security_key,
- backup_enabled: @user.backup_codes_enabled?
- }.merge(Webauthn.allowed_credentials(@user, secure_session))
- end
+ render json: {
+ success: true,
+ message: @success,
+ requires_approval: !Guardian.new(@user).can_access_forum?,
+ redirect_to: Wizard.user_requires_completion?(@user) ? wizard_path : nil
+ }
end
end
end
- rescue ::Webauthn::SecurityKeyError => err
- render json: {
- message: err.message,
- errors: [err.message]
- }
end
def confirm_email_token
@@ -681,89 +672,12 @@ class UsersController < ApplicationController
else
@message = I18n.t("admin_login.errors.unknown_email_address")
end
- elsif (token = params[:token]).present?
- valid_token = EmailToken.valid_token_format?(token)
-
- if valid_token
- if params[:second_factor_token].present?
- RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
- end
-
- email_token_user = EmailToken.confirmable(token)&.user
- totp_enabled = email_token_user&.totp_enabled?
- security_keys_enabled = email_token_user&.security_keys_enabled?
- second_factor_token = params[:second_factor_token]
- second_factor_method = params[:second_factor_method].to_i
- confirm_email = false
- @security_key_required = security_keys_enabled
-
- if security_keys_enabled && params[:security_key_credential].blank?
- Webauthn.stage_challenge(email_token_user, secure_session)
- challenge_and_credentials = Webauthn.allowed_credentials(email_token_user, secure_session)
- @security_key_challenge = challenge_and_credentials[:challenge]
- @security_key_allowed_credential_ids = challenge_and_credentials[:allowed_credential_ids].join(",")
- end
-
- if security_keys_enabled && params[:security_key_credential].present?
- credential = JSON.parse(params[:security_key_credential]).with_indifferent_access
-
- confirm_email = ::Webauthn::SecurityKeyAuthenticationService.new(
- email_token_user,
- credential,
- challenge: Webauthn.challenge(email_token_user, secure_session),
- rp_id: Webauthn.rp_id(email_token_user, secure_session),
- origin: Discourse.base_url
- ).authenticate_security_key
- @message = I18n.t('login.security_key_invalid') if !confirm_email
- elsif security_keys_enabled && second_factor_token.blank?
- confirm_email = false
- @message = I18n.t("login.second_factor_title")
- if totp_enabled
- @second_factor_required = true
- @backup_codes_enabled = true
- end
- else
- confirm_email =
- if totp_enabled
- @second_factor_required = true
- @backup_codes_enabled = true
- @message = I18n.t("login.second_factor_title")
-
- if second_factor_token.present?
- if email_token_user.authenticate_second_factor(second_factor_token, second_factor_method)
- true
- else
- @error = I18n.t("login.invalid_second_factor_code")
- false
- end
- end
- else
- true
- end
- end
-
- if confirm_email
- @user = EmailToken.confirm(token)
-
- if @user && @user.admin?
- log_on_user(@user)
- return redirect_to path("/")
- else
- @message = I18n.t("admin_login.errors.unknown_email_address")
- end
- end
- else
- @message = I18n.t("admin_login.errors.invalid_token")
- end
end
render layout: 'no_ember'
rescue RateLimiter::LimitExceeded
@message = I18n.t("rate_limiter.slow_down")
render layout: 'no_ember'
- rescue ::Webauthn::SecurityKeyError => err
- @message = err.message
- render layout: 'no_ember'
end
def email_login
@@ -1121,7 +1035,7 @@ class UsersController < ApplicationController
user = fetch_user_from_params
if params[:notification_level] == "ignore"
- guardian.ensure_can_ignore_user!(user.id)
+ guardian.ensure_can_ignore_user!(user)
MutedUser.where(user: current_user, muted_user: user).delete_all
ignored_user = IgnoredUser.find_by(user: current_user, ignored_user: user)
if ignored_user.present?
@@ -1130,7 +1044,7 @@ class UsersController < ApplicationController
IgnoredUser.create!(user: current_user, ignored_user: user, expiring_at: Time.parse(params[:expiring_at]))
end
elsif params[:notification_level] == "mute"
- guardian.ensure_can_mute_user!(user.id)
+ guardian.ensure_can_mute_user!(user)
IgnoredUser.where(user: current_user, ignored_user: user).delete_all
MutedUser.find_or_create_by!(user: current_user, muted_user: user)
elsif params[:notification_level] == "normal"
@@ -1432,6 +1346,20 @@ class UsersController < ApplicationController
private
+ def password_reset_find_user(token, committing_change:)
+ if EmailToken.valid_token_format?(token)
+ @user = committing_change ? EmailToken.confirm(token) : EmailToken.confirmable(token)&.user
+ if @user
+ secure_session["password-#{token}"] = @user.id
+ else
+ user_id = secure_session["password-#{token}"].to_i
+ @user = User.find(user_id) if user_id > 0
+ end
+ end
+
+ @error = I18n.t('password_reset.no_token') if !@user
+ end
+
def respond_to_suspicious_request
if suspicious?(params)
render json: {
diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb
index aaead3bf62..96f027880c 100644
--- a/app/controllers/users_email_controller.rb
+++ b/app/controllers/users_email_controller.rb
@@ -56,11 +56,26 @@ class UsersEmailController < ApplicationController
redirect_url = path("/u/confirm-new-email/#{params[:token]}")
- if !@error && @user.totp_enabled? && !@user.authenticate_second_factor(params[:second_factor_token], params[:second_factor_method].to_i)
- RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed!
- flash[:invalid_second_factor] = true
- redirect_to redirect_url
- return
+ RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 3, 1.minute).performed! if params[:second_factor_token].present?
+
+ if !@error
+ # this is needed becase the form posts this field as JSON and it can be a
+ # hash when authenticatong security key.
+ if params[:second_factor_method].to_i == UserSecondFactor.methods[:security_key]
+ begin
+ params[:second_factor_token] = JSON.parse(params[:second_factor_token])
+ rescue JSON::ParserError
+ raise Discourse::InvalidParameters
+ end
+ end
+
+ second_factor_authentication_result = @user.authenticate_second_factor(params, secure_session)
+ if !second_factor_authentication_result.ok
+ flash[:invalid_second_factor] = true
+ flash[:invalid_second_factor_message] = second_factor_authentication_result.error
+ redirect_to redirect_url
+ return
+ end
end
if !@error
@@ -92,15 +107,22 @@ class UsersEmailController < ApplicationController
end
@show_invalid_second_factor_error = flash[:invalid_second_factor]
+ @invalid_second_factor_message = flash[:invalid_second_factor_message]
if !@error
- if @user.totp_enabled?
- @backup_codes_enabled = @user.backup_codes_enabled?
- if params[:show_backup].to_s == "true" && @backup_codes_enabled
- @show_backup_codes = true
- else
+ @backup_codes_enabled = @user.backup_codes_enabled?
+ if params[:show_backup].to_s == "true" && @backup_codes_enabled
+ @show_backup_codes = true
+ else
+ if @user.totp_enabled?
@show_second_factor = true
end
+ if @user.security_keys_enabled?
+ Webauthn.stage_challenge(@user, secure_session)
+ @show_security_key = params[:show_totp].to_s == "true" ? false : true
+ @security_key_challenge = Webauthn.challenge(@user, secure_session)
+ @security_key_allowed_credential_ids = Webauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids]
+ end
end
@to_email = @change_request.new_email
diff --git a/app/helpers/topics_helper.rb b/app/helpers/topics_helper.rb
index 48306aee5b..4f4a56b6e7 100644
--- a/app/helpers/topics_helper.rb
+++ b/app/helpers/topics_helper.rb
@@ -9,13 +9,13 @@ module TopicsHelper
def categories_breadcrumb(topic)
breadcrumb = []
-
category = topic.category
+
if category && !category.uncategorized?
- if (parent = category.parent_category)
- breadcrumb.push url: parent.url, name: parent.name
+ breadcrumb.push(url: category.url, name: category.name)
+ while category = category.parent_category
+ breadcrumb.prepend(url: category.url, name: category.name)
end
- breadcrumb.push url: category.url, name: category.name
end
Plugin::Filter.apply(:topic_categories_breadcrumb, topic, breadcrumb)
diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb
index 2ab94729cf..6798d683a2 100644
--- a/app/jobs/regular/export_csv_file.rb
+++ b/app/jobs/regular/export_csv_file.rb
@@ -198,8 +198,25 @@ module Jobs
end
end
+ if report.modes == [:stacked_chart]
+ header = [:x]
+ data = {}
+
+ report.data.map do |series|
+ header << series[:label]
+ series[:data].each do |datapoint|
+ data[datapoint[:x]] ||= { x: datapoint[:x] }
+ data[datapoint[:x]][series[:label]] = datapoint[:y]
+ end
+ end
+
+ data = data.values
+ else
+ data = report.data
+ end
+
yield header.map { |k| titles[k] || k }
- report.data.each { |row| yield row.values_at(*header).map(&:to_s) }
+ data.each { |row| yield row.values_at(*header).map(&:to_s) }
end
def get_header
diff --git a/app/jobs/regular/mass_award_badge.rb b/app/jobs/regular/mass_award_badge.rb
new file mode 100644
index 0000000000..ae6db5f6f5
--- /dev/null
+++ b/app/jobs/regular/mass_award_badge.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Jobs
+ class MassAwardBadge < ::Jobs::Base
+ def execute(args)
+ badge = Badge.find_by(id: args[:badge_id])
+ users = User.select(:id, :username, :locale).with_email(args[:user_emails])
+
+ return if users.empty? || badge.nil?
+
+ BadgeGranter.mass_grant(badge, users)
+ end
+ end
+end
diff --git a/app/jobs/regular/notify_category_change.rb b/app/jobs/regular/notify_category_change.rb
index fbad7582e4..d6bf0dc0c2 100644
--- a/app/jobs/regular/notify_category_change.rb
+++ b/app/jobs/regular/notify_category_change.rb
@@ -7,7 +7,7 @@ module Jobs
if post&.topic&.visible?
post_alerter = PostAlerter.new
- post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]), include_tag_watchers: false)
+ post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]), include_tag_watchers: false, new_record: false)
post_alerter.notify_first_post_watchers(post, post_alerter.category_watchers(post.topic))
end
end
diff --git a/app/jobs/regular/notify_tag_change.rb b/app/jobs/regular/notify_tag_change.rb
index dff6769de9..d88deaf965 100644
--- a/app/jobs/regular/notify_tag_change.rb
+++ b/app/jobs/regular/notify_tag_change.rb
@@ -7,7 +7,7 @@ module Jobs
if post&.topic&.visible?
post_alerter = PostAlerter.new
- post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]), include_category_watchers: false)
+ post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]), include_category_watchers: false, new_record: false)
post_alerter.notify_first_post_watchers(post, post_alerter.tag_watchers(post.topic))
end
end
diff --git a/app/jobs/regular/update_s3_inventory.rb b/app/jobs/regular/update_s3_inventory.rb
index 2d279268f0..46c34a59bf 100644
--- a/app/jobs/regular/update_s3_inventory.rb
+++ b/app/jobs/regular/update_s3_inventory.rb
@@ -7,7 +7,9 @@ module Jobs
class UpdateS3Inventory < ::Jobs::Base
def execute(args)
- return unless SiteSetting.enable_s3_inventory? && SiteSetting.Upload.enable_s3_uploads
+ return unless SiteSetting.enable_s3_inventory? &&
+ SiteSetting.Upload.enable_s3_uploads &&
+ SiteSetting.s3_configure_inventory_policy
[:upload, :optimized].each do |type|
s3_inventory = S3Inventory.new(Discourse.store.s3_helper, type)
diff --git a/app/jobs/scheduled/badge_grant.rb b/app/jobs/scheduled/badge_grant.rb
index b4fd367c69..8e40939291 100644
--- a/app/jobs/scheduled/badge_grant.rb
+++ b/app/jobs/scheduled/badge_grant.rb
@@ -22,6 +22,7 @@ module Jobs
end
BadgeGranter.revoke_ungranted_titles!
+ UserBadge.ensure_consistency! # Badge granter sometimes uses raw SQL, so hooks do not run. Clean up data
end
end
diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb
index 70d4d14441..cf10d24d44 100644
--- a/app/jobs/scheduled/clean_up_uploads.rb
+++ b/app/jobs/scheduled/clean_up_uploads.rb
@@ -54,6 +54,7 @@ module Jobs
result = Upload.by_users
.where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours")
.where("uploads.created_at < ?", grace_period.hour.ago)
+ .where("uploads.access_control_post_id IS NULL")
.joins(<<~SQL)
LEFT JOIN site_settings ss
ON NULLIF(ss.value, '')::integer = uploads.id
diff --git a/app/jobs/scheduled/drop_backup_schema.rb b/app/jobs/scheduled/drop_backup_schema.rb
new file mode 100644
index 0000000000..1908c72bf7
--- /dev/null
+++ b/app/jobs/scheduled/drop_backup_schema.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Jobs
+ class DropBackupSchema < ::Jobs::Scheduled
+ every 1.day
+
+ def execute(_)
+ BackupRestore::DatabaseRestorer.drop_backup_schema
+ end
+ end
+end
diff --git a/app/models/backup_metadata.rb b/app/models/backup_metadata.rb
index db4bf7e9ad..b7f3a1c4c6 100644
--- a/app/models/backup_metadata.rb
+++ b/app/models/backup_metadata.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class BackupMetadata < ActiveRecord::Base
+ LAST_RESTORE_DATE = "last_restore_date"
+
def self.value_for(name)
where(name: name).pluck_first(:value).presence
end
diff --git a/app/models/badge.rb b/app/models/badge.rb
index 4db471bcbe..7ff7ce0710 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -114,6 +114,8 @@ class Badge < ActiveRecord::Base
after_commit do
SvgSprite.expire_cache
+ UserStat.update_distinct_badge_count if saved_change_to_enabled?
+ UserBadge.ensure_consistency! if saved_change_to_enabled?
end
# fields that can not be edited on system badges
@@ -223,7 +225,7 @@ class Badge < ActiveRecord::Base
def long_description
key = "badges.#{i18n_name}.long_description"
- I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_uri)
+ I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_uri, max_likes_per_day: SiteSetting.max_likes_per_day)
end
def long_description=(val)
@@ -233,7 +235,7 @@ class Badge < ActiveRecord::Base
def description
key = "badges.#{i18n_name}.description"
- I18n.t(key, default: self[:description] || '', base_uri: Discourse.base_uri)
+ I18n.t(key, default: self[:description] || '', base_uri: Discourse.base_uri, max_likes_per_day: SiteSetting.max_likes_per_day)
end
def description=(val)
diff --git a/app/models/category.rb b/app/models/category.rb
index 79ccf38bec..455e50cbe8 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -188,11 +188,16 @@ class Category < ActiveRecord::Base
DB.exec <<~SQL
UPDATE categories c
- SET topic_count = x.topic_count,
- post_count = x.post_count
- FROM (#{topics_with_post_count}) x
+ SET topic_count = COALESCE(x.topic_count, 0),
+ post_count = COALESCE(x.post_count, 0)
+ FROM (
+ SELECT ccc.id as category_id, stats.topic_count, stats.post_count
+ FROM categories ccc
+ LEFT JOIN (#{topics_with_post_count}) stats
+ ON stats.category_id = ccc.id
+ ) x
WHERE x.category_id = c.id
- AND (c.topic_count <> x.topic_count OR c.post_count <> x.post_count)
+ AND (c.topic_count <> COALESCE(x.topic_count, 0) OR c.post_count <> COALESCE(x.post_count, 0))
SQL
# Yes, there are a lot of queries happening below.
@@ -939,6 +944,6 @@ end
# index_categories_on_reviewable_by_group_id (reviewable_by_group_id)
# index_categories_on_search_priority (search_priority)
# index_categories_on_topic_count (topic_count)
-# unique_index_categories_on_name ((COALESCE(parent_category_id, '-1'::integer)), name) UNIQUE
-# unique_index_categories_on_slug ((COALESCE(parent_category_id, '-1'::integer)), slug) UNIQUE WHERE ((slug)::text <> ''::text)
+# unique_index_categories_on_name (COALESCE(parent_category_id, '-1'::integer), name) UNIQUE
+# unique_index_categories_on_slug (COALESCE(parent_category_id, '-1'::integer), slug) UNIQUE WHERE ((slug)::text <> ''::text)
#
diff --git a/app/models/concerns/second_factor_manager.rb b/app/models/concerns/second_factor_manager.rb
index 235242339f..3db84af886 100644
--- a/app/models/concerns/second_factor_manager.rb
+++ b/app/models/concerns/second_factor_manager.rb
@@ -5,6 +5,10 @@ module SecondFactorManager
extend ActiveSupport::Concern
+ SecondFactorAuthenticationResult = Struct.new(
+ :ok, :error, :reason, :backup_enabled, :security_key_enabled, :totp_enabled, :multiple_second_factor_methods
+ )
+
def create_totp(opts = {})
require_rotp
UserSecondFactor.create!({
@@ -67,31 +71,127 @@ module SecondFactorManager
self&.security_keys.where(factor_type: UserSecurityKey.factor_types[:second_factor], enabled: true).exists?
end
+ def has_any_second_factor_methods_enabled?
+ totp_enabled? || security_keys_enabled?
+ end
+
def has_multiple_second_factor_methods?
- security_keys_enabled? && (totp_enabled? || backup_codes_enabled?)
+ security_keys_enabled? && totp_or_backup_codes_enabled?
+ end
+
+ def totp_or_backup_codes_enabled?
+ totp_enabled? || backup_codes_enabled?
+ end
+
+ def only_security_keys_enabled?
+ security_keys_enabled? && !totp_or_backup_codes_enabled?
+ end
+
+ def only_totp_or_backup_codes_enabled?
+ !security_keys_enabled? && totp_or_backup_codes_enabled?
end
def remaining_backup_codes
self&.user_second_factors&.backup_codes&.count
end
- def authenticate_second_factor(token, second_factor_method)
- if second_factor_method == UserSecondFactor.methods[:totp]
- authenticate_totp(token)
- elsif second_factor_method == UserSecondFactor.methods[:backup_codes]
- authenticate_backup_code(token)
- elsif second_factor_method == UserSecondFactor.methods[:security_key]
- # some craziness has happened if we have gotten here...like the user
- # switching around their second factor types then continuing an already
- # started login attempt
- false
+ def authenticate_second_factor(params, secure_session)
+ ok_result = SecondFactorAuthenticationResult.new(true)
+ return ok_result if !security_keys_enabled? && !totp_or_backup_codes_enabled?
+
+ second_factor_token = params[:second_factor_token]
+ second_factor_method = params[:second_factor_method]&.to_i
+
+ if second_factor_method.blank? || UserSecondFactor.methods[second_factor_method].blank?
+ return invalid_second_factor_method_result
end
+
+ if !valid_second_factor_method_for_user?(second_factor_method)
+ return not_enabled_second_factor_method_result
+ end
+
+ case second_factor_method
+ when UserSecondFactor.methods[:totp]
+ return authenticate_totp(second_factor_token) ? ok_result : invalid_totp_or_backup_code_result
+ when UserSecondFactor.methods[:backup_codes]
+ return authenticate_backup_code(second_factor_token) ? ok_result : invalid_totp_or_backup_code_result
+ when UserSecondFactor.methods[:security_key]
+ return authenticate_security_key(secure_session, second_factor_token) ? ok_result : invalid_security_key_result
+ end
+
+ # if we have gotten down to this point without being
+ # OK or invalid something has gone very weird.
+ invalid_second_factor_method_result
+ rescue ::Webauthn::SecurityKeyError => err
+ invalid_security_key_result(err.message)
+ end
+
+ def valid_second_factor_method_for_user?(method)
+ case method
+ when UserSecondFactor.methods[:totp]
+ return totp_enabled?
+ when UserSecondFactor.methods[:backup_codes]
+ return backup_codes_enabled?
+ when UserSecondFactor.methods[:security_key]
+ return security_keys_enabled?
+ end
+ false
+ end
+
+ def authenticate_security_key(secure_session, security_key_credential)
+ ::Webauthn::SecurityKeyAuthenticationService.new(
+ self,
+ security_key_credential,
+ challenge: Webauthn.challenge(self, secure_session),
+ rp_id: Webauthn.rp_id(self, secure_session),
+ origin: Discourse.base_url
+ ).authenticate_security_key
+ end
+
+ def invalid_totp_or_backup_code_result
+ invalid_second_factor_authentication_result(
+ I18n.t("login.invalid_second_factor_code"),
+ "invalid_second_factor"
+ )
+ end
+
+ def invalid_security_key_result(error_message = nil)
+ invalid_second_factor_authentication_result(
+ error_message || I18n.t("login.invalid_security_key"),
+ "invalid_security_key"
+ )
+ end
+
+ def invalid_second_factor_method_result
+ invalid_second_factor_authentication_result(
+ I18n.t("login.invalid_second_factor_method"),
+ "invalid_second_factor_method"
+ )
+ end
+
+ def not_enabled_second_factor_method_result
+ invalid_second_factor_authentication_result(
+ I18n.t("login.not_enabled_second_factor_method"),
+ "not_enabled_second_factor_method"
+ )
+ end
+
+ def invalid_second_factor_authentication_result(error_message, reason)
+ SecondFactorAuthenticationResult.new(
+ false,
+ error_message,
+ reason,
+ backup_codes_enabled?,
+ security_keys_enabled?,
+ totp_enabled?,
+ has_multiple_second_factor_methods?
+ )
end
def generate_backup_codes
codes = []
10.times do
- codes << SecureRandom.hex(8)
+ codes << SecureRandom.hex(16)
end
codes_json = codes.map do |code|
@@ -127,8 +227,9 @@ module SecondFactorManager
codes = self&.user_second_factors&.backup_codes
codes.each do |code|
- stored_code = JSON.parse(code.data)["code_hash"]
- stored_salt = JSON.parse(code.data)["salt"]
+ parsed_data = JSON.parse(code.data)
+ stored_code = parsed_data["code_hash"]
+ stored_salt = parsed_data["salt"]
backup_hash = hash_backup_code(backup_code, stored_salt)
next unless backup_hash == stored_code
diff --git a/app/models/draft.rb b/app/models/draft.rb
index 4f7a03711f..aee066c0e2 100644
--- a/app/models/draft.rb
+++ b/app/models/draft.rb
@@ -7,7 +7,7 @@ class Draft < ActiveRecord::Base
class OutOfSequence < StandardError; end
- def self.set(user, key, sequence, data, owner = nil)
+ def self.set(user, key, sequence, data, owner = nil, retry_not_unique: true)
if SiteSetting.backup_drafts_to_pm_length > 0 && SiteSetting.backup_drafts_to_pm_length < data.length
backup_draft(user, key, sequence, data)
end
@@ -65,13 +65,24 @@ class Draft < ActiveRecord::Base
elsif sequence != current_sequence
raise Draft::OutOfSequence
else
- Draft.create!(
- user_id: user.id,
- draft_key: key,
- data: data,
- sequence: sequence,
- owner: owner
- )
+ begin
+ Draft.create!(
+ user_id: user.id,
+ draft_key: key,
+ data: data,
+ sequence: sequence,
+ owner: owner
+ )
+ rescue ActiveRecord::RecordNotUnique => e
+ # we need this to be fast and with minimal locking, in some cases we can have a race condition
+ # around 2 controller actions calling for draft creation at the exact same time
+ # to avoid complex locking and a distributed mutex, since this is so rare, simply add a single retry
+ if retry_not_unique
+ set(user, key, sequence, data, owenr, retry_not_unique: false)
+ else
+ raise e
+ end
+ end
end
sequence
diff --git a/app/models/group.rb b/app/models/group.rb
index ae696113c2..497333b4fa 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -48,7 +48,6 @@ class Group < ActiveRecord::Base
end
def remove_review_groups
- puts self.id!
Category.where(review_group_id: self.id).update_all(review_group_id: nil)
Category.where(review_group_id: self.id).update_all(review_group_id: nil)
end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index d5f210456c..b442f26208 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -283,4 +283,5 @@ end
# index_invites_on_email_and_invited_by_id (email,invited_by_id)
# index_invites_on_emailed_status (emailed_status)
# index_invites_on_invite_key (invite_key) UNIQUE
+# index_invites_on_invited_by_id (invited_by_id)
#
diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb
index cd50f70b58..7902e366e7 100644
--- a/app/models/optimized_image.rb
+++ b/app/models/optimized_image.rb
@@ -106,7 +106,7 @@ class OptimizedImage < ActiveRecord::Base
# store the optimized image and update its url
File.open(temp_path) do |file|
- url = Discourse.store.store_optimized_image(file, thumbnail)
+ url = Discourse.store.store_optimized_image(file, thumbnail, nil, secure: upload.secure?)
if url.present?
thumbnail.url = url
thumbnail.save
diff --git a/app/models/post.rb b/app/models/post.rb
index 67e30df4c1..db05eb470e 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -505,8 +505,9 @@ class Post < ActiveRecord::Base
end
def with_secure_media?
- return false unless SiteSetting.secure_media?
- topic&.private_message? || SiteSetting.login_required?
+ return false if !SiteSetting.secure_media?
+ SiteSetting.login_required? || \
+ (topic.present? && (topic.private_message? || topic.category&.read_restricted))
end
def hide!(post_action_type_id, reason = nil)
@@ -816,10 +817,10 @@ class Post < ActiveRecord::Base
WITH RECURSIVE breadcrumb(id, level) AS (
SELECT :post_id, 0
UNION
- SELECT reply_id, level + 1
+ SELECT reply_post_id, level + 1
FROM post_replies AS r
JOIN breadcrumb AS b ON (r.post_id = b.id)
- WHERE r.post_id <> r.reply_id
+ WHERE r.post_id <> r.reply_post_id
AND b.level < :max_reply_level
), breadcrumb_with_count AS (
SELECT
@@ -827,8 +828,8 @@ class Post < ActiveRecord::Base
level,
COUNT(*) AS count
FROM post_replies AS r
- JOIN breadcrumb AS b ON (r.reply_id = b.id)
- WHERE r.reply_id <> r.post_id
+ JOIN breadcrumb AS b ON (r.reply_post_id = b.id)
+ WHERE r.reply_post_id <> r.post_id
GROUP BY id, level
)
SELECT id, level
@@ -899,20 +900,21 @@ class Post < ActiveRecord::Base
end
upload_ids |= Upload.where(id: downloaded_images.values).pluck(:id)
-
- disallowed_uploads = []
- if SiteSetting.secure_media? && !self.with_secure_media?
- disallowed_uploads = Upload.where(id: upload_ids, secure: true).pluck(:original_filename)
+ post_uploads = upload_ids.map do |upload_id|
+ { post_id: self.id, upload_id: upload_id }
end
- return disallowed_uploads if disallowed_uploads.count > 0
-
- values = upload_ids.map! { |upload_id| "(#{self.id},#{upload_id})" }.join(",")
PostUpload.transaction do
PostUpload.where(post_id: self.id).delete_all
- if values.size > 0
- DB.exec("INSERT INTO post_uploads (post_id, upload_id) VALUES #{values}")
+ if post_uploads.size > 0
+ PostUpload.insert_all(post_uploads)
+ end
+
+ if SiteSetting.secure_media?
+ Upload.where(id: upload_ids, access_control_post_id: nil).update_all(
+ access_control_post_id: self.id
+ )
end
end
end
@@ -1061,7 +1063,7 @@ class Post < ActiveRecord::Base
def create_reply_relationship_with(post)
return if post.nil? || self.deleted_at.present?
- post_reply = post.post_replies.new(reply_id: id)
+ post_reply = post.post_replies.new(reply_post_id: id)
if post_reply.save
if Topic.visible_post_types.include?(self.post_type)
Post.where(id: post.id).update_all ['reply_count = reply_count + 1']
diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb
index facccc8ebd..4a8148f297 100644
--- a/app/models/post_mover.rb
+++ b/app/models/post_mover.rb
@@ -250,7 +250,7 @@ class PostMover
FROM (
SELECT r.post_id, mp.new_topic_id, COUNT(1) AS moved_reply_count
FROM moved_posts mp
- JOIN post_replies r ON (mp.old_post_id = r.reply_id)
+ JOIN post_replies r ON (mp.old_post_id = r.reply_post_id)
GROUP BY r.post_id, mp.new_topic_id
) x
WHERE x.post_id = p.id AND x.new_topic_id <> p.topic_id
@@ -275,7 +275,7 @@ class PostMover
SET post_id = mp.new_post_id
FROM moved_posts mp
WHERE mp.old_post_id <> mp.new_post_id AND pr.post_id = mp.old_post_id AND
- EXISTS (SELECT 1 FROM moved_posts mr WHERE mr.new_post_id = pr.reply_id)
+ EXISTS (SELECT 1 FROM moved_posts mr WHERE mr.new_post_id = pr.reply_post_id)
SQL
end
@@ -283,8 +283,8 @@ class PostMover
DB.exec <<~SQL
DELETE
FROM post_replies pr USING moved_posts mp, posts p, posts r
- WHERE (pr.reply_id = mp.old_post_id OR pr.post_id = mp.old_post_id) AND
- p.id = pr.post_id AND r.id = pr.reply_id AND p.topic_id <> r.topic_id
+ WHERE (pr.reply_post_id = mp.old_post_id OR pr.post_id = mp.old_post_id) AND
+ p.id = pr.post_id AND r.id = pr.reply_post_id AND p.topic_id <> r.topic_id
SQL
end
diff --git a/app/models/post_reply.rb b/app/models/post_reply.rb
index 37654e6753..9cc9be2692 100644
--- a/app/models/post_reply.rb
+++ b/app/models/post_reply.rb
@@ -1,10 +1,14 @@
# frozen_string_literal: true
class PostReply < ActiveRecord::Base
- belongs_to :post
- belongs_to :reply, class_name: 'Post'
+ self.ignored_columns = %w{
+ reply_id
+ }
- validates_uniqueness_of :reply_id, scope: :post_id
+ belongs_to :post
+ belongs_to :reply, foreign_key: :reply_post_id, class_name: 'Post'
+
+ validates_uniqueness_of :reply_post_id, scope: :post_id
validate :ensure_same_topic
private
@@ -23,13 +27,13 @@ end
#
# Table name: post_replies
#
-# post_id :integer
-# reply_id :integer
-# created_at :datetime not null
-# updated_at :datetime not null
+# post_id :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+# reply_post_id :integer
#
# Indexes
#
-# index_post_replies_on_post_id_and_reply_id (post_id,reply_id) UNIQUE
-# index_post_replies_on_reply_id (reply_id)
+# index_post_replies_on_post_id_and_reply_post_id (post_id,reply_post_id) UNIQUE
+# index_post_replies_on_reply_post_id (reply_post_id)
#
diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb
index 287d5a5f92..c12bfae39f 100644
--- a/app/models/reviewable.rb
+++ b/app/models/reviewable.rb
@@ -138,23 +138,47 @@ class Reviewable < ActiveRecord::Base
potential_spam: potential_spam
)
reviewable.created_new!
- reviewable.save!
- reviewable
- rescue ActiveRecord::RecordNotUnique
+ if target.blank?
+ # If there is no target there's no chance of a conflict
+ reviewable.save!
+ else
+ # In this case, a reviewable might already exist for this (type, target_id) index.
+ # ActiveRecord can only validate indexes using a SELECT before the INSERT which
+ # is not safe under concurrency. Instead, we perform an UPDATE on the status, and return
+ # the previous value. We then know:
+ #
+ # a) if a previous row existed
+ # b) if it was changed
+ #
+ # And that allows us to complete our logic.
- row_count = DB.exec(<<~SQL, status: statuses[:pending], id: target.id, type: target.class.name)
- UPDATE reviewables
- SET status = :status
- WHERE status <> :status
- AND target_id = :id
- AND target_type = :type
- SQL
+ update_args = {
+ status: statuses[:pending],
+ id: target.id,
+ type: target.class.name,
+ potential_spam: potential_spam == true ? true : nil
+ }
- where(target: target).update_all(potential_spam: true) if potential_spam
+ row = DB.query_single(<<~SQL, update_args)
+ UPDATE reviewables
+ SET status = :status,
+ potential_spam = COALESCE(:potential_spam, reviewables.potential_spam)
+ FROM reviewables AS old_reviewables
+ WHERE reviewables.target_id = :id
+ AND reviewables.target_type = :type
+ RETURNING old_reviewables.status
+ SQL
+ old_status = row[0]
+
+ if old_status.blank?
+ reviewable.save!
+ else
+ reviewable = find_by(target: target)
+ reviewable.log_history(:transitioned, created_by) if old_status != statuses[:pending]
+ end
+ end
- reviewable = find_by(target: target)
- reviewable.log_history(:transitioned, created_by) if row_count > 0
reviewable
end
@@ -604,7 +628,7 @@ protected
return partial_result if status == :all
if status == :reviewed
- partial_result.where(status: [statuses[:approved], statuses[:rejected], statuses[:ignored]])
+ partial_result.where(status: statuses.except(:pending).values)
else
partial_result.where(status: statuses[status])
end
diff --git a/app/models/reviewable_claimed_topic.rb b/app/models/reviewable_claimed_topic.rb
index 216aa6543c..dc54c4abf1 100644
--- a/app/models/reviewable_claimed_topic.rb
+++ b/app/models/reviewable_claimed_topic.rb
@@ -3,6 +3,7 @@
class ReviewableClaimedTopic < ActiveRecord::Base
belongs_to :topic
belongs_to :user
+ validates_uniqueness_of :topic
def self.claimed_hash(topic_ids)
result = {}
diff --git a/app/models/reviewable_flagged_post.rb b/app/models/reviewable_flagged_post.rb
index 5f3a3a6d95..340d63c8b5 100644
--- a/app/models/reviewable_flagged_post.rb
+++ b/app/models/reviewable_flagged_post.rb
@@ -228,7 +228,7 @@ class ReviewableFlaggedPost < Reviewable
def perform_delete_and_agree_replies(performed_by, args)
result = agree(performed_by, args)
- PostDestroyer.delete_with_replies(performed_by, post, self, defer_reply_flags: false)
+ PostDestroyer.delete_with_replies(performed_by, post, self)
result
end
diff --git a/app/models/reviewable_score.rb b/app/models/reviewable_score.rb
index 1fd9d2d7eb..13265826c8 100644
--- a/app/models/reviewable_score.rb
+++ b/app/models/reviewable_score.rb
@@ -14,6 +14,13 @@ class ReviewableScore < ActiveRecord::Base
)
end
+ # When extending post action flags, we need to call this method in order to
+ # get the latests flags.
+ def self.reload_types
+ @types = nil
+ types
+ end
+
def self.statuses
@statuses ||= Enum.new(
pending: 0,
@@ -62,7 +69,7 @@ class ReviewableScore < ActiveRecord::Base
# if > 5 flags => (agreed flags / total flags) * 5.0
def self.user_accuracy_bonus(user)
user_stat = user&.user_stat
- return 0.0 if user_stat.blank?
+ return 0.0 if user_stat.blank? || user.bot?
calc_user_accuracy_bonus(user_stat.flags_agreed, user_stat.flags_disagreed)
end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 935cea317c..2d695c3521 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -144,7 +144,7 @@ class Tag < ActiveRecord::Base
end
def full_url
- "#{Discourse.base_url}/tags/#{self.name}"
+ "#{Discourse.base_url}/tag/#{self.name}"
end
def index_search
@@ -192,6 +192,7 @@ end
# created_at :datetime not null
# updated_at :datetime not null
# pm_topic_count :integer default(0), not null
+# target_tag_id :integer
#
# Indexes
#
diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb
index 9a5fb66a3b..49ffeb9751 100644
--- a/app/models/topic_tracking_state.rb
+++ b/app/models/topic_tracking_state.rb
@@ -306,9 +306,10 @@ class TopicTrackingState
#{tags_filter}
topics.deleted_at IS NULL AND
#{category_filter}
- (category_users.id IS NULL OR
- last_read_post_number IS NOT NULL OR
- category_users.notification_level <> #{CategoryUser.notification_levels[:muted]})
+ NOT (
+ last_read_post_number IS NULL AND
+ COALESCE(category_users.notification_level, #{CategoryUser.default_notification_level}) = #{CategoryUser.notification_levels[:muted]}
+ )
SQL
if opts[:topic_id]
diff --git a/app/models/trust_level3_requirements.rb b/app/models/trust_level3_requirements.rb
index 3092259aaf..d40283dbf8 100644
--- a/app/models/trust_level3_requirements.rb
+++ b/app/models/trust_level3_requirements.rb
@@ -104,18 +104,33 @@ class TrustLevel3Requirements
def penalty_counts
args = {
user_id: @user.id,
+ system_user_id: Discourse.system_user.id,
silence_user: UserHistory.actions[:silence_user],
+ unsilence_user: UserHistory.actions[:unsilence_user],
suspend_user: UserHistory.actions[:suspend_user],
+ unsuspend_user: UserHistory.actions[:unsuspend_user],
since: FORGIVENESS_PERIOD.ago
}
sql = <<~SQL
SELECT
- SUM(CASE WHEN action = :silence_user THEN 1 ELSE 0 END) AS silence_count,
- SUM(CASE WHEN action = :suspend_user THEN 1 ELSE 0 END) AS suspend_count
+ SUM(
+ CASE
+ WHEN action = :silence_user THEN 1
+ WHEN action = :unsilence_user AND acting_user_id != :system_user_id THEN -1
+ ELSE 0
+ END
+ ) AS silence_count,
+ SUM(
+ CASE
+ WHEN action = :suspend_user THEN 1
+ WHEN action = :unsuspend_user AND acting_user_id != :system_user_id THEN -1
+ ELSE 0
+ END
+ ) AS suspend_count
FROM user_histories AS uh
WHERE uh.target_user_id = :user_id
- AND uh.action IN (:silence_user, :suspend_user)
+ AND uh.action IN (:silence_user, :suspend_user, :unsilence_user, :unsuspend_user)
AND uh.created_at > :since
SQL
diff --git a/app/models/upload.rb b/app/models/upload.rb
index a6842ce9a9..2c2977a974 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -11,6 +11,7 @@ class Upload < ActiveRecord::Base
URL_REGEX ||= /(\/original\/\dX[\/\.\w]*\/([a-zA-Z0-9]+)[\.\w]*)/
belongs_to :user
+ belongs_to :access_control_post, class_name: 'Post'
has_many :post_uploads, dependent: :destroy
has_many :posts, through: :post_uploads
@@ -232,32 +233,12 @@ class Upload < ActiveRecord::Base
def update_secure_status(secure_override_value: nil)
return false if self.for_theme || self.for_site_setting
- mark_secure = secure_override_value.nil? ? should_be_secure? : secure_override_value
+ mark_secure = secure_override_value.nil? ? UploadSecurity.new(self).should_be_secure? : secure_override_value
self.update_column("secure", mark_secure)
Discourse.store.update_upload_ACL(self) if Discourse.store.external?
end
- def should_be_secure?
- mark_secure = false
- if FileHelper.is_supported_media?(self.original_filename)
- if SiteSetting.secure_media?
- mark_secure = true if SiteSetting.login_required?
- unless SiteSetting.login_required?
- # first post associated with upload determines secure status
- # i.e. an already public upload will stay public even if added to a new PM
- first_post_with_upload = self.posts.order(sort_order: :asc).first
- mark_secure = first_post_with_upload ? first_post_with_upload.with_secure_media? : false
- end
- else
- mark_secure = false
- end
- else
- mark_secure = SiteSetting.prevent_anons_from_downloading_files?
- end
- mark_secure
- end
-
def self.migrate_to_new_scheme(limit: nil)
problems = []
@@ -392,30 +373,38 @@ end
#
# Table name: uploads
#
-# id :integer not null, primary key
-# user_id :integer not null
-# original_filename :string not null
-# filesize :integer not null
-# width :integer
-# height :integer
-# url :string not null
-# created_at :datetime not null
-# updated_at :datetime not null
-# sha1 :string(40)
-# origin :string(1000)
-# retain_hours :integer
-# extension :string(10)
-# thumbnail_width :integer
-# thumbnail_height :integer
-# etag :string
-# secure :boolean default(FALSE), not null
+# id :integer not null, primary key
+# user_id :integer not null
+# original_filename :string not null
+# filesize :integer not null
+# width :integer
+# height :integer
+# url :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# sha1 :string(40)
+# origin :string(1000)
+# retain_hours :integer
+# extension :string(10)
+# thumbnail_width :integer
+# thumbnail_height :integer
+# etag :string
+# secure :boolean default(FALSE), not null
+# access_control_post_id :bigint
+# original_sha1 :string
#
# Indexes
#
-# index_uploads_on_etag (etag)
-# index_uploads_on_extension (lower((extension)::text))
-# index_uploads_on_id_and_url (id,url)
-# index_uploads_on_sha1 (sha1) UNIQUE
-# index_uploads_on_url (url)
-# index_uploads_on_user_id (user_id)
+# index_uploads_on_access_control_post_id (access_control_post_id)
+# index_uploads_on_etag (etag)
+# index_uploads_on_extension (lower((extension)::text))
+# index_uploads_on_id_and_url (id,url)
+# index_uploads_on_original_sha1 (original_sha1)
+# index_uploads_on_sha1 (sha1) UNIQUE
+# index_uploads_on_url (url)
+# index_uploads_on_user_id (user_id)
+#
+# Foreign Keys
+#
+# fk_rails_... (access_control_post_id => posts.id)
#
diff --git a/app/models/user.rb b/app/models/user.rb
index b5247b85fd..2a6f303c92 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -20,8 +20,14 @@ class User < ActiveRecord::Base
has_many :user_actions
has_many :post_actions
- has_many :user_badges, -> { where('user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)') }, dependent: :destroy
+ DEFAULT_FEATURED_BADGE_COUNT = 3
+
+ has_many :user_badges, -> { for_enabled_badges }, dependent: :destroy
has_many :badges, through: :user_badges
+ has_many :default_featured_user_badges,
+ -> { for_enabled_badges.grouped_with_count.where("featured_rank <= ?", DEFAULT_FEATURED_BADGE_COUNT) },
+ class_name: "UserBadge"
+
has_many :email_logs, dependent: :delete_all
has_many :incoming_emails, dependent: :delete_all
has_many :post_timings
@@ -83,6 +89,9 @@ class User < ActiveRecord::Base
has_many :muted_user_records, class_name: 'MutedUser'
has_many :muted_users, through: :muted_user_records
+ has_many :ignored_user_records, class_name: 'IgnoredUser'
+ has_many :ignored_users, through: :ignored_user_records
+
has_many :api_keys, dependent: :destroy
has_many :push_subscriptions, dependent: :destroy
@@ -468,9 +477,19 @@ class User < ActiveRecord::Base
@unread_total_notifications = nil
@unread_pms = nil
@user_fields = nil
+ @ignored_user_ids = nil
+ @muted_user_ids = nil
super
end
+ def ignored_user_ids
+ @ignored_user_ids ||= ignored_users.pluck(:id)
+ end
+
+ def muted_user_ids
+ @muted_user_ids ||= muted_users.pluck(:id)
+ end
+
def unread_notifications_of_type(notification_type)
# perf critical, much more efficient than AR
sql = <<~SQL
@@ -945,31 +964,15 @@ class User < ActiveRecord::Base
end
def badge_count
- user_badges.select('distinct badge_id').count
+ user_stat&.distinct_badge_count
end
- def featured_user_badges(limit = 3)
- tl_badge_ids = Badge.trust_level_badge_ids
-
- query = user_badges
- .group(:badge_id)
- .select(UserBadge.attribute_names.map { |x| "MAX(user_badges.#{x}) AS #{x}" },
- 'COUNT(*) AS "count"',
- 'MAX(badges.badge_type_id) AS badges_badge_type_id',
- 'MAX(badges.grant_count) AS badges_grant_count')
- .joins(:badge)
- .order('badges_badge_type_id ASC, badges_grant_count ASC, badge_id DESC')
- .includes(:user, :granted_by, { badge: :badge_type }, post: :topic)
-
- tl_badge = query.where("user_badges.badge_id IN (:tl_badge_ids)",
- tl_badge_ids: tl_badge_ids)
- .limit(1)
-
- other_badges = query.where("user_badges.badge_id NOT IN (:tl_badge_ids)",
- tl_badge_ids: tl_badge_ids)
- .limit(limit)
-
- (tl_badge + other_badges).take(limit)
+ def featured_user_badges(limit = DEFAULT_FEATURED_BADGE_COUNT)
+ if limit == DEFAULT_FEATURED_BADGE_COUNT
+ default_featured_user_badges
+ else
+ user_badges.grouped_with_count.where("featured_rank <= ?", limit)
+ end
end
def self.count_by_signup_date(start_date = nil, end_date = nil, group_id = nil)
diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb
index e915a7c6e2..5f4a3fcd07 100644
--- a/app/models/user_badge.rb
+++ b/app/models/user_badge.rb
@@ -7,6 +7,16 @@ class UserBadge < ActiveRecord::Base
belongs_to :notification, dependent: :destroy
belongs_to :post
+ scope :grouped_with_count, -> {
+ group(:badge_id, :user_id)
+ .select(UserBadge.attribute_names.map { |x| "MAX(user_badges.#{x}) AS #{x}" },
+ 'COUNT(*) AS "count"')
+ .order('MAX(featured_rank) ASC')
+ .includes(:user, :granted_by, { badge: :badge_type }, post: :topic)
+ }
+
+ scope :for_enabled_badges, -> { where('user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)') }
+
validates :badge_id,
presence: true,
uniqueness: { scope: :user_id },
@@ -18,14 +28,59 @@ class UserBadge < ActiveRecord::Base
after_create do
Badge.increment_counter 'grant_count', self.badge_id
+ UserStat.update_distinct_badge_count self.user_id
+ UserBadge.update_featured_ranks! self.user_id
DiscourseEvent.trigger(:user_badge_granted, self.badge_id, self.user_id)
end
after_destroy do
Badge.decrement_counter 'grant_count', self.badge_id
+ UserStat.update_distinct_badge_count self.user_id
+ UserBadge.update_featured_ranks! self.user_id
DiscourseEvent.trigger(:user_badge_removed, self.badge_id, self.user_id)
end
+ def self.ensure_consistency!
+ self.update_featured_ranks!
+ end
+
+ def self.update_featured_ranks!(user_id = nil)
+ query = <<~SQL
+ WITH featured_tl_badge AS -- Find the best trust level badge for each user
+ (
+ SELECT user_id, max(badge_id) as badge_id
+ FROM user_badges
+ WHERE badge_id IN (1,2,3,4)
+ #{"AND user_id = #{user_id.to_i}" if user_id}
+ GROUP BY user_id
+ ),
+ ranks AS ( -- Take all user badges, group by user_id and badge_id, and calculate a rank for each one
+ SELECT
+ user_badges.user_id,
+ user_badges.badge_id,
+ RANK() OVER (
+ PARTITION BY user_badges.user_id -- Do a separate rank for each user
+ ORDER BY BOOL_OR(badges.enabled) DESC, -- Disabled badges last
+ MAX(featured_tl_badge.user_id) NULLS LAST, -- Best tl badge first
+ CASE WHEN user_badges.badge_id IN (1,2,3,4) THEN 1 ELSE 0 END ASC, -- Non-featured tl badges last
+ MAX(badges.badge_type_id) ASC,
+ MAX(badges.grant_count) ASC,
+ user_badges.badge_id DESC
+ ) rank_number
+ FROM user_badges
+ INNER JOIN badges ON badges.id = user_badges.badge_id
+ LEFT JOIN featured_tl_badge ON featured_tl_badge.user_id = user_badges.user_id AND featured_tl_badge.badge_id = user_badges.badge_id
+ #{"WHERE user_badges.user_id = #{user_id.to_i}" if user_id}
+ GROUP BY user_badges.user_id, user_badges.badge_id
+ )
+ -- Now use that data to update the featured_rank column
+ UPDATE user_badges SET featured_rank = rank_number
+ FROM ranks WHERE ranks.badge_id = user_badges.badge_id AND ranks.user_id = user_badges.user_id AND featured_rank IS DISTINCT FROM rank_number
+ SQL
+
+ DB.exec query
+ end
+
private
def single_grant_badge?
@@ -45,6 +100,7 @@ end
# post_id :integer
# notification_id :integer
# seq :integer default(0), not null
+# featured_rank :integer
#
# Indexes
#
diff --git a/app/models/user_custom_field.rb b/app/models/user_custom_field.rb
index 0fb77e5f63..bba98cd9b4 100644
--- a/app/models/user_custom_field.rb
+++ b/app/models/user_custom_field.rb
@@ -17,5 +17,7 @@ end
#
# Indexes
#
-# index_user_custom_fields_on_user_id_and_name (user_id,name)
+# idx_user_custom_fields_last_reminded_at (name,user_id) UNIQUE WHERE ((name)::text = 'last_reminded_at'::text)
+# idx_user_custom_fields_remind_assigns_frequency (name,user_id) UNIQUE WHERE ((name)::text = 'remind_assigns_frequency'::text)
+# index_user_custom_fields_on_user_id_and_name (user_id,name)
#
diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb
index 14f94b4cd0..ad2701e70d 100644
--- a/app/models/user_profile.rb
+++ b/app/models/user_profile.rb
@@ -167,13 +167,14 @@ end
# profile_background_upload_id :integer
# card_background_upload_id :integer
# granted_title_badge_id :bigint
+# featured_topic_id :integer
#
# Indexes
#
-# index_user_profiles_on_bio_cooked_version (bio_cooked_version)
-# index_user_profiles_on_card_background (card_background)
-# index_user_profiles_on_granted_title_badge_id (granted_title_badge_id)
-# index_user_profiles_on_profile_background (profile_background)
+# index_user_profiles_on_bio_cooked_version (bio_cooked_version)
+# index_user_profiles_on_card_background_upload_id (card_background_upload_id)
+# index_user_profiles_on_granted_title_badge_id (granted_title_badge_id)
+# index_user_profiles_on_profile_background_upload_id (profile_background_upload_id)
#
# Foreign Keys
#
diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb
index e76c8c113a..cda2c1513a 100644
--- a/app/models/user_stat.rb
+++ b/app/models/user_stat.rb
@@ -6,6 +6,7 @@ class UserStat < ActiveRecord::Base
def self.ensure_consistency!(last_seen = 1.hour.ago)
reset_bounce_scores
+ update_distinct_badge_count
update_view_counts(last_seen)
update_first_unread(last_seen)
end
@@ -126,6 +127,29 @@ class UserStat < ActiveRecord::Base
SQL
end
+ def self.update_distinct_badge_count(user_id = nil)
+ sql = <<~SQL
+ UPDATE user_stats
+ SET distinct_badge_count = x.distinct_badge_count
+ FROM (
+ SELECT users.id user_id, COUNT(distinct user_badges.badge_id) distinct_badge_count
+ FROM users
+ LEFT JOIN user_badges ON user_badges.user_id = users.id
+ AND (user_badges.badge_id IN (SELECT id FROM badges WHERE enabled))
+ GROUP BY users.id
+ ) x
+ WHERE user_stats.user_id = x.user_id AND user_stats.distinct_badge_count <> x.distinct_badge_count
+ SQL
+
+ sql = sql + " AND user_stats.user_id = #{user_id.to_i}" if user_id
+
+ DB.exec sql
+ end
+
+ def update_distinct_badge_count
+ self.class.update_distinct_badge_count(self.user_id)
+ end
+
# topic_reply_count is a count of posts in other users' topics
def update_topic_reply_count
self.topic_reply_count = Topic
@@ -200,4 +224,5 @@ end
# flags_disagreed :integer default(0), not null
# flags_ignored :integer default(0), not null
# first_unread_at :datetime not null
+# distinct_badge_count :integer default(0), not null
#
diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb
index 6a080c9763..540422a071 100644
--- a/app/serializers/basic_category_serializer.rb
+++ b/app/serializers/basic_category_serializer.rb
@@ -43,10 +43,18 @@ class BasicCategorySerializer < ApplicationSerializer
object.uncategorized? ? I18n.t('uncategorized_category_name', locale: SiteSetting.default_locale) : object.name
end
+ def description_text
+ object.uncategorized? ? I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) : object.description_text
+ end
+
def description
object.uncategorized? ? I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) : object.description
end
+ def description_excerpt
+ object.uncategorized? ? I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) : object.description_excerpt
+ end
+
def can_edit
true
end
diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb
index d7c2ef58fe..06acfc0c4f 100644
--- a/app/serializers/site_serializer.rb
+++ b/app/serializers/site_serializer.rb
@@ -54,17 +54,16 @@ class SiteSerializer < ApplicationSerializer
def post_action_types
cache_fragment("post_action_types_#{I18n.locale}") do
- types = PostActionType.types.values.map { |id| PostActionType.new(id: id) }
+ types = ordered_flags(PostActionType.types.values)
ActiveModel::ArraySerializer.new(types).as_json
end
end
def topic_flag_types
cache_fragment("post_action_flag_types_#{I18n.locale}") do
- types = PostActionType.topic_flag_types.values.map { |id| PostActionType.new(id: id) }
+ types = ordered_flags(PostActionType.topic_flag_types.values)
ActiveModel::ArraySerializer.new(types, each_serializer: TopicFlagTypeSerializer).as_json
end
-
end
def default_archetype
@@ -163,4 +162,16 @@ class SiteSerializer < ApplicationSerializer
scope.can_create_shared_draft?
end
+ private
+
+ def ordered_flags(flags)
+ notify_moderators_type = PostActionType.flag_types[:notify_moderators]
+ types = flags
+
+ if notify_moderators_flag = types.index(notify_moderators_type)
+ types.insert(types.length, types.delete_at(notify_moderators_flag))
+ end
+
+ types.map { |id| PostActionType.new(id: id) }
+ end
end
diff --git a/app/serializers/upload_serializer.rb b/app/serializers/upload_serializer.rb
index 95fd83cf5e..938ab5b0ad 100644
--- a/app/serializers/upload_serializer.rb
+++ b/app/serializers/upload_serializer.rb
@@ -13,4 +13,9 @@ class UploadSerializer < ApplicationSerializer
:short_url,
:retain_hours,
:human_filesize
+
+ def url
+ return object.url if !object.secure || !SiteSetting.secure_media?
+ UrlHelper.cook_url(object.url, secure: object.secure)
+ end
end
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index 43bbc56976..2eb9ae16dd 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -255,19 +255,21 @@ class UserSerializer < BasicUserSerializer
end
def ignored
- IgnoredUser.where(user_id: scope.user&.id, ignored_user_id: object.id).exists?
+ scope_ignored_user_ids = scope.user&.ignored_user_ids || []
+ scope_ignored_user_ids.include?(object.id)
end
def muted
- MutedUser.where(user_id: scope.user&.id, muted_user_id: object.id).exists?
+ scope_muted_user_ids = scope.user&.muted_user_ids || []
+ scope_muted_user_ids.include?(object.id)
end
def can_mute_user
- scope.can_mute_user?(object.id)
+ scope.can_mute_user?(object)
end
def can_ignore_user
- scope.can_ignore_user?(object.id)
+ scope.can_ignore_user?(object)
end
# Needed because 'send_private_message_to_user' will always return false
diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb
index 25462cf467..620e3cd0b1 100644
--- a/app/services/badge_granter.rb
+++ b/app/services/badge_granter.rb
@@ -12,6 +12,27 @@ class BadgeGranter
BadgeGranter.new(badge, user, opts).grant
end
+ def self.mass_grant(badge, users)
+ return unless badge.enabled?
+
+ system_user_id = Discourse.system_user.id
+ user_badges = users.map { |u| { badge_id: badge.id, user_id: u.id, granted_by_id: system_user_id, granted_at: Time.now } }
+ granted_badges = UserBadge.insert_all(user_badges, returning: %i[user_id])
+
+ users.each do |user|
+ notification = send_notification(user.id, user.username, user.locale, badge)
+
+ DB.exec(
+ "UPDATE user_badges SET notification_id = :notification_id WHERE notification_id IS NULL AND user_id = :user_id AND badge_id = :badge_id",
+ notification_id: notification.id,
+ user_id: user.id,
+ badge_id: badge.id
+ )
+
+ UserBadge.update_featured_ranks!(user.id)
+ end
+ end
+
def grant
return if @granted_by && !Guardian.new(@granted_by).can_grant_badges?(@user)
return unless @badge.enabled?
@@ -46,17 +67,9 @@ class BadgeGranter
if SiteSetting.enable_badges?
unless @badge.badge_type_id == BadgeType::Bronze && user_badge.granted_at < 2.days.ago
- I18n.with_locale(@user.effective_locale) do
- notification = @user.notifications.create(
- notification_type: Notification.types[:granted_badge],
- data: { badge_id: @badge.id,
- badge_name: @badge.display_name,
- badge_slug: @badge.slug,
- badge_title: @badge.allow_title,
- username: @user.username }.to_json
- )
- user_badge.update notification_id: notification.id
- end
+ notification = self.class.send_notification(@user.id, @user.username, @user.effective_locale, @badge)
+
+ user_badge.update notification_id: notification.id
end
end
end
@@ -331,29 +344,9 @@ class BadgeGranter
# old bronze badges do not matter
next if badge.badge_type_id == BadgeType::Bronze && row.granted_at < 2.days.ago
-
- # Try to use user locale in the badge notification if possible without too much resources
- notification_locale = if SiteSetting.allow_user_locale && row.locale.present?
- row.locale
- else
- SiteSetting.default_locale
- end
-
next if row.staff && badge.awarded_for_trust_level?
- notification = I18n.with_locale(notification_locale) do
- Notification.create!(
- user_id: row.user_id,
- notification_type: Notification.types[:granted_badge],
- data: {
- badge_id: badge.id,
- badge_name: badge.display_name,
- badge_slug: badge.slug,
- badge_title: badge.allow_title,
- username: row.username
- }.to_json
- )
- end
+ notification = send_notification(row.user_id, row.username, row.locale, badge)
DB.exec(
"UPDATE user_badges SET notification_id = :notification_id WHERE id = :id",
@@ -387,4 +380,25 @@ class BadgeGranter
SQL
end
+ def self.notification_locale(locale)
+ use_default_locale = !SiteSetting.allow_user_locale || locale.blank?
+ use_default_locale ? SiteSetting.default_locale : locale
+ end
+
+ def self.send_notification(user_id, username, locale, badge)
+ I18n.with_locale(notification_locale(locale)) do
+ Notification.create!(
+ user_id: user_id,
+ notification_type: Notification.types[:granted_badge],
+ data: {
+ badge_id: badge.id,
+ badge_name: badge.display_name,
+ badge_slug: badge.slug,
+ badge_title: badge.allow_title,
+ username: username
+ }.to_json
+ )
+ end
+ end
+
end
diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb
index 379c5b9774..6fc26df6d3 100644
--- a/app/services/post_alerter.rb
+++ b/app/services/post_alerter.rb
@@ -550,7 +550,7 @@ class PostAlerter
end
end
- def notify_post_users(post, notified, include_category_watchers: true, include_tag_watchers: true)
+ def notify_post_users(post, notified, include_category_watchers: true, include_tag_watchers: true, new_record: true)
return unless post.topic
warn_if_not_sidekiq
@@ -607,9 +607,12 @@ class PostAlerter
notify = notify.where("id NOT IN (?)", exclude_user_ids) if exclude_user_ids.present?
DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post)
+
+ notification_type = new_record ? Notification.types[:posted] : Notification.types[:edited]
+
notify.pluck(:id).each do |user_id|
user = User.find_by(id: user_id)
- create_notification(user, Notification.types[:posted], post)
+ create_notification(user, notification_type, post)
end
end
diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb
index 44c262d8f2..30d6d46e91 100644
--- a/app/services/user_updater.rb
+++ b/app/services/user_updater.rb
@@ -89,6 +89,10 @@ class UserUpdater
guardian.can_use_primary_group?(user, attributes[:primary_group_id])
user.primary_group_id = attributes[:primary_group_id]
+ elsif SiteSetting.user_selected_primary_groups &&
+ attributes[:primary_group_id].blank?
+
+ user.primary_group_id = nil
end
CATEGORY_IDS.each do |attribute, level|
diff --git a/app/services/wildcard_url_checker.rb b/app/services/wildcard_url_checker.rb
index aed621b885..f90defe15d 100644
--- a/app/services/wildcard_url_checker.rb
+++ b/app/services/wildcard_url_checker.rb
@@ -1,22 +1,20 @@
# frozen_string_literal: true
module WildcardUrlChecker
- VALID_PROTOCOLS = %w(http https discourse).freeze
-
def self.check_url(url, url_to_check)
- return nil if !valid_url?(url_to_check)
+ return false if !valid_url?(url_to_check)
escaped_url = Regexp.escape(url).sub("\\*", '\S*')
url_regex = Regexp.new("\\A#{escaped_url}\\z", 'i')
- url_to_check.match(url_regex)
+ url_to_check.match?(url_regex)
end
private
def self.valid_url?(url)
uri = URI.parse(url)
- VALID_PROTOCOLS.include?(uri&.scheme) && uri&.host.present?
+ uri&.scheme.present? && uri&.host.present?
rescue URI::InvalidURIError
false
end
diff --git a/app/views/common/_second_factor_backup_input.html.erb b/app/views/common/_second_factor_backup_input.html.erb
index dfe1961672..58abb897d0 100644
--- a/app/views/common/_second_factor_backup_input.html.erb
+++ b/app/views/common/_second_factor_backup_input.html.erb
@@ -1,2 +1,2 @@
-<%= text_field_tag(:second_factor_token, nil, autofocus: true, pattern: '[a-z0-9]{16}', maxlength: 16, type: 'text') %>
+<%= text_field_tag(:second_factor_token, nil, autofocus: true, pattern: '[a-z0-9]{16}', maxlength: 32, type: 'text') %>
<%= hidden_field_tag 'second_factor_method', '2' %>
\ No newline at end of file
diff --git a/app/views/list/list.erb b/app/views/list/list.erb
index 58978f98e3..af99862b96 100644
--- a/app/views/list/list.erb
+++ b/app/views/list/list.erb
@@ -4,7 +4,7 @@
<%- if SiteSetting.tagging_enabled && @tag_id %>
- <%= link_to "#{Discourse.base_url}/tags/#{@tag_id}", itemprop: 'item' do %>
+ <%= link_to "#{Discourse.base_url}/tag/#{@tag_id}", itemprop: 'item' do %>
<%= @tag_id %>
<% end %>
diff --git a/app/views/tags/_tag.html.erb b/app/views/tags/_tag.html.erb
index b8258f9a12..26fdc9f287 100644
--- a/app/views/tags/_tag.html.erb
+++ b/app/views/tags/_tag.html.erb
@@ -1,5 +1,5 @@
-
<%= tag[:text] %>
+
<%= tag[:text] %>
<% if tag[:count] && tag[:count] > 0 %>
x <%= tag[:count] %>
<% end %>
diff --git a/app/views/topics/show.html.erb b/app/views/topics/show.html.erb
index 3a2c86bd2f..c5187769be 100644
--- a/app/views/topics/show.html.erb
+++ b/app/views/topics/show.html.erb
@@ -3,18 +3,16 @@
<%= render_topic_title(@topic_view.topic) %>
- <% @breadcrumbs = categories_breadcrumb(@topic_view.topic)
- if @breadcrumbs.present? %>
-
- <% @breadcrumbs.each_with_index do |c,i| %>
-
- itemref="breadcrumb-<%=(i+1)%>"
- <%-end%>>
-
+ <% @breadcrumbs = categories_breadcrumb(@topic_view.topic) %>
+ <% if @breadcrumbs.present? %>
+
+ <% @breadcrumbs.each_with_index do |c, i| %>
+
<% end %>
@@ -26,7 +24,7 @@
<% @tags.each_with_index do |tag, i| %>
@@ -44,7 +42,7 @@
<% if (u = post.user) %>
- <%= u.username %>
+ <%= u.username %>
<%= "(#{u.name})" if (SiteSetting.display_name_on_posts && SiteSetting.enable_names? && !u.name.blank?) %>
<%
post_custom_fields = @topic_view.post_custom_fields[post.id] || {}
diff --git a/app/views/users/admin_login.html.erb b/app/views/users/admin_login.html.erb
index b8bca6efff..726c62e33a 100644
--- a/app/views/users/admin_login.html.erb
+++ b/app/views/users/admin_login.html.erb
@@ -1,61 +1,10 @@
<% if @message %>
<%= @message %>
<% if @error %><%= @error %>
<% end %>
-
- <% if @security_key_required %>
-
- <% end %>
-
- <% if @second_factor_required %>
-
- <% end %>
<% else %>
<%=form_tag({}, method: :put) do %>
<%= label_tag(:email, t('admin_login.email_input')) %>
<%= text_field_tag(:email, nil, autofocus: true) %>
<%= submit_tag t('admin_login.submit_button'), class: "btn btn-primary" %>
<% end %>
-<% end %>
-
-<%= preload_script "ember_jquery" %>
-<%= preload_script "locales/#{I18n.locale}" %>
-<%= preload_script "locales/i18n" %>
-<%= preload_script "discourse/lib/webauthn" %>
-<%= preload_script "admin-login/admin-login" %>
-<%= preload_script "admin-login/admin-login.no-module" %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/users/omniauth_callbacks/confirm_request.html.erb b/app/views/users/omniauth_callbacks/confirm_request.html.erb
index 9fa44acc4d..8074820193 100644
--- a/app/views/users/omniauth_callbacks/confirm_request.html.erb
+++ b/app/views/users/omniauth_callbacks/confirm_request.html.erb
@@ -2,9 +2,10 @@
<%= t('login.omniauth_confirm_title', provider:(t "js.login.#{params[:provider]}.name", default: params[:provider])) %>
- <%= form_tag do %>
+
diff --git a/app/views/users_email/show_confirm_new_email.html.erb b/app/views/users_email/show_confirm_new_email.html.erb
index fe14cf53df..31108a7ac1 100644
--- a/app/views/users_email/show_confirm_new_email.html.erb
+++ b/app/views/users_email/show_confirm_new_email.html.erb
@@ -21,25 +21,49 @@
<%=form_tag(u_confirm_new_email_path, method: :put) do %>
<%= hidden_field_tag 'token', @token.token %>
+ <%= hidden_field_tag 'second_factor_token', nil, id: 'security-key-credential' %>
+
+
+ <% if @show_invalid_second_factor_error %>
+
<%= @invalid_second_factor_message %>
+ <% end %>
<% if @show_backup_codes %>
+
<%= link_to t("login.second_factor_toggle.totp"), show_backup: "false" %>
+
+ <% elsif @show_security_key %>
+ <%= hidden_field_tag 'security_key_challenge', @security_key_challenge, id: 'security-key-challenge' %>
+ <%= hidden_field_tag 'second_factor_method', UserSecondFactor.methods[:security_key] %>
+ <%= hidden_field_tag 'security_key_allowed_credential_ids', @security_key_allowed_credential_ids, id: 'security-key-allowed-credential-ids' %>
+
+
+ <% if @show_second_factor %>
+ <%= link_to t("login.security_key_alternative"), show_totp: "true" %>
+ <% end %>
+ <% if @backup_codes_enabled %>
+ <%= link_to t("login.second_factor_toggle.backup_code"), show_backup: "true" %>
+ <% end %>
<% elsif @show_second_factor %>
+
<% if @backup_codes_enabled %>
<%= link_to t("login.second_factor_toggle.backup_code"), show_backup: "true" %>
<% end %>
@@ -48,4 +72,11 @@
<% end %>
<%end%>
<% end%>
+
+ <%= preload_script "ember_jquery" %>
+ <%= preload_script "locales/#{I18n.locale}" %>
+ <%= preload_script "locales/i18n" %>
+ <%= preload_script "discourse/lib/webauthn" %>
+ <%= preload_script "confirm-new-email/confirm-new-email" %>
+ <%= preload_script "confirm-new-email/confirm-new-email.no-module" %>
diff --git a/bin/docker/boot_dev b/bin/docker/boot_dev
index be8d17dc5c..443de729cd 100755
--- a/bin/docker/boot_dev
+++ b/bin/docker/boot_dev
@@ -70,7 +70,7 @@ for symlink in $(find $PLUGINS_DIR -type l); do
# This deliberately does not use the `-f` option to canonicalize the value
# because 1) the BSD `readlink` does not support the option, and 2) a
# relative link would not work inside the container anyway.
- symlink_value=$(readlink $symlink)
+ symlink_value=$(readlink -f $symlink)
mount_plugin_symlinks+=" -v ${symlink_value}:${symlink_value}:delegated"
done
diff --git a/config/application.rb b/config/application.rb
index a8e9af88d7..e3bc620c5f 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -151,8 +151,8 @@ module Discourse
wizard-start.js
locales/i18n.js
discourse/lib/webauthn.js
- admin-login/admin-login.js
- admin-login/admin-login.no-module.js
+ confirm-new-email/confirm-new-email.js
+ confirm-new-email/confirm-new-email.no-module.js
onpopstate-handler.js
embed-application.js
}
@@ -224,7 +224,7 @@ module Discourse
# supports etags (post 1.7)
config.middleware.delete Rack::ETag
- unless Rails.env.development?
+ if !(Rails.env.development? || ENV['SKIP_ENFORCE_HOSTNAME'] == "1")
require 'middleware/enforce_hostname'
config.middleware.insert_after Rack::MethodOverride, Middleware::EnforceHostname
end
diff --git a/config/database.yml b/config/database.yml
index a9be456195..c8f44946d9 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -16,10 +16,23 @@ development:
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
+
+<%
+ test_db = ENV["RAILS_DB"]
+ if !test_db.present?
+ test_db = "discourse_test"
+
+ if num = ENV["TEST_ENV_NUMBER"]
+ num = num.presence || "1"
+ test_db += "_#{num}"
+ end
+ end
+%>
+
test:
prepared_statements: false
adapter: postgresql
- database: <%= ENV["RAILS_DB"] ? ENV["RAILS_DB"] : "discourse_test" %>
+ database: <%= test_db %>
min_messages: warning
pool: 5
timeout: 5000
diff --git a/config/discourse_defaults.conf b/config/discourse_defaults.conf
index ff58c37233..9d44aa39f9 100644
--- a/config/discourse_defaults.conf
+++ b/config/discourse_defaults.conf
@@ -241,6 +241,10 @@ refresh_maxmind_db_during_precompile_days = 30
# backup path containing maxmind db files
maxmind_backup_path =
+# register an account at: https://www.maxmind.com/en/geolite2/signup
+# then head to profile and get your license key
+maxmind_license_key=
+
# when enabled the following headers will be added to every response:
# (note, if measurements do not exist for the header they will be omitted)
#
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 55a8f8e6c9..5ee92a55ab 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -30,7 +30,7 @@ Discourse::Application.configure do
config.active_record.migration_error = :page_load
config.watchable_dirs['lib'] = [:rb]
- config.handlebars.precompile = false
+ config.handlebars.precompile = true
# we recommend you use mailcatcher https://github.com/sj26/mailcatcher
config.action_mailer.smtp_settings = { address: "localhost", port: 1025 }
diff --git a/config/environments/test.rb b/config/environments/test.rb
index da7b6f2ef3..bbb7614e80 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -47,7 +47,10 @@ Discourse::Application.configure do
config.eager_load = false
- unless ENV['RAILS_ENABLE_TEST_LOG']
+ if ENV['RAILS_ENABLE_TEST_LOG']
+ config.logger = Logger.new(STDOUT)
+ config.log_level = ENV['RAILS_TEST_LOG_LEVEL'].present? ? ENV['RAILS_TEST_LOG_LEVEL'].to_sym : :info
+ else
config.logger = Logger.new(nil)
config.log_level = :fatal
end
diff --git a/config/initializers/010-discourse_iife.rb b/config/initializers/010-discourse_iife.rb
index 7e50904fa8..3405a62c4b 100644
--- a/config/initializers/010-discourse_iife.rb
+++ b/config/initializers/010-discourse_iife.rb
@@ -5,7 +5,7 @@ require 'discourse_iife'
Rails.application.config.assets.configure do |env|
env.register_preprocessor('application/javascript', DiscourseIIFE)
- unless Rails.env.production? || ENV["DISABLE_EVAL"]
+ unless Rails.env.production?
require 'source_url'
env.register_postprocessor('application/javascript', SourceURL)
end
diff --git a/config/initializers/014-track-setting-changes.rb b/config/initializers/014-track-setting-changes.rb
index e5ca134122..f8276d9dcc 100644
--- a/config/initializers/014-track-setting-changes.rb
+++ b/config/initializers/014-track-setting-changes.rb
@@ -30,7 +30,7 @@ DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value|
end
end
- Jobs.enqueue(:update_s3_inventory) if [:s3_inventory, :s3_upload_bucket].include?(name)
+ Jobs.enqueue(:update_s3_inventory) if [:enable_s3_inventory, :s3_upload_bucket].include?(name)
Jobs.enqueue(:update_private_uploads_acl) if name == :prevent_anons_from_downloading_files
diff --git a/config/initializers/100-oj.rb b/config/initializers/100-oj.rb
index 0936beaa79..57991081c2 100644
--- a/config/initializers/100-oj.rb
+++ b/config/initializers/100-oj.rb
@@ -3,6 +3,7 @@
Oj::Rails.set_encoder()
Oj::Rails.set_decoder()
Oj::Rails.optimize()
+Oj.default_options = Oj.default_options.merge(mode: :compat)
# Not sure why it's not using this by default!
MultiJson.engine = :oj
diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml
index dd3175ecd1..b4c6717a2a 100644
--- a/config/locales/client.ar.yml
+++ b/config/locales/client.ar.yml
@@ -31,6 +31,7 @@ ar:
millions: "{{number}} مليون"
dates:
time: "h:mm a"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM h:mm a"
long_no_year_no_time: "D MMM"
@@ -60,6 +61,13 @@ ar:
few: "%{count}ث"
many: "%{count}ث"
other: "%{count}ث"
+ less_than_x_minutes:
+ zero: "< %{count}ش"
+ one: "< %{count}ش"
+ two: "< %{count}ش"
+ few: "< %{count}ش"
+ many: "< %{count}ش"
+ other: "< %{count}ش"
x_minutes:
zero: "%{count}د"
one: "%{count}د"
@@ -81,6 +89,13 @@ ar:
few: "%{count}ي"
many: "%{count}ي"
other: "%{count}ي"
+ x_months:
+ zero: "%{count}شهر"
+ one: "%{count}شهر"
+ two: "%{count}شهران"
+ few: "%{count}أشهر"
+ many: "%{count}أشهر"
+ other: "%{count}أشهر"
about_x_years:
zero: "%{count}ع"
one: "%{count}ع"
@@ -149,6 +164,20 @@ ar:
few: "قبل %{count} أيام"
many: "قبل %{count} يوما"
other: "قبل %{count} يوما"
+ x_months:
+ zero: "قبل %{count} شهر"
+ one: "قبل %{count} شهر"
+ two: "قبل %{count} شهران"
+ few: "قبل %{count} أشهر"
+ many: "قبل %{count} أشهر"
+ other: "قبل %{count} أشهر"
+ x_years:
+ zero: "قبل %{count} سنة"
+ one: "قبل %{count} سنة"
+ two: "قبل %{count} سنتان"
+ few: "قبل %{count} سنوات"
+ many: "قبل %{count} سنوات"
+ other: "قبل %{count} سنوات"
later:
x_days:
zero: "بعد أقل من يوم"
@@ -175,6 +204,7 @@ ar:
next_month: "الشهر القادم"
placeholder: التاريخ
share:
+ topic_html: 'الموضوع: %{topicTitle} '
post: "المنشور رقم %{postNumber}"
close: "أغلق"
twitter: "شارك هذا رابط على تويتر"
@@ -189,6 +219,7 @@ ar:
user_left: "%{who}أزال نفسه من هذه الرسالة %{when}"
removed_user: "اُقصي %{who} %{when}"
removed_group: "اُقصي %{who} %{when}"
+ autobumped: "رفع مبرمج %{when}"
autoclosed:
enabled: "أُغلق %{when}"
disabled: "فُتح %{when}"
@@ -210,10 +241,14 @@ ar:
banner:
enabled: "اجعل هذا إعلانا %{when}. سوف يظهر اعلى جميع الصفحات حتى يتم الغاؤه بواسطة المستخدم."
disabled: "أزل هذا الإعلان %{when}. لن يظهر بعد الآن في أعلى كلّ صفحة."
+ topic_admin_menu: "عمليات الموضوع"
wizard_required: "مرحبًا في نسختك الجديدة من دسكورس! فلنبدأ مع مُرشد الإعدادات ✨"
emails_are_disabled: "لقد عطّل أحد المدراء الرّسائل الصادرة للجميع. لن تُرسل إشعارات عبر البريد الإلكتروني أيا كان نوعها."
+ bootstrap_mode_enabled: "لكى تتمكن من اطلاق موقعك الجديد بسهولة, الموقع اﻷن علي الوضع التمهيدي. كل المستخدمين الجدد سيحصلون علي مستوي الثقة 1 وسيكون خيار ارسال الملخص اليومى عن طريق البريد الالكترونى مفعل. سيتم الغاء الوضع التمهيدي تلقائيا عندما يشترك %{min_users} عضو."
+ bootstrap_mode_disabled: "سيتوقف الوضع التمهيدي خلال 24 ساعة."
themes:
default_description: "افتراضى"
+ broken_theme_alert: "قد لا يعمل موقعك لأن القالب / المكون %{theme} به أخطاء. ألغه في %{path}."
s3:
regions:
ap_northeast_1: "آسيا والمحيط الهادئ (طوكيو)"
@@ -221,12 +256,19 @@ ar:
ap_south_1: "آسيا والمحيط الهادئ (مومباي)"
ap_southeast_1: "آسيا والمحيط الهادئ (سنغافورة)"
ap_southeast_2: "آسيا والمحيط الهادئ (سيدني)"
+ ca_central_1: "كندا (وسط)"
cn_north_1: "الصين (بكين)"
+ cn_northwest_1: "الصين (نيجكسا)"
eu_central_1: "الاتحاد الأوروبي (فرانكفورت)"
+ eu_north_1: "الاتحاد الأوربي (ستوكهولم)"
eu_west_1: "الاتحاد الأوروبي (أيرلندا)"
eu_west_2: "الاتحاد الأوروبي (لندن)"
+ eu_west_3: "الاتحاد الأوربي (باريس)"
+ sa_east_1: "أمريكا الجنوبية (ساو باولو)"
us_east_1: "شرق الولايات المتحدة (فرجينيا الشمالية)"
us_east_2: "غرب الولايات المتحدة (اوهايو)"
+ us_gov_east_1: "إستضافة أمازون السحابية AWS GovCloud (US-East)"
+ us_gov_west_1: "إستضافة أمازون السحابية AWS GovCloud (US-West)"
us_west_1: "غرب الولايات المتحدة (كاليفورنيا الشمالية)"
us_west_2: "غرب الولايات المتحدة (أوريغون)"
edit: "عدل عنوان و قسم هذا الموضوع"
@@ -259,6 +301,7 @@ ar:
privacy: "الخصوصية "
tos: "شروط الخدمة"
rules: "الشروط"
+ conduct: "قواعد السلوك"
mobile_view: "نسخة الهواتف"
desktop_view: "نسخة سطح المكتب"
you: "انت"
@@ -285,6 +328,7 @@ ar:
other: "{{count}} حرف"
related_messages:
title: "الرسائل ذات الصلة"
+ see_all: 'شاهد
كل الرسائل من @%{username}...'
suggested_topics:
title: "المواضيع المقترحة"
pm_title: "رسائل مقترحة "
@@ -314,9 +358,14 @@ ar:
unbookmark: "انقر لإزالة كلّ العلامات المرجعية في هذا الموضوع"
bookmarks:
created: "لقد وضعت علامة مرجعية علي هذا المنشور"
+ not_bookmarked: "أشر هذا المكتوب"
+ created_with_reminder: "لقد أشرت هذا المكتوب بتذكير عند %{date}"
remove: "أزل العلامة المرجعية"
confirm_clear: "هل أنت متأكد من مسح جميع الاشعارات المرجعية من هذا الموضوع؟"
save: "احفظ"
+ no_timezone: 'لم تحدد منظقتك الزمنية بعد. لن تتمكن من تفعيل التذكيرات. حددها
في ملفك الشخصي .'
+ reminders:
+ at_desktop: "في المرة القادمة سأكون عند مكتبي"
drafts:
resume: "أكمل"
remove: "احذف"
diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml
index cb5ffc471f..2358e55280 100644
--- a/config/locales/client.cs.yml
+++ b/config/locales/client.cs.yml
@@ -151,6 +151,9 @@ cs:
share:
post: "příspěvek #%{postNumber}"
close: "zavřít"
+ twitter: "Sdílet odkaz na Twitteru"
+ facebook: "Sdílet odkaz na Facebooku"
+ email: "Odeslat odkaz emailem"
action_codes:
public_topic: "Téma zveřejněno %{when}"
private_topic: "Téma změněno na osobní zprávu %{when}"
@@ -245,6 +248,8 @@ cs:
every_hour: "každou hodinu"
daily: "denně"
weekly: "týdně"
+ every_month: "každý měsíc"
+ every_six_months: "každých šest měsíců"
max_of_count: "max z"
alternation: "nebo"
character_count:
@@ -254,6 +259,7 @@ cs:
other: "{{count}} znaků"
related_messages:
title: "Související zprávy"
+ see_all: 'Zobrazit
všechny zprávy od @%{username}'
suggested_topics:
title: "Doporučená témata"
pm_title: "Doporučené zprávy"
@@ -889,6 +895,8 @@ cs:
every_hour: "každou hodinu"
daily: "denně"
weekly: "týdně"
+ every_month: "každý měsíc"
+ every_six_months: "každých šest měsíců"
email_level:
title: "Zašli mi email, pokud mě někde cituje, odpoví na můj příspěvek, zmíní mé @jméno nebo mě pozve do tématu."
always: "vždy"
diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml
index c7573ffe30..254d45811f 100644
--- a/config/locales/client.de.yml
+++ b/config/locales/client.de.yml
@@ -264,7 +264,7 @@ de:
remove: "Lesezeichen entfernen"
confirm_clear: "Bist du sicher, dass du alle Lesezeichen in diesem Thema entfernen möchtest? "
save: "Speichern"
- no_timezone: "Du hast noch keine Zeitzone ausgewählt. Du wirst keine Erinnerungen erstellen können. Stelle eine
in deinem Profil ein."
+ no_timezone: 'Du hast noch keine Zeitzone ausgewählt. Du wirst keine Erinnerungen erstellen können. Stelle eine
in deinem Profil ein.'
reminders:
at_desktop: "Nächstes mal wenn ich an meinem PC bin"
later_today: "Im Laufe des Tages
{{date}}"
diff --git a/config/locales/client.el.yml b/config/locales/client.el.yml
index 5071f98b4e..1a8929a694 100644
--- a/config/locales/client.el.yml
+++ b/config/locales/client.el.yml
@@ -194,11 +194,14 @@ el:
every_hour: "κάθε ώρα"
daily: "καθημερινά"
weekly: "κάθε εβδομάδα"
+ every_month: "κάθε μήνα"
max_of_count: "μέγιστο {{count}}"
alternation: "ή"
character_count:
one: "{{count}} χαρακτήρα"
other: "{{count}} χαρακτήρες"
+ related_messages:
+ title: "Σχετικά Μηνύματα"
suggested_topics:
title: "Προτεινόμενα Νήματα"
pm_title: "Προτεινόμενα Μηνύματα"
@@ -258,6 +261,7 @@ el:
total: "Σύνολο"
delete: "Σβήσιμο"
settings:
+ saved: "Αποθηκεύτηκε! "
save_changes: "Αποθήκευση Αλλαγών"
title: "Ρυθμίσεις"
topic: "Νήμα:"
@@ -266,6 +270,7 @@ el:
username: "Όνομα Χρήστη"
email: "Διεύθυνση Email"
name: "Όνομα"
+ fields: "Πεδία"
topics:
topic: "Νήμα"
details: "λεπτομέρειες"
@@ -282,8 +287,14 @@ el:
statuses:
pending:
title: "Εκκρεμή"
+ approved:
+ title: "Εγκρίθηκε "
rejected:
title: "Απορρίφθηκε"
+ ignored:
+ title: "Αγνοήθηκε "
+ deleted:
+ title: "Διαγράφηκε"
types:
reviewable_user:
title: "Χρήστης"
@@ -501,6 +512,7 @@ el:
user_notifications:
ignore_duration_username: "Όνομα Χρήστη"
ignore_duration_save: "Αγνόηση"
+ ignore_option: "Αγνοήθηκε "
mute_option: "Σίγαση"
normal_option: "Φυσιολογικά"
activity_stream: "Δραστηριότητα"
@@ -578,6 +590,7 @@ el:
users: "Χρήστες"
muted_users: "Σε σίγαση"
muted_users_instructions: "Αποσιώπησε όλες τις ειδοποιήσεις από αυτούς τους χρήστες."
+ ignored_users: "Αγνοήθηκε "
tracked_topics_link: "Δείξε"
automatically_unpin_topics: "Τα νήματα ξεκαρφιτσώνονται αυτόματα όταν φτάνω στο κάτω μέρος."
apps: "Εφαρμογές"
@@ -725,6 +738,7 @@ el:
every_hour: "ωριαία"
daily: "καθημερινά"
weekly: "εβδομαδιαία"
+ every_month: "κάθε μήνα"
email_level:
title: "Στείλε μου ένα email όταν κάποιος παραθέσει ανάρτησή μου, απαντήσει σε ανάρτησή μου, αναφέρει το @username μου ή με προσκαλεί σε ένα νήμα."
always: "πάντα"
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 153c312474..697ae7fb80 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -40,9 +40,7 @@ en:
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time: "HH:mm"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
- time_short_day: "ddd HH:mm a"
- # Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
- month_day_time: "MMM D, HH:mm a"
+ time_short_day: "ddd, HH:mm"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
timeline_date: "MMM YYYY"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
@@ -311,7 +309,7 @@ en:
remove: "Remove Bookmark"
confirm_clear: "Are you sure you want to clear all your bookmarks from this topic?"
save: "Save"
- no_timezone: "You have not set a timezone yet. You will not be able to set reminders. Set one up
in your profile ."
+ no_timezone: 'You have not set a timezone yet. You will not be able to set reminders. Set one up
in your profile .'
reminders:
at_desktop: "Next time I'm at my desktop"
later_today: "Later today
{{date}}"
@@ -648,6 +646,7 @@ en:
leave: "Leave"
request: "Request"
message: "Message"
+ confirm_leave: "Are you sure you want to leave this group?"
allow_membership_requests: "Allow users to send membership requests to group owners"
membership_request_template: "Custom template to display to users when sending a membership request"
membership_request:
@@ -3042,6 +3041,7 @@ en:
mark_watching: "%{shortcut} Watch topic"
print: "%{shortcut} Print topic"
defer: "%{shortcut} Defer topic"
+ topic_admin_actions: "%{shortcut} Open topic admin actions"
badges:
earned_n_times:
@@ -4494,6 +4494,14 @@ en:
title: "Select an existing badge or create a new one to get started"
what_are_badges_title: "What are badges?"
badge_query_examples_title: "Badge query examples"
+ mass_award:
+ title: Bulk Award
+ description: Award the same badge to many users at once.
+ no_badge_selected: Please select a badge to get started.
+ perform: "Award Badge to Users"
+ upload_csv: Upload a CSV with user emails
+ aborted: Please upload a CSV containing user emails
+ success: Your CSV was received and users will receive their badge shortly.
emoji:
title: "Emoji"
diff --git a/config/locales/client.en_US.yml b/config/locales/client.en_US.yml
index 683baf554d..1f75f5f43e 100644
--- a/config/locales/client.en_US.yml
+++ b/config/locales/client.en_US.yml
@@ -4,6 +4,8 @@ en_US:
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
time: "h:mm a"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
+ time_short_day: "ddd, h:mm a"
+ # Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
timeline_date: "MMM YYYY"
# Use Moment.js format string: https://momentjs.com/docs/#/displaying/format/
long_no_year: "MMM D h:mm a"
diff --git a/config/locales/client.es.yml b/config/locales/client.es.yml
index 06856d29ba..088c24154c 100644
--- a/config/locales/client.es.yml
+++ b/config/locales/client.es.yml
@@ -27,8 +27,7 @@ es:
millions: "{{number}}M"
dates:
time: "HH:mm"
- time_short_day: "ddd HH:mm a"
- month_day_time: "MMM D, HH:mm a"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM HH:mm"
long_no_year_no_time: "D MMM"
@@ -150,10 +149,11 @@ es:
disabled: "sin destacar %{when}"
visible:
enabled: "listado %{when}"
- disabled: "removido de la lista %{when}"
+ disabled: "quitado de la lista %{when}"
banner:
enabled: "hizo esto un encabezado %{when}. Aparecerá en la parte superior de cada página hasta que el usuario lo descarte."
disabled: "eliminó este encabezado %{when}. Ya no aparecerá en la parte superior de cada página."
+ topic_admin_menu: "acciones del tema"
wizard_required: "¡Bienvenido a tu nuevo Discourse! Empezaremos con
el asistente de configuración ✨"
emails_are_disabled: "Todos los correos electrónicos salientes han sido deshabilitados globalmente por un administrador. No se enviarán notificaciones por correo electrónico de ningún tipo."
bootstrap_mode_enabled: "Para facilitar el lanzamiento de tu nuevo sitio, estás en modo de arranque. A todos los usuarios nuevos se les otorgará el nivel de confianza 1 y se tendrán habilitados los correos electrónicos de resumen diarios. Esta función se desactivará automáticamente cuando %{min_users} usuarios se hayan unido."
@@ -267,9 +267,9 @@ es:
remove: "Eliminar marcador"
confirm_clear: "¿Estás seguro de que deseas eliminar todos tus marcadores en este tema?"
save: "Guardar"
- no_timezone: "No has establecido una zona horaria todavía. No podrás establecer recordatorios. Puedes establecerlo
en tu perfil ."
+ no_timezone: 'No has establecido una zona horaria todavía. No podrás establecer recordatorios. Puedes elegir una
en tu perfil .'
reminders:
- at_desktop: "La próxima vez que este en mi computadora"
+ at_desktop: "La próxima vez que esté en mi ordenador"
later_today: "Más tarde hoy
{{date}}"
next_business_day: "El próximo día hábil
{{date}}"
tomorrow: "Mañana
{{date}}"
@@ -322,7 +322,7 @@ es:
choose_topic:
none_found: "No se encontraron temas."
title:
- search: "busca un tema"
+ search: "Busca un tema"
placeholder: "escribe el título, url o ID del tema aquí"
choose_message:
none_found: "No se encontraron mensajes."
@@ -755,9 +755,13 @@ es:
activity_stream: "Actividad"
preferences: "Preferencias"
feature_topic_on_profile:
+ open_search: "Selecciona un nuevo tema"
+ title: "Selecciona un tema"
+ search_label: "Buscar tema por título"
save: "Guardar"
clear:
title: "Quitar filtros"
+ warning: "¿Estás seguro de que quieres eliminar tu tema destacado?"
profile_hidden: "El perfil público de este usuario está oculto."
expand_profile: "Expandir"
collapse_profile: "Contraer"
@@ -976,6 +980,7 @@ es:
instructions: "Las imágenes de fonodo estarán centradas y tendrán una anchura predeterminada de 590 px."
change_featured_topic:
title: "Tema destacado"
+ instructions: "Un enlace a este tema estará en tu tarjeta de usuario y perfil."
email:
title: "Correo electrónico"
primary: "Correo electrónico principal"
@@ -1111,6 +1116,7 @@ es:
search: "escribe para buscar invitaciones..."
title: "Invitaciones"
user: "Usuario invitado"
+ sent: "Última vez enviada"
none: "Sin invitaciones para mostrar."
truncated:
one: "Mostrando la primera invitación."
@@ -1302,7 +1308,7 @@ es:
private_message_info:
title: "Mensaje"
invite: "Invitar a otros..."
- edit: "Agregar o quitar..."
+ edit: "Añadir o quitar..."
leave_message: "¿Estás seguro de que quieres abandonar este mensaje?"
remove_allowed_user: "¿Estás seguro de que quieres eliminar a {{name}} de este mensaje?"
remove_allowed_group: "¿Estás seguro de que quieres eliminar a {{name}} de este mensaje?"
@@ -1509,7 +1515,7 @@ es:
one: "Al mencionar a {{group}}, estás a punto de notificar a
%{count} persona – ¿seguro que quieres hacerlo?"
other: "Al mencionar a {{group}}, estás a punto de notificar a
{{count}} personas ¿Estás seguro de que quieres hacerlo?"
cannot_see_mention:
- category: "Mencionaste a {{username}} pero no se les notificará porque no tienen acceso a esta categoría. Necesitarás agregarlos a un grupo que tenga acceso a esta categoría."
+ category: "Mencionaste a {{username}} pero no se les notificará porque no tienen acceso a esta categoría. Necesitarás añadirlos a un grupo que tenga acceso a esta categoría."
private: "Mencionaste a {{username}} pero no se les notificará porque no pueden ver este mensaje personal. Necesitarás invitarlos a este MP."
duplicate_link: "Parece que tu enlace a
{{domain}} ya se publicó en el tema por
@{{username}} en
una respuesta el {{ago}} . ¿Estás seguro de que deseas volver a publicarlo?"
reference_topic_title: "RE: {{title}}"
@@ -1535,7 +1541,7 @@ es:
create_shared_draft: "Crear borrador compartido"
edit_shared_draft: "Editar borrador compartido"
title: "O pulsa Ctrl+Intro"
- users_placeholder: "Agregar un usuario"
+ users_placeholder: "Añadir un usuario"
title_placeholder: "En una frase breve, ¿de qué trata este tema?"
title_or_link_placeholder: "Escribe un título o pega un enlace aquí"
edit_reason_placeholder: "¿Por qué lo estás editando?"
@@ -1753,6 +1759,7 @@ es:
title: coincide el título únicamente
likes: me han gustado
posted: he publicado en ellos
+ created: Creado por mi
watching: estoy vigilando
tracking: estoy siguiendo
private: en mis mensajes
@@ -1769,6 +1776,7 @@ es:
label: Donde los temas
open: están abiertos
closed: están cerrados
+ public: son públicos
archived: están archivados
noreplies: no tienen respuestas
single_user: contienen un solo usuario
@@ -1857,10 +1865,10 @@ es:
help: "Marcar como no leído"
title: "Aplazar"
feature_on_profile:
- help: "Agregar un enlace a este tema en tu tarjeta de usuario y perfil"
+ help: "Añadir un enlace a este tema en tu tarjeta de usuario y perfil"
title: "Destacar en el perfil"
remove_from_profile:
- warning: "Tu perfil ya tiene un tema destacado. Si continúas, este tema remplazará el tema ya existente."
+ warning: "Tu perfil ya tiene un tema destacado. Si continúas, este tema remplazará el tema actual."
help: "Eliminar el enlace a este tema de tu perfil de usuario"
title: "Eliminar del perfil"
list: "Temas"
@@ -2350,6 +2358,9 @@ es:
like_capped:
one: "y {{count}} otro le gustó esto"
other: "y {{count}} otros le dieron me gusta a esto"
+ read_capped:
+ one: "y {{count}} otro ha leído"
+ other: "y {{count}} otros han leído"
by_you:
off_topic: "Reportaste esto como sin relación con el tema"
spam: "Reportaste esto como spam"
@@ -2402,7 +2413,7 @@ es:
bookmarks:
create: "Crear marcador"
name: "Nombre"
- name_placeholder: "Ponle un nombre a la publicación que guardaste en marcadores para ayudarte a recordarla"
+ name_placeholder: "Ponle un nombre a la publicación que has guardado en marcadores para ayudarte a recordarla"
set_reminder: "Establecer un recordatorio"
category:
can: "puede… "
@@ -2785,6 +2796,7 @@ es:
mark_watching: "%{shortcut} Vigilar Tema"
print: "%{shortcut} Imprimir tema"
defer: "%{shortcut} Aplazar el tema"
+ topic_admin_actions: "%{shortcut} Abrir acciones de administrador del tema"
badges:
earned_n_times:
one: "Ganó esta medalla %{count} vez"
@@ -2842,13 +2854,13 @@ es:
one: 'Esta etiqueta pertence al grupo: «{{tag_groups}}»'
other: "Esta etiqueta pertence a estos grupos: {{tag_groups}}."
category_restrictions:
- one: "Solo se puede utilizar en esta categoría"
- other: "Solo se puede utilizar en estas categorías"
- edit_synonyms: "Administrar sinónimos"
- add_synonyms_label: "Agregar sinónimos:"
- add_synonyms: "Agregar"
- add_synonyms_failed: "No se pudieron agregar las siguientes etiquetas como sinónimos:
%{tag_names} . Asegúrate de que no tienen sinónimos y de que no son sinónimos de otra etiqueta."
- remove_synonym: "Eliminar sinónimo"
+ one: "Solo se puede utilizar en esta categoría:"
+ other: "Solo se puede utilizar en estas categorías:"
+ edit_synonyms: "Gestionar sinónimos"
+ add_synonyms_label: "Añadir sinónimos:"
+ add_synonyms: "Añadir"
+ add_synonyms_failed: "No se han podido añadir las siguientes etiquetas como sinónimos:
%{tag_names} . Asegúrate de que no tienen sinónimos y de que no son sinónimos de otra etiqueta."
+ remove_synonym: "Quitar sinónimo"
delete_synonym_confirm: '¿Estás seguro de que quieres eliminar el sinónimo «%{tag_name}»?'
delete_tag: "Eliminar etiqueta"
delete_confirm:
@@ -3406,7 +3418,7 @@ es:
upload: "Subir"
select_component: "Seleccionar un componente..."
unsaved_changes_alert: "No has guardado los cambios aún, ¿los quieres descartar y seguir adelante?"
- unsaved_parent_themes: "No has asignado este componente a los temas, ¿quieres continuar?"
+ unsaved_parent_themes: "No has asignado este componente a ningún tema, ¿quieres continuar?"
discard: "Descartar"
stay: "Permanecer"
css_html: "Personalizar CSS/HTML"
@@ -3415,7 +3427,7 @@ es:
delete_upload_confirm: "¿Eliminar este archivo? (¡El tema CSS puede dejar de funcionar!)"
component_on_themes: "Incluir componentes en estos temas"
included_components: "Componentes incluidos"
- add_all: "Agregar todos"
+ add_all: "Añadir todos"
import_web_tip: "Repositorio que contiene el tema"
import_web_advanced: "Avanzado..."
import_file_tip: "archivo .tar.gz, .zip o .dcstyle.json que contiene un tema"
diff --git a/config/locales/client.fi.yml b/config/locales/client.fi.yml
index 97f6bd3f34..8888ec559a 100644
--- a/config/locales/client.fi.yml
+++ b/config/locales/client.fi.yml
@@ -27,6 +27,7 @@ fi:
millions: "{{number}}M"
dates:
time: "H:mm"
+ time_short_day: "ddd HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D. MMMM[ta] H:mm"
long_no_year_no_time: "D. MMMM[ta]"
@@ -152,6 +153,7 @@ fi:
banner:
enabled: "teki tästä bannerin %{when}. Se näytetään jokaisen sivun ylälaidassa, kunnes käyttäjä kuittaa sen nähdyksi."
disabled: "poisti tämän bannerin %{when}. Sitä ei enää näytetä jokaisen sivun ylälaidassa."
+ topic_admin_menu: "ketjun työkalut"
wizard_required: "Tervetuloa uuteen Discourseesi! Aloitetaan
ohjattu asennus ✨"
emails_are_disabled: "Ylläpitäjä on estänyt kaiken lähtevän sähköpostiliikenteen. Mitään sähköposti-ilmoituksia ei lähetetä."
bootstrap_mode_enabled: "Jotta uuden palstan vauhtiin saaminen olisi helpompaa, sivusto on aloitustilassa. Kaikki uudet käyttäjät saavat luottamustason 1 ja heille lähetetään sähköpostitiivistelmät päivittäin. Tila poistetaan automaattisesti, kun %{min_users} käyttäjää on liittynyt."
@@ -261,9 +263,19 @@ fi:
bookmarks:
created: "olet lisännyt tämän viestin kirjanmerkkeihisi"
not_bookmarked: "lisää viesti kirjanmerkkeihin"
+ created_with_reminder: "olet kirjanmerkinnyt tämän viestin ja siitä muistutetaan %{date}"
remove: "Poista kirjanmerkki"
confirm_clear: "Haluatko varmasti poistaa kaikki kirjanmerkkisi tästä ketjusta?"
save: "Tallenna"
+ no_timezone: 'Et ole valinnut aikavyöhykettä, joten et voi asettaa muistutuksia. Aseta se
profiilisivullasi .'
+ reminders:
+ at_desktop: "Ensi kerralla kun olen työpöytäympäristössäni"
+ later_today: "Myöhemmin tänään
{{date}}"
+ next_business_day: "Seuraavana arkipäivänä
{{date}}"
+ tomorrow: "Huomenna
{{date}}"
+ next_week: "Ensi viikolla
{{date}}"
+ next_month: "Ensi kuussa
{{date}}"
+ custom: "Valitse päivämäärä ja kellonaika"
drafts:
resume: "Jatka"
remove: "Poista"
@@ -309,8 +321,14 @@ fi:
install_banner: "Haluatko
asentaa sivuston %{title} tälle laitteelle? "
choose_topic:
none_found: "Yhtään ketjua ei löydetty."
+ title:
+ search: "Etsi ketjua"
+ placeholder: "Syötä ketjun otsikko, url tai id tähän"
choose_message:
none_found: "Keskusteluja ei löytynyt."
+ title:
+ search: "Etsi yksityiskeskustelua"
+ placeholder: "Syötä keskustelun otsikko, url tai id tähän"
review:
order_by: "Järjestä"
in_reply_to: "vastauksena"
@@ -558,6 +576,7 @@ fi:
leave: "Poistu"
request: "Pyyntö"
message: "Viesti"
+ confirm_leave: "Haluatko varmasti poistua ryhmästä?"
allow_membership_requests: "Salli käyttäjän lähettää jäsenhakemuksia ryhmien isännille"
membership_request_template: "Viestipohja, joka näytetään käyttäjälle, kun hän lähettää jäsenhakemusta ryhmään."
membership_request:
@@ -737,9 +756,13 @@ fi:
activity_stream: "Toiminta"
preferences: "Asetukset"
feature_topic_on_profile:
+ open_search: "Valitse uusi ketju"
+ title: "Valitse ketju"
+ search_label: "Etsi ketjua nimellä"
save: "Tallenna"
clear:
title: "Tyhjennä"
+ warning: "Haluatko varmasti nollata valikoimasi ketjun?"
profile_hidden: "Käyttäjän julkinen profiili ei ole nähtävillä."
expand_profile: "Laajenna"
collapse_profile: "Supista"
@@ -772,6 +795,7 @@ fi:
enable_quoting: "Ota käyttöön viestin lainaaminen tekstiä valitsemalla"
enable_defer: "Ota käyttöön lykkäystoiminto, jolla voi merkitä ketjun lukemattomaksi"
change: "vaihda"
+ featured_topic: "Valikoitu ketju"
moderator: "{{user}} on valvoja"
admin: "{{user}} on ylläpitäjä"
moderator_tooltip: "Tämä käyttäjä on valvoja"
@@ -955,6 +979,9 @@ fi:
change_card_background:
title: "Käyttäjäkortin taustakuva"
instructions: "Taustakuvan leveys on 590 pikseliä."
+ change_featured_topic:
+ title: "Valikoitu ketju"
+ instructions: "Käyttäjäkorttiisi ja profiilisivullesi tulee linkki tähän ketjuun."
email:
title: "Sähköposti"
primary: "Ensisijainen sähköpostiosoite"
@@ -1089,6 +1116,7 @@ fi:
search: "kirjoita etsiäksesi kutsuja..."
title: "Kutsut"
user: "Kutsuttu käyttäjä"
+ sent: "Lähetettiin viimeksi"
none: "Ei kutsuja, joita näyttää."
truncated:
one: "Näytetään ensimmäinen kutsu."
@@ -1731,6 +1759,7 @@ fi:
title: Etsi vain otsikoista
likes: joista olen tykännyt
posted: joihin olen kirjoittanut
+ created: jotka minä aloitin
watching: joita tarkkailen
tracking: joita seuraan
private: yksityiskeskusteluistani
@@ -1747,6 +1776,7 @@ fi:
label: Ketju/Ketjuun
open: on avoin
closed: on suljettu
+ public: ovat julkisia
archived: on arkistoitu
noreplies: ei ole vastattu
single_user: on kirjoittanut vain yksi käyttäjä
@@ -1834,6 +1864,13 @@ fi:
defer:
help: "Merkitse lukemattomaksi"
title: "Lykkää"
+ feature_on_profile:
+ help: "Lisää käyttäjäkorttiisi ja profiilisivullesi linkki tähän ketjuun"
+ title: "Valikoi ketju profiiliisi"
+ remove_from_profile:
+ warning: "Profiilillesi on jo valikoitu ketju. Jos jatkat, tämä ketju korvaa aiemman ketjun."
+ help: "Poista linkki tähän ketjuun käyttäjäprofiilissasi"
+ title: "Poista profiilista"
list: "Ketjut"
new: "uusi ketju"
unread: "lukematta"
@@ -2312,9 +2349,18 @@ fi:
notify_moderators: "ilmoitti valvojille"
notify_user: "lähetti viestin"
bookmark: "lisäsi tämän kirjanmerkkeihin"
+ like:
+ one: "tykkäsi tästä"
+ other: "tykkäsivät tästä"
+ read:
+ one: "luki tämän"
+ other: "lukivat tämän"
like_capped:
one: "ja {{count}} muu tykkäsi tästä"
other: "ja {{count}} muuta tykkäsi tästä"
+ read_capped:
+ one: "ja {{count}} muu lukivat tämän"
+ other: "ja {{count}} muuta lukivat tämän"
by_you:
off_topic: "Liputit tämän eksyvän aiheesta"
spam: "Liputit tämän roskapostiksi"
@@ -2365,7 +2411,10 @@ fi:
title: "Näytä sähköpostin HTML-osa"
button: "HTML"
bookmarks:
+ create: "Luo kirjanmerkki"
name: "Nimi"
+ name_placeholder: "Anna kirjanmerkille nimi muistisi virkistämiseksi"
+ set_reminder: "Aseta muistutus"
category:
can: "voivat… "
none: "(ei aluetta)"
@@ -2747,6 +2796,7 @@ fi:
mark_watching: "%{shortcut} Tarkkaile ketjua"
print: "%{shortcut} Tulosta ketju"
defer: "%{shortcut} Lykkää ketjua"
+ topic_admin_actions: "%{shortcut} Avaa ketjun ylläpitotyökalut"
badges:
earned_n_times:
one: "Ansaitsi tämän ansiomerkin yhden kerran"
@@ -3058,14 +3108,14 @@ fi:
delete: "Poista"
delete_confirm: "Poista tämä ryhmä?"
delete_failed: "Ryhmän poistaminen ei onnistu. Jos tämä on automaattinen ryhmä, sitä ei voi poistaa."
- delete_owner_confirm: "Poista omistajan etuudet käyttäjältä '%{username}'?"
+ delete_owner_confirm: "Peru käyttäjältä '%{username}' isännyys?"
add: "Lisää"
custom: "Mukautetut"
automatic: "Automaattiset"
default_title: "Oletustitteli"
default_title_description: "annetaan kaikille ryhmän jäsenille"
- group_owners: Omistajat
- add_owners: Lisää omistajia
+ group_owners: Isännät
+ add_owners: Lisää isäntiä
none_selected: "Aloita valitsemalla ryhmä"
no_custom_groups: "Luo uusi mukautettu ryhmä"
api:
@@ -3097,6 +3147,7 @@ fi:
new_key: Uusi rajapinta-avain
revoked: Jäädytetty
delete: Poista pysyvästi
+ not_shown_again: Tätä avainta ei näytetä toista kertaa. Varmistu että otat talteen ennen kuin jatkat.
continue: Jatka
web_hooks:
title: "Webhookit"
@@ -3366,6 +3417,7 @@ fi:
upload: "Lataa"
select_component: "Valitse komponentti"
unsaved_changes_alert: "Et ole tallentanut tekemiäsi muutoksia. Haluatko hylätä ne ja mennä muualle?"
+ unsaved_parent_themes: "Et ole osoittanut komponenttia millekään teemalle. Haluatko siirtyä eteenpäin?"
discard: "Hylkää"
stay: "Jää"
css_html: "Mukautettu CSS/HTML"
@@ -3687,6 +3739,7 @@ fi:
api_key_create: "loi rajapinta-avaimen"
api_key_update: "päivitti rajapinta-avainta"
api_key_destroy: "tuhosi rajapinta-avaimen"
+ override_upload_secure_status: "kävele yli latauksen turvastatuksesta"
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ä."
@@ -4153,6 +4206,14 @@ fi:
title: "Aloita valitsemalla olemassa oleva ansiomerkki tai luomalla uusi"
what_are_badges_title: "Mitä ansiomerkit ovat?"
badge_query_examples_title: "Esimerkkejä ansiomerkin tietokantakyselyistä"
+ mass_award:
+ title: Myönnä useille
+ description: Myönnä sama ansiomerkki useille käyttäjille samalla kertaa.
+ no_badge_selected: Aloita valitsemalla ansiomerkki.
+ perform: "Myönnä ansiomerkki käyttäjille"
+ upload_csv: Lataa CSV sähköpostiosoitteista
+ aborted: "Lataa CSV, jossa on käyttäjien sähköpostiosoitteet"
+ success: CSV tuli perille ja käyttäjät saavat ansiomerkkinsä pian.
emoji:
title: "Emoji"
help: "Lisää uusi emoji joka on kaikkien käytettävissä. (Voit raahata useita tiedostoja kerralla)"
diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml
index 046a271bd6..17e33d6c7e 100644
--- a/config/locales/client.fr.yml
+++ b/config/locales/client.fr.yml
@@ -27,8 +27,7 @@ fr:
millions: "{{number}}M"
dates:
time: "HH:mm"
- time_short_day: "dddd [à] HH:MM"
- month_day_time: "[Le] D MMMM [à] HH:MM"
+ time_short_day: "ddd [à] HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "DD MMM H:mm"
long_no_year_no_time: "D MMM"
@@ -154,6 +153,7 @@ fr:
banner:
enabled: "a mis à la une %{when}. Il sera affiché en haut de chaque page jusqu’à ce qu'il soit ignoré par un utilisateur."
disabled: "a supprimé de la une %{when}. Il ne sera plus affiché en haut de chaque page."
+ topic_admin_menu: "actions du sujet"
wizard_required: "Bienvenue sur votre nouveau Discourse ! Démarrons par
l'assistant de configuration ✨"
emails_are_disabled: "Le courriel sortant a été désactivé par un administrateur. Aucune notification courriel ne sera envoyée."
bootstrap_mode_enabled: "Pour rendre le lancement de votre site plus facile, vous êtes en mode 'bootstrap'. Tout nouvel utilisateur sera accordé le niveau de confiance 1 et aura les résumés par courriel hebdomadaires activés. Ceci cessera d'être le cas lorsque %{min_users} utilisateurs auront rejoints le site."
@@ -267,7 +267,7 @@ fr:
remove: "Retirer le signet"
confirm_clear: "Êtes-vous sûr de vouloir retirer tous les signets de ce sujet ?"
save: "Sauvegarder"
- no_timezone: "Vous n'avez pas encore défini de fuseau horaire. Vous ne pourrez pas définir de rappels. Configurez-en un
dans votre profil ."
+ no_timezone: 'Vous n'avez pas encore défini de fuseau horaire. Vous ne pourrez pas définir de rappels. Configurez-en un
dans votre profil .'
reminders:
at_desktop: "La prochaine fois je suis à mon bureau"
later_today: "Plus tard dans la journée
{{date}}"
@@ -576,6 +576,7 @@ fr:
leave: "Quitter"
request: "Requête"
message: "Message"
+ confirm_leave: "Êtes-vous sûr(e) de vouloir quitter ce groupe ?"
allow_membership_requests: "Autoriser les utilisateurs à envoyer des demandes d'adhésion aux propriétaires de groupe"
membership_request_template: "Modèle personnalisé à afficher aux utilisateurs lors de l'envoi d'une demande d'adhésion"
membership_request:
@@ -755,9 +756,13 @@ fr:
activity_stream: "Activité"
preferences: "Préférences"
feature_topic_on_profile:
+ open_search: "Sélectionnez un nouveau sujet"
+ title: "Sélectionnez un sujet"
+ search_label: "Rechercher des sujet grâce au titre"
save: "Sauvegarder"
clear:
title: "Vider"
+ warning: "Êtes-vous sûr de vouloir effacer ce sujet à la une ?"
profile_hidden: "Le profil public de cet usagé est caché."
expand_profile: "Développer"
collapse_profile: "Réduire"
@@ -977,6 +982,7 @@ fr:
instructions: "Les images d'arrière-plan seront centrées avec une largeur par défaut de 590 pixels."
change_featured_topic:
title: "Sujet à la une"
+ instructions: "Un lien vers ce sujet sera ajouté sur votre carte d'utilisateur et votre profil."
email:
title: "Courriel"
primary: "Adresse courriel principale"
@@ -1112,6 +1118,7 @@ fr:
search: "commencer à saisir pour rechercher vos invitations…"
title: "Invitations"
user: "Utilisateurs"
+ sent: "Derniers envoyés"
none: "Il n'y a pas d'invitations en attente à afficher."
truncated:
one: "Afficher la première invitation."
@@ -1754,6 +1761,7 @@ fr:
title: Seuls les titres correspondent
likes: que j'ai aimé
posted: auxquels j'ai participé
+ created: J'ai créer
watching: que je surveille
tracking: que je suis
private: qui sont dans mes messages directs
@@ -1770,6 +1778,7 @@ fr:
label: Dont les sujets
open: sont ouverts
closed: sont fermés
+ public: sont publiques
archived: sont archivés
noreplies: n'ont aucune réponse
single_user: contiennent un unique utilisateur
@@ -2342,9 +2351,18 @@ fr:
notify_moderators: "signalé aux modérateurs"
notify_user: "a envoyé un message"
bookmark: "signet ajouté"
+ like:
+ one: "a aimé ceci"
+ other: "ont aimé ceci"
+ read:
+ one: "a lu ceci"
+ other: "ont lu ceci"
like_capped:
one: "et {{count}} autre a aimé ça"
other: "et {{count}} autres ont aimé ça"
+ read_capped:
+ one: "et {{count}} autre a lu ceci"
+ other: "et {{count}} autres ont lu ceci"
by_you:
off_topic: "Vous l'avez signalé comme étant hors-sujet"
spam: "Vous l'avez signalé comme étant du spam"
@@ -2785,6 +2803,7 @@ fr:
mark_watching: "%{shortcut} Surveiller le sujet"
print: "%{shortcut} Imprimer le sujet"
defer: "%{shortcut} Reporter le sujet"
+ topic_admin_actions: "%{shortcut} Ouvrir les actions d'administration sur ce sujet"
badges:
earned_n_times:
one: "A reçu ce badge %{count} fois"
@@ -4204,6 +4223,14 @@ fr:
title: "Choisir un badge existant ou en créer un nouveau pour commencer"
what_are_badges_title: "Qu'est-ce que les badges ?"
badge_query_examples_title: "Exemple de requête de badge"
+ mass_award:
+ title: Accorder en Masse
+ description: Accorder le même badge à plusieurs utilisateurs à la fois.
+ no_badge_selected: Merci de sélectionner un badge pour commencer.
+ perform: "Accorder un Badge aux Utilisateurs"
+ upload_csv: Envoyer un CSV avec les courriels des utilisateurs
+ aborted: Merci d'envoyer un CSV contenant des courriels d'utilisateurs
+ success: Votre CSV a été envoyé et les utilisateurs recevront leur badge rapidement.
emoji:
title: "Emoji"
help: "Ajouter un nouvel emoji qui sera disponible pour tout le monde. (Conseil : glisser-déposer plusieurs fichiers en même temps)"
diff --git a/config/locales/client.he.yml b/config/locales/client.he.yml
index b4a4dde294..d69f3f72a3 100644
--- a/config/locales/client.he.yml
+++ b/config/locales/client.he.yml
@@ -29,8 +29,7 @@ he:
millions: "{{number}} מיליון"
dates:
time: "h:mm a"
- time_short_day: "ddd HH:mm"
- month_day_time: "D בMMM, HH:mm"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D בMMM HH:mm"
long_no_year_no_time: "D בMMM"
@@ -314,9 +313,9 @@ he:
not_bookmarked: "סמנו פוסט זה עם סימנייה"
created_with_reminder: "סימנת את הפוסט הזה עם תזכורת ב־%{date}"
remove: "הסרה מהסימניות"
- confirm_clear: "האם אתם בטוחים שאתם מעוניינים לנקות את כל הסימניות מנושא זה?"
+ confirm_clear: "לנקות את כל הסימניות מנושא זה?"
save: "שמירה"
- no_timezone: "עדיין לא הגדרת אזור זמן. לא תהיה לך אפשרות להגדיר תזכורות. ניתן להגדיר אותו
בפרופיל שלך ."
+ no_timezone: 'עדיין לא הגדרת אזור זמן. לא תהיה לך אפשרות להגדיר תזכורות. ניתן להגדיר אותו
בפרופיל שלך .'
reminders:
at_desktop: "בשימוש הבא דרך שולחן העבודה"
later_today: "המשך היום
{{date}}"
@@ -800,7 +799,7 @@ he:
read_time: "זמן צפייה"
topics_entered: "כניסה לנושאים"
post_count: "# פוסטים"
- confirm_delete_other_accounts: "אתם בטוחים שברצונכם להסיר חשבונות אלו?"
+ confirm_delete_other_accounts: "להסיר חשבונות אלו?"
powered_by: "משתמש
MaxMindDB "
copied: "הועתק"
user_fields:
@@ -812,7 +811,7 @@ he:
edit: "עריכת העדפות"
download_archive:
button_text: "להוריד הכל"
- confirm: "האם אתם בטוחים שאתם מעוניינים להוריד אליכם את הפוסטים שלכם?"
+ confirm: "להוריד את הפוסטים שלך?"
success: "הורדה החלה, תקבלו הודעה כאשר התהליך הסתיים."
rate_limit_error: "ניתן להוריד פוסטים פעם ביום, אנא נסו שוב מחר."
new_private_message: "הודעה חדשה"
@@ -858,7 +857,7 @@ he:
not_supported: "התראות לא נתמכות בדפדפן זה. מצטערים."
perm_default: "הפעלת התראות"
perm_denied_btn: "הרשאות נדחו"
- perm_denied_expl: "מנעתם הרשאה לקבלת התראות. אפשרו התראות בהגדרות הדפדפן שלכם."
+ perm_denied_expl: "דחית הרשאה לקבלת התראות. יש לאפשר התראות בהגדרות הדפדפן שלך."
disable: "כבוי התראות"
enable: "אפשר התראות"
each_browser_note: "הערה: עליך לשנות הגדרה זו עבור כל דפדפן בנפרד."
@@ -890,13 +889,13 @@ he:
label: "מצב רשימת תפוצה"
enabled: "אפשר מצב רשימת תפוצה"
instructions: |
- הגדרה זו באה במקום ההגדרות של ״סיכום פעילות״.
- נושאים וקטגוריות שהושתקו לא יכללו במיילים אלו.
- individual: "שליחת מייל עבור כל פוסט חדש"
- individual_no_echo: "שלח מייל עבור כל פוסט חדש מלבד שלי"
- many_per_day: "שלחו לי מייל עבור כל פוסט חדש (בערך {{dailyEmailEstimate}} ביום)"
- few_per_day: "שלחו לי מייל עבור כל פוסט חדש (בערך 2 ביום)"
- warning: "מצב רשימת תפוצה מופעל. הגדרות הודעת האימייל נדחות."
+ הגדרה זו דורסת את הגדרת „סיכום פעילות”.
+ נושאים וקטגוריות שהושתקו לא יופיעו בהודעות דוא״ל אלו.
+ individual: "לשלוח לי דוא״ל על כל פוסט חדש"
+ individual_no_echo: "לשלוח לי דוא״ל על כל פוסט חדש מלבד שלי"
+ many_per_day: "לשלוח לי דוא״ל על כל פוסט חדש (בערך {{dailyEmailEstimate}} ביום)"
+ few_per_day: "לשלוח לי דוא״ל על כל פוסט חדש (בערך 2 ביום)"
+ warning: "מצב רשימת תפוצה מופעל. מצב זה משבית את הגדרות ההתראות בדוא״ל."
tag_settings: "תגיות"
watched_tags: "נצפה"
watched_tags_instructions: "תעקבו באופן אוטומטי אחרי כל הנושאים עם התגיות הללו. תקבלו התראה על כל הפרסומים והנושאים החדשים. מספר הפרסומים יופיע לצד כותרת הנושא."
@@ -917,7 +916,7 @@ he:
muted_categories_instructions_dont_hide: "לא תישלחנה אליך התראות על שום דבר בנוגע לנושאים בקטגוריות האלו."
no_category_access: "בתור פיקוח יש לך גישה מוגבלת לקטגוריות, שמירה מנוטרלת."
delete_account: "מחק את החשבון שלי"
- delete_account_confirm: "אתם בטוחים שברצונכם להסיר את החשבון? לא ניתן לבטל פעולה זו!"
+ delete_account_confirm: "להסיר את החשבון? לא ניתן לבטל פעולה זו!"
deleted_yourself: "חשבונך נמחק בהצלחה."
delete_yourself_not_allowed: "נא לפנות לחבר סגל אם ברצונך למחוק את חשבונך."
unread_message_count: "הודעות"
@@ -998,7 +997,7 @@ he:
rate_limit: "אנא המתינו לפני שתנסו קוד אישור אחר."
enable_description: |
יש לסרוק את קוד ה־QR הזה ביישומון נתמך (
Android –
iOS ) ולהקליד את קוד האימות שלך.
- disable_description: "אנא הכניסו את קוד האישור מהיישומון שלכם."
+ disable_description: "נא למלא את קוד האישור מהיישומון שלך"
show_key_description: "הכנס ידנית"
short_description: |
הגנה על החשבון שלך עם קודים חד־פעמיים לאבטחה.
@@ -1045,13 +1044,13 @@ he:
change_avatar:
title: "שינוי תמונת הפרופיל"
gravatar: "
Gravatar , מבוסס על"
- gravatar_title: "שנו את הדמות שלכם באתר-Gravatar"
+ gravatar_title: "החלפת הדמות שלך ב־Gravatar"
gravatar_failed: "לא נמצא אווטר עם כתובת הדאו\"ל הזו."
- refresh_gravatar_title: "רענון האווטר שלכם"
+ refresh_gravatar_title: "רענון דמות ה־Gravatar שלך"
letter_based: "תמונת פרופיל משובצת מהמערכת"
uploaded_avatar: "תמונה אישית"
uploaded_avatar_empty: "הוסיפו תמונה אישית"
- upload_title: "העלו את התמונה שלכם"
+ upload_title: "העלאת התמונה שלך"
image_is_not_a_square: "אזהרה: קיצצנו את התמונה שלך; האורך והרוחב לא היו שווים."
change_profile_background:
title: "כותרת פרופיל"
@@ -1064,20 +1063,20 @@ he:
instructions: "קישור לנושא הזה יופיע בכרטיס המשתמש ובפרופיל שלך."
email:
title: "דואר אלקטרוני"
- primary: "כתובת דוא\"ל ראשית"
- secondary: "כתובות דוא\"ל משניות"
- no_secondary: "אין כתובות דוא\"ל משניות"
+ primary: "כתובת דוא״ל ראשית"
+ secondary: "כתובות דוא״ל משניות"
+ no_secondary: "אין כתובות דוא״ל משניות"
sso_override_instructions: "ניתן לעדכן את כתובת הדוא״ל דרך ספק ה־SSO."
instructions: "לעולם לא מוצג לציבור."
ok: "נשלח אליכם דואר אלקטרוני לאישור"
invalid: "בבקשה הכניסו כתובת דואר אלקטרוני תקינה"
authenticated: "כתובת הדואר האלקטרוני שלך אושרה על ידי {{provider}}"
- frequency_immediately: "נשלח לכם מייל מיידית אם לא קראתם את מה ששלחנו לכם עליו מייל."
+ frequency_immediately: "נשלח לך הודעה בדוא״ל מיידית אם טרם קראת את מה ששלחנו לך קודם."
frequency:
- one: "נשלח אליכם דוא\"ל רק אם לא ראינו אתכם בדקה האחרונה."
- two: "נשלח אליכם דוא\"ל רק אם לא ראינו אתכם ב{{count}} הדקות האחרונות."
- many: "נשלח אליכם דוא\"ל רק אם לא ראינו אתכם ב{{count}} הדקות האחרונות."
- other: "נשלח אליכם דוא\"ל רק אם לא ראינו אתכם ב{{count}} הדקות האחרונות."
+ one: "נשלח לך הודעה בדוא״ל רק אם לא הופעת בדקה האחרונה."
+ two: "נשלח לך הודעה בדוא״ל רק אם לא הופעת ב־{{count}} הדקות האחרונות."
+ many: "נשלח לך הודעה בדוא״ל רק אם לא הופעת ב־{{count}} הדקות האחרונות."
+ other: "נשלח לך הודעה בדוא״ל רק אם לא הופעת ב־{{count}} הדקות האחרונות."
associated_accounts:
title: "חשבונות מקושרים"
connect: "התחבר"
@@ -1153,12 +1152,12 @@ he:
first_time: "בפעם הראשונה שמישהו אוהב פוסט"
never: "אף פעם"
email_previous_replies:
- title: "כלול תגובות קודמות בתחתית המיילים"
+ title: "לכלול תגובות קודמות בתחתית הודעות הדוא״ל"
unless_emailed: "אלא אם נשלח לפני כן"
always: "תמיד"
never: "אף פעם"
email_digests:
- title: "כאשר אינני מבקר/ת פה, שלחו לי מייל מסכם של נושאים ותגובות פופולאריים"
+ title: "כשלא ביקרתי כאן תקופה, נא לשלוח לי סיכום בדוא״ל של נושאים ותגובות נפוצים"
every_30_minutes: "מידי 30 דקות"
every_hour: "שעתי"
daily: "יומית"
@@ -1166,13 +1165,13 @@ he:
every_month: "כל חודש"
every_six_months: "כל שישה חודשים"
email_level:
- title: "שלחו לי דוא\"ל כשמישהם מצטטים אותי, מגיבים לפוסט שלי, מזכירים את @שם-המשתמש/ת שלי, או מזמינים אותי לנושא"
+ title: "נא לשלוח לי דוא״ל כשמצטטים אותי, מגיבים לפוסט שלי, מזכירים את @שם-המשתמש שלי, או מזמינים אותי לנושא"
always: "תמיד"
only_when_away: "רק בזמן העדרות"
never: "אף פעם"
- email_messages_level: "שלחו לי דוא\"ל כשמישהם שולחים לי הודעות"
- include_tl0_in_digests: "כללו תכנים ממשתמשים חדשים במיילים מסכמים"
- email_in_reply_to: "הכללת ציטוטים מתגובות לפרסומים שנשלחו בדוא\"ל"
+ email_messages_level: "נא לשלוח לי דוא״ל כשכשנשלחות אלי הודעות"
+ include_tl0_in_digests: "לכלול תכנים ממשתמשים חדשים בהודעות סיכום בדוא״ל"
+ email_in_reply_to: "לכלול ציטוטים מתגובות לפוסטים בתוכן הדוא״ל"
other_settings: "אחר"
categories_settings: "קטגוריות"
new_topic_duration:
@@ -1223,7 +1222,7 @@ he:
rescind_all_confirm: "להסיר את כל ההזמנות שתוקפן פג?"
reinvite: "משלוח חוזר של הזמנה"
reinvite_all: "שלח מחדש את כל ההזמנות"
- reinvite_all_confirm: "האם אתם בטוחים שאתם מעוניינים לשלוח מחדש את כל ההזמנות?"
+ reinvite_all_confirm: "לשלוח מחדש את כל ההזמנות?"
reinvited: "ההזמנה נשלחה שוב"
reinvited_all: "כל ההזמנות נשלחו מחדש!"
time_read: "זמן קריאה"
@@ -1232,19 +1231,19 @@ he:
create: "שליחת הזמנה"
generate_link: "העתקת קישור הזמנה"
link_generated: "קישור הזמנה יוצר בהצלחה!"
- valid_for: "קישור הזמנה תקף רק לכתובת המייל הזו: %{email}"
+ valid_for: "קישור ההזמנה תקף רק לכתובת דוא״ל זו: %{email}"
bulk_invite:
none: "עדיין לא הזמנתם לכאן אף אחד. שילחו הזמנות אישיות, או הזמינו הרבה אנשים ביחד על ידי
העלאת קובץ CSV ."
text: "הזמנה קבוצתית מקובץ"
success: "העלאת הקובץ החלה בהצלחה, תקבלו התראה באמצעות מסר כאשר התהליך יושלם."
error: "מצטערים, לקובץ צריך להיות פורמט CSV"
- confirmation_message: "את/ה עומד/ת לשלוח מייל לכל האנשים בקובץ המצורף"
+ confirmation_message: "פעולה זו תשלח הזמנות בדוא״ל לכל מי שבקובץ שהועלה."
password:
title: "סיסמה"
too_short: "הסיסמה שלך קצרה מידי."
common: "הסיסמה הזו נפוצה מידי."
same_as_username: "הסיסמה שלך זהה לשם המשתמש/ת שלך."
- same_as_email: "הסיסמה שלך זהה לכתובת הדוא\"ל שלך."
+ same_as_email: "הססמה שלך זהה לכתובת הדוא״ל שלך."
ok: "הסיסמה שלך נראית טוב."
instructions: "לפחות %{count} תווים"
summary:
@@ -1338,9 +1337,9 @@ he:
server: "שגיאת שרת"
forbidden: "גישה נדחתה"
unknown: "תקלה"
- not_found: "העמוד אותו אתם מחפשים לא נמצא"
+ not_found: "העמוד לא נמצא"
desc:
- network: "אנא בדקו את החיבור שלכם"
+ network: "נא לבדוק את החיבור שלך."
network_fixed: "נראה שזה חזר לעבוד."
server: "קוד שגיאה: {{status}}"
forbidden: "אינכם רשאים לצפות בזה."
@@ -1396,7 +1395,7 @@ he:
hide_forever: "לא תודה"
hidden_for_session: "סבבה, השאלה תופיע מחר. תמיד ניתן להשתמש ב‚כניסה’ גם כדי ליצור חשבון."
intro: "שלום! נראה שאתם נהנים מהדיון, אבל לא נרשמתם לחשבון עדיין."
- value_prop: "כשאתם נרשמים, אנחנו זוכרים בדיוק מה קראתם כך שכשתחזרו תמשיכו בדיוק איפה שהפסקתם. בנוסף תקבלו התראות דרך האתר ודרך הדואר האלקטרוני שלכם כשפוסטים חדשים נוצרים. ואתם יכולים לעשות לייק לפוסטים כדי לחלוק אהבה :heartpulse:"
+ value_prop: "בעת יצירת החשבון, אנו זוכרים במדויק מה קראת, לכן תמיד יתאפשר לך לחזור להיכן שהפסקת. נוסף על כך, יישלחו אליך התראות, כאן ודרך דוא״ל כשמתקבלת תגובה על משהו שכתבת. יש לך גם אפשרות לסמן לייק פוסטים שאהבת כדי להוסיף ולהפיץ אהבה. :heartpulse:"
summary:
enabled_description: "אתם צופים בסיכום נושא זה: הפוסטים המעניינים ביותר כפי שסומנו על ידי הקהילה."
description: "ישנן
{{replyCount}} תגובות."
@@ -1413,15 +1412,15 @@ he:
invite: "הזמינו אחרים..."
edit: "הוסף או הסר..."
leave_message: "האם אתה באמת רוצה לעזוב את ההודעה הזו?"
- remove_allowed_user: "האם אתם באמת רוצים להסיר את {{name}} מהודעה זו?"
- remove_allowed_group: "האם אתם באמת מעוניינים להסיר את {{name}} מהודעה זו?"
- email: "דוא\"ל"
+ remove_allowed_user: "להסיר את {{name}} מהודעה זו?"
+ remove_allowed_group: "להסיר את {{name}} מהודעה זו?"
+ email: "דוא״ל"
username: "שם משתמש"
last_seen: "נצפה"
created: "נוצר"
created_lowercase: "נוצר/ו"
trust_level: "דרגת אמון"
- search_hint: "שם משתמש/ת, דוא\"ל או כתובת IP"
+ search_hint: "שם משתמש, דוא״ל או כתובת IP"
create_account:
disclaimer: "עצם הרשמתך מביעה את הסכמתך ל
מדיניות הפרטיות ול
תנאי השירות ."
title: "יצירת חשבון חדש"
@@ -1431,10 +1430,10 @@ he:
action: "שכחתי את ססמתי"
invite: "נא להקליד את שם המשתמש וכתובת הדוא״ל שלך ואנו נשלח לך הודעה בדוא״ל לאיפוס ססמה."
reset: "איפוס ססמה"
- complete_username: "אם קיים חשבון שמתאים לשם המשתמש
%{username} , אתם אמורים לקבל בקרוב מייל עם הוראות לאיפוס הסיסמה."
- complete_email: "במידה והחשבון מתאים לכתובת
%{email} , אתם אמורים לקבל בקרוב מייל עם הוראות לאיפוס הסיסמה."
- complete_username_found: "מצאנו חשבון שתואם לשם המשתמש
%{username} , קרוב לודאי שתקבלו דוא\"ל עם הנחיות כיצד לאתחל את הסיסמה שלכם תוך זמן קצר."
- complete_email_found: "מצאנו חשבון תואם ל
%{email} . בתוך זמן קצר תקבלו אליו דוא\"ל עם הנחיות כיצד לאתחל את הסיסמה שלכם."
+ complete_username: "אם קיים חשבון שמתאים לשם המשתמש
%{username} , תוך זמן קצר אמורה להגיע אליך הודעה בדוא״ל עם הנחיות לאיפוס הססמה שלך."
+ complete_email: "אם החשבון מתאים לכתובת
%{email} , תוך זמן קצר אמורה להגיע אליך הודעה בדוא״ל עם הנחיות לאיפוס הססמה שלך."
+ complete_username_found: "נמצא חשבון שתואם לשם המשתמש
%{username} , קרוב לוודאי שתישלח אליך הודעה בדוא״ל עם הנחיות כיצד לאתחל את הססמה שלך תוך זמן קצר."
+ complete_email_found: "נמצא חשבון תואם ל־
%{email} . תוך זמן קצר תישלח אליו הודעה בדוא״ל עם הנחיות כיצד לאתחל את הססמה שלך."
complete_username_not_found: "שום חשבון אינו תואם לשם המשתמש
%{username} "
complete_email_not_found: "שום חשבון אינו תואם ל
%{email} "
help: "ההודעה אינה מגיעה אליך לתיבת הדוא״ל? נא לבדוק את תיקיית הזבל/ספאם קודם.
לא ברור לך באיזו כתובת דוא״ל השתמשת? נא להקליד כתובת דוא״ל ואנו ניידע אותך אם היא קיימת כאן.
אם כבר אין לך גישה לכתובת הדוא״ל של החשבון שלך, נא ליצור קשר עם הסגל המועיל שלנו.
"
@@ -1442,9 +1441,9 @@ he:
button_help: "עזרה"
email_login:
link_label: "נא לשלוח לי קישור לכניסה בדוא״ל"
- button_label: "עם אימייל"
- complete_username: "אם קיים חשבון שמתאים לשם המשתמש
%{username} , אתם אמורים לקבל בקרוב מייל עם קישור התחברות."
- complete_email: "אם קיים חשבון שמתאים ל
%{username} , אתם אמורים לקבל בקרוב מייל עם קישור התחברות."
+ button_label: "עם דוא״ל"
+ complete_username: "אם קיים חשבון שמתאים לשם המשתמש
%{username} , בקרוב אמורה להגיע אליך הודעה בדוא״ל עם קישור כניסה למערכת."
+ complete_email: "אם קיים חשבון שמתאים ל־
%{email} , בקרוב אמורה להגיע אליך הודעה בדוא״ל עם קישור כניסה למערכת."
complete_username_found: "נמצא חשבון תואם לשם המשתמש
%{username} , בקרוב תגיע אליך הודעה בדוא״ל עם קישור לכניסה."
complete_email_found: "נמצא חשבון תואם לשם
%{username} , בקרוב תגיע אליך הודעה בדוא״ל עם קישור לכניסה."
complete_username_not_found: "שום חשבון אינו תואם לשם המשתמש
%{username} "
@@ -1457,7 +1456,7 @@ he:
username: "משתמש"
password: "סיסמה"
second_factor_title: "אימות ב2 גורמים"
- second_factor_description: "אנא הכניסו את קוד האישור מהיישומון שלכם."
+ second_factor_description: "נא למלא את קוד האישור מהיישומון שלך:"
second_factor_backup: "כניסה עם קוד גיבוי"
second_factor_backup_title: "גיבוי דו־שלבי"
second_factor_backup_description: "נא להקליד אחד מהקודים לגיבוי שלך:"
@@ -1473,13 +1472,13 @@ he:
error: "שגיאה לא ידועה"
cookies_error: "כנראה שהעוגיות בדפדפן שלך מנוטרלות. אין אפשרות להיכנס מבלי להפעיל אותן."
rate_limit: "נא להמתין בטרם ביצוע ניסיון כניסה חוזר."
- blank_username: "אנא הכנס כתובת אימייל או שם משתמש."
- blank_username_or_password: "אנא הקישור את כתובת הדוא\"ל או שם המשתמש/ת שלכם וסיסמה."
+ blank_username: "נא למלא כתובת דוא״ל או שם משתמש."
+ blank_username_or_password: "נא למלא את כתובת הדוא״ל או את שם המשתמש שלך וססמה."
reset_password: "אפס סיסמה"
logging_in: "מתחבר...."
or: "או"
authenticating: "מאשר..."
- awaiting_activation: "החשבון שלכם ממתין להפעלה, השתמשו בקישור ה\"שכחתי סיסמה\" כדי ליצור עוד מייל הפעלה."
+ awaiting_activation: "החשבון שלך ממתין להפעלה, נא להשתמש בקישור „שכחתי ססמה” כדי לשלוח הודעת הפעלה נוספת."
awaiting_approval: "החשבון שלך טרם אושר על ידי חבר סגל. תישלח אליך הודעה בדוא״ל כשהוא יאושר."
requires_invite: "סליחה, גישה לפורום הזה היא בהזמנה בלבד."
not_activated: "אינך יכול להתחבר עדיין. שלחנו לך דואר אלקטרוני להפעלת החשבון לכתובת:
{{sentTo}} . יש לעקוב אחר ההוראות בדואר כדי להפעיל את החשבון."
@@ -1487,12 +1486,12 @@ he:
admin_not_allowed_from_ip_address: "הכניסה לניהול מכתובת IP זו אסורה."
resend_activation_email: "יש ללחוץ כאן לשליחת דואר אלקטרוני חוזר להפעלת החשבון."
omniauth_disallow_totp: "בחשבון שלך מופעל אימות ב2 גורמים. אנא התחבר עם הסיסמה שלך."
- resend_title: "שליחה מחדש של מייל הפעלה"
- change_email: "שינוי כתובת מייל"
- provide_new_email: "הזינו כתובת חדשה ונשלח מחדש את מייל האישור שלכם."
- submit_new_email: "עדכון כתובת מייל"
+ resend_title: "שליחה מחדש של הודעת הפעלה בדוא״ל"
+ change_email: "שינוי כתובת דוא״ל"
+ provide_new_email: "נא לספק כתובת חדשה ואנו נשלח מחדש בדוא״ל את הודעת האישור שלך."
+ submit_new_email: "עדכון כתובת דוא״ל"
sent_activation_email_again: "שלחנו לך הודעת דואר אלקטרוני נוספת להפעלת החשבון לכתובת
{{currentEmail}} . זה יכול לקחת כמה דקות עד שיגיע, לא לשכוח לבדוק את תיבת דואר הזבל."
- sent_activation_email_again_generic: "שלחו מייל הפעלה נוסף. ייתכן שיקחו מספר דקות להגעתו. שימו לב לבדוק את תיקיית הספאם שלכם."
+ sent_activation_email_again_generic: "שלחנו הודעת הפעלה נוספת בדוא״ל. ייתכן שיהיה עליך להמתין מספר דקות להגעתה. מוטב לבדוק גם את תיקיית הספאם שלך."
to_continue: "נא להיכנס"
preferences: "כדי לשנות את העדפות המשתמש ראשית יש להיכנס למערכת."
forgot: "אין לי את פרטי החשבון שלי"
@@ -1626,7 +1625,7 @@ he:
cannot_see_mention:
category: "הזכרת את {{username}} אבל לא תישלח אליו/ה התרעה עקב העדר גישה לקטגוריה זו. יהיה עליך להוסיף אותו/ה לקבוצה שיש לה גישה לקטגוריה הזו."
private: "הזכרתם את {{username}} אבל הוא/היא לא יקבלו התראה כיוון שהם לא יכולים לראות את ההודעה הפרטית הזו. תצטרכו להזמין אותם להודעה פרטית זו."
- duplicate_link: "נראה שהקישור שלכם ל
{{domain}} כבר פורסם בנושא זה על ידי
@{{username}} ב
תגובה ב{{ago}} - האם אתם בטוחים שאתם מעוניינים לפרסם אותו שוב?"
+ duplicate_link: "נראה שהקישור שלך אל
{{domain}} כבר פורסם בנושא הזה על ידי
@{{username}} כ
תגובה ב{{ago}} - לפרסם אותו שוב?"
reference_topic_title: "תגובה: {{title}}"
error:
title_missing: "יש להזין כותרת."
@@ -1659,7 +1658,7 @@ he:
reply_placeholder: "הקלידו כאן. השתמשו ב Markdown, BBCode או HTML כדי לערוך. גררו או הדביקו תמונות."
reply_placeholder_no_images: "הקלידו כאן. השתמשו בMarkdown, BBCode או HTML כדי לערוך."
reply_placeholder_choose_category: "נא לבחור בקטגוריה בטרם תחילת ההקלדה כאן."
- view_new_post: "צפו בפוסט החדש שלכם."
+ view_new_post: "הצגת הפוסט החדש שלך."
saving: "שומר"
saved: "נשמר!"
saved_draft: "טיוטאת פוסט בתהליך, לחצו כדי להמשיך."
@@ -1741,7 +1740,7 @@ he:
two: "{{count}} הודעות שלא נקראו"
many: "{{count}} הודעות שלא נקראו"
other: "{{count}} הודעות שלא קראו"
- title: "התראות אודות אזכור @שם, תגובות לפוסטים ולנושאים שלכם, הודעות, וכד'"
+ title: "התראות אזכור @שם, תגובות לפוסטים ולנושאים שלך, הודעות, וכו׳"
none: "לא ניתן לטעון כעת התראות."
empty: "לא נמצאו התראות."
post_approved: "הפוסט שלך אושר"
@@ -1783,12 +1782,12 @@ he:
other: "{{count}} הודעות בתיבת ה{{group_name}} שלך"
popup:
mentioned: '{{username}} הזכיר/ה אותך ב{{topic}}" - {{site_title}}"'
- group_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}}'
+ linked: 'הפוסט שלך קושר על ידי {{username}} מתוך „{{topic}}” - {{site_title}}'
watching_first_post: '{{username}} יצר נושא חדש "{{topic}}" - {{site_title}}'
confirm_title: "התראות הופעלו - %{site_title}"
confirm_body: "הצלחה! התראות הופעלו"
@@ -1822,7 +1821,7 @@ he:
remote_tip: "קישור לתמונה"
remote_tip_with_attachments: "קישור לתמונה או לקובץ {{authorized_extensions}}"
local_tip: "בחרו תמונות ממכשירכם"
- local_tip_with_attachments: "בחרו תמונות או קבצים ממכשיר {{authorized_extensions}} שלכם"
+ local_tip_with_attachments: "בחירת תמונות או קבצים מהמכשיר שלך {{authorized_extensions}}"
hint: "(ניתן גם לגרור לעורך להעלאה)"
hint_for_supported_browsers: "תוכלו גם לגרור או להדביק תמונות לעורך"
uploading: "מעלה"
@@ -1837,7 +1836,7 @@ he:
most_liked: "האהובים ביותר"
select_all: "בחירה של הכל"
clear_all: "נקוי של הכל"
- too_short: "מילת החיפוש שלכם קצרה מידי."
+ too_short: "ביטוי החיפוש שלך קצר מידי."
result_count:
one: "
תוצאה אחת עבור {{term}} "
two: "
{{count}}{{plus}} תוצאות עבור {{term}} "
@@ -1947,19 +1946,17 @@ he:
choose_append_tags: "בחרו תגים חדשים להוסיף לנושאים הללו:"
changed_tags: "התגיות של נושאים אלו השתנו."
none:
- unread: "אין לכם נושאים שלא נקראו."
- new: "אין לכם נושאים חדשים."
- read: "עדיין לא קראתם אף נושא."
+ unread: "אין לך נושאים שלא נקראו."
+ new: "אין לך נושאים חדשים."
+ read: "עדיין לא קראת אף נושא."
posted: "עדיין לא פרסמתם באף נושא."
latest: "אין נושאים אחרונים. זה עצוב."
bookmarks: "אין לך עדיין סימניות לנושאים."
category: "אין נושאים בקטגוריה {{category}}."
top: "אין נושאים מובילים."
educate:
- new: '
הנושאים החדשים שלכם יופיעו כאן.
כברירת מחדל, נושאים נחשבים חדשים ויופיעו עם האינדיקציה חדש אם הם נוצרו ב-2 הימים האחרונים
בקרו בעמוד ההעדפות שלכם כדי לשנות זאת.
'
- unread: '
הנושאים הלא נקראים שלכם יופיעו כאן.
כברירת מחדל, נושאים נחשבים כלא-נקראים ויציגו את הספירה 1 אם את/ה:
יצרת את הנושא קראת את הנושא במשך יותר מ-4 דקות
-
-
או אם הגדרת בצורה מפורשת את הנושא כנושא במעקב או בצפייה דרך מנגנון ההתראות המופיע בתחתית כל נושא.
בקר/י בעמוד ההעדפות שלך כדי לשנות זאת '
+ new: '
הנושאים החדשים שלך יופיעו כאן.
כבררת מחדל, נושאים נחשבים חדשים ויופיעו עם המחוון חדש אם הם נוצרו ביומיים האחרונים
ניתן לבקר בעמוד ההעדפות שלך כדי לשנות זאת.
'
+ unread: '
הנושאים שלא קראת יופיעו כאן.
כבררת מחדל, נושאים ייחשבו שטרם קראת אותם ויציגו את המונה 1 בספירה של הפריטים שלא נקראו אם:
יצרת את הנושא הגבת לנושא קראת את הנושא במשך יותר מ־4 דקות או אם בחרת לעקוב או לצפות בנושא דרך מנגנון ההתראות המופיע בתחתית כל נושא.
ניתן לבקר ב בעמוד ההעדפות שלך כדי לשנות זאת.
'
bottom:
latest: "אין עוד נושאים אחרונים."
posted: "אין עוד נושאים שפורסמו."
@@ -2112,7 +2109,7 @@ he:
auto_close_based_on_last_post: "נושא זה ייסגר %{duration} אחרי התגובה האחרונה."
auto_delete: "נושא זה יימחק אוטומטית %{timeLeft}."
auto_bump: "נושא זה יוקפץ אוטומטית %{timeLeft}."
- auto_reminder: "אתם תתוזכרו בנוגע לנושא זה %{timeLeft}."
+ auto_reminder: "תישלח אליך תזכורת בנוגע לנושא זה %{timeLeft}."
auto_close_title: "הגדרות סגירה אוטומטית"
auto_close_immediate:
one: "הפוסט האחרון בנושא הוא כבר בן שעה, אז הנושא ייסגר מיידית."
@@ -2140,7 +2137,7 @@ he:
notifications:
title: שנו את תדירות ההתראות על הנושא הזה
reasons:
- mailing_list_mode: "אתם במצב רשימת תפוצה, אז תיודעו לגבי תגובות לנושא זה באמצעות מייל."
+ mailing_list_mode: "מצב רשימת תפוצה פעיל, לכן תישלח אליך התראה בדוא״ל על תגובות לנושא זה."
"3_10": "תקבלו התראות כיוון שאתם צופים בתג שקשור לנושא זה."
"3_6": "תקבלו התראות כיוון שאתם עוקבים אחרי קטגוריה זו."
"3_5": "תקבלו התראות כיוון שהתחלתם לעקוב אחרי הנושא הזה אוטומטית."
@@ -2151,8 +2148,8 @@ he:
"2_4": "תראו ספירה של תגובות חדשות כיוון שפרסמתם תגובה לנושא זה."
"2_2": "תראו ספירה של תגובות חדשות כיוון שאתם עוקבים אחר נושא זה:"
"2": 'אתם תראו ספירה של תגובות חדשות כיוון ש
קראתם נושא זה .'
- "1_2": "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לכם."
- "1": "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לכם."
+ "1_2": "תישלח התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך."
+ "1": "תישלח התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך."
"0_7": "אתם מתעלמים מכל ההתראות בקטגוריה זו."
"0_2": "אתם מתעלמים מכל ההתראות בנושא זה."
"0": "אתם מתעלמים מכל ההתראות בנושא זה."
@@ -2170,10 +2167,10 @@ he:
description: "כמו רגיל, בנוסף מספר התגובות שלא נקראו יוצג לנושא זה."
regular:
title: "רגיל"
- description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לכם."
+ description: "תישלח התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך."
regular_pm:
title: "רגיל"
- description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לכם."
+ description: "תישלח התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך."
muted_pm:
title: "מושתק"
description: "לעולם לא תקבלו התראה בנוגע להודעה זו."
@@ -2209,7 +2206,7 @@ he:
help: "התחילו לערוך תגובה לנושא זה"
clear_pin:
title: "נקה נעיצה"
- help: "נקה סטטוס נעוץ של נושא זה כדי שהוא לא יופיע עוד בראש רשימת הנושאים שלכם"
+ help: "לנקות את מצב הנעיצה של הנושא הזה כדי שלא יופיע עוד בראש רשימת הנושאים שלך"
share:
title: "שיתוף"
extended_title: "שתף קישור"
@@ -2227,28 +2224,28 @@ he:
feature_topic:
title: "המליצו על נושא זה"
pin: "גרמו לנושא זה להופיע בראש קטגוריה {{categoryLink}} עד"
- confirm_pin: "יש לכם כבר {{count}} נושאים נעוצים. מספר גדול מידי של נושאים נעוצים עשויים להכביד על משתמשים חדשים או אנונימיים. האם אתם בטוחים שאתם רוצים לנעוץ נושא נוסף בקטגוריה זו? "
+ confirm_pin: "יש לך כבר {{count}} נושאים נעוצים. עודף נושאים נעוצים עשוי להכביד על משתמשים חדשים או אלמוניים. אכן לנעוץ נושא נוסף בקטגוריה זו? "
unpin: "הסרת נושא זה מראש הקטגוריה {{categoryLink}}."
unpin_until: "גרמו לנושא זה להופיע בראש הקטגוריה {{categoryLink}} או המתינו עד
%{until} ."
pin_note: "משתמשים יכולים לבטל עצמאית את נעיצת הנושא באופן פרטני."
pin_validation: "דרוש תאריך על מנת לנעוץ את הנושא."
not_pinned: "אין נושאים שננעצו בקטגוריה {{categoryLink}}."
already_pinned:
- one: "נושא שננעצו, נכון לעכשיו בקטגוריה {{categoryLink}}:
%{count} "
- two: "נושאים ננעצו, נכון לעכשיו, בקטגוריה {{categoryLink}}.:
{{count}} "
- many: "נושאים ננעצו, נכון לעכשיו, בקטגוריה {{categoryLink}}.:
{{count}} "
- other: "נושאים ננעצו, נכון לעכשיו, בקטגוריה {{categoryLink}}.:
{{count}} "
+ one: "נושא שנעוץ כרגע בקטגוריה {{categoryLink}}:
%{count} "
+ two: "נושאים שנעוצים כרגע בקטגוריה {{categoryLink}}:
{{count}} "
+ many: "נושאים שנעוצים כרגע בקטגוריה {{categoryLink}}:
{{count}} "
+ other: "נושאים שנעוצים כרגע בקטגוריה {{categoryLink}}:
{{count}} "
pin_globally: "גרמו לנושא זה להופיע בראש כל רשימות הנושאים עד"
- confirm_pin_globally: "יש לך כבר {{count}} נושאים הנעוצים באופן גלובאלי. עודף נושאים נעוצים עשוי להכביד על משתמשים חדשים או אנונימיים. האם אתם בטוחים שאתם מעוניינים לנעוץ נושא גלובאלי נוסף?"
+ confirm_pin_globally: "יש לך כבר {{count}} נושאים נעוצים באופן גלובלי. עודף נושאים נעוצים עשוי להכביד על משתמשים חדשים או אלמוניים. אכן לנעוץ נושא גלובלי נוסף?"
unpin_globally: "הסרת נושא זה מראש כל רשימות הנושאים."
unpin_globally_until: "הסירו נושא זה מראש כל רשימות הנושאים או המתינו עד
%{until} ."
global_pin_note: "משתמשים יכולים לבטל עצמאית את נעיצת הנושא באופן פרטני."
not_pinned_globally: "אין נושאים נעוצים גלובאלית."
already_pinned_globally:
- one: "נושאים שכרגע נעוצים גלובאלית:
%{count} "
- two: "נושאים שכרגע נעוצים גלובאלית:
{{count}} "
- many: "נושאים שכרגע נעוצים גלובאלית:
{{count}} "
- other: "נושאים שכרגע נעוצים גלובאלית:
{{count}} "
+ one: "נושא שכרגע נעוץ גלובלית:
%{count} "
+ two: "נושאים שכרגע נעוצים גלובלית:
{{count}} "
+ many: "נושאים שכרגע נעוצים גלובלית:
{{count}} "
+ other: "נושאים שכרגע נעוצים גלובלית:
{{count}} "
make_banner: "הפכו נושא זה לבאנר אשר מופיע בראש כל העמודים."
remove_banner: "הסרת הבאנר שמופיע בראש כל העמודים."
banner_note: "משתמשים יכולים לבטל את הבאנר על ידי סגירתו. רק פוסט אחד יכול לשמש כבאנר בזמן נתון."
@@ -2271,17 +2268,17 @@ he:
username_placeholder: "שם משתמש"
action: "שלח הזמנה"
help: "הזמינו אנשים אחרים לנושא זה דרך דואר אלקטרוני או התראות"
- to_forum: "נשלח מייל קצר המאפשר לחבריכם להצטרף באופן מיידי באמצעות לחיצה על קישור, ללא צורך בהתחברות למערכת הפורומים."
+ to_forum: "אנו נשלח הודעה קצרה בדוא״ל שתאפשר לחברים שלך להצטרף באופן מיידי על ידי לחיצה על קישור וללא צורך בכניסה למערכת."
sso_enabled: "הכניסו את שם המשתמש של האדם שברצונכם להזמין לנושא זה."
to_topic_blank: "הכניסו את שם המשתמש או כתובת הדואר האלקטרוני של האדם שברצונכם להזמין לנושא זה."
- to_topic_email: "הזנת כתובת אימייל. אנחנו נשלח הזמנה שתאפשר לחבריכם להשיב לנושא הזה."
+ to_topic_email: "מילאת כתובת דוא״ל. אנחנו נשלח לך הזמנה בדוא״ל שתאפשר לחבריך להשיב לנושא הזה מיידית."
to_topic_username: "הזנת שם משתמש/ת. נשלח התראה עם לינק הזמנה לנושא הזה. "
to_username: "הכנסתם את שם המשתמש של האדם שברצונכם להזמין. אנו נשלח התראה למשתמש זה עם קישור המזמין אותו לנושא זה."
email_placeholder: "name@example.com"
- success_email: "שלחנו הזמנה ל:
{{emailOrUsername}} . נודיע לכם כשהזמנה תענה. בידקו את טאב ההזמנות בעמוד המשתמש שלכם בשביל לעקוב אחרי ההזמנות ששלחתם."
+ success_email: "שלחנו הזמנה אל
{{emailOrUsername}} . נודיע לך כשהזמנה תיענה. כדאי לבדוק את לשונית ההזמנות בעמוד המשתמש שלך כדי לעקוב אחר ההזמנות ששלחת."
success_username: "הזמנו את המשתמש להשתתף בנושא."
error: "מצטערים, לא יכלנו להזמין משתמש/ת אלו. אולי הם כבר הוזמנו בעבר? (תדירות שליחת ההזמנות מוגבלת)"
- success_existing_email: "משתמש עם כתובת דוא\"ל
{{emailOrUsername}} כבר קיים. הזמנו המשתמש להשתתף בנושא."
+ success_existing_email: " כבר קיים משתמש עם כתובת הדוא״ל
{{emailOrUsername}} . נשלחה הזמנה למשתמש להשתתף בנושא."
login_reply: "יש להיכנס כדי להשיב"
filters:
n_posts:
@@ -2425,20 +2422,20 @@ he:
two: "{{count}} אנשים אהבו את התגובה הזו"
many: "{{count}} אנשים אהבו את התגובה הזו"
other: "{{count}} אנשים אהבו את התגובה הזו"
- has_likes_title_only_you: "אתם אהבתם את התגובה הזו"
+ has_likes_title_only_you: "אהבת את התגובה הזו"
has_likes_title_you:
one: "אתם ועוד מישהו אהבתם את הפוסט הזה"
two: "אתם ו {{count}} אנשים אחרים אהבתם את הפוסט הזה"
many: "אתם ו {{count}} אנשים אחרים אהבתם את הפוסט הזה"
other: "אתם ו {{count}} אנשים אחרים אהבתם את הפוסט הזה"
errors:
- create: "סליחה, הייתה שגיאה ביצירת הפוסט שלכם. אנא נסו שנית."
- edit: "סליחה, הייתה שגיאה בעריכת הפוסט שלכם. אנא נסו שנית."
+ create: "אירעה שגיאה ביצירת הפוסט שלך. נא לנסות שוב, עמך הסליחה."
+ edit: "אירעה שגיאה בעריכת הפוסט שלך. נא לנסות שוב, עמך הסליחה."
upload: "סליחה, הייתה שגיאה בהעלאת הקובץ שלך. אנא נסו שנית"
file_too_large: "הקובץ הזה גדול מדי (הגודל המרבי הוא {{max_size_kb}} ק״ב), עמך הסליחה. למה שלא להעלות את הקובץ שלך לשירות שיתוף בענן ואז להדביק את הקישור?"
too_many_uploads: "סליחה, אך ניתן להעלות רק קובץ אחת כל פעם."
too_many_dragged_and_dropped_files: "אפשר להעלות עד {{max}} קבצים בכל פעם, עמך הסליחה."
- upload_not_authorized: "מצטערים, הקובץ שאתם מנסים להעלות אינו מורשה (סיומות מורשות: {{authorized_extensions}})."
+ upload_not_authorized: "הקובץ שמועמד להעלאה אינו מורשה (סיומות מורשות: {{authorized_extensions}}), עמך הסליחה."
image_upload_not_allowed_for_new_user: "סליחה, משתמשים חדשים לא יכולים להעלות תמונות."
attachment_upload_not_allowed_for_new_user: "סליחה, משתמשים חדשים לא יכולים להעלות קבצים."
attachment_download_requires_login: "מצטערים, עליכם להיות מחוברים כדי להוריד את הקבצים המצורפים."
@@ -2448,18 +2445,18 @@ he:
no_save_draft: "לא, לשמור טיוטה"
yes_value: "כן, להתעלם מהעריכה"
abandon:
- confirm: "האם אתם רוצים לנטוש את הפוסט שלכם?"
+ confirm: "לנטוש את הפוסט שלך?"
no_value: "לא, שמור אותו"
no_save_draft: "לא, לשמור טיוטה"
yes_value: "כן, נטוש"
- via_email: "פוסט זה הגיע באמצעות דוא\"ל"
- via_auto_generated_email: "פוסט זה הגיע דרך מייל שנוצר אוטומטית"
+ via_email: "פוסט זה הגיע בדוא״ל"
+ via_auto_generated_email: "פוסט זה הגיע דרך הודעת דוא״ל שנוצרה אוטומטית"
whisper: "פוסט זה הוא לחישה פרטית למפקחים"
wiki:
about: "הפוסט הוא ויקי"
archetypes:
save: "שמור אפשרויות"
- few_likes_left: "תודה שאתם מפזרים אהבה! נותרו לכם מעט לייקים להיום."
+ few_likes_left: "תודה על כל האהבה! נותרו לך סימוני לייק מועטים להיום."
controls:
reply: "התחילו לכתוב תגובה לפוסט זה"
like: "תנו לייק לפוסט זה"
@@ -2495,7 +2492,7 @@ he:
rebake: "בנייה מחודשת של HTML"
unhide: "הסרת הסתרה"
change_owner: "שינוי בעלות"
- grant_badge: "העניקו עיטור"
+ grant_badge: "הענקת עיטור"
lock_post: "נעילת פוסט"
lock_post_description: "למנוע מהמפרסם לערוך את הפוסט הזה"
unlock_post: "שחרור פוסט"
@@ -2591,13 +2588,13 @@ he:
raw_email:
displays:
raw:
- title: "הצגת מייל גולמי"
+ title: "הצגת הודעת הדוא״ל הגולמית"
button: "גולמי"
text_part:
- title: "הצגת טקסט כחלק מהמייל"
+ title: "הצגת חלק הטקסט בהודעת הדוא״ל"
button: "טקסט"
html_part:
- title: "הצגת חלק ה HTML של המייל"
+ title: "הצגת חלק ה־HTML בהודעת הדוא״ל"
button: "HTML"
bookmarks:
create: "יצירת סימנייה"
@@ -2659,9 +2656,9 @@ he:
pending_permission_change_alert: "לא הוספת %{group} לקטגוריה הזאת, יש ללחוץ על הכפתור הזה כדי להוסיף אותן."
images: "תמונות"
email_in: "כתובת דואר נכנס מותאמת אישית:"
- email_in_allow_strangers: "קבלת דוא\"ל ממשתמשים אנונימיים ללא חשבונות במערכת הפורומים"
- email_in_disabled: "אפשרות הפרסום של נושאים חדשים דרך דוא\"ל נוטרלה בהגדרות האתר. כדי לאפשר פרסום באמצעות משלוח דוא\"ל,"
- email_in_disabled_click: 'אפשרו את את ההגדרה "דוא"ל נכנס"'
+ email_in_allow_strangers: "קבלת דוא״ל ממשתמשים אלמוניים ללא חשבונות במערכת הפורומים"
+ email_in_disabled: "האפשרות לשליחת נושאים חדשים בדוא״ל הושבתה בהגדרות האתר. כדי להפעיל פרסום של נושאים חדשים דרך דוא״ל,"
+ email_in_disabled_click: 'לאפשר את את ההגדרה „דוא״ל נכנס”'
mailinglist_mirror: "קטגוריה שמשקפת רשימת תפוצה"
show_subcategory_list: "הצגת רשימת קטגוריות משנה מעל נושאים בקטגוריה זו."
num_featured_topics: "מספר הנושאים המוצגים בדף הקטגוריות:"
@@ -2671,7 +2668,7 @@ he:
sort_order: "סידור ברירת מחדל לנושאים:"
default_view: "תצוגת ברירת מחדל לנושאים:"
default_top_period: "פרק זמן דיפולטיבי להובלה"
- allow_badges_label: "הרשו לעיטורים להיות מוענקים בקטגוריה זו"
+ allow_badges_label: "לאפשר הענקת עיטורים בקטגוריה זו"
edit_permissions: "ערוך הרשאות"
reviewable_by_group: "בנוסף לסגל, פוסטים ודגלים בקטגוריה הזאת יכולים להיות נתונים גם לסקירתם של:"
review_group_name: "שם הקבוצה"
@@ -2698,7 +2695,7 @@ he:
description: "אתם תעקבו אוטומטית אחרי כל הנושאים בקטגוריות אלו. אתם תיודעו אם מישהו מזכיר את @שמכם או עונה לכם, וספירה של תגובות חדשות תופיע לכם."
regular:
title: "נורמלי"
- description: "תקבלו התראה אם מישהו יזכיר את @שם_המשתמש/ת שלכם או ישיב לכם."
+ description: "תישלח אליך התראה אם מישהו יזכיר את @שם_המשתמש שלך או ישיב לך."
muted:
title: "מושתק"
description: "לא תקבלו התראות על נושאים חדשים בקטגוריות אלו, והם לא יופיעו בעמוד הלא-נקראו שלך."
@@ -2740,7 +2737,7 @@ he:
notify_action: "הודעה"
official_warning: "אזהרה רשמית"
delete_spammer: "מחק ספאמר"
- delete_confirm_MF: "אתם עומדים להסיר {POSTS, plural, one {פוסט
אחד } other {
# פוסטים}} ו{TOPICS, plural, one {נושא
אחד } other {
# נושאים}} ממשתמש זה, להסיר את החשבון שלו, לחסום הרשמה מכתובת ה IP
{ip_address} , ולהוסיף את כתובת המייל שלו
{email} לרשימה שחורה. האם אתם בטוחים שמשתמש זה הוא באמת ספאמר?"
+ delete_confirm_MF: "פעולה זו תסיר {POSTS, plural, one {פוסט
אחד } other {
# פוסטים}} ו{TOPICS, plural, one {נושא
אחד } other {־
# נושאים}} שנכתבו על ידי משתמש זה, תסיר את החשבון שלו, תחסום הרשמה מכתובת ה־IP
{ip_address} ותוסיף את כתובת הדוא״ל שלו
{email} לרשימה שחורה קבועה. האם משתמש זה הוא בוודאות מפיץ זבל (ספאמר)?"
yes_delete_spammer: "כן, מחק ספאמר"
ip_address_missing: "(N/A)"
hidden_email_address: "(מוסתר)"
@@ -2753,7 +2750,7 @@ he:
inappropriate: "לא ראוי"
spam: "זהו ספאם"
custom_placeholder_notify_user: "היו ממוקדים, חיובים ותמיד אדיבים."
- custom_placeholder_notify_moderators: "ספרו לנו מה בדיוק מטריד אתכם וצרפו קישורים רלוונטיים ודוגמאות במידת האפשר."
+ custom_placeholder_notify_moderators: "נשמח לשמוע בדיוק מה מטריד אותך ולספק קישורים מתאימים ודוגמאות היכן שניתן."
custom_message:
at_least:
one: "הכניסו לפחות תו אחד"
@@ -2854,7 +2851,7 @@ he:
history: "היסטוריה"
changed_by: "מאת {{author}}"
raw_email:
- title: "מייל נכנס"
+ title: "דוא״ל נכנס"
not_available: "לא זמין!"
categories_list: "רשימת קטגוריות"
filters:
@@ -2870,7 +2867,7 @@ he:
help: "נושאים עם תגובות לאחרונה"
read:
title: "נקרא"
- help: "נושאים שקראתם, לפי סדר קריאתם"
+ help: "נושאים שקראת, לפי סדר קריאתם"
categories:
title: "קטגוריות"
title_in: "קטגוריה - {{categoryName}}"
@@ -3015,17 +3012,18 @@ he:
mark_watching: "%{shortcut} צפו בנושא"
print: "%{shortcut} הדפסת נושא"
defer: "%{shortcut} לדחות נושא לאחר כך"
+ topic_admin_actions: "%{shortcut} פתיחת פעולות ניהול לנושא"
badges:
earned_n_times:
- one: "הרוויחו עיטור זה פעם אחת"
- two: "הרוויחו עיטור זה %{count} פעמים"
- many: "הרוויחו עיטור זה %{count} פעמים"
- other: "הרוויחו עיטור זה %{count} פעמים"
+ one: "עיטור זה הוענק פעם אחת (%{count})"
+ two: "עיטור זה הוענק %{count} פעמים"
+ many: "עיטור זה הוענק %{count} פעמים"
+ other: "עיטור זה הוענק %{count} פעמים"
granted_on: "הוענק לפני %{date}"
others_count: "אחרים עם עיטור זה (%{count})"
title: עיטורים
- allow_title: "אתם יכולים להשתמש בעיטור זה ככותרת"
- multiple_grant: "אתם יכולים לזכות בזאת מספר פעמים"
+ allow_title: "ניתן להשתמש בעיטור זה ככותרת"
+ multiple_grant: "ניתן לזכות בו מספר פעמים"
badge_count:
one: "%{count} עיטורים"
two: "%{count} עיטורים"
@@ -3041,7 +3039,7 @@ he:
two: "%{count} הוענקו"
many: "%{count} הוענקו"
other: "%{count} הוענקו"
- select_badge_for_title: בחרו בעיטור לשימוש בכותרת שלכם
+ select_badge_for_title: נא לבחור עיטור לשימוש בכותרת שלך
none: "(ללא)"
successfully_granted: "העיטור %{badge} הוענק בהצלחה למשתמש %{username}"
badge_grouping:
@@ -3169,12 +3167,12 @@ he:
visible_only_to_staff: "התגיות גלויות בפני הסגל בלבד"
topics:
none:
- unread: "אין לכם נושאים שלא-נקראו."
- new: "אין לכם נושאים חדשים."
- read: "טרם קראתם נושאים."
+ unread: "אין לך נושאים שלא נקראו."
+ new: "אין לך נושאים חדשים."
+ read: "טרם קראת נושאים."
posted: "עדיין לא פרסמתם באף נושא."
latest: "אין נושאים אחרונים."
- bookmarks: "עדיין אין לכם נושאים מסומנים."
+ bookmarks: "עדיין אין לך נושאים מסומנים."
top: "אין נושאים מובילים."
bottom:
latest: "אין יותר נושאים אחרונים."
@@ -3187,8 +3185,8 @@ he:
invite:
custom_message: "ניתן להעניק להזמנה שלך מגע אישי יותר על ידי כתיבת
הודעה אישית ."
custom_message_placeholder: "הכניסו את הודעתכם האישית"
- custom_message_template_forum: "הי, כדאי לכם להצטרף לפורום הזה!"
- custom_message_template_topic: "הי, חשבתי שנושא זה יעניין אתכם!"
+ custom_message_template_forum: "היי, זה פורום מומלץ, כדאי להצטרף אליו!"
+ custom_message_template_topic: "היי, חשבתי שהנושא הזה יעניין אותך!"
forced_anonymous: "עקב עומס חריג, הודעה זו מוצגת באופן זמני לכולם כפי שתופיע בפני משתמשים שלא נכנסו למערכת."
safe_mode:
enabled: "מצב בטוח מאופשר, כדי לצאת ממנו סיגרו את חלון הדפדפן הזה"
@@ -3209,7 +3207,7 @@ he:
last_updated: "עדכון לוח המחוונים:"
discourse_last_updated: "עדכון Discourse:"
version: "גירסה"
- up_to_date: "אתם מעודכנים!"
+ up_to_date: "הכול עדכני!"
critical_available: "עדכון קריטי מוכן להתקנה."
updates_available: "עדכונים מוכנים."
please_upgrade: "בבקשה שדרג!"
@@ -3308,14 +3306,14 @@ he:
bulk_add:
title: "הוספה מרוכזת לקבוצה"
complete_users_not_added: "משתמשים אלו לא נוספו (וודאו שיש להם חשבון):"
- paste: "הדביקו רשימה של שמות משתמשים או כתובות אימייל, אחת בכל שורה:"
+ paste: "נא להדביק רשימה של שמות משתמשים או כתובות דוא״ל, אחת בכל שורה:"
add_members:
as_owner: "הגדרת משתמשים כבעלים של הקבוצה הזו"
manage:
interaction:
email: דואר אלקטרוני
- incoming_email: "התאימו אישית כתובת מייל נכנס"
- incoming_email_placeholder: "הכניסו כתובת מייל"
+ incoming_email: "כתובת דוא״ל נכנס בהתאמה אישית"
+ incoming_email_placeholder: "נא למלא כתובת דוא״ל"
visibility: חשיפה
visibility_levels:
title: "מי יכול לראות קבוצה זו?"
@@ -3334,8 +3332,8 @@ he:
trust_levels_title: "דרגת אמון המוענקת אוטומטית למשתמשים כאשר הם נוספים:"
effects: אפקטים
trust_levels_none: "ללא"
- automatic_membership_email_domains: "משתמשים אשר נרשמים עם מארח דוא\"ל שתואם בדיוק לאחד מהרשימה, יוספו באופן אוטומטי לקבוצה זו:"
- automatic_membership_retroactive: "החלת כלל מארח דוא\"ל זהה כדי להוסיף משתמשים רשומים"
+ automatic_membership_email_domains: "משתמשים שנרשמים עם שם תחום (דומיין) דוא״ל שתואם בדיוק לאחד מהרשימה, יתווספו באופן אוטומטי לקבוצה זו:"
+ automatic_membership_retroactive: "להחיל את אותו כלל שם תחום הדוא״ל כדי להוסיף משתמשים רשומים"
primary_group: "קבע כקבוצה ראשית באופן אוטומטי"
name_placeholder: "שם קבוצה, ללא רווחים, לפי הכללים של שמות משתמשים"
primary: "קבוצה ראשית"
@@ -3392,7 +3390,7 @@ he:
web_hooks:
title: "Webhooks"
none: "אין כרגע webhooks."
- instruction: "Webhooks מאפשרים ל Discourse להודיע לשירותים חיצוניים שאירוע מסויים מתרחש באתר שלכם. כאשר מופעל webhook, נשלחת בקשת POST ל URLs שמוגדרים."
+ instruction: "התליות (Webhooks) מאפשרות ל־Discourse להודיע לשירותים חיצוניים מתי מתרחש אירוע מסוים באתר שלך. כאשר ההתליה מתרחשת, תישלח בקשת POST לכתובות שצוינו."
detailed_instruction: "בקשת POST תישלח ל URL שמוגדר כאשר מתרחש האירוע שנבחר."
new: "Webhook חדש"
create: "יצירה"
@@ -3403,11 +3401,11 @@ he:
go_back: "חזרה לרשימה"
payload_url: "URL של התוכן"
payload_url_placeholder: "https://example.com/postreceive"
- warn_local_payload_url: "נראה שאתם מנסים להגדיר webhook ל url מקומי. אירוע שנשלח לכתובת מקומית עלול לגרום לתופעות בלתי-צפויות מראש. האם להמשיך?"
+ warn_local_payload_url: "נראה שניסית להגדיר את ההתליה (webhook) לכתובת מקומית. אירוע שנשלח לכתובת מקומית עשוי לגרום להשפעות חריגות או בלתי צפויות. להמשיך?"
secret_invalid: "אסור שהסוד יכיל תווי רווח כלשהם."
secret_too_short: "הסוד אמור להכיל לפחות 12 תווים."
secret_placeholder: "מחרוזת רשות, משמשת ליצירת חתימה"
- event_type_missing: "אתם צריכים לקבוע לפחות סוג אירועים אחד."
+ event_type_missing: "עליך לקבוע לפחות סוג אירועים אחד."
content_type: "סוג תוכן"
secret: "סוד"
event_chooser: "אילו ארועים תרצו שיפעילו את ה webhook הזה?"
@@ -3474,7 +3472,7 @@ he:
other: "הושלמו ב {{count}} שניות."
request: "בקשה"
response: "תשובה"
- redeliver_confirm: "האם אתם בטוחים שאתם רוצים לשלוח מחדש את אותו התוכן?"
+ redeliver_confirm: "לשלוח מחדש את אותו התוכן?"
headers: "כותרות"
payload: "תוכן"
body: "גוף"
@@ -3510,7 +3508,7 @@ he:
enable:
title: "הפעלת מצב קריאה-בלבד"
label: "אפשר קריאה-בלבד"
- confirm: "האם אתם בטוחים שאתם מעוניינים לאפשר מצב של קריאה-בלבד?"
+ confirm: "לאפשר מצב של קריאה בלבד?"
disable:
title: "בטל מצב קריאה-בלבד"
label: "בטל קריאה-בלבד"
@@ -3532,7 +3530,7 @@ he:
cancel:
label: "ביטול"
title: "בטל את הפעולה הנוכחית"
- confirm: "אתם בטוחים שברצונכם לבטל את הפעולה הנוכחית?"
+ confirm: "לבטל את הפעולה הנוכחית?"
backup:
label: "גבוי"
title: "צור גיבוי"
@@ -3540,20 +3538,20 @@ he:
without_uploads: "כן (לא לכלול הודעות)"
download:
label: "הורדה"
- title: "שליחת מייל עם לינק להורדה"
+ title: "שליחת הודעה בדוא״ל עם קישור להורדה"
alert: "קישור להורדת גיבוי זה נשלח אליכם."
destroy:
title: "הסר את הגיבוי"
- confirm: "אתם בטוחים שברצונכם להשמיד את הגיבוי הזה?"
+ confirm: "להשמיד את הגיבוי הזה?"
restore:
is_disabled: "שחזור אינו מאופשר לפי הגדרות האתר."
label: "שחזור"
title: "שחזר את הגיבוי"
- confirm: "האם אתם בטוחים שאתם מעוניינים לשחזר את הגיבוי הזה?"
+ confirm: "לשחזר את הגיבוי הזה?"
rollback:
label: "חזור לאחור"
title: "הזחר את מסד הנתונים למצב עבודה קודם"
- confirm: "האם אתם בטוחים שאתם מעוניינים להשיב את בסיס הנתונים למצב קודם?"
+ confirm: "להשיב את מסד הנתונים למצב קודם?"
location:
local: "אחסון מקומי"
s3: "S3"
@@ -3565,7 +3563,7 @@ he:
button_title:
user: "יצוא רשימת המשתמשים המלאה בפורמט CSV."
staff_action: "ייצוא רשימת פעולות הסגל בתצורת CSV."
- screened_email: "יצוא רשימת דוא\"ל מלאה בפורמט CSV"
+ screened_email: "ייצוא רשימת דוא״ל מלאה בתצורת CSV."
screened_ip: "יצוא רשימת IP מלאה בפורמט CSV"
screened_url: "יצוא רשימת URL מלאה בפורמט CSV"
export_json:
@@ -3594,7 +3592,7 @@ he:
email_templates:
title: "דוא״ל"
subject: "נושא"
- multiple_subjects: "תבנית מייל זו מכילה מספר נושאים."
+ multiple_subjects: "תבנית דוא״ל זו מכילה מספר נושאים."
body: "גוף"
none_selected: "בחרו תבנית דואר אלקטרוני לעריכה."
revert: "ביטול שינויים"
@@ -3614,7 +3612,7 @@ he:
create: "יצירה"
create_type: "סוג"
create_name: "שם"
- long_title: "התאמת צבעית, CSS ותכני HTML של האתר שלכם"
+ long_title: "התאמת צבעים, CSS ותכני HTML של האתר שלך"
edit: "עריכה"
edit_confirm: "זו ערכת עיצוב מרוחקת, אם ערכת CSS/HTML השינויים שלך יימחקו עם העדכון הבא של ערכת העיצוב."
update_confirm: "השינויים המקומיים האלה יימחקו על ידי העדכון. להמשיך?"
@@ -3650,7 +3648,7 @@ he:
and_x_more: "ו־{{count}} נוספות."
collapse: צמצום
uploads: "העלאות"
- no_uploads: "ניתן להעלות משאבים שקשורים לערכת העיצוב שלכם כגון גופנים ותמונות"
+ no_uploads: "ניתן להעלות משאבים שקשורים לערכת העיצוב שלך כגון גופנים ותמונות"
add_upload: "הוספת העלאה"
upload_file_tip: "נא לבחור משאב להעלאה (png, woff2 וכו׳)"
variable_name: "שם משתנה SCSS:"
@@ -3798,7 +3796,7 @@ he:
save_error_with_reason: "השינויים שלך לא נשמרו. %{error}"
instructions: "התאמת התבנית שמשמשת כתבנית להודעות html, לצד עיצוב עם CSS."
email:
- title: "מיילים"
+ title: "הודעות דוא״ל"
settings: "הגדרות"
templates: "תבניות"
preview_digest: "תצוגה מקדימה של תמצית"
@@ -3811,7 +3809,7 @@ he:
elided: "טקסט מושמט"
sending_test: "שולח דואר אלקטרוני לבדיקה..."
error: "
שגיאה - %{server_error}"
- test_error: "הייתה בעיה בשליחת הדואר האלקטרוני. בבקשה בידקו את ההגדרות שלכם ונסו שנית."
+ test_error: "אירעה תקלה בשליחת הודעת הדוא״ל לבדיקה. נא לבדוק את הגדרות הדוא״ל שלך ולאמת שהמארח שלך לא חוסם חיבורי דוא״ל נכנסים ולאחר מכן לנסות שוב."
sent: "נשלח"
skipped: "דולג"
bounced: "הוחזר"
@@ -3823,14 +3821,14 @@ he:
email_type: "סוג דואר אלקטרוני"
to_address: "לכתובת"
test_email_address: "כתובת דואר אלקטרוני לבדיקה"
- send_test: "שליחת מייל בדיקה"
+ send_test: "שליחת הודעת דוא״ל בדיקה"
sent_test: "נשלח!"
delivery_method: "שיטת העברה"
- preview_digest_desc: "תצוגה מקדימה של מייל תמצות שנשלח למשתמשים לא פעילים. "
+ preview_digest_desc: "תצוגה מקדימה של הודעת סיכום שנשלחת בדוא״ל למשתמשים לא פעילים. "
refresh: "רענן"
send_digest_label: "שילחו תוצאה זו אל:"
send_digest: "שליחה"
- sending_email: "שולחים מייל..."
+ sending_email: "נשלח דוא״ל…"
format: "פורמט"
html: "html"
text: "טקסט"
@@ -3844,14 +3842,14 @@ he:
cc_addresses: "העתק"
subject: "נושא"
error: "שגיאה"
- none: "לא נמצאו מיילים נכנסים."
+ none: "לא נמצאו הודעות דוא״ל נכנסות."
modal:
- title: "פרטי מייל שנכנס"
+ title: "פרטי דוא״ל נכנס"
error: "שגיאה"
headers: "כותרות"
subject: "נושא"
body: "גוף"
- rejection_message: "מייל דחייה"
+ rejection_message: "הודעות דוא״ל לדחייה"
filters:
from_placeholder: "from@example.com"
to_placeholder: "to@example.com"
@@ -3922,14 +3920,14 @@ he:
unsuspend_user: "ביטול השעיית משתמש"
removed_suspend_user: "השעיית משתמש (הוסר)"
removed_unsuspend_user: "ביטול השעיית משתמש (הוסר)"
- grant_badge: "העניקו עיטור"
- revoke_badge: "שללו עיטור"
- check_email: "בדיקת דוא\"ל"
+ grant_badge: "הענקת עיטור"
+ revoke_badge: "שלילת עיטור"
+ check_email: "בדיקת דוא״ל"
delete_topic: "מחיקת נושא"
recover_topic: "ביטול מחיקת נושא"
delete_post: "מחיקת פוסט"
impersonate: "התחזה"
- anonymize_user: "הפיכת משתמש/ת לאנונימיים"
+ anonymize_user: "הפיכת משתמש לאלמוני"
roll_up: "גלגול למעלה של בלוקים של IP"
change_category_settings: "שינוי הגדרות קטגוריה"
delete_category: "מחיקת קטגוריה"
@@ -3946,7 +3944,7 @@ he:
deleted_tag: "תגית נמחקה"
deleted_unused_tags: "נמחקו תגיות שאינן בשימוש"
renamed_tag: "תגית שונתה"
- revoke_email: "שללו מייל"
+ revoke_email: "שלילת דוא״ל"
lock_trust_level: "נעילת דרגת אמון"
unlock_trust_level: "שחרור דרגת אמון מנעילה"
activate_user: "הפעלת משתמש/ת"
@@ -4003,7 +4001,7 @@ he:
title: "כתובות IP מסוננות"
description: 'כתובות IP שנצפות כרגע. השתמש בכפתור "אפשר" בשביל לבטל חסימת כתובת'
delete_confirm: "האם ברצונך להסיר את הכלל עבור הכתובת %{ip_address}?"
- roll_up_confirm: "האם אתם בטוחים שאתם מעוניינים לגלגל למעלה (roll up) כתובות IP שבדרך-כלל מסוננות לכדי subnets?"
+ roll_up_confirm: "לצמצם כתובות IP מפוקחות לכדי מסכות רשת?"
rolled_up_some_subnets: "ערכי IP אסורים גולגלו בהצלחה לרשתות המשנה הבאות: %{subnets}."
rolled_up_no_subnet: "לא היה שום דבר לגלגל"
actions:
@@ -4085,7 +4083,7 @@ he:
not_found: "סליחה, שם המשתמש הזה אינו קיים במערכת שלנו."
id_not_found: "מצטערים, זהות המשתמש/ת אינה קיימת במערכת שלנו."
active: "מופעל"
- show_emails: "הצגת דוא\"לים"
+ show_emails: "הצגת כתובות דוא״ל"
hide_emails: "הסתרת כתובות דוא״ל"
nav:
new: "חדש"
@@ -4114,7 +4112,7 @@ he:
staged: "משתמשים מבוימים"
not_verified: "לא מאומת"
check_email:
- title: "חשיפת כתובת הדוא\"ל של המשתמש/ת"
+ title: "חשיפת כתובת הדוא״ל של המשתמש"
text: "הצגה"
user:
suspend_failed: "משהו נכשל בהשעיית המשתמש הזה {{error}}"
@@ -4149,7 +4147,7 @@ he:
clear_penalty_history:
title: "מחיקת היסטוריית עונשין"
description: "משתמשים עם עונשין לא יכולים להגיע לדרגת אמון 3"
- delete_all_posts_confirm_MF: "אתם עומדים להסיר {POSTS, plural, one {פוסט אחד} other {# פסוטים}} ו{TOPICS, plural, one {נושא אחד} other {# נושאים}}. האם אתם בטוחים?"
+ delete_all_posts_confirm_MF: "פעולה זו תסיר {POSTS, plural, one {פוסט אחד} other {# פוסטים}} ו{TOPICS, plural, one {נושא אחד} other {־# נושאים}}. להמשיך?"
silence: "השתקה"
unsilence: "ביטול השתקה"
silenced: "בהשתקה?"
@@ -4166,7 +4164,7 @@ he:
logged_out: "המשתמש יצא מהחשבון בכל המכשירים"
revoke_admin: "שלול ניהול ראשי"
grant_admin: "הענקת ניהול"
- grant_admin_confirm: "שלחנו אליכם מייל כדי לוודא את האדמיניסטרטור החדש. בבקשה פיתחו אותו ועיקבו אחר ההוראות."
+ grant_admin_confirm: "שלחנו אליך הודעה בדוא״ל כדי לאמת את חשבון הניהול החדש. נא לפתוח אותה ולעקוב אחר ההוראות."
revoke_moderation: "שלילת פיקוח"
grant_moderation: "הענקת פיקוח"
unsuspend: "ביטול השעייה"
@@ -4193,10 +4191,10 @@ he:
approve_success: "משתמש אושר ונשלחה לו הודעות דואר אלקטרוני עם הוראות הפעלה"
approve_bulk_success: "הצלחה! כל המשתמשים שנבחרו אושרו ויודעו על כך."
time_read: "זמן קריאה"
- anonymize: "הפיכת משתמשים לאנונימיים"
- anonymize_confirm: "האם אתם ב-ט-ו-ח-י-ם שאתם רוצים להפוך חשבון זה לאנונימי? פעולה זו תשנה את שם המשתמש וכתובת הדוא\"ל ותאתחל את כל המידע בפרופיל."
- anonymize_yes: "כן, הפיכת חשבון זה לאנונימי"
- anonymize_failed: "התרחשה בעיה בהפיכת חשבון זה לאנונימי."
+ anonymize: "הפיכת משתמשים לאלמוניים"
+ anonymize_confirm: "
באמת<\\b> להפוך חשבון זה לאלמוני? פעולה זו תשנה את שם המשתמש ואת כתובת הדוא״ל ותאפס את כל נתוני הפרופיל."
+ anonymize_yes: "כן, נא להפוך חשבון זה לאלמוני"
+ anonymize_failed: "אירעה תקלה בהפיכת חשבון זה לאלמוני."
delete: "מחק משתמש/ת"
delete_forbidden_because_staff: "לא ניתן למחוק מנהלים ומפקחים."
delete_posts_forbidden_because_staff: "לא ניתן להסיר את כל הפוסטים של מנהלי מערכת ומפקחים."
@@ -4216,7 +4214,7 @@ he:
many: "לא ניתן למחוק את כל הפוסטים בגלל שלמשתמשים יותר מ %{count} פוסטים. (delete_all_posts_max)"
other: "לא ניתן להסיר את כל הפוסטים בגלל שלמשתמשים יותר מ %{count} פוסטים. (delete_all_posts_max)"
delete_confirm: "בדרך כלל מומלץ להפוך משתמשים לאלמונים מאשר למחוק אותם כדי למנוע הסרת תוכן מדיונים קיימים. האם בוודאות ברצונך למחוק את המשתמש הזה? מדובר בפעולה בלתי הפיכה!"
- delete_and_block: "מחיקה וחסימת כתובת דוא\"ל וכתובת IP אלה"
+ delete_and_block: "מחיקה וחסימה של כתובות הדוא״ל וה־IP האלו"
delete_dont_block: "מחיקה בלבד"
deleting_user: "המשתמש נמחק…"
deleted: "המשתמש נמחק."
@@ -4240,11 +4238,11 @@ he:
deactivate_explanation: "חשבון משתמש מנוטרל נדרש לוודא דואר אלקטרוני מחדש."
suspended_explanation: "משתמש מושעה לא יכול להיכנס."
silence_explanation: "משתמש מושתק לא יכול לפרסם או לפתוח נושאים."
- staged_explanation: "משתמש מועמד יכול לפרסם רק באמצעות מייל בנושאים ספציפיים."
+ staged_explanation: "משתמש מועמד יכול לפרסם לנושאים מסוימים רק דרך דוא״ל."
bounce_score_explanation:
- none: "לא התקבלו החזרים לאחרונה מהמייל הזה."
- some: "כמה החזרים התרחשו לאחרונה מהמייל הזה."
- threshold_reached: "התקבלו יותר מידי החזרים מהמייל הזה."
+ none: "לא התקבלו החזרות לאחרונה מהדוא״ל הזה."
+ some: "מספר החזרות התקבלו לאחרונה מהדוא״ל הזה."
+ threshold_reached: "התקבלו יותר מדי החזרות מהדוא״ל הזה."
trust_level_change_failed: "הייתה בעיה בשינוי דרגת האמון של המשתמש."
suspend_modal_title: "השעה משתמש"
trust_level_2_users: "משתמשים בדרגת אמון 2"
@@ -4289,11 +4287,11 @@ he:
external_id: "ID חיצוני"
external_username: "שם משתמש"
external_name: "שם"
- external_email: "כתובת דוא\"ל"
+ external_email: "כתובת דוא״ל"
external_avatar_url: "כתובת URL לתמונת הפרופיל"
user_fields:
title: "שדות משתמש"
- help: "הוסיפו שדות שהמשתמשים שלכם יכולים למלא."
+ help: "הוספת שדות שהמשתמשים שלך יוכלו למלא."
create: "יצירת שדה משתמש"
untitled: "ללא שם"
name: "שם שדה"
@@ -4326,14 +4324,14 @@ he:
confirm: "אישור"
dropdown: "נגלל"
site_text:
- description: "אתם יכולים להתאים כל טקסט בפורום שלכם. בבקשה התחילו בחיפוש אחרי:"
+ description: "ניתן להתאים כל טקסט בפורום שלך. נא להתחיל בחיפוש שלהלן:"
search: "חפשו טקסט שברצונכם לערוך"
title: "טקסט"
edit: "ערוך"
revert: "בטל שינויים"
- revert_confirm: "האם אתם בטוחים שאתם מעוניינים לבטל את השינויים שלכם?"
+ revert_confirm: "לבטל את השינויים שלך?"
go_back: "חזרה לחיפוש"
- recommended: "אנחנו ממליצים להתאים את הטקסט הבא כדי להתאימו לצרכים שלכם:"
+ recommended: "אנו ממליצים לערוך את הטקסט הבא כדי שיתאים לצרכים שלך:"
show_overriden: "הציגו רק דרוסים"
more_than_50_results: "יש למעלה מ־50 תוצאות. נא למקד את החיפוש שלך."
settings:
@@ -4409,22 +4407,22 @@ he:
reason_help: (קישור לפוסט או לנושא)
save: שמור
delete: מחק
- delete_confirm: "אתם שברצונכם להסיר את העיטור הזה?"
+ delete_confirm: "למחוק את העיטור הזה?"
revoke: שלול
reason: סיבה
expand: הרחבה …
- revoke_confirm: "אתם בטוחים שברצונכם לשלול את העיטור הזה?"
- edit_badges: עירכו עיטורים
- grant_badge: העניקו עיטור
+ revoke_confirm: "לשלול את העיטור הזה?"
+ edit_badges: עריכת עיטורים
+ grant_badge: הענקת עיטור
granted_badges: עיטורים שהוענקו
grant: הענק
- no_user_badges: "ל%{name} לא הוענקו עיטורים."
+ no_user_badges: "למשתמש %{name} לא הוענקו עיטורים."
no_badges: אין עיטורים שניתן להעניק.
- none_selected: "בחרו עיטור כדי להתחיל"
- allow_title: אפשר לעיטור להיות בשימוש ככותרת.
+ none_selected: "נא לבחור עיטור כדי להתחיל"
+ allow_title: לאפשר להשתמש בעיטור ככותרת
multiple_grant: יכולים להינתן מספר פעמים
listable: הצגת עיטורים בעמוד העיטורים הפומבי
- enabled: הפעלת עיטורים
+ enabled: הפעלת עיטור
icon: סמליל
image: תמונה
icon_help: "נא להכניס שם של סמל מתוך FontAwesome (קידומת ‚far-’ לסמלים רגילים וב־‚fab-’ לסמלים ממותגים)"
@@ -4444,12 +4442,12 @@ he:
preview:
link_text: "הצגה מקדימה של עיטורים שהוענקו"
plan_text: "הצגה מקדימה עם query plan"
- modal_title: "הצגה מקדימה של שאילתת עיטורים (Badge Query Preview)"
+ modal_title: "הצגה מקדימה של שאילתת עיטורים"
sql_error_header: "התרחשה תקלה עם השאילתה"
- error_help: "ראו את הקישורים הבאים לעזרה עם שאילתת עיטורים."
+ error_help: "ניתן לעיין בקישורים הבאים לעזרה עם שאילתות עיטורים."
bad_count_warning:
header: "זהירות!"
- text: "חסרות דוגמאות הענקה. זה קורה כשחיפוש עיטורים מחזיר מזהה משתמש או מזהה פוסט שאינם קיימים. זה עלול לגרום לתוצאות לא צפויות מאוחר יותר - אנא בדקו שוב את מחרוזת החיפוש שלכם."
+ text: "חסרות דוגמאות הענקה. יתכן שזה קרה כאשר חיפוש עיטורים החזיר מזהה משתמש או מזהה פוסט שאינם קיימים. מצב זה עלול לגרום לתוצאות לא צפויות מאוחר יותר - נא לבדוק שוב את מחרוזת החיפוש שלך."
no_grant_count: "אין עיטורים להקצאה."
grant_count:
one: "עיטור אחד להקצאה."
@@ -4466,6 +4464,14 @@ he:
title: "נא לבחור בעיטור קיים או ליצור אחד חדש כדי להתחיל"
what_are_badges_title: "מה הם עיטורים?"
badge_query_examples_title: "דוגמאות לתשאול עיטורים"
+ mass_award:
+ title: חלוקת עיטורים
+ description: להעניק את אותו העיטור למגוון משתמשים בו זמנית.
+ no_badge_selected: נא לבחור עיטור כדי להתחיל.
+ perform: "להעניק עיטור למשתמשים"
+ upload_csv: להעלות CSV עם כתובות דוא״ל של משתמשים
+ aborted: נא להעלות קובץ CSV שמכיל כתובות דוא״ל של משתמשים
+ success: ה־CSV שלך התקבל והמשתמשים יקבלו את העיטור שלהם בעוד רגע קט.
emoji:
title: "אמוג׳י"
help: "הוספת אמוג׳י חדשים שיהיו זמינים לכולם. (עצה למקצוענים: עדיף לגרור לכאן כמה קבצים בבת אחת)"
@@ -4486,7 +4492,7 @@ he:
add_host: "הוספת שרת"
settings: "הגדרות הטמעה"
crawling_settings: "הגדרות זחלן"
- crawling_description: "כאשר Discourse יוצר נושאים חדשים עבור פוסטים שלכם, אם לא קיים RSS/ATOM הוא ינסה לפענח את התוכן מתוך ה HTML שלכם. לפעמים זה מאתגר לחלץ את התכנים שלכם אז אנחנו מספקים את האפשרות להגדיר כללי CSS כדי שהחילוץ יהיה קל יותר."
+ crawling_description: "כאשר Discourse יוצר נושאים חדשים עבור פוסטים שלך, אם לא קיים RSS/ATOM הוא ינסה לפענח את התוכן מתוך ה־HTML שלך. לפעמים מדובר באתגר רציני לחלץ את התכנים שלך אז אנחנו מספקים את האפשרות להגדיר כללי CSS כדי שהחילוץ יהיה קל יותר."
embed_by_username: "שם משתמש ליצירת נושא"
embed_post_limit: "מספר מקסימלי של פרסומים להטמעה."
embed_title_scrubber: "ביטוי רגולרי שמשמש כדי לנקות את הכותרת של פוסטים"
diff --git a/config/locales/client.hu.yml b/config/locales/client.hu.yml
index 4845d0ffd7..45d87b4826 100644
--- a/config/locales/client.hu.yml
+++ b/config/locales/client.hu.yml
@@ -95,6 +95,12 @@ hu:
x_days:
one: "%{count} napja"
other: "%{count} napja"
+ x_months:
+ one: "%{count}hónapja"
+ other: "%{count}hónapja"
+ x_years:
+ one: "%{count}éve"
+ other: "%{count}éve"
later:
x_days:
one: "%{count} nappal később"
@@ -146,6 +152,7 @@ hu:
banner:
enabled: "Kiemelve ekkor: %{when}. Amíg a felhasználó nem törli, minden oldal tetején megjelenik."
disabled: "Kiemelés eltávolítva ekkor: %{when}. Többé nem jelenik meg minden oldal tetején."
+ topic_admin_menu: "témaműveletek"
wizard_required: "Üdvözöljük az új Discourse-on. Kezdjük a beállításvarázslóval . ✨"
emails_are_disabled: "Egy adminisztrátor letiltotta a kimenő e-maileket. Semmilyen értesítő e-mail nem lesz elküldve."
bootstrap_mode_enabled: "Hogy könnyebbé tegyük az új oldala elindítását, most előkészítési módban van. Minden új felhasználó 1-es szintet kap és be lesz állítva, hogy napi kivonat e-mailt kapjanak. Ez a beállítás automatikusan kikapcsol, ha legalább %{min_users} felhasználó regisztrált."
@@ -301,8 +308,14 @@ hu:
edit: "Kiemelés szerkesztése >>"
choose_topic:
none_found: "Nem találhatók témák."
+ title:
+ search: "Téma keresése"
+ placeholder: "ide írd a témakör címét, URL-jét vagy azonosítóját"
choose_message:
none_found: "Nem található üzenet."
+ title:
+ search: "Üzenet keresése"
+ placeholder: "ide írd az üzenet címét, URL-jét vagy azonosítóját"
review:
order_by: "Rendezés:"
in_reply_to: "válasz erre:"
@@ -585,6 +598,7 @@ hu:
remove_owner: "Eltávolítás tulajdonosként"
remove_owner_description: "%{username} eltávolítása a csoportból csoporttulajdonoként"
owner: "Tulajdonos"
+ forbidden: "Nincs jogod megnézni a tagokat."
topics: "Témák"
posts: "Bejegyzések"
mentions: "Említések"
@@ -597,6 +611,7 @@ hu:
only_admins: "Csak az adminisztrátorok"
mods_and_admins: "Csak a moderátorok és az adminisztrátorok"
members_mods_and_admins: "Csak a csoporttagok, a moderátorok és az adminisztrátorok"
+ owners_mods_and_admins: "Csak a csoport tulajdonosok, moderátorok és adminisztrátorok"
everyone: "Mindenki"
notifications:
watching:
@@ -715,6 +730,9 @@ hu:
activity_stream: "Aktivitás"
preferences: "Beállítások"
feature_topic_on_profile:
+ open_search: "Új téma kiválasztása"
+ title: "Téma kiválasztása"
+ search_label: "Téma keresése cím alapján"
save: "Mentés"
clear:
title: "Törlés"
@@ -806,6 +824,7 @@ hu:
revoke_access: "Hozzáférés visszavonása"
undo_revoke_access: "Hozzáférés visszaállítása"
api_approved: "Jóváhagyva:"
+ api_last_used_at: "Utoljára használva:"
theme: "Stílus"
home: "Alap főoldal"
staged: "Lépcsőzetes"
@@ -846,6 +865,7 @@ hu:
choose_new: "Új jelszó"
choose: "Új jelszó"
second_factor_backup:
+ title: "Kétlépcsős biztonsági mentés kódok"
regenerate: "Újragenerálás"
disable: "Kikapcsol"
enable: "Engedélyez"
@@ -853,12 +873,14 @@ hu:
copy_to_clipboard_error: "Hiba az adat Vágólapra másolása során"
second_factor:
title: "Két-faktoros hitelesítés"
+ forgot_password: "Elfelejtetted a jelszavad?"
confirm_password_description: "Kérlek erősítsd meg a jelszavad a továbbhaladáshoz"
name: "Név"
label: "Kód"
disable_description: "Írd be az azonosító kódodat az alkalmazásból"
show_key_description: "Manuális beírás"
oauth_enabled_warning: "A közösségi bejelentkezések nem lesznek elérhetőek a fét-faktoros azonosítás aktiválása után a fiókhoz."
+ disable: "kikapcsolás"
edit: "Szerkesztés"
security_key:
register: "Regisztráció"
@@ -937,6 +959,8 @@ hu:
active: "most aktív"
not_you: "Nem te vagy?"
show_all: "Összes megjelenítése ({{count}})"
+ show_few: "Kevesebb megjelenítése"
+ was_this_you: "Te voltál?"
latest_post: "Legutóbbi bejegyzésed..."
last_posted: "Utolsó hozzászólás"
last_emailed: "Utolsó email"
@@ -958,7 +982,7 @@ hu:
like_notification_frequency:
title: "Kedvelés esetén értesítsen"
always: "Mindig"
- first_time: "Első alkalommal like-olt egy postot"
+ first_time: "Első alkalommal kedvelt egy bejegyzést"
never: "Soha"
email_previous_replies:
title: "Az e-mailek vége tartalmazza a korábbi válaszokat"
diff --git a/config/locales/client.ko.yml b/config/locales/client.ko.yml
index 3256b385a6..38e7bb1b6c 100644
--- a/config/locales/client.ko.yml
+++ b/config/locales/client.ko.yml
@@ -73,18 +73,22 @@ ko:
date_year: "'YY MMM D"
medium_with_ago:
x_minutes:
- other: "%{count}분전"
+ other: "%{count}분 전"
x_hours:
- other: "%{count}시간전"
+ other: "%{count}시간 전"
x_days:
- other: "%{count}일전"
+ other: "%{count}일 전"
+ x_months:
+ other: "%{count}달 전"
+ x_years:
+ other: "%{count}년 전"
later:
x_days:
- other: "%{count}일후"
+ other: "%{count}일 후"
x_months:
- other: "%{count}달후"
+ other: "%{count}달 후"
x_years:
- other: "%{count}년후"
+ other: "%{count}년 후"
previous_month: "지난 달"
next_month: "다음 달"
placeholder: 날짜
@@ -132,6 +136,7 @@ ko:
bootstrap_mode_disabled: "Bootstrap 모드가 24시간 이내에 해제됩니다."
themes:
default_description: "기본값"
+ broken_theme_alert: "사이트의 테마 혹은 컴포넌트 (%{theme}) 오류로 정상적으로 작동하지 않을 수 있습니다. %{path}에서 비활성화 해주세요."
s3:
regions:
ap_northeast_1: "아시아 태평양 (토쿄)"
diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml
index 8c3a0e1345..5620cba4dd 100644
--- a/config/locales/client.nl.yml
+++ b/config/locales/client.nl.yml
@@ -27,8 +27,7 @@ nl:
millions: "{{number}}M"
dates:
time: "HH:mm"
- time_short_day: "ddd HH:mm"
- month_day_time: "D MMM, HH:mm"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM HH:mm"
long_no_year_no_time: "D MMM"
@@ -268,7 +267,7 @@ nl:
remove: "Bladwijzer verwijderen"
confirm_clear: "Weet u zeker dat u alle bladwijzers van dit topic wilt verwijderen?"
save: "Opslaan"
- no_timezone: "U hebt nog geen tijdzone ingesteld. Hierdoor kunt u geen herinneringen instellen. Stel er een in in uw profiel ."
+ no_timezone: 'U hebt nog geen tijdzone ingesteld. Hierdoor kunt u geen herinneringen instellen. Stel er een in in uw profiel .'
reminders:
at_desktop: "De volgende keer vanaf mijn computer"
later_today: "Later vandaag {{date}}"
@@ -577,6 +576,7 @@ nl:
leave: "Verlaten"
request: "Aanvraag"
message: "Bericht"
+ confirm_leave: "Weet u zeker dat u deze groep wilt verlaten?"
allow_membership_requests: "Gebruikers mogen lidmaatschapsaanvragen naar groepseigenaren sturen"
membership_request_template: "Aangepaste sjabloon om weer te geven voor gebruikers bij het sturen van een lidmaatschapsaanvraag"
membership_request:
@@ -2797,6 +2797,7 @@ nl:
mark_watching: "%{shortcut} Topic in de gaten houden"
print: "%{shortcut} Topic afdrukken"
defer: "%{shortcut} Topic negeren"
+ topic_admin_actions: "%{shortcut} Beheeracties voor topic openen"
badges:
earned_n_times:
one: "Deze badge is %{count} keer verdiend"
@@ -4208,6 +4209,14 @@ nl:
title: "Selecteer een bestaande badge of maak een nieuwe om te beginnen"
what_are_badges_title: "Wat zijn badges?"
badge_query_examples_title: "Voorbeelden van badgequery's"
+ mass_award:
+ title: Bulktoekenning
+ description: Dezelfde badge aan meerdere gebruikers tegelijk toekennen.
+ no_badge_selected: Selecteer een badge om te beginnen.
+ perform: "Badge aan gebruikers toekennen"
+ upload_csv: Een CSV met gebruikers-e-mailadressen uploaden
+ aborted: Upload een CSV dat gebruikers-e-mailadressen bevat
+ success: Uw CSV is ontvangen en gebruikers ontvangen binnenkort hun badge.
emoji:
title: "Emoji"
help: "Nieuwe emoji toevoegen die voor iedereen beschikbaar zal zijn. (PROTIP: versleep meerdere bestanden tegelijk)"
diff --git a/config/locales/client.pt_BR.yml b/config/locales/client.pt_BR.yml
index 2e5f9ae0ff..0ff0df85b2 100644
--- a/config/locales/client.pt_BR.yml
+++ b/config/locales/client.pt_BR.yml
@@ -27,6 +27,7 @@ pt_BR:
millions: "{{number}}M"
dates:
time: "H:mm"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM H:mm"
long_no_year_no_time: "D MMM"
@@ -266,7 +267,7 @@ pt_BR:
remove: "Remover Favorito"
confirm_clear: "Você tem certeza de que deseja apagar todos os seus favoritos deste tópico?"
save: "Salvar"
- no_timezone: "Você ainda não definiu um fuso horário. VOcê não poderá definir lembretes. Configure um no seu perfil ."
+ no_timezone: 'Você ainda não definiu um fuso horário. VOcê não poderá definir lembretes. Configure um no seu perfil .'
reminders:
at_desktop: "Da próxima vez que estiver na minha área de trabalho"
later_today: "Hoje mais tarde {{date}}"
@@ -350,6 +351,7 @@ pt_BR:
title: "Itens revisáveis criados por usuários com nível de confiança mais alto têm uma pontuação mais alta."
type_bonus:
name: "tipo de bônus"
+ title: "Certos tipos passíveis de revisão podem receber um bônus da equipe para torná-los uma prioridade mais alta."
claim_help:
optional: "Você pode reivindicar este item para impedir que outras pessoas o revisem."
required: "Você precisa reivindicar itens antes de poder revisá-los."
@@ -621,6 +623,7 @@ pt_BR:
remove_owner: "Remover como Proprietário"
remove_owner_description: "Remover %{username} como um proprietário deste grupo"
owner: "Proprietário"
+ forbidden: "Você não tem permissão para visualizar os membros."
topics: "Tópicos"
posts: "Postagens"
mentions: "Menções"
@@ -752,9 +755,13 @@ pt_BR:
activity_stream: "Atividade"
preferences: "Preferências"
feature_topic_on_profile:
+ open_search: "Selecione um Novo Tópico"
+ title: "Selecione um Tópico"
+ search_label: "Pesquisar tópico por título"
save: "Salvar"
clear:
title: "Limpo"
+ warning: "Tem certeza de que deseja limpar o tópico em destaque?"
profile_hidden: "O perfil público deste usuário está oculto."
expand_profile: "Expandir"
collapse_profile: "Recolher"
@@ -787,6 +794,7 @@ pt_BR:
enable_quoting: "Habilitar resposta citando o texto destacado"
enable_defer: "Habilitar adiar para marcar tópicos não lidos"
change: "alterar"
+ featured_topic: "Tópico em Destaque"
moderator: "{{user}} é um moderador"
admin: "{{user}} é um administrador"
moderator_tooltip: "Este usuário é um moderador"
@@ -894,6 +902,7 @@ pt_BR:
copied_to_clipboard: "Copiado para a Área de Transferência"
copy_to_clipboard_error: "Erro ao copiar dados para a Área de Transferência"
remaining_codes: "Você tem {{count}} códigos de backup restantes."
+ use: "Use um código de backup"
enable_prerequisites: "Você deve habilitar um segundo fator primário antes de gerar códigos de backup."
codes:
title: "Códigos de Backup Gerados"
@@ -901,6 +910,7 @@ pt_BR:
second_factor:
title: "Autenticação de Dois Fatores"
enable: "Gerenciar Autenticação de Dois Fatores"
+ forgot_password: "Esqueceu a senha?"
confirm_password_description: "Por favor, confirme sua senha para continuar"
name: "Nome"
label: "Código"
@@ -914,6 +924,7 @@ pt_BR:
extended_description: |
A autenticação de dois fatores adiciona segurança extra à sua conta, exigindo um token único além da sua senha. Tokens podem ser gerados em dispositivos Android e iOS .
oauth_enabled_warning: "Por favor, observe que os logins sociais serão desabilitados quando a autenticação de dois fatores for habilitada na sua conta."
+ use: "Use o Aplicativo Autenticador"
enforced_notice: "Você precisa ativar a autenticação de dois fatores antes de acessar este site."
disable: "Desabilitar"
disable_title: "Desabilitar Segundo Fator"
@@ -921,12 +932,20 @@ pt_BR:
edit: "Editar"
edit_title: "Editar Segundo Fator"
edit_description: "Nome do Segundo Fator"
+ enable_security_key_description: "Quando você tiver sua chave de segurança física preparada, pressione o botão Registrar abaixo."
totp:
title: "Autenticadores Baseados em Token"
add: "Novo Autenticador"
default_name: "Meu Autenticador"
security_key:
register: "Registro"
+ title: "Chaves de Segurança"
+ add: "Registrar Chave de Segurança"
+ default_name: "Chave de Segurança Principal"
+ not_allowed_error: "O processo de registro da chave de segurança atingiu o tempo limite ou foi cancelado."
+ already_added_error: "Você já registrou esta chave de segurança.\nVocê não tem que registrá-la novamente."
+ edit: "Editar Chave de Segurança"
+ edit_description: "Nome da Chave de Segurança"
delete: "Excluir"
change_about:
title: "Modificar Sobre Mim"
@@ -953,9 +972,15 @@ pt_BR:
uploaded_avatar_empty: "Adicionar uma imagem personalizada"
upload_title: "Enviar sua imagem"
image_is_not_a_square: "Aviso: nós cortamos a sua imagem; largura e altura não eram iguais."
+ change_profile_background:
+ title: "Cabeçalho do Perfil"
+ instructions: "Os cabeçalhos do perfil serão centralizados e terão uma largura padrão de 1110px."
change_card_background:
title: "Plano de Fundo do Cartão de Usuário"
instructions: "Imagens de plano de fundo serão centralizadas e terão uma largura padrão de 590px."
+ change_featured_topic:
+ title: "Tópico em Destaque"
+ instructions: "Um link para este tópico estará no seu cartão de usuário e no seu perfil."
email:
title: "E-mail"
primary: "E-mail Primário"
@@ -1091,6 +1116,7 @@ pt_BR:
search: "digite para pesquisar convites..."
title: "Convites"
user: "Usuário Convidado"
+ sent: "Último envio"
none: "Não há convites para exibir."
truncated:
one: "Mostrando o primeiro convite."
@@ -1231,6 +1257,9 @@ pt_BR:
enabled: "Este site está em modo de somente leitura. Por favor, continue a navegar, mas respostas, curtidas e outras ações estão desabilitadas por enquanto."
login_disabled: "O login é desabilitado enquanto o site está em modo de somente leitura."
logout_disabled: "O logout é desabilitado enquanto o site está em modo de somente leitura."
+ too_few_topics_and_posts_notice: "Vamos iniciar a discussão! Há %{currentTopics} tópicos e %{currentPosts} postagens. Os visitantes precisam de mais informações para ler e responder - nós recomendamos pelo menos %{requiredTopics} tópicos e %{requiredPosts} posts. Somente funcionários podem ver esta mensagem."
+ too_few_topics_notice: "Vamos iniciar a discussão! Há %{currentTopics} tópicos. Os visitantes precisam de mais informações para ler e responder - nós recomendamos pelo menos %{requiredTopics} tópicos. Somente funcionários podem ver esta mensagem."
+ too_few_posts_notice: "Vamos inicar a discussão! Há %{currentPosts} postagens. Os visitantes precisam de mais informações para ler e responder - nós recomendamos pelo menos %{requiredPosts} postagens. Somente funcionários podem ver esta mensagem."
logs_error_rate_notice:
reached_hour_MF: "{relativeAge} – {rate, plural, one {# erro/hora} other {# erros/hora}} alcançou o limite de configuração do site de {limit, plural, one {# erro/hora} other {# erros/hora}}."
reached_minute_MF: "{relativeAge} – {rate, plural, one {# erro/minuto} other {# erros/minuto}} alcançou o limite de configuração do site de {limit, plural, one {# erro/minuto} other {# erros/minuto}}."
@@ -1326,8 +1355,16 @@ pt_BR:
password: "Senha"
second_factor_title: "Autenticação de Dois Fatores"
second_factor_description: "Por favor, digite o código de autenticação do seu aplicativo:"
+ second_factor_backup: "Efetuar log in usando um código de backup"
second_factor_backup_title: "Backup de Dois Fatores"
second_factor_backup_description: "Por favor, insira um dos seus códigos de backup:"
+ second_factor: "Efetuar log in usando um Aplicativo de Autenticação."
+ security_key_description: "Quando você tiver uma chave física de segurança preparada, pressione o Anti\n\nQuando sua chave de segurança física estiver pronta, pressione o botão \"Autenticar com Chave de Segurança\" abaixo."
+ security_key_alternative: "Tente outra maneira"
+ security_key_authenticate: "Autenticar com Chave de Segurança"
+ security_key_not_allowed_error: "O processo de autenticação de chave de segurança atingiu o limite de tempo ou foi cancelado."
+ security_key_no_matching_credential_error: "Nenhuma credencial correspondente pôde ser encontrada na chave de segurança fornecida."
+ security_key_support_missing_error: "Seu dispositivo atual ou navegador não suportam o uso de chaves de segurança. Por favor, use um método diferente."
email_placeholder: "e-mail ou nome de usuário"
caps_lock_warning: "Caps Lock está ativado"
error: "Erro desconhecido"
@@ -1374,6 +1411,7 @@ pt_BR:
title: "com GitHub"
discord:
name: "Discord"
+ title: "com Discórdia"
second_factor_toggle:
totp: "Use um aplicativo autenticador"
backup_code: "Use um código de backup"
@@ -1394,6 +1432,7 @@ pt_BR:
apple_international: "Apple/Internacional"
google: "Google"
twitter: "Twitter"
+ emoji_one: "JoyPixels (anteriormente EmojiOne)"
win10: "Win10"
google_classic: "Google Classic"
facebook_messenger: "Facebook Messenger"
@@ -1484,10 +1523,12 @@ pt_BR:
title_missing: "Título é obrigatório"
title_too_short: "Título precisa ter no mínimo {{min}} caracteres"
title_too_long: "Título não pode ter mais de {{max}} caracteres"
+ post_missing: "A postagem não pode estar vazia"
post_length: "Postagem precisa ter no mínimo {{min}} caracteres"
try_like: "Você já tentou o botão {{heart}}?"
category_missing: "Você precisa escolher uma categoria"
tags_missing: "Você precisa escolher pelo menos {{count}} etiquetas"
+ topic_template_not_modified: "Por favor, adicione detalhes e especificações ao seu tópico editando o modelo do tópico."
save_edit: "Salvar Edição"
overwrite_edit: "Sobrescrever Edição"
reply_original: "Responder no Tópico Original"
@@ -1527,6 +1568,7 @@ pt_BR:
link_description: "digite a descrição do link aqui"
link_dialog_title: "Inserir Hyperlink"
link_optional_text: "título opcional"
+ link_url_placeholder: "Cole uma URL ou digite para pesquisar tópicos"
quote_title: "Bloco de Citação"
quote_text: "Bloco de Citação"
code_title: "Texto pré-formatado"
@@ -1616,6 +1658,7 @@ pt_BR:
topic_reminder: "{{username}} {{description}}"
watching_first_post: "Novo Tópico {{description}}"
membership_request_accepted: "Afiliação aceita em '{{group_name}}'"
+ membership_request_consolidated: "{{count}} abre solicitações de associação para '{{group_name}}'"
group_message_summary:
one: "{{count}} mensagem na caixa de entrada de {{group_name}}"
other: "{{count}} mensagens na caixa de entrada de {{group_name}}"
@@ -1651,6 +1694,7 @@ pt_BR:
topic_reminder: "lembrete de tópico"
liked_consolidated: "novas curtidas"
post_approved: "publicação aprovada"
+ membership_request_consolidated: "novas solicitações de associação"
upload_selector:
title: "Adicionar uma imagem"
title_with_attachments: "Adicionar uma imagem ou um arquivo"
@@ -1695,6 +1739,7 @@ pt_BR:
context:
user: "Pesquisar postagens por @{{username}}"
category: "Pesquisar a categoria #{{category}}"
+ tag: "Pesquisar a #{{tag}} etiqueta"
topic: "Pesquisar este tópico"
private_messages: "Pesquisar mensagens"
advanced:
@@ -1714,6 +1759,7 @@ pt_BR:
title: Correspondência somente no título
likes: Eu curti
posted: Eu postei em
+ created: Eu criei
watching: Eu estou observando
tracking: Eu estou acompanhando
private: Nas minhas mensagens
@@ -1730,6 +1776,7 @@ pt_BR:
label: Onde tópicos
open: estão abertos
closed: estão fechados
+ public: são públicos
archived: estão arquivados
noreplies: não possuem respostas
single_user: contém um único usuário
@@ -1745,6 +1792,7 @@ pt_BR:
go_back: "voltar"
not_logged_in_user: "página do usuário com resumo de atividades e preferências atuais"
current_user: "ir para a sua página de usuário"
+ view_all: "ver tudo"
topics:
new_messages_marker: "última visita"
bulk:
@@ -1816,6 +1864,13 @@ pt_BR:
defer:
help: "Marcar como não lido"
title: "Delegar"
+ feature_on_profile:
+ help: "Adicionar um link para este tópico em seu cartão de usuário e perfil"
+ title: "Recurso no Perfil"
+ remove_from_profile:
+ warning: "Seu perfil já tem um tópico em destaque. Se você continuar, este tópico substituirá o tópico existente."
+ help: "Remover o link para este tópico no seu perfil de usuário"
+ title: "Remover do Perfil"
list: "Tópicos"
new: "novo tópico"
unread: "não lido"
@@ -1857,6 +1912,7 @@ pt_BR:
group_request: "Você precisa solicitar filiação ao grupo `{{name}}` para ver este tópico"
group_join: "Você precisa juntar-se ao grupo `{{name}}` para ver este tópico"
group_request_sent: "Sua solicitação de filiação ao grupo foi enviada. Você será informado(a) quando ela for aceita."
+ unread_indicator: "Nenhum membro leu a última postagem deste tópico ainda."
read_more_MF: "Há { UNREAD, plural, =0 {} one { 1 não lido } other { # não lidos } } { NEW, plural, =0 {} one { {BOTH, select, true{e } false {} other{}} 1 novo tópico} other { {BOTH, select, true{e } false {} other{}} # novos tópicos} } restantes, ou {CATEGORY, select, true {veja outros tópicos em {catLink}} false {{latestLink}} other {}}"
browse_all_categories: Ver todas as categorias
view_latest_topics: ver últimos tópicos
@@ -2216,8 +2272,10 @@ pt_BR:
attachment_upload_not_allowed_for_new_user: "Desculpe, usuários novos não podem enviar anexos."
attachment_download_requires_login: "Desculpe, você precisa estar logado para baixar arquivos anexos."
abandon_edit:
+ confirm: "Você tem certeza que deseja descartar suas alterações?"
no_value: "Não, manter."
no_save_draft: "Não, salvar rascunho"
+ yes_value: "Sim, descartar edição"
abandon:
confirm: "Tem certeza que quer abandonar a sua mensagem?"
no_value: "Não, manter"
@@ -2235,6 +2293,7 @@ pt_BR:
reply: "começar a escrever uma resposta para esta postagem"
like: "curtir esta resposta"
has_liked: "você curtiu esta resposta"
+ read_indicator: "membros que leram esta postagem"
undo_like: "desfazer curtida"
edit: "editar esta resposta"
edit_action: "Editar"
@@ -2290,9 +2349,18 @@ pt_BR:
notify_moderators: "notificaram os moderadores"
notify_user: "enviou uma mensagem"
bookmark: "favoritaram isto"
+ like:
+ one: "gostou disto"
+ other: "gostaram disto"
+ read:
+ one: "leu isto"
+ other: "Leram isto"
like_capped:
one: "e {{count}} outro gostou disto"
other: "e {{count}} outros gostaram disto"
+ read_capped:
+ one: "e {{count}} outra leu isto"
+ other: "e {{count}} outras leram isto"
by_you:
off_topic: "Você sinalizou isto como off-topic"
spam: "Você sinalizou isto como spam"
@@ -2343,7 +2411,10 @@ pt_BR:
title: "Mostrar texto html do e-mail"
button: "HTML"
bookmarks:
+ create: "Criar marcador"
name: "Nome"
+ name_placeholder: "Nomeie o marcador para ajudar a melhorar sua memória"
+ set_reminder: "Definir um lembrete"
category:
can: "pode… "
none: "(sem categoria)"
@@ -2359,9 +2430,14 @@ pt_BR:
tags_allowed_tags: "Restringir estas etiquetas a esta categoria:"
tags_allowed_tag_groups: "Restringir estes grupos de etiquetas a esta categoria:"
tags_placeholder: "(Opcional) lista de etiquetas permitidas"
+ tags_tab_description: "As etiquetas e grupos de etiquetas especificadas acima estarão disponíveis apenas nesta categoria e em outras categorias que também as especificarem. Elas não estarão disponíveis para uso em outras categorias."
tag_groups_placeholder: "(Opcional) lista de grupos de etiquetas permitidos"
manage_tag_groups_link: "Gerencie grupos de etiquetas aqui."
allow_global_tags_label: "Permitir também outras etiquetas"
+ tag_group_selector_placeholder: "(Opcional) Grupo de etiquetas"
+ required_tag_group_description: "Exija que novos tópicos tenham etiquetas de um grupo de etiquetas:"
+ min_tags_from_required_group_label: "Número de Etiquetas:"
+ required_tag_group_label: "Grupo de etiquetas:"
topic_featured_link_allowed: "Permitir links em destaque nesta categoria"
delete: "Apagar categoria"
create: "Nova categoria"
@@ -2720,6 +2796,7 @@ pt_BR:
mark_watching: "%{shortcut} Observar o tópico"
print: "%{shortcut} Imprimir tópico"
defer: "%{shortcut} Adiar tópico"
+ topic_admin_actions: "%{shortcut} Abrir ações de administração de tópicos"
badges:
earned_n_times:
one: "Emblema adquirido %{count} vez"
@@ -2769,12 +2846,30 @@ pt_BR:
changed: "etiquetas alteradas:"
tags: "Etiquetas"
choose_for_topic: "etiquetas opcionais"
+ info: "Informações"
+ default_info: "Esta etiqueta não está restrita a nenhuma categorias e não possui sinônimos."
+ synonyms: "Sinônimos"
+ synonyms_description: "Quando as seguintes etiquetas forem usadas, eles serão substituídas por %{base_tag_name} ."
+ tag_groups_info:
+ one: 'Esta etiqueta pertence a este grupo: {{tag_groups}}.'
+ other: "Esta etiqueta pertence a estes grupos: {{tag_groups}}."
+ category_restrictions:
+ one: "Pode ser usada nesta categoria:"
+ other: "Pode ser usada nestas categorias:"
+ edit_synonyms: "Gerenciar sinônimos"
+ add_synonyms_label: "Adicionar sinônimos:"
add_synonyms: "Adicionar"
+ add_synonyms_failed: "As seguintes etiquetas não podem ser adicionadas como sinônimos: %{tag_names} . Se assegure de que elas não tenham sinônimos e não sejam sinônimos de outras etiquetas."
+ remove_synonym: "Remover sinônimo"
+ delete_synonym_confirm: 'Você tem certeza que deseja excluir o sinônimo "%{tag_name}"?'
delete_tag: "Apagar marcação"
delete_confirm:
one: "Tem certeza de que deseja excluir esta tag e removê-la de um tópico para o qual ela está atribuída?"
other: "Tem certeza de que deseja excluir esta tag e removê-la de {{count}} tópicos aos quais ela está atribuída?"
delete_confirm_no_topics: "Tem certeza de que deseja excluir esta tag?"
+ delete_confirm_synonyms:
+ one: "O sinônimo dela também será excluído."
+ other: "Os {{count}} sinônimos dela também serão excluídos."
rename_tag: "Renomear marcador"
rename_instructions: "Escolha um novo nome para o marcador"
sort_by: "Ordenar por"
@@ -2827,6 +2922,7 @@ pt_BR:
parent_tag_description: "Etiquetas deste grupo não podem ser usadas a menos que a etiqueta principal esteja presente."
one_per_topic_label: "Limite uma etiqueta por tópico deste grupo"
new_name: "Novo Grupo de Etiquetas"
+ name_placeholder: "Nome do grupo de etiquetas"
save: "Salvar"
delete: "Apagar"
confirm_delete: "Tem certeza de que deseja remover este grupo de etiquetas?"
@@ -2989,9 +3085,14 @@ pt_BR:
staff: "Proprietários e funcionários do grupo"
owners: "Proprietários do grupo"
description: "Administradores podem ver todos os grupos."
+ members_visibility_levels:
+ title: "Quem pode ver os membros deste grupo?"
+ description: "Administradores podem ver os membros de todos os grupos."
+ publish_read_state: "Em mensagens de grupo, publique o estado de leitura do grupo"
membership:
automatic: Automático
trust_levels_title: "Nível de confiança concedido automaticamente aos membros quando eles são adicionados:"
+ effects: Efeitos
trust_levels_none: "Nenhum"
automatic_membership_email_domains: "Os usuários que se registrarem em um domínio de e-mail que corresponda exatamente a um nesta lista serão adicionados automaticamente a este grupo:"
automatic_membership_retroactive: "Aplicar a mesma regra de domínio de e-mail para adicionar usuários registrados existentes"
@@ -3024,12 +3125,29 @@ pt_BR:
title: "API"
key: "Chave"
created: Criado
+ updated: Atualizado
+ last_used: Últimos usados
+ never_used: (nunca)
generate: "Gerar"
+ undo_revoke: "Desfazer Revogação"
revoke: "Revogar"
all_users: "Todos os Usuários"
+ active_keys: "Chaves de API Ativas"
+ manage_keys: Gerenciar Chaves
show_details: Detalhes
description: Descrição
+ no_description: (sem descrição)
+ all_api_keys: Todas Chaves de API
+ user_mode: Nível de Usuário
+ impersonate_all_users: Representar qualquer usuário
+ single_user: "Único Usuário"
+ user_placeholder: Insira o nome de usuário
+ description_placeholder: "Para que esta chave será usada?"
save: Salvar
+ new_key: Nova Chave de API
+ revoked: Revogada
+ delete: Permanentemente excluída
+ not_shown_again: Esta chave não será exibida novamente. Certifique-se de tirar uma cópia antes de continuar.
continue: Continuar
web_hooks:
title: "Webhooks"
@@ -3092,6 +3210,9 @@ pt_BR:
reviewable_event:
name: "Evento Revisável"
details: "Quando um novo item está pronto para revisão e quando seu status é atualizado."
+ notification_event:
+ name: "Evento de Notificação"
+ details: "Quando um usuário recebe uma notificação no feed dele."
delivery_status:
title: "Status de Entrega"
inactive: "Inativo"
@@ -3270,6 +3391,7 @@ pt_BR:
color_scheme_select: "Selecione as cores a serem usadas pelo tema"
custom_sections: "Seções personalizadas:"
theme_components: "Componentes do Tema"
+ add_all_themes: "Adicionar todos os temas"
convert: "Converter"
convert_component_alert: "Você tem certeza de que deseja converter este componente em tema? Ele será removido como um componente de %{relatives}."
convert_component_tooltip: "Converter este componente em tema"
@@ -3296,12 +3418,16 @@ pt_BR:
upload: "Upload"
select_component: "Selecionar um componente…"
unsaved_changes_alert: "Você ainda não salvou as suas modificações, você deseja descartá-las e prosseguir?"
+ unsaved_parent_themes: "Você não atribuiu o componente a temas. Deseja seguir em frente?"
discard: "Descartar"
stay: "Permanecer"
css_html: "CSS / HTML personalizado"
edit_css_html: "Editar CSS/HTML"
edit_css_html_help: "Você não editou nenhum CSS ou HTML"
delete_upload_confirm: "Excluir este upload? (CSS tema pode parar de funcionar!)"
+ component_on_themes: "Incluir componente nestes temas."
+ included_components: "Componentes incluídos"
+ add_all: "Adicionar todos"
import_web_tip: "Repositório contendo tema"
import_web_advanced: "Avançado…"
import_file_tip: "Arquivo .tar.gz, .zip ou .dcstyle.json contendo tema"
@@ -3342,6 +3468,7 @@ pt_BR:
other: "o tema é {{count}} confirmado por trás"
compare_commits: "(Ver novos commits)"
repo_unreachable: "Não foi possível contatar o repositório Git deste tema. Mensagem de erro:"
+ imported_from_archive: "Este tema foi importado de um arquivo \".zip\""
scss:
text: "CSS"
title: "Digite CSS personalizado, aceitamos todos os estilos CSS e SCSS válidos"
@@ -3609,6 +3736,12 @@ pt_BR:
change_theme_setting: "alterar configurações do tema"
disable_theme_component: "desabilitar componente de tema"
enable_theme_component: "habilitar componente de tema"
+ revoke_title: "Revogar título"
+ change_title: "Mudar título"
+ api_key_create: "Criar Chave de API"
+ api_key_update: "Atualizar Chave de API"
+ api_key_destroy: "Destruir Chave de API"
+ override_upload_secure_status: "Sobrescrever o status seguro do upload"
screened_emails:
title: "Emails Filtrados"
description: "Quando alguém tenta cria uma nova conta, os seguintes endereços de e-mail serão verificados e o registro será bloqueado, ou outra ação será executada."
@@ -3999,7 +4132,9 @@ pt_BR:
secret_list:
invalid_input: "Campos de entrada não podem ser vazios ou conter o caractere de barra vertical."
default_categories:
+ modal_description: "Você gostaria de aplicar esta alteração historicamente? Isto mudará as preferências de %{count} usuários existentes."
modal_yes: "Sim"
+ modal_no: "Não, apenas aplique alterações daqui para frente"
badges:
title: Emblemas
new_badge: Novo Emblema
diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml
index 5e5d389c53..ba6bebac21 100644
--- a/config/locales/client.ru.yml
+++ b/config/locales/client.ru.yml
@@ -29,8 +29,7 @@ ru:
millions: "{{number}} млн."
dates:
time: "HH:mm"
- time_short_day: "ddd HH:mm a"
- month_day_time: "MMM D, HH:mm a"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "D MMM HH:mm"
long_no_year_no_time: "D MMM"
@@ -162,21 +161,21 @@ ru:
placeholder: дата
share:
topic_html: 'Тема: %{topicTitle} '
- post: "пост #%{postNumber}"
+ post: "сообщение #%{postNumber}"
close: "закрыть"
twitter: "Поделиться ссылкой в Twitter"
facebook: "Поделиться ссылкой в Facebook"
email: "Отправить эту ссылку по e-mail"
action_codes:
public_topic: "Сделал эту тему публичной %{when}"
- private_topic: "сделал эту тему персональным сообщением %{when}"
+ private_topic: "Сделал эту тему личным сообщением %{when}"
split_topic: "Разделил эту тему %{when}"
invited_user: "Пригласил %{who} %{when}"
invited_group: "Пригласил %{who} %{when}"
- user_left: "%{who} удалить себя из этого сообщения %{when}"
+ user_left: "%{who} удалил себя из этого сообщения %{when}"
removed_user: "Исключил %{who} %{when}"
removed_group: "Исключил %{who} %{when}"
- autobumped: "автоматически поднято %{when}"
+ autobumped: "Автоматически поднято %{when}"
autoclosed:
enabled: "Закрыл тему %{when}"
disabled: "Открыл тему %{when}"
@@ -196,8 +195,9 @@ ru:
enabled: "Включил в списки %{when}"
disabled: "Исключил из списков %{when}"
banner:
- enabled: "создал(а) из этого баннер %{when}. Он будет виден вверху каждой страницы пока пользователь не закроет его."
- disabled: "удалил это объявление %{when}. Он больше не будет отображаться в верхней части каждой страницы."
+ enabled: "Создал баннер %{when}. Он будет отображаться вверху каждой страницы пока пользователь не закроет его."
+ disabled: "Удалил баннер %{when}. Он больше не будет отображаться в верхней части каждой страницы."
+ topic_admin_menu: "действия по теме"
wizard_required: "Добро пожаловать в ваш новый Discourse! Начните с мастера настройки ✨"
emails_are_disabled: "Все исходящие письма были глобально отключены администратором. Уведомления любого вида не будут отправляться на почту."
bootstrap_mode_enabled: "Чтобы облегчить развитие вашего нового сайта в самом начале, был включен режим запуска. В этом режиме, всем новым пользователям будет автоматически присвоен 1-й уровень доверия при регистрации и включена ежедневная почтовая рассылка сводки новостей. Режим запуска будет выключен автоматически, как только количество зарегистрированных пользователей достигнет %{min_users}."
@@ -227,7 +227,7 @@ ru:
us_gov_west_1: "AWS GovCloud (US-West)"
us_west_1: "US West (N. California)"
us_west_2: "US West (Oregon)"
- edit: "отредактировать название и раздел темы"
+ edit: "Отредактировать название и раздел темы"
expand: "Развернуть"
not_implemented: "Извините, эта функция еще не реализована!"
no_value: "Нет"
@@ -236,12 +236,12 @@ ru:
generic_error: "Извините, произошла ошибка."
generic_error_with_reason: "Произошла ошибка: %{error}"
go_ahead: "Продолжить"
- sign_up: "Зарегистрироваться"
- log_in: "Войти"
+ sign_up: "Регистрация"
+ log_in: "Вход"
age: "Возраст"
joined: "Зарегистрировался"
admin_title: "Админка"
- show_more: "показать дальше"
+ show_more: "Показать ещё"
show_help: "Расширенный поиск"
links: "Ссылки"
links_lowercase:
@@ -255,7 +255,7 @@ ru:
privacy: "Политика конфиденциальности"
tos: "Пользовательское соглашение"
rules: "Правила"
- conduct: "Кодекс Поведения"
+ conduct: "Кодекс поведения"
mobile_view: "Для мобильных устройств"
desktop_view: "Для настольных устройств"
you: "Вы"
@@ -280,7 +280,7 @@ ru:
other: "{{count}} букв"
related_messages:
title: "Связанные сообщения"
- see_all: 'Смотреть все сообщения из @%{username}...'
+ see_all: 'Показать все сообщения от @%{username}...'
suggested_topics:
title: "Похожие темы"
pm_title: "Похожие сообщения"
@@ -303,29 +303,37 @@ ru:
contact: "Контакты"
contact_info: "В случае возникновения критической ошибки или срочного дела, касающегося этого сайта, свяжитесь с нами по адресу %{contact_info}."
bookmarked:
- title: "Избранное"
+ title: "Закладки"
clear_bookmarks: "Очистить закладки"
help:
- bookmark: "Нажмите, чтобы добавить в закладки первое сообщение этой темы"
- unbookmark: "Нажмите, чтобы удалить все закладки в этой теме"
+ bookmark: "Добавить в закладки первое сообщение этой темы"
+ unbookmark: "Удалить все закладки в этой теме"
bookmarks:
- created: "вы добавили это сообщение в закладки"
+ created: "Вы добавили это сообщение в закладки"
not_bookmarked: "Добавить сообщение в закладки"
+ created_with_reminder: "вы добавили это сообщение в закладки с последующим напоминанием %{date}"
remove: "Удалить закладку"
- confirm_clear: "Вы уверены что хотите удалить все ваши закладки из этой темы?"
+ confirm_clear: "Вы действительно хотите удалить все ваши закладки из этой темы?"
save: "Сохранить"
+ no_timezone: 'Укажите ваш часовой пояс в настройках своей учетной записи , чтобы активировать функцию напоминаний.'
reminders:
- custom: "Пользовательская дата и время"
+ at_desktop: "При следующем посещении форума"
+ later_today: "На сегодня, но чуть позже {{date}}"
+ next_business_day: "На следующий рабочий день {{date}}"
+ tomorrow: "На завтра {{date}}"
+ next_week: "На следующую неделю {{date}}"
+ next_month: "На следующий месяц {{date}}"
+ custom: "Установить дату и время напоминания"
drafts:
resume: "Продолжить"
remove: "Удалить"
- new_topic: "Новый черновик темы"
- new_private_message: "Черновик для нового личного сообщения"
+ new_topic: "Черновик новой темы"
+ new_private_message: "Черновик нового личного сообщения"
topic_reply: "Черновик ответа"
abandon:
- confirm: "Вы уже открыли другой проект в этой теме. Уверены, что хотите бросить его?"
- yes_value: "Да, отказаться"
- no_value: "Нет, оставить"
+ confirm: "В этой теме найден ваш назавершенный черновик сообщения. Хотите от него отказаться?"
+ yes_value: "Да, удалить"
+ no_value: "Нет, сохранить"
topic_count_latest:
one: "Есть {{count}} новая или обновленная тема"
few: "Есть {{count}} новых или обновленных темы"
@@ -339,8 +347,8 @@ ru:
topic_count_new:
one: "Посмотреть {{count}} новую тему"
few: "Посмотреть {{count}} новые темы"
- many: "Посмотреть {{count}} новые тем"
- other: "Посмотреть {{count}} новые тем"
+ many: "Посмотреть {{count}} новых тем"
+ other: "Посмотреть {{count}} новых тем"
preview: "Предпросмотр"
cancel: "Отмена"
save: "Сохранить"
@@ -359,7 +367,7 @@ ru:
revert: "Вернуть"
failed: "Проблема"
switch_to_anon: "Войти в анонимный режим"
- switch_from_anon: "Выйти из Анонимного режима"
+ switch_from_anon: "Выйти из анонимного режима"
banner:
close: "Больше не показывать это объявление."
edit: "Редактировать это объявление >>"
@@ -369,29 +377,29 @@ ru:
none_found: "Не найдено ни одной темы."
title:
search: "Поиск темы по названию, url или id:"
- placeholder: "введите название темы здесь"
+ placeholder: "Введите название темы"
choose_message:
none_found: "Совпадений не найдено."
title:
- search: "Поиск в личных сообщениях по названию: "
- placeholder: "введите название сообщения"
+ search: "Поиск в личных сообщениях"
+ placeholder: "Введите заголовок сообщения, url или id"
review:
order_by: "Сортировать по"
in_reply_to: "в ответе"
explain:
why: "объяснить, почему этот элемент оказался в очереди"
- title: "Обзорная Оценка"
+ title: "Обзорная оценка"
formula: "Формула"
subtotal: "Промежуточный итог"
total: "Всего"
- min_score_visibility: "Минимальная Оценка для Видимости"
+ min_score_visibility: "Минимальная оценка для видимости"
score_to_hide: "Оценка, чтобы скрыть сообщение"
take_action_bonus:
name: "принята мера"
- title: "Когда сотрудник решает принять меры, флаг получает бонус."
+ title: "Когда сотрудник решает принять меры, жалоба получает бонус."
user_accuracy_bonus:
name: "точность пользователя"
- title: "Пользователи, чьи флаги были исторически согласованы, получают бонус."
+ title: "Пользователи, чьи жалобы были согласованы, получают бонус."
trust_level_bonus:
name: "уровень доверия"
title: "Проверяемые элементы, созданные пользователями с более высоким уровнем доверия, имеют более высокий балл."
@@ -406,26 +414,26 @@ ru:
claim:
title: "заявить эту тему"
unclaim:
- help: "удалить это требование"
+ help: "Удалить это требование"
awaiting_approval: "В ожидании подтверждения"
delete: "Удалить"
settings:
saved: "Сохранено"
- save_changes: "Сохранить Изменения"
+ save_changes: "Сохранить изменения"
title: "Настройки"
priorities:
- title: "Обзорные Приоритеты"
+ title: "Обзорные приоритеты"
moderation_history: "История модерации"
- view_all: "Посмотреть Все"
- grouped_by_topic: "Сгруппированы по Темам"
- none: "Нет пунктов для обзора."
+ view_all: "Посмотреть все"
+ grouped_by_topic: "Сгруппированы по темам"
+ none: "Нет элементов для премодерации."
view_pending: "просмотр в ожидании"
topic_has_pending:
- one: "В этой теме %{count} пост, ожидающий проверки"
- few: "В этой теме {{count}} поста, ожидающих проверки"
- many: "В этой теме {{count}} постов, ожидающих проверки"
- other: "В этой теме {{count}} постов, ожидающих проверки"
- title: "Обзор"
+ one: "В этой теме %{count} сообщение ожидает проверки"
+ few: "В этой теме {{count}} сообщения ожидает проверки"
+ many: "В этой теме {{count}} сообщений ожидает проверки"
+ other: "В этой теме {{count}} сообщений ожидает проверки"
+ title: "Премодерация"
topic: "Тема:"
filtered_topic: "Вы отфильтровали для просмотра содержимое в одной теме."
filtered_user: "Пользователь"
@@ -439,10 +447,10 @@ ru:
fields: "Поля"
user_percentage:
summary:
- one: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} всего флагов)"
- few: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} всего флагов)"
- many: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} всего флагов)"
- other: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} всего флагов)"
+ one: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} жалоба)"
+ few: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} жалобы)"
+ many: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} жалоб)"
+ other: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} жалоб)"
agreed:
one: "{{count}}% согласие"
few: "{{count}}% согласия"
@@ -454,15 +462,15 @@ ru:
many: "{{count}}% несогласий"
other: "{{count}}% несогласий"
ignored:
- one: "{{count}}% отказ"
- few: "{{count}}% отказа"
- many: "{{count}}% отказов"
- other: "{{count}}% отказов"
+ one: "{{count}}% игнорирование"
+ few: "{{count}}% игнорирования"
+ many: "{{count}}% игнорирований"
+ other: "{{count}}% игнорирований"
topics:
topic: "Тема"
reviewable_count: "Количество"
reported_by: "Сообщает"
- deleted: "[Тема Удалена]"
+ deleted: "[Тема удалена]"
original: "(оригинальная тема)"
details: "подробности"
unique_users:
@@ -480,28 +488,28 @@ ru:
cancel: "Отмена"
new_topic: "Утверждение этого пункта создаст новую тему"
filters:
- all_categories: "(все категории)"
+ all_categories: "(все разделы)"
type:
title: "Тип"
all: "(все типы)"
minimum_score: "Минимальная оценка:"
refresh: "Обновить"
status: "Статус"
- category: "Категория"
+ category: "Раздел"
orders:
priority: "Приоритет"
priority_asc: "Приоритет (обратный)"
created_at: "Создан в"
created_at_asc: "Созданные в (обратный)"
priority:
- title: "Минимальный Приоритет:"
+ title: "Минимальный приоритет:"
low: "(любой)"
medium: "Средний"
high: "Высокий"
conversation:
view_full: "просмотреть полный разговор"
scores:
- about: "Эта оценка рассчитывается на основе уровня доверия репортера, точности их предыдущих флагов и приоритета сообщаемого элемента."
+ about: "Оценка рассчитывается на основе уровня доверия сообщающего, точности его предыдущих жалоб и приоритета сообщения."
score: "Оценка"
date: "Дата"
type: "Тип"
@@ -525,22 +533,22 @@ ru:
title: "(всё)"
types:
reviewable_flagged_post:
- title: "Отмеченный Пост"
+ title: "Сообщение отмечено как ЖАЛОБА"
flagged_by: "Отмечено"
reviewable_queued_topic:
- title: "Тема в Очереди"
+ title: "Тема в очереди"
reviewable_queued_post:
- title: "Пост в очереди"
+ title: "Сообщение в очереди"
reviewable_user:
title: "Пользователь"
approval:
title: "Сообщения для проверки"
description: "Ваше сообщение отправлено, но требует проверки и утверждения модератором. Пожалуйста, будьте терпеливы."
pending_posts:
- one: "У вас есть %{count} сообщение в ожидании."
- few: "У вас есть {{count}} сообщения в ожидании."
- many: "У вас есть {{count}} сообщений в ожидании."
- other: "У вас есть {{count}} сообщений в ожидании."
+ one: "У вас %{count} сообщение в ожидании модерации."
+ few: "У вас {{count}} сообщения в ожидании модерации."
+ many: "У вас {{count}} сообщений в ожидании модерации."
+ other: "У вас {{count}} сообщений в ожидании модерации."
ok: "OK"
user_action:
user_posted_topic: "{{user}} создал тему "
@@ -589,7 +597,7 @@ ru:
member_added: "Добавлено"
member_requested: "По запросу на"
add_members:
- title: "Добавить Участников"
+ title: "Добавить участников"
description: "Редактирование участников группы"
usernames: "Псевдоним"
requests:
@@ -640,6 +648,7 @@ ru:
leave: "Покинуть"
request: "Запрос"
message: "Сообщение"
+ confirm_leave: "Вы действительно хотите выйти из группы?"
allow_membership_requests: "Разрешить пользователям отправлять запросы на вступление в группу владельцев"
membership_request_template: "Настраиваемый шаблон для отображения пользователю при отсылке запроса на подключение"
membership_request:
@@ -650,12 +659,12 @@ ru:
name: "Название"
group_name: "Название группы"
user_count: "Пользователи"
- bio: "О Группе"
+ bio: "О группе"
selector_placeholder: "введите псевдоним"
owner: "владелец"
index:
title: "Группы"
- all: "Все Группы"
+ all: "Все группы"
empty: "Нет видимых групп."
filter: "Фильтр по типу группы"
owner_groups: "Мои группы"
@@ -682,11 +691,11 @@ ru:
title: "Участники"
filter_placeholder_admin: "псевдоним или e-mail"
filter_placeholder: "псевдоним"
- remove_member: "Удалить Пользователя"
+ remove_member: "Удалить пользователя"
remove_member_description: "Удалить %{username} из группы"
- make_owner: "Сделать Владельцем"
+ make_owner: "Сделать владельцем"
make_owner_description: "Сделать %{username} владельцем этой группы"
- remove_owner: "Удалить Владельца"
+ remove_owner: "Удалить владельца"
remove_owner_description: "Удалить %{username} как владельца этой группы"
owner: "Владелец"
forbidden: "Вы не можете просматривать участников."
@@ -709,7 +718,7 @@ ru:
title: "Наблюдать"
description: "Уведомлять по каждому ответу на это сообщение и показывать счётчик новых непрочитанных ответов."
watching_first_post:
- title: "Просмотр Первого сообщения"
+ title: "Просмотр первого сообщения"
description: "Вы будете получать уведомления о новых сообщениях в этой группе, но не ответы на сообщения."
tracking:
title: "Следить"
@@ -720,15 +729,15 @@ ru:
muted:
title: "Выключено"
description: "Вы не будете уведомлены о все о сообщениях в этой группе."
- flair_url: "Изображение Аватара"
+ flair_url: "Изображение аватара"
flair_url_placeholder: "(Необязательно) Ссылка на изображение или класс шрифта Font Awesome"
flair_url_description: 'Используйте квадратные картинки, размером не менее, чем 20px на 20px или иконки FontAwesome (допустимы форматы: "fa-icon", "far fa-icon" или "fab fa-icon")'
- flair_bg_color: "Фоновый Цвет Аватара"
+ flair_bg_color: "Фоновый цвет аватара"
flair_bg_color_placeholder: "(Необязательно) Hex-код цвета"
- flair_color: "Цвет Аватара"
+ flair_color: "Цвет аватара"
flair_color_placeholder: "(Необязательно) Hex-код цвета"
- flair_preview_icon: "Иконка Предпросмотра"
- flair_preview_image: "Изображение Предпросмотра"
+ flair_preview_icon: "Иконка предпросмотра"
+ flair_preview_image: "Изображение предпросмотра"
user_action_groups:
"1": "Мои симпатии"
"2": "Получил симпатий"
@@ -746,7 +755,7 @@ ru:
categories:
all: "Все разделы"
all_subcategories: "все"
- no_subcategory: "Вне подкатегорий"
+ no_subcategory: "Вне подразделов"
category: "Раздел"
category_list: "Показать список разделов"
reorder:
@@ -778,20 +787,20 @@ ru:
other: "%{count} новых тем в прошлом месяце."
n_more: "Разделы (еще %{count}) ..."
ip_lookup:
- title: Поиск IP адреса
+ title: Поиск IP-адреса
hostname: Название хоста
location: Расположение
location_not_found: (неизвестно)
organisation: Организация
phone: Телефон
- other_accounts: "Другие учётные записи с этим IP адресом"
+ other_accounts: "Другие учётные записи с этим IP-адресом"
delete_other_accounts: "Удалить %{count}"
username: "псевдоним"
trust_level: "Уровень"
read_time: "время чтения"
topics_entered: "посещено тем"
post_count: "сообщений"
- confirm_delete_other_accounts: "Вы уверены, что хотите удалить эти учетные записи?"
+ confirm_delete_other_accounts: "Вы действительно хотите удалить эти учетные записи?"
powered_by: "с помощью MaxMindDB "
copied: "скопировано"
user_fields:
@@ -803,14 +812,14 @@ ru:
edit: "Настройки"
download_archive:
button_text: "Скачать всё"
- confirm: "Вы уверены, что хотите скачать свои сообщения?"
+ confirm: "Вы действительно хотите скачать свои сообщения?"
success: "Скачивание началось, вы будете уведомлены, когда процесс завершится."
rate_limit_error: "Сообщения могут быть скачаны лишь раз в день, попробуйте завтра."
new_private_message: "Новое сообщение"
private_message: "Личное сообщение"
private_messages: "Личные сообщения"
user_notifications:
- ignore_duration_title: "Игнорировать Таймер"
+ ignore_duration_title: "Игнорировать таймер"
ignore_duration_username: "Псевдоним"
ignore_duration_when: "Продолжительность:"
ignore_duration_save: "Игнорировать"
@@ -827,9 +836,13 @@ ru:
activity_stream: "Активность"
preferences: "Настройки"
feature_topic_on_profile:
+ open_search: "Выберите тему"
+ title: "Выбор темы"
+ search_label: "Поиск темы по её названию"
save: "Сохранить"
clear:
title: "Очистить"
+ warning: "Перестать считать эту тему избранной?"
profile_hidden: "Публичный профиль пользователя скрыт"
expand_profile: "Развернуть"
collapse_profile: "Свернуть"
@@ -862,17 +875,17 @@ ru:
enable_quoting: "Позволить отвечать с цитированием выделенного текста"
enable_defer: "Включить кнопку отложить, чтобы помечать темы, как непрочитанные"
change: "изменить"
- featured_topic: "Избранная Тема"
+ featured_topic: "Избранная тема"
moderator: "{{user}} — модератор"
admin: "{{user}} — админ"
moderator_tooltip: "Этот пользователь модератор"
admin_tooltip: "{{user}} — админ"
- silenced_tooltip: "Этот пользователь отключённый"
+ silenced_tooltip: "Этот пользователь заблокирован"
suspended_notice: "Пользователь заморожен до {{date}}."
suspended_permanently: "Этот пользователь заморожен."
suspended_reason: "Причина:"
github_profile: "Github"
- email_activity_summary: "Сводка Активности"
+ email_activity_summary: "Сводка активности"
mailing_list_mode:
label: "Режим почтовой рассылки"
enabled: "Включить почтовую рассылку"
@@ -880,13 +893,13 @@ ru:
Настройки почтовой рассылки перекрывают настройки сводки активности.
Темы и разделы с выключенными уведомлениями не будут включены в письма рассылки.
individual: "Присылать письмо для каждого нового сообщения"
- individual_no_echo: "Присылать письмо для каждого нового сообщения, кроме тех случаев, когда пост является моим"
+ individual_no_echo: "Присылать письмо по каждому новому сообщению, кроме моих собственных"
many_per_day: "Присылать письмо для каждого нового сообщения (примерно {{dailyEmailEstimate}} в день)"
few_per_day: "Присылать письмо для каждого нового сообщения (примерно 2 в день)"
warning: "Включён режим списка рассылки. Настройки email-уведомлений переопределены."
tag_settings: "Теги"
watched_tags: "Наблюдение"
- watched_tags_instructions: "Вы будете автоматически отслеживать все темы в этими тегами. Вам будут приходить уведомления о новых сообщениях и темах, рядом с темой появится количество новых сообщений."
+ watched_tags_instructions: "Вы будете автоматически отслеживать все темы с этими тегами. Вам будут приходить уведомления о новых сообщениях и темах, рядом с темой появится количество новых сообщений."
tracked_tags: "Отслеживаемая"
tracked_tags_instructions: "Вы будете автоматически отслеживать все темы с этими тегами. Рядом со списком тем будет отображено количество новых сообщений."
muted_tags: "Выключено"
@@ -895,16 +908,16 @@ ru:
watched_categories_instructions: "Вы будете автоматически отслеживать все темы в этих разделах. Вам будут приходить уведомления о новых сообщениях и темах, рядом со списком тем будет отображено количество новых сообщений."
tracked_categories: "Отслеживаемые разделы"
tracked_categories_instructions: "Вы будете получать уведомления о каждом новом сообщении в этой теме. Рядом со списком тем будет показано количество новых сообщений."
- watched_first_post_categories: "Просмотр Первого сообщения"
+ watched_first_post_categories: "Просмотр первого сообщения"
watched_first_post_categories_instructions: "Уведомлять только о первом сообщении в каждой новой теме в этих разделах."
- watched_first_post_tags: "Просмотр Первого сообщения"
+ watched_first_post_tags: "Просмотр первого сообщения"
watched_first_post_tags_instructions: "Уведомлять только о первом сообщении в каждой новой теме с этими тегами."
muted_categories: "Выключенные разделы"
muted_categories_instructions: "Не уведомлять меня о новых темах в этих разделах и не показывать новые темы на странице «Непрочитанные»."
- muted_categories_instructions_dont_hide: "Вы не будете уведомлены о новых темах в этих категориях."
+ muted_categories_instructions_dont_hide: "Вы не будете уведомлены о новых темах в этих разделах."
no_category_access: "Как модератор Вы ограничены в доступе к разделу, сохранения отклонены."
delete_account: "Удалить мою учётную запись"
- delete_account_confirm: "Вы уверены, что хотите удалить свою учётную запись? Отменить удаление будет невозможно!"
+ delete_account_confirm: "Вы действительно хотите удалить свою учётную запись? Отменить удаление будет невозможно!"
deleted_yourself: "Ваша учётная запись была успешно удалена."
delete_yourself_not_allowed: "Пожалуйста, свяжитесь с администрацией сайта, если хотите удалить свой аккаунт."
unread_message_count: "Сообщения"
@@ -915,15 +928,15 @@ ru:
ignored_users: "Игнорировать"
ignored_users_instructions: "Запретить все сообщения и уведомления от этих пользователей."
tracked_topics_link: "Показать"
- automatically_unpin_topics: "Автоматически откреплять темы после прочтения."
+ automatically_unpin_topics: "Автоматически откреплять темы после прочтения"
apps: "Приложения"
revoke_access: "Лишить прав доступа"
- undo_revoke_access: "Отменить Лишение прав доступа"
+ undo_revoke_access: "Отменить лишение прав доступа"
api_approved: "Подтверждено"
api_last_used_at: "Последнее использование:"
theme: "Стиль"
home: "Домашняя страница по умолчанию"
- staged: "Поэтапный"
+ staged: "Сымитированный"
staff_counters:
flags_given: "полезные жалобы"
flagged_posts: "сообщения с жалобами"
@@ -977,7 +990,7 @@ ru:
description: "Каждый из этих резервных кодов может быть использован только один раз. Храните их в безопасности."
second_factor:
title: "Двухфакторная аутентификация"
- enable: "Управление Двухфакторной аутентификацией"
+ enable: "Управление двухфакторной аутентификацией"
forgot_password: "Забыли пароль?"
confirm_password_description: "Подтвердите ваш пароль чтобы продолжить"
name: "Имя"
@@ -995,32 +1008,32 @@ ru:
use: "Используйте приложение для проверки подлинности "
enforced_notice: "Вы должны включить двухфакторную аутентификацию перед доступом к этому сайту."
disable: "отключить"
- disable_title: "Отключить Второй Фактор"
- disable_confirm: "Вы уверены, что хотите отключить все вторые факторы?"
+ disable_title: "Отключить второй фактор"
+ disable_confirm: "Вы действительно хотите отключить все вторые факторы?"
edit: "Редактировать"
- edit_title: "Изменение Второго Фактора"
- edit_description: "Имя Второго Фактора"
+ edit_title: "Изменение второго фактора"
+ edit_description: "Имя второго фактора"
enable_security_key_description: "Когда вы подготовите свой физический ключ безопасности, нажмите кнопку Регистрация ниже."
totp:
- title: "Token-Based Аутентификация"
- add: "Новый Аутентификатор"
- default_name: "Мой Аутентификатор"
+ title: "Token-Based аутентификация"
+ add: "Новый аутентификатор"
+ default_name: "Мой аутентификатор"
security_key:
register: "Зарегистрироваться"
title: "Ключи безопасности"
add: "Зарегистрировать ключ безопасности"
- default_name: "Главный Ключ Безопасности"
+ default_name: "Главный ключ безопасности"
not_allowed_error: "Время регистрации ключа безопасности истекло или было отменено."
already_added_error: "Вы уже зарегистрировали этот ключ безопасности. Вам не нужно регистрировать его снова."
- edit: "Изменить Ключ Безопасности"
- edit_description: "Имя Ключа Безопасности"
+ edit: "Изменить ключ безопасности"
+ edit_description: "Имя ключа безопасности"
delete: "Удалить"
change_about:
title: "Изменить информацию обо мне"
error: "При изменении значения произошла ошибка."
change_username:
title: "Изменить псевдоним"
- confirm: "Вы абсолютно уверены, что хотите изменить свое имя пользователя?"
+ confirm: "Вы абсолютно действительно хотите изменить свое имя пользователя?"
taken: "Этот псевдоним уже занят."
invalid: "Псевдоним должен состоять только из цифр и латинских букв"
change_email:
@@ -1047,13 +1060,14 @@ ru:
title: "Фон карточки пользователя"
instructions: "Картинки фона будут отцентрированы и по-умолчанию имеют ширину 590 пикселей."
change_featured_topic:
- title: "Избранная Тема"
+ title: "Избранная тема"
+ instructions: "Ссылка на эту тему будет отображаться в карточке пользователя и в вашем профиле."
email:
title: "E-mail"
primary: "Основной адрес электронной почты"
secondary: "Дополнительный адрес электронной почты"
no_secondary: "Нет дополнительного адреса электронной почты"
- sso_override_instructions: "E-mail может быть обновлен от поставщика SSO."
+ sso_override_instructions: "E-mail может быть переопределен от поставщика SSO."
instructions: "Не будет опубликован."
ok: "Мы вышлем вам письмо для подтверждения"
invalid: "Введите действующий адрес электронной почты"
@@ -1076,7 +1090,7 @@ ru:
generic: "Ваша%{provider} учетная запись будет использоваться для аутентификации."
name:
title: "Имя"
- instructions: "ваше полное имя (опционально)"
+ instructions: "Ваше полное имя (опционально)"
instructions_required: "Ваше полное имя"
too_short: "Ваше имя слишком короткое"
ok: "Допустимое имя"
@@ -1144,7 +1158,7 @@ ru:
always: "всегда"
never: "никогда"
email_digests:
- title: "В случае моего отсутствия на форуме, присылайте мне сводку популярных новостей"
+ title: "В случае моего отсутствия на форуме присылать мне сводку популярных новостей"
every_30_minutes: "каждые 30 минут"
every_hour: "каждый час"
daily: "ежедневно"
@@ -1152,7 +1166,7 @@ ru:
every_month: "каждый месяц"
every_six_months: "каждые шесть месяцев"
email_level:
- title: "Присылать почтовое уведомление, когда кто-то цитирует меня, отвечает на мой пост, упоминает мой @псевдоним или приглашает меня в тему"
+ title: "Присылать письмо когда кто-то меня цитирует, отвечает на мое сообщение, упоминает мой @псевдоним или приглашает меня в тему"
always: "всегда"
only_when_away: "если вы в офлайне"
never: "никогда"
@@ -1180,11 +1194,12 @@ ru:
after_4_minutes: "более 4х минут"
after_5_minutes: "более 5 минут"
after_10_minutes: "более 10 минут"
- notification_level_when_replying: "Когда я пишу в теме, установить уровень уведомлений для темы"
+ notification_level_when_replying: "Когда я пишу в теме, установить для неё следующий уровень уведомлений"
invited:
search: "Введите текст для поиска по приглашениям..."
title: "Приглашения"
user: "Кто приглашен"
+ sent: "Последний Отправленный"
none: "Нет приглашений для отображения."
truncated:
one: "Первое приглашение"
@@ -1205,7 +1220,7 @@ ru:
rescinded: "Приглашение отозвано"
rescind_all: "Удалить все просроченные приглашения"
rescinded_all: "Все просроченные приглашения удалены!"
- rescind_all_confirm: "Вы уверены, что хотите удалить все просроченные приглашения?"
+ rescind_all_confirm: "Вы действительно хотите удалить все просроченные приглашения?"
reinvite: "Повторить приглашение"
reinvite_all: "Повторить все приглашения"
reinvite_all_confirm: "Вы действительно хотите отправить все приглашения повторно?"
@@ -1292,13 +1307,13 @@ ru:
most_liked_users: "Фавориты"
most_replied_to_users: "Самые активные собеседники"
no_likes: "Пока ни одной симпатии."
- top_categories: "Лучшие Категории"
+ top_categories: "Лучшие разделы"
topics: "Темы"
replies: "Отвечает"
ip_address:
- title: "Последний IP адрес"
+ title: "Последний IP-адрес"
registration_ip_address:
- title: "IP адрес регистрации"
+ title: "IP-адрес регистрации"
avatar:
title: "Аватар"
header_title: "профиль, сообщения, закладки и настройки"
@@ -1345,7 +1360,7 @@ ru:
logout_disabled: "Выход отключён, пока сайт в режиме «только для чтения»"
too_few_topics_and_posts_notice: "Давайте приступим к обсуждению! Есть %{currentTopics} тем и %{currentPosts} постов. Пользователи должны больше читать и отвечать – мы рекомендуем, по крайней мере %{requiredTopics} тем и %{requiredPosts} постов. Только сотрудники могут видеть это сообщение."
too_few_topics_notice: "Давайте приступим к обсуждению! Есть %{currentTopics} тем. Пользователи должны больше читать и отвечать – мы рекомендуем, по крайней мере %{requiredTopics} тем. Только сотрудники могут видеть это сообщение."
- too_few_posts_notice: "Давайте приступим к обсуждению! Есть %{currentPosts} постов. Пользователям нужно больше читать и отвечать - мы рекомендуем хотя бы %{requiredPosts} постов. Только сотрудники могут видеть это сообщение."
+ too_few_posts_notice: "Давайте приступим к обсуждению! Есть %{currentPosts} сообщений. Пользователям нужно больше читать и отвечать - мы рекомендуем хотя бы %{requiredPosts} сообщений. Только сотрудники могут видеть это сообщение."
logs_error_rate_notice:
reached_hour_MF: "{relativeAge} – {rate, plural, one {# error/hour} или {# errors/hour}} достигнут предел настройки сайта {limit, plural, one {# error/hour} или {# errors/hour}}."
reached_minute_MF: "{relativeAge} – {rate, plural, one {# error/minute} или {# errors/minute}} достигнут предел настройки сайта {limit, plural, one {# error/minute} или {# errors/minute}}."
@@ -1396,7 +1411,7 @@ ru:
private_message_info:
title: "Сообщение"
invite: "Пригласить других ..."
- edit: "Добавить или Удалить ..."
+ edit: "Добавить или удалить ..."
leave_message: "Вы действительно хотите оставить это сообщение?"
remove_allowed_user: "Вы действительно хотите удалить {{name}} из данного сообщения?"
remove_allowed_group: "Вы действительно хотите удалить {{name}} из данного сообщения?"
@@ -1406,7 +1421,7 @@ ru:
created: "Создан"
created_lowercase: "создано"
trust_level: "Уровень доверия"
- search_hint: "Псевдоним, e-mail или IP адрес"
+ search_hint: "Псевдоним, e-mail или IP-адрес"
create_account:
disclaimer: "Регистрируясь, вы соглашаетесь с политикой конфиденциальности и условиями предоставления услуг ."
title: "Зарегистрироваться"
@@ -1436,7 +1451,7 @@ ru:
complete_email_not_found: "Нет совпадений аккаунта по %{email} "
confirm_title: "Перейти на %{site_name}"
logging_in_as: "Войти как %{email}"
- confirm_button: Завершить Вход
+ confirm_button: Завершить вход
login:
title: "Войти"
username: "Пользователь"
@@ -1447,9 +1462,9 @@ ru:
second_factor_backup_title: "Запасной вход двухфакторной аутентификации"
second_factor_backup_description: "Введите запасной код:"
second_factor: "Войти с помощью программы аутентификации"
- security_key_description: "Когда вы подготовите свой физический ключ безопасности, нажмите кнопку Аутентификация с ключом безопасности ниже."
+ security_key_description: "Когда вы подготовите свой физический ключ безопасности, нажмите кнопку Аутентификация с ключом безопасности ниже."
security_key_alternative: "Попробуйте другой способ"
- security_key_authenticate: "Аутентификация с Ключом Безопасности."
+ security_key_authenticate: "Аутентификация с ключом безопасности."
security_key_not_allowed_error: "Время проверки подлинности ключа безопасности истекло или было отменено."
security_key_no_matching_credential_error: "В указанном ключе безопасности не найдено подходящих учетных данных."
security_key_support_missing_error: "Ваше текущее устройство или браузер не поддерживает использование ключей безопасности. Пожалуйста, используйте другой метод."
@@ -1476,7 +1491,7 @@ ru:
change_email: "Изменить электронную почту"
provide_new_email: "Укажите новый адрес электронной почты, чтобы задействовать его и заново выслать активационное письмо."
submit_new_email: "Обновить электронную почту"
- sent_activation_email_again: "По адресу {{currentEmail}} повторно отправлено письмо с инструкциями по активации вашей учетной записи. Доставка сообщения может занять несколько минут. Имейте в виду, что иногда по ошибке письмо может попасть в папку Спам."
+ sent_activation_email_again: "По адресу {{currentEmail}} повторно отправлено письмо с инструкциями по активации вашей учетной записи. Доставка сообщения может занять несколько минут. Имейте в виду, что иногда по ошибке письмо может попасть в папку Спам ."
sent_activation_email_again_generic: "Мы отправили еще одно письмо для активации. Это может занять несколько минут для того, чтобы письмо было доставлено; не забудьте проверить папку со спамом."
to_continue: "Пожалуйста, войдите"
preferences: "Необходимо войти на сайт для редактирования настроек профиля."
@@ -1526,11 +1541,11 @@ ru:
facebook_messenger: "Facebook Messenger"
category_page_style:
categories_only: "Только разделы"
- categories_with_featured_topics: "Разделы и их лучшие темы"
+ categories_with_featured_topics: "Разделы с избранными темами"
categories_and_latest_topics: "Разделы и список последних тем форума"
- categories_and_top_topics: "Категории и главные темы"
- categories_boxes: "Коробки с Подкатегориями"
- categories_boxes_with_topics: "Коробки с Избранными Темами"
+ categories_and_top_topics: "Разделы и главные темы"
+ categories_boxes: "Блоки с подразделами"
+ categories_boxes_with_topics: "Блоки с избранными темами"
shortcut_modifier_key:
shift: "Shift"
ctrl: "Ctrl"
@@ -1539,7 +1554,7 @@ ru:
conditional_loading_section:
loading: Загрузка...
category_row:
- topic_count: "{{count}} тем в этой категории"
+ topic_count: "{{count}} тем в этом разделе"
select_kit:
default_header_text: Выбрать...
no_content: Совпадений не найдено
@@ -1565,9 +1580,9 @@ ru:
filter_placeholder: Искать emoji
smileys_&_emotion: Смайлики и эмоции
people_&_body: Люди и части тел
- animals_&_nature: Животные и Природа
- food_&_drink: Еда и Напитки
- travel_&_places: Путешествия и Места
+ animals_&_nature: Животные и природа
+ food_&_drink: Еда и напитки
+ travel_&_places: Путешествия и места
activities: Деятельность
objects: Objects
symbols: Атрибутика
@@ -1581,11 +1596,11 @@ ru:
medium_dark_tone: Средний темный тон скина
dark_tone: Темный оттенок скина
shared_drafts:
- title: "Общие Черновики"
- notice: "Эти темы видны только тем, кто может видеть {{category}} категорию."
- destination_category: "Категория Назначения"
- publish: "Публикация Общего Черновика"
- confirm_publish: "Вы уверены, что хотите опубликовать этот черновик?"
+ title: "Общие черновики"
+ notice: "Эти темы видны только тем, кому доступен раздел {{category}} ."
+ destination_category: "Раздел назначения"
+ publish: "Публикация общего черновика"
+ confirm_publish: "Вы действительно хотите опубликовать этот черновик?"
publishing: "Публикация темы..."
composer:
emoji: "Смайлики :)"
@@ -1632,7 +1647,7 @@ ru:
create_topic: "Создать тему"
create_pm: "Отправить личное сообщение"
create_whisper: "Внутреннее сообщение"
- create_shared_draft: "Создать Общий Проект"
+ create_shared_draft: "Создать общий черновик"
edit_shared_draft: "Редактировать общий черновик"
title: "Или нажмите Ctrl+Enter"
users_placeholder: "Добавить пользователя"
@@ -1642,8 +1657,8 @@ ru:
topic_featured_link_placeholder: "Введите ссылку, отображаемую с названием."
remove_featured_link: "Удалить ссылку из темы."
reply_placeholder: "Поддерживаемые форматы: Markdown, BBCode и HTML. Чтобы вставить картинку, перетащите ее сюда или вставьте с помощью Ctrl+V, Command-V, или нажмите правой кнопкой мыши и выберите меню \"вставить\"."
- reply_placeholder_no_images: "Введите здесь. Используйте Markdown, BBCode или HTML для форматирования."
- reply_placeholder_choose_category: "Выберите категорию перед вводом здесь."
+ reply_placeholder_no_images: "Введите текст здесь. Используйте Markdown, BBCode или HTML для форматирования."
+ reply_placeholder_choose_category: "Выберите раздел перед тем, как вводить текст."
view_new_post: "Посмотреть созданное вами сообщение."
saving: "Сохранение..."
saved: "Сохранено!"
@@ -1654,7 +1669,7 @@ ru:
quote_post_title: "Процитировать сообщение целиком"
bold_label: "Ж"
bold_title: "Жирный"
- bold_text: "вставьте сюда текст, которный нужно выделить жирным"
+ bold_text: "вставьте сюда текст, который нужно выделить жирным"
italic_label: "К"
italic_title: "Курсив"
italic_text: "вставьте сюда текст, который нужно выделить курсивом"
@@ -1662,24 +1677,24 @@ ru:
link_description: "введите описание ссылки"
link_dialog_title: "Вставить ссылку"
link_optional_text: "кликабельный текст ссылки"
- link_url_placeholder: "Вставьте URL или введите для поиска темы"
+ link_url_placeholder: "Вставьте URL или введите текст для поиска темы"
quote_title: "Цитата"
quote_text: "Впишите сюда текст цитаты"
code_title: "Текст \"как есть\" (без применения форматирования)"
- code_text: "Впишите сюда текст; также, отключить форматирование текста можно, начав строку с 4х пробелов"
+ code_text: "Впишите сюда текст; также отключить форматирование текста можно, начав строку с 4х пробелов"
paste_code_text: "Напечатайте или вставьте сюда код"
upload_title: "Вставить картинку или прикрепить файл"
upload_description: "Впишите сюда описание файла"
olist_title: "Нумерованный список"
ulist_title: "Ненумерованный список"
list_item: "Пункт первый"
- toggle_direction: "Переключить Направление"
+ toggle_direction: "Переключить направление"
help: "Справка по форматированию (Markdown)"
- collapse: "свернуть панель редактора"
- open: "открыть панель редактора"
+ collapse: "Свернуть панель редактора"
+ open: "Открыть панель редактора"
abandon: "закрыть редактор и отменить черновик"
- enter_fullscreen: "войти в полноэкранный режим редактора"
- exit_fullscreen: "выйти из полноэкранного режима редактора"
+ enter_fullscreen: "Включить полноэкранный режим редактора"
+ exit_fullscreen: "Выключить полноэкранный режима редактора"
modal_ok: "OK"
modal_cancel: "Отмена"
cant_send_pm: "К сожалению, вы не можете отправлять сообщения пользователю %{username}."
@@ -1693,7 +1708,7 @@ ru:
edit: Редактировать
reply_to_post:
label: "Ответить на сообщение %{postNumber} от %{postUsername}"
- desc: Ответ на конкретный пост
+ desc: Ответить на конкретное сообщение
reply_as_new_topic:
label: Ответить в новой связанной теме
desc: "Создать новую тему, связанную с этой темой"
@@ -1702,14 +1717,14 @@ ru:
desc: Создать новое личное сообщение
reply_to_topic:
label: Ответить на тему
- desc: "Ответ на тему, а не какой-либо конкретный пост"
+ desc: "Ответить на тему в целом, а на конкретное сообщение"
toggle_whisper:
label: Включить шопот
- desc: Шёпот виден только персоналу
+ desc: Внутреннее сообщение доступно только персоналу
create_topic:
label: "Новая тема"
shared_draft:
- label: "Общий Проект"
+ label: "Общий проект"
desc: "Проект темы, которая будет видна только сотрудникам"
toggle_topic_bump:
label: "Не поднимать тему"
@@ -1726,10 +1741,10 @@ ru:
few: "{{count}} непрочитанных сообщения"
many: "{{count}} непрочитанных сообщений"
other: "{{count}} непрочитанных сообщений"
- title: "уведомления об упоминании @псевдонима, ответах на ваши посты и темы, сообщения и т.д."
+ title: "уведомления об упоминании вашего @псевдонима, ответах на ваши сообщения и темы, сообщения и т.д."
none: "Уведомления не могут быть загружены."
empty: "Уведомления не найдены."
- post_approved: "Ваш пост был одобрен"
+ post_approved: "Ваше сообщение было одобрено"
reviewable_items: "пункты, требующие рассмотрения"
mentioned: "{{username}} {{description}}"
group_mentioned: "{{username}} {{description}}"
@@ -1745,10 +1760,10 @@ ru:
many: "{{username}}, {{username2}} и {{count}} других {{description}}"
other: "{{username}}, {{username2}} и {{count}} других {{description}}"
liked_consolidated_description:
- one: "Понравился {{count}} ваш пост"
- few: "Понравилось {{count}} ваших поста"
- many: "Понравилось {{count}} ваших постов"
- other: "Понравилось {{count}} ваших постов"
+ one: "Понравилось {{count}} ваше сообщение"
+ few: "Понравилось {{count}} ваших сообщений"
+ many: "Понравилось {{count}} ваших сообщений"
+ other: "Понравилось {{count}} ваших сообщений"
liked_consolidated: "{{username}} {{description}}"
private_message: "{{username}} {{description}}"
invited_to_private_message: " {{username}} {{description}}"
@@ -1773,43 +1788,43 @@ ru:
replied: '{{username}} ответил вам в "{{topic}}" - {{site_title}}'
posted: '{{username}} написал в "{{topic}}" - {{site_title}}'
private_message: '{{username}} отправил вам личное сообщение в "{{topic}}" - {{site_title}}'
- linked: '{{username}} ссылается на ваш пост в теме: "{{topic}}" - {{site_title}}'
+ linked: '{{username}} сослался на ваше сообщение из "{{topic}}" - {{site_title}}'
watching_first_post: '{{username}} создал новую тему "{{topic}}" - {{site_title}}'
confirm_title: "Уведомления включены - %{site_title}"
confirm_body: "Успешно! Уведомления были включены."
custom: "Уведомления от {{username}} до %{site_title}"
titles:
- mentioned: "упомянутый"
- replied: "новый ответ"
+ mentioned: "Упомянутый"
+ replied: "Новый ответ"
quoted: "цитируемый"
edited: "отредактированный"
- liked: "новая симпатия"
- private_message: "новое личное сообщение"
- invited_to_private_message: "приглашен в личное сообщение"
- invitee_accepted: "приглашение принято"
- posted: "новый пост"
- moved_post: "сообщение перемещено"
+ liked: "Новая симпатия"
+ private_message: "Новое личное сообщение"
+ invited_to_private_message: "Приглашен в личное сообщение"
+ invitee_accepted: "Приглашение принято"
+ posted: "Новое сообщение"
+ moved_post: "Сообщение перемещено"
linked: "связанный"
granted_badge: "награда получена"
invited_to_topic: "приглашен в тему"
group_mentioned: "упомянутая группа"
group_message_summary: "новые групповые сообщения"
- watching_first_post: "новая тема"
- topic_reminder: "напоминание о теме"
+ watching_first_post: "Новая тема"
+ topic_reminder: "Напоминание о теме"
liked_consolidated: "новые симпатии"
- post_approved: "сообщение утверждено"
+ post_approved: "Сообщение утверждено"
membership_request_consolidated: "новые запросы на вступление"
upload_selector:
- title: "Add an image"
+ title: "Вставка изображения"
title_with_attachments: "Добавить изображение или файл"
from_my_computer: "С моего устройства"
- from_the_web: "С интернета"
- remote_tip: "ссылка на изображение"
- remote_tip_with_attachments: "ссылка на изображение или файл {{authorized_extensions}}"
- local_tip: "выбрать изображения с вашего устройства"
- local_tip_with_attachments: "выбрать изображения или файлы с вашего устройства {{authorized_extensions}}"
- hint: "(вы так же можете перетащить объект в редактор для его загрузки)"
- hint_for_supported_browsers: "вы так же можете перетащить или скопировать изображения в редактор"
+ from_the_web: "Из интернета"
+ remote_tip: "Ссылка на изображение"
+ remote_tip_with_attachments: "Ссылка на изображение или файл {{authorized_extensions}}"
+ local_tip: "Выбор изображения с вашего устройства"
+ local_tip_with_attachments: "Выбор изображения или файла с вашего устройства {{authorized_extensions}}"
+ hint: "(вы также можете перетащить объект в редактор для его загрузки)"
+ hint_for_supported_browsers: "вы также можете перетащить или скопировать изображения в редактор"
uploading: "Загрузка"
select_file: "Выбрать файл"
default_image_alt_text: изображение
@@ -1837,8 +1852,8 @@ ru:
results_page: "Результаты поиска для '{{term}}'"
more_results: "Найдено множество результатов. Пожалуйста, уточните, критерии поиска."
cant_find: "Не можете найти нужную информацию?"
- start_new_topic: "Создать новую тему?"
- or_search_google: "Или попробуйте поискать в Google:"
+ start_new_topic: "Создайте новую тему"
+ or_search_google: "или попробуйте поискать в Google:"
search_google: "Попробуйте поискать в Google:"
search_google_button: "Google"
search_google_title: "Искать на этом сайте"
@@ -1859,29 +1874,31 @@ ru:
with_badge:
label: С наградами
with_tags:
- label: Помеченный
+ label: Теги
filters:
label: Ограничить поиск по темам/сообщениям...
- title: Совпадениям в заголовке
- likes: Мне понравились
+ title: С совпадениями в заголовке
+ likes: Понравившиеся
posted: В которых я писал
+ created: Которые я создал
watching: За которыми я наблюдаю
tracking: За которыми я слежу
private: В моих сообщениях
- bookmarks: Мои закладки
+ bookmarks: В моих закладках
first: Только первые сообщения в темах
- pinned: Закреплены
- unpinned: Не закреплены
+ pinned: Закреплённые
+ unpinned: Незакреплённые
seen: Прочитанные
- unseen: Отметить непрочитанным
+ unseen: Непрочитанные
wiki: Являются вики
images: Содержат изображения
all_tags: Все вышеуказанные теги
statuses:
label: Где темы
- open: Открыта
- closed: Закрыта
- archived: Заархивирована
+ open: Открытые
+ closed: Закрытые
+ public: Публичные
+ archived: Заархивированные
noreplies: Без ответов
single_user: С одним пользователем
post:
@@ -1939,8 +1956,8 @@ ru:
category: "В разделе {{category}} отсутствуют темы."
top: "Нет обсуждаемых тем."
educate:
- new: '
Ваши новые темы скоро появятся тут.
По умолчанию, новые темы отмечаются иконкой: Новая , если она была создана в течении 2 недель.
Перейдите в настройки для того, чтобы выбрать период активности новых тем.
'
- unread: '
Ваши непрочитанные темы скоро появятся тут.
По умолчанию темы получают счётчик 1 , если:
Создана тема Ответили на тему Тема прочитана по истечении 4 минут, после её создания Или можно задать свои настройки отслеживания новых тем.
Перейдите в свои настройки .
'
+ new: '
Ваши новые темы скоро появятся тут.
Новые темы по умолчанию отмечаются иконкой Новая , если они были созданы за последние 2 дня.
При необходимости вы можете изменить параметры уведомлений в настройках профиля пользователя.
'
+ unread: '
Ваши непрочитанные темы скоро появятся тут.
По умолчанию темы получают счётчик 1 , если:
Создана тема Ответили на тему Время чтения темы пользователем превышает 4 минуты Вы можете изменить настройки уведомлений в нижней части каждой темы.
При необходимости вы можете изменить стандартные параметры уведомлений в настройках профиля пользователя.
'
bottom:
latest: "Тем больше нет."
posted: "Созданных тем больше нет."
@@ -1956,7 +1973,7 @@ ru:
few: "{{count}} сообщения в теме"
many: "{{count}} сообщений в теме"
other: "{{count}} сообщений в теме"
- create: "Создать Тему"
+ create: "Создать тему"
create_long: "Создать новую тему"
open_draft: "Открыть черновик"
private_message: "Новое личное сообщение"
@@ -1974,11 +1991,11 @@ ru:
title: "Отложить"
feature_on_profile:
help: "Добавьте ссылку на эту тему в карточку пользователя и профиль"
- title: "Добавить в Профиль"
+ title: "Добавить в профиль"
remove_from_profile:
warning: "В вашем профиле уже есть избранная тема. Если вы продолжите, эта тема заменит существующую."
help: "Удалить ссылку на эту тему в вашем профиле "
- title: "Убрать из Профиля"
+ title: "Убрать из профиля"
list: "Темы"
new: "новая тема"
unread: "не прочитано"
@@ -2004,15 +2021,15 @@ ru:
title: "Тема не найдена"
description: "К сожалению, запрошенная тема не найдена. Возможно, она была удалена модератором."
total_unread_posts:
- one: "у вас {{count}} непрочитанное сообщение в этой теме"
- few: "у вас {{count}} непрочитанных сообщения в этой теме"
- many: "у вас {{count}} непрочитанных сообщений в этой теме"
- other: "у вас {{count}} непрочитанных сообщений в этой теме"
+ one: "У вас {{count}} непрочитанное сообщение в этой теме"
+ few: "У вас {{count}} непрочитанных сообщения в этой теме"
+ many: "У вас {{count}} непрочитанных сообщений в этой теме"
+ other: "У вас {{count}} непрочитанных сообщений в этой теме"
unread_posts:
- one: "у вас {{count}} непрочитанное старое сообщение в этой теме"
- few: "у вас {{count}} непрочитанных старых сообщения в этой теме"
- many: "у вас {{count}} непрочитанных старых сообщений в этой теме"
- other: "у вас {{count}} непрочитанных старых сообщений в этой теме"
+ one: "У вас {{count}} непрочитанное старое сообщение в этой теме"
+ few: "У вас {{count}} непрочитанных старых сообщения в этой теме"
+ many: "У вас {{count}} непрочитанных старых сообщений в этой теме"
+ other: "У вас {{count}} непрочитанных старых сообщений в этой теме"
new_posts:
one: "в этой теме {{count}} новое сообщение с её последнего просмотра вами"
few: "в этой теме {{count}} новых сообщения с её последнего просмотра вами"
@@ -2025,15 +2042,15 @@ ru:
other: "в теме {{count}} лайков"
back_to_list: "Вернуться к списку тем"
options: "Опции темы"
- show_links: "показать ссылки в теме"
- toggle_information: "скрыть / показать подробную информацию о теме"
+ show_links: "Показать ссылки в теме"
+ toggle_information: "Показать/скрыть подробную информацию о теме"
read_more_in_category: "Хотите почитать что-нибудь ещё? Можно посмотреть темы в {{catLink}} или {{latestLink}}."
read_more: "Хотите почитать что-нибудь ещё? {{catLink}} или {{latestLink}}."
group_request: "Вам нужно запросить членство в группе `{{name}}` чтобы увидеть эту тему."
group_join: "Вам нужно присоединиться к группе `{{name}}` чтобы увидеть эту тему."
group_request_sent: "Ваш запрос на членство в группе был отправлен. Вам сообщат, когда будет одобрено."
- unread_indicator: "Ни один участник еще не прочитал последний пост этой темы."
- read_more_MF: "У вас осталось { UNREAD, plural, =0 {} one {
1 непрочитанная } other {
# непрочитанных } } { NEW, plural, =0 {} one { {BOTH, select, true{и } false { } other{}}
1 новая тема} other { {BOTH, select, true{и } false { } other{}}
# новых тем} }, или {CATEGORY, select, true {посмотрите другие темы в разделе {catLink}} false {{latestLink}} other {}}"
+ unread_indicator: "Никто еще не дочитал до конца этой темы."
+ read_more_MF: "У вас осталось { UNREAD, plural, =0 {} one {
1 непрочитанная } other {
# непрочитанных } } { NEW, plural, =0 {} one { {BOTH, select, true{и } false { } other{}}
1 новая тема} other { {BOTH, select, true{и } false { } other{}}
# новых тем} }, вы также можете {CATEGORY, select, true {посмотреть другие темы в разделе {catLink}} false {{latestLink}} other {}}"
browse_all_categories: Просмотреть все разделы
view_latest_topics: посмотреть последние темы
suggest_create_topic: "Почему бы вам не создать новую тему?"
@@ -2041,14 +2058,14 @@ ru:
jump_reply_down: перейти к более поздним ответам
deleted: "Тема удалена"
topic_status_update:
- title: "Таймер Темы"
- save: "Установить Таймер"
+ title: "Таймер темы"
+ save: "Установить таймер"
num_of_hours: "Количество часов:"
- remove: "Удалить Таймер"
+ remove: "Удалить таймер"
publish_to: "Опубликовать в:"
when: "Когда:"
public_timer_types: Таймер темы
- private_timer_types: Таймер Тем Пользователя
+ private_timer_types: Таймер пользователя
time_frame_required: "Пожалуйста, выберите временные рамки"
auto_update_input:
none: "Выбор таймфрейма"
@@ -2068,7 +2085,7 @@ ru:
pick_date_and_time: "Выбрать дату и время"
set_based_on_last_post: "Закрыть после последнего сообщения"
publish_to_category:
- title: "Расписание публикации"
+ title: "Опубликовать в разделе..."
temp_open:
title: "Открыть на время"
auto_reopen:
@@ -2079,11 +2096,11 @@ ru:
title: "Автоматическое закрытие темы"
label: "Закрыть тему через:"
error: "Пожалуйста, введите корректное значение."
- based_on_last_post: "Не закрывайте, пока последний пост в теме не станет слишком старым."
+ based_on_last_post: "Не закрывать, пока не пройдет столько времени с последнего сообщения в теме."
auto_delete:
title: "Автоматическое удаление темы"
auto_bump:
- title: "Само-поднятие темы"
+ title: "Автоматическое поднятие темы"
reminder:
title: "Напомнить мне"
status_update_notice:
@@ -2122,16 +2139,16 @@ ru:
title: изменить частоту уведомлений об этой теме
reasons:
mailing_list_mode: "Вы включили режим почтовой рассылки, поэтому Вы будете получать уведомления об ответах в этой теме через e-mail."
- "3_10": "Вы будете получать уведомления, т.к. наблюдаете за тегом этой темы."
- "3_6": "Вы будете получать уведомления, т.к. наблюдаете за этим разделом."
- "3_5": "Вы будете получать уведомления, т.к. наблюдение темы началось автоматически."
- "3_2": "Вы будете получать уведомления, т.к. наблюдаете за этой темой."
- "3_1": "Вы будете получать уведомления, т.к. создали эту тему."
- "3": "Вы будете получать уведомления, т.к. наблюдаете за темой."
- "2_8": "Вы увидите количество новых ответов, т.к. следите за этим разделом."
- "2_4": "Вы увидите количество новых ответов, т.к. вы размещали ответ в этой теме."
- "2_2": "Вы увидите количество новых ответов, т.к. следите за этой темой."
- "2": 'Вы увидите количество новых ответов, т.к.
читали эту тему .'
+ "3_10": "Вы будете получать уведомления, поскольку наблюдаете за тегом этой темы."
+ "3_6": "Вы будете получать уведомления, поскольку наблюдаете за этим разделом."
+ "3_5": "Вы будете получать уведомления, поскольку наблюдение темы началось автоматически."
+ "3_2": "Вы будете получать уведомления, поскольку наблюдаете за этой темой."
+ "3_1": "Вы будете получать уведомления, поскольку создали эту тему."
+ "3": "Вы будете получать уведомления, поскольку наблюдаете за темой."
+ "2_8": "Вы увидите количество новых ответов, поскольку следите за этим разделом."
+ "2_4": "Вы увидите количество новых ответов, поскольку вы размещали ответ в этой теме."
+ "2_2": "Вы увидите количество новых ответов, поскольку следите за этой темой."
+ "2": 'Вы увидите количество новых ответов, поскольку
читали эту тему .'
"1_2": "Вы будете получать уведомления, если кто-то упомянет ваш @псевдоним или ответит вам."
"1": "Вы будете получать уведомления, если кто-то упомянет ваш @псевдоним или ответит вам."
"0_7": "Не получать уведомлений из этого раздела."
@@ -2176,8 +2193,8 @@ ru:
invisible: "Исключить из списков"
visible: "Включить в списки"
reset_read: "Сбросить счетчики"
- make_public: "Сделать тему публичной"
- make_private: "Написать личное сообщение"
+ make_public: "Превратить в публичную тему"
+ make_private: "Превратить в личное сообщение"
reset_bump_date: "Сбросить дату поднятия"
feature:
pin: "Закрепить тему"
@@ -2187,7 +2204,7 @@ ru:
remove_banner: "Удалить объявление"
reply:
title: "Ответить"
- help: "начать составление ответа к этой теме"
+ help: "Начать составление ответа к этой теме"
clear_pin:
title: "Открепить"
help: "Открепить тему, чтобы она более не показывалась в самом начале списка тем"
@@ -2199,16 +2216,16 @@ ru:
title: "Печать"
help: "Открыть версию для печати"
flag_topic:
- title: "Жалоба"
- help: "пожаловаться на сообщение"
+ title: "Пожаловаться"
+ help: "Пожаловаться на сообщение"
success_message: "Вы пожаловались на тему."
make_public:
- title: "Преобразовать в Публичную Тему"
- choose_category: "Пожалуйста, выберите категорию для публичной темы:"
+ title: "Преобразовать в публичную тему"
+ choose_category: "Пожалуйста, выберите раздел для публичной темы:"
feature_topic:
title: "Закрепить эту тему"
pin: "Закрепить эту тему вверху раздела {{categoryLink}} до"
- confirm_pin: "У вас уже есть закрепленные темы в разделе ({{count}}). Перебор таких тем может оказаться неприятным неудобством для новичков и анонимных читателей. Вы уверены, что хотите закрепить еще одну тему в этом разделе?"
+ confirm_pin: "У вас уже есть закрепленные темы в разделе ({{count}}). Перебор таких тем может оказаться неприятным неудобством для новичков и анонимных читателей. Вы действительно хотите закрепить еще одну тему в этом разделе?"
unpin: "Отменить закрепление этой темы вверху раздела {{categoryLink}}."
unpin_until: "Отменить закрепление этой темы вверху раздела {{categoryLink}} (произойдет автоматически
%{until} )."
pin_note: "Пользователи могут открепить тему, каждый сам для себя."
@@ -2220,7 +2237,7 @@ ru:
many: "Глобально закрепленных тем в разделе {{categoryLink}}:
{{count}} "
other: "Глобально закрепленных тем в разделе {{categoryLink}}:
{{count}} "
pin_globally: "Закрепить эту тему вверху всех разделов и списков тем до"
- confirm_pin_globally: "У вас уже есть глобально закрепленные темы ({{count}}). Перебор таких тем может оказаться неприятным неудобством для новичков и анонимных читателей. Вы уверены, что хотите глобально закрепить еще одну тему?"
+ confirm_pin_globally: "У вас уже есть глобально закрепленные темы ({{count}}). Перебор таких тем может оказаться неприятным неудобством для новичков и анонимных читателей. Вы действительно хотите глобально закрепить еще одну тему?"
unpin_globally: "Отменить прикрепление этой темы вверху всех разделов и списков тем."
unpin_globally_until: "Отменить прикрепление этой темы вверху всех разделов и списков тем (произойдет автоматически
%{until} )."
global_pin_note: "Пользователи могут открепить тему, каждый сам для себя."
@@ -2251,7 +2268,7 @@ ru:
title: "Пригласить"
username_placeholder: "псевдоним"
action: "Отправить приглашение"
- help: "пригласить других в эту тему с помощью email или уведомлений"
+ help: "Пригласить других в эту тему с помощью email или уведомлений"
to_forum: "Будет отправлено короткое письмо, которое позволит вашему другу присоединиться, просто кликнув по ссылке, без необходимости входа на сайт."
sso_enabled: "Введите псевдоним пользователя, которого вы хотите пригласить в эту тему."
to_topic_blank: "Введите псевдоним или email пользователя, которого вы хотите пригласить в эту тему."
@@ -2272,14 +2289,14 @@ ru:
other: "{{count}} сообщений"
cancel: "Отменить фильтр"
move_to:
- title: "Переместить во"
- action: "переместить во"
- error: "При перемещении поста произошла ошибка."
+ title: "Перемещение"
+ action: "Переместить"
+ error: "При перемещении сообщения произошла ошибка."
split_topic:
title: "Переместить в новую тему"
- action: "переместить в новую тему"
- topic_name: "Название Новой Темы"
- radio_label: "Новая Тема"
+ action: "Переместить в новую тему"
+ topic_name: "Название новой темы"
+ radio_label: "Новая тема"
error: "Во время перемещения сообщений в новую тему возникла ошибка."
instructions:
one: "Сейчас вы создадите новую тему и в неё переместится выбранное вами
{{count}} сообщение."
@@ -2288,42 +2305,42 @@ ru:
other: "Сейчас вы создадите новую тему и в неё переместятся выбранные вами
{{count}} сообщения."
merge_topic:
title: "Переместить в существующую тему"
- action: "переместить в существующую тему"
+ action: "Переместить в существующую тему"
error: "Во время перемещения сообщений в тему возникла ошибка."
- radio_label: "Существующая Тема"
+ radio_label: "Существующая тема"
instructions:
one: "Пожалуйста, выберите тему, в которую вы хотели бы переместить это
{{count}} сообщение."
few: "Пожалуйста, выберите тему, в которую вы хотели бы переместить эти
{{count}} сообщения."
many: "Пожалуйста, выберите тему, в которую вы хотели бы переместить эти
{{count}} сообщений."
other: "Пожалуйста, выберите тему, в которую вы хотели бы переместить эти
{{count}} сообщений."
move_to_new_message:
- title: "Перейти к Новому Сообщению"
- action: "перейти к новому сообщению"
- message_title: "Новый Заголовок Сообщения"
- radio_label: "Новое Сообщение"
+ title: "Переместить в новое личное сообщение"
+ action: "Переместить в новое личное сообщение"
+ message_title: "Новый заголовок сообщения"
+ radio_label: "Новое сообщение"
participants: "Участники"
instructions:
- one: "Вы собираетесь создать новое сообщение и заполнить его выбранным вами сообщением."
- few: "Вы собираетесь создать новое сообщение и заполнить его
{{count}} сообщениями, которые вы выбрали."
- many: "Вы собираетесь создать новое сообщение и заполнить его
{{count}} сообщениями, которые вы выбрали."
- other: "Вы собираетесь создать новое сообщение и заполнить его
{{count}} сообщениями, которые вы выбрали."
+ one: "Вы собираетесь создать новое личное сообщение и заполнить его выбранным вами сообщением."
+ few: "Вы собираетесь создать новое личное сообщение и заполнить его
{{count}} сообщениями, которые вы выбрали."
+ many: "Вы собираетесь создать новое личное сообщение и заполнить его
{{count}} сообщениями, которые вы выбрали."
+ other: "Вы собираетесь создать новое личное сообщение и заполнить его
{{count}} сообщениями, которые вы выбрали."
move_to_existing_message:
- title: "Перейти к Существующему Сообщению"
- action: "перейти к существующему сообщению"
- radio_label: "Существующее Сообщение"
+ title: "Переместить в существующее личное сообщение"
+ action: "Переместить в существующее личное сообщение"
+ radio_label: "Существующее личное сообщение"
participants: "Участники"
instructions:
- one: "Пожалуйста, выберите сообщение, в которое вы хотите переместить это сообщение."
- few: "Пожалуйста, выберите сообщение, которое вы хотите переместить
{{count}} сообщений."
- many: "Пожалуйста, выберите сообщение, которое вы хотите переместить
{{count}} сообщений."
- other: "Пожалуйста, выберите сообщение, которое вы хотите переместить
{{count}} сообщений."
+ one: "Пожалуйста, выберите личное сообщение, в которое вы хотите переместить это сообщение."
+ few: "Пожалуйста, выберите личное сообщение, в которое вы хотите переместить
{{count}} сообщения."
+ many: "Пожалуйста, выберите личное сообщение, в которое вы хотите переместить
{{count}} сообщений."
+ other: "Пожалуйста, выберите личное сообщение, в которое вы хотите переместить
{{count}} сообщений."
merge_posts:
- title: "Соединить выделенные сообщения"
- action: "Соединить выделенные сообщения"
- error: "Произошла ошибка во время соединения выделенных сообщений."
+ title: "Объединить выделенные сообщения"
+ action: "Объединить выделенные сообщения"
+ error: "Произошла ошибка во время объединения выделенных сообщений."
change_owner:
- title: "Сменить Владельца"
- action: "изменить владельца"
+ title: "Сменить владельца"
+ action: "Изменить владельца"
error: "При смене владельца сообщений произошла ошибка."
placeholder: "псевдоним нового владельца"
instructions:
@@ -2333,50 +2350,50 @@ ru:
other: "Пожалуйста, выберите нового владельца {{count}} сообщений для
@{{old_user}} "
change_timestamp:
title: "Изменить временную метку..."
- action: "изменить временную метку"
+ action: "Изменить временную метку"
invalid_timestamp: "Временная метка не может быть в будущем"
error: "При изменении временной метки темы возникла ошибка"
instructions: "Пожалуйста, выберите новую временную метку. Сообщения в теме будут обновлены, чтобы убрать временные различия."
multi_select:
- select: "выбрать"
- selected: "выбрано ({{count}})"
+ select: "Выбрать"
+ selected: "Выбрано ({{count}})"
select_post:
- label: "выбрать"
+ label: "Выбрать"
title: "Добавить сообщение в выделение"
selected_post:
- label: "выбранный"
+ label: "Выбрано"
title: "Нажмите, чтобы удалить сообщение из выборки"
select_replies:
- label: "выбрать + ответы"
+ label: "Выбрать + ответы"
title: "Добавить запись и все ответы для выбора"
select_below:
- label: "выбрать + все ниже"
+ label: "Выбрать + все ниже"
title: "Добавить запись и все ответы для выбора"
- delete: удалить выбранные
- cancel: отменить выделение
- select_all: выбрать все
- deselect_all: снять весь выбор
+ delete: Удалить выбранные
+ cancel: Отменить
+ select_all: Выбрать все
+ deselect_all: Снять весь выбор
description:
one: Вы выбрали
{{count}} сообщение.
few: "Вы выбрали
{{count}} сообщения."
many: "Вы выбрали
{{count}} сообщений."
other: "Вы выбрали
{{count}} сообщений."
deleted_by_author:
- one: "(тема отозвана автором и будет автоматически удалена в течение %{count} часа, если только на сообщение не поступит жалоба)"
- few: "(тема отозвана автором и будет автоматически удалена в течение %{count} часов, если только на сообщение не поступит жалоба)"
- many: "(тема отозвана автором и будет автоматически удалена в течение %{count} часов, если только на сообщение не поступит жалоба)"
- other: "(тема отозвана автором и будет автоматически удалена в течение %{count} часов, если только на сообщение не поступит жалоба)"
+ one: "(тема отозвана автором и будет автоматически удалена через %{count} час, если только на нее не поступит жалоба)"
+ few: "(тема отозвана автором и будет автоматически удалена через %{count} часа, если только на нее не поступит жалоба)"
+ many: "(тема отозвана автором и будет автоматически удалена через %{count} часов, если только на нее не поступит жалоба)"
+ other: "(тема отозвана автором и будет автоматически удалена через %{count} часов, если только на нее не поступит жалоба)"
post:
quote_reply: "Цитата"
edit_reason: "Причина:"
post_number: "сообщение {{number}}"
ignored: "Проигнорированное содержание"
wiki_last_edited_on: "вики редактировалось"
- last_edited_on: "последний раз сообщение редактировалось"
+ last_edited_on: "Последний раз сообщение редактировалось"
reply_as_new_topic: "Ответить в новой связанной теме"
reply_as_new_private_message: "Ответить новым сообщением тем же адресатам"
continue_discussion: "Продолжая обсуждение из {{postLink}}:"
- follow_quote: "перейти к цитируемому сообщению"
+ follow_quote: "Перейти к цитируемому сообщению"
show_full: "Показать полный текст"
show_hidden: "Просмотр игнорируемого содержимого."
deleted_by_author:
@@ -2384,23 +2401,23 @@ ru:
few: "(сообщение отозвано автором и будет автоматически удалено в течение %{count} часов, если только на сообщение не поступит жалоба)"
many: "(сообщение отозвано автором и будет автоматически удалено в течение %{count} часов, если только на сообщение не поступит жалоба)"
other: "(сообщение отозвано автором и будет автоматически удалено в течение %{count} часов, если только на сообщение не поступит жалоба)"
- collapse: "свернуть"
- expand_collapse: "развернуть/свернуть"
- locked: "сотрудник заблокировал это сообщение для редактирования"
+ collapse: "Свернуть"
+ expand_collapse: "Развернуть/свернуть"
+ locked: "Сотрудник заблокировал это сообщение для редактирования"
gap:
- one: "просмотреть {{count}} скрытый ответ"
- few: "просмотреть {{count}} скрытых ответа"
- many: "просмотреть {{count}} скрытых ответов"
- other: "просмотреть {{count}} скрытых ответов"
+ one: "Просмотреть {{count}} скрытый ответ"
+ few: "Просмотреть {{count}} скрытых ответа"
+ many: "Просмотреть {{count}} скрытых ответов"
+ other: "Просмотреть {{count}} скрытых ответов"
notice:
- new_user: "Это первая публикация {{user}} — давайте приветствовать его в нашем сообществе!"
- returning_user: "Прошло много времени с тех пор, как мы видели {{user}} — его последний пост был {{time}}."
+ new_user: "Это первая публикация {{user}} — поприветствуем его в нашем сообществе!"
+ returning_user: "Пользователь {{user}} давно не появлялся — его последнее сообщение было {{time}}."
unread: "Сообщение не прочитано"
has_replies:
- one: "{{count}} Ответ"
- few: "{{count}} Ответа"
- many: "{{count}} Ответов"
- other: "{{count}} Ответов"
+ one: "{{count}} ответ"
+ few: "{{count}} ответа"
+ many: "{{count}} ответов"
+ other: "{{count}} ответов"
has_likes_title:
one: "Это сообщение понравилось {{count}} человеку"
few: "Это сообщение понравилось {{count}} людям"
@@ -2424,39 +2441,39 @@ ru:
attachment_upload_not_allowed_for_new_user: "К сожалению, загрузка файлов недоступна новым пользователям."
attachment_download_requires_login: "Войдите, чтобы скачивать прикрепленные файлы."
abandon_edit:
- confirm: "Вы уверены, что хотите отменить свои изменения?"
+ confirm: "Вы действительно хотите отменить свои изменения?"
no_value: "Нет, оставить"
no_save_draft: "Нет, сохраните черновик"
yes_value: "Да, отменить редактирование"
abandon:
- confirm: "Вы уверены, что хотите отказаться от сообщения?"
+ confirm: "Вы действительно хотите отказаться от сообщения?"
no_value: "Нет, оставить"
no_save_draft: "Нет, сохраните черновик"
yes_value: "Да, отказаться"
via_email: "это сообщение пришло с почты"
via_auto_generated_email: "это сообщение пришло с автосгенерированого e-mail"
- whisper: "Это внутреннее сообщение, т.е. оно видно только модераторам"
+ whisper: "Это внутреннее сообщение доступно только модераторам"
wiki:
about: "это вики-сообщение"
archetypes:
save: "Параметры сохранения"
few_likes_left: "Спасибо, что делитесь любовью. На сегодня у Вас осталось несколько лайков."
controls:
- reply: "начать составление ответа на сообщение"
- like: "мне нравится"
+ reply: "Начать составление ответа на сообщение"
+ like: "Мне нравится"
has_liked: "Вам понравилось это сообщение"
- read_indicator: "пользователи, которые читают этот пост"
- undo_like: "больше не нравится"
+ read_indicator: "Пользователи, которые читают это сообщение"
+ undo_like: "Больше не нравится"
edit: "Изменить сообщение"
edit_action: "Изменить"
edit_anonymous: "Войдите, чтобы отредактировать это сообщение."
- flag: "пожаловаться на сообщение"
+ flag: "Пожаловаться на сообщение"
delete: "удалить сообщение"
- undelete: "отменить удаление"
- share: "поделиться ссылкой на сообщение"
+ undelete: "Отменить удаление"
+ share: "Поделиться ссылкой на сообщение"
more: "Ещё"
delete_replies:
- confirm: "Вы также хотите удалить ответы на этот пост?"
+ confirm: "Хотите удалить также и ответы на это сообщение?"
direct_replies:
one: "Да, и %{count} прямой ответ"
few: "Да, и {{count}} прямых ответа"
@@ -2467,8 +2484,8 @@ ru:
few: "Да, и все {{count}} ответа"
many: "Да, и все {{count}} ответов"
other: "Да, и все {{count}} ответов"
- just_the_post: "Нет, только этот пост"
- admin: "действия администратора над сообщением"
+ just_the_post: "Нет, только это сообщение"
+ admin: "Действия администратора над сообщением"
wiki: "Сделать вики-сообщением"
unwiki: "Отменить вики-сообщение"
convert_to_moderator: "Добавить цвет модератора"
@@ -2478,15 +2495,15 @@ ru:
change_owner: "Изменить владельца"
grant_badge: "Выдать награду"
lock_post: "Заморозить сообщение"
- lock_post_description: "запретить автору редактирование этот пост"
+ lock_post_description: "Запретить автору редактировать это сообщение"
unlock_post: "Разморозить сообщение"
- unlock_post_description: "разрешить автору редактировать этот пост"
- delete_topic_disallowed_modal: "У вас нет разрешения на удаление этой темы. Если вы действительно хотите, чтобы она была удалена, используйте функцию флага модератору вместе с аргументацией."
- delete_topic_disallowed: "у вас нет разрешения на удаление этой темы"
- delete_topic: "удалить тему"
- add_post_notice: "Добавить уведомление персонала"
- remove_post_notice: "Удалить уведомление персонала"
- remove_timer: "отменить таймер"
+ unlock_post_description: "Разрешить автору редактировать это сообщение"
+ delete_topic_disallowed_modal: "У вас нет разрешения на удаление этой темы. Если вы действительно хотите, чтобы она была удалена, воспользуйтесь кнопкой
Пожаловаться , указав причину, по которой тема должна быть удалена."
+ delete_topic_disallowed: "У вас нет разрешения на удаление этой темы"
+ delete_topic: "Удалить тему"
+ add_post_notice: "Сообщение от модератора"
+ remove_post_notice: "Удалить сообщение модератора"
+ remove_timer: "Отменить таймер"
actions:
flag: "Жалоба"
defer_flags:
@@ -2501,17 +2518,32 @@ ru:
bookmark: "Удалить из закладок"
like: "Больше не нравится"
people:
- off_topic: "отмечено как \"не по теме\""
- spam: "отмечено как спам"
- inappropriate: "отмечено как неуместное"
+ off_topic: "Отмечено как \"не по теме\""
+ spam: "Отмечено как спам"
+ inappropriate: "Отмечено как неуместное"
notify_moderators: "уведомлённые модераторы"
notify_user: "отправил сообщение"
- bookmark: "добавить закладку"
+ bookmark: "добавил закладку"
+ like:
+ one: "понравилось"
+ few: "понравилось"
+ many: "понравилось"
+ other: "понравилось"
+ read:
+ one: "прочитал"
+ few: "прочитали"
+ many: "прочитали"
+ other: "прочитали"
like_capped:
one: "и {{count}} понравилось"
few: "и {{count}} другим понравилось"
many: "и {{count}} другим понравилось"
other: "и {{count}} другим понравилось"
+ read_capped:
+ one: "и ещё {{count}} прочитал"
+ few: "и ещё {{count}} прочитали"
+ many: "и ещё {{count}} прочитали"
+ other: "и ещё {{count}} прочитали"
by_you:
off_topic: "Помечена вами как оффтопик"
spam: "Помечена вами как спам"
@@ -2522,16 +2554,16 @@ ru:
like: "Вам нравится"
delete:
confirm:
- one: "Вы уверены, что хотите удалить это сообщение?"
- few: "Вы уверены, что хотите удалить {{count}} сообщения?"
- many: "Вы уверены, что хотите удалить {{count}} сообщений?"
- other: "Вы уверены, что хотите удалить {{count}} сообщений?"
+ one: "Вы действительно хотите удалить это сообщение?"
+ few: "Вы действительно хотите удалить {{count}} сообщения?"
+ many: "Вы действительно хотите удалить {{count}} сообщений?"
+ other: "Вы действительно хотите удалить {{count}} сообщений?"
merge:
confirm:
- one: "Вы уверены, что хотите объединить эти сообщения?"
- few: "Вы уверены, что хотите объединить эти {{count}} сообщения?"
- many: "Вы уверены, что хотите объединить эти {{count}} сообщений?"
- other: "Вы уверены, что хотите объединить эти {{count}} сообщений?"
+ one: "Вы действительно хотите объединить эти сообщения?"
+ few: "Вы действительно хотите объединить эти {{count}} сообщения?"
+ many: "Вы действительно хотите объединить эти {{count}} сообщений?"
+ other: "Вы действительно хотите объединить эти {{count}} сообщений?"
revisions:
controls:
first: "Начальная версия"
@@ -2563,17 +2595,18 @@ ru:
title: "Показать текстовую версию письма"
button: "Текст"
html_part:
- title: "Показать HTML версию письма"
+ title: "Показать HTML-версию письма"
button: "HTML"
bookmarks:
create: "Создать закладку"
name: "Имя"
- set_reminder: "Установить напоминание"
+ name_placeholder: "Присвойте имя закладке (необязательно)"
+ set_reminder: "Настроить напоминание (необязательно)"
category:
can: "может… "
none: "(вне раздела)"
all: "Все разделы"
- choose: "категории…"
+ choose: "разделе…"
edit: "Изменить"
edit_dialog_title: "Редактировать: %{categoryName}"
view: "Просмотр тем по разделам"
@@ -2581,20 +2614,20 @@ ru:
settings: "Настройки"
topic_template: "Шаблон темы"
tags: "Теги"
- tags_allowed_tags: "Ограничить эти теги данной категорией:"
- tags_allowed_tag_groups: "Ограничьте эти группы тегов этой категорией:"
+ tags_allowed_tags: "Ограничить эти теги этим разделом:"
+ tags_allowed_tag_groups: "Ограничьте эти группы тегов этим разделом:"
tags_placeholder: "(Необязательно) список доступных тегов"
- tags_tab_description: "Теги и группы тегов, указанные здесь, будут доступны только в этой категории и других категориях, в которых они также указаны. Они не будут доступны для использования в других категориях."
+ tags_tab_description: "Теги и группы тегов, указанные здесь, будут доступны только в этом разделе и других разделах, в которых они были указаны. Они не будут доступны для использования в других разделах."
tag_groups_placeholder: "(Необязательно) список доступных групп тегов"
manage_tag_groups_link: "Управлять группами тегов здесь."
allow_global_tags_label: "Также разрешить другие теги"
tag_group_selector_placeholder: "(Необязательно) Группа тегов"
required_tag_group_description: "Требовать, чтобы новые темы имели теги из группы тегов:"
- min_tags_from_required_group_label: "Номер Тега:"
+ min_tags_from_required_group_label: "Номер тега:"
required_tag_group_label: "Группа тегов:"
- topic_featured_link_allowed: "Разрешить популярные ссылки в этой категории"
+ topic_featured_link_allowed: "Разрешить избранные ссылки в этом разделе"
delete: "Удалить раздел"
- create: "Создать Раздел"
+ create: "Создать раздел"
create_long: "Создать новый раздел"
save: "Сохранить раздел"
slug: "Ссылка на раздел"
@@ -2620,8 +2653,8 @@ ru:
security: "Безопасность"
special_warning: "Внимание: данный раздел был предустановлен и настройки безопасности не могут быть изменены. Если не хотите использовать этот раздел, удалите его вместо изменения."
uncategorized_security_warning: "Эта категория особенная. Он предназначен для хранения тем, которые не имеют категории; у него не может быть настроек безопасности."
- uncategorized_general_warning: 'Эта категория особенная. Он используется в качестве категории по умолчанию для новых тем, для которых не выбрана категория. Если вы хотите предотвратить такое поведение и принудительно выбрать категорию, отключите настройку здесь . Если вы хотите изменить имя или описание, перейдите к
Настроить / Текстовое содержимое .'
- pending_permission_change_alert: "Вы не добавили %{group} в эту категорию; нажмите эту кнопку, чтобы добавить их."
+ uncategorized_general_warning: 'Это особенный раздел. Он используется в качестве раздела по умолчанию для новых тем, для которых не был выбран конкретный раздел. Если вы хотите предотвратить такое поведение и принудительно выбрать раздел, отключите настройку здесь . Если вы хотите изменить имя или описание, перейдите к
Настроить / текстовое содержимое .'
+ pending_permission_change_alert: "Вы не добавили %{group} в этот раздел; нажмите эту кнопку, чтобы добавить их."
images: "Изображения"
email_in: "Индивидуальный адрес входящей почты:"
email_in_allow_strangers: "Принимать письма от анонимных пользователей без учётных записей"
@@ -2630,7 +2663,7 @@ ru:
mailinglist_mirror: "Категория отражает список рассылки"
show_subcategory_list: "Показывать список подразделов над списком тем в этом разделе."
num_featured_topics: "Количество тем на странице разделов"
- subcategory_num_featured_topics: "Количество избранных тем на странице родительской категории:"
+ subcategory_num_featured_topics: "Количество избранных тем на странице родительского раздела:"
all_topics_wiki: "Создание новых тем Wikis по умолчанию"
subcategory_list_style: "Стиль списка подразделов:"
sort_order: "Порядок сортировки тем:"
@@ -2638,12 +2671,12 @@ ru:
default_top_period: "Верхний период по умолчанию:"
allow_badges_label: "Разрешить вручение наград в этом разделе"
edit_permissions: "Изменить права доступа"
- reviewable_by_group: "Помимо персонала, посты и флаги в этой категории также могут быть рассмотрены:"
+ reviewable_by_group: "Кто еще, помимо персонала, может рассматривать сообщения и жалобы в этом разделе:"
review_group_name: "название группы"
require_topic_approval: "Требовать одобрения модератором всех новых тем"
require_reply_approval: "Требовать одобрения модератором всех новых ответов"
this_year: "за год"
- position: "Позиция на странице категории:"
+ position: "Позиция на странице раздела:"
default_position: "Позиция по умолчанию"
position_disabled: "Разделы будут показаны в порядке активности. Чтобы настроить порядок разделов,"
position_disabled_click: 'включите настройку "fixed category positions".'
@@ -2657,10 +2690,10 @@ ru:
description: "Наблюдать за всеми темами этого раздела. Уведомлять о каждом новом сообщении в любой из тем и показывать счётчик новых ответов."
watching_first_post:
title: "Наблюдать за первым сообщением"
- description: "Вы будете уведомлены о новых темах в этой категории, но не на ответы в них."
+ description: "Вы будете уведомлены о новых темах в этом разделе, но не на ответы в них."
tracking:
title: "Следить"
- description: "Отслеживать все темы этого раздела. Уведомлять если кто-то упомянет ваше @name или ответит вам, показывать счётчик новых ответов."
+ description: "Отслеживать все темы этого раздела. Уведомлять, если кто-то упомянет мой @псевдоним или ответит на мое сообщение. Показывать счётчик новых ответов."
regular:
title: "Уведомлять"
description: "Уведомлять, если кто-нибудь упомянет мой @псевдоним или ответит на мое сообщение."
@@ -2668,14 +2701,14 @@ ru:
title: "Без уведомлений"
description: "Не уведомлять о новых темах в этом разделе и скрыть их из последних."
search_priority:
- label: "Приоритет Поиска"
+ label: "Приоритет поиска"
options:
normal: "Нормальный"
ignore: "Игнорировать"
- very_low: "Очень Низкий"
+ very_low: "Очень низкий"
low: "Низкий"
high: "Высокий"
- very_high: "Очень Высокий"
+ very_high: "Очень высокий"
sort_options:
default: "По умолчанию"
likes: "Количество симпатий"
@@ -2690,9 +2723,9 @@ ru:
sort_descending: "По убыванию"
subcategory_list_styles:
rows: "Строки"
- rows_with_featured_topics: "Строки с обсуждаемыми темами"
+ rows_with_featured_topics: "Строки с избранными темами"
boxes: "Блоки"
- boxes_with_featured_topics: "Блоки с обсуждаемыми темами"
+ boxes_with_featured_topics: "Блоки с избранными темами"
settings_sections:
general: "Основные"
moderation: "Модерация"
@@ -2705,7 +2738,7 @@ ru:
notify_action: "Сообщение"
official_warning: "Официальное предупреждение"
delete_spammer: "Удалить спамера"
- delete_confirm_MF: "Вы собираетесь удалить {POSTS, plural, one {
1 сообщение} other {
# сообщений}} и {TOPICS, plural, one {
1 тему} other {
# темы}} этого пользователя, а так же удалить его учётную запись, добавить его IP адрес
{ip_address} и его почтовый адрес
{email} в чёрный список. Вы действительно уверены, что ваши помыслы чисты и действия не продиктованы гневом?"
+ delete_confirm_MF: "Вы собираетесь удалить {POSTS, plural, one {
1 сообщение} other {
# сообщений}} и {TOPICS, plural, one {
1 тему} other {
# темы}} этого пользователя, а так же удалить его учётную запись, добавить его IP-адрес
{ip_address} и его почтовый адрес
{email} в чёрный список. Вы действительно действительно ваши помыслы чисты и действия не продиктованы гневом?"
yes_delete_spammer: "Да, удалить спамера"
ip_address_missing: "(не доступно)"
hidden_email_address: "(скрыто)"
@@ -2824,7 +2857,7 @@ ru:
categories_list: "Список разделов"
filters:
with_topics: "%{filter} темы"
- with_category: "%{filter} %{category} темы"
+ with_category: "%{category} - %{filter} темы"
latest:
title: "Последние"
title_with_count:
@@ -2832,14 +2865,14 @@ ru:
few: "Последние ({{count}})"
many: "Последние ({{count}})"
other: "Последние ({{count}})"
- help: "темы с недавними сообщениями"
+ help: "Темы с недавними сообщениями"
read:
title: "Прочитанные"
- help: "темы, которые вас заинтересовали (в обратном хронологическом порядке)"
+ help: "Темы, которые вас заинтересовали (в обратном хронологическом порядке)"
categories:
title: "Разделы"
title_in: "Раздел - {{categoryName}}"
- help: "все темы, сгруппированные по разделам"
+ help: "Все темы, сгруппированные по разделам"
unread:
title: "Непрочитанные"
title_with_count:
@@ -2847,7 +2880,7 @@ ru:
few: "Непрочитанные ({{count}})"
many: "Непрочитанные ({{count}})"
other: "Непрочитанные ({{count}})"
- help: "наблюдаемые или отслеживаемые темы с непрочитанными сообщениями"
+ help: "Наблюдаемые или отслеживаемые темы с непрочитанными сообщениями"
lower_title_with_count:
one: "{{count}} непрочитанная"
few: "{{count}} непрочитанных"
@@ -2866,13 +2899,13 @@ ru:
few: "Новые ({{count}})"
many: "Новые ({{count}})"
other: "Новые ({{count}})"
- help: "темы, созданные за последние несколько дней"
+ help: "Темы, созданные за последние несколько дней"
posted:
title: "Мои"
- help: "темы, в которых вы принимали участие"
+ help: "Темы, в которых вы принимали участие"
bookmarks:
title: "Закладки"
- help: "темы, которые вы добавили в закладки"
+ help: "Темы, которые вы добавили в закладки"
category:
title: "{{categoryName}}"
title_with_count:
@@ -2880,7 +2913,7 @@ ru:
few: "{{categoryName}} ({{count}})"
many: "{{categoryName}} ({{count}})"
other: "{{categoryName}} ({{count}})"
- help: "последние темы в разделе {{categoryName}}"
+ help: "Последние темы в разделе {{categoryName}}"
top:
title: "Обсуждаемые"
help: "Самые активные темы за последний год, месяц, квартал, неделю или день"
@@ -2980,6 +3013,7 @@ ru:
mark_watching: "%{shortcut} Наблюдать за темой"
print: "%{shortcut} Печатать тему"
defer: "%{shortcut} Отложить тему"
+ topic_admin_actions: "%{shortcut} Откройте раздел действия администратора"
badges:
earned_n_times:
one: "Заработал эту награду %{count} раз"
@@ -3038,25 +3072,25 @@ ru:
tags: "Теги"
choose_for_topic: "Выберите теги для этой темы (опционально)"
info: "Информация"
- default_info: "Этот тег не ограничен никакими категориями и не имеет синонимов."
+ default_info: "Этот тег не ограничен никакими разделами и не имеет синонимов."
synonyms: "Синонимы"
- synonyms_description: "При использовании следующих тегов они будут заменены на
%{base_tag_name} ."
+ synonyms_description: "При использовании следующих тегов они будут заменены на
%{base_tag_name} ."
tag_groups_info:
one: 'Этот тег принадлежит группе "{{tag_groups}}".'
few: "Этот тег принадлежит к этим группам: {{tag_groups}}."
many: "Этот тег принадлежит к этим группам: {{tag_groups}}."
other: "Этот тег принадлежит к этим группам: {{tag_groups}}."
category_restrictions:
- one: "Его можно использовать только в этой категории:"
- few: "Их можно использовать только в этой категории:"
- many: "Их можно использовать только в этой категории:"
- other: "Их можно использовать только в этой категории:"
- edit_synonyms: "Управление Синонимами"
+ one: "Его можно использовать только в этом разделе:"
+ few: "Их можно использовать только в этом разделе:"
+ many: "Их можно использовать только в этом разделе:"
+ other: "Их можно использовать только в этом разделе:"
+ edit_synonyms: "Управление синонимами"
add_synonyms_label: "Добавить синонимы:"
add_synonyms: "Добавить"
add_synonyms_failed: "Следующие теги не могут быть добавлены в качестве синонимов:
%{tag_names} . Убедитесь, что они не имеют синонимов и не являются синонимами другого тега."
- remove_synonym: "Удалить Синоним"
- delete_synonym_confirm: 'Вы уверены, что хотите удалить синоним "%{tag_name}"?'
+ remove_synonym: "Удалить синоним"
+ delete_synonym_confirm: 'Вы действительно хотите удалить синоним "%{tag_name}"?'
delete_tag: "Удалить тег"
delete_confirm:
one: "Вы действительно хотите удалить этот тег и удалить его из %{count} темы, которой он присвоен?"
@@ -3076,7 +3110,7 @@ ru:
sort_by_name: "Название"
manage_groups: "Управление группами тегов"
manage_groups_description: "Организуйте теги в группы"
- upload: "Загрузить Теги"
+ upload: "Загрузить теги"
upload_description: "Загрузить csv-файл для массового создания тегов"
upload_instructions: "По одному в строке, необязательно, с группой тегов в формате 'tag_name,tag_group'."
upload_successful: "Теги успешно загружены"
@@ -3090,7 +3124,7 @@ ru:
few: "%{tags} и более %{count} "
many: "%{tags} и более %{count} "
other: "%{tags} и более %{count} "
- delete_unused: "Удалить Неиспользуемые Теги"
+ delete_unused: "Удалить неиспользуемые теги"
delete_unused_description: "Удалите все теги, которые не прикреплены к темам или личным сообщениям"
cancel_delete_unused: "Отменить"
filters:
@@ -3125,10 +3159,10 @@ ru:
parent_tag_description: "Теги из этой группы будут доступны только после добавления к теме родительского тега."
one_per_topic_label: "Разрешить не более одного тега из этой группы в одной теме"
new_name: "Название новой группы"
- name_placeholder: "Имя Группы Тегов"
+ name_placeholder: "Имя группы тегов"
save: "Сохранить"
delete: "Удалить"
- confirm_delete: "Вы уверены, что хотите удалить эту группу тегов?"
+ confirm_delete: "Вы действительно хотите удалить эту группу тегов?"
everyone_can_use: "Все могут использовать теги"
usable_only_by_staff: "Теги видны всем, но использовать их может только персонал"
visible_only_to_staff: "Теги видны только персоналу"
@@ -3189,14 +3223,14 @@ ru:
no_problems: "Проблем не обнаружено."
moderators: "Модераторы:"
admins: "Администраторы:"
- silenced: "Отключенные:"
- suspended: "Заморожен:"
+ silenced: "Заблокированные:"
+ suspended: "Замороженные:"
private_messages_short: "Сообщ."
private_messages_title: "Сообщений"
mobile_title: "Мобильный"
space_used: "%{usedSize} используемый"
space_used_and_free: "%{usedSize} (%{freeSize} свободно)"
- uploads: "Загрузить"
+ uploads: "Загрузки"
backups: "Backups"
backup_count:
one: "%{count} резервная копия @ %{location}"
@@ -3206,7 +3240,7 @@ ru:
lastest_backup: "Последние: %{date}"
traffic_short: "Трафик"
traffic: "Трафик (веб-запросы)"
- page_views: "Просмотров Страниц"
+ page_views: "Просмотров страниц"
page_views_short: "Просмотров"
show_traffic_report: "Расширенный отчет по трафику"
community_health: Состояние сообщества
@@ -3238,8 +3272,8 @@ ru:
view_table: "Таблица"
view_graph: "График"
refresh_report: "Обновить отчет"
- start_date: "Дата Начала (UTC)"
- end_date: "Дата Окончания (UTC)"
+ start_date: "Дата начала (UTC)"
+ end_date: "Дата окончания (UTC)"
groups: "Все группы"
disabled: "Этот отчет отключен"
totals_for_sample: "Итоги по выборке"
@@ -3255,7 +3289,7 @@ ru:
group:
label: Группа
category:
- label: Категория
+ label: Раздел
commits:
latest_changes: "Обновления в репозитории Github"
by: "от"
@@ -3331,27 +3365,28 @@ ru:
key: "Ключ"
created: Создано
updated: Обновленный
- last_used: Последнее Использование
+ last_used: Последнее использование
never_used: (никогда)
generate: "Сгенерировать"
undo_revoke: "Отменить"
revoke: "Отозвать"
all_users: "Все пользователи"
- active_keys: "Активные API ключи"
- manage_keys: Управление Ключами
+ active_keys: "Активные API-ключи"
+ manage_keys: Управление ключами
show_details: Детали
description: Описание
no_description: (без описания)
- all_api_keys: Все API ключи
- user_mode: Уровень Пользователя
+ all_api_keys: Все API-ключи
+ user_mode: Уровень пользователя
impersonate_all_users: Представиться как пользователь
- single_user: "Отдельный Пользователь"
+ single_user: "Отдельный пользователь"
user_placeholder: Введите псевдоним
description_placeholder: "Для чего будет использоваться этот ключ?"
save: Сохранить
- new_key: Новый API ключ
+ new_key: Новый API-ключ
revoked: Отозвать
- delete: Окончательно Удалить
+ delete: Окончательно удалить
+ not_shown_again: "Эта клавиша больше не будет отображаться. Убедитесь, что вы сделали копию, прежде чем продолжить."
continue: Продолжать
web_hooks:
title: "Webhooks"
@@ -3372,7 +3407,7 @@ ru:
secret_too_short: "Ключ должен быть не менее 12 символов."
secret_placeholder: "Дополнительная строка, используется для создания подписи"
event_type_missing: "Вам необходимо настроить по крайней мере один тип событий."
- content_type: "Тип Содержимого"
+ content_type: "Тип содержимого"
secret: "Ключ"
event_chooser: "Какие события должны вызывать срабатывание этого Webhook?"
wildcard_event: "Присылать мне всё."
@@ -3381,42 +3416,42 @@ ru:
active: "Активный"
active_notice: "Мы будем отправлять подробности события, когда оно будет происходить."
categories_filter_instructions: "Подходящие веб-перехватчики будут срабатывать только если событие связано с указанными разделами. Оставьте пустым, чтобы веб-перехватчик срабатывал для всех разделов."
- categories_filter: "Только Для Этих Разделов"
+ categories_filter: "Только для этих разделов"
tags_filter_instructions: "Соответствующие webhooks будут активированы, только если событие связано с указанными тегами. Оставьте пустым, чтобы активировать webhooks для всех тегов."
- tags_filter: "Сработавшие Теги"
+ tags_filter: "Сработавшие теги"
groups_filter_instructions: "Подходящие веб-перехватчики будут срабатывать только если событие связано с указанными группами. Оставьте пустым, чтобы веб-перехватчик срабатывал для всех групп."
- groups_filter: "Только Для Групп"
+ groups_filter: "Только для групп"
delete_confirm: "Удалить Webhook?"
topic_event:
name: "Событие темы"
- details: "Происходит, когда тема создается, пересматривается, изменяется или удаляется."
+ details: "При создании, проверке, изменении или удалении темы."
post_event:
name: "Событие сообщения"
- details: "Происходит, когда сообщение создается, редактируется, удаляется или восстанавливается."
+ details: "При создании, изменении, удалении или восстановлении сообщения."
user_event:
name: "Событие пользователя"
- details: "Когда пользователь входит, выходит, создается, подтверждается или изменяется."
+ details: "При входе, выходе, создании, подтверждении или изменении пользователя."
group_event:
- name: "Групповое Мероприятие"
+ name: "Событие группы"
details: "При создании, обновлении или удалении группы."
category_event:
- name: "Категория Событий"
- details: "Когда категория создается, обновляется или удаляется."
+ name: "Событие раздела"
+ details: "При создании, обновлении или удалении раздела."
tag_event:
- name: "Отметить событие"
- details: "Когда тег создан, обновлен или удален."
+ name: "Событие тега"
+ details: "При создании, обновлении или удалении тега."
flag_event:
- name: "Отметить событие"
- details: "Когда флаг создан, согласован, не согласен или игнорируется."
+ name: "Событие жалобы"
+ details: "При создании, согласовании, несогласовании или игнорировании жалобы."
queued_post_event:
- name: "Пост после утверждения"
- details: "Когда новая запись в очереди создается, утверждается или отклоняется."
+ name: "Событие утверждения сообщения"
+ details: "При добавлении нового сообщения в очередь, а затем при его утверждении или отклонении."
reviewable_event:
- name: "Пересмотрены События"
- details: "Когда новый элемент готов к рассмотрению и когда его статус обновляется."
+ name: "Событие пересмотра"
+ details: "При готовности нового элемента к рассмотрению и при обновлении его статуса."
notification_event:
- name: "Событие Уведомления"
- details: "Когда пользователь получает уведомление в своей ленте."
+ name: "Событие уведомления"
+ details: "При добавлении нового элемента в ленту уведомлений пользователя."
delivery_status:
title: "Статус передачи"
inactive: "Неактивна"
@@ -3438,7 +3473,7 @@ ru:
other: "Завершится через {{count}} секунд."
request: "Запрос"
response: "Ответ"
- redeliver_confirm: "Вы уверены, что хотите повторно отправить те же самые данные?"
+ redeliver_confirm: "Вы действительно хотите повторно отправить те же самые данные?"
headers: "Заголовки"
payload: "Данные для отправки"
body: "Тело"
@@ -3446,10 +3481,10 @@ ru:
go_details: "Редактировать webhook"
go_events: "Перейти к событию"
ping: "Ping"
- status: "Код Состояния"
+ status: "Код состояния"
event_id: "Идентификатор (ID)"
timestamp: "Создано"
- completion: "Время Завершения"
+ completion: "Время завершения"
actions: "Действия"
plugins:
title: "Плагины"
@@ -3474,7 +3509,7 @@ ru:
enable:
title: "Включить режим \"только для чтения\""
label: "Включить режим \"только для чтения\""
- confirm: "Вы уверены, что хотите включить режим \"только для чтения\"?"
+ confirm: "Вы действительно хотите включить режим \"только для чтения\"?"
disable:
title: "Выключить режим \"только для чтения\""
label: "Выключить режим \"только для чтения\""
@@ -3496,7 +3531,7 @@ ru:
cancel:
label: "Отменить"
title: "Отменить текущую операцию"
- confirm: "Вы уверены, что хотите отменить текущую операцию?"
+ confirm: "Вы действительно хотите отменить текущую операцию?"
backup:
label: "Резервная копия"
title: "Создать резервную копию"
@@ -3508,16 +3543,16 @@ ru:
alert: "Вам была отправлена ссылка для скачивания бэкапа."
destroy:
title: "Удалить резервную копию"
- confirm: "Вы уверены, что хотите уничтожить резервную копию?"
+ confirm: "Вы действительно хотите уничтожить резервную копию?"
restore:
is_disabled: "Восстановление отключено в настройках сайта."
label: "Восстановить"
title: "Восстановить резервную копию"
- confirm: "Вы уверены, что хотите восстановить этот бэкап?"
+ confirm: "Вы действительно хотите восстановить этот бэкап?"
rollback:
label: "Откатить"
title: "Откатить базу данных к предыдущему рабочему состоянию"
- confirm: "Вы уверены, что хотите откатить базу данных до предыдущего рабочего состояния?"
+ confirm: "Вы действительно хотите откатить базу данных до предыдущего рабочего состояния?"
location:
local: "Локальное хранилище"
s3: "S3"
@@ -3547,7 +3582,7 @@ ru:
new_style: "Новый стиль"
install: "Install"
delete: "Удалить"
- delete_confirm: 'Вы уверены, что хотите удалить "%{theme_name}"?'
+ delete_confirm: 'Вы действительно хотите удалить "%{theme_name}"?'
color: "Цвет"
opacity: "Прозрачность"
copy: "Копировать"
@@ -3562,7 +3597,7 @@ ru:
body: "Текст сообщения"
none_selected: "Выберите шаблон письма, чтобы начать редактирование."
revert: "Отменить изменения"
- revert_confirm: "Вы уверены, что хотите отменить Ваши изменения?"
+ revert_confirm: "Вы действительно хотите отменить Ваши изменения?"
theme:
theme: "Тема"
component: "Компонент"
@@ -3570,9 +3605,9 @@ ru:
theme_name: "Название темы"
component_name: "Имя компонента"
themes_intro: "Выберите существующую тему или установите новую, чтобы начать работу"
- beginners_guide_title: "Руководство для начинающих по использованию Discourse Тем"
+ beginners_guide_title: "Руководство для начинающих по использованию тем Discourse"
developers_guide_title: "Руководство разработчика по темам Discourse"
- browse_themes: "Просмотр Тем сообщества "
+ browse_themes: "Просмотр тем сообщества "
customize_desc: "Настроить:"
title: "Стили"
create: "Создать"
@@ -3581,7 +3616,7 @@ ru:
long_title: "Стилизация сайта: цвета, CSS и HTML"
edit: "Редактировать"
edit_confirm: "Это импортированный стиль. Изменения CSS/HTML потеряются после очередного обновления стиля."
- update_confirm: "Эти локальные изменения будут удалены обновлением. Вы уверены, что хотите продолжить?"
+ update_confirm: "Эти локальные изменения будут удалены обновлением. Вы действительно хотите продолжить?"
update_confirm_yes: "Да, продолжить обновление"
common: "Общее"
desktop: "Настольный"
@@ -3601,9 +3636,9 @@ ru:
theme_components: "Компоненты стиля"
add_all_themes: "Добавить все темы"
convert: "Конвертировать"
- convert_component_alert: "Вы уверены, что хотите преобразовать этот компонент в тему? Он будет удален как компонент из %{relatives}."
+ convert_component_alert: "Вы действительно хотите преобразовать этот компонент в тему? Он будет удален как компонент из %{relatives}."
convert_component_tooltip: "Преобразовать этот компонент в тему"
- convert_theme_alert: "Вы уверены, что хотите преобразовать эту тему в компонент? Он будет удален как родитель из %{relatives}."
+ convert_theme_alert: "Вы действительно хотите преобразовать эту тему в компонент? Он будет удален как родитель из %{relatives}."
convert_theme_tooltip: "Преобразовать эту тему в компонент"
inactive_themes: "Неактивные темы:"
inactive_components: "Неиспользуемые компоненты:"
@@ -3626,6 +3661,7 @@ ru:
upload: "Загрузить"
select_component: "Выберите компонент..."
unsaved_changes_alert: "Вы еще не сохранили свои изменения, хотите отменить их и двигаться дальше?"
+ unsaved_parent_themes: "Вы не назначили компонент темам, вы хотите двигаться дальше?"
discard: "Отказать"
stay: "Остаться"
css_html: "Настройка CSS/HTML"
@@ -3668,7 +3704,7 @@ ru:
add: "Добавить"
theme_settings: "Настройки темы"
no_settings: "Эта тема не имеет настроек."
- theme_translations: "Перевод Темы"
+ theme_translations: "Перевод темы"
empty: "Нет элементов"
commits_behind:
one: "Тема находится на %{count} коммит сзади!"
@@ -3707,10 +3743,10 @@ ru:
title: "Выберите базовую цветовую палитру"
description: "Базовая палитра:"
title: "Цвета"
- edit: "Редактировать Цветовую Палитру"
+ edit: "Редактировать цветовую палитру"
long_title: "Цветовые палитры"
about: "Измените цвета, используемые в темах. Создайте новую цветовую палитру для начала."
- new_name: "Новая Цветовая Палитра"
+ new_name: "Новая цветовая палитра"
copy_name_prefix: "Копия"
delete_confirm: "Удалить эту цветовую палитру?"
undo: "отменить"
@@ -3752,12 +3788,12 @@ ru:
warning: "Это навсегда переопределит любые связанные настройки сайта."
overridden: Файл robots.txt по умолчанию вашего сайта перезаписан.
email_style:
- title: "E-mail Стиль"
- heading: "Настроить Стиля E-mail Почты"
- html: "HTML Шаблон"
+ title: "Стиль E-mail"
+ heading: "Настройка стиля E-mail"
+ html: "HTML-шаблон"
css: "CSS"
reset: "Сбросить по умолчанию"
- reset_confirm: "Вы уверены, что хотите сбросить настройки по умолчанию %{fieldName} и потерять все свои изменения?"
+ reset_confirm: "Вы действительно хотите сбросить настройки по умолчанию %{fieldName} и потерять все свои изменения?"
save_error_with_reason: "Ваши изменения не были сохранены. %{error}"
instructions: "Настройка шаблона, в котором отображаются все html-сообщения e-mail почты, и стиль с помощью CSS."
email:
@@ -3766,12 +3802,12 @@ ru:
templates: "Шаблоны"
preview_digest: "Сводка новостей"
advanced_test:
- title: "Расширенный Тест"
+ title: "Расширенный тест"
desc: "Посмотрите, как Discourse обрабатывает полученные письма. Чтобы правильно обработать письмо, вставьте ниже оригинальное сообщение."
email: "Исходное сообщение"
- run: "Выполнить Тест"
- text: "Выбранный Текст сообщения"
- elided: "Выделенный Текст"
+ run: "Выполнить тест"
+ text: "Выбранный текст сообщения"
+ elided: "Выделенный текст"
sending_test: "Отправка тестового письма..."
error: "
ОШИБКА - %{server_error}"
test_error: "При отправке тестового письма произошла ошибка. Проверьте почтовые настройки, убедитесь, что ваш почтовый провайдер не блокирует почтовые соединения, и попробуйте снова."
@@ -3833,11 +3869,11 @@ ru:
performed_by: "Выполнено пользователем "
no_results: "История модерации недоступна."
actions:
- delete_user: "Удалить Пользователя"
- suspend_user: "Пользователь приостановлен"
- silence_user: "Пользователь отключен"
- delete_post: "Комментарий удален"
- delete_topic: "Тема удалена"
+ delete_user: "Удаление пользователя"
+ suspend_user: "Заморозка пользователя"
+ silence_user: "Блокировка пользователя"
+ delete_post: "Удаление сообщения"
+ delete_topic: "Удаление темы"
post_approved: "Сообщение утверждено"
logs:
title: "Логи"
@@ -3874,86 +3910,86 @@ ru:
no_previous: "Старое значение отсутствует."
deleted: "Новое значение отсутствует. Запись была удалена."
actions:
- delete_user: "удален пользователь"
- change_trust_level: "изменен уровень доверия"
- change_username: "изменен псевдоним"
- change_site_setting: "изменена настройка сайта"
- change_theme: "Изменить стиль"
- delete_theme: "Удалить стиль"
- change_site_text: "изменен текст"
- suspend_user: "пользователь заморожен"
- unsuspend_user: "пользователь разморожен"
- removed_suspend_user: "приостановить пользователя (удалено)"
- removed_unsuspend_user: "приостановленный пользователей (удален)"
- grant_badge: "выдана награда"
- revoke_badge: "отозвана награда"
- check_email: "доступ к адресу e-mail"
- delete_topic: "удалена тема"
- recover_topic: "восстановить удаленную тему"
- delete_post: "удалено сообщение"
- impersonate: "вход от имени пользователя"
- anonymize_user: "пользователь анонимизирован"
- roll_up: "сгруппированы заблокированные IP адреса в подсеть"
- change_category_settings: "изменена настройка раздела"
- delete_category: "удален раздел"
- create_category: "создан раздел"
- silence_user: "заблокировать пользователя"
- unsilence_user: "разблокировать пользователя"
- removed_silence_user: "молчание пользователя (удалено)"
- removed_unsilence_user: "приостановленный пользователей (удален)"
- grant_admin: "выданы права администратора"
- revoke_admin: "отозваны права администратора"
- grant_moderation: "выданы права модератора"
- revoke_moderation: "отозваны права модератора"
- backup_create: "создать резервную копию"
- deleted_tag: "удалённый тег"
- deleted_unused_tags: "удалены неиспользуемые теги"
- renamed_tag: "переименованный тег"
- revoke_email: "отозвать e-mail"
- lock_trust_level: "заморозка уровня доверия"
- unlock_trust_level: "разморозка уровня доверия"
- activate_user: "активация пользователя"
- deactivate_user: "деактивация пользователя"
- change_readonly_mode: "изменение режима \"только для чтения\""
- backup_download: "скачать резервную копию"
- backup_destroy: "удалить резервную копию"
- reviewed_post: "просмотренное сообщение"
- custom_staff: "действия в плагинах"
- post_locked: "сообщение заморожено"
- post_edit: "редактировать сообщение"
- post_unlocked: "сообщение разморожено"
- check_personal_message: "проверить личное сообщение"
- disabled_second_factor: "отключить двухфакторную аутентификацию"
- topic_published: "тема опубликована"
- post_approved: "сообщение утверждено"
- post_rejected: "сообщение отклонено"
- create_badge: "создать награду"
- change_badge: "изменить награду"
- delete_badge: "удалить награду"
- merge_user: "объединить пользователя"
- entity_export: "экспортный объект"
- change_name: "изменить имя"
- topic_timestamps_changed: "метки времени темы изменены"
- approve_user: "одобренный пользователь"
- web_hook_create: "создать webhook"
- web_hook_update: "обновить webhook "
- web_hook_destroy: "удалить webhook"
- web_hook_deactivate: "деактивировать webhook"
- embeddable_host_create: "создать встраиваемый хост"
- embeddable_host_update: "обновить встраиваемый хост"
- embeddable_host_destroy: "удалить встраиваемый хост"
- change_theme_setting: "изменить настройки темы"
- disable_theme_component: "отключить компонент темы"
- enable_theme_component: "включить компонент темы"
- revoke_title: "отозвать название"
- change_title: "изменить название"
- api_key_create: "создать api ключ"
- api_key_update: "обновление api ключа"
- api_key_destroy: "уничтожить api ключ"
+ delete_user: "Удаление пользователя"
+ change_trust_level: "Изменение уровня доверия"
+ change_username: "Изменение псевдонима"
+ change_site_setting: "Изменение настроек сайта"
+ change_theme: "Изменение стиля"
+ delete_theme: "Удаление стиля"
+ change_site_text: "Изменение сообщений сайта"
+ suspend_user: "Заморозка пользователя"
+ unsuspend_user: "Разморозка пользователя"
+ removed_suspend_user: "Удаление замороженного пользователя"
+ removed_unsuspend_user: "Удаление размороженного пользователя"
+ grant_badge: "Выдача награды"
+ revoke_badge: "Отзыв награды"
+ check_email: "Доступ к адресу e-mail"
+ delete_topic: "Удаление темы"
+ recover_topic: "Восстановление удаленной темы"
+ delete_post: "Удаление сообщения"
+ impersonate: "Вход от имени пользователя"
+ anonymize_user: "Анонимизация пользователя"
+ roll_up: "сгруппированы заблокированные IP-адреса в подсеть"
+ change_category_settings: "Изменение настроек раздела"
+ delete_category: "Удаление раздела"
+ create_category: "Создание раздела"
+ silence_user: "Блокировка пользователя"
+ unsilence_user: "Разблокировка пользователя"
+ removed_silence_user: "Удаление заблокированного пользователя"
+ removed_unsilence_user: "Удаление разблокированного пользователя"
+ grant_admin: "Выдача прав администратора"
+ revoke_admin: "Отзыв прав администратора"
+ grant_moderation: "Выдача прав модератора"
+ revoke_moderation: "Отзыв права модератора"
+ backup_create: "Создание резервной копии"
+ deleted_tag: "Удаление тега"
+ deleted_unused_tags: "Удаление неиспользуемых тегов"
+ renamed_tag: "Переименование тега"
+ revoke_email: "Отзыв e-mail"
+ lock_trust_level: "Заморозка уровня доверия"
+ unlock_trust_level: "Разморозка уровня доверия"
+ activate_user: "Активация пользователя"
+ deactivate_user: "Деактивация пользователя"
+ change_readonly_mode: "Изменение режима \"только для чтения\""
+ backup_download: "Загрузка резервной копии"
+ backup_destroy: "Удаление резервной копии"
+ reviewed_post: "Рецензирование сообщения"
+ custom_staff: "Действия в плагинах"
+ post_locked: "Заморозка сообщения"
+ post_edit: "Редактирование сообщения"
+ post_unlocked: "Разморозка сообщения"
+ check_personal_message: "Проверка личного сообщения"
+ disabled_second_factor: "Отключение двухфакторной аутентификации"
+ topic_published: "Публикация темы"
+ post_approved: "Одобрение сообщения"
+ post_rejected: "Отклонение сообщения"
+ create_badge: "Создание награды"
+ change_badge: "Изменение награды"
+ delete_badge: "Удаление награды"
+ merge_user: "Объединение пользователя"
+ entity_export: "Экспорт объекта"
+ change_name: "Изменение имени"
+ topic_timestamps_changed: "Изменение метки времени темы"
+ approve_user: "Одобрение пользователя"
+ web_hook_create: "Создание webhook"
+ web_hook_update: "Обновление webhook "
+ web_hook_destroy: "Удаление webhook"
+ web_hook_deactivate: "Деактивация webhook"
+ embeddable_host_create: "Создание встраиваемого хоста"
+ embeddable_host_update: "Обновление встраиваемого хоста"
+ embeddable_host_destroy: "Удаление встраиваемого хоста"
+ change_theme_setting: "Изменение настроек темы"
+ disable_theme_component: "Отключение компонента темы"
+ enable_theme_component: "Включение компонента темы"
+ revoke_title: "Отзыв названия"
+ change_title: "Изменение названия"
+ api_key_create: "Создание API-ключа"
+ api_key_update: "Обновление API-ключа"
+ api_key_destroy: "Удаление API-ключа"
override_upload_secure_status: "перезаписать защищенный статус загрузки"
screened_emails:
title: "Почтовые адреса"
- description: "Когда кто-то создаёт новую учётную запись, проверяется данный почтовый адрес и регистрация блокируется или производятся другие дополнительные действия."
+ description: "Когда кто-то создаёт новую учётную запись, указанный пользователем почтовый адрес проверяется на соответствие с указанным ниже списком, и в случае совпадения регистрация блокируется или производятся другие дополнительные действия."
email: "Почтовый адрес"
actions:
allow: "Разрешить"
@@ -3963,11 +3999,11 @@ ru:
url: "URL"
domain: "Домен"
screened_ips:
- title: "IP адреса"
- description: 'Список правил для IP адресов. Чтобы добавить IP адрес в белый список, используйте правило "Разрешить".'
- delete_confirm: "Удалить правило для IP адреса %{ip_address}?"
- roll_up_confirm: "Сгруппировать отдельные экранированные IP адреса в подсети?"
- rolled_up_some_subnets: "Заблокированные IP адреса сгруппированы в следующие подсети: %{subnets}."
+ title: "IP-адреса"
+ description: 'Список правил для IP-адресов. Чтобы добавить IP-адрес в белый список, используйте правило "Разрешить".'
+ delete_confirm: "Удалить правило для IP-адреса %{ip_address}?"
+ roll_up_confirm: "Сгруппировать отдельные экранированные IP-адреса в подсети?"
+ rolled_up_some_subnets: "Заблокированные IP-адреса сгруппированы в следующие подсети: %{subnets}."
rolled_up_no_subnet: "Ничего не найдено для группирования"
actions:
block: "Заблокировать"
@@ -3975,12 +4011,12 @@ ru:
allow_admin: "Разрешить админов"
form:
label: "Новое правило:"
- ip_address: "IP адрес"
+ ip_address: "IP-адрес"
add: "Добавить"
filter: "Поиск"
roll_up:
text: "Группировка"
- title: "Создание новой записи бана целой подсети, если уже имеется хотя бы 'min_ban_entries_for_roll_up' записей отдельных IP адресов."
+ title: "Создание новой записи бана целой подсети, если уже имеется хотя бы 'min_ban_entries_for_roll_up' записей отдельных IP-адресов."
search_logs:
title: "Логи поиска"
term: "Правило"
@@ -3998,41 +4034,41 @@ ru:
title: "Отслеживаемые слова"
search: "поиск"
clear_filter: "Очистить"
- show_words: "показать слова"
+ show_words: "Показать слова"
one_word_per_line: "Одно слово в строке"
download: Скачать
clear_all: Сбросить все
- clear_all_confirm_block: "Вы уверены, что хотите удалить все просматриваемые слова для действия Блокировать?"
- clear_all_confirm_censor: "Вы уверены, что хотите очистить все просматриваемые слова для действия Цензора?"
- clear_all_confirm_flag: "Вы уверены, что хотите удалить все просматриваемые слова для действия Флаг?"
- clear_all_confirm_require_approval: "Вы уверены, что хотите удалить все просматриваемые слова для действия Требовать утверждение?"
+ clear_all_confirm_block: "Вы действительно хотите удалить все слова, используемые при блокировке сообщений?"
+ clear_all_confirm_censor: "Вы действительно хотите удалить все слова, используемые при цензурировании сообщений?"
+ clear_all_confirm_flag: "Вы действительно хотите удалить все слова, которые используются при оценке сообщений, помеченных как неприемлемые?"
+ clear_all_confirm_require_approval: "Вы действительно хотите удалить все слова, которые используются при оценке сообщений, требующих одобрения?"
word_count:
one: "%{count} слово"
few: "%{count}слова"
many: "%{count}слов"
other: "%{count}слов"
actions:
- block: "Заблокировать"
+ block: "Блокировка"
censor: "Цензура"
require_approval: "Требующие одобрения"
- flag: "Жалоба"
+ flag: "Жалобы"
action_descriptions:
block: "Запретить публикацию сообщений, содержащих эти слова. Пользователь увидит сообщение об ошибке при попытке отправить свое сообщение."
- censor: "Разрешить сообщения, содержащие эти слова, но заменять их символами, которые скрывают цензурные выражения."
+ censor: "Разрешить сообщения, содержащие эти слова, но заменять их символами, которые скрывают нецензурные выражения."
require_approval: "Комментарии, содержащие эти слова, будут требовать одобрения персонала, прежде чем их можно будет увидеть."
- flag: "Разрешить сообщения, содержащие эти слова, но помечать их как неприемлемые, чтобы модераторы могли их оценивать."
+ flag: "Разрешить сообщения, содержащие эти слова, но помечать их как неприемлемые, чтобы модераторы могли их оценить."
form:
label: "Новое слово:"
placeholder: "слово целиком, звездочка (*) используется как знак подстановки "
placeholder_regexp: "Регулярное выражение"
add: "Добавить"
- success: "Успех"
+ success: "Слово успешно добавлено"
exists: "Уже существует"
upload: "Добавить из файла"
upload_successful: "Загрузка прошла успешна. Слова добавлены."
test:
button_label: "Тест"
- modal_title: "Тест '%{action}' Наблюдаемые Слова"
+ modal_title: "Тест '%{action}' Наблюдаемые слова"
description: "Введите текст ниже, чтобы проверить совпадения с наблюдаемыми словами"
found_matches: "Найденные совпадения:"
no_matches: "Совпадений не найдено"
@@ -4055,9 +4091,9 @@ ru:
active: "Активные"
staff: "Персонал"
suspended: "Замороженные"
- silenced: "Отключенный"
+ silenced: "Заблокированные"
suspect: "Подозрительные"
- staged: "Поэтапные"
+ staged: "Сымитированные"
approved: "Подтвердить?"
titles:
active: "Активные пользователи"
@@ -4071,11 +4107,11 @@ ru:
staff: "Персонал"
admins: "Администраторы"
moderators: "Модераторы"
- silenced: "Отключенные пользователи"
+ silenced: "Заблокированные пользователи"
suspended: "Замороженные пользователи"
suspect: "Подозрительные пользователи"
- staged: "Поэтапный Пользователи"
- not_verified: "Не проверенные"
+ staged: "Сымитированные пользователи"
+ not_verified: "Непроверенные"
check_email:
title: "Открыть e-mail этого пользователя"
text: "Показать"
@@ -4083,39 +4119,39 @@ ru:
suspend_failed: "Ошибка заморозки пользователя {{error}}"
unsuspend_failed: "Ошибка разморозки пользователя {{error}}"
suspend_duration: "На сколько времени заморозить пользователя?"
- suspend_reason_label: "Причина заморозки? Данный текст
будет виден всем на странице профиля пользователя и будет отображаться, когда пользователь пытается войти. Введите краткое описание."
- suspend_reason_hidden_label: "Почему вы приостанавливаете пользователя? Этот текст будет показан пользователю, когда он попытается войти в систему. Будьте краткими."
+ suspend_reason_label: "Причина заморозки? Данный текст
будет виден всем на странице профиля пользователя и будет отображаться, когда пользователь попытается войти. Введите краткое описание."
+ suspend_reason_hidden_label: "Почему вы замораживаете пользователя? Этот текст будет показан пользователю, когда он попытается войти в систему. Будьте краткими."
suspend_reason: "Причина"
- suspend_reason_placeholder: "Причина приостановки"
+ suspend_reason_placeholder: "Причина заморозки"
suspend_message: "Сообщение почты"
- suspend_message_placeholder: "При желании, предоставьте дополнительную информацию о приостановке, и она будет отправлена пользователю по электронной почте."
+ suspend_message_placeholder: "При желании, предоставьте дополнительную информацию о заморозке, и она будет отправлена пользователю по электронной почте."
suspended_by: "Заморожен (кем)"
silence_reason: "Причина"
- silenced_by: "Отключен пользователем "
- silence_modal_title: "Отключенный пользователь"
- silence_duration: "Как долго пользователь будет отключен?"
- silence_reason_label: "Почему вы собираетесь отключить пользователя?"
- silence_reason_placeholder: "Причина отключения"
- silence_message: "Сообщение почты"
- silence_message_placeholder: "(оставьте незаполненным, чтобы отправить дефолтное сообщение)"
+ silenced_by: "Заблокирован пользователем "
+ silence_modal_title: "Заблокированный пользователь"
+ silence_duration: "Как долго пользователь будет заблокирован?"
+ silence_reason_label: "Почему вы собираетесь заблокировать пользователя?"
+ silence_reason_placeholder: "Причина блокировки"
+ silence_message: "Сообщение на электронную почту"
+ silence_message_placeholder: "(оставьте незаполненным, чтобы отправить стандартное сообщение)"
suspended_until: "(пока не будет %{until})"
cant_suspend: "Этого пользователя нельзя заморозить."
delete_all_posts: "Удалить все сообщения"
- delete_posts_progress: "Удаление постов..."
- delete_posts_failed: "При удалении постов возникла проблема."
+ delete_posts_progress: "Удаление сообщений..."
+ delete_posts_failed: "При удалении сообщений возникла ошибка."
penalty_post_actions: "Что нужно сделать со связанным сообщением?"
penalty_post_delete: "Удалить сообщение"
- penalty_post_delete_replies: "Удалить пост + любые ответы"
+ penalty_post_delete_replies: "Удалить сообщение + ответы"
penalty_post_edit: "Редактировать сообщение"
penalty_post_none: "Ничего не делать"
penalty_count: "Количество нарушений"
clear_penalty_history:
- title: "Очистить Историю Штрафов"
+ title: "Очистить историю штрафов"
description: "пользователи со штрафами не могут достичь TL3"
delete_all_posts_confirm_MF: "Вы собираетесь удалить {POSTS, plural, one {1 сообщение} other {# сообщений}} и {TOPICS, plural, one {1 тему} other {# тем}}. Вы уверены?"
- silence: "Отключить"
- unsilence: "Подключить"
- silenced: "Отключен?"
+ silence: "Заблокировать"
+ unsilence: "Разблокировать"
+ silenced: "Заброкирован?"
moderator: "Модератор?"
admin: "Администратор?"
suspended: "Заморожен?"
@@ -4130,13 +4166,13 @@ ru:
revoke_admin: "Лишить прав Администратора"
grant_admin: "Выдать права Администратора"
grant_admin_confirm: "Мы отправили вам письмо с инструкциями для активации нового администратора."
- revoke_moderation: "Лишить прав Модератора"
- grant_moderation: "Выдать права Модератора"
+ revoke_moderation: "Лишить прав модератора"
+ grant_moderation: "Выдать права модератора"
unsuspend: "Разморозить"
suspend: "Заморозить"
- show_flags_received: "Показать полученные флаги"
- flags_received_by: "Флаги, полученные %{username}"
- flags_received_none: "Этот пользователь не получил никаких флагов."
+ show_flags_received: "Показать полученные жалобы"
+ flags_received_by: "Жалобы, полученные %{username}"
+ flags_received_none: "Этот пользователь не получил никаких жалоб."
reputation: Репутация
permissions: Права
activity: Активность
@@ -4152,9 +4188,9 @@ ru:
warnings_received_count: Получено предупреждений
flags_given_received_count: "Жалоб отправил / получил"
approve: "Одобрить"
- approved_by: "кем одобрено"
- approve_success: "Пользователь одобрен и на электронную почту отправлено письмо с инструкцией по активации."
- approve_bulk_success: "Успех! Все выбранные пользователи были одобрены и уведомлены."
+ approved_by: "Кем одобрено"
+ approve_success: "Пользователь одобрен и на его электронную почту отправлено письмо с инструкцией по активации."
+ approve_bulk_success: "Операция выполнена успешно! Все выбранные пользователи были одобрены и уведомлены."
time_read: "Время чтения"
anonymize: "Анонимизировать пользователя"
anonymize_confirm: "Вы точно УВЕРЕНЫ, что хотите анонимизировать эту учетную запись? Это приведет к изменению псевдонима и адреса электронной почты и очистит всю информацию профиля."
@@ -4178,8 +4214,8 @@ ru:
few: "Не удаётся удалить все сообщения, потому что у пользователя более %{count} сообщений. (Настройка delete_all_posts_max.)"
many: "Не удаётся удалить все сообщения, потому что у пользователя более %{count} сообщений. (Настройка delete_all_posts_max.)"
other: "Не удаётся удалить все сообщения, потому что у пользователя более %{count} сообщений. (Настройка delete_all_posts_max.)"
- delete_confirm: "Обычно предпочтительнее анонимизировать пользователей, чем удалять их, чтобы избежать удаления контента из существующих обсуждений.
Вы уверены, что хотите удалить этого пользователя? Это навсегда!"
- delete_and_block: "Удалить и
заблокировать этот e-mail и IP адрес"
+ delete_confirm: "Обычно предпочтительнее анонимизировать пользователей, чем удалять их, чтобы избежать удаления контента из существующих обсуждений.
Вы действительно хотите удалить этого пользователя? Это навсегда!"
+ delete_and_block: "Удалить и
заблокировать этот e-mail и IP-адрес"
delete_dont_block: "Только удалить"
deleting_user: "Удалить пользователя..."
deleted: "Пользователь удалён."
@@ -4191,19 +4227,19 @@ ru:
activate_failed: "Во время активации пользователя произошла ошибка."
deactivate_account: "Деактивировать"
deactivate_failed: "Во время деактивации пользователя произошла ошибка."
- unsilence_failed: "При подключении этого пользователя произошла ошибка."
- silence_failed: "При отключении этого пользователя произошла ошибка."
- silence_confirm: "Вы уверены, что хотите отключить этого пользователя? Он не сможет создавать новые темы или сообщения."
- silence_accept: "Да, отключить этого пользователя"
+ unsilence_failed: "При разблокировке этого пользователя произошла ошибка."
+ silence_failed: "При блокировке этого пользователя произошла ошибка."
+ silence_confirm: "Вы действительно хотите заблокировать этого пользователя? Он не сможет создавать новые темы или сообщения."
+ silence_accept: "Да, заблокировать этого пользователя"
bounce_score: "Возвратов писем"
reset_bounce_score:
label: "Сбросить"
title: "сбросить карму к 0"
visit_profile: "Посетите
страницу настроек этого пользователя , чтобы отредактировать его профиль"
- deactivate_explanation: "Дезактивированные пользователи должны заново подтвердить свой e-mail."
+ deactivate_explanation: "Деактивированный пользователь должен заново подтвердить свой e-mail."
suspended_explanation: "Замороженный пользователь не может войти (авторизоваться)."
- silence_explanation: "Отключенный пользователь не может публиковать или отвечать на темы."
- staged_explanation: "Имитированный пользователь может отправлять сообщения только по эл.почте в определённые темы."
+ silence_explanation: "Заблокированный пользователь не может создавать темы или отвечать на темы."
+ staged_explanation: "Сымитированный пользователь может отправлять сообщения только по электронной почте в определённые темы."
bounce_score_explanation:
none: "Нет возвратов полученных недавно от этой эл.почты."
some: "Несколько возвратов получено недавно от этой эл.почты."
@@ -4212,10 +4248,10 @@ ru:
suspend_modal_title: "Заморозить пользователя"
trust_level_2_users: "Пользователи с уровнем доверия 2"
trust_level_3_requirements: "Требуется 3 уровень доверия"
- trust_level_locked_tip: "уровень доверия заморожен, система не сможет по надобности разжаловать или продвинуть пользователя"
- trust_level_unlocked_tip: "уровень доверия разморожен, система сможет по надобности разжаловать или продвинуть пользователя"
- lock_trust_level: "Заморозить уровень доверия"
- unlock_trust_level: "Разморозить уровень доверия"
+ trust_level_locked_tip: "Уровень доверия заблокирован, система не может изменять уровень доверия пользователя"
+ trust_level_unlocked_tip: "Уровень доверия разблокирован, система может изменять уровень доверия пользователя"
+ lock_trust_level: "Заблокировать изменение уровня доверия"
+ unlock_trust_level: "Разблокировать изменение уровня доверия"
tl3_requirements:
title: "Требования для 3 уровня доверия"
table_title:
@@ -4238,15 +4274,15 @@ ru:
likes_received: "Получено симпатий"
likes_received_days: "Получено симпатий: отдельные дни"
likes_received_users: "Получено симпатий: уникальные пользователи"
- suspended: "Приостановлен (за последние 6 месяцев)"
- silenced: "Заморожен (последние 6 месяцев)"
+ suspended: "Заморожен (последние 6 месяцев)"
+ silenced: "Заблокирован (последние 6 месяцев)"
qualifies: "Заслуживает уровень доверия 3."
does_not_qualify: "Не заслуживает уровень доверия 3."
will_be_promoted: "Пользователь будет скоро продвинут до этого уровня."
will_be_demoted: "Пользователь скоро будет разжалован."
on_grace_period: "В данный момент в периоде доверия и не может быть разжалован."
- locked_will_not_be_promoted: "Уровень доверия заморожен, поэтому пользователь никогда не будет продвинут до этого уровня."
- locked_will_not_be_demoted: "Уровень доверия заморожен, поэтому пользователь никогда не будет разжалован."
+ locked_will_not_be_promoted: "Изменение уровня доверия заблокировано, поэтому пользователь никогда не будет продвинут до этого уровня."
+ locked_will_not_be_demoted: "Изменение уровня доверия заблокировано, поэтому пользователь никогда не будет разжалован."
sso:
title: "Технология единого входа SSO"
external_id: "Внешний идентификатор"
@@ -4266,7 +4302,7 @@ ru:
edit: "Изменить"
delete: "Удалить"
cancel: "Отмена"
- delete_confirm: "Вы уверены, что хотите удалить это поле?"
+ delete_confirm: "Вы действительно хотите удалить это поле?"
options: "Опции"
required:
title: "Обязательное во время регистрации?"
@@ -4282,7 +4318,7 @@ ru:
disabled: "Не показывать в профиле"
show_on_user_card:
title: "Показывать в карточке пользователя?"
- enabled: "показывается в карточке пользователя"
+ enabled: "Показывать в карточке пользователя"
disabled: "Не показывать в карточке пользователя"
field_types:
text: "Текстовое поле"
@@ -4296,9 +4332,9 @@ ru:
revert: "Отменить изменения"
revert_confirm: "Вы уверены что хите отменить ваши изменения?"
go_back: "Вернуться к поиску"
- recommended: "Мы рекомендуем изменить следующий текст под ваши нужды:"
+ recommended: "Мы рекомендуем изменить следующий текст под ваши требования:"
show_overriden: "Показывать только изменённые"
- more_than_50_results: "Найдено более 50 результатов. Пожалуйста уточните параметры поиска."
+ more_than_50_results: "Найдено более 50 результатов. Пожалуйста, уточните параметры поиска."
settings:
show_overriden: "Показывать только изменённые"
reset: "сброс"
@@ -4306,14 +4342,14 @@ ru:
site_settings:
title: "Настройки"
no_results: "Ничего не найдено."
- more_than_30_results: "Найдено более 30 результатов. Пожалуйста уточните параметры поиска или выберите раздел."
+ more_than_30_results: "Найдено более 30 результатов. Пожалуйста, уточните параметры поиска или выберите раздел."
clear_filter: "Очистить"
add_url: "Добавить URL"
- add_host: "добавить хост"
+ add_host: "Добавить хост"
add_group: "добавить группу"
uploaded_image_list:
label: "Изменить список"
- empty: "Фотографий пока нет. Пожалуйста, загрузите одно."
+ empty: "Изображений пока нет. Пожалуйста, загрузите одно."
upload:
label: "Загрузить"
title: "Загрузить изображение(я)"
@@ -4349,7 +4385,7 @@ ru:
groups: "Группы"
dashboard: "Админка"
secret_list:
- invalid_input: "поля ввода не могут быть пустыми или содержать символ вертикальной черты."
+ invalid_input: "Поля ввода не могут быть пустыми или содержать символ вертикальной черты."
default_categories:
modal_description: "Хотели бы вы применить это изменение исторически? Это изменит настройки для %{count} существующих пользователей."
modal_yes: "Да"
@@ -4372,11 +4408,11 @@ ru:
reason_help: (Ссылка на сообщение или тему)
save: Сохранить
delete: Удалить
- delete_confirm: "Вы уверены, что хотите удалить эту награду?"
+ delete_confirm: "Вы действительно хотите удалить эту награду?"
revoke: Отозвать
reason: Причина
expand: Развернуть …
- revoke_confirm: "Вы уверены, что хотите отозвать эту награду?"
+ revoke_confirm: "Вы действительно хотите отозвать эту награду?"
edit_badges: Редактировать награды
grant_badge: Выдать награду
granted_badges: Выданные награды
@@ -4395,7 +4431,7 @@ ru:
query: Выборка награды (SQL)
target_posts: Выборка целевых сообщений
auto_revoke: Запускать запрос на отзыв ежедневно
- show_posts: "Показывать сообщение, на основе которого была выдана награда, на странице наград"
+ show_posts: "Показывать на странице наград сообщение, на основе которого была выдана награда"
trigger: Запуск
trigger_type:
none: "Обновлять ежедневно"
@@ -4429,21 +4465,29 @@ ru:
title: "Выберите существующую награду или создайте новую, чтобы начать работу"
what_are_badges_title: "Что такое награды?"
badge_query_examples_title: "Примеры запроса награды"
+ mass_award:
+ title: Массовое награждение
+ description: Присуждение награды сразу нескольким пользователям.
+ no_badge_selected: "Пожалуйста, выберите награду."
+ perform: "Наградить пользователей"
+ upload_csv: Загрузите файл в формате
CSV с электронными адресами пользователей
+ aborted: "Пожалуйста, загрузите файл в формате
CSV , содержащий адреса электронной почты пользователей."
+ success: "Файл успешно загружен, пользователи получат награды в ближайшее время."
emoji:
title: "Иконки"
help: "Добавить новые смайлики-emoji, которые будут доступны всем. (Подсказка: можно перетаскивать несколько файлов за раз)"
add: "Добавить новую иконку"
name: "Название"
image: "Изображение"
- delete_confirm: "Вы уверены, что хотите удалить иконку :%{name}:?"
+ delete_confirm: "Вы действительно хотите удалить иконку :%{name}:?"
embedding:
get_started: "Для встраивания на другой сайт необходимо добавить соответствующий хост."
- confirm_delete: "Вы уверены, что хотите удалить это поле?"
+ confirm_delete: "Вы действительно хотите удалить это поле?"
sample: "Используйте следующий HTML-код на своём сайте, для возможности создания связанных тем. Замените
REPLACE_ME канонической ссылкой страницы, куда производится встраивание."
title: "Встраивание"
- host: "Разрешённые Хосты"
+ host: "Разрешённые хосты"
class_name: "Имя класса"
- path_whitelist: "Разрешённый Путь"
+ path_whitelist: "Разрешённый путь"
edit: "изменить"
category: "Опубликовать в разделе"
add_host: "Добавить хост"
@@ -4460,7 +4504,7 @@ ru:
save: "Сохранить настройки встраивания"
permalink:
title: "Постоянные ссылки"
- description: "Обратите внимание, что это относится только к внешним источникам, ссылки, размещенные на вашем форуме, не будут перенаправлены."
+ description: "Обратите внимание, что это относится только к внешним источникам. Ссылки, размещенные на вашем форуме, не будут перенаправлены."
url: "Ссылка URL"
topic_id: "Номер темы"
topic_title: "Тема"
@@ -4476,12 +4520,12 @@ ru:
filter: "Поиск по ссылке или внешней ссылке (URL)"
reseed:
action:
- label: "Заменить Текст..."
- title: "Заменить текст категорий и тем с переводами"
+ label: "Заменить текст..."
+ title: "Заменить переводами текст разделов и тем"
modal:
- title: "Заменить Текст"
- subtitle: "Заменить текст системных категорий и тем последними переводами"
- categories: "Категории"
+ title: "Заменить текст"
+ subtitle: "Заменить текст системных разделов и тем последними переводами"
+ categories: "Разделы"
topics: "Темы"
replace: "Заменить"
wizard_js:
@@ -4502,11 +4546,11 @@ ru:
other: "В вашем сообществе %{count} сотрудников, включая вас."
invites:
add_user: "добавить"
- none_added: "Вы ещё не пригласили сотрудников. Вы уверены, что хотите продолжить?"
+ none_added: "Вы ещё не пригласили сотрудников. Вы действительно хотите продолжить?"
roles:
admin: "Администратор"
moderator: "Модератор"
- regular: "Постоянный Пользователь"
+ regular: "Постоянный пользователь"
previews:
topic_title: "Тема обсуждения"
share_button: "Поделиться"
diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml
index cc0b85e87d..f19567a23e 100644
--- a/config/locales/client.sv.yml
+++ b/config/locales/client.sv.yml
@@ -27,6 +27,7 @@ sv:
millions: "{{number}}M"
dates:
time: "h:mm a"
+ time_short_day: "ddd, HH:mm"
timeline_date: "MMM YYYY"
long_no_year: "MMM D h:mm a"
long_no_year_no_time: "MMM D"
@@ -48,6 +49,9 @@ sv:
x_seconds:
one: "%{count}s"
other: "%{count}s"
+ less_than_x_minutes:
+ one: "< %{count}m"
+ other: "< %{count}m"
x_minutes:
one: "%{count}m"
other: "%{count}m"
@@ -57,6 +61,9 @@ sv:
x_days:
one: "%{count}d"
other: "%{count}d"
+ x_months:
+ one: "%{count}mån"
+ other: "%{count}mån"
about_x_years:
one: "%{count}å"
other: "%{count}å"
@@ -89,6 +96,12 @@ sv:
x_days:
one: "%{count} dag sedan"
other: "%{count} dagar sedan"
+ x_months:
+ one: "%{count}månad sedan"
+ other: "%{count} månader sedan"
+ x_years:
+ one: "%{count} år sedan"
+ other: "%{count} år sedan"
later:
x_days:
one: "%{count} dag senare"
@@ -103,6 +116,7 @@ sv:
next_month: "Nästkommande månad"
placeholder: datum
share:
+ topic_html: 'Ämne:
%{topicTitle} '
post: "inlägg #%{postNumber}"
close: "stäng"
twitter: "Dela denna länk på Twitter"
@@ -117,6 +131,7 @@ sv:
user_left: "%{who} tog bort sig själva från detta meddelande %{when}"
removed_user: "tog bort %{who} %{when}"
removed_group: "tog bort %{who} %{when}"
+ autobumped: "automatiskt positionerad %{when}"
autoclosed:
enabled: "stängdes %{when}"
disabled: "öppnades %{when}"
@@ -138,12 +153,14 @@ sv:
banner:
enabled: "gjorde detta till en banderoll %{when}. Den kommer att visas högst upp på varje sida tills den blir avfärdad av användaren."
disabled: "tog bort denna banderoll %{when}. Den kommer inte längre att visas högst upp på varje sida."
+ topic_admin_menu: "ämnesåtgärder"
wizard_required: "Välkommen till din nya Discourse! Låt oss komma igång genom
uppsättnings guiden ✨"
emails_are_disabled: "All utgående e-post har blivit globalt inaktiverad av en administratör. Inga e-postnotifikationer av något slag kommer att skickas ut."
bootstrap_mode_enabled: "Du är i bootstrap-läge för att göra lanseringen av din nya webbplats enklare. Alla nya användare kommer att beviljas förtroendenivå 1 och få dagliga sammanfattningar skickade via e-post. Det här stängs automatiskt av när det totala antalet användare överstiger %{min_users}."
bootstrap_mode_disabled: "Bootstrap-läge stängs av om 24 timmar."
themes:
default_description: "Standard"
+ broken_theme_alert: "Din sida kanske inte fungerar på grunda av att tema / komponent %{theme}har felaktigheter. Avaktivera det från %{path}."
s3:
regions:
ap_northeast_1: "Asien Stillahavsområdet (Tokyo)"
@@ -151,14 +168,19 @@ sv:
ap_south_1: "Asien Stillahavsområdet (Mumbai)"
ap_southeast_1: "Asien Stillahavsområdet (Singapore)"
ap_southeast_2: "Asien Stillahavsområdet (Sydney)"
+ ca_central_1: "Canada (Central)"
cn_north_1: "Kina (Peking)"
cn_northwest_1: "Kina (Ningxia)"
eu_central_1: "EU (Frankfurt)"
+ eu_north_1: "EU (Stockholm)"
eu_west_1: "EU (Irland)"
eu_west_2: "EU (London)"
eu_west_3: "EU (Paris)"
+ sa_east_1: "Sydamerika (São Paulo)"
us_east_1: "Östra USA (N. Virginia)"
- us_east_2: "Öster USA (Ohio)"
+ us_east_2: "Östra USA (Ohio)"
+ us_gov_east_1: "AWS GovCloud (Östra USA)"
+ us_gov_west_1: "AWS GovCloud (Västra USA)"
us_west_1: "Västra USA (N. Kalifornien)"
us_west_2: "Västra USA (Oregon)"
edit: "redigera rubrik och kategori för det här ämnet"
@@ -187,6 +209,7 @@ sv:
privacy: "Integritet"
tos: "Användarvillkor"
rules: "Regler"
+ conduct: "Uppförandekod"
mobile_view: "Mobilvy"
desktop_view: "Desktop-vy"
you: "Du"
@@ -200,11 +223,16 @@ sv:
every_hour: "varje timme"
daily: "dagligen"
weekly: "veckovis"
+ every_month: "varje månad"
+ every_six_months: "var sjätte månad"
max_of_count: "max av {{count}}"
alternation: "eller"
character_count:
one: "{{count}} tecken"
other: "{{count}} tecken"
+ related_messages:
+ title: "Relaterade meddelanden"
+ see_all: 'Visa
alla meddelanden från @%{username}...'
suggested_topics:
title: "Föreslagna ämnen"
pm_title: "Föreslagna meddelanden"
@@ -234,16 +262,39 @@ sv:
unbookmark: "Klicka för att radera alla bokmärken i ämnet"
bookmarks:
created: "du har bokmärkt detta inlägg"
+ not_bookmarked: "bokmärk detta inlägg"
+ created_with_reminder: "du har bokmärkt detta inlägg med en påminnelse vid %{date}"
remove: "Ta bort bokmärke"
+ confirm_clear: "Är du säker på att du vill radera alla dina bokmärken från ämnet?"
save: "Spara"
+ no_timezone: 'Du har ännu inte valt tidszon. Du kommer därför inte kunna sätta påminnelser. Ange en
i din profil .'
+ reminders:
+ at_desktop: "Nästa gång är jag vid datorn"
+ later_today: "Senare idag
{{date}}"
+ next_business_day: "Nästa arbetsdag
{{date}}"
+ tomorrow: "Imorgon
{{date}}"
+ next_week: "Nästa vecka
{{date}}"
+ next_month: "Nästa månad
{{date}}"
+ custom: "Anpassa datum och tid"
drafts:
+ resume: "Återuppta"
remove: "Ta bort"
+ new_topic: "Nytt utkast för ämne"
+ new_private_message: "Nytt utkast för privat meddelande"
+ topic_reply: "Svar på utkast"
abandon:
+ confirm: "Du har redan öppnat ett annat utkast för detta ämne. Är du säker på att du vill överge denna?"
yes_value: "Ja, överge"
- no_value: "nej, behåll"
+ no_value: "Nej, behåll"
topic_count_latest:
one: "Visa {{count}} nytt eller uppdaterat ämne"
other: "Visa {{count}} nya eller uppdaterade ämnen"
+ topic_count_unread:
+ one: "Visa {{count}}oläst ämne"
+ other: "Visa {{count}} olästa ämnen"
+ topic_count_new:
+ one: "Visa {{count}} nytt ämne"
+ other: "Visa {{count}} nya ämnen"
preview: "förhandsgranska"
cancel: "avbryt"
save: "Spara ändringar"
@@ -251,6 +302,8 @@ sv:
saved: "Sparat!"
upload: "Ladda upp"
uploading: "Laddar upp..."
+ uploading_filename: "Laddar upp:{{filename}}... "
+ clipboard: "urklipp"
uploaded: "Uppladdad!"
pasting: "Klistrar in..."
enable: "Aktivera"
@@ -264,47 +317,168 @@ sv:
banner:
close: "Stäng denna banderoll"
edit: "Redigera denna banderoll >>"
+ pwa:
+ install_banner: "Vill du
installera%{title} på denna enhet? "
choose_topic:
none_found: "Inga ämnen hittades."
+ title:
+ search: "Sök efter ett ämne"
+ placeholder: "skriv ämnets titel, url eller id här"
+ choose_message:
+ none_found: "Inga meddelanden hittades."
+ title:
+ search: "Sök efter ett meddelande"
+ placeholder: "skriv meddelandets titel, url eller id här"
review:
+ order_by: "Sortera på"
in_reply_to: "som svar till"
explain:
+ why: "förklara varför detta föremål hamnade i kön"
+ title: "Granskningsvärdering"
+ formula: "Formel"
+ subtotal: "Delsumma"
total: "Totalt"
+ min_score_visibility: "Lägsta poäng för att synas"
+ score_to_hide: "Poäng för att gömma inlägg"
+ take_action_bonus:
+ name: "agerade"
+ title: "När någon ur personalen väljer att agera får flaggningen en ökad bonus."
+ user_accuracy_bonus:
+ name: "användarnas noggrannhet"
+ title: "Användare vars flaggor historiskt har instämts ges en bonus."
+ trust_level_bonus:
+ name: "förtroendenivå"
+ title: "Granskade objekt skapade av användare med högre förtroendenivå har en högre poäng."
+ type_bonus:
+ name: "typ bonus"
+ title: "Vissa granskningsärenden kan tilldelas en bonus av personal för att ge dem en högre prioritet."
+ claim_help:
+ optional: "Du kan göra anspråk på detta för att förhindra andra från att granska det."
+ required: "Du måste göra anspråk innan du kan granska dem."
+ claimed_by_you: "Du har gjort anspråk på detta och kan nu granska det."
+ claimed_by_other: "Detta föremål kan enbart granskas av
{{username}} ."
+ claim:
+ title: "Gör anspråk på ämnet"
+ unclaim:
+ help: "ta bort detta anspråk"
+ awaiting_approval: "Inväntar godkännande"
delete: "Radera"
settings:
+ saved: "Sparad"
save_changes: "Spara ändringar"
title: "Inställningar"
+ priorities:
+ title: "Granskningsprioriteter"
+ moderation_history: "Modereringshistorik"
+ view_all: "Visa alla"
+ grouped_by_topic: "Gruppera på ämne"
+ none: "Det finns inga föremål för granskning."
+ view_pending: "visa avvaktande"
+ topic_has_pending:
+ one: "Detta ämne har
%{count} inlägg som inväntar godkännande"
+ other: "Detta ämne har
{{count}} inlägg som inväntar godkännande"
+ title: "Granska"
topic: "Ämne:"
+ filtered_topic: "Du har filtrerat för granskning av innehåll i ett enskilt ämne."
filtered_user: "Användare"
+ show_all_topics: "visa alla ämnen"
+ deleted_post: "(inlägg raderat)"
+ deleted_user: "(användare raderad)"
user:
username: "Användarnamn"
email: "E-post"
name: "Namn"
+ fields: "Fält"
+ user_percentage:
+ summary:
+ one: "{{agreed}},{{disagreed}},{{ignored}} ({{count}} flagga totalt)"
+ other: "{{agreed}},{{disagreed}},{{ignored}} ({{count}} flaggor totalt)"
+ agreed:
+ one: "{{count}}% instämmer"
+ other: "{{count}}% instämmer"
+ disagreed:
+ one: "{{count}}% bestrider"
+ other: "{{count}}% bestrider"
+ ignored:
+ one: "{{count}}% ignorerar"
+ other: "{{count}}% ignorerar"
topics:
topic: "Ämne"
+ reviewable_count: "Räkna"
+ reported_by: "Rapporterad av"
+ deleted: "[Ämne raderat]"
+ original: "(original ämne)"
+ details: "detaljer"
+ unique_users:
+ one: "%{count} användare"
+ other: "{{count}} användare"
+ replies:
+ one: "%{count} svar"
+ other: "{{count}} svar"
edit: "Redigera"
save: "Spara"
cancel: "Avbryt"
+ new_topic: "Godkännande av detta föremål skapar ett nytt ämne"
filters:
+ all_categories: "(alla kategorier)"
type:
title: "Typ"
+ all: "(alla typer)"
+ minimum_score: "Lägsta poäng:"
refresh: "Uppdatera"
+ status: "Status"
category: "Kategori"
orders:
+ priority: "Prioritet"
+ priority_asc: "Prioritet (omvänt)"
created_at: "Skapad den"
+ created_at_asc: "Skapad den (omvänt)"
+ priority:
+ title: "Lägsta prioritet"
+ low: "(valfri)"
+ medium: "Medium"
+ high: "Hög"
+ conversation:
+ view_full: "visa hela konversationen"
scores:
+ about: "Denna poäng beräknas på förtroendenivån av de som rapporterar, deras träffsäkerhet på deras tidigare flaggningar, samt på prioriteringen av föremålet som rapporteras."
+ score: "Poäng"
+ date: "Datum"
type: "Typ"
+ status: "Status"
+ submitted_by: "Inskickad av"
+ reviewed_by: "Granskad av"
statuses:
pending:
title: "Väntar"
+ approved:
+ title: "Godkända"
rejected:
title: "Avvisade"
+ ignored:
+ title: "Ignorerade"
+ deleted:
+ title: "Raderade"
+ reviewed:
+ title: "(alla granskade)"
+ all:
+ title: "(allting)"
types:
+ reviewable_flagged_post:
+ title: "Flaggat inlägg"
+ flagged_by: "Flaggad av"
+ reviewable_queued_topic:
+ title: "Köat ämne"
+ reviewable_queued_post:
+ title: "Köat inlägg"
reviewable_user:
title: "Användare"
approval:
title: "Inlägget behöver godkännande"
description: "Vi har mottagit ditt nya inlägg men det behöver bli godkänt av en moderator innan det kan visas. Ha tålamod."
+ pending_posts:
+ one: "Du har
%{count} inlägg som avvaktar."
+ other: "Du har
{{count}} inlägg som avvaktar."
ok: "OK"
user_action:
user_posted_topic: "
{{user}} skrev
ämnet "
@@ -348,11 +522,20 @@ sv:
make_user_group_owner: "Gör till ägare"
remove_user_as_group_owner: "Återkalla ägare"
groups:
+ member_added: "Tillagd"
+ member_requested: "Begärd vid"
add_members:
title: "Lägg till medlemmar"
+ description: "Hantera medlemskap för denna grupp"
usernames: "Användarnamn"
requests:
+ title: "Ansökningar"
reason: "Anledning"
+ accept: "Acceptera"
+ accepted: "accepterad"
+ deny: "Neka"
+ denied: "nekad"
+ undone: "förfrågan återkallad"
manage:
title: "Hantera"
name: "Namn"
@@ -378,9 +561,12 @@ sv:
details: "Detaljer"
from: "Från"
to: "Till"
+ public_admission: "Tillåt användare att gå med i grupp fritt (Kräver offentligt synliga grupper)"
+ public_exit: "Tillåt användare att fritt lämna grupp"
empty:
posts: "Det är inga kommentarer från medlemmar i denna grupp"
members: "Det finns inga medlemmar i denna grupp"
+ requests: "Det finns inga medlemsansökningar för denna grupp."
mentions: "Det finns inga omnämnande av denna grupp"
messages: "Det finns inga meddelanden för denna grupp."
topics: "Det finns inga trådar från medlemmar av denna grupp"
@@ -390,36 +576,68 @@ sv:
leave: "Lämna"
request: "Förfrågning"
message: "Nytt meddelande"
+ confirm_leave: "Är du säker på att du vill lämna den här gruppen?"
allow_membership_requests: "Tillåt användare att skicka medlemskapsförfrågan till gruppägare"
+ membership_request_template: "Anpassa mallen som visas när användare skickar en medlemsansökan"
+ membership_request:
+ submit: "Skicka ansökan"
+ title: "Ansökan att gå med i @%{group_name}"
+ reason: "Låt gruppägarna få veta varför du hör till denna grupp"
membership: "Medlemskap"
name: "Namn"
+ group_name: "Gruppnamn"
user_count: "Användare"
bio: "Om grupp"
selector_placeholder: "ange användarnamn"
owner: "ägare"
index:
title: "Grupper"
+ all: "Alla grupper"
empty: "Det finns inga synliga grupper"
+ filter: "Filtrerar på grupptyp"
+ owner_groups: "Grupper jag äger"
+ close_groups: "Stängda grupper"
+ automatic_groups: "Automatiska grupper"
automatic: "Automatisk"
+ closed: "Stängd"
public: "Publikt"
private: "Privat"
+ public_groups: "Offentliga grupper"
automatic_group: Automatisk grupp
+ close_group: Stängda grupper
my_groups: "Mina Grupper"
+ group_type: "Grupptyper"
is_group_user: "Medlem"
+ is_group_owner: "Ägare"
+ title:
+ one: "Grupp"
+ other: "Grupper"
activity: "Aktivitet"
members:
title: "Medlemmar"
filter_placeholder_admin: "användarnamn eller lösenord"
filter_placeholder: "användarnamn"
+ remove_member: "Ta bort medlem"
+ remove_member_description: "Ta bort
%{username} från denna grupp"
+ make_owner: "Gör till ägare"
+ make_owner_description: "Gör
%{username} till ägare av denna grupp"
+ remove_owner: "Ta bort som ägare"
+ remove_owner_description: "Ta bort
%{username} som ägare för denna grupp"
+ owner: "Ägare"
+ forbidden: "Du är inte tillåten att se medlemmarna."
topics: "Ämnen"
posts: "Inlägg"
mentions: "Omnämnaden"
messages: "Meddelanden"
+ notification_level: "Standard notifieringsnivå för gruppmeddelanden"
alias_levels:
+ mentionable: "Vem kan @omnämna denna grupp?"
+ messageable: "Vem kan meddela denna grupp?"
nobody: "Ingen"
only_admins: "Bara administratörer"
mods_and_admins: "Bara moderatorer och administratörer"
members_mods_and_admins: "Bara gruppmedlemmar, moderatorer och administratörer"
+ owners_mods_and_admins: "Bara gruppägare, moderatorer och administratörer"
everyone: "Alla"
notifications:
watching:
@@ -427,6 +645,7 @@ 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 notifieras med samtliga nya meddelanden i denna grupp men inte svaren på dessa meddelanden."
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."
@@ -435,6 +654,11 @@ sv:
description: "Du kommer att notifieras om någon nämner ditt @namn eller svarar dig."
muted:
title: "Tystad"
+ description: "Du kommer inte att bli meddelad om någonting för meddelanden i denna grupp.\n "
+ flair_url: "Avatar Flair Bild"
+ flair_url_placeholder: "(Valfritt) Bild-URL eller typ Awesome-klass"
+ flair_url_description: 'Använd fyrkantiga bilder som inte är mindre än 20px gånger 20px eller FontAwesome-ikoner (accepterade format: "fa-icon", "far fa-icon" eller "fab fa-icon").'
+ flair_bg_color: "Avatar Flair Bakgrundsfärg"
flair_bg_color_placeholder: "Hex färgvärde"
flair_color: "En Avatar som ändrar färg beroende på ljuset"
flair_color_placeholder: "(Valfritt) Hex-färgvärde"
@@ -453,6 +677,7 @@ sv:
"12": "Skickade föremål"
"13": "Inkorg"
"14": "Väntar"
+ "15": "Utkast"
categories:
all: "alla kategorier"
all_subcategories: "alla"
@@ -474,6 +699,13 @@ sv:
topic_sentence:
one: "%{count} ämne"
other: "%{count} ämnen"
+ topic_stat_sentence_week:
+ one: "%{count}nytt ämne under senaste veckan."
+ other: "%{count} nya ämnen under senaste veckan."
+ topic_stat_sentence_month:
+ one: "%{count} nytt ämne under senaste månaden."
+ other: "%{count} nya ämnen under senaste månaden."
+ n_more: "Kategorier (%{count} fler) ..."
ip_lookup:
title: Kolla upp IP-adress
hostname: Värdnamn
@@ -489,6 +721,8 @@ sv:
topics_entered: "besökta ämnen"
post_count: "# inlägg"
confirm_delete_other_accounts: "Är du säker på att du vill ta bort dessa här konton?"
+ powered_by: "använd
MaxMindDB "
+ copied: "kopierad"
user_fields:
none: "(välj ett alternativ)"
user:
@@ -497,6 +731,7 @@ sv:
mute: "Tysta"
edit: "Redigera inställningar"
download_archive:
+ button_text: "Ladda ner alla"
confirm: "Är du säker att du vill ladda ner dina inlägg?"
success: "Nedladdning påbörjad, du kommer bli notifierad via meddelande när processen är klar."
rate_limit_error: "Inlägg kan laddas ner en gång per dag, försök igen imorgon."
@@ -504,23 +739,42 @@ sv:
private_message: "Meddelande"
private_messages: "Meddelanden"
user_notifications:
+ ignore_duration_title: "Ignorera timer"
ignore_duration_username: "Användarnamn"
+ ignore_duration_when: "Varaktighet:"
+ ignore_duration_save: "Ignorera"
+ ignore_duration_note: "Vänligen notera att alla ignoreringar automatiskt tas bort efter det att ignoreringens varaktighet har löpt ut."
+ ignore_duration_time_frame_required: "Vänligen välj en tidsram"
+ ignore_no_users: "Du har inga ignorerade användare."
+ ignore_option: "Ignorerade"
+ ignore_option_title: "Du kommer inte ta emot notifieringar gällande denna användare och alla ämnen och svar kommer att gömmas."
+ add_ignored_user: "Lägg till..."
mute_option: "Tystad"
+ mute_option_title: "Du kommer inte erhålla några notifieringar från denna användaren."
normal_option: "Normal"
+ normal_option_title: "Du kommer att notifieras om denna användare svarar dig, citerar dig, eller nämner dig."
activity_stream: "Aktivitet"
preferences: "Inställningar"
feature_topic_on_profile:
+ open_search: "Välj ett nytt ämne"
+ title: "Välj ett ämne"
+ search_label: "Sök efter ämne genom titel"
save: "Spara"
clear:
title: "Rensa"
+ warning: "Är du säker på att vill rensa ditt utvalda ämne?"
+ profile_hidden: "Denna användarens offentliga profil är dold."
expand_profile: "Utvidga"
+ collapse_profile: "Förminska"
bookmarks: "Bokmärken"
bio: "Om mig"
+ timezone: "Tidszon"
invited_by: "Inbjuden Av"
trust_level: "Förtroendenivå"
notifications: "Notifieringar"
statistics: "Statistik"
desktop_notifications:
+ label: "Realtids notifieringar"
not_supported: "Aviseringar stöds inte i den här webbläsaren. Tyvärr!"
perm_default: "Sätt på aviseringar"
perm_denied_btn: "Behörighet saknas"
@@ -528,18 +782,27 @@ sv:
disable: "Inaktivera aviseringar"
enable: "Aktivera aviseringar"
each_browser_note: "Notera: Du behöver ändra denna inställning i varje webbläsare du använder."
+ consent_prompt: "Vill du ha realtidsnotifieringar när personer svarar på dina inlägg?"
dismiss: "Avfärda"
dismiss_notifications: "Avfärda alla"
dismiss_notifications_tooltip: "Markera alla olästa aviseringar som lästa"
first_notification: "Du har fått din första notifikation! Markera den för att börja."
+ dynamic_favicon: "Visa räknare i webbläsarens ikon"
+ theme_default_on_all_devices: "Gör detta till standardtema för alla mina enheter"
+ text_size_default_on_all_devices: "Gör detta till standardstorlek för text på alla mina enheter"
+ allow_private_messages: "Tillåt andra användare att skicka mig personliga meddelanden"
external_links_in_new_tab: "Öppna alla externa länkar i en ny flik"
enable_quoting: "Aktivera citatsvar för markerad text"
+ enable_defer: "Aktivera uppskjutning för att markera ämnen som olästa"
change: "ändra"
+ featured_topic: "Utvalt Ämne"
moderator: "{{user}} är en moderator"
admin: "{{user}} är en admin"
moderator_tooltip: "Den här användaren är moderator"
admin_tooltip: "Den här användaren är administrator"
+ silenced_tooltip: "Den här användaren är tystad."
suspended_notice: "Den här användaren är avstängd till {{date}}."
+ suspended_permanently: "Den här användaren är utesluten."
suspended_reason: "Anledning:"
github_profile: "Github"
email_activity_summary: "Aktivitetssammanfattning"
@@ -553,6 +816,7 @@ sv:
individual_no_echo: "Jag vill ha ett mail när nya poster publiceras"
many_per_day: "Skicka ett e-postmeddelande för varje nytt inlägg (ungefär {{dailyEmailEstimate}} per dag)"
few_per_day: "Skicka ett e-postmeddelande för varje nytt inlägg (ungefär 2 per dag)"
+ warning: "Funktion för e-postlista aktiverad. Inställningar för e-postnotifiering är åsidosatta."
tag_settings: "Taggar"
watched_tags: "Bevakade"
watched_tags_instructions: "Du kommer automatiskt att bevaka alla ämnen med de här taggarna. Du blir notifierad om alla nya inlägg och ämnen, och en räknare över antalet nya inlägg visas bredvid ämnet. "
@@ -569,21 +833,30 @@ sv:
watched_first_post_tags: "Bevakar första inlägget"
watched_first_post_tags_instructions: "Du kommer att bli notifierad om första inlägget i varje nytt ämne med dessa taggar."
muted_categories: "Tystad"
+ muted_categories_instructions: "Du kommer inte att notifieras om någonting gällande nya ämnen i dessa kategorier, och de kommer inte dyka upp på kategorier eller senaste sidorna. "
+ muted_categories_instructions_dont_hide: "Du kommer inte att notifieras om någonting gällande nya ämnen i dessa kategorier."
+ no_category_access: "Som moderator har du begränsat tillträde till kategorier, spara är avstängt."
delete_account: "Radera mitt konto"
delete_account_confirm: "Är du säker på att du vill ta bort ditt konto permanent? Denna åtgärd kan inte ångras!"
deleted_yourself: "Ditt konto har tagits bort."
+ delete_yourself_not_allowed: "Vänligen kontakta någon ur personalen om du önskar att ditt konto ska raderas."
unread_message_count: "Meddelanden"
admin_delete: "Radera"
users: "Användare"
muted_users: "Tystat"
muted_users_instructions: "Undanta alla notiser från dessa användare."
+ ignored_users: "Ignorerade"
+ ignored_users_instructions: "Tysta alla inlägg och notifieringar från dessa användare."
tracked_topics_link: "Visa"
automatically_unpin_topics: "Avklistra automatiskt ämnen när jag når botten."
apps: "Appar"
revoke_access: "Återkalla åtkomst"
undo_revoke_access: "Ångra återkallelse av åtkomst"
api_approved: "Godkända:"
+ api_last_used_at: "Senast använd vid:"
theme: "Tema"
+ home: "Standard hemsida"
+ staged: "Arrangerad"
staff_counters:
flags_given: "hjälpsamma flaggor"
flagged_posts: "flaggade inlägg"
@@ -603,12 +876,14 @@ sv:
select_all: "Markera alla"
tags: "Taggar"
preferences_nav:
+ account: "Konto"
profile: "Profil"
emails: "E-post"
notifications: "Notifieringar"
categories: "Kategorier"
users: "Användare"
tags: "Taggar"
+ interface: "Gränssnitt"
apps: "Appar"
change_password:
success: "(e-post skickat)"
@@ -619,20 +894,67 @@ sv:
choose_new: "Välj ett nytt lösenord"
choose: "Välj ett lösenord"
second_factor_backup:
+ title: "Två faktoriga reservkoder"
regenerate: "Regenerera"
disable: "Inaktivera"
enable: "Aktivera"
+ enable_long: "Aktivera reservkoder"
+ manage: "Hantera reservkoder. Du har
{{count}} reservkod kvar."
+ copied_to_clipboard: "Kopierad till urklipp"
+ copy_to_clipboard_error: "Fel vid kopiering av data till urklipp"
+ remaining_codes: "Du har
{{count}} reservkoder kvar."
+ use: "Använd en reservkod"
+ enable_prerequisites: "Då måste aktivera en primär andra faktor innan du skapar reservkoder."
+ codes:
+ title: "Reservkoder skapade"
+ description: "Var och en av dessa reservkoder kan enbart användas en gång. Förvara dem någonstans säkert men tillgängligt."
second_factor:
+ title: "Tvåfaktor Autentisering"
+ enable: "Hantera Tvåfaktor autentisering"
+ forgot_password: "Glömt lösenord?"
+ confirm_password_description: "Vänligen bekräfta till lösenord för att fortsätta"
name: "Namn"
+ label: "kod"
+ rate_limit: "Vänligen vänta innan du testar en annan autentiseringskod."
+ enable_description: |
+ Scanna denna QR kod i en app som stöds (
Android –
iOS ) och ange din autentiseringskod.
+ disable_description: "Vänligen ange din autentiseringskod från din app"
+ show_key_description: "Ange manuellt"
+ short_description: |
+ Skydda ditt konto med engångssäkerhetskoder.
+ extended_description: |
+ Tvåfaktorsautentisering lägger till extra säkerhet till ditt konto genom att begära ett engångsbevis utöver ditt lösenord.
+ Bevis kan skapas på
Android - och
iOS -enheter.
+ oauth_enabled_warning: "Vänligen notera att inloggning genom sociala medier kommer att stängas av när tvåfaktors autentisering har aktiverats för ditt konto."
+ use: "Använd autentiseringsappen"
+ enforced_notice: "Du behöver aktivera tvåfaktor autentisering innan du kan besöka denna sida."
+ disable: "inaktivera"
+ disable_title: "Inaktivera tvåfaktor"
+ disable_confirm: "Är du säker på att du vill inaktivera alla tvåfaktorer?"
edit: "Redigera"
+ edit_title: "Redigera tvåfaktor"
+ edit_description: "Tvåfaktor namn"
+ enable_security_key_description: "När du har din fysiska säkerhetsnyckel förberedd, tryck på registrera-knappen nedan."
+ totp:
+ title: "Bevisbaserade autentiserare"
+ add: "Ny autentiserare"
+ default_name: "Min autentiserare"
security_key:
register: "Registrera"
+ title: "Säkerhetsnycklar"
+ add: "Registrera säkerhetsnyckel"
+ default_name: "Huvud säkerhetsnyckel"
+ not_allowed_error: "Registreringsprocessen för säkerhetsnyckel dröjde antingen för länge eller avbröts."
+ already_added_error: "Du har redan registrerat denna säkerhetsnyckel.\nDu behöver inte registrera den igen."
+ edit: "Ändra säkerhetsnyckel"
+ edit_description: "Namn på säkerhetsnyckel"
delete: "Radera"
change_about:
title: "Ändra Om Mig"
error: "Ett fel inträffade vid ändringen av det här värdet."
change_username:
title: "Byt användarnamn"
+ confirm: "Är du absolut säker på att du vill ändra ditt användarnamn?"
taken: "Tyvärr, det användarnamnet är taget."
invalid: "Det användarnamnet är ogiltigt. Det får bara innehålla siffror och bokstäver"
change_email:
@@ -640,21 +962,33 @@ sv:
taken: "Tyvärr den e-postadressen är inte tillgänglig."
error: "Det uppstod ett problem under bytet av din e-post. Är kanske adressen redan upptagen?"
success: "Vi har skickat e-post till den adressen. Var god följ bekräftelseinstruktionerna."
+ success_staff: "Vi har skickat e-post till din nuvarande adress. Vänligen följ instruktionerna för konfirmering."
change_avatar:
title: "Ändra din profilbild"
gravatar: "
Gravatar , baserat på"
gravatar_title: "Byt din avatar på Gravatars hemsida"
+ gravatar_failed: "Vi hittade ingen Gravatar med den e-postadressen."
refresh_gravatar_title: "Uppdatera din Gravatar"
letter_based: "Profilbild tilldelad av systemet"
uploaded_avatar: "Anpassad bild"
uploaded_avatar_empty: "Lägg till en anpassad bild"
upload_title: "Ladda upp din bild"
image_is_not_a_square: "Varning: vi beskar din bild; bredden och höjden var inte samma."
+ change_profile_background:
+ title: "Profilrubrik"
+ instructions: "Profilrubriken kommer att centreras samt ha en standardbredd på 1110px."
change_card_background:
title: "Användarkortets bakgrund"
instructions: "Bakgrundsbilder kommer att vara centrerade och ha en standardbredd på 590 px."
+ change_featured_topic:
+ title: "Utvalt Ämne"
+ instructions: "En länk till detta ämne kommer att finnas från ditt användarkort samt profil."
email:
title: "E-post"
+ primary: "Primär e-post"
+ secondary: "Sekundär e-post"
+ no_secondary: "Inga sekundära e-post"
+ sso_override_instructions: "E-post kan uppdateras från SSO leverantören."
instructions: "Visas aldrig publikt"
ok: "Vi skickar e-post till dig för bekräftelse"
invalid: "Vänligen ange en giltig e-postadress"
@@ -664,8 +998,15 @@ sv:
one: "Vi skickar bara e-post om du inte synts till den senaste minuten."
other: "Vi skickar bara e-post om du inte synts till de senaste {{count}} minuterna."
associated_accounts:
+ title: "Associerade konton"
+ connect: "Koppla"
revoke: "Återkalla"
cancel: "Avbryt"
+ not_connected: "(inte kopplade)"
+ confirm_modal_title: "Koppla %{provider} konto"
+ confirm_description:
+ account_specific: "Ditt %{provider} konto '%{account_description}' kommer att användas för autentisering."
+ generic: "Ditt %{provider} konto kommer att användas för autentisering."
name:
title: "Namn"
instructions: "ditt fullständiga namn (tillval)"
@@ -691,8 +1032,19 @@ sv:
password_confirmation:
title: "Lösenord igen"
auth_tokens:
+ title: "Senaste använda enheter"
ip: "IP"
details: "Detaljer"
+ log_out_all: "Logga ut alla"
+ active: "aktiv nu"
+ not_you: "Inte du?"
+ show_all: "Visa alla ({{count}})"
+ show_few: "Visa färre"
+ was_this_you: "Var detta du?"
+ was_this_you_description: "Om det inte var du, rekommenderar vi att du ändrar ditt lösenord och loggar ut överallt."
+ browser_and_device: "{{browser}} på {{device}}"
+ secure_account: "Säkra mitt konto"
+ latest_post: "Ditt senaste postade..."
last_posted: "Senaste inlägg"
last_emailed: "Senast mailad"
last_seen: "Sedd"
@@ -701,8 +1053,18 @@ sv:
location: "Plats"
website: "Webbplats"
email_settings: "E-post"
+ hide_profile_and_presence: "Dölj min offentliga profil och närvarofunktioner"
+ enable_physical_keyboard: "Aktivera stöd för fysiskt tangentbord på iPad"
text_size:
+ title: "Textstorlek"
+ smaller: "Mindre"
normal: "Normal"
+ larger: "Större"
+ largest: "Störst"
+ title_count_mode:
+ title: "Titel på bakgrundssidan visar antalet:"
+ notifications: "Nya notifieringar"
+ contextual: "Nytt sidinnehåll"
like_notification_frequency:
title: "Notifiera vid gillning"
always: "Alltid"
@@ -715,13 +1077,17 @@ sv:
always: "alltid"
never: "aldrig"
email_digests:
+ title: "Skicka mig en e-postsammanfattning av populära ämnen och inlägg när jag inte besökt sidan"
every_30_minutes: "var 30:e minut"
every_hour: "varje timma"
daily: "dagligen"
weekly: "veckovis"
+ every_month: "varje månad"
+ every_six_months: "var sjätte månad"
email_level:
title: "Sänd mig e-post när någon citerar mig, besvarar mitt inlägg, nämner mitt @användarnamn eller bjuder in mig till ett ämne."
always: "alltid"
+ only_when_away: "enbart när jag är borta"
never: "aldrig"
email_messages_level: "Sänd mig e-post när någon skickar mig ett meddelande"
include_tl0_in_digests: "Inkludera innehåll från nya användare i sammanfattningsmeddelanden via e-post"
@@ -752,6 +1118,8 @@ sv:
search: "sök efter inbjudningar..."
title: "Inbjudningar"
user: "Inbjuden Användare"
+ sent: "Senast skickade"
+ none: "Inga inbjudningar att visa."
truncated:
one: "Visar den första inbjudningen."
other: "Visar de första {{count}} inbjudningarna."
@@ -767,8 +1135,12 @@ sv:
expired: "Denna inbjudan har gått ut."
rescind: "Ta bort"
rescinded: "Inbjudan borttagen"
+ rescind_all: "Ta bort alla utgångna inbjudningar"
+ rescinded_all: "Alla utgångna inbjudningar har tagits bort!"
+ rescind_all_confirm: "Är du säker på att du vill ta bort alla utgångna inbjudningar?"
reinvite: "Skicka inbjudan igen"
reinvite_all: "Skicka alla inbjudningar igen"
+ reinvite_all_confirm: "Är du säker på att du vill skicka om alla inbjudningar?"
reinvited: "Inbjudan skickad"
reinvited_all: "Alla inbjudningar har skickats igen!"
time_read: "Lästid"
@@ -783,6 +1155,7 @@ sv:
text: "Massinbjudan från fil"
success: "Filen laddades upp, du blir underrättad via meddelande när processen är klar"
error: "Tyvärr, filen bör vara i CSV-format."
+ confirmation_message: "Du är på väg att e-posta inbjudningar till alla personer i den uppladdade filen."
password:
title: "Lösenord"
too_short: "Ditt lösenord är för kort."
@@ -795,15 +1168,25 @@ sv:
title: "Sammanfattning"
stats: "Statistik"
time_read: "lästid"
+ recent_time_read: "senaste lästid"
topic_count:
one: "ämnet skapades"
other: "ämnen skapades"
post_count:
one: "inlägg skapat"
other: "inlägg skapade"
+ likes_given:
+ one: "tilldelad"
+ other: "tilldelade"
+ likes_received:
+ one: "mottagen"
+ other: "mottagna"
days_visited:
one: "dag besökt"
other: "dagar besökta"
+ topics_entered:
+ one: "ämne visat"
+ other: "ämnen visade"
posts_read:
one: "inlägg läst"
other: "inlägg lästa"
@@ -822,9 +1205,10 @@ sv:
top_links: "Topplänkar"
no_links: "Inga länkar ännu."
most_liked_by: "Mest gillad av"
- most_liked_users: "Mest gillad"
+ most_liked_users: "Gillar mest"
most_replied_to_users: "Mest svarad till"
no_likes: "Inga gillningar ännu."
+ top_categories: "Topp kategorier"
topics: "Ämnen"
replies: "Svar"
ip_address:
@@ -875,6 +1259,14 @@ sv:
enabled: "Webbplatsen är i skrivskyddat läge. Du kan fortsätta bläddra på sidan, men att skriva inlägg, gilla och andra interaktioner är inaktiverade för tillfället."
login_disabled: "Det går inte att logga in medan siten är i skrivskyddat läge."
logout_disabled: "Det går inte att logga ut medan webbplatsen är i skrivskyddat läge. "
+ too_few_topics_and_posts_notice: "Låt oss
påbörja diskussionen! Det finns
%{currentTopics} ämnen och
%{currentPosts} inlägg. Besökare behöver mer att läsa och svara på – vi rekommenderar åtminstone
%{requiredTopics} ämnen och
%{requiredPosts} inlägg. Enbart personal kan se detta meddelande."
+ too_few_topics_notice: "Låt oss
påbörja diskussionen! Det finns
%{currentTopics} ämnen. Besökare behöver mer att läsa och svara på – vi rekommenderar åtminstone
%{requiredTopics} ämnen. Enbart personal kan se detta meddelande."
+ too_few_posts_notice: "Låt oss
påbörja diskussionen! Det finns
%{currentPosts} inlägg. Besökare behöver mer att läsa och svara på – vi rekommenderar åtminstone
%{requiredPosts} inlägg. Enbart personal kan se detta meddelande."
+ logs_error_rate_notice:
+ reached_hour_MF: "
{relativeAge} -
{rate, plural, one {# error/hour} other {# errors/hour}} har uppnått webbplatsinställningarnas gräns på {limit, plural, one {# error/hour} other {# errors/hour}}."
+ reached_minute_MF: "
{relativeAge} –
{rate, plural, one {# error/minute} other {# errors/minute}} har uppnått weplatsinställningarnas gräns på {limit, plural, one {# error/minute} other {# errors/minute}}."
+ exceeded_hour_MF: "
{relativeAge} –
{rate, plural, one {# error/hour} other {# errors/hour}} har överskridit webbplatsinställningarnas gräns på {limit, plural, one {# error/hour} other {# errors/hour}}."
+ exceeded_minute_MF: "
{relativeAge} –
{rate, plural, one {# error/minute} other {# errors/minute}} har överskridit webbplatsinställningarnas gräns på {limit, plural, one {# error/minute} other {# errors/minute}}."
learn_more: "lär dig mer..."
all_time: "totalt"
all_time_desc: "totalt antal ämnen skapade"
@@ -890,6 +1282,9 @@ sv:
unmute: Avtysta
last_post: Publicerad
time_read: Läst
+ time_read_recently: "%{time_read} nyligen"
+ time_read_tooltip: "%{time_read} total lästid"
+ time_read_recently_tooltip: "%{time_read} total lästid (%{recent_time_read} under de senaste 60 dagarna)"
last_reply_lowercase: senaste svar
replies_lowercase:
one: svar
@@ -899,6 +1294,8 @@ sv:
hide_session: "Påminn mig imorgon"
hide_forever: "nej tack"
hidden_for_session: "Ok, jag frågar dig imorgon. Du kan alltid använda 'Logga in' för att skapa ett konto, också. "
+ intro: "Hejsan! Det verkar som du gillar diskussionen, men du har ännu inte registrerat dig för ett konto."
+ value_prop: "När du skapar ett konto så kommer vi ihåg precis vad du har läst, så att du alltid kan komma tillbaka precis där du lämnade oss. Du kan också få notifieringar, här och via e-post, närhelst någon svarar dig. Du kan också gilla inlägg för att sprida kärlek. :heartpulse: "
summary:
enabled_description: "Sammanfattning över de inlägg som användarna tycker är mest intressanta."
description: "Det finns
{{replyCount}} svar."
@@ -912,6 +1309,9 @@ sv:
disable: "Visa raderade inlägg"
private_message_info:
title: "Meddelande"
+ invite: "Bjud in andra ..."
+ edit: "Lägg till eller ta bort ..."
+ leave_message: "Vill du verkligen lämna detta meddelande?"
remove_allowed_user: "Vill du verkligen ta bort {{name}} från det här meddelandet?"
remove_allowed_group: "Vill du verkligen ta bort {{name}} från det här meddelandet?"
email: "E-post"
@@ -922,6 +1322,7 @@ sv:
trust_level: "Förtroendenivå"
search_hint: "användarnamn, e-post eller IP-adress"
create_account:
+ disclaimer: "Genom att registrera dig godkänner du
integritetspolicyn och
användarvillkoren ."
title: "Registrera nytt konto"
failed: "Något gick fel, kanske är denna e-post redan registrerad, försök glömt lösenordslänken"
forgot_password:
@@ -935,18 +1336,41 @@ sv:
complete_email_found: "Vi hittade ett konto som matchade
%{email} , du kommer snart att få ett e-postmeddelande med instruktioner om hur du ska återställa ditt lösenord."
complete_username_not_found: "Det finns inget konto som matchar användarnamnet
%{username} "
complete_email_not_found: "Det finns inget konto som matchar
%{email} "
+ help: "Får du ingen e-post? Kontrollera först din skräppostmapp.
Är du inte säker på vilken e-postadress du använde? Ange en e-postadress så meddelar vi dig om den finns här.
Om du inte längre har tillgång till e-postadressen för ditt konto, vänligen kontakta vår trevliga personal.
"
button_ok: "OK"
+ button_help: "Hjälp"
email_login:
+ link_label: "E-posta mig en inloggningslänk"
+ button_label: "per e-post"
+ complete_username: "Om ett konto matchar användarnamnet
%{username} , bör du inom kort få ett e-postmeddelande med en inloggningslänk."
+ complete_email: "Om ett konto matchar
%{email} , bör du inom kort få ett e-postmeddelande med en inloggningslänk."
+ complete_username_found: "Vi hittade ett konto som matchade användarnamnet
%{username} , du kommer snart att få ett e-postmeddelande med länk för inloggning inom kort."
+ complete_email_found: "Vi hittade ett konto som matchade
%{email} , du kommer snart att få ett e-postmeddelande med länk för inloggning inom kort."
complete_username_not_found: "Det finns inget konto som matchar användarnamnet
%{username} "
complete_email_not_found: "Det finns inget konto som matchar
%{email} "
confirm_title: "Fortsätt till %{site_name}"
+ logging_in_as: "Loggar in som %{email}"
+ confirm_button: Avsluta inloggning
login:
title: "Logga in"
username: "Användare"
password: "Lösenord"
+ second_factor_title: "Tvåfaktor Autentisering"
+ second_factor_description: "Vänligen ange din autentiseringskod från din app:"
+ second_factor_backup: "Logga in genom reservkod"
+ second_factor_backup_title: "Två faktorig reserv"
+ second_factor_backup_description: "Vänligen ange en av dina reservkoder:"
+ second_factor: "Logga in genom autentiseringsappen"
+ security_key_description: "När du har din fysiska säkerhetsnyckel förberedd, tryck på knappen autentisera med säkerhetsnyckel nedanför."
+ security_key_alternative: "Prova ett annat sätt"
+ security_key_authenticate: "Autentisera med säkerhetsnyckel"
+ security_key_not_allowed_error: "Processen för autentiseringssäkerhetsnyckel dröjde antingen för länge eller avbröts."
+ security_key_no_matching_credential_error: "Inga matchande referenser kunde hittas i den medföljande säkerhetsnyckeln."
+ security_key_support_missing_error: "Din nuvarande enhet eller webbläsare stöder inte användning av säkerhetsnycklar. Använd en annan metod."
email_placeholder: "e-post eller användarnamn"
caps_lock_warning: "Caps Lock är aktiverad"
error: "Okänt fel"
+ cookies_error: "Din webbläsare verkar ha cookies inaktiverade. Du kanske inte kan logga in utan att aktivera dem först."
rate_limit: "Var god vänta innan du försöker logga in igen."
blank_username: "Vänligen ange din mail eller ditt användarnamn"
blank_username_or_password: "Vänligen ange din e-post eller användarnamn och lösenord."
@@ -961,11 +1385,13 @@ sv:
not_allowed_from_ip_address: "Du kan inte logga in från den IP-adressen"
admin_not_allowed_from_ip_address: "Du kan inte logga in som admin från den IP-adressen."
resend_activation_email: "Klicka här för att skicka aktiveringsbrevet igen."
+ omniauth_disallow_totp: "Ditt konto har tvåfaktor autentisering aktiverat. Logga in med ditt lösenord."
resend_title: "Skicka aktiveringsmail igen"
change_email: "Ändra mailadress"
provide_new_email: "Ange en ny adress så skickar vi ett nytt bekräftelsemail."
submit_new_email: "Ändra mailadress"
sent_activation_email_again: "Vi har skickat ännu ett aktiveringsmail till dig via
{{currentEmail}} . Det kan ta ett par minuter för det att komma fram; var noga med att kolla din skräppost."
+ sent_activation_email_again_generic: "Vi skickade ett nytt aktiveringsmeddelande. Det kan ta några minuter innan det kommer; kontrollera din skräppostmapp."
to_continue: "Var vänligen och logga in"
preferences: "Du behöver logga in för att ändra dina användarpreferenser."
forgot: "Jag kommer inte ihåg mina kontouppgifter"
@@ -977,11 +1403,20 @@ sv:
name: "Twitter"
title: "med Twitter"
instagram:
+ name: "Instagram"
title: "med Instagram"
facebook:
+ name: "Facebook"
title: "med Facebook"
github:
+ name: "GitHub"
title: "med GitHub"
+ discord:
+ name: "Discord"
+ title: "med Discord"
+ second_factor_toggle:
+ totp: "Använd en autentiseringsapp istället"
+ backup_code: "Använd en reservkod istället"
invites:
accept_title: "Inbjudan"
welcome_to: "Välkommen till %{site_name}!"
@@ -992,28 +1427,76 @@ sv:
success: "Ditt konto har skapats och du är nu inloggad."
name_label: "Namn"
password_label: "Välj lösenord"
+ optional_description: "(valfri)"
password_reset:
continue: "Fortsätt till %{site_name}"
emoji_set:
apple_international: "Apple/International"
google: "Google"
twitter: "Twitter"
+ emoji_one: "JoyPixels (tidigare EmojiOne)"
win10: "Win10"
+ google_classic: "Google Classic"
+ facebook_messenger: "Facebook Messenger"
category_page_style:
categories_only: "Endast kategorier"
categories_with_featured_topics: "Kategorier med utvalda ämnen"
categories_and_latest_topics: "Kategorier med senaste ämnen"
+ categories_and_top_topics: "Kategorier och toppämnen"
+ categories_boxes: "Boxar med underkategorier"
+ categories_boxes_with_topics: "Boxar med utvalda ämnen"
shortcut_modifier_key:
shift: "Shift"
ctrl: "Ctrl"
alt: "Alt"
+ enter: "Ange"
conditional_loading_section:
loading: Laddar...
+ category_row:
+ topic_count: "{{count}} ämnen i denna kategorin"
+ select_kit:
+ default_header_text: Välj...
+ no_content: Inga matchningar funna
+ filter_placeholder: Sök...
+ filter_placeholder_with_any: Sök eller skapa...
+ create: "Skapa: '{{content}}'"
+ max_content_reached:
+ one: "Du kan endast välja{{count}} föremål."
+ other: "Du kan endast välja {{count}} föremål."
+ min_content_not_reached:
+ one: "Välj åtminstone {{count}} föremål."
+ other: "Välj åtminstone {{count}} föremål."
date_time_picker:
from: Från
to: Till
+ errors:
+ to_before_from: "Till datum måste vara senare än från datum."
emoji_picker:
+ filter_placeholder: Sök efter emoji
+ smileys_&_emotion: Smileys och känslor
+ people_&_body: Människor och kropp
+ animals_&_nature: Djur och natur
+ food_&_drink: Mat och dryck
+ travel_&_places: Resor och platser
+ activities: Aktiviteter
+ objects: Objekt
+ symbols: Symboler
flags: Flaggor
+ custom: Anpassa emojis
+ recent: Senaste använda
+ default_tone: Ingen hudton
+ light_tone: Ljus hudton
+ medium_light_tone: Medium ljus hudton
+ medium_tone: Medium hudton
+ medium_dark_tone: Medium mörk hudton
+ dark_tone: Mörk hudton
+ shared_drafts:
+ title: "Delade utkast"
+ notice: "Detta ämne är enbart synligt för de som kan se kategorin
{{category}} ."
+ destination_category: "Mål kategori"
+ publish: "Publicera delat utkast"
+ confirm_publish: "Är du säker på att du vill publicera detta utkast?"
+ publishing: "Publicerar ämne..."
composer:
emoji: "Emoji :)"
more_emoji: "mer..."
@@ -1028,32 +1511,47 @@ sv:
saved_local_draft_tip: "sparat lokalt"
similar_topics: "Ditt ämne liknar..."
drafts_offline: "utkast offline"
+ edit_conflict: "redigera konflikter"
+ group_mentioned_limit: "
Varning! Du omnämnde
{{group}} , denna grupp har fler medlemmar än vad administratorn tillåter för omnämningar. Begränsningsregeln för omnämningar är satt till {{max}}användare. Ingen kommer därför att notifieras."
group_mentioned:
one: "Genom att nämna {{group}}, så kommer du att notifiera
%{count} person – är du säker?"
other: "Genom att nämna {{group}}, så kommer du att notifiera
{{count}} medlemmar – är du säker?"
cannot_see_mention:
category: "Du nämnde {{username}} men hen kommer inte få någon notifikation för hen har inte tillgång till denna kategori. Du behöver lägga till hen till en grupp som har tillgång till den här kategorin."
private: "Du nämnde {{username}} men hen kommer inte få någon notifikation eftersom hen inte kan se detta personliga meddelande. Du behöver bjuda in hen till detta PM."
+ duplicate_link: "Det verkar som din länk till
{{domain}} redan har lagts in i ämnet av
@{{username}} i
ett svar den {{ago}} – är du säker på att du vill lägga upp den igen?"
+ reference_topic_title: "SV: {{title}}"
error:
title_missing: "Du måste ange en rubrik"
title_too_short: "Rubriken måste vara minst {{min}} tecken lång."
title_too_long: "Rubriken får inte vara längre än {{max}} tecken"
+ post_missing: "Inlägg får inte vara tomt"
post_length: "Inlägg måste vara minst {{min}} tecken långa."
+ try_like: "Har du provat {{heart}} knappen?"
category_missing: "Du måste välja en kategori"
+ tags_missing: "Du måste välja åtminstone {{count}} taggar"
+ topic_template_not_modified: "Lägg till detaljer och specifikationer till ditt ämne genom att redigera ämnesmallen."
save_edit: "Spara ändring"
+ overwrite_edit: "Överskriv redigering"
reply_original: "Svara på ursprungsämnet"
reply_here: "Svara här"
reply: "Svara"
cancel: "Avbryt"
create_topic: "Skapa ämne"
create_pm: "Nytt meddelande"
+ create_whisper: "Viska"
+ create_shared_draft: "Skapa delat utkast"
+ edit_shared_draft: "Redigera delat utkast"
title: "eller tryck Ctrl+Enter"
users_placeholder: "Lägg till en användare"
title_placeholder: "Vad handlar ämnet om i en kort mening?"
title_or_link_placeholder: "Skriv in en titel, eller klistra in en länk här"
edit_reason_placeholder: "varför redigerar du?"
topic_featured_link_placeholder: "Ange länken som visas med titeln"
+ remove_featured_link: "Ta bort länk från ämne."
reply_placeholder: "Skriv här. Använd Markdown, BBCode eller HTML för formattering. Släpp eller klistra in bilder."
+ reply_placeholder_no_images: "Skriv här. Använd Markdown, BBCode eller HTML för formattering. "
+ reply_placeholder_choose_category: "Välj en kategori innan du skriver här."
view_new_post: "Visa ditt nya inlägg."
saving: "Sparar"
saved: "Sparat!"
@@ -1072,6 +1570,7 @@ sv:
link_description: "skriv en länkbeskrivning här"
link_dialog_title: "Infoga Hyperlänk"
link_optional_text: "valfri rubrik"
+ link_url_placeholder: "Klistra in en URL eller skriv för att söka ämnen"
quote_title: "Citat"
quote_text: "Citat"
code_title: "Förformatterad text"
@@ -1082,7 +1581,13 @@ sv:
olist_title: "Numrerad lista"
ulist_title: "Punktlista"
list_item: "Listobjekt"
+ toggle_direction: "Växla riktning"
help: "Markdown redigeringshjälp"
+ collapse: "minimera skaparpanelen"
+ open: "öppna skaparpanelen"
+ abandon: "stäng skaparen och kasta utkast"
+ enter_fullscreen: "skaparen i helskärm"
+ exit_fullscreen: "avsluta helskärmsläge för skaparen"
modal_ok: "OK"
modal_cancel: "Avbryt"
cant_send_pm: "Tyvärr, du kan inte skicka ett meddelande till %{username}."
@@ -1092,23 +1597,106 @@ sv:
admin_options_title: "Valfria personalinställningar för detta ämne"
composer_actions:
reply: Svara
+ draft: Utkast
edit: Redigera
+ reply_to_post:
+ label: "Svara på inlägg %{postNumber} av %{postUsername}"
+ desc: Svara på ett specifikt inlägg
+ reply_as_new_topic:
+ label: Svara som länkat ämne
+ desc: Skapa ett nytt ämne länkat till detta ämne
+ reply_as_private_message:
+ label: Nytt meddelande
+ desc: Skapa ett nytt personligt meddelande
+ reply_to_topic:
+ label: Svara på ämne
+ desc: "Svara på ämnet, inte ett specifikt inlägg"
+ toggle_whisper:
+ label: Växla viskning
+ desc: Viskningar är enbart synliga för personal
create_topic:
label: "Nytt ämne"
+ shared_draft:
+ label: "Delat utkast"
+ desc: "Utarbeta ett ämne som endast är synligt för personalen"
+ toggle_topic_bump:
+ label: "Växla ämnes knuff"
+ desc: "Svara utan att ändra senaste svarsdatum"
notifications:
+ tooltip:
+ regular:
+ one: "%{count}oläst notifikation"
+ other: "{{count}} olästa notifikationer"
+ message:
+ one: "%{count} oläst meddelande"
+ other: "{{count}} olästa meddelanden"
title: "notiser från @namn-omnämnanden, svar på dina inlägg och ämnen, meddelanden, etc"
none: "Kan inte ladda notiser just nu."
empty: "Inga notifieringar hittades."
+ post_approved: "Ditt inlägg blev godkänt"
+ reviewable_items: "objekt som kräver granskning"
+ mentioned: "
{{username}} {{description}}"
+ group_mentioned: "
{{username}} {{description}}"
+ quoted: "
{{username}} {{description}}"
+ replied: "
{{username}} {{description}}"
+ posted: "
{{username}} {{description}}"
+ edited: "
{{username}} {{description}}"
+ liked: "
{{username}} {{description}}"
+ liked_2: "
{{username}},{{username2}} {{description}}"
+ liked_many:
+ one: "
{{username}},{{username2}} och %{count} annan {{description}}"
+ other: "
{{username}},{{username2}} och {{count}} andra {{description}}"
+ liked_consolidated_description:
+ one: "gillade {{count}} av dina inlägg"
+ other: "gillade {{count}} av dina inlägg"
+ liked_consolidated: "
{{username}} {{description}}"
+ private_message: "
{{username}} {{description}}"
+ invited_to_private_message: "
{{username}} {{description}}"
+ invited_to_topic: "{{username}} {{description}}"
+ invitee_accepted: "{{username}} accepterade din inbjudan"
+ moved_post: "{{username}} flyttade {{description}}"
+ linked: "{{username}} {{description}}"
granted_badge: "Förtjänade '{{description}}'"
+ topic_reminder: "{{username}} {{description}}"
+ watching_first_post: "Nytt ämne {{description}}"
+ membership_request_accepted: "Medlemskap godkänt i '{{group_name}}'"
+ membership_request_consolidated: "{{count}} öppna medlemsansökningar för '{{group_name}}'"
+ group_message_summary:
+ one: "{{count}} meddelande i din {{group_name}} inkorg"
+ other: "{{count}} meddelanden i din {{group_name}}-inkorg"
popup:
mentioned: '{{username}} nämnde dig i "{{topic}}" - {{site_title}}'
group_mentioned: '{{username}} nämnde dig i "{{topic}}" - {{site_title}}'
quoted: '{{username}} citerade dig i "{{topic}}" - {{site_title}}'
replied: '{{username}} svarade dig i "{{topic}}" - {{site_title}}'
posted: '{{username}} skrev i "{{topic}}" - {{site_title}}'
+ private_message: '{{username}} skickade dig ett privat meddelande i "{{topic}}" - {{site_title}}'
linked: '{{username}} länkade till ett inlägg du gjort från "{{topic}}" - {{site_title}}'
+ watching_first_post: '{{username}} skapade ett nytt ämne "{{topic}}" - {{site_title}}'
+ confirm_title: "Notifieringar aktiverade - %{site_title}"
+ confirm_body: "Framgång! Meddelanden har aktiverats."
+ custom: "Notifikationer från {{username}} hos %{site_title}"
titles:
+ mentioned: "omnämnd"
+ replied: "nytt svar"
+ quoted: "citerad"
+ edited: "redigerad"
+ liked: "ny gillning"
+ private_message: "nytt privat meddelande"
+ invited_to_private_message: "inbjuden till privat meddelande"
+ invitee_accepted: "inbjudan accepterad"
+ posted: "nytt inlägg"
+ moved_post: "inlägg flyttat"
+ linked: "länkat"
+ granted_badge: "utmärkelse tilldelad"
+ invited_to_topic: "inbjuden till ämne"
+ group_mentioned: "grupp omnämnde"
+ group_message_summary: "nytt gruppmeddelande"
watching_first_post: "nytt ämne"
+ topic_reminder: "ämnespåminnelse"
+ liked_consolidated: "nya gillningar"
+ post_approved: "inlägg godkänt"
+ membership_request_consolidated: "ny begäran om medlemskap"
upload_selector:
title: "Lägg till en bild"
title_with_attachments: "Lägg till en bild eller en fil"
@@ -1133,40 +1721,64 @@ sv:
select_all: "Markera alla"
clear_all: "Rensa allt"
too_short: "Din sökterm är för kort."
+ result_count:
+ one: "%{count} resultat för {{term}} "
+ other: "{{count}}{{plus}} resultat för {{term}} "
title: "sök efter ämnen, inlägg, användare, eller kategorier"
+ full_page_title: "sök ämnen eller inlägg"
no_results: "Inga resultat hittades."
no_more_results: "Inga fler resultat hittades."
searching: "Söker ..."
post_format: "#{{post_number}} av {{username}}"
+ results_page: "Sökresultat för '{{term}}'"
+ more_results: "Det finns fler resultat. Vänligen förfina ditt sökkriterium."
+ cant_find: "Hittar du inte det du söker?"
+ start_new_topic: "Kanske skapa ett nytt ämne?"
+ or_search_google: "Eller försök söka med Google istället:"
+ search_google: "Försök söka med Google istället:"
search_google_button: "Google"
search_google_title: "Sök på hemsidan"
context:
user: "Sök inlägg av @{{username}}"
category: "Sök #{{category}} kategorin"
+ tag: "Sök efter #{{tag}} taggen"
topic: "Sök i det här ämnet"
private_messages: "Sök meddelanden"
advanced:
title: Avancerad sökning
posted_by:
label: Postat av
+ in_category:
+ label: Kategoriserad
in_group:
label: I gruppen
with_badge:
label: Med märke
+ with_tags:
+ label: Taggad
filters:
+ label: Returnera enbart ämnen/inlägg....
+ title: Matcha enbart i titeln
likes: Jag gillade
posted: Jag postade i
+ created: Mina skapade
watching: Jag tittar
tracking: Jag bevakar
+ private: Bland mina meddelanden
+ bookmarks: Som jag har bokmärkt
first: är den första posten
pinned: är pinnade
unpinned: är inte pinnade
+ seen: Som jag har läst
unseen: Jag har inte läst
wiki: är wiki
+ images: inkludera bild(er)
+ all_tags: Alla ovanstående taggar
statuses:
label: Där ämnen
open: är öppen
closed: är stängd
+ public: som är offentligt
archived: är arkiverad
noreplies: har noll svar
single_user: innehåller en ensam användare
@@ -1182,12 +1794,14 @@ sv:
go_back: "gå tillbaka"
not_logged_in_user: "användarsida med sammanställning av aktuell aktivitet och inställningar"
current_user: "gå till din användarsida"
+ view_all: "visa alla"
topics:
new_messages_marker: "senaste besök"
bulk:
select_all: "Välj alla"
clear_all: "Rensa alla"
unlist_topics: "Avlista ämnen"
+ relist_topics: "Lista om ämnen"
reset_read: "Återställ lästa"
delete: "Ta bort ämnen"
dismiss: "Avfärda"
@@ -1198,6 +1812,7 @@ sv:
dismiss_new: "Avfärda Nya"
toggle: "växla val av flertalet ämnen"
actions: "Massändringar"
+ change_category: "Sätt kategori"
close_topics: "Stäng ämnen"
archive_topics: "Arkivera ämnen"
notification_level: "Notifieringar"
@@ -1237,6 +1852,7 @@ sv:
other: "{{count}} inlägg i ämnet"
create: "Nytt ämne"
create_long: "Skapa ett nytt ämne"
+ open_draft: "Öppna utkast"
private_message: "Skriv meddelande"
archive_message:
help: "Flytta meddelandet till ditt arkiv"
@@ -1244,8 +1860,19 @@ sv:
move_to_inbox:
title: "Flytta till inkorgen"
help: "Flytta tillbaka meddelandet till inkorgen"
+ edit_message:
+ help: "Redigera första inlägget av meddelanden"
+ title: "Ändra meddelande"
defer:
+ help: "Märk som oläst"
title: "Skjut upp"
+ feature_on_profile:
+ help: "Lägg till en länk till detta ämne från ditt användarkort samt profil"
+ title: "Föredra från profil"
+ remove_from_profile:
+ warning: "Din profil har redan ett föredraget ämne. Om du fortsätter kommer detta ämne att ersätta ditt tidigare ämne."
+ help: "Ta bort länken till detta ämne från din användarprofil"
+ title: "Ta bort från profil"
list: "Ämnen"
new: "nytt ämne"
unread: "oläst"
@@ -1284,6 +1911,10 @@ sv:
toggle_information: "slå av/på ämnesdetaljer"
read_more_in_category: "Vill du läsa mer? Bläddra bland andra ämnen i {{catLink}} eller {{latestLink}}."
read_more: "Vill du läsa mer? {{catLink}} eller {{latestLink}}."
+ group_request: "Du behöver begära medlemskap i `{{name}}` gruppen för att se detta ämne"
+ group_join: "Du behöver gå med i `{{name}}` gruppen för att se detta ämne"
+ group_request_sent: "Din begäran om gruppmedlemskap har skickats. Du kommer informeras om den har accepterats."
+ unread_indicator: "Ingen medlem har läst det senaste inlägget ännu."
read_more_MF: "Det finns { UNREAD, plural, =0 {} one { 1 oläst } other { # olästa } } { NEW, plural, =0 {} one { {BOTH, select, true{och } false {} other{}} 1 nytt ämne} other { {BOTH, select, true{och } false {} other{}} # nya ämnen} } kvar, eller {CATEGORY, select, true {bläddra bland andra ämnen i {catLink}} false {{latestLink}} other {}}"
browse_all_categories: Bläddra bland alla kategorier
view_latest_topics: visa senaste ämnen
@@ -1292,9 +1923,32 @@ sv:
jump_reply_down: hoppa till senare svar
deleted: "Ämnet har raderats"
topic_status_update:
+ title: "Ämnestidtagning"
save: "Ställ in timer"
+ num_of_hours: "Antal timmar:"
remove: "Ta bort timer:"
publish_to: "Publicera till:"
+ when: "När:"
+ public_timer_types: Ämnestidtagningar
+ private_timer_types: Användares ämnestidtagning
+ time_frame_required: Vänligen välj en tidsram
+ auto_update_input:
+ none: "Välj en tidsram"
+ later_today: "Senare idag"
+ tomorrow: "Imorgon"
+ later_this_week: "Senare denna vecka"
+ this_weekend: "Detta veckoslut"
+ next_week: "Nästa vecka"
+ two_weeks: "Två veckor"
+ next_month: "Nästa månad"
+ two_months: "Två månader"
+ three_months: "Tre månader"
+ four_months: "Fyra månader"
+ six_months: "Sex månader"
+ one_year: "Ett år"
+ forever: "Förevigt"
+ pick_date_and_time: "Välj datum och tid"
+ set_based_on_last_post: "Stäng baserat på senaste inlägg"
publish_to_category:
title: "Schemalägg publicering"
temp_open:
@@ -1305,13 +1959,23 @@ sv:
title: "Stäng tillfälligt"
auto_close:
title: "Stäng ämne automatiskt"
+ label: "Tid för att automatiskt stänga ämne:"
error: "Vänligen ange ett giltigt värde."
based_on_last_post: "Stäng inte förrän det sista inlägget i ämnet är åtminstone så här gammalt."
+ auto_delete:
+ title: "Radera ämne automatiskt:"
+ auto_bump:
+ title: "Auto-knuffa Ämne"
+ reminder:
+ title: "Påminn mig"
status_update_notice:
auto_open: "Detta ämne kommer öppnas automatiskt om %{timeLeft}."
auto_close: "Det här ämnet kommer stängas automatiskt om %{timeLeft}."
auto_publish_to_category: "Detta ämne kommer att publiceras till #%{categoryName} %{timeLeft}."
auto_close_based_on_last_post: "Detta ämne stängs %{duration} efter sista svaret."
+ auto_delete: "Det här ämnet kommer raderas automatiskt om %{timeLeft}."
+ auto_bump: "Det här ämnet kommer knuffas automatiskt %{timeLeft}."
+ auto_reminder: "Du kommer att påminnas om detta ämne om: %{timeLeft}."
auto_close_title: "Inställningar för automatisk stängning"
auto_close_immediate:
one: "Senaste inlägget i det här ämnet är redan %{count} timme gammalt, så ämnet kommer att stängas omedelbart. "
@@ -1328,7 +1992,9 @@ sv:
jump_bottom: "hoppa till sista inlägget"
jump_prompt: "hoppa till"
jump_prompt_of: "av %{count} inlägg"
+ jump_prompt_long: "Hoppa till..."
jump_bottom_with_number: "hoppa till inlägg %{post_number}"
+ jump_prompt_to_date: "till datum"
jump_prompt_or: "eller"
total: antal inlägg
current: nuvarande inlägg
@@ -1342,6 +2008,10 @@ sv:
"3_2": "Du kommer att ta emot notifikationer för att du bevakar detta ämne."
"3_1": "Du kommer ta emot notifikationer för att du skapade detta ämne."
"3": "Du kommer att ta emot notifikationer för att du bevakar detta ämne."
+ "2_8": "Du kommer se en räknare för nya svar eftersom du följer denna kategorin."
+ "2_4": "Du kommer se en räknare för nya svar eftersom du postat ett svar i detta ämne."
+ "2_2": "Du kommer se en räknare för nya svar eftersom du följer detta ämne."
+ "2": 'Du kommer se en räknare för nya svar eftersom du läser detta ämne . '
"1_2": "Du kommer få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg."
"1": "Du kommer få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg."
"0_7": "Du ignorerar alla notifikationer i den här kategorin."
@@ -1378,6 +2048,7 @@ sv:
open: "Öppna ämne"
close: "Stäng ämne"
multi_select: "Välj inlägg..."
+ timed_update: "Sätt tidtagning för ämne..."
pin: "Klistra ämne..."
unpin: "Avklistra ämne..."
unarchive: "Dearkivera ämne"
@@ -1386,6 +2057,8 @@ sv:
visible: "Markera listad"
reset_read: "Återställ läsdata"
make_public: "Skapa allmänt ämne"
+ make_private: "Skapa personligt meddelande"
+ reset_bump_date: "Återställ knuffdatum"
feature:
pin: "Klistra ämne"
unpin: "Avklistra ämne"
@@ -1400,6 +2073,7 @@ sv:
help: "Ta bort den klistrade statusen från detta ämne så den inte längre hamnar i toppen av din ämneslista"
share:
title: "Dela"
+ extended_title: "Dela en länk"
help: "dela en länk till detta ämne"
print:
title: "Skriv ut"
@@ -1408,6 +2082,9 @@ sv:
title: "Flagga"
help: "flagga privat detta ämne för uppmärksamhet eller skicka en privat notifiering om den"
success_message: "Du flaggade framgångsrikt detta ämne."
+ make_public:
+ title: "Konvertera till offentligt ämne"
+ choose_category: "Vänligen välj en kategori för det offentliga ämne:"
feature_topic:
title: "Gör till utvalt ämne"
pin: "Gär det här ämnet synligt i toppen av {{categoryLink}} kategorin tills "
@@ -1461,15 +2138,21 @@ sv:
success_email: "Vi skickade ut en inbjudan till {{emailOrUsername}} . Vi meddelar dig när inbjudan lösts in. Kolla inbjudningsfliken på din användarsida för att hålla koll på dina inbjudningar."
success_username: "Vi har bjudit in användaren att delta i detta ämne."
error: "Tyvärr, vi kunde inte bjuda in den personen. Personen kanske redan har blivit inbjuden? (Invites are rate limited)"
+ success_existing_email: "En användare med e-post {{emailOrUsername}} finns redan. Vi kommer att bjuda in denna personen att delta i detta ämne."
login_reply: "Logga in för att svara"
filters:
n_posts:
one: "%{count} inlägg"
other: "{{count}} inlägg"
cancel: "Ta bort filter"
+ move_to:
+ title: "Flytta till"
+ action: "flytta till"
+ error: "Det uppstod ett fel vid flyttning av inlägg."
split_topic:
title: "Flytta till nytt ämne"
action: "flytta till nytt ämne"
+ topic_name: "Ny ämnestitel"
radio_label: "Nytt ämne"
error: "Ett fel inträffade då inläggen skulle flyttas till det nya ämnet."
instructions:
@@ -1479,23 +2162,41 @@ sv:
title: "Flytta till befintligt ämne"
action: "flytta till befintligt ämne"
error: "Ett fel inträffade då inlägg skulle flyttas till det ämnet."
+ radio_label: "Befintliga ämnen"
instructions:
one: "Välj vilket ämne du vill flytta det inlägget till."
other: "Välj vilket ämne du vill flytta de {{count}} inläggen till."
move_to_new_message:
+ title: "Flytta till nytt meddelande"
+ action: "flytta till nytt meddelande"
+ message_title: "Titel på nytt meddelande"
radio_label: "Nytt meddelande"
participants: "Deltagare"
+ instructions:
+ one: "Du håller på att skapa ett nytt meddelande och tillföra det inlägget som du valt."
+ other: "Du håller på att skapa ett nytt meddelande och tillföra de {{count}} inlägg som du har valt."
move_to_existing_message:
+ title: "Flytta till existerande meddelande"
+ action: "flytta till existerande meddelande"
+ radio_label: "Existerande meddelande"
participants: "Deltagare"
+ instructions:
+ one: "Vänligen välj det meddelande som du vill flytta detta inlägg till."
+ other: "Vänligen välj det meddelande som du vill flytta dessa {{count}} inlägg till."
merge_posts:
title: "Sammanfoga markerade inlägg"
action: "sammanfoga markerade inlägg"
error: "Det uppstod ett fel vid sammanfogningen av de markerade inläggen."
change_owner:
+ title: "Byt ägare"
action: "ändra ägare"
error: "Ett fel uppstod vid ändringen av ämnets ägarskap."
placeholder: "användarnamn på den nya ägaren"
+ instructions:
+ one: "Vänligen välj en ny ägare för inlägget från @{{old_user}} "
+ other: "Vänligen välj en ny ägare för {{count}} inlägg från @{{old_user}} "
change_timestamp:
+ title: "Ändra tidsstämpel..."
action: "ändra tidsstämpeln"
invalid_timestamp: "Tidsstämpeln kan inte sättas till ett framtida datum."
error: "Ett fel uppstod vid ändringen av ämnets tidsstämpel."
@@ -1505,8 +2206,16 @@ sv:
selected: "markerade ({{count}})"
select_post:
label: "markera"
+ title: "Lägg till inlägg för markerad"
+ selected_post:
+ label: "markerad"
+ title: "Klicka för att ta bort inlägg från markering"
select_replies:
label: "välj +svar"
+ title: "Lägg till inlägg samt alla tillhörande svar till markering"
+ select_below:
+ label: "välj +nedanför"
+ title: "Lägg till inlägg samt allting efter den till markering"
delete: radera markerade
cancel: avbryt markering
select_all: markera alla
@@ -1514,10 +2223,14 @@ sv:
description:
one: Du har markerat %{count} inlägg.
other: "Du har markerat {{count}} inlägg."
+ deleted_by_author:
+ one: "(ämne tillbakadraget av författaren, kommer att raderas automatiskt om %{count} timme om det inte flaggas)"
+ other: "(ämne tillbakadraget av författaren, kommer att raderas automatiskt om %{count} timmar om det inte flaggas)"
post:
quote_reply: "Citat"
edit_reason: "Anledning:"
post_number: "inlägg {{number}}"
+ ignored: "Ignorerat innehåll"
wiki_last_edited_on: "Senast gången som wiki redigerades"
last_edited_on: "inlägg senast ändrat den"
reply_as_new_topic: "Svara som länkat ämne"
@@ -1525,13 +2238,19 @@ sv:
continue_discussion: "Fortsätter diskussionen från {{postLink}}:"
follow_quote: "gå till det citerade inlägget"
show_full: "Visa hela inlägget"
+ show_hidden: "Visa ignorerat innehåll"
deleted_by_author:
one: "(inlägg tillbakadraget av skaparen, kommer att raderas automatiskt om %{count} timme om det inte flaggas)"
other: "(inlägg tillbakadraget av skaparen, kommer att raderas automatiskt om %{count} timmar om det inte flaggas)"
+ collapse: "förminska"
expand_collapse: "utvidga/förminska"
+ locked: "en i personalen har låst detta inlägg från att redigeras"
gap:
one: "visa %{count} dolt svar"
other: "visa {{count}} dolda svar"
+ notice:
+ new_user: "Detta är första gången som användaren {{user}} har skapat ett inlägg — låt oss välkomna dessa till vår gemenskap!"
+ returning_user: "Det var ett tag sedan vi såg {{user}} — deras senaste inlägg var {{time}}."
unread: "Inlägget är oläst"
has_replies:
one: "{{count}} svar"
@@ -1547,16 +2266,22 @@ sv:
create: "Tyvärr, det uppstod ett fel under skapandet av ditt inlägg. Var god försök igen."
edit: "Tyvärr, det uppstod ett fel under ändringen av ditt inlägg. Var god försök igen."
upload: "Tyvärr, det uppstod ett fel under uppladdandet av den filen. Vad god försök igen."
+ file_too_large: "Tyvärr, filen är för stor (maximal filstorlek är {{max_size_kb}}kb). Varför inte ladda upp din stora fil till en moln-delningstjänst och sen dela länken?"
too_many_uploads: "Tyvärr, du kan bara ladda upp en bild i taget."
+ too_many_dragged_and_dropped_files: "Tyvärr, du kan bara ladda upp {{max}} filer åt gången."
upload_not_authorized: "Tyvärr, filen du försöker ladda upp är inte tillåten (tillåtna filtyper: %{authorized_extensions})."
image_upload_not_allowed_for_new_user: "Tyvärr, nya användare kan inte ladda upp bilder."
attachment_upload_not_allowed_for_new_user: "Tyvärr, nya användare kan inte bifoga filer."
attachment_download_requires_login: "Tyvärr, du måste vara inloggad för att kunna ladda ned bifogade filer."
abandon_edit:
+ confirm: "Är du säker på att du vill ångra dina ändringar?"
no_value: "nej, behåll"
+ no_save_draft: "Nej, spara utkast"
+ yes_value: "Ja, kasta ändringar"
abandon:
confirm: "Är du säker på att du vill avbryta ditt inlägg?"
no_value: "nej, behåll"
+ no_save_draft: "Nej, spara utkast"
yes_value: "Ja, överge"
via_email: "det här inlägget har gjorts via e-post"
via_auto_generated_email: "det här inlägget anlände via ett autogenererat e-postmeddelande"
@@ -1570,6 +2295,7 @@ sv:
reply: "börja komponera ett svar till detta inlägg"
like: "gilla detta inlägg"
has_liked: "du har gillat detta inlägg"
+ read_indicator: "användare som läser detta inlägg"
undo_like: "ångra gillning"
edit: "ändra detta inlägg"
edit_action: "Redigera"
@@ -1580,6 +2306,13 @@ sv:
share: "dela en länk till detta inlägg"
more: "Mer"
delete_replies:
+ confirm: "Vill du också radera svaren för detta inlägg?"
+ direct_replies:
+ one: "Ja, och %{count} direkta svar"
+ other: "Ja, och {{count}} direkta svar"
+ all_replies:
+ one: "Ja, och %{count} svar"
+ other: "Ja, och alla {{count}} svar"
just_the_post: "Nej, bara det här inlägget"
admin: "administratörsåtgärder för inlägg"
wiki: "Skapa wiki"
@@ -1590,9 +2323,21 @@ sv:
unhide: "Visa"
change_owner: "Ändra ägare"
grant_badge: "Utfärda utmärkelse"
+ lock_post: "Lås inlägg"
+ lock_post_description: "förhindra postaren från att redigera inlägget"
+ unlock_post: "Lås upp inlägg"
+ unlock_post_description: "tillåt postaren att redigera inlägget"
+ delete_topic_disallowed_modal: "Du har inte behörighet att radera detta ämne. Om du verkligen vill radera det, skapa en flagga för att uppmärksamma moderatorn tillsammans med orsak. "
+ delete_topic_disallowed: "du har inte behörighet att radera detta ämne"
delete_topic: "ta bort ämne"
+ add_post_notice: "Lägg till personalnotering"
+ remove_post_notice: "Ta bort personalnotering"
+ remove_timer: "Ta bort tidtagning"
actions:
flag: "Flagga"
+ defer_flags:
+ one: "Ignorera flagga"
+ other: "Ignorera flaggor"
undo:
off_topic: "Ångra flaggning"
spam: "Ångra flaggning"
@@ -1606,6 +2351,18 @@ sv:
notify_moderators: "notifierade moderatorer"
notify_user: "skickade ett meddelande"
bookmark: "bokmärkte det här"
+ like:
+ one: "gillade det här"
+ other: "gillade det här"
+ read:
+ one: "läste detta"
+ other: "läste detta"
+ like_capped:
+ one: "och {{count}} annan gillade detta"
+ other: "och {{count}} andra gillade detta"
+ read_capped:
+ one: "och {{count}} annan läste detta"
+ other: "och {{count}} andra läste detta"
by_you:
off_topic: "Du flaggade detta som orelevant"
spam: "Du flaggade detta som spam"
@@ -1614,6 +2371,14 @@ sv:
notify_user: "Du skickade ett meddelande till denna användare"
bookmark: "Du bokmärkte detta inlägg"
like: "Du gillade detta"
+ delete:
+ confirm:
+ one: "Är du säker på att du vill radera detta inlägg?"
+ other: "Är du säker på att du vill radera dessa {{count}} inlägg?"
+ merge:
+ confirm:
+ one: "Är du säker på att du vill slå ihop dessa inlägg?"
+ other: "Är du säker på att du vill sammanfoga de {{count}} inläggen?"
revisions:
controls:
first: "Första revision"
@@ -1625,6 +2390,7 @@ sv:
revert: "Återgå till den här revisionen"
edit_wiki: "Uppdatera Wiki"
edit_post: "Ändra meddelandet"
+ comparing_previous_to_current_out_of_total: "{{previous}} {{icon}}{{current}} / {{total}}"
displays:
inline:
title: "Visa resultat med tillägg och borttagningar inline"
@@ -1638,6 +2404,7 @@ sv:
raw_email:
displays:
raw:
+ title: "Visa e-post utan formatering"
button: "Rå"
text_part:
title: "Visa text-delen av mailet"
@@ -1646,19 +2413,33 @@ sv:
title: "Visa html-delen av mailet"
button: "HTML"
bookmarks:
+ create: "Skapa bokmärke"
name: "Namn"
+ name_placeholder: "Namnge bokmärket för att underlätta för ditt minne"
+ set_reminder: "Skapa påminnelse"
category:
can: "can… "
none: "(ingen kategori)"
all: "Alla kategorier"
+ choose: "kategori…"
edit: "Redigera"
+ edit_dialog_title: "Ändra: %{categoryName}"
view: "Visa ämnen i kategori"
general: "Allmänt"
settings: "Inställningar"
topic_template: "Ämnesmall"
tags: "Taggar"
+ tags_allowed_tags: "Begränsa dessa taggar till följande kategori:"
+ tags_allowed_tag_groups: "Begränsa dessa tagg-grupper till denna kategori:"
tags_placeholder: "(Valfritt) lista av tillåtna taggar"
+ tags_tab_description: "Taggar och tagg-grupper specificerade ovanför kommer enbart att vara tillgängligt för denna kategori samt andra kategorier som också har specificerat dem. De kommer inte vara tillgängliga i övriga kategorier."
tag_groups_placeholder: "(Valfritt) lista av tillåtna grupptaggar"
+ manage_tag_groups_link: "Hantera tagg-grupper här."
+ allow_global_tags_label: "Tillåt även andra taggar"
+ tag_group_selector_placeholder: "(Valfri) Tagg-grupp"
+ required_tag_group_description: "Kräv att nya ämnen har taggar från en tagg-grupp:"
+ min_tags_from_required_group_label: "Nummer taggar:"
+ required_tag_group_label: "Tagg-grupp:"
topic_featured_link_allowed: "Tillåt utvalda länkar i denna kategori"
delete: "Radera kategori"
create: "Ny kategori"
@@ -1686,30 +2467,45 @@ sv:
already_used: "Den här färgen används redan av en annan kategori"
security: "Säkerhet"
special_warning: "Varning: Den här kategorin är en förbestämd kategori och säkerhetsinställningarna kan inte ändras. Om du inte vill använda kategorin, ta bort den istället för att återanvända den."
+ uncategorized_security_warning: "Denna kategori är speciell. Den är avsedd för att innehålla ämnen som inte har en kategori. Den kan inte ha säkerhetsinställningar."
+ uncategorized_general_warning: 'Denna kategori är speciell. Den används som standardkategori för nya ämnen som inte har en vald kategori. Om du vill förhindra detta beteende och tvinga val av kategori, vänligen deaktivera inställningen här . Om du vill ändra namn eller beskrivning, gå till Anpassa / Textinnehåll .'
+ pending_permission_change_alert: "Du har inte lagt till %{group} för denna kategori; klicka på förljande knapp för att lägga till dem."
images: "Bilder"
email_in: "Egenvald inkommande e-postadress:"
email_in_allow_strangers: "Acceptera e-post från anonyma användare utan konton"
email_in_disabled: "Att skapa nya ämnen via e-post är avaktiverat i webbplatsinställningarna. För att aktivera ämnen skapade via e-post,"
email_in_disabled_click: 'aktivera "inkommande e-post" inställningen.'
+ mailinglist_mirror: "Kategori speglar en e-postlista"
show_subcategory_list: "Visa listan med underkategorier ovanför ämnen i denna kategori."
num_featured_topics: "Antal ämnen som visas på sidan kategorier:"
+ subcategory_num_featured_topics: "Antalet favoriserade ämnen på överordnad kategorisida:"
+ all_topics_wiki: "Gör nya ämnen till wikis som standard"
subcategory_list_style: "Liststil på underkategori:"
sort_order: "Sortera ämneslista enligt:"
default_view: "Förvald ämneslista"
+ default_top_period: "Standard Topp-period:"
allow_badges_label: "Tillåt utmärkelser i den här kategorin"
edit_permissions: "Redigera behörigheter"
+ reviewable_by_group: "I tillägg till personal, kan inlägg och flaggor i denna kategori också granskas av:"
review_group_name: "gruppnamn"
+ require_topic_approval: "Kräv att en moderator godkänner alla nya ämnen"
+ require_reply_approval: "Kräv att en moderator godkänner alla nya svar"
this_year: "i år"
+ position: "Position på kategorisidan:"
default_position: "Standardposition"
position_disabled: "Kategorier kommer att sorteras efter deras aktivitet. För att ställa in sorteringen av kategorier i den här listan,"
position_disabled_click: 'aktivera "fasta kategoripositioner" inställningen.'
- parent: "Förälderkategori"
+ minimum_required_tags: "Minsta antalet taggar som krävs för ett ämne:"
+ parent: "Överordnad kategori"
+ num_auto_bump_daily: "Antalet av öppnade ämnen dagligen för att automatiskt knuffa ett ämne:"
+ navigate_to_first_post_after_read: "Navigera till första inlägget efter att ämnen är lästa"
notifications:
watching:
title: "Bevakar"
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 notifieras vid nya ämnen för denna kategori men inte svar på ämnen."
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."
@@ -1720,8 +2516,14 @@ sv:
title: "Tystad"
description: "Du kommer aldrig att notifieras om något som rör nya ämnen i de här kategorierna och de kommer inte att dyka upp i din olästa tabb."
search_priority:
+ label: "Sökprioritering"
options:
normal: "Normal"
+ ignore: "Ignorera"
+ very_low: "Väldigt låg"
+ low: "Låg"
+ high: "Hög"
+ very_high: "Väldigt hög"
sort_options:
default: "Standard"
likes: "Gillar"
@@ -1737,8 +2539,12 @@ sv:
subcategory_list_styles:
rows: "Rader"
rows_with_featured_topics: "Rader med utvalda ämnen"
+ boxes: "Boxar"
+ boxes_with_featured_topics: "Boxar med utvalda ämnen"
settings_sections:
general: "Allmänt"
+ moderation: "Moderering"
+ appearance: "Utseende"
email: "E-post"
flagging:
title: "Tack för att du hjälper till att hålla forumet civiliserat!"
@@ -1810,6 +2616,8 @@ sv:
help: "Detta ämne är klistrat för dig. Det visas i toppen av dess kategori"
unlisted:
help: "Det här ämnet är olistat; det kommer inte visas i ämneslistorna och kan bara nås via en direktlänk"
+ personal_message:
+ title: "Detta ämne är ett personligt meddelande"
posts: "Inlägg"
posts_long: "det finns {{number}} inlägg i detta ämne"
posts_likes_MF: |
@@ -1913,13 +2721,25 @@ sv:
this_week: "Vecka"
today: "Idag"
other_periods: "se toppen"
+ browser_update: 'Tyvärr, din webbläsare är för gammal för att fungera på den här sidan . Vänligen uppgradera din webbläsare . '
permission_types:
full: "Skapa / svara / se"
create_post: "Svara / se"
readonly: "se"
lightbox:
download: "ladda ned"
+ previous: "Föregående (Vänster piltangent)"
+ next: "Nästa (Höger piltangent)"
+ counter: "%curr% av %total%"
+ close: "Stäng (Esc)"
+ content_load_error: 'Innehållet kunde inte laddas.'
+ image_load_error: 'Bilden kunde inte laddas.'
keyboard_shortcuts_help:
+ shortcut_key_delimiter_comma: ", "
+ shortcut_key_delimiter_plus: "+"
+ shortcut_delimiter_or: "%{shortcut1} eller %{shortcut2}"
+ shortcut_delimiter_slash: "%{shortcut1}/%{shortcut2}"
+ shortcut_delimiter_space: "%{shortcut1}%{shortcut2}"
title: "Tangentbordsgenvägar"
jump_to:
title: "Hoppa till"
@@ -1932,6 +2752,7 @@ sv:
bookmarks: "%{shortcut} Bokmärken"
profile: "%{shortcut} Profil"
messages: "%{shortcut} Meddelanden"
+ drafts: "%{shortcut} Utkast"
navigation:
title: "Navigering"
jump: "%{shortcut} Gå till inlägg #"
@@ -1939,6 +2760,7 @@ sv:
up_down: "%{shortcut} Flytta markering ↑ ↓"
open: "%{shortcut} Öppna valt ämne"
next_prev: "%{shortcut} Nästa/föregående avsnitt"
+ go_to_unread_post: "%{shortcut} Gå till det första olästa inlägget"
application:
title: "Applikation"
create: "%{shortcut} Skapa ett nytt ämne"
@@ -1946,10 +2768,15 @@ sv:
hamburger_menu: "%{shortcut} Öppna hamburgarmenyn"
user_profile_menu: "%{shortcut} Öppna användarmeny"
show_incoming_updated_topics: "%{shortcut} Visa uppdaterade ämnen"
+ search: "%{shortcut} Sök"
help: "%{shortcut} Öppna tangentbordshjälp"
dismiss_new_posts: "%{shortcut} Avfärda nya/inlägg"
dismiss_topics: "%{shortcut} Avfärda ämnen"
log_out: "%{shortcut} Logga ut"
+ composing:
+ title: "Skapar"
+ return: "%{shortcut} Återgå till skaparen"
+ fullscreen: "%{shortcut} Helskärmsläge för skaparen"
actions:
title: "Åtgärder"
bookmark_topic: "%{shortcut} Växla bokmärkning av ämne"
@@ -1970,6 +2797,8 @@ sv:
mark_tracking: "%{shortcut} Följ ämne"
mark_watching: "%{shortcut} Bevaka ämne"
print: "%{shortcut} Skriv ut ämne"
+ defer: "%{shortcut} Skjut upp ämne"
+ topic_admin_actions: "%{shortcut} Öppna administratörsåtgärder för ämne"
badges:
earned_n_times:
one: "Förtjänade den här utmärkelsen %{count} gång"
@@ -1977,6 +2806,8 @@ sv:
granted_on: "Utfärdad %{date}"
others_count: "Andra med den här utmärkelsen (%{count})"
title: Utmärkelser
+ allow_title: "Du kan använda denna utmärkelse som en titel"
+ multiple_grant: "Du kan förtjäna denna flera gånger"
badge_count:
one: "%{count} Utmärkelse"
other: "%{count} Utmärkelser"
@@ -1988,6 +2819,7 @@ sv:
other: "%{count} utfärdade"
select_badge_for_title: Välj en utmärkelse att använda som din titel
none: "(ingen)"
+ successfully_granted: "Framgångsrikt beviljat %{badge} till %{username}"
badge_grouping:
getting_started:
name: Komma igång
@@ -2010,12 +2842,36 @@ sv:
tagging:
all_tags: "Alla taggar"
+ other_tags: "Övriga taggar"
selector_all_tags: "alla taggar"
selector_no_tags: "inga taggar"
changed: "taggar ändrade:"
tags: "Taggar"
+ choose_for_topic: "alternativa taggar"
+ info: "Info"
+ default_info: "Denna tagg är inte begränsad till någon kategori, och har inga synonymer."
+ synonyms: "Synonymer"
+ synonyms_description: "När följande taggar används, kommer de att ersättas med
%{base_tag_name} ."
+ tag_groups_info:
+ one: 'Denna tagg tillhör gruppen: "{{tag_groups}}".'
+ other: "Denna tagg tillhör dessa grupper: {{tag_groups}}."
+ category_restrictions:
+ one: "Den kan enbart användas i denna kategori:"
+ other: "Den kan enbart användas i dessa kategorier:"
+ edit_synonyms: "Hantera synonymer"
+ add_synonyms_label: "Lägg till synonymer:"
add_synonyms: "Lägg till"
+ add_synonyms_failed: "Följande tagg kunde inte läggas till som synonym:
%{tag_names} . Försäkra att de inte har synonymer eller är synonymer för andra taggar."
+ remove_synonym: "Ta bort synonym"
+ delete_synonym_confirm: 'Är du säker på att du vill ta bort synonymen "%{tag_name}"?'
delete_tag: "Radera tag"
+ delete_confirm:
+ one: "Är du säker på att du vill radera denna tagg och ta bort den från %{count} ämne som den har tilldelats?"
+ other: "Är du säker på att du vill radera denna tagg och ta bort den från {{count}} ämnen som den har tilldelats?"
+ delete_confirm_no_topics: "Är du säker på att du vill ta bort denna tagg?"
+ delete_confirm_synonyms:
+ one: "Dess synonym kommer också att raderas."
+ other: "Dess {{count}} synonymer kommer också att raderas."
rename_tag: "Döp om taggen"
rename_instructions: "Välj ett nytt namn för taggen:"
sort_by: "Sortera efter:"
@@ -2023,6 +2879,18 @@ sv:
sort_by_name: "namn"
manage_groups: "Hantera grupptaggar"
manage_groups_description: "Definiera grupper för att organisera taggar"
+ upload: "Ladda upp taggar"
+ upload_description: "Ladda upp en csv-fil för att skapa taggar på bulk."
+ upload_instructions: "En per rad, alternativt med en tagg-grupp i formatet 'tag_name,tag_group'."
+ upload_successful: "Lyckad uppladdning av taggar "
+ delete_unused_confirmation:
+ one: "%{count} tagg kommer att raderas: %{tags}"
+ other: "%{count} taggar kommer att raderas: %{tags}"
+ delete_unused_confirmation_more_tags:
+ one: "%{tags} och %{count} till"
+ other: "%{tags} och %{count} till"
+ delete_unused: "Radera oanvända taggar"
+ delete_unused_description: "Radera alla taggar som inte är kopplade till något ämne eller personliga meddelanden"
cancel_delete_unused: "Avbryt"
filters:
without_category: "%{filter} %{tag} ämnen"
@@ -2032,28 +2900,37 @@ sv:
notifications:
watching:
title: "Bevakar"
+ description: "Du kommer automatiskt att bevaka alla ämnen med den här taggen. Du kommer att få notifieringar om alla nya inlägg och ämnen, och en räknare över olästa och nya inlägg kommer att visas bredvid ämnen."
watching_first_post:
title: "Bevakar första inlägget"
+ description: "Du kommer att notifieras vid nya ämnen för denna tagg men inte svar på ämnen."
tracking:
title: "Bevakade"
+ description: "Du kommer automatiskt följa alla ämnen med den här taggen. En räknare över olästa och nya inlägg kommer att visas bredvid ämnen."
regular:
title: "Vanlig"
description: "Du kommer att få en notifiering om någon nämner ditt @namn eller svarar på ditt inlägg."
muted:
title: "Tystad"
+ description: "Du kommer inte att få notifieringar om nya ämnen med den här taggen, och de kommer inte att visas under din \"oläst\"-flik."
groups:
title: "Grupptaggar"
about: "Lägg till taggar i grupper för att lättare hantera dem."
new: "Ny grupp"
tags_label: "Taggar i den här gruppen:"
- parent_tag_label: "Föräldertagg:"
+ tags_placeholder: "taggar"
+ parent_tag_label: "Överordnad tagg:"
parent_tag_placeholder: "Valfria"
- parent_tag_description: "Taggar från den här gruppen kan inte användas om inte föräldertaggen är med."
+ parent_tag_description: "Taggar från den här gruppen kan inte användas om inte den överordnade taggen är med."
one_per_topic_label: "Sätt gräns till en tagg för varje ämne för den här gruppen"
new_name: "Ny grupptagg"
+ name_placeholder: "Namn för tagg-grupp"
save: "Spara"
delete: "Radera"
confirm_delete: "Är du säker på att du vill ta bort den här grupptaggen?"
+ everyone_can_use: "Taggar kan användas av alla"
+ usable_only_by_staff: "Taggar är synliga för alla, men enbart personal kan använda dem"
+ visible_only_to_staff: "Taggar är synliga enbart för personal"
topics:
none:
unread: "Du har inga olästa ämnen."
@@ -2072,9 +2949,11 @@ sv:
top: "Det finns inga fler toppämnen."
bookmarks: "Det finns inga fler bokmärkta ämnen."
invite:
+ custom_message: "Gör din inbjudan lite personligare genom att skriva ett
anpassat meddelande ."
custom_message_placeholder: "Skriv ditt personliga meddelande"
custom_message_template_forum: "Hej! Du borde gå med i det här forumet!"
custom_message_template_topic: "Hej! Jag tror att du kanske skulle uppskatta det här ämnet!"
+ forced_anonymous: "På grund av extrem belastning, visas detta för samtliga precis som en utloggad användare skulle se det."
safe_mode:
enabled: "Säkert läge är aktiverat, för att lämna säkert läge stäng detta webläsarfönster"
admin_js:
@@ -2085,9 +2964,14 @@ sv:
tags:
remove_muted_tags_from_latest:
always: "alltid"
+ only_muted: "när det används ensamt eller med andra tystade taggar"
never: "aldrig"
+ reports:
+ title: "Lista på tillgängliga rapporter"
dashboard:
title: "Översiktspanel"
+ last_updated: "Översiktspanelen uppdaterad:"
+ discourse_last_updated: "Discourse uppdaterad:"
version: "Version"
up_to_date: "Du är aktuell!"
critical_available: "En kritisk uppdatering är tillgänglig."
@@ -2098,25 +2982,48 @@ sv:
version_check_pending: "Det verkar som att du har uppgraderat nyligen. Utmärkt!"
installed_version: "Installerad"
latest_version: "Senaste"
+ problems_found: "Några förslag baserat på din nuvarande sidinställning"
last_checked: "Senast kollad"
refresh_problems: "Uppdatera"
no_problems: "Inga problem upptäcktes."
moderators: "Moderatorer:"
admins: "Administratörer:"
+ silenced: "Tystad:"
suspended: "Avstängd:"
private_messages_short: "Meddelanden"
private_messages_title: "Meddelanden"
mobile_title: "Mobil"
+ space_used: "%{usedSize} används"
+ space_used_and_free: "%{usedSize} (%{freeSize} ledigt)"
+ uploads: "Uppladdningar"
backups: "Säkerhetskopior"
+ backup_count:
+ one: "%{count} reserv på %{location}"
+ other: "%{count} reserver på %{location}"
+ lastest_backup: "Senaste: %{date}"
traffic_short: "Trafik"
traffic: "Applikations-webbegäran"
page_views: "Sidvisningar"
page_views_short: "Sidvisningar"
show_traffic_report: "Visa detaljerad trafikrapport"
+ community_health: Hälsa för gemenskapen
+ moderators_activity: Moderatorers aktivitet
+ whats_new_in_discourse: "Vad är nytt i Discource?"
+ activity_metrics: Aktivitetsdata
+ all_reports: "Alla rapporter"
general_tab: "Allmänt"
+ moderation_tab: "Moderering"
security_tab: "Säkerhet"
+ reports_tab: "Rapporter"
report_filter_any: "något"
+ disabled: Inaktiverad
+ timeout_error: "Tyvärr, förfrågan tar för lång tid, vänligen välj ett snävare intervall"
+ exception_error: "Tyvärr, ett fel uppstod vid körning av förfrågan"
+ too_many_requests: Du har genomfört denna handlingen för många gånger. Vänligen vänta innan du försöker igen.
+ not_found_error: "Tyvärr, denna rapport finns inte"
+ filter_reports: Filtrera rapporter
reports:
+ trend_title: "%{percent} förändring. Nuvarande %{current}, var %{prev} i tidigare perioden."
today: "Idag"
yesterday: "Igår"
last_7_days: "Senaste 7"
@@ -2128,8 +3035,20 @@ sv:
view_table: "tabell"
view_graph: "graf"
refresh_report: "Uppdatera rapport"
+ start_date: "Startdatum (UTC)"
+ end_date: "Slutdatum (UTC)"
groups: "Alla grupper"
+ disabled: "Denna rapport är avstängd"
+ totals_for_sample: "Totalt för prov"
+ average_for_sample: "Genomsnitt för prov"
+ total: "All tid totalt"
+ no_data: "Ingen data att visa."
+ trending_search:
+ more: '
Sökloggar '
+ disabled: 'Den trendiga sökrapporten är inaktiverad. Aktivera
sökfrågor för loggar för att samla data. '
filters:
+ file-extension:
+ label: Filändelse
group:
label: Grupp
category:
@@ -2141,19 +3060,41 @@ sv:
new:
title: "Ny grupp"
create: "Skapa"
+ name:
+ too_short: "Gruppnamn är för kort"
+ too_long: "Gruppnamn är för långt"
+ checking: "Kollar gruppnamnets tillgänglighet..."
+ available: "Gruppnamn är tillgängligt"
+ not_available: "Gruppnamn är inte tillgängligt"
+ blank: "Gruppnamn kan inte vara tomt"
bulk_add:
title: "Masstilläggning till grupp"
+ complete_users_not_added: "Dessa användare lades inte till (kontrollera att de har ett konto):"
paste: "Klistra in en lista av användarnamn eller e-postadresser, en per rad:"
+ add_members:
+ as_owner: "Sätt användare som ägare för denna grupp"
manage:
interaction:
email: E-post
incoming_email: "Egenvald inkommande e-postadress"
incoming_email_placeholder: "Ange e-postadress"
+ visibility: Synlighet
visibility_levels:
+ title: "Vem kan se denna grupp?"
public: "Alla"
+ logged_on_users: "Inloggade användare"
+ members: "Gruppägare, medlemmar"
+ staff: "Gruppägare och personal"
+ owners: "Gruppägare"
+ description: "Administratörer kan se alla grupper"
+ members_visibility_levels:
+ title: "Vem kan se denna grupps medlemmar?"
+ description: "Administratörer kan se medlemmar för alla grupper."
+ publish_read_state: "För gruppmeddelanden publicera gruppens lästillstånd"
membership:
automatic: Automatisk
trust_levels_title: "Förtroendenivå som automatiskt beviljas användare när de läggs till:"
+ effects: Effekter
trust_levels_none: "Inga"
automatic_membership_email_domains: "Användare som registrerar sig med en e-post vars domän exakt matchar en domän i den här listan kommer automatiskt att bli tillagd i den här gruppen:"
automatic_membership_retroactive: "Använd samma regel för e-postdomän för att lägga till nya användare"
@@ -2173,6 +3114,8 @@ sv:
add: "Lägg till"
custom: "Anpassad"
automatic: "Automatisk"
+ default_title: "Standardtitel"
+ default_title_description: "kommer appliceras för alla användare i denna grupp"
group_owners: Ägare
add_owners: Lägg till ägare
none_selected: "Välj en grupp för att komma igång"
@@ -2184,12 +3127,29 @@ sv:
title: "API"
key: "Key"
created: Skapad
+ updated: Uppdaterad
+ last_used: Senast använd
+ never_used: (aldrig)
generate: "Generera"
+ undo_revoke: "Ångra återkallelse"
revoke: "Återkalla"
all_users: "Alla användare"
+ active_keys: "Aktiva API Nycklar"
+ manage_keys: Hantera nycklar
show_details: Detaljer
description: Beskrivning
+ no_description: (ingen beskrivning)
+ all_api_keys: Alla API nycklar
+ user_mode: Användarnivå
+ impersonate_all_users: Imitera en användare
+ single_user: "Enstaka användare"
+ user_placeholder: Ange användarnamn
+ description_placeholder: "Vad används denna nyckel till?"
save: Spara
+ new_key: Ny API nyckel
+ revoked: Upphävd
+ delete: Radera permanent
+ not_shown_again: Denna nyckel kommer inte att visas igen. Försäkra dig om att du har tagit en kopia innan du fortsätter.
continue: Fortsätt
web_hooks:
title: "Webhookar"
@@ -2205,12 +3165,14 @@ sv:
go_back: "Tillbaka till listan"
payload_url: "Försändelse-URL"
payload_url_placeholder: "https://example.com/postreceive"
+ warn_local_payload_url: "Det verkar som du försöker sätta upp en webbkoppling till en lokal url. Händelser levererade till en lokal adress kan skapa sidoeffekter eller oväntade beteenden. Fortsätta?"
secret_invalid: "Hemligheten får inte ha några blanka tecken."
secret_too_short: "Hemligheten bör vara minst 12 tecken."
secret_placeholder: "En alternativ sträng, använd för att generera signature"
event_type_missing: "Du behöver sätta upp åtminstone en händelse."
content_type: "Innehållstyp"
secret: "Hemlighet"
+ event_chooser: "Vilka händelser ska trigga denna webbkoppling?"
wildcard_event: "Skicka mig allt."
individual_event: "Välj enskilda event."
verify_certificate: "Kontrollera TLS-certifikatet för försändelse-URL:en"
@@ -2218,6 +3180,8 @@ sv:
active_notice: "Vi kommer att leverera detaljerna kring eventet när det händer."
categories_filter_instructions: "Relevanta webhookar kommer endast att utlösas om eventet är relaterat med specifika kategorier. Lämna blank för att utlösa webhookar för alla kategorier. "
categories_filter: "Utlösta kategorier"
+ tags_filter_instructions: "Relevanta webbkopplingar kommer enbart att aktiveras om händelsen är relaterad med specifika taggar. Lämna tomt för att trigga webbkopplingar för alla taggar."
+ tags_filter: "Triggade taggar"
groups_filter_instructions: "Relevanta webhookar kommer endast att utlösas om eventet är relaterat med specifierade grupper. Lämna blank för att utlösa webhookar för alla grupper."
groups_filter: "Utlösta grupper"
delete_confirm: "Ta bort den här webhooken?"
@@ -2229,11 +3193,34 @@ sv:
details: "När det finns ett nytt svar, redigerat, borttaget eller återskapat."
user_event:
name: "Användarevent"
+ details: "När en användare loggar in, loggar ut, skapas, godkänns eller uppdateras."
+ group_event:
+ name: "Grupphändelse"
+ details: "När en grupp skapas, uppdateras, eller förstörs."
+ category_event:
+ name: "Kategorihändelse"
+ details: "När en kategori skapas, uppdateras eller förstörs."
+ tag_event:
+ name: "Tagghändelse"
+ details: "När en tagg skapas, uppdateras eller förstörs."
+ flag_event:
+ name: "Flagghändelse"
+ details: "När en flagga skapas, motsätts eller ignoreras."
+ queued_post_event:
+ name: "Godkänt inläggshändelse"
+ details: "När ett köat inlägg skapas, godkänns eller nekas. "
+ reviewable_event:
+ name: "Granskningshändelse"
+ details: "När ett föremål är redo för granskning och när dess status är uppdaterad."
+ notification_event:
+ name: "Notifikationshändelse"
+ details: "När en användare mottager en notifikation i deras flöde."
delivery_status:
title: "Leveransstatus"
inactive: "Inaktiv"
failed: "Misslyckad"
successful: "Lyckad"
+ disabled: "Inaktiverad"
events:
none: "Det finns inga relaterade event."
redeliver: "Leverera igen"
@@ -2270,6 +3257,7 @@ sv:
change_settings: "Ändra inställningar"
change_settings_short: "Inställningar"
howto: "Hur installerar jag tillägg?"
+ official: "Officiellt tillägg"
backups:
title: "Säkerhetskopior"
menu:
@@ -2293,6 +3281,8 @@ sv:
label: "Ladda upp"
title: "Ladda upp en säkerhetskopia till denna instans"
uploading: "Laddar upp..."
+ uploading_progress: "Laddar upp... {{progress}} %"
+ success: "'{{filename}}' har framgångsrikt uppladdats. Filen bearbetas nu och det kan dröja upp till en minut innan den syns i listan."
error: "Ett fel har uppstått vid uppladdning av '{{filename}}': {{message}}"
operations:
is_running: "En operation körs just nu..."
@@ -2305,6 +3295,7 @@ sv:
label: "Säkerhetskopia"
title: "Skapa en säkerhetskopia"
confirm: "Vill du skapa en ny säkerhetskopiering?"
+ without_uploads: "Ja (inkludera inte uppladdningar)"
download:
label: "Ladda ner"
title: "Skicka mail med nedladdningslänk"
@@ -2321,6 +3312,10 @@ sv:
label: "Tillbakarullning"
title: "Gör en tillbakarullning på databasen till ett tidigare fungerande tillstånd."
confirm: "Är du säker på att du vill göra en tillbakarullning på databasen till det tidigare fungerande tillståndet?"
+ location:
+ local: "Lokal lagring"
+ s3: "S3"
+ backup_storage_error: "Misslyckades få tillgång till återställningsdata:%{error_message}"
export_csv:
success: "Export påbörjad, du får en notis via meddelande när processen är genomförd."
failed: "Exporteringen misslyckades. Kontrollera loggarna."
@@ -2340,13 +3335,20 @@ sv:
title: "Anpassa"
long_title: "Sidanpassningar"
preview: "förhandsgranska"
+ explain_preview: "Visa sidan med detta tema aktiverat"
save: "Spara"
new: "Ny"
new_style: "Ny stil"
+ install: "Installera"
delete: "Radera"
+ delete_confirm: 'Är du säker på att du vill radera "%{theme_name}"?'
color: "Färg"
opacity: "Opacitet"
copy: "Kopiera"
+ copy_to_clipboard: "Kopiera till urklipp"
+ copied_to_clipboard: "Kopierad till utklipp"
+ copy_to_clipboard_error: "Fel vid kopiering av data till urklipp"
+ theme_owner: "Ej redigerbar, ägs av:"
email_templates:
title: "E-post"
subject: "Ämne"
@@ -2357,40 +3359,157 @@ sv:
revert_confirm: "Är du säker på att du vill ångra dina ändringar?"
theme:
theme: "Tema"
+ component: "Komponent"
+ components: "Komponenter"
+ theme_name: "Temanamn"
+ component_name: "Komponentnamn"
+ themes_intro: "Välj ett existerande tema eller installera ett nytt för att komma igång"
+ beginners_guide_title: "Nybörjarguide för användning av Discourse-teman"
+ developers_guide_title: "Utvecklarguide för Discourse-teman"
+ browse_themes: "Visa gemenskapens teman"
+ customize_desc: "Anpassa:"
+ title: "Teman"
create: "Skapa"
create_type: "Typ"
create_name: "Namn"
+ long_title: "Ändra färger, CSS och HTML innehåll för din sida"
edit: "Redigera"
+ edit_confirm: "Detta är ett fjärrtema, om du redigerar CSS/HTML kommer dina ändringar att raderas nästa gång du uppdaterar temat."
+ update_confirm: "De lokala ändringar kommer att raderas vid uppdatering. Är du säker på att du vill fortsätta?"
+ update_confirm_yes: "Ja, fortsätt med uppdateringen"
+ common: "Vanlig"
+ desktop: "Skrivbord"
mobile: "Mobil"
settings: "Inställningar"
+ translations: "Översättningar"
+ extra_scss: "Extra SCSS"
preview: "Förhandsgranska"
+ show_advanced: "Visa avancerade fält"
+ hide_advanced: "Dölj avancerade fält"
+ hide_unused_fields: "Dölj oanvända fält"
+ is_default: "Temat är aktiverat som standard"
+ user_selectable: "Temat kan väljas av användare"
+ color_scheme: "Färgpalett"
+ color_scheme_select: "Välj färger som ska användas av temat"
+ custom_sections: "Anpassade avsnitt:"
+ theme_components: "Temakomponenter"
+ add_all_themes: "Lägg till alla teman"
+ convert: "Konvertera"
+ convert_component_alert: "Är du säker på att du vill konvertera den här komponenten till temat? Den kommer att tas bort som en komponent från %{relatives}."
+ convert_component_tooltip: "Konvertera den här komponenten till tema"
+ convert_theme_alert: "Är du säker på att du vill konvertera detta tema till komponent? Det kommer att tas bort som överordnad från %{relatives}."
+ convert_theme_tooltip: "Konvertera detta tema till komponent"
+ inactive_themes: "Inaktiva teman:"
+ inactive_components: "Oanvända komponenter:"
+ broken_theme_tooltip: "Detta tema har fel i sin CSS, HTML eller YAML"
+ disabled_component_tooltip: "Den här komponenten har inaktiverats"
+ default_theme_tooltip: "Detta tema är webbplatsens standardtema"
+ updates_available_tooltip: "Uppdateringar finns tillgängliga för detta tema"
+ and_x_more: "och ytterligare {{count}}."
+ collapse: Förminska
+ uploads: "Uppladningar"
+ no_uploads: "Du kan ladda upp tillgångar som är kopplade till ditt tema, t.ex. teckensnitt och bilder"
+ add_upload: "Lägg till uppladdning"
+ upload_file_tip: "Välj en tillgång att ladda upp (png, woff2, etc...)"
+ variable_name: "Namn på SCSS-variabel:"
+ variable_name_invalid: "Ogiltigt namn för variabel. Enbart alfanumeriska tillåts. Måste börja med en bokstav. Måste vara unikt."
+ variable_name_error:
+ invalid_syntax: "Ogiltigt namn för variabel. Enbart alfanumeriska tillåts. Måste börja med en bokstav."
+ no_overwrite: "Ogiltigt namn för variabel. Får inte skriva över en existerande variabel."
+ must_be_unique: "Ogiltigt namn för variabel. Måste vara unikt."
upload: "Ladda upp"
+ select_component: "Välj en komponent..."
+ unsaved_changes_alert: "Du har inte sparat dina ändringar ännu, vill du förkasta dem och fortsätta?"
+ unsaved_parent_themes: "Du har inte tilldelat komponenter till teman, vill du fortsätta?"
+ discard: "Förkasta"
+ stay: "Stanna"
+ css_html: "Anpassa CSS/HTML"
+ edit_css_html: "Redigera CSS/HTML"
+ edit_css_html_help: "Du har inte redigerat någon CSS eller HTML"
+ delete_upload_confirm: "Radera denna uppladdning? (Temats CSS kan sluta fungera!)"
+ component_on_themes: "Inkludera komponenter för dessa teman"
+ included_components: "Inkluderade komponenter"
+ add_all: "Lägg till alla"
+ import_web_tip: "Förvaringsplats som innehåller tema"
+ import_web_advanced: "Avancerat..."
+ import_file_tip: ".tar.gz, .zip eller .dcstyle.json fil innehåller tema"
+ is_private: "Teman är i en privat git-förvaring"
+ remote_branch: "Filialnamn (valfritt)"
+ public_key: "Ge följande offentliga nyckel tillträde till återköpsavtal:"
+ install: "Installera"
installed: "Installerad"
install_popular: "Populära"
+ install_upload: "Från din enhet"
+ install_git_repo: "Från en git-förvaring"
+ install_create: "Skapa ny"
about_theme: "Om"
+ license: "Licens"
+ version: "Version:"
+ authors: "Författad av:"
+ source_url: "Källa"
enable: "Aktivera"
disable: "Inaktivera"
+ disabled: "Den här komponenten har inaktiverats"
+ disabled_by: "Den här komponenten har inaktiverats av"
+ required_version:
+ error: "Det här temat har automatiskt inaktiverats på grund av att det inte är kompatibelt med denna versionen av Discourse."
+ minimum: "Kräver Discourse version {{version}} eller högre."
+ maximum: "Kräver Discourse version {{version}} eller lägre."
+ component_of: "Komponent av:"
+ update_to_latest: "Uppdatera till senaste"
+ check_for_updates: "Sök efter uppdateringar"
+ updating: "Uppdaterar..."
+ up_to_date: "Temat är uppdaterat, senaste kontroll:"
add: "Lägg till"
+ theme_settings: "Inställningar för tema"
+ no_settings: "Detta tema har inga inställningar"
+ theme_translations: "Översättningar för tema"
+ empty: "Inga objekt"
+ commits_behind:
+ one: "Temat ligger %{count} åtagande efter!"
+ other: "Temat ligger {{count}} åtaganden efter!"
+ compare_commits: "(se nya åtaganden)"
+ repo_unreachable: "Kunde inte kontakta Git-förvaringen för detta tema. Felmeddelande:"
+ imported_from_archive: "Detta tema importerades från en .zip fil"
scss:
text: "CSS"
+ title: "Ange anpassad CSS, vi accepterar alla giltiga CSS och SCSS typer"
header:
text: "Sidhuvud"
+ title: "Ange HTML att visa ovanför sidans rubrik"
+ after_header:
+ text: "Efter rubrik"
+ title: "Ange HTML att visa på alla sidor efter rubriken"
footer:
text: "Sidfot"
+ title: "Ange HTML att visa i sidfoten"
embedded_scss:
text: "Inbäddad CSS"
+ title: "Ange anpassad CSS som ska levereras med inbäddade versionen av kommentarer"
head_tag:
text: ""
title: "HTML som kommer att sättas in före taggen"
body_tag:
text: "