- {{/if}}
{{/d-modal-body}}
diff --git a/app/assets/javascripts/discourse/app/templates/preferences.hbs b/app/assets/javascripts/discourse/app/templates/preferences.hbs
index 45f82f56c9..19f90739eb 100644
--- a/app/assets/javascripts/discourse/app/templates/preferences.hbs
+++ b/app/assets/javascripts/discourse/app/templates/preferences.hbs
@@ -25,17 +25,19 @@
{{i18n "user.preferences_nav.notifications"}}
{{/link-to}}
-
- {{#if siteSettings.tagging_enabled}}
+ {{#if (and model.can_change_tracking_preferences siteSettings.tagging_enabled)}}
{{#link-to "preferences.tags"}}
{{i18n "user.preferences_nav.tags"}}
diff --git a/app/assets/javascripts/discourse/app/templates/user/summary.hbs b/app/assets/javascripts/discourse/app/templates/user/summary.hbs
index de025dbf16..1199c4bf82 100644
--- a/app/assets/javascripts/discourse/app/templates/user/summary.hbs
+++ b/app/assets/javascripts/discourse/app/templates/user/summary.hbs
@@ -151,13 +151,21 @@
{{#if siteSettings.enable_badges}}
{{i18n "user.summary.top_badges"}}
- {{#each model.badges as |badge|}}
- {{badge-card badge=badge count=badge.count username=user.username_lower}}
+
+ {{#if model.badges}}
+
+ {{#each model.badges as |badge|}}
+ {{badge-card badge=badge count=badge.count username=user.username_lower}}
+ {{/each}}
+
{{else}}
{{i18n "user.summary.no_badges"}}
- {{/each}}
+ {{/if}}
+
{{#if moreBadges}}
-
{{#link-to "user.badges" user class="more"}}{{i18n "user.summary.more_badges"}}{{/link-to}}
+ {{#link-to "user.badges" user class="more"}}
+ {{i18n "user.summary.more_badges"}}
+ {{/link-to}}
{{/if}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/widgets/embedded-post.js b/app/assets/javascripts/discourse/app/widgets/embedded-post.js
index 2be773dcaf..e5192f4486 100644
--- a/app/assets/javascripts/discourse/app/widgets/embedded-post.js
+++ b/app/assets/javascripts/discourse/app/widgets/embedded-post.js
@@ -30,7 +30,7 @@ export default createWidget("embedded-post", {
h("div.row", [
this.attach("post-avatar", attrs),
h("div.topic-body", [
- h("div.topic-meta-data", [
+ h("div.topic-meta-data.embedded-reply", [
this.attach("poster-name", attrs),
this.attach("post-link-arrow", {
above: state.above,
diff --git a/app/assets/javascripts/discourse/app/widgets/header.js b/app/assets/javascripts/discourse/app/widgets/header.js
index 784d5265db..0c292dea95 100644
--- a/app/assets/javascripts/discourse/app/widgets/header.js
+++ b/app/assets/javascripts/discourse/app/widgets/header.js
@@ -2,7 +2,6 @@ import DiscourseURL, { userPath } from "discourse/lib/url";
import I18n from "I18n";
import { addExtraUserClasses } from "discourse/helpers/user-avatar";
import { ajax } from "discourse/lib/ajax";
-import { applySearchAutocomplete } from "discourse/lib/search";
import { avatarImg } from "discourse/widgets/post";
import { createWidget } from "discourse/widgets/widget";
import { get } from "@ember/object";
@@ -484,17 +483,9 @@ export default createWidget("header", {
if (this.state.searchVisible) {
schedule("afterRender", () => {
- const $searchInput = $("#search-term");
- $searchInput.focus().select();
-
- applySearchAutocomplete(
- $searchInput,
- this.siteSettings,
- this.appEvents,
- {
- appendSelector: ".menu-panel",
- }
- );
+ const searchInput = document.querySelector("#search-term");
+ searchInput.focus();
+ searchInput.select();
});
}
},
diff --git a/app/assets/javascripts/discourse/app/widgets/post.js b/app/assets/javascripts/discourse/app/widgets/post.js
index 1021ecf76c..a184f91a8f 100644
--- a/app/assets/javascripts/discourse/app/widgets/post.js
+++ b/app/assets/javascripts/discourse/app/widgets/post.js
@@ -755,6 +755,9 @@ export default createWidget("post", {
if (attrs.topicOwner) {
classNames.push("topic-owner");
}
+ if (this.currentUser && attrs.user_id === this.currentUser.id) {
+ classNames.push("current-user-post");
+ }
if (attrs.groupModerator) {
classNames.push("category-moderator");
}
diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu-controls.js b/app/assets/javascripts/discourse/app/widgets/search-menu-controls.js
index dd2f485467..dc329df3e2 100644
--- a/app/assets/javascripts/discourse/app/widgets/search-menu-controls.js
+++ b/app/assets/javascripts/discourse/app/widgets/search-menu-controls.js
@@ -13,10 +13,6 @@ createWidget("search-term", {
return { afterAutocomplete: false };
},
- searchAutocompleteAfterComplete() {
- this.state.afterAutocomplete = true;
- },
-
buildAttributes(attrs) {
return {
type: "text",
@@ -28,12 +24,8 @@ createWidget("search-term", {
},
keyUp(e) {
- if (e.which === 13) {
- if (this.state.afterAutocomplete) {
- this.state.afterAutocomplete = false;
- } else {
- return this.sendWidgetAction("fullSearch");
- }
+ if (e.which === 13 && !this.state.afterAutocomplete) {
+ return this.sendWidgetAction("fullSearch");
}
const val = this.attrs.value;
diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu-results.js b/app/assets/javascripts/discourse/app/widgets/search-menu-results.js
index 3a5885d947..32457733eb 100644
--- a/app/assets/javascripts/discourse/app/widgets/search-menu-results.js
+++ b/app/assets/javascripts/discourse/app/widgets/search-menu-results.js
@@ -10,6 +10,25 @@ import highlightSearch from "discourse/lib/highlight-search";
import { iconNode } from "discourse-common/lib/icon-library";
import renderTag from "discourse/lib/render-tag";
+const suggestionShortcuts = [
+ "in:title",
+ "in:pinned",
+ "status:open",
+ "status:closed",
+ "status:public",
+ "status:noreplies",
+ "order:latest",
+ "order:views",
+ "order:likes",
+ "order:latest_topic",
+];
+
+export function addSearchSuggestion(value) {
+ if (suggestionShortcuts.indexOf(value) === -1) {
+ suggestionShortcuts.push(value);
+ }
+}
+
class Highlighted extends RawHtml {
constructor(html, term) {
super({ html: `${html}` });
@@ -207,6 +226,14 @@ createWidget("search-menu-results", {
tagName: "div.results",
html(attrs) {
+ if (attrs.suggestionKeyword) {
+ return this.attach("search-menu-assistant", {
+ fullTerm: attrs.term,
+ suggestionKeyword: attrs.suggestionKeyword,
+ results: attrs.suggestionResults || [],
+ });
+ }
+
if (attrs.invalidTerm) {
return h("div.no-results", I18n.t("search.too_short"));
}
@@ -320,3 +347,135 @@ createWidget("search-menu-results", {
return content;
},
});
+
+createWidget("search-menu-assistant", {
+ tagName: "ul.search-menu-assistant",
+
+ html(attrs) {
+ if (this.currentUser) {
+ addSearchSuggestion("in:likes");
+ addSearchSuggestion("in:bookmarks");
+ addSearchSuggestion("in:mine");
+ addSearchSuggestion("in:personal");
+ addSearchSuggestion("in:seen");
+ addSearchSuggestion("in:tracking");
+ addSearchSuggestion("in:unseen");
+ addSearchSuggestion("in:watching");
+ }
+ if (this.siteSettings.tagging_enabled) {
+ addSearchSuggestion("in:tagged");
+ addSearchSuggestion("in:untagged");
+ }
+
+ const content = [];
+ const { fullTerm, suggestionKeyword } = attrs;
+ const prefix = fullTerm.split(suggestionKeyword)[0].trim() || null;
+
+ switch (suggestionKeyword) {
+ case "#":
+ attrs.results.forEach((category) => {
+ const slug = prefix
+ ? `${prefix} #${category.slug} `
+ : `#${category.slug} `;
+
+ content.push(
+ this.attach("search-menu-assistant-item", {
+ prefix: prefix,
+ category,
+ slug,
+ })
+ );
+ });
+ break;
+ case "@":
+ attrs.results.forEach((user) => {
+ const slug = prefix
+ ? `${prefix} @${user.username} `
+ : `@${user.username} `;
+
+ content.push(
+ this.attach("search-menu-assistant-item", {
+ prefix: prefix,
+ user,
+ slug,
+ })
+ );
+ });
+ break;
+ default:
+ suggestionShortcuts.forEach((item) => {
+ if (item.includes(suggestionKeyword)) {
+ const slug = prefix ? `${prefix} ${item} ` : `${item} `;
+ content.push(this.attach("search-menu-assistant-item", { slug }));
+ }
+ });
+ break;
+ }
+
+ return content.filter((c, i) => i <= 8);
+ },
+});
+
+createWidget("search-menu-assistant-item", {
+ tagName: "li.search-menu-assistant-item",
+
+ html(attrs) {
+ if (attrs.category) {
+ return h(
+ "a.widget-link.search-link",
+ {
+ attributes: {
+ href: attrs.category.url,
+ },
+ },
+ [
+ h("span.search-item-prefix", attrs.prefix),
+ this.attach("category-link", {
+ category: attrs.category,
+ allowUncategorized: true,
+ }),
+ ]
+ );
+ } else if (attrs.user) {
+ const userResult = [
+ avatarImg("small", {
+ template: attrs.user.avatar_template,
+ username: attrs.user.username,
+ }),
+ h("span.username", formatUsername(attrs.user.username)),
+ ];
+
+ return h(
+ "a.widget-link.search-link",
+ {
+ attributes: {
+ href: "#",
+ },
+ },
+ [
+ h("span.search-item-prefix", attrs.prefix),
+ h("span.search-item-user", userResult),
+ ]
+ );
+ } else {
+ return h(
+ "a.widget-link.search-link",
+ {
+ attributes: {
+ href: "#",
+ },
+ },
+ h("span.search-item-slug", attrs.slug)
+ );
+ }
+ },
+
+ click(e) {
+ const searchInput = document.querySelector("#search-term");
+ searchInput.value = this.attrs.slug;
+ searchInput.focus();
+ this.sendWidgetAction("triggerAutocomplete", this.attrs.slug);
+ e.preventDefault();
+ return false;
+ },
+});
diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu.js b/app/assets/javascripts/discourse/app/widgets/search-menu.js
index 246aeb4626..f34668e243 100644
--- a/app/assets/javascripts/discourse/app/widgets/search-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/search-menu.js
@@ -1,4 +1,5 @@
import { isValidSearchTerm, searchForTerm } from "discourse/lib/search";
+import Category from "discourse/models/category";
import DiscourseURL from "discourse/lib/url";
import { createWidget } from "discourse/widgets/widget";
import discourseDebounce from "discourse-common/lib/debounce";
@@ -6,6 +7,11 @@ import { get } from "@ember/object";
import getURL from "discourse-common/lib/get-url";
import { h } from "virtual-dom";
import { popupAjaxError } from "discourse/lib/ajax-error";
+import userSearch from "discourse/lib/user-search";
+
+const CATEGORY_SLUG_REGEXP = /(\#[a-zA-Z0-9\-:]*)$/gi;
+const USERNAME_REGEXP = /(\@[a-zA-Z0-9\-\_]*)$/gi;
+const SUGGESTIONS_REGEXP = /(in:|status:|order:|:)([a-zA-Z]*)$/gi;
const searchData = {};
@@ -17,6 +23,7 @@ export function initSearchData() {
searchData.typeFilter = null;
searchData.invalidTerm = false;
searchData.topicId = null;
+ searchData.afterAutocomplete = false;
}
initSearchData();
@@ -39,6 +46,52 @@ const SearchHelper = {
const { term, typeFilter, contextEnabled } = searchData;
const searchContext = contextEnabled ? widget.searchContext() : null;
const fullSearchUrl = widget.fullSearchUrl();
+ const matchSuggestions = this.matchesSuggestions();
+
+ if (matchSuggestions) {
+ searchData.noResults = true;
+ searchData.results = [];
+ searchData.loading = false;
+
+ if (matchSuggestions.type === "category") {
+ const categorySearchTerm = matchSuggestions.categoriesMatch[0].replace(
+ "#",
+ ""
+ );
+
+ searchData.suggestionResults = Category.search(categorySearchTerm);
+ searchData.suggestionKeyword = "#";
+ widget.scheduleRerender();
+ } else if (matchSuggestions.type === "username") {
+ const userSearchTerm = matchSuggestions.usernamesMatch[0].replace(
+ "@",
+ ""
+ );
+ const opts = { includeGroups: true, limit: 6 };
+ if (userSearchTerm.length > 0) {
+ opts.term = userSearchTerm;
+ } else {
+ opts.lastSeenUsers = true;
+ }
+
+ userSearch(opts).then((result) => {
+ if (result?.users?.length > 0) {
+ searchData.suggestionResults = result.users;
+ searchData.suggestionKeyword = "@";
+ } else {
+ searchData.noResults = true;
+ searchData.suggestionKeyword = false;
+ }
+ widget.scheduleRerender();
+ });
+ } else {
+ searchData.suggestionKeyword = matchSuggestions[0];
+ widget.scheduleRerender();
+ }
+ return;
+ }
+
+ searchData.suggestionKeyword = false;
if (!isValidSearchTerm(term, widget.siteSettings)) {
searchData.noResults = true;
@@ -73,10 +126,35 @@ const SearchHelper = {
.catch(popupAjaxError)
.finally(() => {
searchData.loading = false;
+ searchData.afterAutocomplete = false;
widget.scheduleRerender();
});
}
},
+
+ matchesSuggestions() {
+ if (searchData.term === undefined) {
+ return false;
+ }
+
+ const categoriesMatch = searchData.term.match(CATEGORY_SLUG_REGEXP);
+
+ if (categoriesMatch) {
+ return { type: "category", categoriesMatch };
+ }
+
+ const usernamesMatch = searchData.term.match(USERNAME_REGEXP);
+ if (usernamesMatch) {
+ return { type: "username", usernamesMatch };
+ }
+
+ const suggestionsMatch = searchData.term.match(SUGGESTIONS_REGEXP);
+ if (suggestionsMatch) {
+ return suggestionsMatch;
+ }
+
+ return false;
+ },
};
export default createWidget("search-menu", {
@@ -132,10 +210,14 @@ export default createWidget("search-menu", {
},
panelContents() {
- const contextEnabled = searchData.contextEnabled;
+ const { contextEnabled, afterAutocomplete } = searchData;
let searchInput = [
- this.attach("search-term", { value: searchData.term, contextEnabled }),
+ this.attach(
+ "search-term",
+ { value: searchData.term, contextEnabled },
+ { state: { afterAutocomplete } }
+ ),
];
if (searchData.term && searchData.loading) {
searchInput.push(h("div.searching", h("div.spinner")));
@@ -157,6 +239,8 @@ export default createWidget("search-menu", {
results: searchData.results,
invalidTerm: searchData.invalidTerm,
searchContextEnabled: searchData.contextEnabled,
+ suggestionKeyword: searchData.suggestionKeyword,
+ suggestionResults: searchData.suggestionResults,
})
);
}
@@ -200,7 +284,7 @@ export default createWidget("search-menu", {
});
},
- mouseDownOutside() {
+ clickOutside() {
this.sendWidgetAction("toggleSearchMenu");
},
@@ -211,7 +295,7 @@ export default createWidget("search-menu", {
return false;
}
- if (searchData.loading || searchData.noResults) {
+ if (searchData.loading) {
return;
}
@@ -306,6 +390,11 @@ export default createWidget("search-menu", {
this.triggerSearch();
},
+ triggerAutocomplete(term) {
+ searchData.afterAutocomplete = true;
+ this.searchTermChanged(term);
+ },
+
fullSearch() {
if (!isValidSearchTerm(searchData.term, this.siteSettings)) {
return;
diff --git a/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js b/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js
index ea41e3b504..0aecb13f9a 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/invite-accept-test.js
@@ -1,6 +1,7 @@
import {
acceptance,
exists,
+ query,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
import { fillIn, visit } from "@ember/test-helpers";
@@ -22,7 +23,12 @@ function setAuthenticationData(hooks, json) {
});
}
-function preloadInvite({ link = false, email_verified_by_link = false } = {}) {
+function preloadInvite({
+ link = false,
+ email_verified_by_link = false,
+ different_external_email = false,
+ hidden_email = false,
+} = {}) {
const info = {
invited_by: {
id: 123,
@@ -33,6 +39,8 @@ function preloadInvite({ link = false, email_verified_by_link = false } = {}) {
},
username: "invited",
email_verified_by_link: email_verified_by_link,
+ different_external_email: different_external_email,
+ hidden_email: hidden_email,
};
if (link) {
@@ -362,6 +370,32 @@ acceptance(
}
);
+acceptance(
+ "Email Invite link with different external email address",
+ function (needs) {
+ needs.settings({ enable_local_logins: false });
+
+ setAuthenticationData(needs.hooks, {
+ auth_provider: "facebook",
+ email: "foobar+different@example.com",
+ email_valid: true,
+ username: "foobar",
+ name: "barfoo",
+ });
+
+ test("display information that email is invalid", async function (assert) {
+ preloadInvite({ different_external_email: true, hidden_email: true });
+
+ await visit("/invites/myvalidinvitetoken");
+
+ assert.equal(
+ query(".bad").textContent.trim(),
+ "Your invitation email does not match the email authenticated by Facebook"
+ );
+ });
+ }
+);
+
acceptance(
"Email Invite link with valid authentication data, valid email token, unverified authentication email",
function (needs) {
diff --git a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js
index 0014ebc2bf..0d5f64bcda 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/preferences-test.js
@@ -524,3 +524,36 @@ acceptance("Security", function (needs) {
);
});
});
+
+acceptance(
+ "User Preferences for staged user and don't allow tracking prefs",
+ function (needs) {
+ needs.settings({
+ allow_changing_staged_user_tracking: false,
+ tagging_enabled: true,
+ });
+ needs.pretender(preferencesPretender);
+
+ test("staged user doesn't show category and tag preferences", async function (assert) {
+ await visit("/u/staged/preferences");
+
+ assert.ok($("body.user-preferences-page").length, "has the body class");
+ assert.equal(
+ currentURL(),
+ "/u/staged/preferences/account",
+ "defaults to account tab"
+ );
+ assert.ok(exists(".user-preferences"), "it shows the preferences");
+
+ assert.ok(
+ !exists(".preferences-nav .nav-categories a"),
+ "categories tab isn't there for staged users"
+ );
+
+ assert.ok(
+ !exists(".preferences-nav .nav-tags a"),
+ "tags tab isn't there for staged users"
+ );
+ });
+ }
+);
diff --git a/app/assets/javascripts/discourse/tests/acceptance/review-test.js b/app/assets/javascripts/discourse/tests/acceptance/review-test.js
index 522fe2c189..23c7b64c13 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/review-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/review-test.js
@@ -197,7 +197,7 @@ acceptance("Review", function (needs) {
publishToMessageBus("/reviewable_counts", {
review_count: 1,
updates: {
- 1234: { status: 1 },
+ 1234: { last_performing_username: "foo", status: 1 },
},
});
@@ -206,5 +206,11 @@ acceptance("Review", function (needs) {
assert.ok(reviewable.className.includes("reviewable-stale"));
assert.equal(count("[data-reviewable-id=1234] .status .approved"), 1);
assert.equal(count(".stale-help"), 1);
+ assert.ok(query(".stale-help").innerText.includes("foo"));
+
+ await visit("/");
+ await visit("/review"); // reload review
+
+ assert.equal(count(".stale-help"), 0);
});
});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/search-test.js b/app/assets/javascripts/discourse/tests/acceptance/search-test.js
index c759a1aa3d..982749a6c1 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/search-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/search-test.js
@@ -2,6 +2,7 @@ import {
acceptance,
count,
exists,
+ query,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
@@ -51,6 +52,22 @@ acceptance("Search - Anonymous", function (needs) {
);
});
+ test("search button toggles search menu", async function (assert) {
+ await visit("/");
+
+ await click("#search-button");
+ assert.ok(exists(".search-menu"));
+
+ await click(".d-header"); // click outside
+ assert.ok(!exists(".search-menu"));
+
+ await click("#search-button");
+ assert.ok(exists(".search-menu"));
+
+ await click("#search-button"); // toggle same button
+ assert.ok(!exists(".search-menu"));
+ });
+
test("search for a tag", async function (assert) {
await visit("/");
@@ -247,3 +264,105 @@ acceptance("Search - with tagging enabled", function (needs) {
assert.equal(tags, "dev slow");
});
});
+
+acceptance("Search - assistant", function (needs) {
+ needs.user();
+
+ needs.pretender((server, helper) => {
+ server.get("/u/search/users", () => {
+ return helper.response({
+ users: [
+ {
+ username: "TeaMoe",
+ name: "TeaMoe",
+ avatar_template:
+ "https://avatars.discourse.org/v3/letter/t/41988e/{size}.png",
+ },
+ {
+ username: "TeamOneJ",
+ name: "J Cobb",
+ avatar_template:
+ "https://avatars.discourse.org/v3/letter/t/3d9bf3/{size}.png",
+ },
+ {
+ username: "kudos",
+ name: "Team Blogeto.com",
+ avatar_template:
+ "/user_avatar/meta.discourse.org/kudos/{size}/62185_1.png",
+ },
+ ],
+ });
+ });
+ });
+
+ test("shows category shortcuts when typing #", async function (assert) {
+ await visit("/");
+
+ await click("#search-button");
+
+ await fillIn("#search-term", "#");
+ await triggerKeyEvent("#search-term", "keyup", 51);
+
+ const firstCategory =
+ ".search-menu .results ul.search-menu-assistant .search-link";
+ assert.ok(exists(query(firstCategory)));
+
+ const firstResultSlug = query(
+ `${firstCategory} .category-name`
+ ).innerText.trim();
+
+ await click(firstCategory);
+ assert.equal(query("#search-term").value, `#${firstResultSlug} `);
+
+ await fillIn("#search-term", "sam #");
+ await triggerKeyEvent("#search-term", "keyup", 51);
+
+ assert.ok(exists(query(firstCategory)));
+ assert.equal(
+ query(
+ ".search-menu .results ul.search-menu-assistant .search-item-prefix"
+ ).innerText,
+ "sam"
+ );
+
+ await click(firstCategory);
+ assert.equal(query("#search-term").value, `sam #${firstResultSlug} `);
+ });
+
+ test("shows in: shortcuts", async function (assert) {
+ await visit("/");
+ await click("#search-button");
+
+ const firstTarget =
+ ".search-menu .results ul.search-menu-assistant .search-link .search-item-slug";
+
+ await fillIn("#search-term", "in:");
+ await triggerKeyEvent("#search-term", "keyup", 51);
+ assert.equal(query(firstTarget).innerText, "in:title");
+
+ await fillIn("#search-term", "sam in:");
+ await triggerKeyEvent("#search-term", "keyup", 51);
+ assert.equal(query(firstTarget).innerText, "sam in:title");
+
+ await fillIn("#search-term", "in:pers");
+ await triggerKeyEvent("#search-term", "keyup", 51);
+ assert.equal(query(firstTarget).innerText, "in:personal");
+ });
+
+ test("shows users when typing @", async function (assert) {
+ await visit("/");
+
+ await click("#search-button");
+
+ await fillIn("#search-term", "@");
+ await triggerKeyEvent("#search-term", "keyup", 51);
+
+ const firstUser =
+ ".search-menu .results ul.search-menu-assistant .search-item-user";
+ const firstUsername = query(firstUser).innerText.trim();
+ assert.equal(firstUsername, "TeaMoe");
+
+ await click(query(firstUser));
+ assert.equal(query("#search-term").value, `@${firstUsername} `);
+ });
+});
diff --git a/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js
index fd0b5c016d..fbec7395ef 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/share-topic-test.js
@@ -30,7 +30,7 @@ acceptance("Share and Invite modal", function (needs) {
"it shows the topic sharing url"
);
- assert.ok(count("button[class^='share-']") > 1, "it shows social sources");
+ assert.ok(count("button[class*='share-']") > 1, "it shows social sources");
assert.ok(
exists(".btn[aria-label='Notify']"),
diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js
index 49654926e8..421c5fdc34 100644
--- a/app/assets/javascripts/discourse/tests/acceptance/topic-test.js
+++ b/app/assets/javascripts/discourse/tests/acceptance/topic-test.js
@@ -40,7 +40,7 @@ acceptance("Topic", function (needs) {
test("Reply as new topic", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("button.share:nth-of-type(1)");
- await click(".reply-as-new-topic a");
+ await click("button.new-topic");
assert.ok(exists(".d-editor-input"), "the composer input is visible");
@@ -59,7 +59,7 @@ acceptance("Topic", function (needs) {
test("Reply as new message", async function (assert) {
await visit("/t/pm-for-testing/12");
await click("button.share:nth-of-type(1)");
- await click(".reply-as-new-topic a");
+ await click("button.new-topic");
assert.ok(exists(".d-editor-input"), "the composer input is visible");
diff --git a/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js
index 0ece7ac630..84e7424046 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/search-fixtures.js
@@ -159,7 +159,7 @@ export default {
uploaded_avatar_id: 3281,
created_at: "2014-04-12T22:22:07.930Z",
cooked:
- 'So you want to set up Discourse on Ubuntu to hack on and develop with?
\n\nWe\'ll assume that you don\'t have Ruby/Rails/Postgre/Redis installed on your Ubuntu system. Let\'s begin!
\n\nAlthough this guide assumes that you are using Ubuntu, but the set-up instructions will work fine for any Debian based distribution.
\n\n(If you want to install Discourse for production use, see our install guide)
\n\nInstall Discourse Dependencies
\n\nRun this script in terminal, to setup Rails development environment:
\n\nbash <(wget -qO- https://raw.githubusercontent.com/techAPJ/install-rails/master/linux)
\n\n \n\nThis will install following new packages on your system:
\n\n\n\nInstall Phantomjs:
\n\nFor 32 bit machine:
\n\ncd /usr/local/share\nsudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-i686.tar.bz2\nsudo tar xvf phantomjs-1.9.8-linux-i686.tar.bz2\nsudo rm phantomjs-1.9.8-linux-i686.tar.bz2\nsudo ln -s /usr/local/share/phantomjs-1.9.8-linux-i686/bin/phantomjs /usr/local/bin/phantomjs\ncd
\n\nFor 64 bit machine:
\n\ncd /usr/local/share\nsudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo tar xvf phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo rm phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo ln -s /usr/local/share/phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs\ncd
\n\n \n\nIn case you have any of this package pre-installed and don\'t want to run entire script, see the script and pick the packages you don\'t have currently installed. The script is fine-tuned for Discourse, and includes all the packages required for Discourse installation.
\n\nNow that we have installed Discourse dependencies, let\'s move on to install Discourse itself.
\n\nClone Discourse
\n\nClone the Discourse repository in ~/discourse folder:
\n\ngit clone https://github.com/discourse/discourse.git ~/discourse
\n\n \n\nSetup Database
\n\nOpen psql prompt as postgre user
\n\nsudo -u postgres psql postgres
\n\n \n\nCreate role with the same name as your ubuntu system username with discourse as password:
\n\nCREATE ROLE discourse WITH LOGIN ENCRYPTED PASSWORD \'discourse\' CREATEDB SUPERUSER;
\n\nIn the above command, I named the role as discourse, this means that my ubuntu system username is discourse. (It is necessary for role name to be same as system username, otherwise migrations will not run)
\n\nCheck that you have successfully created discourse role:
\n\n\\du
\n\n \n\nCreate discourse_development and discourse_test database:
\n\nCREATE DATABASE discourse_development WITH OWNER discourse ENCODING \'UTF8\' TEMPLATE template0;\nCREATE DATABASE discourse_test WITH OWNER discourse ENCODING \'UTF8\' TEMPLATE template0;
\n\n \n\nExit psql prompt by pressing ctrld
\n\nNow access psql prompt in discourse_development database as discourse user:
\n\npsql -d discourse_development -U discourse -h localhost
\n\nWhen prompted for password, provide the password which you set at the time of creating role, if you followed the guide as is, the password is discourse
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n\n\nExit psql prompt by pressing ctrld
\n\nNow access psql prompt in discourse_test database as discourse user:
\n\npsql -d discourse_test -U discourse -h localhost
\n\nWhen prompted for password, provide the password which you set at the time of creating role, if you followed the guide as is, the password is discourse
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n \n\nExit psql prompt by pressing ctrld
\n\nYou have set-up the database successfully!
\n\nBootstrap Discourse
\n\nSwitch to your Discourse folder:
\n\ncd ~/discourse
\n\nInstall the needed gems
\n\nbundle install
\n\n \n\nNow that you have successfully configured database connection, run this command:
\n\nbundle exec rake db:migrate db:test:prepare db:seed_fu
\n\nNow, try running the specs:
\n\nbundle exec rake autospec
\n\n \n\nStart rails server:
\n\nbundle exec rails server
\n\n \n\nYou should now be able to connect to discourse app on http://localhost:3000 - try it out!
\n\n \n\nConfigure Mail and Create New Account
\n\nWe will use MailCatcher to serve emails in development environment. Install and run MailCatcher:
\n\ngem install mailcatcher\nmailcatcher --http-ip 0.0.0.0
\n\nCreate new account:
\n\n \n\nCheck confirmation email by going to MailCatcher web interface at http://localhost:1080/
\n\n \n\nIf you did not receive the email, try running this in console: bundle exec sidekiq -q default
\n\nClick the confirmation link and your account will be activated!
\n\n \n\nAccess Admin
\n\nNow, to make your account as admin, run the following commands in rails console:
\n\nRAILS_ENV=development bundle exec rails c\nu = User.last\nu.admin = true\nu.save
\n\n \n\nOnce you execute the above commands successfully, check out your Discourse account again:
\n\n \n\nCongratulations! You are now the admin of your own Discourse installation!
\n\nHappy hacking!
\n\nIf anything needs to be improved in this guide, feel free to ask on meta.discourse.org, or even better, submit a pull request.
',
+ 'So you want to set up Discourse on Ubuntu to hack on and develop with?
\n\nWe\'ll assume that you don\'t have Ruby/Rails/Postgre/Redis installed on your Ubuntu system. Let\'s begin!
\n\nAlthough this guide assumes that you are using Ubuntu, but the set-up instructions will work fine for any Debian based distribution.
\n\n(If you want to install Discourse for production use, see our install guide)
\n\nInstall Discourse Dependencies
\n\nRun this script in terminal, to setup Rails development environment:
\n\nbash <(wget -qO- https://raw.githubusercontent.com/techAPJ/install-rails/master/linux)
\n\n \n\nThis will install following new packages on your system:
\n\n\n\nInstall Phantomjs:
\n\nFor 32 bit machine:
\n\ncd /usr/local/share\nsudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-i686.tar.bz2\nsudo tar xvf phantomjs-1.9.8-linux-i686.tar.bz2\nsudo rm phantomjs-1.9.8-linux-i686.tar.bz2\nsudo ln -s /usr/local/share/phantomjs-1.9.8-linux-i686/bin/phantomjs /usr/local/bin/phantomjs\ncd
\n\nFor 64 bit machine:
\n\ncd /usr/local/share\nsudo wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo tar xvf phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo rm phantomjs-1.9.8-linux-x86_64.tar.bz2\nsudo ln -s /usr/local/share/phantomjs-1.9.8-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs\ncd
\n\n \n\nIn case you have any of this package pre-installed and don\'t want to run entire script, see the script and pick the packages you don\'t have currently installed. The script is fine-tuned for Discourse, and includes all the packages required for Discourse installation.
\n\nNow that we have installed Discourse dependencies, let\'s move on to install Discourse itself.
\n\nClone Discourse
\n\nClone the Discourse repository in ~/discourse folder:
\n\ngit clone https://github.com/discourse/discourse.git ~/discourse
\n\n \n\nSetup Database
\n\nOpen psql prompt as postgre user
\n\nsudo -u postgres psql postgres
\n\n \n\nCreate role with the same name as your ubuntu system username with discourse as password:
\n\nCREATE ROLE discourse WITH LOGIN ENCRYPTED PASSWORD \'discourse\' CREATEDB SUPERUSER;
\n\nIn the above command, I named the role as discourse, this means that my ubuntu system username is discourse. (It is necessary for role name to be same as system username, otherwise migrations will not run)
\n\nCheck that you have successfully created discourse role:
\n\n\\du
\n\n \n\nCreate discourse_development and discourse_test database:
\n\nCREATE DATABASE discourse_development WITH OWNER discourse ENCODING \'UTF8\' TEMPLATE template0;\nCREATE DATABASE discourse_test WITH OWNER discourse ENCODING \'UTF8\' TEMPLATE template0;
\n\n \n\nExit psql prompt by pressing ctrld
\n\nNow access psql prompt in discourse_development database as discourse user:
\n\npsql -d discourse_development -U discourse -h localhost
\n\nWhen prompted for password, provide the password which you set at the time of creating role, if you followed the guide as is, the password is discourse
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n\n\nExit psql prompt by pressing ctrld
\n\nNow access psql prompt in discourse_test database as discourse user:
\n\npsql -d discourse_test -U discourse -h localhost
\n\nWhen prompted for password, provide the password which you set at the time of creating role, if you followed the guide as is, the password is discourse
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n \n\nExit psql prompt by pressing ctrld
\n\nYou have set-up the database successfully!
\n\nBootstrap Discourse
\n\nSwitch to your Discourse folder:
\n\ncd ~/discourse
\n\nInstall the needed gems
\n\nbundle install
\n\n \n\nNow that you have successfully configured database connection, run this command:
\n\nbundle exec rake db:migrate db:test:prepare db:seed_fu
\n\nNow, try running the specs:
\n\nbundle exec rake autospec
\n\n \n\nStart rails server:
\n\nbundle exec rails server
\n\n \n\nYou should now be able to connect to discourse app on http://localhost:3000 - try it out!
\n\n \n\nConfigure Mail and Create New Account
\n\nWe will use MailCatcher to serve emails in development environment. Install and run MailCatcher:
\n\ngem install mailcatcher\nmailcatcher --http-ip 0.0.0.0
\n\nCreate new account:
\n\n \n\nCheck confirmation email by going to MailCatcher web interface at http://localhost:1080/
\n\n \n\nIf you did not receive the email, try running this in console: bundle exec sidekiq -q default
\n\nClick the confirmation link and your account will be activated!
\n\n \n\nAccess Admin
\n\nNow, to make your account as admin, run the following commands in rails console:
\n\nRAILS_ENV=development bundle exec rails c\nu = User.last\nu.admin = true\nu.save
\n\n \n\nOnce you execute the above commands successfully, check out your Discourse account again:
\n\n \n\nCongratulations! You are now the admin of your own Discourse installation!
\n\nHappy hacking!
\n\nIf anything needs to be improved in this guide, feel free to ask on meta.discourse.org, or even better, submit a pull request.
',
post_number: 1,
post_type: 1,
updated_at: "2015-06-22T17:24:20.607Z",
@@ -249,7 +249,7 @@ export default {
uploaded_avatar_id: 3281,
created_at: "2014-05-19T16:59:51.082Z",
cooked:
- 'So you want to set up Discourse on Mac OS X to hack on and develop with?
\n\nWe\'ll assume that you don\'t have Ruby/Rails/Postgre/Redis installed on your Mac. Let\'s begin!
\n\n(If you want to install Discourse for production use, see our install guide)
\n\nInstall Discourse Dependencies
\n\nRun this script in terminal, to setup Rails development environment:
\n\nbash <(curl -s https://raw.githubusercontent.com/techAPJ/install-rails/master/mac)
\n\nThis script will install following new packages on your system:
\n\n\n\nIn case you have any of this package pre-installed and don\'t want to run entire script, see the script and pick the packages you don\'t have currently installed. The script is fine-tuned for Discourse, and includes all the packages required for Discourse installation.
\n\nNow that we have installed Discourse dependencies, let\'s move on to install Discourse itself.
\n\nClone Discourse
\n\nClone the Discourse repository in ~/discourse folder:
\n\ngit clone https://github.com/discourse/discourse.git ~/discourse
\n\n
\n\n~ indicates home folder, so Discourse source code will be available in your home folder.
\n\nSetup Database
\n\nOpen psql prompt:
\n\npsql postgres
\n\n
\n\nCreate discourse_development and discourse_test database with your account short name specified as role:
\n\nCREATE DATABASE discourse_development WITH OWNER techapj ENCODING \'UTF8\' TEMPLATE template0;\nCREATE DATABASE discourse_test WITH OWNER techapj ENCODING \'UTF8\' TEMPLATE template0;
\n\nNote that in above commands I specified the role as techapj, this means that my short name is techapj, replace this with your own short name.
\n\n
\n\nExit psql prompt by pressing controld
\n\nNow access psql prompt in discourse_development database as your short name user:
\n\npsql -d discourse_development -U techapj -h localhost
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n
\n\nExit psql prompt by pressing controld
\n\nNow access psql prompt in discourse_test database as your short name user:
\n\npsql -d discourse_test -U techapj -h localhost
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n
\n\nExit psql prompt by pressing controld
\n\nYou have set-up the database successfully!
\n\nBootstrap Discourse
\n\nSwitch to your Discourse folder:
\n\ncd ~/discourse
\n\nInstall the needed gems
\n\nbundle install
\n\n
\n\nNow that you have successfully installed gems, run this command:
\n\nbundle exec rake db:migrate db:test:prepare db:seed_fu
\n\nTry running the specs:
\n\nbundle exec rake autospec
\n\n
\n\nAll the tests should pass.
\n\nStart rails server:
\n\nbundle exec rails server
\n\n
\n\nYou should now be able to connect with your Discourse app on http://localhost:3000 - try it out!
\n\n \n\nCreate New Admin
\n\nTo create a new admin, run the following commands in rails console:
\n\nRAILS_ENV=development bundle exec rake admin:create
\n\nJust enter your input as suggested, you can create an admin account.
\n\n \n\n \n\nHappy hacking!
',
+ 'So you want to set up Discourse on Mac OS X to hack on and develop with?
\n\nWe\'ll assume that you don\'t have Ruby/Rails/Postgre/Redis installed on your Mac. Let\'s begin!
\n\n(If you want to install Discourse for production use, see our install guide)
\n\nInstall Discourse Dependencies
\n\nRun this script in terminal, to setup Rails development environment:
\n\nbash <(curl -s https://raw.githubusercontent.com/techAPJ/install-rails/master/mac)
\n\nThis script will install following new packages on your system:
\n\n\n\nIn case you have any of this package pre-installed and don\'t want to run entire script, see the script and pick the packages you don\'t have currently installed. The script is fine-tuned for Discourse, and includes all the packages required for Discourse installation.
\n\nNow that we have installed Discourse dependencies, let\'s move on to install Discourse itself.
\n\nClone Discourse
\n\nClone the Discourse repository in ~/discourse folder:
\n\ngit clone https://github.com/discourse/discourse.git ~/discourse
\n\n
\n\n~ indicates home folder, so Discourse source code will be available in your home folder.
\n\nSetup Database
\n\nOpen psql prompt:
\n\npsql postgres
\n\n
\n\nCreate discourse_development and discourse_test database with your account short name specified as role:
\n\nCREATE DATABASE discourse_development WITH OWNER techapj ENCODING \'UTF8\' TEMPLATE template0;\nCREATE DATABASE discourse_test WITH OWNER techapj ENCODING \'UTF8\' TEMPLATE template0;
\n\nNote that in above commands I specified the role as techapj, this means that my short name is techapj, replace this with your own short name.
\n\n
\n\nExit psql prompt by pressing controld
\n\nNow access psql prompt in discourse_development database as your short name user:
\n\npsql -d discourse_development -U techapj -h localhost
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n
\n\nExit psql prompt by pressing controld
\n\nNow access psql prompt in discourse_test database as your short name user:
\n\npsql -d discourse_test -U techapj -h localhost
\n\nRun following commands, separately:
\n\nCREATE EXTENSION pg_trgm;\nCREATE EXTENSION hstore;
\n\n
\n\nExit psql prompt by pressing controld
\n\nYou have set-up the database successfully!
\n\nBootstrap Discourse
\n\nSwitch to your Discourse folder:
\n\ncd ~/discourse
\n\nInstall the needed gems
\n\nbundle install
\n\n
\n\nNow that you have successfully installed gems, run this command:
\n\nbundle exec rake db:migrate db:test:prepare db:seed_fu
\n\nTry running the specs:
\n\nbundle exec rake autospec
\n\n
\n\nAll the tests should pass.
\n\nStart rails server:
\n\nbundle exec rails server
\n\n
\n\nYou should now be able to connect with your Discourse app on http://localhost:3000 - try it out!
\n\n \n\nCreate New Admin
\n\nTo create a new admin, run the following commands in rails console:
\n\nRAILS_ENV=development bundle exec rake admin:create
\n\nJust enter your input as suggested, you can create an admin account.
\n\n \n\n \n\nHappy hacking!
',
post_number: 1,
post_type: 1,
updated_at: "2015-04-26T06:51:23.549Z",
@@ -339,7 +339,7 @@ export default {
uploaded_avatar_id: null,
created_at: "2014-01-24T15:08:06.111Z",
cooked:
- 'Continuing the discussion from Log of setting up Docker in Virtualbox:
\n\n\n\nWhat is the preferred development environment these days? I have Vagrant up and running as recommended in Discourse as Your First Rails App and Discourse Vagrant Developer Guide, but much of the recent discussion has been about Discourse Docker (which I freely admit I haven\'t really looked at for lack of time).
\n\nFor development purposes, should I carry on using Vagrant for the time being? Or should I be setting up a VM with Ubuntu and then installing Docker and Discourse Docker?
\n\n(As a related side issue, my current production environment was built by following the Discourse Install Guide. Would it be prudent to switch that over to Docker at some point as well? Meaning, is version 1.0 likely to recommend Docker instead of a raw installation? This question deserves a topic of its own in some other category, but it seems best to see what people have to say about development environments before launching a second, better-informed discussion about production environments.)
\n\nSo, for development, Vagrant or Docker?
',
+ 'Continuing the discussion from Log of setting up Docker in Virtualbox:
\n\n\n\nWhat is the preferred development environment these days? I have Vagrant up and running as recommended in Discourse as Your First Rails App and Discourse Vagrant Developer Guide, but much of the recent discussion has been about Discourse Docker (which I freely admit I haven\'t really looked at for lack of time).
\n\nFor development purposes, should I carry on using Vagrant for the time being? Or should I be setting up a VM with Ubuntu and then installing Docker and Discourse Docker?
\n\n(As a related side issue, my current production environment was built by following the Discourse Install Guide. Would it be prudent to switch that over to Docker at some point as well? Meaning, is version 1.0 likely to recommend Docker instead of a raw installation? This question deserves a topic of its own in some other category, but it seems best to see what people have to say about development environments before launching a second, better-informed discussion about production environments.)
\n\nSo, for development, Vagrant or Docker?
',
post_number: 1,
post_type: 1,
updated_at: "2014-01-24T15:08:06.111Z",
diff --git a/app/assets/javascripts/discourse/tests/fixtures/topic.js b/app/assets/javascripts/discourse/tests/fixtures/topic.js
index cf9740bba8..b2cde97a0d 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/topic.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/topic.js
@@ -377,7 +377,7 @@ export default {
uploaded_avatar_id: 40181,
created_at: "2013-02-05T21:32:47.649Z",
cooked:
- 'The application strings are externalized, so localization should be entirely possible with enough translation effort.
Link for unknown-test: link
',
+ 'The application strings are externalized, so localization should be entirely possible with enough translation effort.
Link for unknown-test: link
',
post_number: 2,
post_type: 1,
updated_at: "2013-02-06T10:15:27.965Z",
@@ -400,7 +400,7 @@ export default {
link_counts: [
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales/en.yml",
+ "https://github.com/discourse/discourse/blob/main/config/locales/en.yml",
internal: false,
reflection: false,
clicks: 118,
@@ -473,7 +473,7 @@ export default {
uploaded_avatar_id: 5297,
created_at: "2013-02-06T02:26:24.922Z",
cooked:
- 'Yep, all strings are going through a lookup table.*
\n\nmaster/config/locales
\n\nSo you could replace that lookup table with the "de" one to get German.
\n\n* we didn\'t get all the strings into the lookup table for launch, but the vast, vast majority of them are and the ones that are not, we will fix as we go!
',
+ 'Yep, all strings are going through a lookup table.*
\n\nmaster/config/locales
\n\nSo you could replace that lookup table with the "de" one to get German.
\n\n* we didn\'t get all the strings into the lookup table for launch, but the vast, vast majority of them are and the ones that are not, we will fix as we go!
',
post_number: 3,
post_type: 1,
updated_at: "2014-02-24T05:23:39.211Z",
@@ -496,7 +496,7 @@ export default {
link_counts: [
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales",
+ "https://github.com/discourse/discourse/blob/main/config/locales",
internal: false,
reflection: false,
title:
@@ -2405,7 +2405,7 @@ export default {
links: [
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales/en.yml",
+ "https://github.com/discourse/discourse/blob/main/config/locales/en.yml",
title: null,
fancy_title: null,
internal: false,
@@ -2438,7 +2438,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales",
+ "https://github.com/discourse/discourse/blob/main/config/locales",
title:
"discourse/config/locales at master · discourse/discourse · GitHub",
fancy_title: null,
@@ -2521,7 +2521,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/tree/master/config/locales",
+ "https://github.com/discourse/discourse/tree/main/config/locales",
title:
"discourse/config/locales at master · discourse/discourse · GitHub",
fancy_title: null,
@@ -2566,7 +2566,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales/client.en.yml#L691",
+ "https://github.com/discourse/discourse/blob/main/config/locales/client.en.yml#L691",
title:
"discourse/config/locales/client.en.yml at master · discourse/discourse · GitHub",
fancy_title: null,
@@ -2588,7 +2588,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales/client.nl.yml",
+ "https://github.com/discourse/discourse/blob/main/config/locales/client.nl.yml",
title:
"discourse/config/locales/client.nl.yml at master · discourse/discourse · GitHub",
fancy_title: null,
@@ -2820,7 +2820,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/blob/master/app/assets/javascripts/locales/date_locales.js",
+ "https://github.com/discourse/discourse/blob/main/app/assets/javascripts/locales/date_locales.js",
title: null,
fancy_title: null,
internal: false,
@@ -2885,7 +2885,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales/client.de.yml",
+ "https://github.com/discourse/discourse/blob/main/config/locales/client.de.yml",
title:
"discourse/config/locales/client.de.yml at master · discourse/discourse · GitHub",
fancy_title: null,
@@ -2919,7 +2919,7 @@ export default {
},
{
url:
- "https://github.com/discourse/discourse/blob/master/config/locales/server.de.yml",
+ "https://github.com/discourse/discourse/blob/main/config/locales/server.de.yml",
title:
"discourse/config/locales/server.de.yml at master · discourse/discourse · GitHub",
fancy_title: null,
diff --git a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
index e1b428f0db..7c23b4631f 100644
--- a/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
+++ b/app/assets/javascripts/discourse/tests/fixtures/user-fixtures.js
@@ -190,6 +190,7 @@ export default {
card_image_badge_id: 120,
muted_usernames: [],
can_change_location: true,
+ can_change_tracking_preferences: true,
ignored_usernames: [],
invited_by: {
id: 1,
@@ -2534,6 +2535,7 @@ export default {
ignored_usernames: [],
mailing_list_posts_per_day: 0,
can_change_bio: true,
+ can_change_tracking_preferences: true,
user_api_keys: null,
user_auth_tokens: [],
invited_by: null,
@@ -3322,4 +3324,127 @@ export default {
timezone: "Australia/Brisbane",
},
},
+ "/u/staged.json": {
+ user_badges: [],
+ badges: [],
+ badge_types: [
+ { id: 1, name: "Gold", sort_order: 9 },
+ { id: 2, name: "Silver", sort_order: 8 },
+ { id: 3, name: "Bronze", sort_order: 7 },
+ ],
+ users: [
+ {
+ id: 20,
+ username: "staged",
+ uploaded_avatar_id: null,
+ avatar_template:
+ "/letter_avatar/staged/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+ },
+ {
+ id: -1,
+ username: "system",
+ uploaded_avatar_id: null,
+ avatar_template:
+ "/letter_avatar/system/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+ },
+ ],
+ topics: [],
+ user: {
+ user_option: {
+ text_size_seq: 1,
+ },
+ id: 20,
+ username: "staged",
+ staged: true,
+ uploaded_avatar_id: null,
+ avatar_template:
+ "/letter_avatar/staged/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+ name: "Staged",
+ email: "staged.user@example.com",
+ associated_accounts: [],
+ last_posted_at: "2015-05-07T15:23:35.074Z",
+ last_seen_at: "2015-05-13T14:34:23.188Z",
+ bio_raw: "",
+ bio_cooked: "",
+ created_at: "2013-02-03T15:19:22.704Z",
+ website: "",
+ location: "",
+ can_edit: true,
+ can_edit_username: true,
+ can_edit_email: true,
+ can_edit_name: true,
+ stats: [],
+ can_send_private_messages: true,
+ can_send_private_message_to_user: true,
+ bio_excerpt: "",
+ trust_level: 0,
+ moderator: false,
+ admin: false,
+ title: null,
+ badge_count: 0,
+ notification_count: 0,
+ has_title_badges: false,
+ custom_fields: {},
+ user_fields: {},
+ pending_count: 0,
+ post_count: 0,
+ can_be_deleted: true,
+ can_delete_all_posts: true,
+ locale: "",
+ email_digests: false,
+ email_messages_level: 0,
+ email_level: 1,
+ digest_after_minutes: 10080,
+ mailing_list_mode: false,
+ auto_track_topics_after_msecs: 60000,
+ new_topic_duration_minutes: 1440,
+ external_links_in_new_tab: false,
+ dynamic_favicon: true,
+ skip_new_user_tips: false,
+ enable_quoting: true,
+ muted_category_ids: [],
+ regular_category_ids: [],
+ tracked_category_ids: [],
+ watched_category_ids: [],
+ watched_first_post_category_ids: [],
+ private_messages_stats: {},
+ gravatar_avatar_upload_id: 5275,
+ custom_avatar_upload_id: 1573,
+ card_image_badge: "/images/avatar.png",
+ card_image_badge_id: 120,
+ muted_usernames: [],
+ can_change_location: true,
+ can_change_tracking_preferences: false,
+ ignored_usernames: [],
+ invited_by: {
+ id: 1,
+ username: "sam",
+ uploaded_avatar_id: null,
+ avatar_template:
+ "/letter_avatar/sam/{size}/3_f9720745f5ce6dfc2b5641fca999d934.png",
+ },
+ custom_groups: [],
+ featured_user_badge_ids: [],
+ card_badge: null,
+ user_auth_tokens: [],
+ user_notification_schedule: {
+ enabled: false,
+ day_0_start_time: 480,
+ day_0_end_time: 1020,
+ day_1_start_time: 480,
+ day_1_end_time: 1020,
+ day_2_start_time: 480,
+ day_2_end_time: 1020,
+ day_3_start_time: 480,
+ day_3_end_time: 1020,
+ day_4_start_time: 480,
+ day_4_end_time: 1020,
+ day_5_start_time: 480,
+ day_5_end_time: 1020,
+ day_6_start_time: 480,
+ day_6_end_time: 1020,
+ },
+ timezone: "Australia/Brisbane",
+ },
+ },
};
diff --git a/app/assets/javascripts/discourse/tests/integration/components/pick-files-button-tests.js b/app/assets/javascripts/discourse/tests/integration/components/pick-files-button-tests.js
new file mode 100644
index 0000000000..4c82e9d510
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/integration/components/pick-files-button-tests.js
@@ -0,0 +1,78 @@
+import componentTest, {
+ setupRenderingTest,
+} from "discourse/tests/helpers/component-test";
+import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
+import hbs from "htmlbars-inline-precompile";
+import { triggerEvent } from "@ember/test-helpers";
+import sinon from "sinon";
+
+function createBlob(mimeType, extension) {
+ const blob = new Blob(["content"], {
+ type: mimeType,
+ });
+ blob.name = `filename${extension}`;
+ return blob;
+}
+
+discourseModule(
+ "Integration | Component | pick-files-button",
+ function (hooks) {
+ setupRenderingTest(hooks);
+
+ componentTest(
+ "it shows alert if a file with an unsupported extension was chosen",
+ {
+ skip: true,
+ template: hbs`
+ {{pick-files-button
+ acceptedFileTypes=this.acceptedFileTypes
+ onFilesChosen=this.onFilesChosen}}`,
+
+ beforeEach() {
+ const expectedExtension = ".json";
+ this.set("acceptedFileTypes", [expectedExtension]);
+ this.set("onFilesChosen", () => {});
+ },
+
+ async test(assert) {
+ sinon.stub(bootbox, "alert");
+
+ const wrongExtension = ".txt";
+ const file = createBlob("text/json", wrongExtension);
+
+ await triggerEvent("input#file-input", "change", { files: [file] });
+
+ assert.ok(bootbox.alert.calledOnce);
+ },
+ }
+ );
+
+ componentTest(
+ "it shows alert if a file with an unsupported MIME type was chosen",
+ {
+ skip: true,
+ template: hbs`
+ {{pick-files-button
+ acceptedFileTypes=this.acceptedFileTypes
+ onFilesChosen=this.onFilesChosen}}`,
+
+ beforeEach() {
+ const expectedMimeType = "text/json";
+ this.set("acceptedFileTypes", [expectedMimeType]);
+ this.set("onFilesChosen", () => {});
+ },
+
+ async test(assert) {
+ sinon.stub(bootbox, "alert");
+
+ const wrongMimeType = "text/plain";
+ const file = createBlob(wrongMimeType, ".json");
+
+ await triggerEvent("input#file-input", "change", { files: [file] });
+
+ assert.ok(bootbox.alert.calledOnce);
+ },
+ }
+ );
+ }
+);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/themes-list-test.js b/app/assets/javascripts/discourse/tests/integration/components/themes-list-test.js
index 375340ae6e..cb820a2160 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/themes-list-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/themes-list-test.js
@@ -6,26 +6,37 @@ import componentTest, {
import {
count,
discourseModule,
+ exists,
+ query,
queryAll,
} from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
+import { click, fillIn } from "@ember/test-helpers";
+
+function createThemes(itemsCount, customAttributesCallback) {
+ return [...Array(itemsCount)].map((_, i) => {
+ const attrs = { name: `Theme ${i + 1}` };
+ if (customAttributesCallback) {
+ Object.assign(attrs, customAttributesCallback(i + 1));
+ }
+ return Theme.create(attrs);
+ });
+}
discourseModule("Integration | Component | themes-list", function (hooks) {
setupRenderingTest(hooks);
componentTest("current tab is themes", {
template: hbs`{{themes-list themes=themes components=components currentTab=currentTab}}`,
beforeEach() {
- this.themes = [1, 2, 3, 4, 5].map((num) =>
- Theme.create({ name: `Theme ${num}` })
- );
- this.components = [1, 2, 3, 4, 5].map((num) =>
- Theme.create({
- name: `Child ${num}`,
+ this.themes = createThemes(5);
+ this.components = createThemes(5, (n) => {
+ return {
+ name: `Child ${n}`,
component: true,
- parentThemes: [this.themes[num - 1]],
+ parentThemes: [this.themes[n - 1]],
parent_themes: [1, 2, 3, 4, 5],
- })
- );
+ };
+ });
this.setProperties({
themes: this.themes,
components: this.components,
@@ -94,17 +105,15 @@ discourseModule("Integration | Component | themes-list", function (hooks) {
componentTest("current tab is components", {
template: hbs`{{themes-list themes=themes components=components currentTab=currentTab}}`,
beforeEach() {
- this.themes = [1, 2, 3, 4, 5].map((num) =>
- Theme.create({ name: `Theme ${num}` })
- );
- this.components = [1, 2, 3, 4, 5].map((num) =>
- Theme.create({
- name: `Child ${num}`,
+ this.themes = createThemes(5);
+ this.components = createThemes(5, (n) => {
+ return {
+ name: `Child ${n}`,
component: true,
- parentThemes: [this.themes[num - 1]],
+ parentThemes: [this.themes[n - 1]],
parent_themes: [1, 2, 3, 4, 5],
- })
- );
+ };
+ });
this.setProperties({
themes: this.themes,
components: this.components,
@@ -144,4 +153,139 @@ discourseModule("Integration | Component | themes-list", function (hooks) {
);
},
});
+
+ componentTest(
+ "themes filter is not visible when there are less than 10 themes",
+ {
+ template: hbs`{{themes-list themes=themes components=[] currentTab=currentTab}}`,
+
+ beforeEach() {
+ const themes = createThemes(9);
+ this.setProperties({
+ themes,
+ currentTab: THEMES,
+ });
+ },
+ async test(assert) {
+ assert.ok(
+ !exists(".themes-list-filter"),
+ "filter input not shown when we have fewer than 10 themes"
+ );
+ },
+ }
+ );
+
+ componentTest(
+ "themes filter keeps themes whose names include the filter term",
+ {
+ template: hbs`{{themes-list themes=themes components=[] currentTab=currentTab}}`,
+
+ beforeEach() {
+ const themes = ["osama", "OsAmaa", "osAMA 1234"]
+ .map((name) => Theme.create({ name: `Theme ${name}` }))
+ .concat(createThemes(7));
+ this.setProperties({
+ themes,
+ currentTab: THEMES,
+ });
+ },
+ async test(assert) {
+ assert.ok(exists(".themes-list-filter"));
+ await fillIn(".themes-list-filter .filter-input", " oSAma ");
+ assert.deepEqual(
+ Array.from(queryAll(".themes-list-item .name")).map((node) =>
+ node.textContent.trim()
+ ),
+ ["Theme osama", "Theme OsAmaa", "Theme osAMA 1234"],
+ "only themes whose names include the filter term are shown"
+ );
+ },
+ }
+ );
+
+ componentTest(
+ "switching between themes and components tabs keeps the filter visible only if both tabs have at least 10 items",
+ {
+ template: hbs`{{themes-list themes=themes components=components currentTab=currentTab}}`,
+
+ beforeEach() {
+ const themes = createThemes(10, (n) => {
+ return { name: `Theme ${n}${n}` };
+ });
+ const components = createThemes(5, (n) => {
+ return {
+ name: `Component ${n}${n}`,
+ component: true,
+ parent_themes: [1],
+ parentThemes: [1],
+ };
+ });
+ this.setProperties({
+ themes,
+ components,
+ currentTab: THEMES,
+ });
+ },
+ async test(assert) {
+ await fillIn(".themes-list-filter .filter-input", "11");
+ assert.equal(
+ query(".themes-list-container").textContent.trim(),
+ "Theme 11",
+ "only 1 theme is shown"
+ );
+ await click(".themes-list-header .components-tab");
+ assert.ok(
+ !exists(".themes-list-filter"),
+ "filter input/term do not persist when we switch to the other" +
+ " tab because it has fewer than 10 items"
+ );
+ assert.deepEqual(
+ Array.from(queryAll(".themes-list-item .name")).map((node) =>
+ node.textContent.trim()
+ ),
+ [
+ "Component 11",
+ "Component 22",
+ "Component 33",
+ "Component 44",
+ "Component 55",
+ ],
+ "all components are shown"
+ );
+
+ this.set(
+ "components",
+ this.components.concat(
+ createThemes(5, (n) => {
+ n += 5;
+ return {
+ name: `Component ${n}${n}`,
+ component: true,
+ parent_themes: [1],
+ parentThemes: [1],
+ };
+ })
+ )
+ );
+ assert.ok(
+ exists(".themes-list-filter"),
+ "filter is now shown for the components tab"
+ );
+
+ await fillIn(".themes-list-filter .filter-input", "66");
+ assert.equal(
+ query(".themes-list-container").textContent.trim(),
+ "Component 66",
+ "only 1 component is shown"
+ );
+
+ await click(".themes-list-header .themes-tab");
+ assert.equal(
+ query(".themes-list-container").textContent.trim(),
+ "Theme 66",
+ "filter term persisted between tabs because both have more than 10 items"
+ );
+ },
+ }
+ );
});
diff --git a/app/assets/javascripts/pretty-text/addon/emoji/version.js b/app/assets/javascripts/pretty-text/addon/emoji/version.js
index a57796bd77..5b50dd5dec 100644
--- a/app/assets/javascripts/pretty-text/addon/emoji/version.js
+++ b/app/assets/javascripts/pretty-text/addon/emoji/version.js
@@ -1,4 +1,4 @@
// DO NOT EDIT THIS FILE!!!
// Update it by running `rake javascript:update_constants`
-export const IMAGE_VERSION = "9";
+export const IMAGE_VERSION = "10";
diff --git a/app/assets/javascripts/select-kit/addon/components/icon-picker.js b/app/assets/javascripts/select-kit/addon/components/icon-picker.js
index 69b7767b1d..6d506ab64d 100644
--- a/app/assets/javascripts/select-kit/addon/components/icon-picker.js
+++ b/app/assets/javascripts/select-kit/addon/components/icon-picker.js
@@ -4,10 +4,10 @@ import {
enableMissingIconWarning,
} from "discourse-common/lib/icon-library";
import MultiSelectComponent from "select-kit/components/multi-select";
-import { ajax } from "discourse/lib/ajax";
import { computed } from "@ember/object";
import { isDevelopment } from "discourse-common/config/environment";
import { makeArray } from "discourse-common/lib/helpers";
+import { ajax } from "select-kit/lib/ajax-helper";
export default MultiSelectComponent.extend({
pluginApiIdentifiers: ["icon-picker"],
diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js
index 26f8b810bb..9e8b734e55 100644
--- a/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js
+++ b/app/assets/javascripts/select-kit/addon/components/select-kit/select-kit-row.js
@@ -69,6 +69,10 @@ export default Component.extend(UtilsMixin, {
);
}),
+ dasherizedTitle: computed("title", function () {
+ return (this.title || "").replace(".", "-").dasherize();
+ }),
+
label: computed("rowLabel", "item.label", "title", "rowName", function () {
const label =
this.rowLabel ||
diff --git a/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js b/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js
new file mode 100644
index 0000000000..4cd5d97ece
--- /dev/null
+++ b/app/assets/javascripts/select-kit/addon/lib/ajax-helper.js
@@ -0,0 +1,8 @@
+let ajax;
+if (window.Discourse) {
+ ajax = requirejs("discourse/lib/ajax").ajax;
+} else {
+ ajax = requirejs("wizard/lib/ajax").ajax;
+}
+
+export { ajax };
diff --git a/app/assets/javascripts/select-kit/addon/mixins/tags.js b/app/assets/javascripts/select-kit/addon/mixins/tags.js
index 400e921dfc..fa3dfabd7e 100644
--- a/app/assets/javascripts/select-kit/addon/mixins/tags.js
+++ b/app/assets/javascripts/select-kit/addon/mixins/tags.js
@@ -1,6 +1,6 @@
import I18n from "I18n";
import Mixin from "@ember/object/mixin";
-import { ajax } from "discourse/lib/ajax";
+import { ajax } from "select-kit/lib/ajax-helper";
import getURL from "discourse-common/lib/get-url";
import { isEmpty } from "@ember/utils";
import { makeArray } from "discourse-common/lib/helpers";
diff --git a/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs b/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs
index 8672c07bec..8f68a91b5e 100644
--- a/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs
+++ b/app/assets/javascripts/select-kit/addon/templates/components/multi-select.hbs
@@ -10,7 +10,9 @@
{{#select-kit/select-kit-body selectKit=selectKit id=(concat selectKit.uniqueID "-body")}}
{{#if selectKit.isLoading}}
- {{loading-spinner size="small"}}
+ {{#if site}}
+ {{loading-spinner size="small"}}
+ {{/if}}
{{else}}
{{#if selectKit.filter}}
diff --git a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs
index 9927703f52..2882367dca 100644
--- a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs
+++ b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-filter.hbs
@@ -1,4 +1,5 @@
{{#unless isHidden}}
+ {{!-- filter-input-search prevents 1password from attempting autocomplete --}}
{{input
tabindex=-1
class="filter-input"
@@ -6,6 +7,7 @@
autocomplete="discourse"
autocorrect="off"
autocapitalize="off"
+ name="filter-input-search"
autofocus=selectKit.options.autofocus
spellcheck=false
value=(readonly selectKit.filter)
diff --git a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-row.hbs b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-row.hbs
index b27647f8b4..e3e3dcbe1c 100644
--- a/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-row.hbs
+++ b/app/assets/javascripts/select-kit/addon/templates/components/select-kit/select-kit-row.hbs
@@ -1,5 +1,5 @@
{{#each icons as |icon|}}
- {{d-icon icon translatedtitle=(dasherize title)}}
+ {{d-icon icon translatedtitle=dasherizedTitle}}
{{/each}}
diff --git a/app/assets/javascripts/wizard/lib/ajax.js b/app/assets/javascripts/wizard/lib/ajax.js
index ec0dd1c5cd..670747e807 100644
--- a/app/assets/javascripts/wizard/lib/ajax.js
+++ b/app/assets/javascripts/wizard/lib/ajax.js
@@ -14,11 +14,20 @@ export function getToken() {
}
export function ajax(args) {
+ let url;
+
+ if (arguments.length === 2) {
+ url = arguments[0];
+ args = arguments[1];
+ } else {
+ url = args.url;
+ }
+
return new Promise((resolve, reject) => {
args.headers = { "X-CSRF-Token": getToken() };
args.success = (data) => run(null, resolve, data);
args.error = (xhr) => run(null, reject, xhr);
- args.url = getUrl(args.url);
+ args.url = getUrl(url);
jQuery.ajax(args);
});
}
diff --git a/app/assets/stylesheets/color_definitions.scss b/app/assets/stylesheets/color_definitions.scss
index 9e4174e47a..2990ac7e71 100644
--- a/app/assets/stylesheets/color_definitions.scss
+++ b/app/assets/stylesheets/color_definitions.scss
@@ -41,6 +41,7 @@
--secondary-high: #{$secondary-high};
--secondary-very-high: #{$secondary-very-high};
+ --tertiary-very-low: #{$tertiary-very-low};
--tertiary-low: #{$tertiary-low};
--tertiary-medium: #{$tertiary-medium};
--tertiary-high: #{$tertiary-high};
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index 045235d3c1..4eeb081142 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -295,6 +295,37 @@
width: 100%;
}
}
+
+ .themes-list-filter {
+ display: flex;
+ align-items: center;
+ position: sticky;
+ top: 0;
+ background: var(--secondary);
+ z-index: z("base");
+ height: 3em;
+
+ .d-icon {
+ position: absolute;
+ padding-left: 0.5em;
+ }
+
+ .filter-input {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ border: 0;
+ padding-left: 2em;
+
+ &:focus {
+ outline: 0;
+
+ ~ .d-icon {
+ color: var(--tertiary-hover);
+ }
+ }
+ }
+ }
}
.theme.settings {
diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss
index 4ef29d0a97..7572adc88b 100644
--- a/app/assets/stylesheets/common/base/_index.scss
+++ b/app/assets/stylesheets/common/base/_index.scss
@@ -33,6 +33,7 @@
@import "modal";
@import "not-found";
@import "onebox";
+@import "personal-message";
@import "popup-menu";
@import "redirection";
@import "request_access";
diff --git a/app/assets/stylesheets/common/base/compose.scss b/app/assets/stylesheets/common/base/compose.scss
index 31ddf209d8..4ff99259e3 100644
--- a/app/assets/stylesheets/common/base/compose.scss
+++ b/app/assets/stylesheets/common/base/compose.scss
@@ -322,7 +322,7 @@
display: flex;
align-items: center;
margin-right: auto;
- button {
+ .btn-primary {
flex: 0 0 auto;
}
.cancel {
diff --git a/app/assets/stylesheets/common/base/edit-category.scss b/app/assets/stylesheets/common/base/edit-category.scss
index f454f49b80..0fcbd51823 100644
--- a/app/assets/stylesheets/common/base/edit-category.scss
+++ b/app/assets/stylesheets/common/base/edit-category.scss
@@ -158,7 +158,8 @@ div.edit-category {
padding: 0 1.5em 2em 0;
}
- .category-topic-auto-close-hours {
+ .category-topic-auto-close-hours,
+ .category-default-slow-mode-seconds {
width: 200px;
}
}
diff --git a/app/assets/stylesheets/common/base/groups.scss b/app/assets/stylesheets/common/base/groups.scss
index d14ff4a51e..763ad34eed 100644
--- a/app/assets/stylesheets/common/base/groups.scss
+++ b/app/assets/stylesheets/common/base/groups.scss
@@ -14,8 +14,7 @@
.groups-header-filters-name,
.groups-header-filters-type {
- margin-right: 5px;
- margin-bottom: 0.5em;
+ margin: 0 0.5em 0.5em 0;
}
&:last-child {
@@ -28,65 +27,68 @@
}
.groups-boxes {
+ display: grid;
+ grid-gap: 1em;
+ grid-template-columns: repeat(4, 1fr);
margin: 1em 0;
width: 100%;
- @supports (display: grid) {
- display: grid;
- grid-template-columns: repeat(4, 24%);
- grid-column-gap: 1.333%;
- grid-row-gap: 1em;
- @include breakpoint("tablet") {
- grid-template-columns: repeat(3, 32%);
- grid-column-gap: 2%;
- }
- @include breakpoint("mobile-large") {
- grid-template-columns: 100%;
- }
+
+ @include breakpoint("medium") {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ @include breakpoint("mobile-extra-large") {
+ grid-template-columns: repeat(2, 1fr);
}
.group-box {
- @include breakpoint("mobile-large") {
- margin: 0;
- }
display: flex;
box-sizing: border-box;
cursor: pointer;
border: 1px solid var(--primary-low);
color: var(--primary);
+
.discourse-no-touch & {
transition: all 0.25s;
+
&:hover {
box-shadow: shadow("card");
}
}
+
.group-membership {
color: var(--primary-medium);
margin-top: auto;
padding-top: 1em;
+
.is-group-owner,
.is-group-member {
color: var(--success);
}
}
+
.group-box-inner {
padding: 1em;
width: 100%;
display: flex;
flex-direction: column;
box-sizing: border-box;
+ min-height: 8em;
+
.group-info-wrapper {
+ align-items: center;
display: flex;
- overflow: hidden;
flex: 0 1 auto;
+ margin-bottom: 0.25em;
+ min-height: 40px;
+ overflow: hidden;
+
.group-avatar-flair {
- margin-top: 0.2em;
- margin-right: 8px;
- flex: 0 0 auto;
+ margin-right: 0.5em;
}
+
.group-info {
- flex: 1 0 auto;
- margin-bottom: 1em;
- width: 70%;
+ flex: 1 1 auto;
span {
width: 100%;
@@ -95,6 +97,7 @@
}
}
}
+
.group-user-count {
display: flex;
align-items: center;
diff --git a/app/assets/stylesheets/common/base/modal.scss b/app/assets/stylesheets/common/base/modal.scss
index a74f4c1ecf..51a27ce4eb 100644
--- a/app/assets/stylesheets/common/base/modal.scss
+++ b/app/assets/stylesheets/common/base/modal.scss
@@ -631,7 +631,9 @@
&.single-tab {
background: none;
color: var(--primary);
- padding: 4px 0;
+ padding: 0;
+ font-size: var(--font-up-3);
+ font-weight: bold;
}
}
}
@@ -838,166 +840,6 @@
}
}
-.create-invite-modal,
-.share-topic-modal {
- .input-group {
- margin-bottom: 0.5em;
-
- &:last-child {
- margin-bottom: 0;
- }
-
- input[type="text"] {
- width: 100%;
- }
-
- textarea#invite-message,
- &.invite-to-topic input[type="text"],
- .group-chooser,
- .user-chooser,
- .future-date-input-selector {
- width: 100%;
- }
-
- &.invite-to-topic input[type="radio"] {
- margin-left: 10px;
- }
-
- label .d-icon {
- color: var(--primary-medium);
- margin-right: 10px;
- }
- }
-
- .input-group input[type="text"],
- .input-group .btn,
- .user-chooser .select-kit-header,
- .future-date-input .select-kit-header {
- height: 34px;
- }
-
- .invite-input-with-button {
- display: flex;
-
- .btn {
- margin-left: 3px;
- }
- }
-
- .input-group.input-expires-at,
- .input-group.input-email,
- .input-group.invite-max-redemptions {
- margin-bottom: 0;
-
- input[type="text"] {
- width: unset;
- }
- }
-
- .future-date-input {
- .date-picker-wrapper {
- input {
- margin: 0;
- }
- }
-
- .control-group:nth-child(2),
- .control-group:nth-child(3) {
- display: inline-block;
- margin-bottom: 0;
- width: 49%;
-
- input {
- margin-bottom: 0;
- width: 150px;
- }
- }
- }
-
- .input-group.input-email {
- align-items: baseline;
- display: flex;
-
- label {
- display: inline;
- }
-
- .invite-input-with-button {
- display: inline-flex;
- flex: 1;
-
- input[type="text"] {
- flex: 1;
- margin-left: 5px;
- }
- }
- }
-
- .invite-max-redemptions {
- label {
- display: inline;
- }
-
- input {
- width: 80px;
- }
- }
-
- .invite-to-topic {
- #choose-topic-title {
- margin-bottom: 0;
- }
- }
-
- .show-advanced {
- margin-left: auto;
- margin-right: 0;
- }
-}
-
-.create-invite-modal {
- .input-group {
- textarea#invite-message,
- &.invite-to-topic input[type="text"],
- .group-chooser,
- .user-chooser,
- .future-date-input-selector {
- margin-left: 25px;
- width: calc(100% - 25px);
- }
- }
-}
-
-.share-topic-modal {
- form {
- margin-bottom: 0;
- }
- .sources {
- display: flex;
- flex-wrap: wrap;
- align-items: stretch;
- button {
- margin-right: 0.5em;
- margin-bottom: 0.5em;
- }
- }
-
- .share-twitter {
- .d-icon {
- color: var(--twitter);
- }
- }
- .share-facebook {
- .d-icon {
- color: var(--facebook);
- }
- }
-
- .invite-users {
- margin: 1em 0;
- }
-}
-
.group-add-members-modal {
.input-group {
margin-bottom: 0.5em;
diff --git a/app/assets/stylesheets/common/base/personal-message.scss b/app/assets/stylesheets/common/base/personal-message.scss
new file mode 100644
index 0000000000..a1dc0de11f
--- /dev/null
+++ b/app/assets/stylesheets/common/base/personal-message.scss
@@ -0,0 +1,156 @@
+.archetype-private_message {
+ --border-radius: 2em;
+
+ @keyframes current-user-background-fade-highlight {
+ 0% {
+ background-color: var(--secondary);
+ border-color: var(--primary-low);
+ }
+ 100% {
+ background-color: var(--tertiary-very-low);
+ border-color: transparent;
+ }
+ }
+
+ .topic-body .cooked {
+ box-sizing: border-box;
+ border: 1px solid var(--primary-low);
+ margin-top: 0.25em;
+ margin-left: -1.35em;
+ padding: 1.5em 1.5em 0.5em 2em;
+ border-radius: 0 var(--border-radius) var(--border-radius)
+ var(--border-radius);
+ }
+
+ .current-user-post {
+ .topic-body .cooked {
+ border: 1px solid transparent;
+ background: var(--tertiary-very-low);
+ }
+ .topic-body.highlighted {
+ .cooked {
+ animation: current-user-background-fade-highlight 2.5s ease-out;
+ }
+ }
+ .embedded-posts {
+ .topic-body .cooked {
+ border: 1px solid var(--primary-low);
+ background: transparent;
+ }
+ }
+ }
+
+ .moderator {
+ .topic-body .cooked {
+ border: 1px solid transparent;
+ }
+ }
+
+ .deleted .topic-body {
+ .cooked {
+ background: var(--danger-low);
+ }
+ }
+
+ .whisper {
+ .topic-body .cooked {
+ background: transparent;
+ border: 2px dashed var(--primary-low);
+ }
+
+ &.my-post .topic-body .cooked {
+ border: 2px dashed var(--tertiary-very-low);
+ }
+ }
+
+ .topic-body.highlighted {
+ animation: none;
+ .cooked {
+ animation: background-fade-highlight 2.5s ease-out;
+ }
+ }
+
+ .topic-avatar,
+ .topic-body {
+ border: none;
+ }
+
+ .post-menu-area {
+ margin-top: 0.5em;
+ }
+
+ .small-action-desc.timegap {
+ flex: 0 0 auto;
+ padding: 0 1em;
+ margin-top: -1.75em;
+ margin-left: -1em;
+ background: var(--secondary);
+ max-width: calc(758px - 1.5em);
+ }
+
+ .post-notice {
+ margin-bottom: 1em;
+ border: none;
+ background: var(--primary-very-low);
+ border-radius: var(--border-radius);
+ margin-left: 1.5em;
+ box-sizing: border-box;
+ padding: 1.5em 2em;
+ }
+
+ .topic-map {
+ margin-left: -1.5em;
+ border: none;
+ border-radius: var(--border-radius);
+ padding: 1.25em;
+
+ section {
+ border: none;
+ }
+ .map:not(.map-collapsed) {
+ .avatars {
+ margin: 0.5em 0;
+ }
+ }
+
+ .participants {
+ margin-bottom: 1.5em;
+ .user {
+ border: none;
+ background: var(--primary-low);
+ padding: 2px 4px;
+ border-radius: var(--border-radius);
+ }
+ }
+ }
+
+ .map:first-of-type .buttons .btn {
+ border: none;
+ border-radius: var(--border-radius);
+ }
+
+ .embedded-posts {
+ border: none;
+ .topic-body {
+ overflow: visible;
+ }
+ .topic-body,
+ .topic-avatar {
+ border: none !important; // overrides some specificity for .bottom
+ }
+ .collapse-down,
+ .collapse-up {
+ display: none;
+ }
+ }
+
+ .timeline-replies {
+ display: flex;
+ align-items: baseline;
+ margin-right: 0.15em;
+ }
+
+ .gap {
+ margin-bottom: 2em;
+ }
+}
diff --git a/app/assets/stylesheets/common/base/search-menu.scss b/app/assets/stylesheets/common/base/search-menu.scss
index 883318421a..b6c6926f22 100644
--- a/app/assets/stylesheets/common/base/search-menu.scss
+++ b/app/assets/stylesheets/common/base/search-menu.scss
@@ -251,6 +251,23 @@
}
}
}
+
+ .search-menu-assistant {
+ min-width: 100%;
+ margin-top: -1em;
+
+ .search-menu-assistant-item {
+ > span {
+ vertical-align: baseline;
+ display: inline-block;
+ }
+ }
+
+ .search-item-user .avatar,
+ .search-item-prefix {
+ margin-right: 0.5em;
+ }
+ }
}
.searching {
diff --git a/app/assets/stylesheets/common/base/share_link.scss b/app/assets/stylesheets/common/base/share_link.scss
index 4fbc56ff9e..69c6a2899f 100644
--- a/app/assets/stylesheets/common/base/share_link.scss
+++ b/app/assets/stylesheets/common/base/share_link.scss
@@ -1,26 +1,80 @@
-// styles that apply to the "share" popup when sharing a link to a post or topic
+// styles that apply to the "share" modal & popup when sharing a link to a post or topic
+
+.link-share-container {
+ display: flex;
+ button {
+ transition-property: background-color, color; // don't transition outline
+ }
+ input {
+ width: 100%;
+ font-size: var(--font-up-1);
+ margin-bottom: 0;
+ &:focus + button {
+ outline: 1px solid var(--tertiary);
+ }
+ }
+}
+
+.link-share-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+
+ button {
+ margin-top: 0.5em;
+ margin-right: 0.5em;
+ }
+
+ .sources {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ }
+
+ .new-topic {
+ margin-right: 0;
+ }
+
+ .alt-actions {
+ display: flex;
+ justify-content: flex-end;
+ margin-left: auto;
+ }
+}
+
+.share-twitter {
+ .d-icon {
+ color: var(--twitter);
+ }
+}
+.share-facebook {
+ .d-icon {
+ color: var(--facebook);
+ }
+}
+
+// post share popup
#share-link {
position: absolute;
- left: 20px;
z-index: z("dropdown");
box-shadow: shadow("card");
background-color: var(--secondary);
- padding: 8px 8px 4px 8px;
+ padding: 0.5em;
width: 300px;
display: none;
&.visible {
display: block;
}
+
input[type="text"] {
width: 100%;
}
.title {
- margin-bottom: 4px;
+ margin-bottom: 0.5em;
align-items: center;
display: flex;
- justify-content: space-between;
h3 {
font-size: $font-0;
@@ -30,74 +84,166 @@
.date {
font-weight: normal;
color: var(--primary-med-or-secondary-med);
- }
- }
-
- .copy-text {
- display: inline-block;
- position: absolute;
- margin: 5px 5px 5px 15px;
- color: var(--success);
- opacity: 1;
- transition: opacity 0.25s;
- font-size: $font-0;
- &:not(.success) {
- opacity: 0;
- }
- }
- .social-link {
- margin-right: 8px;
- font-size: $font-up-4;
- .d-icon {
- color: var(--tertiary-or-white);
- }
- .d-icon-fab-facebook {
- // Adheres to Facebook brand guidelines
- color: var(--facebook-or-white);
- }
- .d-icon-fab-twitter-square {
- // Adheres to Twitter brand guidelines
- color: var(--twitter-or-white);
- }
- }
-
- input[type="text"] {
- font-size: $font-up-1;
- margin-bottom: 0;
- }
-
- .actions {
- display: flex;
- align-items: flex-end;
- margin-top: 8px;
-
- .sources {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- flex-direction: row;
- max-width: 45%;
-
- .social-link {
- margin-right: 8px;
- }
+ margin-left: 0.5em;
}
- .alt-actions {
- display: flex;
- align-items: center;
- flex: 1;
- justify-content: flex-end;
- height: 36px;
-
- .close-share {
- font-size: $font-up-3;
- color: var(--primary-med-or-secondary-med);
- }
-
- .new-topic {
- margin-right: 16px;
- }
+ .btn.close {
+ margin-left: auto;
+ }
+ }
+}
+
+// topic share modal
+
+.share-topic-modal {
+ form {
+ margin-bottom: 0;
+ }
+
+ .invite-users {
+ display: flex;
+ flex-direction: column;
+ margin: 1.5em 0 0.25em;
+ width: 100%;
+
+ button {
+ margin-top: 0;
+ margin-right: 0;
+ }
+ }
+}
+
+.notify-user-input {
+ display: flex;
+ align-items: stretch;
+
+ .select-kit {
+ width: 100%;
+ }
+
+ .multi-select-header {
+ width: 100%;
+ }
+
+ .select-kit.multi-select .choices {
+ padding: 0;
+ }
+
+ .select-kit.multi-select.is-expanded + button {
+ outline: 1px solid var(--tertiary); // outline the button when the input is outlined, to match height
+ }
+}
+
+// topic invite modal
+
+.create-invite-modal {
+ form {
+ margin: 0;
+ input[type="text"],
+ .btn,
+ .select-kit-header {
+ height: 2.27rem;
+ }
+ }
+
+ input {
+ margin-bottom: 0;
+ }
+
+ label {
+ margin-right: 0.5em;
+ .d-icon {
+ color: var(--primary-medium);
+ margin-right: 0.75em;
+ }
+ }
+
+ textarea {
+ margin-bottom: 0;
+ }
+
+ .input-group:not(:last-of-type) {
+ margin-bottom: 1em;
+ }
+ .input-group.input-expires-at,
+ .input-group.input-email,
+ .input-group.invite-max-redemptions {
+ input[type="text"] {
+ width: unset;
+ }
+ }
+
+ .existing-topic,
+ p {
+ // p is for "no topics found"
+ margin-left: 1.75em;
+ margin-top: 0.25em;
+ }
+
+ .future-date-input {
+ display: grid;
+ grid-template-columns: auto 1fr;
+
+ .control-group:nth-child(1) {
+ grid-column-start: 1;
+ grid-column-end: 3;
+ }
+ .control-group:nth-child(2) {
+ margin-left: 1.75em;
+ margin-right: 1.5em;
+ }
+ .control-group:nth-child(2),
+ .control-group:nth-child(3) {
+ grid-row-start: 2;
+ display: inline-flex;
+ align-items: center;
+ margin-bottom: 0;
+ input {
+ height: 100%;
+ margin-bottom: 0;
+ margin-left: 0.5em;
+ width: 150px;
+ }
+ }
+ }
+
+ .input-group.input-email {
+ display: flex;
+ align-items: baseline;
+ label {
+ display: inline;
+ }
+ }
+
+ .invite-email-container {
+ flex: 1 1 auto;
+ #invite-email {
+ width: 100%;
+ }
+ }
+
+ .invite-max-redemptions {
+ label {
+ display: inline;
+ }
+ input {
+ width: 80px;
+ }
+ }
+
+ .show-advanced {
+ margin-left: auto;
+ margin-right: 0;
+ }
+
+ .input-group {
+ textarea#invite-message,
+ &.invite-to-topic input[type="text"],
+ .group-chooser,
+ .user-chooser,
+ .future-date-input-selector {
+ margin-left: 1.75em;
+ width: calc(100% - 1.75em);
}
}
}
diff --git a/app/assets/stylesheets/common/base/topic-post.scss b/app/assets/stylesheets/common/base/topic-post.scss
index 3238f3eb9a..def3ebb001 100644
--- a/app/assets/stylesheets/common/base/topic-post.scss
+++ b/app/assets/stylesheets/common/base/topic-post.scss
@@ -206,7 +206,6 @@ $quote-share-maxwidth: 150px;
.moderator {
.regular > .cooked {
background-color: var(--highlight-low-or-medium);
- padding: 10px;
}
.clearfix > .topic-meta-data > .names {
span.user-title {
@@ -235,6 +234,53 @@ $quote-share-maxwidth: 150px;
}
}
+.deleted {
+ .regular > .cooked {
+ background-color: var(--danger-low-mid);
+ }
+ .topic-meta-data:not(.embedded-reply) {
+ color: var(--danger);
+
+ .post-info a,
+ a {
+ color: currentColor;
+ }
+
+ .d-icon {
+ color: currentColor;
+ }
+ }
+ nav.post-controls {
+ color: var(--danger);
+
+ .show-replies,
+ button.reply.create {
+ color: var(--danger);
+ .d-icon {
+ color: var(--danger);
+ }
+ }
+ .widget-button {
+ &:hover {
+ color: currentColor;
+ background: var(--danger-low);
+ .d-icon {
+ color: currentColor;
+ }
+ }
+ &.fade-out {
+ opacity: 1;
+ }
+ }
+ .d-icon {
+ color: var(--danger);
+ }
+ }
+ .post-action {
+ color: var(--danger);
+ }
+}
+
// we use aside to hold expandable quotes (versus, say, static blockquotes)
aside.quote {
margin-top: 1em;
diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss
index 57eb631bd2..02745f3cf0 100644
--- a/app/assets/stylesheets/common/components/_index.scss
+++ b/app/assets/stylesheets/common/components/_index.scss
@@ -17,6 +17,7 @@
@import "ignored-user-list";
@import "keyboard_shortcuts";
@import "navs";
+@import "pick-files-button";
@import "relative-time-picker";
@import "share-and-invite-modal";
@import "svg";
diff --git a/app/assets/stylesheets/common/components/pick-files-button.scss b/app/assets/stylesheets/common/components/pick-files-button.scss
new file mode 100644
index 0000000000..c9945168f0
--- /dev/null
+++ b/app/assets/stylesheets/common/components/pick-files-button.scss
@@ -0,0 +1,5 @@
+.pick-files-button {
+ input[type="file"] {
+ display: none;
+ }
+}
diff --git a/app/assets/stylesheets/common/components/share-and-invite-modal.scss b/app/assets/stylesheets/common/components/share-and-invite-modal.scss
index ff164eedb9..9aaedeec6b 100644
--- a/app/assets/stylesheets/common/components/share-and-invite-modal.scss
+++ b/app/assets/stylesheets/common/components/share-and-invite-modal.scss
@@ -2,11 +2,6 @@
.modal-body {
max-width: 475px;
min-width: 320px;
- padding: 0;
- }
-
- .modal-panel {
- padding: 0.667em;
}
.modal-header {
diff --git a/app/assets/stylesheets/common/components/user-card.scss b/app/assets/stylesheets/common/components/user-card.scss
index 82e876602e..d3c5ba832f 100644
--- a/app/assets/stylesheets/common/components/user-card.scss
+++ b/app/assets/stylesheets/common/components/user-card.scss
@@ -237,6 +237,7 @@ $avatar_margin: -50px; // negative margin makes avatars extend above cards
}
.user-card-badge-link,
.more-user-badges {
+ vertical-align: top;
display: inline-block;
}
.more-user-badges a {
diff --git a/app/assets/stylesheets/common/foundation/color_transformations.scss b/app/assets/stylesheets/common/foundation/color_transformations.scss
index 4b1261d170..ca43415a87 100644
--- a/app/assets/stylesheets/common/foundation/color_transformations.scss
+++ b/app/assets/stylesheets/common/foundation/color_transformations.scss
@@ -23,6 +23,7 @@ $secondary-high: dark-light-diff($secondary, $primary, 30%, -35%) !default;
$secondary-very-high: dark-light-diff($secondary, $primary, 7%, -7%) !default;
//tertiary
+$tertiary-very-low: dark-light-diff($tertiary, $secondary, 90%, -75%) !default;
$tertiary-low: dark-light-diff($tertiary, $secondary, 85%, -65%) !default;
$tertiary-medium: dark-light-diff($tertiary, $secondary, 50%, -45%) !default;
$tertiary-high: dark-light-diff($tertiary, $secondary, 20%, -25%) !default;
diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss
index 8b4970ddc3..7e0be8be02 100644
--- a/app/assets/stylesheets/desktop/compose.scss
+++ b/app/assets/stylesheets/desktop/compose.scss
@@ -85,16 +85,17 @@
}
.composer-popup {
+ box-sizing: border-box;
position: absolute;
- width: calc(50% - 45px);
+ width: calc(50% - 30px);
max-width: 724px;
- top: 20px;
+ top: 21px; // grippie height + .reply-to margin-top + .reply-area padding-top
bottom: 10px;
left: 51%;
overflow-y: auto;
z-index: z("composer", "popover");
padding: 10px 10px 35px 10px;
- box-shadow: shadow("card");
+ box-shadow: shadow("dropdown");
background: var(--highlight-medium);
.hide-preview & {
z-index: z("composer", "dropdown") + 1;
@@ -108,6 +109,18 @@
background-color: var(--tertiary-low);
}
+ &.dominating-topic-message {
+ bottom: unset;
+ padding: 2.25em 6em 2.5em 2.25em;
+ p {
+ margin-top: 0;
+ font-size: var(--font-up-1);
+ }
+ button {
+ margin-top: 0.5em;
+ }
+ }
+
h3 {
margin-bottom: 10px;
}
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss
index 7b29f7b285..6ff674301c 100644
--- a/app/assets/stylesheets/desktop/topic-post.scss
+++ b/app/assets/stylesheets/desktop/topic-post.scss
@@ -523,12 +523,6 @@ video {
position: relative;
}
-.deleted {
- .topic-body {
- background-color: var(--danger-low-mid);
- }
-}
-
.post-select {
float: right;
margin-right: 20px;
diff --git a/app/assets/stylesheets/mobile/_index.scss b/app/assets/stylesheets/mobile/_index.scss
index 7d5fcaa7da..6d3cf8a313 100644
--- a/app/assets/stylesheets/mobile/_index.scss
+++ b/app/assets/stylesheets/mobile/_index.scss
@@ -21,6 +21,7 @@
@import "login";
@import "menu-panel";
@import "modal";
+@import "personal-message";
@import "push-notifications-mobile";
@import "reviewables";
@import "ring";
diff --git a/app/assets/stylesheets/mobile/personal-message.scss b/app/assets/stylesheets/mobile/personal-message.scss
new file mode 100644
index 0000000000..d193ed5c96
--- /dev/null
+++ b/app/assets/stylesheets/mobile/personal-message.scss
@@ -0,0 +1,58 @@
+.archetype-private_message {
+ .topic-body .cooked {
+ margin-top: 0.5em;
+ margin-left: 0;
+ padding: 1.5em 1em;
+ border-radius: 0.75em var(--border-radius) var(--border-radius)
+ var(--border-radius);
+ }
+ .topic-avatar {
+ margin-bottom: -1em; // creates bubble overlap
+ }
+ .boxed .contents {
+ padding: 0;
+ }
+
+ .topic-post {
+ margin: 0 0 1em;
+ article {
+ border-top: none;
+ }
+ }
+
+ .topic-body {
+ flex: 1 1 auto;
+ }
+
+ .topic-map {
+ padding: 1em 0.5em;
+ margin-left: 0;
+ }
+
+ .post-notice {
+ padding: 1em;
+ margin: 0 0 1em;
+ width: 100%;
+ }
+
+ .small-action {
+ margin-left: 0;
+ }
+
+ .small-action-desc.timegap {
+ margin-left: 0;
+ padding: 1em 1em 1em 0;
+ }
+
+ .small-action:not(.time-gap) {
+ padding: 1em;
+ }
+
+ .topic-meta-data .names .first.staff {
+ flex-basis: 100%;
+ & + .second,
+ & + .second + .user-title {
+ flex-basis: unset;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss
index 78ad0a6d53..ebfe9441a4 100644
--- a/app/assets/stylesheets/mobile/topic-post.scss
+++ b/app/assets/stylesheets/mobile/topic-post.scss
@@ -87,11 +87,7 @@ span.badge-posts {
&.expand-post {
margin: 10px 0 10px 0;
}
- &.reply {
- .d-icon {
- color: var(--primary-high);
- }
- }
+
&.has-like {
.d-icon {
color: var(--love);
@@ -126,6 +122,10 @@ span.badge-posts {
}
}
+nav.post-controls button.reply .d-icon {
+ color: var(--primary-high);
+}
+
.post-admin-menu {
bottom: -50px;
left: 135px;
@@ -337,12 +337,6 @@ button.select-post {
display: inline-block;
}
-.deleted {
- .topic-body {
- background-color: var(--danger-low-mid);
- }
-}
-
.deleted-user-avatar {
font-size: $font-up-5;
}
diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb
index b60b1a2956..c22d6be7e3 100644
--- a/app/controllers/categories_controller.rb
+++ b/app/controllers/categories_controller.rb
@@ -302,6 +302,7 @@ class CategoriesController < ApplicationController
:mailinglist_mirror,
:all_topics_wiki,
:allow_unlimited_owner_edits_on_first_post,
+ :default_slow_mode_seconds,
:parent_category_id,
:auto_close_hours,
:auto_close_based_on_last_post,
diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb
index 2a063adaed..490ff8c698 100644
--- a/app/controllers/embed_controller.rb
+++ b/app/controllers/embed_controller.rb
@@ -5,14 +5,12 @@ class EmbedController < ApplicationController
skip_before_action :check_xhr, :preload_json, :verify_authenticity_token
- before_action :ensure_embeddable, except: [ :info, :topics ]
before_action :prepare_embeddable, except: [ :info ]
before_action :ensure_api_request, only: [ :info ]
layout 'embed'
rescue_from Discourse::InvalidAccess do
- response.headers.delete('X-Frame-Options')
if current_user.try(:admin?)
@setup_url = "#{Discourse.base_url}/admin/customize/embedding"
@show_reason = true
@@ -24,7 +22,6 @@ class EmbedController < ApplicationController
def topics
discourse_expires_in 1.minute
- response.headers.delete('X-Frame-Options')
unless SiteSetting.embed_topics_list?
render 'embed_topics_error', status: 400
return
@@ -73,6 +70,11 @@ class EmbedController < ApplicationController
def comments
embed_url = params[:embed_url]
embed_username = params[:discourse_username]
+ embed_topic_id = params[:topic_id]&.to_i
+
+ unless embed_topic_id || EmbeddableHost.url_allowed?(embed_url)
+ raise Discourse::InvalidAccess.new('invalid embed host')
+ end
topic_id = nil
if embed_url.present?
@@ -147,6 +149,7 @@ class EmbedController < ApplicationController
private
def prepare_embeddable
+ response.headers.delete('X-Frame-Options')
@embeddable_css_class = ""
embeddable_host = EmbeddableHost.record_for_url(request.referer)
@embeddable_css_class = " class=\"#{embeddable_host.class_name}\"" if embeddable_host.present? && embeddable_host.class_name.present?
@@ -158,19 +161,4 @@ class EmbedController < ApplicationController
def ensure_api_request
raise Discourse::InvalidAccess.new('api key not set') if !is_api?
end
-
- def ensure_embeddable
- if !(Rails.env.development? && current_user&.admin?)
- referer = request.referer
-
- unless referer && EmbeddableHost.url_allowed?(referer)
- raise Discourse::InvalidAccess.new('invalid referer host')
- end
- end
-
- response.headers.delete('X-Frame-Options')
- rescue URI::Error
- raise Discourse::InvalidAccess.new('invalid referer host')
- end
-
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index ec07ac68e0..440deb4894 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -333,19 +333,9 @@ class GroupsController < ApplicationController
def add_members
group = Group.find(params[:id])
- group.public_admission ? ensure_logged_in : guardian.ensure_can_edit!(group)
+ guardian.ensure_can_edit!(group)
+
users = users_from_params.to_a
-
- if group.public_admission
- if !guardian.can_log_group_changes?(group) && current_user != users.first
- raise Discourse::InvalidAccess
- end
-
- unless current_user.staff?
- RateLimiter.new(current_user, "public_group_membership", 3, 1.minute).performed!
- end
- end
-
emails = []
if params[:emails]
params[:emails].split(",").each do |email|
@@ -376,17 +366,10 @@ class GroupsController < ApplicationController
count: usernames_already_in_group.size
))
else
+ notify = params[:notify_users]&.to_s == "true"
uniq_users = users.uniq
uniq_users.each do |user|
- begin
- group.add(user)
- GroupActionLogger.new(current_user, group).log_add_user_to_group(user)
- group.notify_added_to_group(user) if params[:notify_users]&.to_s == "true"
- rescue ActiveRecord::RecordNotUnique
- # Under concurrency, we might attempt to insert two records quickly and hit a DB
- # constraint. In this case we can safely ignore the error and act as if the user
- # was added to the group.
- end
+ add_user_to_group(group, user, notify)
end
emails.each do |email|
@@ -408,6 +391,20 @@ class GroupsController < ApplicationController
end
end
+ def join
+ ensure_logged_in
+ unless current_user.staff?
+ RateLimiter.new(current_user, "public_group_membership", 3, 1.minute).performed!
+ end
+
+ group = Group.find(params[:id])
+ raise Discourse::NotFound unless group
+ raise Discourse::InvalidAccess unless group.public_admission
+
+ return if group.users.exists?(id: current_user.id)
+ add_user_to_group(group, current_user)
+ end
+
def handle_membership_request
group = Group.find_by(id: params[:id])
raise Discourse::InvalidParameters.new(:id) if group.blank?
@@ -467,7 +464,7 @@ class GroupsController < ApplicationController
def remove_member
group = Group.find_by(id: params[:id])
raise Discourse::NotFound unless group
- group.public_exit ? ensure_logged_in : guardian.ensure_can_edit!(group)
+ guardian.ensure_can_edit!(group)
# Maintain backwards compatibility
params[:usernames] = params[:username] if params[:username].present?
@@ -478,16 +475,6 @@ class GroupsController < ApplicationController
'user_ids or usernames or user_emails must be present'
) if users.empty?
- if group.public_exit
- if !guardian.can_log_group_changes?(group) && current_user != users.first
- raise Discourse::InvalidAccess
- end
-
- unless current_user.staff?
- RateLimiter.new(current_user, "public_group_membership", 3, 1.minute).performed!
- end
- end
-
removed_users = []
skipped_users = []
@@ -510,6 +497,21 @@ class GroupsController < ApplicationController
)
end
+ def leave
+ ensure_logged_in
+ unless current_user.staff?
+ RateLimiter.new(current_user, "public_group_membership", 3, 1.minute).performed!
+ end
+
+ group = Group.find_by(id: params[:id])
+ raise Discourse::NotFound unless group
+ raise Discourse::InvalidAccess unless group.public_exit
+
+ if group.remove(current_user)
+ GroupActionLogger.new(current_user, group).log_remove_user_from_group(current_user)
+ end
+ end
+
MAX_NOTIFIED_OWNERS ||= 20
def request_membership
@@ -650,6 +652,16 @@ class GroupsController < ApplicationController
private
+ def add_user_to_group(group, user, notify = false)
+ group.add(user)
+ GroupActionLogger.new(current_user, group).log_add_user_to_group(user)
+ group.notify_added_to_group(user) if notify
+ rescue ActiveRecord::RecordNotUnique
+ # Under concurrency, we might attempt to insert two records quickly and hit a DB
+ # constraint. In this case we can safely ignore the error and act as if the user
+ # was added to the group.
+ end
+
def group_params(automatic: false)
permitted_params =
if automatic
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index f883e4c4d5..270aed283f 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -50,10 +50,13 @@ class InvitesController < ApplicationController
email = Email.obfuscate(invite.email)
# Show email if the user already authenticated their email
+ different_external_email = false
if session[:authentication]
auth_result = Auth::Result.from_session_data(session[:authentication], user: nil)
if invite.email == auth_result.email
email = invite.email
+ else
+ different_external_email = true
end
end
@@ -73,6 +76,10 @@ class InvitesController < ApplicationController
email_verified_by_link: email_verified_by_link
}
+ if different_external_email
+ info[:different_external_email] = true
+ end
+
if staged_user = User.where(staged: true).with_email(invite.email).first
info[:username] = staged_user.username
info[:user_fields] = staged_user.user_fields
diff --git a/app/controllers/qunit_controller.rb b/app/controllers/qunit_controller.rb
index 98f7d70dac..26a1160cb1 100644
--- a/app/controllers/qunit_controller.rb
+++ b/app/controllers/qunit_controller.rb
@@ -10,6 +10,7 @@ class QunitController < ApplicationController
# only used in test / dev
def index
+ raise Discourse::NotFound.new if request.headers["HTTP_X_DISCOURSE_EMBER_CLI"] == "true"
raise Discourse::InvalidAccess.new if Rails.env.production?
end
diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb
index ffd938165a..5857416195 100644
--- a/app/controllers/session_controller.rb
+++ b/app/controllers/session_controller.rb
@@ -589,13 +589,8 @@ class SessionController < ApplicationController
end
def failed_to_login(user)
- message = user.suspend_reason ? "login.suspended_with_reason" : "login.suspended"
-
{
- error: I18n.t(message,
- date: I18n.l(user.suspended_till, format: :date_only),
- reason: Rack::Utils.escape_html(user.suspend_reason)
- ),
+ error: user.suspended_message,
reason: 'suspended'
}
end
diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb
index 60b1275461..abf2b9f054 100644
--- a/app/controllers/user_badges_controller.rb
+++ b/app/controllers/user_badges_controller.rb
@@ -109,14 +109,10 @@ class UserBadgesController < ApplicationController
return render json: failed_json, status: 400
end
- new_is_favorite_value = !user_badge.is_favorite
UserBadge
.where(user_id: user_badge.user_id, badge_id: user_badge.badge_id)
- .update_all(is_favorite: new_is_favorite_value)
+ .update_all(is_favorite: !user_badge.is_favorite)
UserBadge.update_featured_ranks!(user_badge.user_id)
-
- user_badge.is_favorite = new_is_favorite_value
- render_serialized(user_badge, DetailedUserBadgeSerializer, root: :user_badge)
end
private
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 8e77c49090..725694f2b6 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -1079,6 +1079,8 @@ class UsersController < ApplicationController
}
options[:include_staged_users] = !!ActiveModel::Type::Boolean.new.cast(params[:include_staged_users])
+ options[:last_seen_users] = !!ActiveModel::Type::Boolean.new.cast(params[:last_seen_users])
+ options[:limit] = params[:limit].to_i if params[:limit].present?
options[:topic_id] = topic_id if topic_id
options[:category_id] = category_id if category_id
diff --git a/app/jobs/regular/notify_reviewable.rb b/app/jobs/regular/notify_reviewable.rb
index 3205129333..9c3e729fe2 100644
--- a/app/jobs/regular/notify_reviewable.rb
+++ b/app/jobs/regular/notify_reviewable.rb
@@ -19,7 +19,10 @@ class Jobs::NotifyReviewable < ::Jobs::Base
if args[:updated_reviewable_ids].present?
Reviewable.where(id: args[:updated_reviewable_ids]).each do |r|
- payload = { status: r.status }
+ payload = {
+ last_performing_username: args[:performing_username],
+ status: r.status
+ }
all_updates[:admins][r.id] = payload
all_updates[:moderators][r.id] = payload if r.reviewable_by_moderator?
diff --git a/app/jobs/scheduled/clean_up_stylesheet_cache.rb b/app/jobs/scheduled/clean_up_stylesheet_cache.rb
new file mode 100644
index 0000000000..51913a4f4b
--- /dev/null
+++ b/app/jobs/scheduled/clean_up_stylesheet_cache.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Jobs
+ class CleanUpStylesheetCache < ::Jobs::Scheduled
+ every 1.week
+
+ def execute(args)
+ StylesheetCache.clean_up
+ end
+ end
+end
diff --git a/app/jobs/scheduled/process_user_notification_schedules.rb b/app/jobs/scheduled/process_user_notification_schedules.rb
index a82db6e2d5..1e793e01f0 100644
--- a/app/jobs/scheduled/process_user_notification_schedules.rb
+++ b/app/jobs/scheduled/process_user_notification_schedules.rb
@@ -8,8 +8,8 @@ module Jobs
UserNotificationSchedule.enabled.includes(:user).each do |schedule|
begin
schedule.create_do_not_disturb_timings
- rescue
- Rails.logger.warn("Failed to process user_notification_schedule with ID #{schedule.id}")
+ rescue => e
+ Discourse.warn_exception(e, message: "Failed to process user_notification_schedule with ID #{schedule.id}")
end
end
end
diff --git a/app/mailers/group_smtp_mailer.rb b/app/mailers/group_smtp_mailer.rb
index 9107101792..cc9d17f8a9 100644
--- a/app/mailers/group_smtp_mailer.rb
+++ b/app/mailers/group_smtp_mailer.rb
@@ -48,7 +48,7 @@ class GroupSmtpMailer < ActionMailer::Base
locale: SiteSetting.default_locale,
delivery_method_options: delivery_options,
from: from_group.email_username,
- from_alias: I18n.t('email_from', user_name: group_name, site_name: Email.site_title),
+ from_alias: I18n.t('email_from_without_site', user_name: group_name),
html_override: html_override(post),
cc: cc_addresses
)
diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb
index d5a20e0f07..87703304dd 100644
--- a/app/mailers/user_notifications.rb
+++ b/app/mailers/user_notifications.rb
@@ -153,13 +153,22 @@ class UserNotifications < ActionMailer::Base
return unless user_history = opts[:user_history]
- build_email(
- user.email,
- template: "user_notifications.account_silenced",
- locale: user_locale(user),
- reason: user_history.details,
- silenced_till: I18n.l(user.silenced_till, format: :long)
- )
+ if user.silenced_forever?
+ build_email(
+ user.email,
+ template: "user_notifications.account_silenced_forever",
+ locale: user_locale(user),
+ reason: user_history.details
+ )
+ else
+ build_email(
+ user.email,
+ template: "user_notifications.account_silenced",
+ locale: user_locale(user),
+ reason: user_history.details,
+ silenced_till: I18n.l(user.silenced_till, format: :long)
+ )
+ end
end
def account_suspended(user, opts = nil)
@@ -167,13 +176,22 @@ class UserNotifications < ActionMailer::Base
return unless user_history = opts[:user_history]
- build_email(
- user.email,
- template: "user_notifications.account_suspended",
- locale: user_locale(user),
- reason: user_history.details,
- suspended_till: I18n.l(user.suspended_till, format: :long)
- )
+ if user.suspended_forever?
+ build_email(
+ user.email,
+ template: "user_notifications.account_suspended_forever",
+ locale: user_locale(user),
+ reason: user_history.details
+ )
+ else
+ build_email(
+ user.email,
+ template: "user_notifications.account_suspended",
+ locale: user_locale(user),
+ reason: user_history.details,
+ suspended_till: I18n.l(user.suspended_till, format: :long)
+ )
+ end
end
def account_exists(user, opts = {})
diff --git a/app/models/api_key_scope.rb b/app/models/api_key_scope.rb
index 2bf69db9f2..f4cb44e2f8 100644
--- a/app/models/api_key_scope.rb
+++ b/app/models/api_key_scope.rb
@@ -41,6 +41,7 @@ class ApiKeyScope < ActiveRecord::Base
log_out: { actions: %w[admin/users#log_out] },
anonymize: { actions: %w[admin/users#anonymize] },
delete: { actions: %w[admin/users#destroy] },
+ list: { actions: %w[admin/users#index] },
},
email: {
receive_emails: { actions: %w[admin/email#handle_mail] }
diff --git a/app/models/category.rb b/app/models/category.rb
index 73e8f6ee8f..d36d490ace 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -1027,6 +1027,7 @@ end
# read_only_banner :string
# default_list_filter :string(20) default("all")
# allow_unlimited_owner_edits_on_first_post :boolean default(FALSE), not null
+# default_slow_mode_seconds :integer
#
# Indexes
#
diff --git a/app/models/discourse_single_sign_on.rb b/app/models/discourse_single_sign_on.rb
index 4abfc61a4a..7be3b50537 100644
--- a/app/models/discourse_single_sign_on.rb
+++ b/app/models/discourse_single_sign_on.rb
@@ -342,7 +342,7 @@ class DiscourseSingleSignOn < SingleSignOn
if card_background_url.present?
card_background_missing = user.user_profile.card_background_upload.blank? || Upload.get_from_url(user.user_profile.card_background_upload.url).blank?
- if card_background_missing || SiteSetting.discourse_connect_overrides_profile_background
+ if card_background_missing || SiteSetting.discourse_connect_overrides_card_background
card_background_changed = sso_record.external_card_background_url != card_background_url
if card_background_changed || card_background_missing
Jobs.enqueue(:download_profile_background_from_url,
diff --git a/app/models/embeddable_host.rb b/app/models/embeddable_host.rb
index e76dbce643..212348bc3d 100644
--- a/app/models/embeddable_host.rb
+++ b/app/models/embeddable_host.rb
@@ -44,6 +44,8 @@ class EmbeddableHost < ActiveRecord::Base
end
def self.url_allowed?(url)
+ return false if url.nil?
+
# Work around IFRAME reload on WebKit where the referer will be set to the Forum URL
return true if url&.starts_with?(Discourse.base_url) && EmbeddableHost.exists?
diff --git a/app/models/emoji.rb b/app/models/emoji.rb
index f755a7eaad..d9f8c06d19 100644
--- a/app/models/emoji.rb
+++ b/app/models/emoji.rb
@@ -2,7 +2,7 @@
class Emoji
# update this to clear the cache
- EMOJI_VERSION = "9"
+ EMOJI_VERSION = "10"
FITZPATRICK_SCALE ||= [ "1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff" ]
diff --git a/app/models/notification.rb b/app/models/notification.rb
index b5a07a32e2..a22be0e901 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -75,7 +75,7 @@ class Notification < ActiveRecord::Base
DB.exec(<<~SQL)
DELETE
FROM notifications n
- WHERE high_priority
+ WHERE high_priority AND notification_type <> #{types[:chat_mention].to_i}
AND NOT EXISTS (
SELECT 1
FROM posts p
@@ -116,7 +116,8 @@ class Notification < ActiveRecord::Base
reaction: 25,
votes_released: 26,
event_reminder: 27,
- event_invitation: 28
+ event_invitation: 28,
+ chat_mention: 29
)
end
diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb
index e8998a1d14..bff1c63064 100644
--- a/app/models/reviewable.rb
+++ b/app/models/reviewable.rb
@@ -371,7 +371,8 @@ class Reviewable < ActiveRecord::Base
Jobs.enqueue(
:notify_reviewable,
reviewable_id: self.id,
- updated_reviewable_ids: result.remove_reviewable_ids,
+ performing_username: performed_by.username,
+ updated_reviewable_ids: result.remove_reviewable_ids
)
end
diff --git a/app/models/site.rb b/app/models/site.rb
index 338091faee..4874009819 100644
--- a/app/models/site.rb
+++ b/app/models/site.rb
@@ -7,6 +7,14 @@ class Site
cattr_accessor :preloaded_category_custom_fields
self.preloaded_category_custom_fields = Set.new
+ def self.add_categories_callbacks(&block)
+ categories_callbacks << block
+ end
+
+ def self.categories_callbacks
+ @categories_callbacks ||= []
+ end
+
def initialize(guardian)
@guardian = guardian
end
@@ -104,6 +112,11 @@ class Site
end
categories.reject! { |c| c[:parent_category_id] && !by_id[c[:parent_category_id]] }
+
+ self.class.categories_callbacks.each do |callback|
+ callback.call(categories)
+ end
+
categories
end
end
diff --git a/app/models/stylesheet_cache.rb b/app/models/stylesheet_cache.rb
index 03d921c076..819f9ab366 100644
--- a/app/models/stylesheet_cache.rb
+++ b/app/models/stylesheet_cache.rb
@@ -4,6 +4,7 @@ class StylesheetCache < ActiveRecord::Base
self.table_name = 'stylesheet_cache'
MAX_TO_KEEP = 50
+ CLEANUP_AFTER_DAYS = 150
def self.add(target, digest, content, source_map, max_to_keep: nil)
max_to_keep ||= MAX_TO_KEEP
@@ -42,6 +43,10 @@ class StylesheetCache < ActiveRecord::Base
end
end
+ def self.clean_up
+ StylesheetCache.where('created_at < ?', CLEANUP_AFTER_DAYS.days.ago).delete_all
+ end
+
end
# == Schema Information
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 5150a28952..a618b56923 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -334,6 +334,7 @@ class Topic < ActiveRecord::Base
if category_id_changed? || new_record?
inherit_auto_close_from_category
+ inherit_slow_mode_from_category
end
end
@@ -1251,6 +1252,12 @@ class Topic < ActiveRecord::Base
end
end
+ def inherit_slow_mode_from_category
+ if self.category&.default_slow_mode_seconds
+ self.slow_mode_seconds = self.category&.default_slow_mode_seconds
+ end
+ end
+
def inherit_auto_close_from_category(timer_type: :close)
auto_close_hours = self.category&.auto_close_hours
diff --git a/app/models/user.rb b/app/models/user.rb
index da4dbeda2a..4f807458db 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -958,6 +958,10 @@ class User < ActiveRecord::Base
silenced_record.try(:created_at) if silenced?
end
+ def silenced_forever?
+ silenced_till > 100.years.from_now
+ end
+
def suspend_record
UserHistory.for(self, :suspend_user).order('id DESC').first
end
@@ -974,6 +978,27 @@ class User < ActiveRecord::Base
nil
end
+ def suspended_message
+ return nil unless suspended?
+
+ message = "login.suspended"
+ if suspend_reason
+ if suspended_forever?
+ message = "login.suspended_with_reason_forever"
+ else
+ message = "login.suspended_with_reason"
+ end
+ end
+
+ I18n.t(message,
+ date: I18n.l(suspended_till, format: :date_only),
+ reason: Rack::Utils.escape_html(suspend_reason))
+ end
+
+ def suspended_forever?
+ suspended_till > 100.years.from_now
+ end
+
# Use this helper to determine if the user has a particular trust level.
# Takes into account admin, etc.
def has_trust_level?(level)
diff --git a/app/models/user_search.rb b/app/models/user_search.rb
index 13d74df069..ba53653594 100644
--- a/app/models/user_search.rb
+++ b/app/models/user_search.rb
@@ -12,6 +12,7 @@ class UserSearch
@topic_allowed_users = opts[:topic_allowed_users]
@searching_user = opts[:searching_user]
@include_staged_users = opts[:include_staged_users] || false
+ @last_seen_users = opts[:last_seen_users] || false
@limit = opts[:limit] || 20
@groups = opts[:groups]
@@ -162,6 +163,15 @@ class UserSearch
.each { |id| users << id }
end
+ # 5. last seen users (for search auto-suggestions)
+ if @last_seen_users
+ scoped_users
+ .order('last_seen_at DESC NULLS LAST')
+ .limit(@limit - users.size)
+ .pluck(:id)
+ .each { |id| users << id }
+ end
+
users.to_a
end
diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb
index fe42e1172a..0efe3f5b5b 100644
--- a/app/serializers/category_serializer.rb
+++ b/app/serializers/category_serializer.rb
@@ -20,7 +20,8 @@ class CategorySerializer < SiteCategorySerializer
:custom_fields,
:topic_featured_link_allowed,
:search_priority,
- :reviewable_by_group_name
+ :reviewable_by_group_name,
+ :default_slow_mode_seconds
def reviewable_by_group_name
object.reviewable_by_group.name
diff --git a/app/serializers/user_card_serializer.rb b/app/serializers/user_card_serializer.rb
index 617db6a726..4ff67b9913 100644
--- a/app/serializers/user_card_serializer.rb
+++ b/app/serializers/user_card_serializer.rb
@@ -59,6 +59,7 @@ class UserCardSerializer < BasicUserSerializer
:recent_time_read,
:primary_group_id,
:primary_group_name,
+ :flair_group_id,
:flair_name,
:flair_url,
:flair_bg_color,
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index 1c9afecd4e..01df2bddfe 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -57,6 +57,7 @@ class UserSerializer < UserCardSerializer
:can_change_bio,
:can_change_location,
:can_change_website,
+ :can_change_tracking_preferences,
:user_api_keys,
:user_auth_tokens,
:user_notification_schedule,
@@ -132,6 +133,10 @@ class UserSerializer < UserCardSerializer
!(SiteSetting.enable_discourse_connect && SiteSetting.discourse_connect_overrides_website)
end
+ def can_change_tracking_preferences
+ scope.can_change_tracking_preferences?(object)
+ end
+
def user_api_keys
keys = object.user_api_keys.where(revoked_at: nil).map do |k|
{
diff --git a/app/serializers/web_hook_user_serializer.rb b/app/serializers/web_hook_user_serializer.rb
index 43200e4fd6..16f32b6d4c 100644
--- a/app/serializers/web_hook_user_serializer.rb
+++ b/app/serializers/web_hook_user_serializer.rb
@@ -30,6 +30,7 @@ class WebHookUserSerializer < UserSerializer
can_change_bio
can_change_location
can_change_website
+ can_change_tracking_preferences
user_api_keys
group_users
user_auth_tokens
diff --git a/app/services/topic_timestamp_changer.rb b/app/services/topic_timestamp_changer.rb
index d02cda99f4..a8d5059c4d 100644
--- a/app/services/topic_timestamp_changer.rb
+++ b/app/services/topic_timestamp_changer.rb
@@ -29,6 +29,7 @@ class TopicTimestampChanger
end
end
+ @topic.reset_bumped_at
update_topic(last_posted_at)
yield(@topic) if block_given?
@@ -48,7 +49,6 @@ class TopicTimestampChanger
@topic.update(
created_at: @timestamp,
updated_at: @timestamp,
- bumped_at: @timestamp,
last_posted_at: last_posted_at
)
end
diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb
index 7c56d8eebe..adb2cc6dba 100644
--- a/app/services/user_updater.rb
+++ b/app/services/user_updater.rb
@@ -124,20 +124,22 @@ class UserUpdater
if attributes[:flair_group_id] &&
attributes[:flair_group_id] != user.flair_group_id &&
(attributes[:flair_group_id].blank? ||
- guardian.can_use_primary_group?(user, attributes[:flair_group_id]))
+ guardian.can_use_flair_group?(user, attributes[:flair_group_id]))
user.flair_group_id = attributes[:flair_group_id]
end
- CATEGORY_IDS.each do |attribute, level|
- if ids = attributes[attribute]
- CategoryUser.batch_set(user, level, ids)
+ if @guardian.can_change_tracking_preferences?(user)
+ CATEGORY_IDS.each do |attribute, level|
+ if ids = attributes[attribute]
+ CategoryUser.batch_set(user, level, ids)
+ end
end
- end
- TAG_NAMES.each do |attribute, level|
- if attributes.has_key?(attribute)
- TagUser.batch_set(user, level, attributes[attribute]&.split(',') || [])
+ TAG_NAMES.each do |attribute, level|
+ if attributes.has_key?(attribute)
+ TagUser.batch_set(user, level, attributes[attribute]&.split(',') || [])
+ end
end
end
diff --git a/config/locales/client.ar.yml b/config/locales/client.ar.yml
index 53bf38bc54..9fbc4f4d5a 100644
--- a/config/locales/client.ar.yml
+++ b/config/locales/client.ar.yml
@@ -213,7 +213,7 @@ ar:
url: "نسخ عنوان URL ومشاركته"
action_codes:
public_topic: "جعل هذا الموضوع عامًا في %{when}"
- private_topic: "حوَّل هذا الموضوع إلى رسالة شخصية في %{when}"
+ private_topic: "حوَّل هذا الموضوع إلى رسالة خاصة في %{when}"
split_topic: "قسَّم هذا الموضوع في %{when}"
invited_user: "دعا %{who} في %{when}"
invited_group: "دعا %{who} في %{when}"
@@ -234,7 +234,7 @@ ar:
enabled: "تم تثبيته في %{when}"
disabled: "تم إلغاء تثبيته في %{when}"
pinned_globally:
- enabled: "تم تثبيته عموميًا في %{when}"
+ enabled: "تم تثبيته بشكلٍ عام في %{when}"
disabled: "تم إلغاء تثبيته في %{when}"
visible:
enabled: "تم الإدراج في %{when}"
@@ -245,8 +245,9 @@ ar:
forwarded: "أعاد توجيه الرسالة الإلكترونية أعلاه"
topic_admin_menu: "إجراءات الموضوع"
wizard_required: "مرحبًا بك في Discourse! لنبدأ من معالج الإعداد ✨"
- emails_are_disabled: "عطَّل أحد المسؤولين البريد الصادر بشكلٍ عمومي. ولن يتم إرسال إشعارات عبر البريد الإلكتروني أيًا كان نوعها."
+ emails_are_disabled: "أوقف أحد المسؤولين البريد الصادر بشكلٍ عام. ولن يتم إرسال إشعارات عبر البريد الإلكتروني أيًا كان نوعها."
software_update_prompt:
+ message: "لقد حدَّثنا هذا الموقع، يُرجى التحديث ، أو قد تواجه سلوكًا غير متوقَّع."
dismiss: "تجاهل"
bootstrap_mode_enabled:
zero: "أنت في وضع تمهيد التشغيل لتسهيل إطلاق موقعك الجديد. سيتم منح جميع المستخدمين الجُدد مستوى الثقة 1 وتفعيل الرسائل الإلكترونية التلخيصية لهم. وسيتم إيقاف هذا الوضع تلقائيًا عند انضمام %{count} مستخدم."
@@ -255,14 +256,15 @@ ar:
few: "أنت في وضع تمهيد التشغيل لتسهيل إطلاق موقعك الجديد. سيتم منح جميع المستخدمين الجُدد مستوى الثقة 1 وتفعيل الرسائل الإلكترونية التلخيصية لهم. وسيتم إيقاف هذا الوضع تلقائيًا عند انضمام %{count} مستخدمين."
many: "أنت في وضع تمهيد التشغيل لتسهيل إطلاق موقعك الجديد. سيتم منح جميع المستخدمين الجُدد مستوى الثقة 1 وتفعيل الرسائل الإلكترونية التلخيصية لهم. وسيتم إيقاف هذا الوضع تلقائيًا عند انضمام %{count} مستخدمًا."
other: "أنت في وضع تمهيد التشغيل لتسهيل إطلاق موقعك الجديد. سيتم منح جميع المستخدمين الجُدد مستوى الثقة 1 وتفعيل الرسائل الإلكترونية التلخيصية لهم. وسيتم إيقاف هذا الوضع تلقائيًا عند انضمام %{count} مستخدم."
- bootstrap_mode_disabled: "سيتم تعطيل وضع تمهيد التشغيل خلال 24 ساعة."
+ bootstrap_mode_disabled: "سيتم إيقاف وضع تمهيد التشغيل خلال 24 ساعة."
themes:
default_description: "افتراضية"
- broken_theme_alert: "قد لا يعمل موقعك الإلكتروني كما ينبغي بسبب وجود أخطاء في السمة/المكوِّن %{theme}. ويمكنك تعطيله من المسار %{path}."
+ broken_theme_alert: "قد لا يعمل الموقع كما ينبغي بسبب وجود أخطاء في السمة/المكوِّن %{theme}. أوقفه من %{path}."
s3:
regions:
ap_northeast_1: "آسيا والمحيط الهادئ (طوكيو)"
ap_northeast_2: "آسيا والمحيط الهادئ (سول)"
+ ap_east_1: "آسيا والمحيط الهادئ (هونغ كونغ)"
ap_south_1: "آسيا والمحيط الهادئ (مومباي)"
ap_southeast_1: "آسيا والمحيط الهادئ (سنغافورة)"
ap_southeast_2: "آسيا والمحيط الهادئ (سيدني)"
@@ -281,6 +283,7 @@ ar:
us_gov_west_1: "AWS GovCloud (غرب الولايات المتحدة)"
us_west_1: "غرب الولايات المتحدة (كاليفورنيا الشمالية)"
us_west_2: "غرب الولايات المتحدة (أوريغون)"
+ clear_input: "مسح الإدخال"
edit: "عدَّل عنوان هذا الموضوع وفئته"
expand: "وسّع"
not_implemented: "عذرًا، لم يتم تنفيذ هذه الميزة بعد."
@@ -313,11 +316,17 @@ ar:
conduct: "قواعد السلوك"
mobile_view: "العرض على الجوَّال"
desktop_view: "العرض على كمبيوتر سطح المكتب"
- you: "أنت"
or: "أو"
now: "منذ لحظات"
read_more: "قراءة المزيد"
more: "المزيد"
+ x_more:
+ zero: "%{count} أكثر"
+ one: "%{count} أكثر"
+ two: "%{count} أكثر"
+ few: "%{count} أكثر"
+ many: "%{count} أكثر"
+ other: "%{count} أكثر"
never: "أبدًا"
every_30_minutes: "كلّ 30 دقيقة"
every_hour: "كلّ ساعة"
@@ -348,6 +357,9 @@ ar:
moderators: "المشرفون"
stat:
all_time: "طوال الوقت"
+ last_day: "آخر 24 ساعة"
+ last_7_days: "آخر 7 أيام"
+ last_30_days: "آخر 30 يومًا"
like_count: "الإعجابات"
topic_count: "الموضوعات"
post_count: "المشاركات"
@@ -357,10 +369,13 @@ ar:
contact_info: "في حال حدوث مشكلة خطيرة أو أمر عاجل يؤثر على الموقع، يُرجى مراسلتنا على %{contact_info}."
bookmarked:
title: "وضع إشارة مرجعية"
+ edit_bookmark: "تعديل الإشارة المرجعية"
clear_bookmarks: "مسح الإشارات المرجعية"
help:
bookmark: "انقر لإضافة إشارة مرجعية على المنشور الأول في هذا الموضوع"
+ edit_bookmark: "انقر لتعديل الإشارة المرجعية في هذا الموضوع"
unbookmark: "انقر لإزالة كل الإشارات المرجعية في هذا الموضوع"
+ unbookmark_with_reminder: "انقر لإزالة جميع الإشارات المرجعية والتذكيرات في هذا الموضوع."
bookmarks:
created: "لقد وضعت إشارة مرجعية على هذا المنشور. %{name}"
not_bookmarked: "وضع إشارة مرجعية على هذا المنشور"
@@ -370,7 +385,7 @@ ar:
confirm_delete: "هل تريد بالتأكيد حذف هذه الإشارة المرجعية؟ سيتم حذف التذكير أيضًا."
confirm_clear: "هل تريد بالتأكيد مسح كل إشاراتك المرجعية من هذا الموضوع؟"
save: "حفظ"
- no_timezone: 'لم تحدد منطقتك الزمنية بعد. ولن تتمكن من ضبط التذكيرات. حدد منطقة زمنية في ملفك الشخصي.'
+ no_timezone: 'لم تحدد منطقتك الزمنية بعد. ولن تتمكن من ضبط التذكيرات. حدِّد منطقة زمنية في ملفك الشخصي.'
invalid_custom_datetime: "التاريخ والوقت الذين أدخلتهما غير صالحين، يُرجى إعادة المحاولة."
list_permission_denied: "ليس لديك إذن بعرض الإشارات المرجعية لهذا المستخدم."
no_user_bookmarks: "ليس لديك منشورات موضوع عليها إشارة مرجعية. تتيح لك الإشارات المرجعية الرجوع إلى المنشورات التي تريدها بسرعة."
@@ -393,8 +408,11 @@ ar:
remove: "إزالة"
remove_confirmation: "هل تريد بالتأكيد حذف هذه المسودة؟"
new_topic: "مسودة موضوع جديد"
- new_private_message: "مسودة رسالة خاصة جديدة"
topic_reply: "مسودة الرد"
+ abandon:
+ confirm: "لديك مسودة محفوظة لهذا الموضوع. ماذا تريد أن تفعل بها؟"
+ yes_value: "تجاهل"
+ no_value: "استئناف التعديل"
topic_count_latest:
zero: "عرض %{count} موضوع جديد أو محدَّث"
one: "عرض موضوع واحد (%{count}) جديد أو محدَّث"
@@ -425,11 +443,12 @@ ar:
upload: "تحميل"
uploading: "جارٍ التحميل..."
uploading_filename: "جارٍ تحميل: %{filename}..."
+ processing_filename: "قيد المعالجة: %{filename}..."
clipboard: "الحافظة"
uploaded: "تم التحميل!"
pasting: "جارٍ اللصق..."
enable: "تفعيل"
- disable: "تعطيل"
+ disable: "إيقاف"
continue: "متابعة"
undo: "تراجَع"
revert: "عودة"
@@ -464,7 +483,7 @@ ar:
score_to_hide: "النقاط اللازمة لإخفاء المنشور"
take_action_bonus:
name: "اتخذ إجراءً"
- title: "عندما يقرر أحد أعضاء الطاقم اتخاذ إجراء، يتم منح مكافأة على البلاغ."
+ title: "عندما يقرر أحد أعضاء الفريق اتخاذ إجراء، يتم منح مكافأة على البلاغ."
user_accuracy_bonus:
name: "دقة المستخدم"
title: "يحصل المستخدمون الذين تم التحقُّق من صحة بلاغاتهم بشكلٍ متكرر على مكافأة."
@@ -473,7 +492,8 @@ ar:
title: "تحظى العناصر القابلة للمراجعة التي أنشأها مستخدمون من مستوى ثقة أعلى بنقاط أعلى."
type_bonus:
name: "مكافأة النوع"
- title: "يمكن لطاقم العمل تخصيص مكافأة لبعض الأنواع القابلة للمراجعة لمنحها أولوية أعلى."
+ title: "يمكن لفريق العمل تخصيص مكافأة لبعض الأنواع القابلة للمراجعة لمنحها أولوية أعلى."
+ stale_help: "تم إجراء هذه المراجعة بواسطة شخص آخر."
claim_help:
optional: "يمكنك المطالبة بهذا العنصر لمنع الآخرين من مراجعته."
required: "يجب عليك المطالبة بالعناصر قبل أن تتمكن من مراجعتها."
@@ -520,6 +540,13 @@ ar:
fields: "الحقول"
reject_reason: "السبب"
user_percentage:
+ summary:
+ zero: "%{agreed}، %{disagreed}، %{ignored} (من آخر %{count} بلاغ)"
+ one: "%{agreed}، %{disagreed}، %{ignored} (من آخر بلاغ)"
+ two: "%{agreed}، %{disagreed}، %{ignored} (من آخر بلاغين (%{count}))"
+ few: "%{agreed}، %{disagreed}، %{ignored} (من آخر %{count} بلاغات)"
+ many: "%{agreed}، %{disagreed}، %{ignored} (من آخر %{count} بلاغًا)"
+ other: "%{agreed}، %{disagreed}، %{ignored} (من آخر %{count} بلاغ)"
agreed:
zero: "%{count}% اتفقوا"
one: "%{count}% اتفقوا"
@@ -635,7 +662,24 @@ ar:
other: "لديك %{count} منشور قيد الانتظار."
ok: "حسنًا"
example_username: "اسم المستخدم"
+ reject_reason:
+ title: "لماذا ترفض هذا المستخدم؟"
+ send_email: "إرسال رسالة إلكترونية بالرفض"
relative_time_picker:
+ minutes:
+ zero: "الدقائق"
+ one: "دقيقة واحدة"
+ two: "الدقائق"
+ few: "الدقائق"
+ many: "الدقائق"
+ other: "الدقائق"
+ hours:
+ zero: "الساعات"
+ one: "ساعة واحدة"
+ two: "الساعات"
+ few: "الساعات"
+ many: "الساعات"
+ other: "الساعات"
days:
zero: "يوم"
one: "يوم واحد"
@@ -643,16 +687,37 @@ ar:
few: "أيام"
many: "يومًا"
other: "يوم"
+ months:
+ zero: "شهر"
+ one: "شهر"
+ two: "شهران"
+ few: "أشهر"
+ many: "شهرًا"
+ other: "شهر"
+ years:
+ zero: "عام"
+ one: "عام"
+ two: "عامان"
+ few: "أعوام"
+ many: "عامًا"
+ other: "عام"
+ relative: "نسبي"
time_shortcut:
later_today: "لاحقًا اليوم"
next_business_day: "يوم العمل التالي"
tomorrow: "غدًا"
post_local_date: "التاريخ في المنشور"
later_this_week: "لاحقًا هذا الأسبوع"
+ this_weekend: "عطلة هذا الأسبوع"
start_of_next_business_week: "الاثنين"
start_of_next_business_week_alt: "الاثنين القادم"
+ two_weeks: "أسبوعان"
next_month: "الشهر القادم"
+ six_months: "ستة أشهر"
custom: "تاريخ ووقت مخصَّصان"
+ relative: "وقت نسبي"
+ none: "لا حاجة إليه"
+ last_custom: "آخر تاريخ ووقت مخصَّصين"
user_action:
user_posted_topic: "نشر %{user} الموضوع"
you_posted_topic: "أنت نشرت الموضوع"
@@ -694,8 +759,9 @@ ar:
many: "%{count} مستخدمًا"
other: "%{count} مستخدم"
edit_columns:
+ title: "تعديل أعمدة الدليل"
save: "حفظ"
- reset_to_default: "إعادة التعيين إلى الافتراضي"
+ reset_to_default: "إعادة الضبط على الافتراضي"
group:
all: "كل المجموعات"
group_histories:
@@ -709,7 +775,12 @@ ar:
member_added: "تمت الإضافة"
member_requested: "تاريخ الطلب"
add_members:
+ title: "إضافة مستخدمين إلى %{group_name}"
+ description: "أدخل قائمة المستخدمين الذين تريد دعوتهم إلى المجموعة أو الصقها في قائمة مفصولة بفاصلات:"
+ usernames_placeholder: "اسم المستخدمين"
+ usernames_or_emails_placeholder: "أسماء المستخدمين أو عناوين البريد الإلكتروني"
notify_users: "إشعار المستخدمين"
+ set_owner: "تعيين المستخدمين كمالكين لهذه المجموعة"
requests:
title: "الطلبات"
reason: "السبب"
@@ -723,6 +794,8 @@ ar:
title: "إدارة"
name: "الاسم"
full_name: "الاسم بالكامل"
+ add_members: "إضافة مستخدمين"
+ invite_members: "دعوة"
delete_member_confirm: "هل تريد إزالة \"%{username}\" من المجموعة \"%{group}\"؟"
profile:
title: الملف الشخصي
@@ -733,6 +806,24 @@ ar:
email:
title: "البريد الإلكتروني"
status: "تمت مزامنة %{old_emails}/%{total_emails} من الرسائل الإلكترونية عبر IMAP."
+ enable_smtp: "تفعيل SMTP"
+ enable_imap: "تفعيل IMAP"
+ test_settings: "إعدادات الاختبار"
+ save_settings: "إعدادات الحفظ"
+ last_updated: "تاريخ آخر تحديث:"
+ last_updated_by: "بواسطة"
+ settings_required: "جميع الإعدادات مطلوبة، يُرجى ملء جميع الحقول قبل التحقُّق."
+ smtp_settings_valid: "إعدادات SMTP صالحة."
+ smtp_instructions: "عند تمكين SMTP للمجموعة، سيتم إرسال جميع الرسائل الإلكترونية الصادرة المُرسَلة من صندوق الوارد الخاص بالمجموعة عبر إعدادات SMTP المحدَّدة هنا بدلًا من خادم البريد الذي تم إعداده للرسائل الإلكترونية الأخرى التي يرسلها منتداك."
+ imap_additional_settings: "إعدادات إضافية"
+ imap_instructions: 'عند تمكين IMAP للمجموعة، تتم مزامنة الرسائل الإلكترونية بين صندوق الوارد للمجموعة وخادم IMAP وصندوق البريد المقدَّمين. يجب تفعيل SMTP باستخدام بيانات اعتماد صالحة ومُختبَرة قبل تفعيل IMAP. سيتم استخدام اسم مستخدم البريد الإلكتروني وكلمة المرور المستخدمين لخادم SMTP في خادم IMAP. لمزيد من المعلومات، راجع إعلان الميزة في Discourse Meta.'
+ imap_alpha_warning: "تحذير: هذه الميزة في مرحلة الإصدار الأولي. ويتم دعم Gmail فقط بشكلٍ رسمي. استخدمها على مسؤوليتك الخاصة!"
+ imap_settings_valid: "إعدادات IMAP صالحة."
+ smtp_disable_confirm: "إذا أوقفت SMTP، فستتم إعادة ضبط جميع إعدادات SMTP وIMAP وإيقاف الوظائف المرتبطة. هل تريد بالتأكيد الاستمرار؟"
+ imap_disable_confirm: "إذا أوقفت IMAP، فستتم إعادة ضبط جميع إعدادات IMAP وإيقاف الوظائف المرتبطة. هل تريد بالتأكيد الاستمرار؟"
+ imap_mailbox_not_selected: "يجب تحديد صندوق بريد لإعداد خادم IMAP وإلا فلن تتم مزامنة أي صناديق بريد!"
+ prefill:
+ title: "الملء المسبق بإعدادات:"
credentials:
title: "بيانات الاعتماد"
smtp_server: "خادم SMTP"
@@ -745,9 +836,12 @@ ar:
password: "كلمة السر"
settings:
title: "الإعدادات"
+ allow_unknown_sender_topic_replies: "السماح بالرد على الموضوعات من مُرسِلين غير معروفين."
+ allow_unknown_sender_topic_replies_hint: "يسمح لمُرسِلين غير معروفين بالرد على موضوعات المجموعة. إذا لم يتم تفعيل هذا الإعداد، فستؤدي الردود الواردة من عناوين البريد الإلكتروني التي لم تتم دعوتها بالفعل إلى الموضوع إلى إنشاء موضوع جديد."
mailboxes:
synchronized: "صندوق البريد المتزامن"
none_found: "لم يتم العثور على صناديق بريد في حساب البريد الإلكتروني هذا."
+ disabled: "معطَّل"
membership:
title: العضوية
access: الوصول
@@ -800,7 +894,7 @@ ar:
message: "رسالة"
confirm_leave: "هل تريد بالتأكيد مغادرة هذه المجموعة؟"
allow_membership_requests: "السماح للمستخدمين بإرسال طلبات العضوية إلى مالكي المجموعة (يلزم أن تكون المجموعة عامة)"
- membership_request_template: "قالب مخصَّص يتم عرضه للمستخدمين عند إرسال طلب عضوية"
+ membership_request_template: "نموذج مخصَّص يتم عرضه للمستخدمين عند إرسال طلب عضوية"
membership_request:
submit: "إرسال طلب"
title: "طلب الانضمام إلى @%{group_name}"
@@ -825,8 +919,6 @@ ar:
public: "عامة"
private: "خاصة"
public_groups: "المجموعات العامة"
- automatic_group: مجموعة تلقائية
- close_group: مجموعة مغلقة
my_groups: "مجموعاتي"
group_type: "نوع المجموعة"
is_group_user: "عضو"
@@ -849,7 +941,22 @@ ar:
make_owner_description: "جعل %{username} أحد مالكي هذه المجموعة"
remove_owner: "إزالة كمالك"
remove_owner_description: "إزالة %{username} كمالك هذه المجموعة"
+ make_primary: "الضبط كمجموعة أساسية"
+ make_primary_description: "جعل هذه المجموعة هي المجموعة الأساسية للمستخدم %{username}"
+ remove_primary: "الإزالة كمجموعة أساسية"
+ remove_primary_description: "إزالة هذه المجموعة كمجموعة أساسية للمستخدم %{username}"
+ remove_members: "إزالة الأعضاء"
+ remove_members_description: "إزالة المستخدمين المحدَّدين من هذه المجموعة"
+ make_owners: "التعيين كمالكين"
+ make_owners_description: "تعيين المستخدمين المحدَّدين كمالكين لهذه المجموعة"
+ remove_owners: "إزالة المالكين"
+ remove_owners_description: "إزالة المستخدمين المحدَّدين كمالكين لهذه المجموعة"
+ make_all_primary: "الضبط كمجموعة أساسية للجميع"
+ make_all_primary_description: "جعل هذه المجموعة هي المجموعة الأساسية للمستخدمين المحدَّدين"
+ remove_all_primary: "الإزالة كمجموعة أساسية"
+ remove_all_primary_description: "إزالة هذه المجموعة كمجموعة أساسية"
owner: "مالك"
+ primary: "أساسية"
forbidden: "غير مسموح لك بعرض الأعضاء."
topics: "الموضوعات"
posts: "المنشورات"
@@ -890,7 +997,7 @@ ar:
flair_preview_icon: "معاينة الأيقونة"
flair_preview_image: "معاينة الصورة"
flair_type:
- icon: "اختيار أيقونة"
+ icon: "تحديد أيقونة"
image: "تحميل صورة"
user_action_groups:
"1": "الإعجابات"
@@ -921,7 +1028,6 @@ ar:
posts: "المشاركات"
topics: "الموضوعات"
latest: "الحديثة"
- toggle_ordering: "تفعيل التحكم في الترتيب"
subcategories: "الفئات الفرعية"
muted: "الفئات المكتومة"
topic_sentence:
@@ -931,9 +1037,23 @@ ar:
few: "%{count} موضوعات"
many: "%{count} موضوعًا"
other: "%{count} موضوع"
+ topic_stat:
+ zero: "%{number}/%{unit}"
+ one: "%{number}/%{unit}"
+ two: "%{number}/%{unit}"
+ few: "%{number}/%{unit}"
+ many: "%{number}/%{unit}"
+ other: "%{number}/%{unit}"
topic_stat_unit:
week: "أسبوع"
month: "شهر"
+ topic_stat_all_time:
+ zero: "الإجمالي: %{number}"
+ one: "الإجمالي: %{number}"
+ two: "الإجمالي: %{number}"
+ few: "الإجمالي: %{number}"
+ many: "الإجمالي: %{number}"
+ other: "الإجمالي: %{number}"
topic_stat_sentence_week:
zero: "%{count} موضوع جديد خلال الأسبوع الماضي."
one: "موضوع واحد (%{count}) جديد خلال الأسبوع الماضي."
@@ -967,7 +1087,7 @@ ar:
powered_by: "باستخدام MaxMindDB"
copied: "تم النسخ"
user_fields:
- none: "(حدِّد خيارًا)"
+ none: "(تحديد خيار)"
required: 'يُرجى إدخال قيمة لـ "%{name}"'
user:
said: "%{username}:"
@@ -1003,15 +1123,26 @@ ar:
normal_option: "عادي"
normal_option_title: "سنُرسل إليك إشعارًا في حال ردَّ هذا المستخدم عليك أو اقتبس كلامك أو أشار إليك."
notification_schedule:
+ title: "جدول الإشعارات"
+ label: "تفعيل جدول الإشعارات المخصَّص"
+ tip: "سيتم وضعك في وضع \"عدم الإزعاج\" تلقائيًا خارج هذه الساعات."
+ midnight: "منتصف الليل"
none: "لا يوجد"
monday: "الاثنين"
+ tuesday: "الثلاثاء"
+ wednesday: "الأربعاء"
+ thursday: "الخميس"
+ friday: "الجمعة"
+ saturday: "السبت"
+ sunday: "الأحد"
to: "إلى"
activity_stream: "النشاط"
read: "المقروءة"
+ read_help: "الموضوعات المقروءة مؤخرًا"
preferences: "التفضيلات"
feature_topic_on_profile:
- open_search: "اختيار موضوع جديد"
- title: "اختيار موضوع"
+ open_search: "تحديد موضوع جديد"
+ title: "تحديد موضوع"
search_label: "البحث عن موضوع بالعنوان"
save: "حفظ"
clear:
@@ -1034,33 +1165,44 @@ ar:
perm_default: "تفعيل الإشعارات"
perm_denied_btn: "تم رفض الإذن"
perm_denied_expl: "لقد رفضت منح الإذن بإرسال الإشعارات. يمكنك السماح بالإشعارات في إعدادات المتصفح."
- disable: "تعطيل الإشعارات"
+ disable: "إيقاف الإشعارات"
enable: "تفعيل الإشعارات"
- each_browser_note: 'ملاحظة: عليك تغيير هذا الإعداد في كل متصفح تستخدمه. سيتم تعطيل جميع الإشعارات في وضع "عدم الإزعاج"، بغض النظر عن هذا الإعداد.'
+ each_browser_note: 'ملاحظة: عليك تغيير هذا الإعداد في كل متصفح تستخدمه. سيتم إيقاف جميع الإشعارات في وضع "عدم الإزعاج"، بغض النظر عن هذا الإعداد.'
consent_prompt: "هل تريد تلقي إشعارات فورية عند رد الأشخاص على منشوراتك؟"
dismiss: "تجاهل"
dismiss_notifications: "تجاهل الكل"
dismiss_notifications_tooltip: "وضع علامة مقروءة على كل الإشعارات غير المقروءة"
+ no_messages_title: "ليس لديك أي رسائل"
+ no_messages_body: >
+ هل تحتاج إلى إجراء محادثة شخصية مباشرة مع شخص ما خارج مسار إجراء المحادثات التقليدي؟ راسله عن طريق تحديد صورته الرمزية واستخدام زر الرسالة %{icon}.
إذا كنت بحاجة إلى مساعدة، يمكنك مراسلة عضو في فريق العمل.
+ no_bookmarks_title: "لم تضع إشارة مرجعية على أي شيء بعد"
+ no_bookmarks_body: >
+ ابدأ في وضع إشارة مرجعية على المنشورات باستخدام الزر %{icon} وسيتم إدراجها هنا للرجوع إليها بسهولة. يمكنك جدولة تذكير أيضًا!
+ no_notifications_title: "ليس لديك أي إشعارات بعد"
+ no_notifications_body: >
+ سيتم إعلامك في هذه اللوحة بالنشاط ذي الصلة المباشرة بك، بما في ذلك الردود على موضوعاتك ومنشوراتك، وعندما يشير إليك شخص ما @mentions أو يقتبس منك، وعندما يرد على الموضوعات التي تراقبها. سيتم أيضًا إرسال الإشعارات إلى بريدك الإلكتروني في حال عدم قيامك بتسجيل الدخول لفترة من الوقت.
ابحث عن%{icon} لتحديد الموضوعات والفئات والوسوم المحدَّدة التي تريد أن يتم إرسال إشعار إلىك بها. لمزيد من المعلومات، راجع تفضيلات الإشعارات.
first_notification: "أول إشعار تستلمه! اضغط عليه للبدء."
dynamic_favicon: "عرض الأعداد على أيقونة المتصفح"
skip_new_user_tips:
description: "تخطي نصائح وشارات تهيئة المستخدم الجديد"
not_first_time: "ليست المرة الأولى لك؟"
skip_link: "تخطي هذه النصائح"
+ read_later: "سأقرأها لاحقًا."
theme_default_on_all_devices: "جعل هذه السمة الافتراضية على كل أجهزتي"
color_scheme_default_on_all_devices: "ضبط نظام (أنظمة) الألوان الافتراضي على جميع أجهزتي"
color_scheme: "نظام الألوان"
color_schemes:
+ default_description: "السمة الافتراضية"
disable_dark_scheme: "مثل العادي"
dark_instructions: "يمكنك معاينة نظام ألوان الوضع الداكن عن طريق تفعيل الوضع الداكن لجهازك."
- undo: "إعادة التعيين"
+ undo: "إعادة الضبط"
regular: "العادي"
dark: "الوضع الداكن"
default_dark_scheme: "(الوضع الافتراضي للموقع)"
dark_mode: "الوضع الداكن"
dark_mode_enable: "تفعيل نظام الألوان في الوضع الداكن تلقائيًا"
text_size_default_on_all_devices: "جعل هذا الحجم الافتراضي للنص على جميع أجهزتي"
- allow_private_messages: "السماح للمستخدمين الآخرين بإرسال رسائل شخصية إليَّ"
+ allow_private_messages: "السماح للمستخدمين الآخرين بإرسال رسائل خاصة إليَّ"
external_links_in_new_tab: "فتح كل الروابط الخارجية في علامة تبويب جديدة"
enable_quoting: "تفعيل الرد باقتباس للنص المميز"
enable_defer: "تفعيل التأجيل لوضع علامة على الموضوعات كغير مقروءة"
@@ -1107,21 +1249,21 @@ ar:
muted_categories_instructions_dont_hide: "لن تتلقى أي إشعارات أبدًا بخصوص الموضوعات الجديدة في هذه الفئات."
regular_categories: "العادية"
regular_categories_instructions: "سترى هذه الفئات في قوائم الموضوعات \"الحديثة\" و\"الأكثر عرضًا\"."
- no_category_access: "كمشرف لديك صلاحيات وصول محدودة للفئات، فالحفظ معطَّل."
+ no_category_access: "كمشرف لديك صلاحيات وصول محدودة للفئات، فالحفظ متوقف."
delete_account: "حذف حسابي"
delete_account_confirm: "هل تريد بالتأكيد حذف حسابك للأبد؟ لا يمكن التراجع عن هذا الإجراء!"
deleted_yourself: "تم حذف حسابك بنجاح."
- delete_yourself_not_allowed: "يُرجى التواصل مع أحد أعضاء الطاقم إذا أردت حذف حسابك."
+ delete_yourself_not_allowed: "يُرجى التواصل مع أحد أعضاء الفريق إذا أردت حذف حسابك."
unread_message_count: "الرسائل"
admin_delete: "حذف"
users: "المستخدمون"
muted_users: "المكتومون"
- muted_users_instructions: "منع جميع الإشعارات والرسائل الشخصية من هؤلاء المستخدمين"
+ muted_users_instructions: "منع جميع الإشعارات والرسائل الخاصة من هؤلاء المستخدمين"
allowed_pm_users: "السماح"
- allowed_pm_users_instructions: "السماح بالرسائل الشخصية من هؤلاء المستخدمين فقط"
- allow_private_messages_from_specific_users: "السماح لمستخدمين محدَّدين فقط بإرسال الرسائل الشخصية إليَّ"
+ allowed_pm_users_instructions: "السماح بالرسائل الخاصة من هؤلاء المستخدمين فقط"
+ allow_private_messages_from_specific_users: "السماح لمستخدمين محدَّدين فقط بإرسال الرسائل الخاصة إليَّ"
ignored_users: "التجاهل"
- ignored_users_instructions: "منع جميع المشاركات والإشعارات والرسائل الشخصية من هؤلاء المستخدمين"
+ ignored_users_instructions: "منع جميع المشاركات والإشعارات والرسائل الخاصة من هؤلاء المستخدمين"
tracked_topics_link: "إظهار"
automatically_unpin_topics: "إلغاء تثبيت الموضوعات تلقائيًا عند وصولي إلى نهايتها."
apps: "التطبيقات"
@@ -1148,8 +1290,9 @@ ar:
groups: "مجموعاتي"
move_to_inbox: "نقل إلى صندوق الوارد"
move_to_archive: "أرشفة"
- failed_to_move: "فشل نقل الرسائل المحدَّدة (قد تكون شبكتك معطَّلة)"
+ failed_to_move: "فشل نقل الرسائل المحدَّدة (قد تكون شبكتك متوقفة)"
tags: "الوسوم"
+ warnings: "تحذيرات رسمية"
preferences_nav:
account: "الحساب"
security: "الأمان"
@@ -1166,14 +1309,14 @@ ar:
in_progress: "(جارٍ إرسال رسالة البريد الإلكتروني)"
error: "(خطأ)"
emoji: "قفل الرمز التعبيري"
- action: "إرسال رسالة إلكترونية لإعادة تعيين كلمة المرور"
+ action: "إرسال رسالة إلكترونية لإعادة ضبط كلمة المرور"
set_password: "ضبط كلمة المرور"
choose_new: "اختيار كلمة مرور جديدة"
choose: "اختيار كلمة مرور"
second_factor_backup:
title: "الرموز الاحياطية للمصادقة الثنائية"
regenerate: "أعِد التوليد"
- disable: "تعطيل"
+ disable: "إيقاف"
enable: "تفعيل"
enable_long: "تفعيل الرموز الاحتياطية"
manage:
@@ -1202,7 +1345,7 @@ ar:
second_factor:
title: "المصادقة الثنائية"
enable: "إدارة المصادقة الثنائية"
- disable_all: "تعطيل الكل"
+ disable_all: "إيقاف الكل"
forgot_password: "هل نسيت كلمة المرور؟"
confirm_password_description: "يُرجى تأكيد كلمة المرور للمتابعة"
name: "الاسم"
@@ -1216,11 +1359,11 @@ ar:
احمِ حسابك برموز أمان تُستخدَم لمرة واحدة.
extended_description: |
تضيف المصادقة الثنائية أمانًا إضافيًا إلى حسابك من خلال طلب رمز مميز لمرة واحدة بالإضافة إلى كلمة مرورك. يمكن إنشاء الرموز المميزة على الأجهزة التي تعمل بنظام التشغيل Android وiOS.
- oauth_enabled_warning: "يُرجى العلم بأنه سيتم تعطيل تسجيل الدخول بحسابات التواصل الاجتماعي بعد تفعيل المصادقة الثنائية على حسابك."
+ oauth_enabled_warning: "يُرجى العلم بأنه سيتم إيقاف تسجيل الدخول بحسابات التواصل الاجتماعي بعد تفعيل المصادقة الثنائية على حسابك."
use: "استخدام تطبيق المصادقة"
enforced_notice: "يلزم تفعيل المصادقة الثنائية قبل الوصول إلى هذا الموقع."
- disable: "تعطيل"
- disable_confirm: "هل تريد بالتأكيد تعطيل جميع وسائل المصادقة الثنائية؟"
+ disable: "إيقاف"
+ disable_confirm: "هل تريد بالتأكيد إيقاف جميع وسائل المصادقة الثنائية؟"
save: "حفظ"
edit: "تعديل"
edit_title: "تعديل تطبيق المصادقة"
@@ -1272,6 +1415,7 @@ ar:
uploaded_avatar_empty: "إضافة صورة مخصَّصة"
upload_title: "تحميل صورتك"
image_is_not_a_square: "تحذير: لقد قصصنا صورتك؛ لأن عرضها وارتفاعها لم يكونا متساويين."
+ logo_small: "شعار الموقع الصغير. يتم استخدامه بشكلٍ افتراضي."
change_profile_background:
title: "رأس الملف الشخصي"
instructions: "سيتم توسيط رؤوس الملفات الشخصية وسيكون عرضها الافتراضي 1110 بكسل."
@@ -1294,13 +1438,16 @@ ar:
set_primary: "ضبط عنوان البريد الإلكتروني الرئيسي"
destroy: "إزالة عنوان البريد الإلكتروني"
add_email: "إضافة بريد إلكتروني بديل"
+ auth_override_instructions: "يمكن تحديث البريد الإلكتروني من موفِّر المصادقة."
no_secondary: "لا توجد عناوين بريد إلكتروني ثانوية"
instructions: "لا يظهر للعامة أبدًا."
- admin_note: "ملاحظة: يشير تغيير المستخدم المسؤول لعنوان البريد الإلكتروني لمستخدم آخر غير مسؤول إلى أن المستخدم قد فقد الوصول إلى حساب البريد الإلكتروني الأصلي؛ لذلك ستتم مراسلته عبر البريد الإلكتروني لإعادة تعيين كلمة المرور إلى عنوانه الجديد. ولن يتغير عنوان البريد الإلكتروني للمستخدم حتى يكمل عملية إعادة تعيين كلمة المرور."
+ admin_note: "ملاحظة: يشير تغيير المستخدم المسؤول لعنوان البريد الإلكتروني لمستخدم آخر غير مسؤول إلى أن المستخدم قد فقد الوصول إلى حساب البريد الإلكتروني الأصلي؛ لذلك ستتم مراسلته عبر البريد الإلكتروني لإعادة ضبط كلمة المرور إلى عنوانه الجديد. ولن يتغير عنوان البريد الإلكتروني للمستخدم حتى يكمل عملية إعادة ضبط كلمة المرور."
ok: "سنُرسل إليك رسالة إلكترونية للتأكيد"
required: "يُرجى إدخال عنوان بريد إلكتروني"
invalid: "يُرجى إدخال عنوان بريد إلكتروني صالح"
authenticated: "تمت مصادقة عنوان بريدك الإلكتروني بواسطة %{provider}"
+ invite_auth_email_invalid: "لا يتطابق البريد الإلكتروني للدعوة مع البريد الإلكتروني المُصادَق عليه بواسطة %{provider}"
+ authenticated_by_invite: "تمت المصادقة على بريدك الإلكتروني من خلال الدعوة"
frequency_immediately: "سنُرسل إليك رسالة إلكترونية فورًا إذا كنت لم تقرأ الشيء الذي نُرسل إليك بشأنه."
frequency:
zero: "لن نراسلك عبر البريد الإلكتروني إلا في حال مُضي %{count} دقيقة على آخر زيارة لك للموقع."
@@ -1338,6 +1485,7 @@ ar:
checking: "جارٍ التحقُّق من توفُّر اسم المستخدم..."
prefilled: "هذا البريد الالكتروني مطابق لاسم المستخدم المسجَّل هذا"
required: "يُرجى إدخال اسم مستخدم"
+ edit: "تعديل اسم المستخدم"
locale:
title: "لغة الواجهة"
instructions: "لغة واجهة المستخدم. ستتغير عند تحديث الصفحة."
@@ -1364,7 +1512,6 @@ ar:
browser_active: '%{browser} | نشط الآن'
browser_last_seen: "%{browser} | %{date}"
last_posted: "آخر مشاركة"
- last_emailed: "آخر رسالة عبر البريد الإلكتروني"
last_seen: "آخر ظهور"
created: "تاريخ الانضمام"
log_out: "تسجيل الخروج"
@@ -1437,15 +1584,22 @@ ar:
title: "الدعوات"
pending_tab: "قيد الانتظار"
pending_tab_with_count: "قيد الانتظار (%{count})"
+ expired_tab: "منتهية"
+ expired_tab_with_count: "المنتهية (%{count})"
redeemed_tab: "تم استردادها"
redeemed_tab_with_count: "تم استردادها (%{count})"
invited_via: "الدعوة"
+ invited_via_link: "رابط %{key} (تم استرداد %{count}/%{max})"
groups: "المجموعات"
topic: "الموضوع"
+ sent: "تاريخ الإنشاء/آخر إرسال"
expires_at: "وقت الانتهاء"
edit: "تعديل"
remove: "إزالة"
+ copy_link: "إنشاء رابط"
+ reinvite: "إعادة إرسال الرسالة الإلكترونية"
reinvited: "تمت إعادة إرسال الدعوة"
+ removed: "تمت إزالتها"
search: "اكتب للبحث في الدعوات..."
user: "المستخدم المدعو"
none: "لا توجد دعوات لعرضها."
@@ -1465,7 +1619,9 @@ ar:
remove_all: "إزالة الدعوات المنتهية"
removed_all: "تمت إزالة جميع الدعوات المنتهية!"
remove_all_confirm: "هل تريد بالتأكيد إزالة جميع الدعوات المنتهية؟"
+ reinvite_all: "إعادة إرسال جميع الدعوات"
reinvite_all_confirm: "هل تريد بالتأكيد إعادة إرسال كل الدعوات؟"
+ reinvited_all: "تم إرسال جميع الدعوات!"
time_read: "وقت القراءة"
days_visited: "أيام الزيارة"
account_age_days: "عمر الحساب بالأيام"
@@ -1481,9 +1637,34 @@ ar:
error: "حدث خطأ في أثناء إنشاء رابط الدعوة"
max_redemptions_allowed_label: "كم شخصًا يمكنه التسجيل باستخدام هذا الرابط؟"
expires_at: "متى تنتهي صلاحية رابط الدعوة هذا؟"
+ invite:
+ new_title: "إنشاء دعوة"
+ edit_title: "تعديل الدعوة"
+ instructions: "شارك هذا الرابط لمنح حق الوصول إلى هذا الموقع على الفور"
+ copy_link: "نسخ الرابط"
+ expires_in_time: "تنتهي صلاحيتها في %{time}"
+ expired_at_time: "تنتهي صلاحيتها في %{time}"
+ show_advanced: "عرض الخيارات المتقدمة"
+ hide_advanced: "عرض الخيارات المتقدمة"
+ restrict_email: "التقييد على عنوان بريد إلكتروني واحد"
+ max_redemptions_allowed: "الحد الأقصى لمرات الاستخدام"
+ add_to_groups: "إضافة إلى المجموعات"
+ invite_to_topic: "الوصول إلى هذا الموضوع"
+ expires_at: "تنتهي بعد"
+ custom_message: "رسالة خاصة اختيارية"
+ send_invite_email: "حفظ الرسالة الإلكترونية وإرسالها"
+ save_invite: "حفظ الدعوة"
+ invite_saved: "تم حفظ الدعوة."
+ invite_copied: "تم نسخ رابط الدعوة."
bulk_invite:
none: "لا توجد دعوات لعرضها على هذه الصفحة."
text: "دعوة جماعية"
+ instructions: |
+ أرسل دعوة إلى قائمة من المستخدمين لإنشاء مجتمعك بسرعة. يمكنك إعداد ملف CSV يتضمَّن صفًا واحدًا على الأقل لكل عنوان بريد إلكتروني للمستخدمين الذين تريد دعوتهم. يمكنك إدخال المعلومات التالية المفصولة بفاصلات إذا كنت تريد إضافة أشخاص إلى مجموعات أو إرسالهم إلى موضوع معيَّن في المرة الأولى التي يسجِّلون فيها الدخول.
+ john@smith.com,first_group_name;second_group_name,topic_id
+ سيتم إرسال دعوة إلى كل عنوان بريد إلكتروني في ملف CSV الذي تم تحميله، وستتمكن من إدارته لاحقًا.
+ progress: "تم تحميل %{progress}%..."
+ success: "تم تحميل الملف بنجاح. سيتم إرسال إشعار إليك عبر رسالة عند اكتمال العملية."
error: "عذرًا، يجب أن يكون الملف بتنسيق CSV."
password:
title: "كلمة السر"
@@ -1580,12 +1761,15 @@ ar:
avatar:
title: "صورة الملف الشخصي"
header_title: "الملف الشخصي والرسائل والإشارات المرجعية والتفضيلات"
+ edit: "تعديل صورة الملف الشخصي"
title:
title: "اللقب"
none: "(لا يوجد)"
+ instructions: "يظهر بعد اسم المستخدم"
flair:
title: "الطابع"
none: "(لا يوجد)"
+ instructions: "يتم عرض الرمز بجوار صورة ملفك الشخصي"
primary_group:
title: "المجموعة الرئيسية"
none: "(لا يوجد)"
@@ -1620,19 +1804,20 @@ ar:
close: "إغلاق"
dismiss_error: "تجاهل الخطأ"
close: "إغلاق"
+ assets_changed_confirm: "لقد تلقى هذا الموقع ترقية برمجية للتو. هل تريد الحصول على أحدث نسخة الآن؟"
logout: "تم تسجيل خروجك."
refresh: "تحديث"
home: "الصفحة الرئيسية"
read_only_mode:
- enabled: "هذا الموقع في وضع القراءة فقط. نأمل أن تواصل تصفُّحه، لكن الرد وتسجيل الإعجاب وغيرهما من الإجراءات ستكون معطَّلة حاليًا."
- login_disabled: "يكون تسجيل الدخول معطلًا في حال كان الموقع في وضع القراءة فقط."
- logout_disabled: "يتم تعطيل تسجيل الخروج عندما يكون الموقع في وضع القراءة فقط."
+ enabled: "هذا الموقع في وضع القراءة فقط. نأمل أن تواصل تصفُّحه، لكن الرد وتسجيل الإعجاب وغيرهما من الإجراءات ستكون متوقفة حاليًا."
+ login_disabled: "يكون تسجيل الدخول متوقفًا في حال كان الموقع في وضع القراءة فقط."
+ logout_disabled: "يتم إيقاف تسجيل الخروج عندما يكون الموقع في وضع القراءة فقط."
too_few_topics_and_posts_notice_MF: >-
- لنبدأ المناقشة! هناك {currentTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} و{currentPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}}. يحتاج الزوار إلى المزيد ليقرؤوه ويردوا عليه. إننا نقترح {requiredTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} و{requiredPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}} على الأقل. يمكن لطاقم العمل فقط رؤية هذه الرسالة.
+ لنبدأ المناقشة! هناك {currentTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} و{currentPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}}. يحتاج الزوار إلى المزيد ليقرؤوه ويردوا عليه. إننا نقترح {requiredTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} و{requiredPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}} على الأقل. يمكن لفريق العمل فقط رؤية هذه الرسالة.
too_few_topics_notice_MF: >-
- لنبدأ المناقشة! هناك {currentTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}}. يحتاج الزوار إلى المزيد ليقرؤوه ويردوا عليه. إننا نقترح {requiredTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} على الأقل. يمكن لطاقم العمل فقط رؤية هذه الرسالة.
+ لنبدأ المناقشة! هناك {currentTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}}. يحتاج الزوار إلى المزيد ليقرؤوه ويردوا عليه. إننا نقترح {requiredTopics, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} على الأقل. يمكن لفريق العمل فقط رؤية هذه الرسالة.
too_few_posts_notice_MF: >-
- لنبدأ المناقشة! هناك {currentPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}}. يحتاج الزوار إلى المزيد ليقرؤوه ويردوا عليه. إننا نقترح {requiredPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}} على الأقل. يمكن لطاقم العمل فقط رؤية هذه الرسالة.
+ لنبدأ المناقشة! هناك {currentPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}}. يحتاج الزوار إلى المزيد ليقرؤوه ويردوا عليه. إننا نقترح {requiredPosts, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}} على الأقل. يمكن لفريق العمل فقط رؤية هذه الرسالة.
logs_error_rate_notice:
reached_hour_MF: "{relativeAge} – لقد بلغ معدل الخطأ {rate, plural, zero {# خطأ/الساعة} one {خطأ واحد (#)/الساعة} two {خطآن (#)/الساعة} few {# أخطاء/الساعة} many {# خطأ/الساعة} other {# خطأ/الساعة}} حد إعدادات الموقع البالغ {limit, plural, zero {# خطأ/الساعة} one {خطأ واحد (#)/الساعة} two {خطآن (#)/الساعة} few {# أخطاء/الساعة} many {# خطأ/الساعة} other {# خطأ/الساعة}}."
reached_minute_MF: "{relativeAge} – لقد بلغ معدل الخطأ {rate, plural, zero {# خطأ/الدقيقة} one {خطأ واحد (#)/الدقيقة} two {خطآن (#)/الدقيقة} few {# أخطاء/الدقيقة} many {# خطأ/الدقيقة} other {# خطأ/الدقيقة}} حد إعدادات الموقع البالغ {limit, plural, zero {# خطأ/الدقيقة} one {خطأ واحد (#)/الدقيقة} two {خطآن (#)/الدقيقة} few {# أخطاء/الدقيقة} many {# خطأ/الدقيقة} other {# خطأ/الدقيقة}}."
@@ -1660,10 +1845,19 @@ ar:
sign_up: "الاشتراك"
hide_session: "تذكيري غدًا"
hide_forever: "لا، شكرًا"
+ hidden_for_session: "حسنًا، سنسألك غدًا. يمكنك دائمًا استخدام \"تسجيل الدخول\" لإنشاء حساب أيضًا."
intro: "مرحبًا! يبدو أنك تستمتع بالمناقشة، لكنك لم تشترك للحصول على حساب حتى الآن."
value_prop: "عند إنشاء حساب، فإننا نتذكَّر ما قرأته بالضبط؛ حتى تتمكن دائمًا من العودة والمتابعة من حيث توقفت. ستتلقى أيضًا الإشعارات هنا وعبر البريد الإلكتروني كلما ردَّ أحد عليك. ويمكنك تسجيل إعجابك بالمنشورات لمشاركة مشاعر الود. :heartpulse:"
summary:
enabled_description: "أنت تعرض ملخصًا لهذا الموضوع: المنشورات الأكثر إثارة للاهتمام وفقًا للمجتمع."
+ description:
+ zero: "هناك %{count} من الردود."
+ one: "هناك رد واحد (%{count})."
+ two: "هناك %{count} من الردود."
+ few: "هناك %{count} من الردود."
+ many: "هناك %{count} من الردود."
+ other: "هناك %{count} من الردود."
+ description_time_MF: "هناك {replyCount, plural, zero {# رد} one {رد واحد (#)} two {ردَّان (#)} few {# ردود} many {# ردًا} other {# رد}} والوقت المقدَّر للقراءة هو {readingTime, plural, zero {# دقيقة} one {دقيقة واحدة (#)} two {دقيقتان (#)} few {# دقائق} many {# دقيقة} other {# دقيقة}}"
enable: "تلخيص هذا الموضوع"
disable: "عرض كل المنشورات"
deleted_filter:
@@ -1689,25 +1883,28 @@ ar:
search_hint: "اسم المستخدم أو البريد إلكتروني أو عنوان IP"
create_account:
header_title: "مرحبًا!"
+ subheader_title: "لننشئ حسابك"
disclaimer: "يشير التسجيل إلى موافقتك على سياسة الخصوصية وشروط الخدمة."
+ title: "إنشاء حسابك"
failed: "حدث خطأ ما. قد يكون هذا البريد الإلكتروني مسجلًا بالفعل. جرِّب رابط نسيان كلمة المرور"
forgot_password:
- title: "إعادة تعيين كلمة المرور"
+ title: "إعادة ضبط كلمة المرور"
action: "نسيت كلمة مروري"
- invite: "أدخِل اسم المستخدم أو عنوان البريد الإلكتروني، وسنُرسل إليك رسالة إلكترونية لإعادة تعيين كلمة المرور."
+ 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: "لم تصلك الرسالة الإلكترونية بعد؟ احرص على التحقُّق من مجلد البريد غير المرغوب فيه أولًا.لست متأكدًا من عنوان البريد الإلكتروني الذي استخدمته؟ أدخِل عنوان البريد الإلكتروني وسنخبرك إذا كان موجودًا هنا.
إذا فقدت الوصول إلى عنوان البريد الإلكتروني المرتبط بحسابك، يُرجى التواصل مع طاقم عملنا لمساعدتك.
"
+ help: "لم تصلك الرسالة الإلكترونية بعد؟ احرص على التحقُّق من مجلد البريد غير المرغوب فيه أولًا.لست متأكدًا من عنوان البريد الإلكتروني الذي استخدمته؟ أدخِل عنوان البريد الإلكتروني وسنخبرك إذا كان موجودًا هنا.
إذا فقدت الوصول إلى عنوان البريد الإلكتروني المرتبط بحسابك، يُرجى التواصل مع فريق عملنا لمساعدتك.
"
button_ok: "حسنًا"
button_help: "المساعدة"
email_login:
link_label: "مراسلتي عبر البريد الإلكتروني برابط تسجيل الدخول"
button_label: "عبر البريد الإلكتروني"
+ login_link: "تخطي كلمة المرور؛ مراسلتي عبر البريد الإلكتروني برابط تسجيل الدخول"
emoji: "رمز قفل"
complete_username: "إذا تطابق أحد الحسابات مع اسم المستخدم %{username}، فمن المفترض أن تتلقى رسالة إلكترونية برابط تسجيل الدخول قريبًا."
complete_email: "إذا تطابق أحد الحسابات مع عنوان البريد الإلكتروني %{email}، فمن المفترض أن تتلقى رسالة إلكترونية برابط تسجيل الدخول قريبًا."
@@ -1721,6 +1918,7 @@ ar:
login:
header_title: "مرحبًا بعودتك"
subheader_title: "تسجيل الدخول إلى حسابك"
+ title: "تسجيل الدخول"
username: "المستخدم"
password: "كلمة المرور"
second_factor_title: "المصادقة الثنائية"
@@ -1735,9 +1933,10 @@ ar:
security_key_not_allowed_error: "انتهت مهلة عملية المصادقة باستخدام مفتاح أمان أو تم إلغاؤها."
security_key_no_matching_credential_error: "تعذَّر العثور على بيانات اعتماد مطابقة في مفتاح الأمان المقدَّم."
security_key_support_missing_error: "لا يدعم جهازك أو متصفحك الحالي استخدام مفاتيح الأمان. يُرجى استخدام طريقة مختلفة."
+ email_placeholder: "البريد الإلكتروني/اسم المستخدم"
caps_lock_warning: "مفتاح Caps Lock مفعَّل"
error: "خطأ غير معروف"
- cookies_error: "يبدو أن ملفات تعريف الارتباط في متصفحك معطَّلة. قد لا تتمكن من تسجيل الدخول قبل تفعيلها أولًا."
+ cookies_error: "يبدو أن ملفات تعريف الارتباط في متصفحك متوقفة. قد لا تتمكن من تسجيل الدخول قبل تفعيلها أولًا."
rate_limit: "يُرجى الانتظار قبل محاولة تسجيل الدخول مرة أخرى."
blank_username: "يُرجى إدخال بريدك الإلكتروني أو اسم المستخدم."
blank_username_or_password: "يُرجى إدخال بريدك الإلكتروني أو اسم المستخدم، وكلمة المرور."
@@ -1746,7 +1945,7 @@ ar:
or: "أو"
authenticating: "جارٍ المصادقة..."
awaiting_activation: "ما زال حسابك بانتظار التفعيل، استخدم رابط \"نسيت كلمة المرور\" لإرسال رسالة إلكترونية أخرى للتفعيل."
- awaiting_approval: "لم يوافق أي من أعضاء طاقم العمل على حسابك بعد. سنُرسل إليك رسالة إلكترونية عند الموافقة عليه."
+ awaiting_approval: "لم يوافق أي من أعضاء فريق العمل على حسابك بعد. سنُرسل إليك رسالة إلكترونية عند الموافقة عليه."
requires_invite: "عذرًا، الوصول إلى هذا المنتدى مقصور على أصحاب الدعوات فقط."
not_activated: "لا يمكنك تسجيل الدخول بعد. لقد أرسلنا سابقًا رسالة إلكترونية للتفعيل إلى %{sentTo}. يُرجى اتباع الإرشادات الواردة في هذه الرسالة لتفعيل حسابك."
not_allowed_from_ip_address: "لا يمكنك تسجيل الدخول من عنوان IP هذا."
@@ -1826,7 +2025,23 @@ ar:
few: "%{count} موضوعات في هذه الفئة"
many: "%{count} موضوعًا في هذه الفئة"
other: "%{count} موضوع في هذه الفئة"
+ plus_subcategories_title:
+ zero: "%{name} تصنيف فرعي واحد و %{count} تصنيفات فرعية"
+ one: "%{name} وفئة فرعية واحدة (%{count})"
+ two: "%{name} وفئتان فرعيتان (%{count})"
+ few: "%{name} و%{count} فئات فرعية"
+ many: "%{name} و%{count} فئة فرعية"
+ other: "%{name} و%{count} فئة فرعية"
+ plus_subcategories:
+ zero: "تصنيف فرعي + %{count} و تصنيفات فرعية"
+ one: "+ %{count} تصنيفات فرعية"
+ two: "+ %{count} تصنيف فرعي واحد و تصنيفات فرعية"
+ few: "+ %{count} فئات فرعية"
+ many: "+ %{count} فئة فرعية"
+ other: "+ %{count} فئة فرعية"
select_kit:
+ filter_by: "التصفية حسب: %{name}"
+ select_to_filter: "تحديد قيمة للتصفية"
default_header_text: تحديد...
no_content: لم يتم العثور على نتائج مطابقة
filter_placeholder: بحث...
@@ -1847,13 +2062,15 @@ ar:
many: "حدِّد %{count} عنصرًا على الأقل."
other: "حدِّد %{count} عنصر على الأقل."
invalid_selection_length:
- zero: "يجب ألا يقل الاختيار عن %{count} حرف على الأقل."
- one: "يجب ألا يقل الاختيار عن حرف واحد (%{count}) على الأقل."
- two: "يجب ألا يقل الاختيار عن حرفين (%{count}) على الأقل."
- few: "يجب ألا يقل الاختيار عن %{count} أحرف على الأقل."
- many: "يجب ألا يقل الاختيار عن %{count} حرفًا على الأقل."
- other: "يجب ألا يقل الاختيار عن %{count} حرف على الأقل."
+ zero: "يجب ألا يقل التحديد عن %{count} حرف على الأقل."
+ one: "يجب ألا يقل التحديد عن حرف واحد (%{count}) على الأقل."
+ two: "يجب ألا يقل التحديد عن حرفين (%{count}) على الأقل."
+ few: "يجب ألا يقل التحديد عن %{count} أحرف على الأقل."
+ many: "يجب ألا يقل التحديد عن %{count} حرفًا على الأقل."
+ other: "يجب ألا يقل التحديد عن %{count} حرف على الأقل."
components:
+ tag_drop:
+ filter_for_more: المزيد من عوامل التصفية...
categories_admin_dropdown:
title: "إدارة الفئات"
date_time_picker:
@@ -1880,6 +2097,7 @@ ar:
default: الرموز التعبيرية المخصَّصة
shared_drafts:
title: "المسودات المشتركة"
+ notice: "هذا الموضوع مرئي فقط لمن يمكنهم نشر المسودات المشتركة."
destination_category: "فئة الوجهة"
publish: "نشر المسودة المشتركة"
confirm_publish: "هل تريد بالتأكيد نشر هذه المسودة؟"
@@ -1914,12 +2132,33 @@ ar:
other: "تشير الإشارة إلى %{group} أنك على وشك إرسال إشعار إلى %{count} شخص). هل أنت متأكد؟"
cannot_see_mention:
category: "لقد أشرت إلى %{username}، لكنه لن يتلقي إشعارًا لأنه ليس لديه إذن بالوصول إلى هذه الفئة. عليك إضافته إلى إحدى المجموعات التي لديها إذن بالوصول إلى هذه الفئة."
- private: "لقد أشرت إلى %{username}، ولكن لن يتم إشعاره لأنه لا يمكنه رؤية هذه الرسالة الشخصية. عليك دعوته إلى هذه الرسالة الشخصية."
+ private: "لقد أشرت إلى %{username}، ولكن لن يتم إشعاره لأنه لا يمكنه رؤية هذه الرسالة الخاصة. عليك دعوته إلى هذه الرسالة الخاصة."
duplicate_link: "يبدو أن رابطك إلى %{domain} قدم تم نشره في الموضوع بواسطة @%{username} في رد بتاريخ %{ago}. هل تريد بالتأكيد نشره مرة أخرى؟"
reference_topic_title: "بخصوص: %{title}"
error:
title_missing: "العنوان مطلوب"
+ title_too_short:
+ zero: "العنوان يجب أن يكون علي الاقل %{count} حرف"
+ one: "يجب ألا يقل العنوان عن حرف واحد (%{count})"
+ two: "العنوان يجب أن يكون علي الاقل %{count} حرف"
+ few: "العنوان يجب أن يكون علي الاقل %{count} حرف"
+ many: "العنوان يجب أن يكون علي الاقل %{count} حرف"
+ other: "العنوان يجب أن يكون علي الاقل %{count} حرف"
+ title_too_long:
+ zero: "العنوان يجب أن لا يزيد عن %{count} حرف"
+ one: "يجب ألا يزيد العنوان عن حرف واحد (%{count})"
+ two: "العنوان يجب أن لا يزيد عن %{count} حرف"
+ few: "العنوان يجب أن لا يزيد عن %{count} حرف"
+ many: "العنوان يجب أن لا يزيد عن %{count} حرف"
+ other: "العنوان يجب أن لا يزيد عن %{count} حرف"
post_missing: "لا يمكن ترك المنشور فارغًا"
+ post_length:
+ zero: "المنشور يجب أن يكون علي الاقل %{count} حرف"
+ one: "يجب ألا يقل المنشور عن حرف واحد (%{count})"
+ two: "المنشور يجب أن يكون علي الاقل %{count} حرف"
+ few: "المنشور يجب أن يكون علي الاقل %{count} حرف"
+ many: "المنشور يجب أن يكون علي الاقل %{count} حرف"
+ other: "المنشور يجب أن يكون علي الاقل %{count} حرف"
try_like: "هل جرَّبت زر %{heart}؟"
category_missing: "عليك اختيار فئة"
tags_missing:
@@ -1929,7 +2168,7 @@ ar:
few: "عليك اختيار %{count} وسوم على الأقل"
many: "عليك اختيار %{count} وسمًا على الأقل"
other: "عليك اختيار %{count} وسم على الأقل"
- topic_template_not_modified: "يُرجى إضافة التفاصيل والمواصفات المحدَّدة إلى موضوعك من خلال تعديل قالب الموضوع."
+ topic_template_not_modified: "يُرجى إضافة التفاصيل والمواصفات المحدَّدة إلى موضوعك من خلال تعديل نموذج الموضوع."
save_edit: "حفظ التعديل"
overwrite_edit: "استبدال التعديل"
reply_original: "كتابة رد على الموضوع الأصلي"
@@ -1950,12 +2189,14 @@ ar:
remove_featured_link: "أزِل الرابط من الموضوع."
reply_placeholder: "اكتب هنا. استخدم Markdown أو BBCode أو HTML للتنسيق. اسحب الصور أو الصقها."
reply_placeholder_no_images: "اكتب هنا. استخدم Markdown أو BBCode أو HTML للتنسيق."
- reply_placeholder_choose_category: "اختر فئةً قبل الكتابة هنا."
+ reply_placeholder_choose_category: "حدِّد فئةً قبل الكتابة هنا."
view_new_post: "اعرض منشورك الجديد."
saving: "جارٍ الحفظ"
saved: "تم الحفظ!"
saved_draft: "لديك مسودة منشور محفوظة. اضغط لاستئنافها."
uploading: "جارٍ التحميل..."
+ show_preview: "إظهار المعاينة"
+ hide_preview: "إخفاء المعاينة"
quote_post_title: "اقتباس المنشور بأكمله"
bold_label: "B"
bold_title: "غامق"
@@ -1993,7 +2234,9 @@ ar:
yourself_confirm:
title: "هل نسيت إضافة المستلمين؟"
body: "هذه الرسالة يتم إرسالها إليك فقط في الوقت الحالي!"
- admin_options_title: "إعدادات طاقم العمل الاختيارية لهذا الموضوع"
+ slow_mode:
+ error: "هذا الموضوع في الوضع البطيء. لقد نشرت بالفعل مؤخرًا؛ يمكنك النشر مرة أخرى بعد %{timeLeft}."
+ admin_options_title: "إعدادات فريق العمل الاختيارية لهذا الموضوع"
composer_actions:
reply: الرد
draft: مسودة
@@ -2007,16 +2250,15 @@ ar:
confirm: لديك مسودة موضوع جديد تم حفظها، وسيتم استبدالها إذا أنشأت موضوعًا مرتبطًا.
reply_as_new_group_message:
label: الرد كرسالة جديدة للمجموعة
- desc: إنشاء رسالة خاصة جديدة مع المستلمين أنفسهم
reply_as_private_message:
label: رسالة جديدة
- desc: إنشاء رسالة شخصية جديدة
+ desc: إنشاء رسالة خاصة جديدة
reply_to_topic:
label: الرد على الموضوع
desc: الرد على الموضوع، وليس أي منشور محدَّد
toggle_whisper:
label: تفعيل الهمس
- desc: تكون الهمسات مرئية لأعضاء طاقم العمل فقط
+ desc: تكون الهمسات مرئية لأعضاء فريق العمل فقط
create_topic:
label: "موضوع جديد"
shared_draft:
@@ -2088,6 +2330,7 @@ ar:
granted_badge: "تم منحك شارة \"%{description}\""
topic_reminder: "%{username} %{description}"
watching_first_post: "موضوع جديد: %{description}"
+ membership_request_accepted: "تم قبول العضوية في \"%{group_name}\""
membership_request_consolidated:
zero: "%{count} طلب عضوية مفتوح للمجموعة \"%{group_name}\""
one: "طلب عضوية واحد (%{count}) مفتوح للمجموعة \"%{group_name}\""
@@ -2097,6 +2340,7 @@ ar:
other: "%{count} طلب عضوية مفتوح للمجموعة \"%{group_name}\""
reaction: "%{username} %{description}"
reaction_2: "%{username} و%{username2} %{description}"
+ votes_released: "%{description} - اكتمل"
group_message_summary:
zero: "%{count} رسالة في صندوق البريد الوارد للمجموعة %{group_name}"
one: "رسالة واحدة (%{count}) في صندوق البريد الوارد للمجموعة %{group_name}"
@@ -2110,25 +2354,40 @@ ar:
quoted: 'اقتبس %{username} كلامك في "%{topic}" - %{site_title}'
replied: 'ردَّ %{username} عليك في "%{topic}" - %{site_title}'
posted: 'نشر %{username} منشورًا في "%{topic}" - %{site_title}'
- private_message: 'أرسل %{username} إليك رسالة شخصية في "%{topic}" - %{site_title}'
+ private_message: 'أرسل %{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: "رد جديد"
quoted: "تم اقتباس لكلامك"
+ edited: "تم التعديل"
liked: "مرة إعجاب جديدة"
private_message: "رسالة خاصة جديدة"
invited_to_private_message: "تمت دعوتك إلى رسالة خاصة"
invitee_accepted: "تم قبول الدعوة"
posted: "منشور جديد"
+ moved_post: "تم نقل المنشور"
+ linked: "تم ربطه"
+ bookmark_reminder: "تذكير الإشارة المرجعية"
+ bookmark_reminder_with_name: "تذكير الإشارة المرجعية - %{name"
granted_badge: "تم منحك شارة"
invited_to_topic: "تمت دعوتك إلى موضوع"
+ group_mentioned: "تمت الإشارة إلى المجموعة"
+ group_message_summary: "رسائل جديدة للمجموعة"
watching_first_post: "موضوع جديد"
+ topic_reminder: "تذكير الموضوع"
+ liked_consolidated: "إعجابات جديدة"
+ post_approved: "تمت الموافقة على المنشور"
+ membership_request_consolidated: "طلبات العضوية الجديدة"
reaction: "تفاعل جديد"
+ votes_released: "تم تحرير التصويت"
upload_selector:
uploading: "جارٍ التحميل"
+ processing: "التحميل قيد المعالجة"
select_file: "تحديد الملف"
default_image_alt_text: صورة
search:
@@ -2164,6 +2423,7 @@ ar:
context:
user: "البحث عن المنشورات باسم المستخدم @%{username}"
category: "البحث في الفئة #%{category}"
+ tag: "البحث عن الوسم #%{tag}"
topic: "البحث في هذا الموضوع"
private_messages: "البحث في الرسائل"
advanced:
@@ -2186,12 +2446,14 @@ ar:
created: أنشأتها
watching: أراقبها
tracking: أتتبعها
+ private: في رسائلي
bookmarks: وضعت عليها إشارة مرجعية
first: تكون أول منشور
pinned: تكون مثبَّتة
seen: قرأتها
unseen: لم أقرأها
wiki: تكون من النوع Wiki
+ images: يتضمَّن صورًا
all_tags: كل الوسوم أعلاه
statuses:
label: حيث تكون الموضوعات
@@ -2223,6 +2485,7 @@ ar:
go_back: "الرجوع"
not_logged_in_user: "صفحة المستخدم مع ملخص عن نشاطه الحالي وتفضيلاته"
current_user: "الانتقال إلى صفحة المستخدم"
+ view_all: "عرض الكل %{tab}"
topics:
new_messages_marker: "آخر زيارة"
bulk:
@@ -2230,14 +2493,35 @@ ar:
clear_all: "مسح الكل"
unlist_topics: "إلغاء إدراج الموضوعات"
relist_topics: "إعادة إدراج الموضوعات"
- reset_read: "إعادة تعيين القراءة"
+ reset_read: "إعادة ضبط القراءة"
delete: "حذف الموضوعات"
dismiss: "تجاهل"
dismiss_read: "تجاهل جميع الموضوعات غير المقروءة"
+ dismiss_read_with_selected:
+ zero: "تجاهل %{count} غير مقروءة"
+ one: "تجاهل %{count} غير مقروءة"
+ two: "تجاهل %{count}غير مقروءين"
+ few: "تجاهل %{count} غير مقروءة"
+ many: "تجاهل %{count} غير مقروءة"
+ other: "تجاهل %{count} غير مقروءة"
dismiss_button: "تجاهل..."
+ dismiss_button_with_selected:
+ zero: "تجاهل (%{count})…"
+ one: "تجاهل (%{count})…"
+ two: "تجاهل (%{count})…"
+ few: "تجاهل (%{count})…"
+ many: "تجاهل (%{count})…"
+ other: "تجاهل (%{count})…"
dismiss_tooltip: "تجاهل المنشورات الجديدة فقط أو التوقف عن تتبُّع الموضوعات"
also_dismiss_topics: "التوقف عن تتبُّع هذه الموضوعات حتي لا تظهر لي كغير مقروءة مرة أخرى"
dismiss_new: "تجاهل الجديدة"
+ dismiss_new_with_selected:
+ zero: "تجاهل الجديدة (%{count})"
+ one: "تجاهل الجديدة (%{count})"
+ two: "تجاهل الجديدة (%{count})"
+ few: "تجاهل الجديدة (%{count})"
+ many: "تجاهل الجديدة (%{count})"
+ other: "تجاهل الجديدة (%{count})"
toggle: "تفعيل التحديد الجماعي للموضوعات"
actions: "الإجراءات الجماعية"
change_category: "ضبط الفئة"
@@ -2245,6 +2529,7 @@ ar:
archive_topics: "أرشفة الموضوعات"
move_messages_to_inbox: "النقل إلى صندوق الوارد"
notification_level: "الإشعارات"
+ change_notification_level: "تغيير مستوى الإشعارات"
choose_new_category: "اختر الفئة الجديدة للموضوعات:"
selected:
zero: "لقد حدَّدت %{count} موضوع."
@@ -2258,14 +2543,33 @@ ar:
choose_new_tags: "اختيار الوسوم الجديدة لهذه الموضوعات:"
choose_append_tags: "اختيار الوسوم الجديدة لإضافتها إلى هذه الموضوعات:"
changed_tags: "تم تغيير وسومات هذه الموضوعات."
+ remove_tags: "إزالة كل الوسوم"
+ confirm_remove_tags:
+ zero: "ستتم إزالة كل الوسوم من %{count} موضوع. هل أنت متأكد؟"
+ one: "ستتم إزالة كل الوسوم من هذا الموضوع. هل أنت متأكد؟"
+ two: "ستتم إزالة كل الوسوم من موضوعَين (%{count}). هل أنت متأكد؟"
+ few: "ستتم إزالة كل الوسوم من %{count} موضوعات. هل أنت متأكد؟"
+ many: "ستتم إزالة كل الوسوم من %{count} موضوعًا. هل أنت متأكد؟"
+ other: "ستتم إزالة كل الوسوم من %{count} موضوع. هل أنت متأكد؟"
+ progress:
+ zero: "التقدُّم: %{count} موضوع"
+ one: "التقدُّم: موضوع واحد (%{count})"
+ two: "التقدُّم: موضوعان (%{count})"
+ few: "التقدُّم: %{count} موضوعات"
+ many: "التقدُّم: %{count} موضوعًا"
+ other: "التقدُّم: %{count} موضوع"
none:
unread: "ليست هناك موضوعات غير مقروءة."
new: "ليس لديك موضوعات جديدة."
read: "لم تقرأ أي موضوع بعد."
posted: "لم تنشر في أي موضوع بعد."
+ latest: "لا توجد موضوعات أخرى!"
bookmarks: "لم تضع إشارة مرجعية على أي موضوع بعد."
category: "لا توجد موضوعات في الفئة %{category}."
top: "لا توجد موضوعات في الأكثر نشاطًا."
+ educate:
+ new: 'ستظهر موضوعاتك الجديدة هنا. تُعتبَر الموضوعات جديدة بشكلٍ افتراضي وستعرض مؤشر إذا كان قد تم إنشاؤها في آخر يومين.
يمكنك الانتقال إلى a href="%{userPrefsUrl}">تفضيلاتك لتغيير ذلك.
'
+ unread: "ستظهر موضوعاتك غير المقروءة هنا.
تُعتبَر الموضوعات غير مقروءة بشكلٍ افتراضي وستعرض 1 بالأعداد غير المقروءة إذا كنت قد:
- أنشأت الموضوع
- رددت على الموضوع
- قرأت الموضوع لأكثر من 4 دقائق
أو إذا وضعت الموضوع صراحةً تحت \"المراقبة\" أو \"التتبُّع\" عبر \U0001F514 في كل موضوع.
يمكنك الانتقال إلى a href=\"%{userPrefsUrl}\">تفضيلاتك لتغيير ذلك.
"
bottom:
latest: "لا يوجد المزيد من الموضوعات الحديثة."
posted: "لا يوجد المزيد من الموضوعات المنشورة."
@@ -2295,6 +2599,7 @@ ar:
title: "النقل إلى صندوق الوارد"
help: "إعادة الرسالة إلى صندوق الوارد"
edit_message:
+ help: "تعديل أول منشور في الرسالة"
title: "تعديل"
defer:
help: "وضع علامة كغير مقروءة"
@@ -2344,6 +2649,8 @@ ar:
back_to_list: "العودة إلى قائمة الموضوعات"
options: "خيارات الموضوعات"
show_links: "إظهار الروابط في هذا الموضوع"
+ collapse_details: "طي تفاصيل الموضوع"
+ expand_details: "توسيع تفاصيل الموضوع"
read_more_in_category: "هل تريد قراءة المزيد؟ تصفَّح الموضوعات الأخرى في %{catLink} أو %{latestLink}."
read_more: "هل تريد قراءة المزيد؟ %{catLink} أو %{latestLink}."
unread_indicator: "لم يقرأ أي عضو آخر منشور في هذا الموضوع بعد."
@@ -2352,14 +2659,18 @@ ar:
browse_all_categories: تصفُّح كل الفئات
browse_all_tags: تصفُّح كل الوسوم
view_latest_topics: عرض أحدث الموضوعات
+ suggest_create_topic: هل أنت مستعد لبدء محادثة جديدة؟
jump_reply_up: الانتقال إلى الرد السابق
jump_reply_down: الانتقال إلى الرد التالي
deleted: "لقد تم حذف الموضوع"
slow_mode_update:
title: "الوضع البطيء"
select: "لا يمكن للمستخدمين النشر في هذا الموضوع إلا مرة كل:"
+ description: "لتعزيز المناقشة عميقة الفكر في المناقشات سريعة التطور أو المثيرة للجدل، يجب على المستخدمين الانتظار قبل النشر مرة أخرى في هذا الموضوع."
enable: "تفعيل"
- remove: "تعطيل"
+ update: "تحديث"
+ enabled_until: "مفعَّل حتى:"
+ remove: "إيقاف"
hours: "الساعات:"
minutes: "الدقائق:"
seconds: "الثواني:"
@@ -2374,13 +2685,20 @@ ar:
8_hours: "8 ساعات"
12_hours: "12 ساعة"
24_hours: "24 ساعة"
+ custom: "المدة المخصَّصة"
+ slow_mode_notice:
+ duration: "يُرجى الانتظار لمدة %{duration} بين المنشورات في هذا الموضوع"
topic_status_update:
+ title: "مؤقِّت الموضوع"
save: "ضبط المؤقِّت"
num_of_hours: "عدد الساعات:"
+ num_of_days: "عدد الأيام:"
remove: "إزالة المؤقِّت"
publish_to: "النشر على:"
when: "التوقيت:"
time_frame_required: "يُرجى تحديد إطار زمني"
+ min_duration: "يجب أن تكون المدة أكبر من 0"
+ max_duration: "يجب أن تكون المدة أقل من 20 عامًا"
auto_update_input:
none: "تحديد إطار زمني"
now: "الآن"
@@ -2389,7 +2707,13 @@ ar:
later_this_week: "لاحقًا هذا الأسبوع"
this_weekend: "عطلة هذا الأسبوع"
next_week: "الأسبوع القادم"
+ two_weeks: "أسبوعان"
next_month: "الشهر القادم"
+ two_months: "شهران"
+ three_months: "ثلاثة أشهر"
+ four_months: "أربعة أشهر"
+ six_months: "ستة أشهر"
+ one_year: "عام واحد"
forever: "للأبد"
pick_date_and_time: "اختيار التاريخ والوقت"
set_based_on_last_post: "الإغلاق حسب آخر منشور"
@@ -2403,8 +2727,11 @@ ar:
title: "غلق الموضوع مؤقتًا"
auto_close:
title: "غلق الموضوع تلقائيًا"
+ label: "إغلاق الموضوع تلقائيًا بعد:"
error: "يُرجى إدخال قيمة صالحة."
based_on_last_post: "لا تُغلقه إلى أن يمضي على آخر منشور في الموضوع هذه الفترة على الأقل."
+ auto_close_after_last_post:
+ title: "إغلاق الموضوع تلقائيًا بعد المنشور الأخير"
auto_delete:
title: "حذف الموضوع تلقائيًا"
auto_bump:
@@ -2430,6 +2757,13 @@ ar:
few: "مضت %{count} ساعات بالفعل على آخر منشور في الموضوع؛ لذا فسيتم إغلاق الموضوع على الفور."
many: "مضت %{count} ساعة بالفعل على آخر منشور في الموضوع؛ لذا فسيتم إغلاق الموضوع على الفور."
other: "مضت %{count} ساعة بالفعل على آخر منشور في الموضوع؛ لذا فسيتم إغلاق الموضوع على الفور."
+ auto_close_momentarily:
+ zero: " مضت %{count} ساعة بالفعل على آخر منشور في الموضوع؛ لذلك سيتم إغلاق الموضوع مؤقتًا."
+ one: " مضت ساعة واحدة (%{count}) بالفعل على آخر منشور في الموضوع؛ لذلك سيتم إغلاق الموضوع مؤقتًا."
+ two: " مضت ساعتان (%{count}) بالفعل على آخر منشور في الموضوع؛ لذلك سيتم إغلاق الموضوع مؤقتًا."
+ few: " مضت %{count} ساعات بالفعل على آخر منشور في الموضوع؛ لذلك سيتم إغلاق الموضوع مؤقتًا."
+ many: " مضت %{count} ساعة بالفعل على آخر منشور في الموضوع؛ لذلك سيتم إغلاق الموضوع مؤقتًا."
+ other: " مضت %{count} ساعة بالفعل على آخر منشور في الموضوع؛ لذلك سيتم إغلاق الموضوع مؤقتًا."
timeline:
back: "الرجوع"
back_description: "الرجوع إلى آخر منشور غير مقروء"
@@ -2441,6 +2775,13 @@ ar:
go: "الانتقال"
jump_bottom: "الانتقال إلى آخر منشور"
jump_prompt: "انتقل إلى..."
+ jump_prompt_of:
+ zero: "من %{count} منشورات"
+ one: "من منشور واحد (%{count})"
+ two: "من %{count} منشورات"
+ few: "من %{count} منشورات"
+ many: "من %{count} منشورات"
+ other: "من %{count} منشورات"
jump_prompt_long: "الانتقال إلى..."
jump_bottom_with_number: "انتقل إلى المشاركة رقم %{post_number}"
jump_prompt_to_date: "إلى التاريخ"
@@ -2452,12 +2793,15 @@ ar:
reasons:
mailing_list_mode: "وضع القائمة البريدية مفعَّل لديك؛ لذا فتتلقى إشعارات بالردود على هذا الموضوع عبر البريد الإلكتروني."
"3_10": "ستتلقى إشعارات لأنك تراقب وسمًا في هذا الموضوع."
+ "3_10_stale": "ستتلقى إشعارات لأنك كنت تراقب وسمًا في هذا الموضوع في الماضي."
"3_6": "ستتلقى إشعارات لأنك تراقب هذه الفئة."
+ "3_6_stale": "ستتلقى إشعارات لأنك كنت تراقب هذه الفئة في الماضي."
"3_5": "ستتلقى إشعارات لأنك بدأت في مراقبة هذا الموضوع تلقائيًا."
"3_2": "ستتلقى إشعارات لأنك تراقب هذا الموضوع."
"3_1": "ستتلقى إشعارات لأنك أنشأت هذا الموضوع."
"3": "ستتلقى إشعارات لأنك تراقب هذا الموضوع."
"2_8": "سترى عدد الردود الجديدة لأنك تتتبَّع هذه الفئة."
+ "2_8_stale": "سترى عدد الردود الجديدة لأنك كنت تتتبَّع هذه الفئة في الماضي."
"2_4": "سترى عدد الردود الجديدة لأنك نشرت ردًا في هذا الموضوع."
"2_2": "سترى عدد الردود الجديدة لأنك تتتبَّع هذا الموضوع."
"2": 'سترى عدد الردود الجديدة لأنك قرأت هذا الموضوع.'
@@ -2505,14 +2849,15 @@ ar:
archive: "أرشفة الموضوع"
invisible: "إلغاء الإدراج"
visible: "إدراج"
- reset_read: "إعادة تعيين بيانات القراءة"
+ reset_read: "إعادة ضبط بيانات القراءة"
make_public: "التحويل إلى موضوع عام"
- make_private: "التحويل إلى رسالة شخصية"
- reset_bump_date: "إعادة تعيين تاريخ الرفع"
+ make_private: "التحويل إلى رسالة خاصة"
+ reset_bump_date: "إعادة ضبط تاريخ الرفع"
feature:
pin: "تثبيت الموضوع"
unpin: "إلغاء تثبيت الموضوع"
- pin_globally: "تثبيت الموضوع بشكلٍ عمومي"
+ pin_globally: "تثبيت الموضوع بشكلٍ عام"
+ make_banner: "تحويل الموضوع إلى بانر"
remove_banner: "إزالة الموضوع البانر"
reply:
title: "الرد"
@@ -2521,6 +2866,18 @@ ar:
title: "المشاركة"
extended_title: "مشاركة رابط"
help: "شارِك رابطًا إلى هذا الموضوع"
+ instructions: "مشاركة رابط إلى هذا الموضوع:"
+ copied: "تم نسخ رابط الدعوة."
+ notify_users:
+ title: "إرسال إشعار"
+ instructions: "إرسال إشعار بشأن هذا الموضوع إلى المستخدمين التاليين:"
+ success:
+ zero: "تم إرسال إشعار بشأن هذا الموضوع إلى جميع المستخدمين بنجاح."
+ one: "تم إرسال إشعار بشأن هذا الموضوع إلى %{username} بنجاح."
+ two: "تم إرسال إشعار بشأن هذا الموضوع إلى جميع المستخدمين بنجاح."
+ few: "تم إرسال إشعار بشأن هذا الموضوع إلى جميع المستخدمين بنجاح."
+ many: "تم إرسال إشعار بشأن هذا الموضوع إلى جميع المستخدمين بنجاح."
+ other: "تم إرسال إشعار بشأن هذا الموضوع إلى جميع المستخدمين بنجاح."
invite_users: "دعوة"
print:
title: "الطباعة"
@@ -2549,23 +2906,23 @@ ar:
other: "الموضوعات المثبَّتة حاليًا في %{categoryLink}: %{count}"
pin_globally: "جعل هذا الموضوع يظهر أعلى قوائم الموضوعات حتى"
confirm_pin_globally:
- zero: "لديك بالفعل %{count} موضوع مثبَّت بشكلٍ عمومي. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عمومي؟"
- one: "لديك بالفعل موضوع واحد (%{count}) مثبَّت بشكلٍ عمومي. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عمومي؟"
- two: "لديك بالفعل موضوعان (%{count}) مثبَّتان بشكلٍ عمومي. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عمومي؟"
- few: "لديك بالفعل %{count} موضوعات مثبَّتة بشكلٍ عمومي. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عمومي؟"
- many: "لديك بالفعل %{count} موضوعًا مثبَّتًا بشكلٍ عمومي. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عمومي؟"
- other: "لديك بالفعل %{count} موضوع مثبَّت بشكلٍ عمومي. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عمومي؟"
+ zero: "لديك بالفعل %{count} موضوع مثبَّت بشكلٍ عام. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عام؟"
+ one: "لديك بالفعل موضوع واحد (%{count}) مثبَّت بشكلٍ عام. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عام؟"
+ two: "لديك بالفعل موضوعان (%{count}) مثبَّتان بشكلٍ عام. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عام؟"
+ few: "لديك بالفعل %{count} موضوعات مثبَّتة بشكلٍ عام. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عام؟"
+ many: "لديك بالفعل %{count} موضوعًا مثبَّتًا بشكلٍ عام. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عام؟"
+ other: "لديك بالفعل %{count} موضوع مثبَّت بشكلٍ عام. قد تصبح كثرة الموضوعات المثبَّتة مصدر إزعاج للمستخدمين الجُدد والمجهولين. هل تريد بالتأكيد تثبيت موضوع آخر بشكلٍ عام؟"
unpin_globally: "إزالة هذا الموضوع من أعلى جميع قوائم الموضوعات"
unpin_globally_until: "إزالة هذا الموضوع من أعلى جميع قوائم الموضوعات أو الانتظار حتى %{until}."
global_pin_note: "يمكن للمستخدمين إلغاء تثبيت الموضوع بشكلٍ فردي لأنفسهم."
- not_pinned_globally: "لا توجد موضوعات مثبَّتة بشكلٍ عمومي."
+ not_pinned_globally: "لا توجد موضوعات مثبَّتة بشكلٍ عام."
already_pinned_globally:
- zero: "الموضوعات المثبَّتة حاليًا بشكلٍ عمومي: %{count}"
- one: "الموضوعات المثبَّتة حاليًا بشكلٍ عمومي: %{count}"
- two: "الموضوعات المثبَّتة حاليًا بشكلٍ عمومي: %{count}"
- few: "الموضوعات المثبَّتة حاليًا بشكلٍ عمومي: %{count}"
- many: "الموضوعات المثبَّتة حاليًا بشكلٍ عمومي: %{count}"
- other: "الموضوعات المثبَّتة حاليًا بشكلٍ عمومي: %{count}"
+ zero: "الموضوعات المثبَّتة حاليًا بشكلٍ عام: %{count}"
+ one: "الموضوعات المثبَّتة حاليًا بشكلٍ عام: %{count}"
+ two: "الموضوعات المثبَّتة حاليًا بشكلٍ عام: %{count}"
+ few: "الموضوعات المثبَّتة حاليًا بشكلٍ عام: %{count}"
+ many: "الموضوعات المثبَّتة حاليًا بشكلٍ عام: %{count}"
+ other: "الموضوعات المثبَّتة حاليًا بشكلٍ عام: %{count}"
make_banner: "تحويل هذا الموضوع إلى إعلان يظهر في أعلى جميع الصفحات."
remove_banner: "إزالة البانر الذي يظهر في أعلى جميع الصفحات"
banner_note: "يمكن للمستخدمين تجاهل البانر بإغلاقه. ولا يمكن تحويل أكثر من موضوع واحد إلى بانر في الوقت نفسه."
@@ -2589,6 +2946,7 @@ ar:
username_placeholder: "اسم المستخدم"
action: "إرسال دعوة"
help: "دعوة الآخرين إلى هذا الموضوع عبر البريد الإلكتروني أو الإشعارات"
+ to_forum: "سنُرسل رسالة إلكترونية مختصرة تسمح لصديقك بالانضمام فورًا بالنقر على رابط."
discourse_connect_enabled: "أدخِل اسم المستخدم للشخص الذي تريد دعوته إلى هذا الموضوع."
to_topic_blank: "أدخِل اسم المستخدم أو عنوان البريد الإلكتروني للشخص الذي تريد دعوته إلى هذا الموضوع."
to_topic_email: "لقد أدخلت عنوان بريد إلكتروني. سنُرسل دعوة عبر البريد الإلكتروني تتيح لصديقك الرد فورًا على هذا الموضوع."
@@ -2713,7 +3071,7 @@ ar:
label: "تحديد"
title: "إضافة المنشور إلى التحديد"
selected_post:
- label: "تم تحديده"
+ label: "محدَّد"
title: "انقر لإزالة المنشور من التحديد"
select_replies:
label: "تحديد المنشور +الردود"
@@ -2732,21 +3090,25 @@ ar:
few: "لقد حدَّدت %{count} منشورات."
many: "لقد حدَّدت %{count} منشورًا."
other: "لقد حدَّدت %{count} منشور."
+ deleted_by_author_simple: "(تم حذف الموضوع بواسطة الكاتب)"
post:
quote_reply: "اقتباس"
quote_share: "مشاركة"
edit_reason: "السبب: "
post_number: "المنشور %{number}"
ignored: "محتوى تم تجاهله"
+ wiki_last_edited_on: "تم تعديل Wiki آخر مرة في %{dateTime}"
+ last_edited_on: "تم تعديل المنشور آخر مرة في %{dateTime}"
reply_as_new_topic: "الرد كموضوع مرتبط"
reply_as_new_private_message: "الرد في رسالة جديدة إلى المستلمين أنفسهم"
continue_discussion: "متابعة المناقشة من %{postLink}:"
follow_quote: "انتقل إلى المنشور الذي تم اقتباسه"
show_full: "عرض المنشور الكامل"
show_hidden: "عرض المحتوى الذي تم تجاهله"
+ deleted_by_author_simple: "(تم حذف المنشور بواسطة الكاتب)"
collapse: "طي"
expand_collapse: "توسيع/طي"
- locked: "قفل أحد أعضاء طاقم العمل تعديل هذه المشاركة"
+ locked: "قفل أحد أعضاء فريق العمل تعديل هذه المشاركة"
gap:
zero: "عرض %{count} رد مخفي"
one: "عرض رد مخفي واحد (%{count})"
@@ -2782,7 +3144,22 @@ ar:
few: "لقد سجَّلت إعجابك أنت و%{count} أشخاص آخرين بهذا المنشور"
many: "لقد سجَّلت إعجابك أنت و%{count} شخصًا آخر بهذا المنشور"
other: "لقد سجَّلت إعجابك أنت و%{count} شخص آخر بهذا المنشور"
+ filtered_replies_hint:
+ zero: "عرض هذا المنشور وردوده %{count}"
+ one: "عرض هذا المنشور والرد عليه"
+ two: "عرض هذا المنشور وردَّين (%{count}) عليه"
+ few: "عرض هذا المنشور و%{count} ردود عليه"
+ many: "عرض هذا المنشور و%{count} ردًا عليه"
+ other: "عرض هذا المنشور و%{count} رد عليه"
+ filtered_replies_viewing:
+ zero: "عرض رد %{count} على"
+ one: "يتم عرض رد واحد (%{count}) على"
+ two: "يتم عرض ردَّين (%{count}) رد على"
+ few: "يتم عرض %{count} ردود على"
+ many: "يتم عرض %{count} ردًا على"
+ other: "يتم عرض %{count} رد على"
in_reply_to: "تحميل المنشور الرئيسي"
+ view_all_posts: "عرض جميع المنشورات"
errors:
create: "عذرًا، حدث خطأ في أثناء إنشاء منشورك. يُرجى إعادة المحاولة."
edit: "عذرا، حدث خطأ في أثناء تعديل منشورك. يُرجى إعادة المحاولة."
@@ -2800,13 +3177,16 @@ ar:
image_upload_not_allowed_for_new_user: "عذرًا، لا يمكن للمستخدمين الجُدد تحميل الصور."
attachment_upload_not_allowed_for_new_user: "عذرًا، لا يمكن للمستخدمين الجُدد تحميل المرفقات."
attachment_download_requires_login: "عذرًا، عليك تسجيل الدخول لتنزيل المرفقات."
+ cancel_composer:
+ confirm: "ماذا تريد أن تفعل بمنشورك؟"
+ discard: "تجاهل"
+ save_draft: "حفظ المسودة لوقتٍ لاحق"
+ keep_editing: "الاستمرار في التعديل"
via_email: "لقد وصل هذا المنشور عبر البريد الإلكتروني"
via_auto_generated_email: "لقد وصل هذا المنشور عبر رسالة إلكترونية تم إنشاؤها تلقائيًا"
whisper: "هذا المنشور عبارة عن همسة خاصة للمشرفين"
wiki:
about: "هذا المنشور عبارة عن Wiki"
- archetypes:
- save: "خيارات الحفظ"
few_likes_left: "نشكرك على نشر المحبة في المجتمع! و لكن للأسف لقد اقتربت من الحد اليومي المسموح به لمرات تسجيل الإعجاب."
controls:
reply: "ابدأ في كتابة رد على هذا المنشور"
@@ -2842,8 +3222,8 @@ ar:
admin: "إجراءات المسؤول على المنشور"
wiki: "التحويل إلى Wiki"
unwiki: "إزالة Wiki"
- convert_to_moderator: "لون إضافة طاقم العمل"
- revert_to_regular: "لون إزالة طاقم العمل"
+ convert_to_moderator: "لون إضافة فريق العمل"
+ revert_to_regular: "لون إزالة فريق العمل"
rebake: "إعادة صياغة HTML"
publish_page: "نشر الصفحة"
unhide: "إظهار"
@@ -2852,17 +3232,25 @@ ar:
lock_post: "قفل المنشور"
lock_post_description: "امنع كاتب المنشور من تعديله"
unlock_post: "إلغاء قفل المنشور"
- unlock_post_description: "اسمح لكاتب المنشور بتعديله"
+ unlock_post_description: "اسمح لكاتب المشاركة تعديلها"
delete_topic_disallowed_modal: "ليس لديك إذن بحذف هذا الموضوع. إذا كنت تريد حقًا حذفه، فأبلِغ عنه مع ذكر السبب للفت انتباه أحد المشرفين إليه."
delete_topic_disallowed: "ليس لديك الإذن بحذف هذا الموضوع"
+ delete_topic_confirm_modal:
+ zero: "هذا الموضوع يحتوي حاليا على أكثر من %{count} مشاهدة، وقد يكون مشهورًا في نتائج البحث. هل متأكد من حذف هذا الموضوع بالكامل، بدلاً من تعديله لتحسينه؟"
+ one: "يحتوي هذا الموضوع حاليًا على أكثر من مرة عرض واحدة (%{count})، وقد يصبح رائجًا في نتائج البحث. هل تريد بالتأكيد حذف هذا الموضوع بالكامل بدلًا من تعديله لتحسينه؟"
+ two: "هذا الموضوع يحتوي حاليا على أكثر من %{count} مشاهدة، وقد يكون مشهورًا في نتائج البحث. هل متأكد من حذف هذا الموضوع بالكامل، بدلاً من تعديله لتحسينه؟"
+ few: "هذا الموضوع يحتوي حاليا على أكثر من %{count} مشاهدة، وقد يكون مشهورًا في نتائج البحث. هل متأكد من حذف هذا الموضوع بالكامل، بدلاً من تعديله لتحسينه؟"
+ many: "هذا الموضوع يحتوي حاليا على أكثر من %{count} مشاهدة، وقد يكون مشهورًا في نتائج البحث. هل متأكد من حذف هذا الموضوع بالكامل، بدلاً من تعديله لتحسينه؟"
+ other: "هذا الموضوع يحتوي حاليا على أكثر من %{count} مشاهدة، وقد يكون مشهورًا في نتائج البحث. هل متأكد من حذف هذا الموضوع بالكامل، بدلاً من تعديله لتحسينه؟"
delete_topic_confirm_modal_yes: "نعم، حذف هذا الموضوع"
delete_topic_confirm_modal_no: "لا، الاحتفاظ بهذا الموضوع"
delete_topic_error: "حدث خطأ في أثناء حذف هذا الموضوع"
delete_topic: "حذف الموضوع"
- add_post_notice: "إضافة إخطار من طاقم العمل"
- change_post_notice: "تغيير إخطار طاقم العمل"
- delete_post_notice: "حذف إخطار طاقم العمل"
+ add_post_notice: "إضافة إشعار من فريق العمل"
+ change_post_notice: "تغيير إشعار فريق العمل"
+ delete_post_notice: "حذف إشعار فريق العمل"
remove_timer: "إزالة المؤقِّت"
+ edit_timer: "تعديل المؤقِّت"
actions:
people:
like:
@@ -2963,6 +3351,12 @@ ar:
edit_bookmark:
name: "تعديل الإشارة المرجعية"
description: "تعديل اسم الإشارة المرجعية أو تغيير تاريخ التذكير ووقته"
+ pin_bookmark:
+ name: "تثبيت الإشارة المرجعية"
+ description: "ثبِّت هذه الإشارة المرجعية. سيؤدي ذلك إلى ظهورها في أعلى قائمة الإشارات المرجعية لديك."
+ unpin_bookmark:
+ name: "إلغاء تثبيت الإشارة المرجعية"
+ description: "قم بإلغاء تثبيت هذه الإشارة المرجعية. ولن تظهر في أعلى قائمة الإشارات المرجعية لديك بعد الآن."
filtered_replies:
viewing_posts_by: "يتم الآن عرض %{post_count} منشور بواسطة"
viewing_subset: "بعض الردود مطوية"
@@ -2970,7 +3364,6 @@ ar:
post_number: "%{username}، المنشور #%{post_number}"
show_all: "إظهار الكل"
category:
- can: "يمكن… "
none: "(بلا فئة)"
all: "كل الفئات"
choose: "الفئة…"
@@ -3004,7 +3397,6 @@ ar:
save_error: حدث خطأ في أثناء حفظ الفئة.
name: "اسم الفئة"
description: "الوصف"
- topic: "موضوع الفئة"
logo: "صورة شعار الفئة"
background_image: "صورة خلفية الفئة"
badge_colors: "ألوان الشارات"
@@ -3025,19 +3417,19 @@ ar:
see: "العرض"
reply: "الرد"
create: "إنشاء"
- no_groups_selected: "لم يتم منح إذن الوصول لأي مجموعة؛ لذا فإن هذه الفئة ستكون مرئية لطاقم العمل فقط."
+ no_groups_selected: "لم يتم منح إذن الوصول لأي مجموعة؛ لذا فإن هذه الفئة ستكون مرئية لفريق العمل فقط."
everyone_has_access: 'هذه الفئة عامة، ويمكن للجميع رؤية المنشورات والرد عليها وإنشائها. لتقييد الأذونات، عليك إزالة واحد أو أكثر من الأذونات الممنوحة لمجموعة "الجميع".'
toggle_reply: "إذن تفعيل الرد"
toggle_full: "إذن تفعيل الإنشاء"
inherited: 'هذا الإذن مكتسب من "الجميع"'
special_warning: "تحذير: هذه الفئة مصنَّفة مسبقًا ولا يمكن تعديل إعدادات الأمان لها. إذا كنت لا ترغب في استخدام هذه الفئة، فعليك حذفها بدلًا من إعادة توظيفها."
uncategorized_security_warning: "هذه الفئة خاصة. وتهدف إلى جمع الموضوعات التي لا تنتمي إلى أي فئة، ولا يمكنك ضبط إعدادات حماية لها."
- uncategorized_general_warning: 'هذه الفئة خاصة. ويتم استخدامها كفئة افتراضية للموضوعات الجديدة التي لا تنتمي إلى أي فئة. إذا أردت منع هذا السلوك وفرض اختيار الفئة، يُرجى تعطيل هذا الإعداد من هنا. إذا أردت تغيير اسم الفئة أو وصفها، فانتقل إلى التخصيص/المحتوى النصي.'
+ uncategorized_general_warning: 'هذه الفئة خاصة. ويتم استخدامها كفئة افتراضية للموضوعات الجديدة التي لا تنتمي إلى أي فئة. إذا أردت منع هذا السلوك وفرض تحديد الفئة، يُرجى إيقاف هذا الإعداد من هنا. إذا أردت تغيير اسم الفئة أو وصفها، فانتقل إلى التخصيص/المحتوى النصي.'
pending_permission_change_alert: "لم تُضف المجموعة %{group} إلى هذه الفئة، انقر على هذا الزر لإضافتها."
images: "الصور"
- email_in: "عنوان مخصَّص للبريد الوارد:"
+ email_in: "العنوان المخصَّص للبريد الوارد:"
email_in_allow_strangers: "قبول الرسائل الإلكترونية من المستخدمين المجهولين الذين لا يملكون حسابات"
- email_in_disabled: "تم تعطيل نشر الموضوعات الجديدة عبر البريد الإلكتروني في إعدادات الموقع. لتفعيل نشر الموضوعات الجديدة عبر البريد الإلكتروني، "
+ email_in_disabled: "تم إيقاف نشر الموضوعات الجديدة عبر البريد الإلكتروني في إعدادات الموقع. لتفعيل نشر الموضوعات الجديدة عبر البريد الإلكتروني، "
email_in_disabled_click: 'فعليك تفعيل خيار "email in" في الإعدادات.'
mailinglist_mirror: "الفئة تعكس قائمة بريدية"
show_subcategory_list: "اعرض قائمة الفئات الفرعية أعلى الموضوعات في هذه الفئة."
@@ -3045,6 +3437,7 @@ ar:
num_featured_topics: "عدد الموضوعات المعروضة في صفحة الفئات:"
subcategory_num_featured_topics: "عدد الموضوعات المميزة في صفحة الفئة الرئيسية."
all_topics_wiki: "تحويل الموضوعات الجديدة إلى Wiki بشكلٍ افتراضي"
+ allow_unlimited_owner_edits_on_first_post: "السماح للمالك بعدد غير محدود من التعديلات على أول منشور"
subcategory_list_style: "نمط قائمة الفئات الفرعية:"
sort_order: "ترتيب قائمة الموضوعات حسب:"
default_view: "قائمة الموضوعات الافتراضية:"
@@ -3052,7 +3445,7 @@ ar:
default_list_filter: "تصفية القائمة الافتراضية:"
allow_badges_label: "السماح بمنح الشارات في هذه الفئة"
edit_permissions: "تعديل الأذونات"
- reviewable_by_group: "بالإضافة إلى طاقم العمل، يمكن أيضًا مراجعة المحتوى في هذه الفئة بواسطة:"
+ reviewable_by_group: "بالإضافة إلى فريق العمل، يمكن أيضًا مراجعة المحتوى في هذه الفئة بواسطة:"
review_group_name: "اسم المجموعة"
require_topic_approval: "طلب موافقة المشرف على جميع الموضوعات الجديدة"
require_reply_approval: "طلب موافقة المشرف على جميع الردود الجديدة"
@@ -3068,16 +3461,19 @@ ar:
notifications:
watching:
title: "المراقبة"
+ description: "ستراقب تلقائيًا جميع الموضوعات في هذه الفئة. وسيتم إرسال إشعار إليك بكل منشور جديد في كل موضوع، وسيتم عرض عدد الردود الجديدة."
watching_first_post:
title: "مراقبة أول منشور"
description: "سنُرسل إليك إشعارًا بالموضوعات الجديدة في هذه الفئة، ولكن ليس الردود على الموضوعات."
tracking:
title: "المتابعة"
+ description: "ستتتبَّع تلقائيًا جميع الموضوعات في هذه الفئة. وسيتم إرسال إشعار إليك إذا أشار إليك شخص ما @name أو ردَّ عليك، وسيتم عرض عدد الردود الجديدة."
regular:
title: "عادي"
description: "سنُرسل إليك إشعارًا إذا أشار أحد إلى اسمك باستخدام الرمز @ أو ردَّ عليك."
muted:
title: "الكتم"
+ description: "لن يتم إرسال إشعار إليك أبدًا بأي شيء يتعلَّق بالموضوعات الجديدة في هذه الفئة، ولن تظهر في الموضوعات الحديثة."
search_priority:
label: "أولوية البحث"
options:
@@ -3112,6 +3508,7 @@ ar:
list_filters:
all: "جميع الموضوعات"
none: "لا توجد فئات فرعية"
+ colors_disabled: "لا يمكنك تحديد الألوان لأنه ليس لديك نمط فئة."
flagging:
title: "نشكرك على مساعدتك في الحفاظ على مجتمعنا متحضرًا."
action: "الإبلاغ عن المنشور"
@@ -3119,23 +3516,25 @@ ar:
take_action_options:
default:
title: "اتخاذ إجراء"
- details: "الوصول إلى الحد الأدنى للبلاغات على الفور بدلًا من انتظار المزيد من البلاغات من أعضاء المجتمع."
+ details: "الوصول إلى الحد الأقصى للبلاغات على الفور بدلًا من انتظار المزيد من البلاغات من أعضاء المجتمع."
suspend:
title: "تعليق المستخدم"
- details: "الوصول إلى الحد الأدنى للبلاغات وتعليق المستخدم"
+ details: "الوصول إلى الحد الأقصى للبلاغات وتعليق المستخدم"
silence:
title: "كتم المستخدم"
- details: "الوصول إلى الحد الأعلى للبلاغات، وكتم المستخدم"
+ details: "الوصول إلى الحد الأقصى للبلاغات، وكتم المستخدم"
notify_action: "رسالة"
official_warning: "تحذير رسمي"
delete_spammer: "حذف صاحب الأسلوب غير المرغوب فيه"
+ flag_for_review: "قائمة انتظار المراجعة"
+ delete_confirm_MF: "أنت على وشك حذف {POSTS, plural, zero {# منشور} one {منشور واحد (#)} two {منشوران (#)} few {# منشورات} many {# منشورًا} other {# منشور}} و{TOPICS, plural, zero {# موضوع} one {موضوع واحد (#)} two {موضوعان (#)} few {# موضوعات} many {# موضوعًا} other {# موضوع}} من هذا المستخدم، وإزالة حسابه، وحظر عمليات الاشتراك من عنوان IP الخاص به {ip_address}، وإضافة عنوان بريده الإلكتروني {email} إلى قائمة الحظر الدائمة. هل أنت متأكد من أن هذا المستخدم هو بالفعل صاحب الأسلوب غير المرغوب فيه؟"
yes_delete_spammer: "نعم، حذف صاحب الأسلوب غير المرغوب فيه"
ip_address_missing: "(لا يوجد)"
hidden_email_address: "(مخفي)"
submit_tooltip: "إرسال البلاغ الخاص"
- take_action_tooltip: "الوصول إلى الحد الأدنى للبلاغات على الفور بدلًا من انتظار المزيد من البلاغات من أعضاء المجتمع."
+ take_action_tooltip: "الوصول إلى الحد الأقصى للبلاغات على الفور بدلًا من انتظار المزيد من البلاغات من أعضاء المجتمع."
cant: "عذرًا، لا يمكنك الإبلاغ عن هذا المنشور في الوقت الحالي."
- notify_staff: "إرسال إشعار إلى طاقم العمل بشكلٍ خاص"
+ notify_staff: "إرسال إشعار إلى فريق العمل بشكلٍ خاص"
formatted_name:
off_topic: "خارج عن الموضوع"
inappropriate: "غير لائق"
@@ -3170,7 +3569,7 @@ ar:
notify_action: "رسالة"
topic_map:
title: "ملخص الموضوع"
- participants_title: "الناشرون الدائمون"
+ participants_title: "الناشرون المتكررون"
links_title: "الروابط الرائجة"
links_shown: "عرض المزيد من الروابط..."
clicks:
@@ -3204,17 +3603,23 @@ ar:
title: "غير مثبَّت"
help: "هذا الموضوع غير مثبّت لك، وسيتم عرضه بالترتيب العادي"
pinned_globally:
- title: "مثبَّت بشكلٍ عمومي"
- help: "هذا الموضوع مثبَّت بشكل عمومي؛ لذا فإنه سيظهر في أعلى صفحة أحدث الموضوعات وفي أعلى فئته."
+ title: "مثبَّت بشكلٍ عام"
+ help: "هذا الموضوع مثبَّت بشكلٍ عام؛ لذا فإنه سيظهر في أعلى صفحة أحدث الموضوعات وفي أعلى فئته."
pinned:
title: "مثبَّت"
help: "هذا الموضوع مثبَّت لك وسيتم عرضه في أعلى فئته"
unlisted:
help: "هذا الموضوع غير مدرج؛ لذا فإنه لن يظهر في قوائم الموضوعات، ولا يمكن الوصول إليه إلا برابط مباشر"
personal_message:
- title: "هذا الموضوع عبارة عن رسالة شخصية"
- help: "هذا الموضوع عبارة عن رسالة شخصية"
+ title: "هذا الموضوع عبارة عن رسالة خاصة"
+ help: "هذا الموضوع عبارة عن رسالة خاصة"
posts: "المنشورات"
+ posts_likes_MF: |
+ يتضمَّن هذا الموضوع {count, plural, zero {# رد} one {ردًا واحدًا (#)} two {ردَّين (#)} few {# ردود} many {# ردًا} other {# رد}} {ratio, select,
+ low {بمعدل إعجابات مرتفع على المنشورات}
+ med {بمعدل إعجابات مرتفع جدًا على المنشورات}
+ high {بمعدل إعجابات مرتفع للغاية على المنشورات}
+ other {}}
original_post: "المنشور الأصلي"
views: "مرات العرض"
views_lowercase:
@@ -3250,6 +3655,7 @@ ar:
many: "مستخدمًا"
other: "مستخدم"
category_title: "الفئة"
+ history: "السجل، آخر 100 مراجعة"
changed_by: "بواسطة %{author}"
raw_email:
title: "البريد الوارد"
@@ -3361,6 +3767,7 @@ ar:
close: "إغلاق (Esc)"
content_load_error: 'تعذَّر تحميل المحتوى.'
image_load_error: 'تعذَّر تحميل الصورة.'
+ cannot_render_video: لا يمكن عرض هذا الفيديو لأن متصفحك لا يدعم برنامج الترميز.
keyboard_shortcuts_help:
shortcut_key_delimiter_comma: ", "
shortcut_key_delimiter_plus: "+"
@@ -3399,6 +3806,7 @@ ar:
show_incoming_updated_topics: "%{shortcut} عرض الموضوعات المحدَّثة"
search: "%{shortcut} البحث"
help: "%{shortcut} فتح مساعدة لوحة المفاتيح"
+ dismiss_new: "%{shortcut} تجاهل الجديد"
dismiss_topics: "%{shortcut} تجاهل الموضوعات"
log_out: "%{shortcut} تسجيل الخروج"
composing:
@@ -3492,6 +3900,9 @@ ar:
name: أخرى
posting:
name: النشر
+ favorite_max_reached: "لا يمكنك تفضيل المزيد من الشارات."
+ favorite_max_not_reached: "وضع علامة على هذه الشارة كمفضَّلة"
+ favorite_count: "تم وضع علامة على %{count}/%{max} شارة كمفضَّلة"
tagging:
all_tags: "كل الوسوم"
other_tags: "وسوم أخرى"
@@ -3501,6 +3912,7 @@ ar:
tags: "الوسوم"
choose_for_topic: "الوسوم الاختيارية"
info: "المعلومات"
+ default_info: "هذا الوسم ليس مقصورًا على أي فئات وليس له أي مرادفات. لإضافة قيود، ضع هذا الوسم في مجموعة وسوم."
category_restricted: "هذا الوسم مقيَّد بالفئات التي ليس لديك إذن بالوصول إليها."
synonyms: "المرادفات"
synonyms_description: "عند استخدام الوسوم التالية، سيتم استبدالها بالوسم %{base_tag_name}."
@@ -3573,8 +3985,9 @@ ar:
many: "%{tags} و%{count} وسمًا آخر"
other: "%{tags} و%{count} وسم آخر"
delete_no_unused_tags: "لا توجد وسوم غير مستخدمة:"
+ tag_list_joiner: "، "
delete_unused: "حذف الوسوم غير المستخدمة"
- delete_unused_description: "حذف جميع الوسوم غير المرتبطة بأي موضوعات أو رسائل شخصية"
+ delete_unused_description: "حذف جميع الوسوم غير المرتبطة بأي موضوعات أو رسائل خاصة"
cancel_delete_unused: "إلغاء"
filters:
without_category: "%{filter} موضوعات %{tag}"
@@ -3599,15 +4012,29 @@ ar:
description: "لن تتلقى أي إشعارات أبدًا بخصوص الموضوعات الجديدة التي تحمل هذا الوسم، ولن تظهر في علامة تبويب الموضوعات غير المقروءة."
groups:
title: "مجموعات الوسوم"
+ about_heading: "حدِّد مجموعة وسوم أو أنشئ مجموعة جديدة"
+ about_heading_empty: "أنشئ مجموعة وسوم جديدة للبدء"
+ about_description: "تساعدك مجموعات الوسوم في إدارة الأذونات للعديد من الوسوم في مكانٍ واحد."
new: "مجموعة جديدة"
+ new_title: "إنشاء مجموعة جديدة"
+ edit_title: "تعديل مجموعة الوسوم"
+ tags_label: "الوسوم في هذه المجموعة"
+ parent_tag_label: "الوسم الرئيسي"
+ parent_tag_description: "لا يمكن استخدام الوسوم من هذه المجموعة إلا في حال وجود الوسم الرئيسي."
one_per_topic_label: "تحديد وسم واحد فقط لكل موضوع من هذه المجموعة"
new_name: "مجموعة وسوم جديدة"
+ name_placeholder: "الاسم"
save: "حفظ"
delete: "حذف"
confirm_delete: "هل تريد بالتأكيد حذف مجموعة الوسوم هذه؟"
everyone_can_use: "يمكن للجميع استخدام الوسوم"
usable_only_by_groups: "تكون الوسوم مرئية للجميع، ولكن يمكن للمجموعات التالية فقط استخدامها"
visible_only_to_groups: "تكون الوسوم مرئية للمجموعات التالية فقط"
+ cannot_save: "لا يمكن حفظ مجموعة الوسوم. تأكَّد من وجود وسم واحد على الأقل، وأن اسم مجموعة الوسوم ليس فارغًا، وأن هناك مجموعة محدَّدة لمنحها إذن الوسوم."
+ tags_placeholder: "البحث أو إنشاء الوسوم"
+ parent_tag_placeholder: "اختياري"
+ select_groups_placeholder: "تحديد المجموعات..."
+ disabled: "وضع الوسوم معطَّل. "
topics:
none:
unread: "ليس لديك أي موضوع غير مقروء."
@@ -3620,9 +4047,11 @@ ar:
invite:
custom_message: "جعل دعوتك أكثر اتسامًا بالطابع الشخصي عن طريق كتابة رسالة مخصَّصة."
custom_message_placeholder: "أدخِل رسالتك المخصَّصة"
+ approval_not_required: "ستتم الموافقة تلقائيًا على المستخدم بمجرد قبوله لهذه الدعوة."
custom_message_template_forum: "مرحبًا. يجب عليك الانضمام إلى هذا المنتدى!"
custom_message_template_topic: "مرحبًا. أظن أنك ستستمتع بهذا الموضوع!"
forced_anonymous: "بسبب الضغط الشديد، يتم عرض هذا الموضوع للجميع مؤقتًا كما يراه مستخدم سجَّل الخروج."
+ forced_anonymous_login_required: "الموقع تحت ضغط شديد ولا يمكن تحميله في الوقت الحالي، حاول مرة أخرى في غضون بضع دقائق."
footer_nav:
back: "للخلف"
forward: "للأمام"
@@ -3641,6 +4070,7 @@ ar:
two_hours: "ساعتان"
tomorrow: "حتى الغد"
custom: "مخصَّص"
+ set_schedule: "ضبط جدول الإشعارات"
trust_levels:
names:
newuser: "مستخدم جديد"
@@ -3676,16 +4106,21 @@ ar:
latest_version: "الحديثة"
problems_found: "بعض النصائح بناءً على إعدادات موقعك الحالية"
new_features:
+ title: "\U0001F381 الميزات الجديدة"
dismiss: "تجاهل"
+ learn_more: "معرفة المزيد"
last_checked: "تاريخ آخر تحقُّق"
refresh_problems: "تحديث"
no_problems: "لم يتم العثور على مشكلات."
moderators: "المشرفون:"
admins: "المسؤولون:"
+ silenced: "المكتومون:"
suspended: "المعلَّقون:"
private_messages_short: "الرسائل"
private_messages_title: "الرسائل"
mobile_title: "الجوَّال"
+ space_used: "تم استخدام %{usedSize}"
+ space_used_and_free: "%{usedSize} (%{freeSize} فارغة)"
uploads: "التحميلات"
backups: "النسخ الاحتياطية"
backup_count:
@@ -3695,18 +4130,30 @@ ar:
few: "%{count} نسخ احتياطية في %{location}"
many: "%{count} نسخة احتياطية في %{location}"
other: "%{count} نسخة احتياطية في %{location}"
+ lastest_backup: "تاريخ آخر نسخ احتياطي: %{date}"
traffic_short: "الزيارات"
traffic: "طلبات الويب للتطبيق"
page_views: "مرات عرض الصفحة"
page_views_short: "مرات عرض الصفحة"
show_traffic_report: "عرض تقرير مفصَّل عن الزيارات"
+ community_health: صحة المجتمع
+ moderators_activity: نشاط المشرفين
whats_new_in_discourse: ما الجديد في Discourse؟
+ activity_metrics: مقاييس النشاط
+ all_reports: "جميع التقارير"
general_tab: "عام"
moderation_tab: "الإشراف"
security_tab: "الأمان"
+ reports_tab: "التقارير"
report_filter_any: "أي"
+ disabled: معطَّل
+ timeout_error: عذرًا، يستغرق الاستعلام وقتًا طويلًا، يُرجى اختيار فترة زمنية أقصر
+ exception_error: عذرًا، حدث خطأ في أثناء تنفيذ الاستعلام
too_many_requests: لقد نفَّذت هذا الإجراء مرات كثيرة جدًا. يُرجى الانتظار قليلًا قبل إعادة المحاولة.
+ not_found_error: عذرًا، هذا التقرير غير موجود
+ filter_reports: تصفية التقارير
reports:
+ trend_title: "تغيير بنسبة %{percent}. الحالي %{current}، كان %{prev} في فترة سابقة."
today: "اليوم"
yesterday: "أمس"
last_7_days: "آخر 7"
@@ -3721,10 +4168,17 @@ ar:
daily: يوميًا
monthly: شهريًا
weekly: أسبوعيًا
+ dates: "التواريخ (بالتوقيت العالمي الموحَّد)"
groups: "كل المجموعات"
- disabled: "هذا التقرير معطَّل"
+ disabled: "هذا التقرير متوقف"
+ totals_for_sample: "إجماليات العينة"
+ average_for_sample: "متوسط العينة"
total: "إجمالي الوقت الكلي"
no_data: "لا توجد بيانات لعرضها."
+ trending_search:
+ more: 'سجلات البحث'
+ disabled: 'تقرير عبارات البحث الرائجة معطَّل. فعِّل تسجيل استعلامات البحث لجمع البيانات.'
+ average_chart_label: المتوسط
filters:
file_extension:
label: امتداد الملف
@@ -3732,6 +4186,8 @@ ar:
label: المجموعة
category:
label: الفئة
+ include_subcategories:
+ label: "تضمين الفئات الفرعية"
groups:
new:
title: "مجموعة جديدة"
@@ -3748,18 +4204,33 @@ ar:
email: البريد الإلكتروني
incoming_email: "عنوان مخصَّص للبريد الوارد"
incoming_email_placeholder: "أدخل عنوان البريد الإلكتروني"
+ visibility: الرؤية
visibility_levels:
title: "من يمكنه رؤية هذه المجموعة؟"
public: "الجميع"
+ logged_on_users: "المستخدمون الذين سجَّلوا الدخول"
+ members: "مالكو المجموعة والأعضاء والمشرفين"
+ staff: "مالكو المجموعة والمشرفين"
+ owners: "مالكو المجموعة"
+ description: "يمكن للمسؤولين رؤية جميع المجموعات."
members_visibility_levels:
title: "من يمكنه رؤية أعضاء هذه المجموعة؟"
description: "يمكن للمسؤولين رؤية أعضاء كل المجموعات."
+ publish_read_state: "نشر حالة قراءة المجموعة على رسائل المجموعة"
membership:
automatic: تلقائية
trust_levels_title: "مستوى الثقة الذي يتم منحه للأعضاء تلقائيًا عند إضافتهم:"
+ effects: التأثيرات
trust_levels_none: "لا يوجد"
automatic_membership_email_domains: "ستتم إضافة المستخدمين الذين يسجِّلون بنطاق بريد إلكتروني مطابق لنطاق في هذه القائمة إلى هذه المجموعة تلقائيًا:"
- primary_group: "التعيين تلقائيًا كمجموعة أساسية"
+ automatic_membership_user_count:
+ zero: "يوجد %{count} مستخدم لديه نطاقات البريد الإلكتروني الجديدة وستتم إضافته إلى المجموعة."
+ one: "يوجد مستخدم واحد (%{count}) لديه نطاقات البريد الإلكتروني الجديدة وستتم إضافته إلى المجموعة."
+ two: "يوجد مستخدمان (%{count}) لديهما نطاقات البريد الإلكتروني الجديدة وستتم إضافتهما إلى المجموعة."
+ few: "يوجد %{count} مستخدمين لديهم نطاقات البريد الإلكتروني الجديدة وستتم إضافتهم إلى المجموعة."
+ many: "يوجد %{count} مستخدمًا لديهم نطاقات البريد الإلكتروني الجديدة وستتم إضافته إلى المجموعة."
+ other: "يوجد %{count} مستخدم لديه نطاقات البريد الإلكتروني الجديدة وستتم إضافته إلى المجموعة."
+ primary_group: "الضبط تلقائيًا كمجموعة أساسية"
name_placeholder: "اسم المجموعة بلا مسافات، على غرار قاعدة اسم المستخدم"
primary: "المجموعة الأساسية"
no_primary: "(لا توجد مجموعة أساسية)"
@@ -3770,11 +4241,21 @@ ar:
group_members: "أعضاء المجموعة"
delete: "حذف"
delete_confirm: "هل تريد حذف هذه المجموعة؟"
+ delete_with_messages_confirm:
+ zero: "سيؤدي حذف هذه المجموعة إلى عزل %{count} رسالة، ولن يتمكن أعضاء المجموعة من الوصول إليها مرة أخرى.
هل أنت متأكد؟"
+ one: "سيؤدي حذف هذه المجموعة إلى عزل رسالة واحدة (%{count})، ولن يتمكن أعضاء المجموعة من الوصول إليها مرة أخرى.
هل أنت متأكد؟"
+ two: "سيؤدي حذف هذه المجموعة إلى عزل رسالتَين (%{count})، ولن يتمكن أعضاء المجموعة من الوصول إليهما مرة أخرى.
هل أنت متأكد؟"
+ few: "سيؤدي حذف هذه المجموعة إلى عزل %{count} رسائل، ولن يتمكن أعضاء المجموعة من الوصول إليها مرة أخرى.
هل أنت متأكد؟"
+ many: "سيؤدي حذف هذه المجموعة إلى عزل %{count} رسالة، ولن يتمكن أعضاء المجموعة من الوصول إليها مرة أخرى.
هل أنت متأكد؟"
+ other: "سيؤدي حذف هذه المجموعة إلى عزل %{count} رسالة، ولن يتمكن أعضاء المجموعة من الوصول إليها مرة أخرى.
هل أنت متأكد؟"
delete_failed: "يتعذَّر حذف المجموعة. إذا كانت هذه مجموعة تلقائية، فلا يمكن تدميرها."
+ delete_automatic_group: هذه مجموعة تلقائية ولا يمكن حذفها.
delete_owner_confirm: "هل تريد إزالة صلاحيات المالك من \"%{username}\"؟"
add: "إضافة"
custom: "مخصَّصة"
automatic: "تلقائية"
+ default_title: "العنوان الافتراضي"
+ default_title_description: "سيتم تطبيقه على جميع المستخدمين في المجموعة"
group_owners: المالكون
add_owners: إضافة مالكين
none_selected: "حدِّد مجموعة للبدء"
@@ -3784,9 +4265,13 @@ ar:
none: "لا توجد مفاتيح API نشطة حاليًا."
user: "المستخدم"
title: "API"
+ key: "المفتاح"
created: تاريخ الإنشاء
updated: تاريخ التحديث
+ last_used: آخر استخدام
+ never_used: (أبدًا)
generate: "إنشاء"
+ undo_revoke: "التراجع عن الإلغاء"
revoke: "إلغاء"
all_users: "جميع المستخدمين"
active_keys: "مفاتيح API النشطة"
@@ -3796,14 +4281,47 @@ ar:
no_description: (لا يوجد وصف)
all_api_keys: جميع مفاتيح API
user_mode: مستوى المستخدم
+ impersonate_all_users: انتحال شخصية أي مستخدم
+ single_user: "مستخدم فردي"
user_placeholder: أدخِل اسم المستخدم
description_placeholder: ما الذي سيتم استخدام هذا المفتاح فيه؟
save: حفظ
new_key: مفتاح API جديد
revoked: تم الإلغاء
+ delete: الحذف بشكلٍ دائم
+ not_shown_again: لن يتم عرض هذا المفتاح مرة أخرى. تأكَّد من أخذ نسخة قبل المتابعة.
continue: متابعة
+ use_global_key: المفتاح العام (يسمح بجميع الإجراءات)
scopes:
+ description: |
+ عند استخدام النطاقات، يمكنك تقييد مفتاح API على مجموعة محدَّدة من نقاط النهاية.
+ يمكنك أيضًا تحديد المعلمات التي سيتم السماح بها. استخدم الفاصلات للفصل بين القيم المتعددة.
+ title: النطاقات
+ resource: المورد
action: الإجراء
+ allowed_parameters: المعلمات المسموح بها
+ optional_allowed_parameters: المعلمات المسموح بها (اختياري)
+ any_parameter: (أي معلمة)
+ allowed_urls: عناوين URL المسموح بها
+ descriptions:
+ topics:
+ read: قراءة موضوع أو منشور محدَّد فيه. يتم دعم RSS أيضًا.
+ write: إنشاء موضوع جديد أو النشر في موضوع موجود
+ read_lists: قراءة قوائم الموضوعات مثل الأكثر عرضًا، والجديدة، والحديثة، وما إلى ذلك. يتم دعم RSS أيضًا.
+ wordpress: ضرورية لكي يعمل المكوِّن الإضافي wp-discourse على WordPress.
+ posts:
+ edit: تعديل أي منشور أو منشور معيَّن.
+ users:
+ bookmarks: إدراج الإشارات المرجعية للمستخدم. تعرض تذكيرات بالإشارات المرجعية عند استخدام تنسيق ICS.
+ sync_sso: مزامنة مستخدم باستخدام DiscourseConnect
+ show: الحصول على معلومات عن مستخدم
+ check_emails: إدراج عناوين البريد الإلكتروني للمستخدمين
+ update: تحديث معلومات الملف الشخصي لمستخدم
+ log_out: تسجيل الخروج من جميع الجلسات لمستخدم
+ anonymize: إخفاء هوية حسابات المستخدمين
+ delete: حذف حسابات المستخدمين
+ email:
+ receive_emails: ادمج هذا النطاق مع مستقبل البريد لمعالجة الرسائل الإلكترونية الواردة.
web_hooks:
title: "خطافات الويب"
none: "لا توجد خطافات ويب حاليًا."
@@ -3818,6 +4336,7 @@ ar:
go_back: "العودة إلي القائمة"
payload_url: "عنوان URL للحمولة"
payload_url_placeholder: "https://example.com/postreceive"
+ warn_local_payload_url: "يبدو أنك تحاول إعداد خطاف ويب على عنوان URL محلي. قد يتسبَّب تسليم الحدث إلى عنوان محلي في حدوث آثار جانبية أو سلوكيات غير متوقعة. هل تريد المتابعة؟"
secret_invalid: "يجب ألا يحتوي الرمز السري على حروف فارغة."
secret_too_short: "يجب ألا يقل الرمز السري عن 12 حرفًا."
secret_placeholder: "سلسلة اختيارية، تُستخدَم في إنشاء توقيع إلكتروني"
@@ -3830,17 +4349,55 @@ ar:
verify_certificate: "التحقُّق من شهادة TLS لعنوان URL للحمولة"
active: "نشط"
active_notice: "سنُرسل تفاصيل الحدث عند وقوعه."
+ categories_filter_instructions: "لن يتم تشغيل خطافات الويب ذات الصلة إلا إذا كان الحدث مرتبطًا بفئات محدَّدة. اتركه فارغًا لتشغيل خطافات الويب لجميع الفئات."
+ categories_filter: "الفئات المشغَّلة"
+ tags_filter_instructions: "لن يتم تشغيل خطافات الويب ذات الصلة إلا إذا كان الحدث مرتبطًا بوسوم محدَّدة. اتركه فارغًا لتشغيل خطافات الويب لجميع الوسوم."
+ tags_filter: "الوسوم المشغَّلة"
+ groups_filter_instructions: "لن يتم تشغيل خطافات الويب ذات الصلة إلا إذا كان الحدث مرتبطًا بمجموعات محدَّدة. اتركه فارغًا لتشغيل خطافات الويب لجميع المجموعات."
+ groups_filter: "المجموعات المشغَّلة"
+ delete_confirm: "هل تريد حذف خطاف الويب هذا؟"
topic_event:
name: "حدث الموضوع"
+ details: "عندما يكون هناك موضوع جديد، أو تمت مراجعته أو تغييره أو حذفه."
+ post_event:
+ name: "حدث المنشور"
+ details: "عندما يكون هناك رد جديد، أو تم تعديله أو حذفه أو استعادته."
user_event:
name: "حدث المستخدم"
+ details: "عند تسجيل المستخدم للدخول أو الخروج، أو تأكيده لبريده الإلكتروني أو إنشائه أو الموافقة عليه أو تحديثه."
+ group_event:
+ name: "حدث المجموعة"
+ details: "عند إنشاء مجموعة أو تحديثها أو مسحها."
+ category_event:
+ name: "حدث الفئة"
+ details: "عند إنشاء فئة أو تحديثها أو مسحها."
+ tag_event:
+ name: "حدث الوسم"
+ details: "عند إنشاء وسم أو تحديثه أو مسحه."
+ reviewable_event:
+ name: "حدث العنصر القابل للمراجعة"
+ details: "عند وجود عنصر جديد جاهز للمراجعة وعند تحديث حالته."
+ notification_event:
+ name: "حدث الإشعار"
+ details: "عند تلقي المستخدم إشعار في موجزه."
+ user_badge_event:
+ name: "حدث منح الشارة"
+ details: "عند حصول المستخدم على شارة."
+ group_user_event:
+ name: "حدث مستخدم المجموعة"
+ details: "عند إضافة مستخدم أو إزالته في مجموعة."
+ like_event:
+ name: "حدث الإعجاب"
+ details: "عند تسجيل المستخدم إعجابه بمنشور."
delivery_status:
title: "حالة التسليم"
inactive: "غير نشط"
failed: "فشل"
successful: "نجح"
+ disabled: "معطَّل"
events:
none: "لا توجد أحداث ذات صلة."
+ redeliver: "إعادة التسليم"
incoming:
zero: "هناك %{count} حدث جديد."
one: "هناك حدث واحد جديد."
@@ -3857,9 +4414,12 @@ ar:
other: "اكتمل خلال %{count} ثانية."
request: "الطلب"
response: "الاستجابة"
+ redeliver_confirm: "هل تريد بالتأكيد إعادة تسليم الحمولة نفسها؟"
headers: "الرؤوس"
+ payload: "الحمولة"
body: "النص"
go_list: "الانتقال إلى القائمة"
+ go_details: "تعديل خطاف الويب"
go_events: "الانتقال إلى الأحداث"
ping: "اختبار الاتصال"
status: "رمز الحالة"
@@ -3879,6 +4439,7 @@ ar:
change_settings: "تغيير الإعدادات"
change_settings_short: "الإعدادات"
howto: "كيف يمكنني تثبيت الإضافات؟"
+ official: "المكوِّن الإضافي الرسمي"
backups:
title: "النسخ الاحتياطية"
menu:
@@ -3891,8 +4452,8 @@ ar:
label: "تفعيل القراءة فقط"
confirm: "هل تريد بالتأكيد تفعيل وضع القراءة فقط؟"
disable:
- title: "تعطيل وضع القراءة فقط"
- label: "تعطيل القراءة فقط"
+ title: "إيقاف وضع القراءة فقط"
+ label: "إيقاف القراءة فقط"
logs:
none: "لا توجد سجلات بعد..."
columns:
@@ -3916,15 +4477,16 @@ ar:
label: "نسخة احتياطية"
title: "إنشاء نسخة احتياطية"
confirm: "هل تريد إنشاء نسخة احتياطية جديدة؟"
+ without_uploads: "نعم (عدم تضمين التحميلات)"
download:
label: "تنزيل"
title: "مراسلتي عبر البريد الإلكتروني برابط التنزيل"
alert: "تم مراسلتك عبر البريد الإلكتروني برابط تنزيل هذه النسخة الاحتياطية."
destroy:
title: "إزالة النسخة الاحتياطية"
- confirm: "هل تريد بالتأكيد إتلاف هذه النسخة الاحتياطية؟"
+ confirm: "هل تريد بالتأكيد مسح هذه النسخة الاحتياطية؟"
restore:
- is_disabled: "الاستعادة معطَّلة في إعدادات الموقع."
+ is_disabled: "الاستعادة متوقفة في إعدادات الموقع."
label: "استعادة"
title: "استعادة النسخة الاحتياطية"
confirm: "هل تريد بالتأكيد استعادة هذه النسخة الاحتياطية؟"
@@ -3942,7 +4504,7 @@ ar:
button_text: "التصدير"
button_title:
user: "تصدير قائمة بجميع المستخدمين بتنسيق CSV"
- staff_action: "تصدير سجل إجراءات طاقم العمل الكامل بتنسيق CSV"
+ staff_action: "تصدير سجل إجراءات فريق العمل الكامل بتنسيق CSV"
screened_email: "تصدير القائمة الكاملة بتنسيق CSV لعناوين البريد الإلكتروني الخاضعة للمراقبة."
screened_ip: "تصدير القائمة الكاملة بتنسيق CSV لعناوين IP الخاضعة للمراقبة."
screened_url: "تصدير القائمة الكاملة بتنسيق CSV لعناوين URL الخاضعة للمراقبة."
@@ -3972,70 +4534,127 @@ ar:
email_templates:
title: "البريد الإلكتروني"
subject: "الموضوع"
- multiple_subjects: "توجد موضوعات متعددة في قالب البريد الإلكتروني هذا."
+ multiple_subjects: "توجد موضوعات متعددة في نموذج البريد الإلكتروني هذا."
body: "النص"
revert: "التراجع عن التغييرات"
revert_confirm: "هل تريد بالتأكيد التراجع عن التغييرات؟"
theme:
theme: "السمة"
+ component: "المكوِّن"
+ components: "المكوِّنات"
+ theme_name: "اسم السمة"
+ component_name: "اسم المكوِّن"
+ themes_intro: "حدِّد سمة موجودة أو ثبِّت سمة جديدة للبدء"
+ themes_intro_emoji: "الرمز التعبيري لفنانة"
+ beginners_guide_title: "دليل المبتدئين لاستخدام سمات Discourse"
+ developers_guide_title: "دليل المطوِّرين لاستخدام سمات Discourse"
+ browse_themes: "تصفُّح سمات المجتمع"
customize_desc: "تخصيص:"
title: "السمات"
create: "إنشاء"
create_type: "النوع"
create_name: "الاسم"
+ long_title: "تعديل الألوان ومحتويات CSS وHTML لموقعك"
edit: "تعديل"
+ edit_confirm: "هذه سمة بعيدة، إذا أجريت تعديلات على CSS/HTML، فسيتم مسح تغييراتك عند تحديث السمة في المرة التالية."
+ update_confirm: "سيؤدي التحديث إلى محو التغييرات المحلية. هل تريد بالتأكيد المتابعة؟"
+ update_confirm_yes: "نعم، متابعة التحديث"
common: "مشترك"
desktop: "سطح المكتب"
mobile: "الجوَّال"
settings: "الإعدادات"
translations: "الترجمات"
+ extra_scss: "SCSS إضافية"
+ extra_files: "ملفات إضافية"
+ extra_files_upload: "يجب تصدير السمة لعرض هذه الملفات."
+ extra_files_remote: "يجب تصدير السمة أو التحقُّق من مستودع Git لعرض هذه الملفات."
preview: "معاينة"
show_advanced: "إظهار الحقول المتقدمة"
hide_advanced: "إخفاء الحقول المتقدمة"
hide_unused_fields: "إخفاء الحقول غير المستخدمة"
is_default: "السمة مفعَّلة بشكلٍ افتراضي"
user_selectable: "يمكن للمستخدمين تحديد السمة"
+ color_scheme_user_selectable: "يمكن تحديد نظام الألوان بواسطة المستخدمين"
+ auto_update: "التحديث التلقائي عند تحديث Discourse"
+ color_scheme: "لوحة الألوان"
+ default_light_scheme: "فاتحة (افتراضي)"
color_scheme_select: "تحديد الألوان التي ستستخدمها السمة"
custom_sections: "الأقسام المخصَّصة:"
theme_components: "مكوِّنات السمة"
add_all_themes: "إضافة جميع السمات"
+ convert: "تحويل"
convert_component_alert: "هل تريد بالتأكيد تحويل هذا المكوِّن إلى سمة؟ ستتم إزالته كمكوِّن من %{relatives}."
+ convert_component_tooltip: "تحويل هذا المكوِّن إلى سمة"
+ convert_component_alert_generic: "هل تريد بالتأكيد تحويل هذا المكوِّن إلى سمة؟"
convert_theme_alert: "هل تريد بالتأكيد تحويل هذه السمة إلى مكوِّن؟ ستتم إزالته كعنصر رئيسي من %{relatives}."
+ convert_theme_alert_generic: "هل تريد بالتأكيد تحويل هذه السمة إلى مكوِّن؟"
+ convert_theme_tooltip: "تحويل هذه السمة إلى مكوِّن"
inactive_themes: "السمات غير النشطة:"
inactive_components: "المكوِّنات غير المستخدمة:"
- disabled_component_tooltip: "لقد تم تعطيل هذا المكوِّن"
+ broken_theme_tooltip: "هذه السمة بها أخطاء في CSS أو HTML أو YAML"
+ disabled_component_tooltip: "لقد تم إيقاف هذا المكوِّن"
default_theme_tooltip: "هذه السمة هي السمة الافتراضية للموقع"
updates_available_tooltip: "تتوفَّر تحديثات لهذه السمة"
+ and_x_more: "و%{count} أخرى."
collapse: طي
uploads: "التحميلات"
no_uploads: "يمكنك تحميل الملفات المرتبطة بسمتك، مثل الخطوط والصور"
add_upload: "إضافة ملف"
upload_file_tip: "اختر ملفًا لتحميله (png، woff2، إلى آخره)"
variable_name: "اسم متغير SCSS:"
+ variable_name_invalid: "اسم المتغير غير صالح. مسموح بالأحرف الأبجدية الرقمية فقط. يجب أن تبدأ بحرف. ويجب أن يكون فريدًا."
+ variable_name_error:
+ invalid_syntax: "اسم المتغير غير صالح. مسموح بالأحرف الأبجدية الرقمية فقط. يجب أن تبدأ بحرف."
+ no_overwrite: "اسم المتغير غير صالح. يجب ألا تستبدل متغيرًا موجودًا."
+ must_be_unique: "اسم المتغير غير صالح. يجب أن يكون فريدًا."
upload: "تحميل"
+ select_component: "تحديد مكوِّن..."
+ unsaved_changes_alert: "لم تحفظ تغييراتك بعد، هل تريد تجاهلها والمضي قدمًا؟"
+ unsaved_parent_themes: "لم تخصِّص المكوِّن للسمات، هل تريد المضي قدمًا؟"
+ discard: "تجاهل"
+ stay: "البقاء"
css_html: "CSS/HTML مخصَّص"
edit_css_html: "تعديل CSS/HTML"
edit_css_html_help: "لم تعدِّل CSS أو HTML"
delete_upload_confirm: "هل تريد حذف هذا الملف؟ (قد يتوقف ملف CSS عن العمل!)"
+ component_on_themes: "تضمين العنصر في هذه السمات"
+ included_components: "المكوِّنات المضمَّنة"
+ add_all: "إضافة الكل"
import_web_tip: "المستودع الذي يحتوي السمة"
+ direct_install_tip: "هل تريد بالتأكيد تثبيت %{name} من المستودع المُدرَج أدناه؟"
+ import_web_advanced: "متقدم..."
+ import_file_tip: "ملف .tar.gz, .zip, or .dcstyle.json الذي يحتوي على السمة"
+ is_private: "السمة موجودة في مستودع Git خاص"
remote_branch: "اسم الفرع (اختياري)"
+ public_key: "امنح المفتاح العام التالي إذن الوصول إلى المستودع:"
+ public_key_note: "بعد إدخال عنوان URL صالح لمستودع خاص أعلاه، سيتم إنشاء مفتاح SSH وعرضه هنا."
install: "تثبيت"
installed: "مثبَّت"
install_popular: "رائج"
install_upload: "من جهازك"
install_git_repo: "من مستودع Git"
+ install_create: "إنشاء جديد"
+ duplicate_remote_theme: "مكوِّن السمة \"%{name}\" مثبَّت بالفعل، هل تريد بالتأكيد تثبيت نسخة أخرى؟"
about_theme: "نبذة"
license: "الترخيص"
version: "الإصدار:"
+ authors: "تم تأليفها بواسطة:"
+ creator: "تم إنشاؤها بواسطة:"
source_url: "المصدر"
enable: "تفعيل"
- disable: "تعطيل"
- disabled: "لقد تم تعطيل هذا المكوِّن."
- disabled_by: "لقد تم تعطيل هذا المكوِّن بواسطة"
+ disable: "إيقاف"
+ disabled: "لقد تم إيقاف هذا المكوِّن."
+ disabled_by: "لقد تم إيقاف هذا المكوِّن بواسطة"
+ required_version:
+ error: "تم إيقاف هذه السمة تلقائيًا لأنها غير متوافقة مع هذا الإصدار من Discourse."
+ minimum: "تتطلب الإصدار %{version} من Discourse أو أحدث."
+ maximum: "تتطلب الإصدار %{version} من Discourse أو أقدم."
+ component_of: "مكوِّن:"
update_to_latest: "تحديث إلى الإصدار الأخير"
check_for_updates: "التحقُّق من وجود تحديثات"
updating: "جارٍ التحديث..."
up_to_date: "السمة محدَّثة، تاريخ آخر تحقُّق:"
+ has_overwritten_history: "لم يعُد إصدار السمة الحالي موجودًا لأن سجل Git قد تم استبداله إجباريًا."
add: "إضافة"
theme_settings: "إعدادات السمة"
no_settings: "لا توجد إعدادات لهذه السمة."
@@ -4049,6 +4668,7 @@ ar:
many: "السمة متأخرة بعدد %{count} مساهمة!"
other: "السمة متأخرة بعدد %{count} مساهمة!"
compare_commits: "(عرض المساهمات الجديدة)"
+ remote_theme_edits: "إذا كنت ترغب في تعديل هذه السمة0، يجب عليك طلب تغيير في مستودعها"
repo_unreachable: "تعذَّر الاتصال بمستودع Git لهذه السمة. رسالة الخطأ:"
imported_from_archive: "تم استيراد هذه السمة من ملف .zip"
scss:
@@ -4065,18 +4685,32 @@ ar:
title: "إدخال HTML للعرض في تذييل الصفحة"
embedded_scss:
text: "CSS المدمج"
+ title: "أدخل CSS مخصَّصة لتسليمها مع النسخة المضمَّنة من التعليقات"
color_definitions:
text: "تحديدات الألوان"
title: "إدخال تحديدات الألوان المخصَّصة (للمستخدمين المتقدمين فقط)"
+ placeholder: |2-
+
+ استخدم ورقة الأنماط هذه لإضافة ألوان مخصَّصة إلى خصائص CSS المخصَّصة.
+
+ مثال:
+
+ %{example}
+
+ يوصى بشدة بوضع بادئة لأسماء الخصائص لتجنُّب تعارضها مع المكوِّنات الإضافية أو الأساسية.
head_tag:
text: "head>"
title: "HTML الذي سيتم إدراجه قبل وسم "
body_tag:
text: "body>"
title: "HTML الذي سيتم إدراجه قبل وسم